Recoil이란? (RecoilRoot, atom, selector 의 사용 방법 in react)

생성됨
Sep 7, 2022 04:17 PM
태그

0. Recoil 은 무슨 기술?

Recoil은 React 프로젝트를 위한 전역 상태관리 라이브러리들 중 하나로, 2020년 5월 Facebook에서 출시하였습니다. (mobx → redux → recoil 순서대로 페이스북에서 만들었습니다) 그렇기에, 다른 라이브러리(Redux, Mobx)와는 달리 React 전용이며 React에 최적화되어 있다고 할 수 있습니다.
props 를 통해서 부모 컴포넌트에서 자식 컴포넌트로 보내줄 수 있는데 왜 전역 상태 라이브러리를 사용해야 할까요? 주고 받을 수는 있지만 코드가 굉장히 복잡해지고 어디서 어떤 data를 받았는지, 내려줬는지 일일히 다 체크해야하기 때문입니다. 그래서 이 문제를 해결하기위해 상태 관리 라이브러리가 나왔습니다.
notion imagenotion image
그림1) 첫번째 그림은 최상단 컴포넌트가 X 내려받을 컴포넌트가 Y 라고 가정했을 때 X → Y 로 데이터가 전달 되려면 저렇게 3번이나 내려가야 값을 전달할 수 있습니다.
그림2) 두 번째의 네모 역할이 상태 관리 라이브러리입니다. 장바구니, 샵 이라고도 많이 불립니다. 필요한 data를 저곳에 넣어 놓고 어디서든 꺼내서 사용하면 됩니다. 저런 식으로 전역적으로 상태를 관리해준다면, 관리하기 편하겠죠? 그래서 사용하는 것입니다.

1. RecoilRoot

recoil state 를 가지는 컴포넌트들이 필요로 하는, atom context 를 가지는 root 입니다. 해당 Root 이하의 컴포넌트는 모두 같은 전역 상태브러리의 값을 쓰겠다는 말이지요.
< App.js >
import TempCelsius from "./TempCelsius" import {RecoilRoot} from 'recoil'; function App() { return ( <RecoilRoot> <TempCelsius /> </RecoilRoot> ); } export default App;
위 코드처럼 RecoilRoot 로 감싸주지 않으면 에러가 납니다. TempCelsius 라는 컴포넌트에서 recoil을 쓴다면, 이를 감싸주는 곳에서 RecoilRoot 를 써줘야 합니다. (절대로 TempCelsius 컴포넌트 안에서 해주는게 아님!)
예시로 TempCelsius라는 컴포넌트를 만들었는데, 안에서는 atom 과 selector 을 열심히 써주고 있습니다.
<RecoilRoot>는 여러개가 같이 존재할 수 있습니다. RecoilRoot 밑에 RecoilRoot를 또 만들어준다면, 두 번째 RecoilRoot에서는 아예 새로운 상태를 가지게 됩니다. (상위 recoil을 무시합니다) atom은 각 루트에 따라 다른 값을 갖게 되는 것입니다.

2. Atom

🎯 컴포넌트끼리 공유 가능한 가장 작은 단위의 state 를 atom
Atom은 상태의 단위입니다.
Atom이 업데이트되면 해당 Atom을 구독하고 있던 모든 컴포넌트들이 새로운 값으로 리렌더링됩니다. 또 여러 컴포넌트에서 같은 Atom을 구독하고 있으면 그 컴포넌트들이 상태를 동일하게 공유합니다.
< 짧은 예시 >
import { atom } from "recoil"; const postingId = atom<string>({ key: "posting/id", default: "", });
Recoil 상태를 구성하는 제일 작은 단위입니다. (위의 예시는 타입스크립트로 들었습니다)
useState 로 만든 상태와 개념적으로 비슷하지만, 다른 컴포넌트와 상태를 공유할 수 있다는 점이 다릅니다.
그런데 이 때 다른 컴포넌트라 함은, 같은 '페이지'안의 다른 컴포넌트입니다. 다른 페이지는 영향을 받지 않습니다.
  • typescript 에서 atom 키워드 뒤에 오는 타입은 atom 의 값의 타입을 말합니다.
  • key 는 atom 마다 고유하게 만들어 줘야 합니다. (key 생성 규칙도 정해봅니다.)
  • default 는 기본 상태를 말합니다.
