개인 프로젝트에서 Recoil을 이용해서 만들어야 하는 미션이 있다.
Recoil 이라는 개념이 생소하기 때문에 정리하면서 개념을 익혀보았고 프리온보딩 강의 중 Recoil에 관련된 내용도 추가적으로 정리했다.
Recoil 은 본질적인 방향 그래프를 정의하고 React 트리에 붙인다. 상태 변화는 이 그래프의 뿌리(atoms) 로 부터 순수함수(selectors)를 거쳐 컴포넌트로 흐른다.
Recoil을 사용하면 atoms(공유 상태)에서 selectors(순수 함수)를 거쳐 React 컴포넌트로 내려가는 data-flow graph 를 만들 수 있다.
Atoms 는 상태의 단위이며, 업데이트와 구독이 가능하다. atom이 업데이트 되면 각각의 구독된 컴포넌트는 새로운 값을 반영하여 다시 렌더링 된다.
atoms는 런타임에서 생성될 수도 있다. Atoms 는 React의 로컬 컴포넌트의 상태 대신 사용할 수 있다. 동일한 atom 이 여러 컴포넌트에서 사용되는 경우 모든 컴포넌트는 상태를 공유한다.
Atoms 는 atom 함수를 사용해 생성한다.
const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});
Atoms 는 디버깅, 지속성 및 모든 atoms 의 map 을 볼 수 있는 특정 고급 API 에 사용되는 고유한 키가 필요하다. 두개의 atom 이 같은 키를 갖는 것은 오류이므로 전역적으로 고유해야한다. React 컴포넌트의 상태처럼 기본값도 갖는다.
컴포넌트에서 atom 을 읽고 쓰려면 useRecoilState 이라는 훅을 사용한다. React 의 useState 와 비슷하지만 컴포넌트 간에 공유 될 수 있다는 차이가 있다.
function FontButton() {
const [fontsize, setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick = {() => setFontSize((size) => size + 1) style={{fontSize}}>
Click to Here
</button>
)
}
버튼을 클릭하면 버튼 글꼴의 크기가 1만큼 증가하여 fontSizeState atom 을 사용하는 다른 컴포넌트의 글꼴 크기도 같이 변화한다.
function Text() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return <p style={{fontSize}}>This text will increase in size too.</p>;
}
Selector 는 atoms나 다른 selector를 입력으로 받아들이는 순수함수 이다.
순수함수 : 부수효과가 없는 함수, 어떤 함수에 동일한 인자를 주었을 때 항상 같은 값을 리턴하는 함수, 외부 상태를 변경하지 않는 함수
상위의 atoms 또는 selectors 가 업데이트 되면 하위의 selectors 함수도 다시 실행된다. 컴포넌트들은 selectors를 atoms 처럼 구독할 수있고 selectors 가 변경되면 컴포넌트들도 다시 렌더링 된다.
Selectors 는 상태를 기반으로 하는 파생 데이터를 계산하는데 사용된다. 최소한의 상태 집합만 atoms 에 저장하고 모든 파생되는 데이터는 selectors 에 명시한 함수를 통해 효율적으로 계산함으로써 쓸모없는 상태의 보존을 방지한다.
Selectors 는 어떤 컴포넌트가 자신을 필요로하는지, 또 자신은 어떤 상태에 의존하는 지를 추적하기 때문에 이러한 함수적인 접근 방식을 매우 효율적으로 만든다.
컴포넌트의 관점에서 보면 selectors 와 atoms 는 동일한 인터페이스를 가지므로 서로 대체할 수 있다.
Selectors 는 selector 함수를 통해 정의한다.
const fontSizeLabelState = selector({
key : 'fontSizeLabelState',
get : ({get}) => {
const fontSize = get(fontSizeState);
const unit = 'px';
return `${fontSize}${unit}`;
}
});
get 속성은 계산될 함수다. 전달되는 get 인자를 통해서 atoms와 다른 selectors에 접근할 수 있다.
다른 atoms 나 selector 에 접근하면 자동으로 종속 관계가 생성되므로 참조했던 다른 atom 이나 selectors 가 업데이트 되면 이 함수도 다시 실행된다.
이 fontSizeLabelState 예시의 selector 는 fontSizeState 라는 하나의 atom에 의존성을 갖는다. 이 selector는 fontSizeState를 입력으로 사용하고 형식화된 글꼴 크기 레이블을 출력으로 반환하는 순수 함수처럼 동작한다.
Selectors는 useRecoilValue()를 사용해 읽을 수 있다.
useRecoilValue() 는 하나의 atom 이나 selector 를 인자로 받아 대응하는 값을 반환한다.
fontSizeLabelState selector 는 writable 하지 않기 때문에 useRecoilState()를 이용하지 않는다.
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>
</>
);
}
버튼를 클릭하면 버튼의 글꼴 크기가 증가하는 동시에 현재 글꼴 크기를 반영하도록 글꼴 크기 레이블을 업데이트하는 두 가지 작업이 수행된다.
리코일은 React를 위한 상태 관리 라이브러리 이므로 React 가 설치되어있어야 한다.
npx create-react-app 앱이름
#npm
npm install recoil
#yarn
yarn add rec
recoil 의 상태를 사용하는 컴포넌트는 부모 트리 어딘가에 RecoilRoot 가 필요하다. 루트 컴포넌트가 RecoilRoot 를 넣기에 가장 적합한 장소이다.
import React from 'react';
import {
RecoilRoot,
atom,
selector,
useRecoilState,
useRecoilValue,
} from 'recoil';
function App() {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
);
}
CharacterCounter 컴포넌트를 구현해 보자.
Atom 은 상태(state)의 일부를 나타낸다. Atoms 는 어떤 컴포넌트에서나 읽고 쓸수 있다. atom 의 값을 읽는 컴포넌트들은 암묵적으로 atom 을 구독한다. atom 에 어떤 변화가 있으면 구독하는 모든 컴포넌트들이 재랜더링 된다.
const textState = atom({
key: 'textState', // unique ID (with respect to other atoms/selectors)
default: '', // default value (aka initial value)
});
컴포넌트가 atom을 읽고 쓰게 하기 위해서는 useRecoilState()를 아래와 같이 사용한다.
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>
);
}
Selector 는 파생된 상태의 일부를 나타낸다. 파생된 상태를 어떤 방법으로든 주어진 상태를 수정하는 순수 함수에 전달된 상태의 결과물로 생각할 수 있다.
const charCountState = selector({
key: 'charCountState', // unique ID (with respect to other atoms/selectors)
get: ({get}) => {
const text = get(textState);
return text.length;
},
});
우리는 useRecoilValue() 훅을 사용해서 charCountState 값을 읽을 수 있다.
function CharacterCount() {
const count = useRecoilValue(charCountState);
return <>Character Count: {count}</>;
}
위와같은 경우를
아래와 같은 식으로 단축해서 쓸수있다.