Recoil은 리액트 전용 상태 관리 라이브러리이다.
리액트에서는 본인 또는 부모의 상태에만 접근 가능하다는 점 때문에 코드 복잡도가 올라가거나 불필요한 리렌더링이 발생하기도 한다. Recoil은 리액트스러운 메커니즘을 유지하면서 이러한 단점을 극복하기 위해 등장했다고 한다.
Recoil의 가장 작은 단위인 아톰(atoms)부터 순수함수인 셀렉터(selectors)까지 하나씩 알아보자.
📦 아톰 (atoms)
아톰은 상태(state)의 단위이다. 1 아톰 1 상태라고 생각하면 된다.
컴포넌트는 아톰의 변화를 구독(subscribe)할 수 있다. 아톰이 업데이트 되면, 아톰을 구독하고 있던 컴포넌트들은 새로운 아톰의 값을 반영하여 리렌더(re-render)된다.
만약 아톰을 여러 컴포넌트에서 사용하고 있더라도, 상태는 문제없이 공유된다.
아톰은 recoil에서 atom 함수를 가져와서 만들면 된다. 각 아톰은 앱 전체에서 고유한 key를 가져야 한다는 점을 주의하자. default에는 초기값을 넣어주자.
import { atom } from 'recoil';
const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});
컴포넌트에서 아톰의 값을 읽거나 수정하고 싶다면, useRecoilState 훅을 사용하면 된다. 리액트의 useState와 사용하는 방법이 매우 유사한데, 컴포넌트 트리 구조에 관계없이 다른 점은 어느 컴포넌트에서나 사용할 수 있다는 점이 다르다.
import { useRecoilState } from 'recoil';
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
Click to Enlarge
</button>
);
}
만약 버튼을 클릭해서 폰트 사이즈를 증가시켰다면 fontSize 아톰을 동일하게 사용하고 있는 다른 컴포넌트에도 이 내용이 반영된다.
function Text() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return <p style={{fontSize}}>This text will increase in size too.</p>;
}
🖍 셀렉터(selector)
셀렉터는 아톰이나 다른 셀렉터를 받는 순수함수이다. 아톰이나 셀렉터가 변경되면, 이 셀렉터 함수가 다시 평가된다.
컴포넌트는 아톰을 구독할 수 있듯이 셀렉터도 구독해둘 수 있다. 셀렉터를 구독해두면, 셀렉터가 변경되었을 때 마찬가지로 리렌더된다.
셀렉터는 상태값으로 뭔가 계산해낼 때 사용할 수 있어서 중복된 상태를 마구마구 생산하는 것을 방지해준다. 딱 필요한 상태만 아톰으로 만들어두고, 셀렉터로 아톰을 적절하게 활용하면 된다.
셀렉터는 recoil에서 selector함수를 가져와서 만들면 된다. 계산하는 내용을 get 메서드에다가 작성하면 되는데, 다른 아톰이나 셀렉터를 가져오려면 인자로 { get } 을 받아서 get()으로 접근하면 된다. get()으로 접근하고 있는 아톰이나 셀렉터가 업데이트 되면, 이 셀럭터를 구독하고 있는 컴포넌트도 당연히 리렌더된다.
import { selecotr } from 'recoil';
const fontSizeLabelState = selector({
key: 'fontSizeLabelState',
get: ({get}) => {
const fontSize = get(fontSizeState);
const unit = 'px';
return `${fontSize}${unit}`;
},
});
셀렉터를 가져오려면 useRecoilValue를 쓰면 된다. 아톰이나 셀렉터를 인자로 넣어주면 된다. 셀렉터의 경우 변경할 수 없기 때문에 useRecoilState로 가져와서 사용할 수는 없다. 반대로 아톰의 경우 변경하려는 것이 아니라면 useRecoilValue로 가져와서 쓸 수 있다.
import { useRecoilState, useRecoilValue } from 'recoil';
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
const fontSizeLabel = useRecoilValue(fontSizeLabelState);
return (
<>
<div>Current font size: {fontSizeLabel}</div>
<button onClick={() => setFontSize(fontSize + 1)} style={{fontSize}}>
Click to Enlarge
</button>
</>
);
}
하나의 셀렉터로 필요한 여러 개의 값을 한번에 리턴해줄 수도 있다.
const todoListStatsState = selector({
key: 'todoListStatsState',
get: ({get}) => {
const todoList = get(todoListState);
const totalNum = todoList.length;
const totalCompletedNum = todoList.filter((item) => item.isComplete).length;
const totalUncompletedNum = totalNum - totalCompletedNum;
const percentCompleted = totalNum === 0 ? 0 : totalCompletedNum / totalNum * 100;
return {
totalNum,
totalCompletedNum,
totalUncompletedNum,
percentCompleted,
};
},
});
참고자료
'FrontEnd+' 카테고리의 다른 글
nestjs 네스트 서버 기초 하루 만에 끝내기 (1) | 2022.04.13 |
---|---|
Less 입문 - 기초 문법 알아보기 (0) | 2022.01.12 |
웹 앱 매니페스트 manifest.json 를 작성해보자. (2) | 2021.10.27 |
[번역] 리액트의 스케줄링 (Pull 방식 vs Push 방식) (0) | 2021.10.22 |
월간 키워드 - 2021년 10월 (0) | 2021.10.08 |