작고 React스러운
: 빠르고 유연한 공유되는 상태
데이터 흐름 그래프
: 파생 데이터와 비동기 쿼리는 순수 함수와 효율적인 구독으로 관리됨
교차하는 앱 관찰
: 코드 분할을 손상시키지 않고 앱 전체의 모든 상태 변경을 관찰하여 지속성, 라우팅, 시간 이동 디버깅 또는 실행 취소를 구현함
페이스북에서 만듦!
호환성 및 단순함을 이유로 외부의 글로벌 상태관리 라이브러리보다는 React 자체에 내장된 상태 관리 기능을 사용하는 것이 가장 좋다. 그러나 React는 다음과 같은 한계가 있다.
Recoil은 직교(orthogonal)하지만 본질적인 방향 그래프를 정의하고 React 트리에 붙인다. 상태 변화는 이 그래프의 뿌리(atoms)로부터 순수함수(selectors)를 거쳐 컴포넌트로 흐르며, 다음과 같은 접근 방식을 따른다.
Recoil을 사용하면 atoms (공유 상태)에서 selectors (순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow graph를 만들 수 있다. Atoms는 컴포넌트가 구독할 수 있는 상태의 단위다. Selectors는 atoms 상태값을 동기 또는 비동기 방식을 통해 변환한다.
npm install recoil
import { RecoilRoot } from "recoil";
function App() {
return (
<div className="App">
<RecoilRoot>
</RecoilRoot>
</div>
);
}
export default App;
src > components > store.js
import { atom } from "recoil";
export const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});
src > components > FontButton.jsx
import { useRecoilState } from "recoil";
import { fontSizeState } from "./store";
export default function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
//앞에서 atom으로 감싸준 뿌리 root 상태 값 넣어줌 (fontSizeState)
return (
<button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
Click to Enlarge
</button>
);
}
src > components > Text.jsx
import { useRecoilState } from "recoil";
import { fontSizeState } from "./store";
export function Text() {
const [fontSize] = useRecoilState(fontSizeState);
//font의 값을 꺼내오고,
return <p style={{fontSize}}>This text will increase in size too.</p>;
//p의 스타일로 폰트에 넣어줌
}
App.js
import { RecoilRoot } from "recoil";
import FontButton from "./components/RecoilExample/FontButton";
import Text from "./components/RecoilExample/Text";
function App() {
return (
<div className="App">
<RecoilRoot>
<FontButton />
<Text />
</RecoilRoot>
</div>
);
}
export default App;
버튼에도 fontsize가 들어있고, text에도 fontsize가 들어있다. 두가지 컴포넌트는 서로 다르고, provider로 감싸있는 거지만, 굉장히 간단한 atom을 하나 추가해 놓고 useState와 유사한 useRecoilState라는 훅으로 감싸서 간단하게 처리함
atom : 기본 state를 주고, 다른곳에서 쓸 때는 useRecoilState 사용
Selector는 atoms나 다른 selectors를 입력으로 받아들이는 순수 함수(pure function)다. 상위의 atoms 또는 selectors가 업데이트되면 하위의 selector 함수도 다시 실행된다. 컴포넌트들은 selectors를 atoms처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 다시 렌더링된다.
src > components > store.js
import { atom, selector } from "recoil";
export const fontSizeState = atom({
key: "fontSizeState",
default: 14,
});
export const fontSizeLabelState = selector({
key: "fontSizeLabelState",
get: ({ get }) => {
const fontSize = get(fontSizeState);
const unit = "px";
return `${fontSize}${unit}`;
},
});
src > components > FontButton.jsx
import { useRecoilState, useRecoilValue } from "recoil";
import { fontSizeLabelState, fontSizeState } from "./store";
export default function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
const fontSizeLabel = useRecoilValue(fontSizeLabelState);
return (
<>
{" "}
<div>Current font size: {fontSizeLabel}</div>
<button
onClick={() => setFontSize((size) => size + 1)}
style={{ fontSize }}
>
Click to Enlarge
</button>
</>
);
}
atom이나 다른 셀렉터의 값을 보며 값이 바뀌면 자기도 바뀜
값이 바뀌는데 무엇인가 더하거나(여기예제에선 px라는 unit을 더해줌), 값을 변경을 취해서 보여주는 것
selector는 주로 값을 꺼내서쓴다!
src > components > CounterStore.js
import { atom } from "recoil";
export const textState = atom({
key: "textState", // unique ID (with respect to other atoms/selectors)
default: "", // default value (aka initial value)
});
src > components > CharacterCounter.jsx
import { useRecoilState, useRecoilValue } from "recoil";
import { charCountState, textState } from "./CounterStore";
export default function CharacterCounter() {
return (
<div>
<TextInput />
<CharacterCount />
</div>
);
}
function TextInput() {
const [text, setText] = useRecoilState(textState);
const onChange = (event) => {
setText(event.target.value);
};
return (
<div>
<input type="text" value={text} onChange={onChange} />
<br />
Echo: {text}
</div>
);
}
function CharacterCount() {
const count = useRecoilValue(charCountState);
return <>Character Count: {count}</>;
}
src > components > FontButton.jsx
import { useRecoilState, useRecoilValue } from "recoil";
import { textState } from "./CounterStore"; //추가
import { fontSizeLabelState, fontSizeState } from "./store";
export default function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
const fontSizeLabel = useRecoilValue(fontSizeLabelState);
const [text] = useRecoilState(textState); //추가
return (
<>
{" "}
<div>Current font size: {fontSizeLabel}</div>
<button
onClick={() => setFontSize((size) => size + 1)}
style={{ fontSize }}
>
Click to Enlarge {text} //추가
</button>
</>
);
}
-상태 공유를 위해 상위 요소까지 끌어올리면 거대한 트리가 재렌더 될 가능성 있음
- Context는 단일 값만 저장할 수 있고, 자체 Consumer를 가지는 여러값의 집합은 담을 수 없음
- 최상단(state가 존재하는 곳)부터 트리의 잎(state가 사용되는 곳)까지의 코드 분할이 어려움
atoms(상태) : 고유한 키를 가짐
selectors(순수함수): atoms이나 selectors를 입력으로 받음
components
Tree를 벗어난 3D shared state