- common(컴포넌트, 이후 디자인시스템이 될 컴포넌트들)이 있고,
- 그 외에 특정한 기능을 타고 만들어지는 컴포넌트들은 feature 단위로 만들어집니다.
- feature 내부에 그 기능을 위한 components, hooks, util 등이 모두 존재합니다.
- 컴포넌트들을 조합하고, 페이지만을 위한 컴포넌트인 페이지 레벨 컴포넌트의 경우 page 에서 관리됩니다.
- 해당 페이지만을 위한, 해당 페이지 구성을 위해 필요한 컴포넌트들은 페이지레벨 컴포넌트라고 보고 page 하위에 작성합니다.
src/
├── common/ # 공통으로 사용되는 컴포넌트, 유틸리티 등 (향후 디자인시스템)
│ └── components/ # 공통 컴포넌트
│
├── pages/ # 페이지 컴포넌트들
│ ├── Home/ # 홈 페이지
│ └── Game/ # 게임 페이지
│
└── feature/ # 기능별 모듈
└── WordleGame/ # 워들 게임 관련 기능
├── components/ # 워들 관련 컴포넌트들
├── services/ # 워들 관련 서비스
│ ├── GameService.ts
│ ├── StorageService.ts
│ └── DictionaryService.ts
└── state/ # 워들 상태 관리
├── actions.ts
└── state.ts
단어 박스 하나하나인 charbox, 단어 길이만큼 묶여 wordbox, 모두 묶여서 wordleBoard 형태로 단계로 구성했습니다. 단어 입력 값이 keyboard 컴포넌트와 연동 되어야 하므로 context api를 먼저 떠올렸으나, 키 입력 시 마다 상태가 변화하고, 이가 여러 컴포넌트에 영향을 주기 때문에 atomic 하게 상태 관리가 필요하다고 판단되어 jotai를 사용하였습니다.
키보드의 경우 게임이 확장된다고 가정해봤을 떄, UI가 동일한 상태로 사용될 것이라 판단하여, 스타일 정도의 다양성만 받는 형태로 common으로 만들었고, 스타일과 기타 wordle 만을 위한 기능을 포함하여 wordle feature 내에서 wordle만을 위한 keypad가 만들어집니다. feature 내부에 있으므로 해당 feature에 대한 기능을 마음껏 포함시켜도 됩니다.
api 호출의 경우 사전에 있는 단어인지 확인하는 용도이므로, 한번만 fetch 해온 후에는 결과가 달라지지 않으므로 (사전이 바뀌지 않는 이상) 플레이타임 내에서는 변화가 없다고 판단하여 tanstack-query를 도입하여 캐싱을 사용하고자 하였고, staleTime을 Infinity로 두어 한번 캐싱된 내용을 계속 사용하도록 하였습니다.
동작 구현 이후 공통되는 목적과 기능을 기반으로 로직들을 모아 서비스 클래스로 분리하였습니다. 그 과정에서 단순히 데이터로써 참조하며 값의 변경이 UI나 로직을 트리거 하지 않는 단순 참조용도의 값들은 상태로 사용하지 않도록 atom에서 제거하였습니다. game service의 경우에는 게임별로 하나만 선언되어 관리할 수 있도록 싱글톤 패턴을 적용하였습니다. 처음에는 context API로 전역에 공유되도록 고민했으나, atom에서 참조할 수 없는 등의 이유가 있어서 더 범용성 있게 접근할 수 있도록 클래스로 구현하였습니다.
렌더링 최적화를 고려하여 컴포넌트를 작성하였습니다.
jotai를 사용하여 atomic 하게 상태가 관리되며, 메모이제이션을 통해 변화되는 요소만 리렌더링 될 수 있도록 하였습니다.