atom으로 만든 상태를 읽는 모든 컴포넌트는 atom의 상태가 변경되면 rerendering 됩니다.

3. selector

🎯 atom 또는 다른 selector 로 구성한 순수함수
< 짧은 예시 >
import { atom, selector } from 'recoil'; const tempFahrenheit = atom({ key: 'tempFahrenheit', default: 32, }); const tempCelcius = selector({ key: 'tempCelcius', get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9, set: ({set}, newValue) => set( tempFahrenheit, newValue instanceof DefaultValue ? newValue : (newValue * 9) / 5 + 32, ), });
atom 또는 selector 를 기반으로 새롭게 결과를 구성해주는 순수함수입니다.
구독중인 atom 또는 selector 가 업데이트 되면 selector 도 업데이트 됩니다.
  • get 을 이용하여 다른 atom도 구독할 수 있습니다. 이 말은 즉, 해당 atom이 변하면 selector도 변한다는 말입니다.
  • selector을 왜 쓰느냐? getter, setter 을 사용해 전처리후처리를 한 atom을 사용할 수 있기 때문입니다.
  • get 을 통해 전처리한 atom값 을 받아올 수 있습니다.
  • set 을 이용하여 atom값을 후처리 할 수 있습니다.

4. useRecoilState

🎯useState 와 같은 역할입니다.
전역으로 useState을 설정해주는 거라고 생각하시면 편합니다. 다만 여기서는 선언을 하는 것이 아니라, 전역적으로 설정된 atom을 가져오는 역할만 합니다.
const [a, b] = useRecoilState();

5. useRecoilValue

🎯atom을 조회할 때만 사용한다.
const [a, b] = useRecoilState();
가 있다면, a 만을 따로 쓰는 행위라고 생각해도 됩니다.
이런 식으로!

6. useSetRecoilState

🎯atom을 set 할 때 사용한다.
const [a, b] = useRecoilState();
가 있다면, b 만을 따로 쓰는 행위라고 생각해도 됩니다.
이런 식으로!

7. 종합 예시

다음 코드의 예시를 봅니다! 거듭 말하지만 atom은 같은 페이지내의 다른 컴포넌트끼리 상태값이 공유 가능한 것입니다. 상위-하위 컴포넌트로 연결이 되어 있는 상태가 아니라면, RecoilState는 각자 독립적인 state가 되어버립니다.
<Atoms.js>
import { atom, selector } from "recoil"; const tempFahrenheit = atom({ key: 'tempFahrenheit', default: 32, }); const tempCelsius = selector({ key: 'tempCelsius', get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9, set: ({set}, newValue) => set(tempFahrenheit, (newValue * 9) / 5 + 32), }); export {tempFahrenheit, tempCelsius}
< TempCelsius.js >
import {atom, selector, useRecoilState, useRecoilValue} from 'recoil'; import {tempFahrenheit, tempCelsius} from "./Atoms.js" function TempCelsius() { const [tempC, setTempC] = useRecoilState(tempCelsius); const [tempF, setTempF] = useRecoilState(tempFahrenheit); const originTempC = useRecoilValue(tempCelsius); const originTempF = useRecoilValue(tempFahrenheit); const addTenCelsius = () => setTempC(tempC + 10); const addTenFahrenheit = () => setTempF(tempF + 10); return ( <div> Temp (Celsius): {tempC} <br /> Temp (Fahrenheit): {tempF} <br /> <button onClick={addTenCelsius}>Add 10 Celsius</button> <br /> <button onClick={addTenFahrenheit}>Add 10 Fahrenheit</button> <br /> <p>{`originC : ${originTempC} / originB : ${originTempF}`}</p> </div> ); } export default TempCelsius;
<결과>
notion imagenotion image
< 출처사이트 >
'📲 For Front/⚛️ React Knowledge' Related Articles