지난 프로젝트 때 멘토님이 언급해주셨던 상태관리 라이브러리인 Recoil을 사용해보려고 한다!
Redux 보다 훨씬 간결하고 사용하기 쉽다고 하셔서 이번 토이 프로젝트 때 적용하고자 한다.
기존에 많은 사람들이 사용중인 상태관리 라이브러리 Redux, MobX는 그저 단순 자바스크립트 상태관리 라이브러리 일뿐, 리액트를 위한 상태관리 라이브러리는 아니었다.
2020년 5월, React europe 2020에서 페이스북이 발표한 리액트 상태관리 라이브러리 Recoil은 최대한 Reactish 하게 문법이 이루어져있으며 오직 리액트를 위한 상태관리 라이브러리 이다.
Redux나 MobX를 사용하기 위해서는 store 구성을 위한 package.json에 있는 CRA 설정부터 action, reducer 등등의 기본적인 보일러 플레이트를 설계해야하고, 그에 따른 코드들을 적어주어야 비로소 환경설정이 끝나게 된다.
하지만 Recoil은 위와같은 과정은 전혀 없었으며 그저 RecoilRoot를 최상위로 감싸준다면 사실상 프로젝트 설정은 모두 끝난셈인 것이다!
TypeScript + React + Recoil을 이용한 토이프로젝트를 진행 중인 것을 참고해주시기 바랍니다!
npm install recoil
recoil 상태를 사용하는 컴포넌트는 부모 트리 어딘가에 나타나는 RecoilRoot 가 필요하다.
루트 컴포넌트가 RecoilRoot를 넣기에 가장 좋은 장소다.
나의 경우는 아래와 같다.
import React from 'react';
//...
import Mypage from './pages/Mypage';
import DetailPage from './pages/DetailPage';
import KaKaoLoginPage from './components/Login/KaKaoLogin';
import Check from './components/Write/SeriesArea';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { CookiesProvider } from 'react-cookie';
const App: React.FC = () => {
return (
<>
<CookiesProvider>
{/* 사용된 곳 */}
<RecoilRoot>
<BrowserRouter>
<Routes>
<Route path='/' element={<MainPage />}></Route>
<Route path='/:nickname' element={<PostPage />}></Route>
<Route path='/:nickname/series' element={<SeriesPage />}></Route>
<Route path='/:nickname/:postId' element={<DetailPage />}></Route>
<Route path='/login' element={<LoginPage />}></Route>
<Route path='/signup' element={<SignupPage />}></Route>
<Route path='/write' element={<WritePage />}></Route>
<Route path='/mypage' element={<Mypage />}></Route>
<Route path='/oauth/callback/kakao' element={<KaKaoLoginPage />} />
</Routes>
</BrowserRouter>
</RecoilRoot>
</CookiesProvider>
</>
);
};
export default App;
atom은 Recoil에서 상태를 정의하여 사용하기 위한 방법입니다.
atom을 이용하여 상태를 정의할 때는 고유값(key)과 기본값(default)을 설정해줘야한다.
나의 경우는 회원가입 폼에 한번 적용시켜보았다.
// signup.ts
import { atom } from 'recoil';
//회원가입 폼 인터페이스
export interface ISignupForm {
name: string;
nickname: string;
email: string;
password: string;
pwdCheck: string;
}
// 회원가입 폼
export const inputsState = atom<ISignupForm>({
key: 'inputsState', //유니크한 값
default: { //기본 값
name: '',
nickname: '',
email: '',
password: '',
pwdCheck: '',
},
});
위와같이 atom으로 지정하면 전역적으로 inputState에 접근할 수 있게 된다.
key는 고유한 값이다.
다른 atom과 중복되면 안된다.
이제 컴포넌트에서는 어떻게 접근하는지 알아보자!
//Signup.tsx
import { useRecoilState, useResetRecoilState } from 'recoil';
//...~
const Signup = () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [form, setForm] = useRecoilState(inputsState);
//....
return (
//...
)
};
atom으로 정의한 상태는 아래의 Hook을 이용하여 사용할 수 있다.
나의 경우는 useRecoilState 를 사용했다.
하지만 그 외에도 여러가지가 있다.
그 중 내가 생각했을 때 유용했던 것을 알아보자.
const [recoilCounter, setRecoilCounter] = useRecoilState(recoilCounterState);
const recoilCounterValue = useRecoilValue(recoilCounterState);
const setRecoilCounter = useSetRecoilState(recoilCounterState);
const resetRecoilCounter = useResetRecoilState(recoilCounterState);
useRecoilState
atom의 상태를 구독.
useState Hook과 같이 배열의 첫번째 파라미터로 상태, 두번째 파라미터로 상태에 대한 setter 함수를 반환.
useRecoilValue
setter 함수 없이 atom의 상태만 반환.
useSetRecoilState
atom 상태 없이 setter 함수만 반환.
useResetRecoilState
atom 상태를 default 상태로 reset.
Recoil에서 selector는 순수 함수이며 파생된 상태(derived state)라고 정의하고 있다.
파생된 상태는 다른 데이터에 의존하는 동적인 데이터를 만들 수 있는 개념이다.
하나의 상태를 순수 함수인 selector에 전달하여 반환 받은 결과물에 대해 파생된 상태로 볼 수 있다.
파생된 상태에 대한 예시
selector는 다음과 같이 구성하여 사용한다.
export const recoilTodoSelector = selector({
key: 'recoilTodoSelector',
get: ({ get }) => {
return get(recoilTodoState);
},
set: ({ set }, value: CommonState)=> {
set(recoilTodoState, value);
}
});
key
내부적으로 selector를 식별하는 고유한 값.
get
파생된 상태를 반환.
atom이나 selector 또는 Promise를 반환.
selector를 구성시
get 함수만 설정하면 읽기만 가능한 RecoilValueReadOnly 객체를 반환하고 userRecoilValue Hook으로만 상태 조회가 가능하다.
반면에 get과 set 함수를 모두 설정하면 읽기/쓰기가 가능한 RecoilState 객체를 반환하고 useRecoilState Hook을 사용할 수 있다.
나의 경우 아직 사용하지 않았다!
Recoil을 사용하다가 전역상태가 새로고침 하면 초기화되는 현상이 발생했다.
그래서 찾아보던 중 Recoil-persist를 사용하면 이전처럼 새로고침을 해도 recoil state가 날아가지고 않고 sessionStorage 또는 localStorage에 보관된다. 아무런 설정도 해주지 않으면 key는 "recoil-persist", 저장소는 localStorage에 기본적으로 저장된다.
npm i recoil-persist
// currentUser.ts
import { atom } from 'recoil';
//persist 적용
import { recoilPersist } from 'recoil-persist';
const { persistAtom } = recoilPersist();
// 로그인한 유저 정보 인터페이스
export interface CurrentUser {
userId: number | null;
name: string;
nickname: string;
email: string;
profile: string;
}
//Recoil-persist를 적용시키려면
//아래의 effects_UNSTABLE을 적어주어야 한다.
export const curUser = atom<CurrentUser>({
key: 'curUser',
default: {
userId: null,
name: '',
nickname: '',
email: '',
profile: '',
},
effects_UNSTABLE: [persistAtom],
});
아직 selector에 대해선 정확하게 뭔지 모른다.
추후에 정리하도록 하자.