리액트는 단방향으로 바인딩하는 라이브러리이다. 부모에서 자식 방향으로만 프로퍼티를 전달할 수 있다. 큰 프로젝트를 진행한다면 전역 상태 관리 라이브러리를 사용하는 게 매우 보편적이다.
프로미서 또한 프로젝트가 커짐에 따라 전역 상태 관리가 필요해졌다. Atomic Design 패턴을 사용하다보니 컴포넌트 간 부모-자식 관계가 많아졌고, 최상단에서 만든 state를 자식 컴포넌트까지 공유하기 위해 불필요하게 중복되는 코드가 생겼다. 때문에 전역 상태 관리 라이브러리를 사용하게 됐고, 그 중 Recoil을 채택했다.
현존하는 수많은 상태 관리 라이브러리 중 Recoil을 채택한 이유, 그리고 Recoil을 어떻게 프로젝트에 사용했는지 적어보았다.
리액트를 사용하는 사람들은 hook 형태를 사용하는 것에 익숙하다. Recoil은 기존의 useState
의 형태와 매우 비슷한 useRecoilValue
, useSetRecoilState
, useRecoilState
등의 hook을 사용한다. 이 세 개의 이름이 비슷비슷해서 헷갈리지만…
selector를 통해 state를 가공하고 그 값을 받아올 수 있으며, 비동기 처리도 가능하다. 이 부분은 사실상 API 통신을 위해 사용하는 것인데, React Query에서 캐싱과 비동기 처리를 해주기 때문에 이번 프로젝트에서는 사용하지 않았다.
그룹을 생성하고 그룹원을 초대하는 Group Maker
컴포넌트의 구조이다. Round Element
에서 발생하는 이벤트를 Group Maker
에서 감지하고 상태를 업데이트 해야 한다.
Atom은 스토어와 유사한 개념으로, 상태의 단위이다. Atom이 업데이트 되면 해당 atom을 구독하고 있던 모든 컴포넌트들의 state가 리렌더된다.
Atom은 키로 구분한다. 여기서는 selectedFriends를 키로 등록했다.
import { atom } from "recoil";
export const selectedFriendsAtom = atom<number[]>({
key: "selectedFriends",
default: [],
});
RoundElement에서 useRecoileState
를 사용했다. useState
와 유사한 형태로, 선언해준 atom을 인자로 넣어주면 된다. State 값만 불러올 땐 useRecoilValue
를, setter만 불러올 땐 useSetRecoilState
를 사용한다.
RoundElement에서 Change 이벤트가 발생했을 때(체크박스 상태가 변경됐을 때), 체크박스의 상태에 따라 state에 아이디를 등록하거나 삭제하는 코드이다.
const [selectedFriends, setSelectedFriends] =
useRecoilState(selectedFriendsAtom);
const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let newSelected = [...selectedFriends];
const targetId = Number(e.target.id);
if (!e.target.checked) {
newSelected = newSelected.filter((value) => value !== targetId);
} else {
newSelected.push(targetId);
}
setSelectedFriends(newSelected);
};
GroupMaker
에서 이 데이터가 필요하므로 다음과 같이 상태값을 불러와 사용할 수 있다.
const selectedFriends = useRecoilValue(selectedFriendsAtom);
다크모드는 한 번 설정을 바꿨을 때 유저가 다시 바꾸지 않는 이상 새로고침해도 유지되어야 한다. 또한 전역적으로 적용돼야 하는 값이다. 따라서 recoil-persist
를 사용하여 localStorage에 다크모드 상태를 저장했다.
recoil-persist
를 설치한 뒤, 기존의 atom을 선언하는 방식에 Persist에 관한 세 줄만 더 추가하면 된다.
import { atom } from "recoil";
import { recoilPersist } from "recoil-persist";
const { persistAtom } = recoilPersist();
export const darkModeState = atom({
key: "darkmode",
default: false,
effects_UNSTABLE: [persistAtom],
});
effects_UNSTABLE
은 불안정하다는 뜻이다. Recoil의 단점 중 하나는 세상에 나온지 얼마 되지 않았기 때문에 아직 미흡한 기능들이 있다는 것이다. persist 기능 외에도 hook 이름 뒤에 _UNSTABLE
이 붙는 것은 기능이 온전치 못하다는 것이므로 알아두자.