https://recoiljs.org/ko/docs/introduction/installation
Recoil?
2020년에 Facebook에서 발표한, React 전용 상태관리 라이브러리이다.
Recoil을 통한 상태관리는 아주 간결하다는 장점이 있다.
Redux의 경우
Flux 패턴을 사용하여 안정적으로 상태관리가 가능하지만,
- React 전용 라이브러리가 아니다.
- 초기 세팅이 요구된다.
- 비동기 데이터를 사용하려면 추가적인 라이브러리 설치가 필요하다.
- 많은 양의 **보일러플레이트 코드를 작성해야 한다.
는 단점을 가지고 있기도 하다. Recoil은 이러한 단점을 보완한다.
** Boilerplate Code : 변화 없이 여러 군데에서 반복되는 코드
Why Recoil
- 전역 상태 관리를 컴포넌트 내부의 상태 관리처럼 간단한 get/set 인터페이스로 사용할 수 있도록 boilerplate-free API를 제공한다.
- Concurrent Mode를 비롯한 다양한 React 기능들과 호환이 가능하다.
- 상태 정의에 대한 코드 분할이 가능하다.
- 전역 상태를 사용하고 있는 컴포넌트는 수정 없이 변경된 상태로 교체 가능하다.
- 컴포넌트 수정 없이 동기/비동기 전환이 가능하다.
- 전역 상태의 경우 애플리케이션이 변경되더라도 그대로 유지된다.
Recoil 시작하기
npm install recoil
// or
yarn add recoil
설치 후, 루트 컴포넌트에 RecoilRoot를 추가한다.
import React from 'react';
import {
RecoilRoot,
atom,
selector,
useRecoilState,
useRecoilValue,
} from 'recoil';
function App() {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
);
}
<RecoilRoot ...props>
Recoil hooks를 사용하는 모든 구성 요소의 조상 컴포넌트이다. 여러개의 루트가 존재할 수 있다.
- initializeState?: (MutableSnapshot => void)
- MutableSnapshot을 사용해서 <RecoilRoot>의 상태를 초기화한다.
- 초기 렌더링에 대한 상태를 설정하는 것이지 비동기적인 초기화를 위한 것(useSetRecoilState(), useRecoilCallback())은 아니다.
- override?: boolean
- <RecoilRoot>가 다른 <RecoilRoot>와 중첩된 경우 사용된다.
- true => 해당 루트는 새로운 Recoil 범위를 생성한다.
- false => 해당 루트는 자식 렌더링만 수행하므로, 이 루트의 자식 컴포넌트는 가장 가까운 루트의 Recoil 값에 접근한다.
여러 <RecoilRoot>를 사용하는 경우 각각 독립적인 루트가 된다.
atom은 각 루트에 따라 다른 값을 갖게 되는 것이다. 이러한 동작은 override를 false로 지정하지 않는 한, 루트가 다른 루트에 중첩될 때 동일하게 발생된다.
Recoil 주요 개념
Atoms
atom은 Recoil의 상태를 표현한다. 함수는 쓰기가 가능한 RecoilState 객체를 반환한다.
function atom<T>({
key: string, // atom을 식별하기 위한 고유한 문자열
default: T | Promise<T> | RecoilValue<T>, // 상태 초기 값
effects_UNSTABLE?: $ReadOnlyArray<AtomEffect<T>>,
dangerouslyAllowMutability?: boolean,
}): RecoilState<T>
- Atom이 업데이트되면 각각 구독된 컴포넌트는 리렌더링된다.
- Atoms는 런타임에서 생성될 수도 있다.
- Atoms는 React의 로컬 컴포넌트의 상태 대신 사용할 수 있다.
- 동일한 Atom이 여러 컴포넌트에서 사용되는 경우 모든 컴포넌트는 상태를 공유한다.
// Atoms는 atom 함수를 사용해 생성한다.
const fontSizeState = atom({
// 모든 atoms의 map을 볼 수 있는 API에 사용되는 고유한 key가 필요하다.
// 두 개의 atom이 같은 키를 갖는 것은 오류이기 때문에 키 값은 전역적으로 고유해야 한다.
key: 'fontSizeState',
default: 14,
});
컴포넌트에서 atom을 읽고 쓰려면 useRecoilState라는 훅을 사용한다.
React의 useState와 비슷하지만, 상태가 컴포넌트 간에 공유된다는 차이점이 있다.
아래의 예제에서 버튼을 클릭하면 버튼의 글꼴 크기가 1만큼 증가하며 , fontSizeState atom을 사용하는 다른 컴포넌트의 크기도 같이 변화한다.
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
Click to Enlarge
</button>
);
}
//-------------------------------------------------------------------------------
function Text() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return <p style={{fontSize}}>This text will increase in size too.</p>;
}
자주 사용되는 Hooks - atom
setRecoilState(state)
상태의 값과, 상태의 값을 업데이트하는 setter 함수를 반환한다.
- Recoil 상태를 인수로 받는다는 점을 제외하고는 useState() hook과 비슷하다.
- 상태가 업데이트 되었을 때 리렌더링 된다.
function useRecoilState<T>(state: RecoilState<T>): [T, SetterOrUpdater<T>];
type SetterOrUpdater<T> = (T | (T => T)) => void;
useRecoilValue(state)
주어진 상태의 값을 반환한다. atom을 읽을 때에 사용한다.
function useRecoilValue<T>(state: RecoilValue<T>): T;
useSetRecoilState(state)
Recoil의 상태 값을 업데이트하기 위한 setter 함수를 반환한다.
function useSetRecoilState<T>(state: RecoilState<T>): SetterOrUpdater<T>;
type SetterOrUpdater<T> = (T | (T => T)) => void;
useResetRecoilState(state)
주어진 상태를 default 값으로 리셋하는 함수를 반환한다.
function useResetRecoilState<T>(state: RecoilState<T>): () => void;
Selectors
Selector는 atoms나 다른 selectors를 입력으로 받아들이는 순수함수이다.
쉽게 말하면 기존의 atom을 처리하여 새로운 값을 리턴하거나 수정하는 역할을 수행한다. (computed,,와 같은 역할을 하는 것 같다.)
selectors에 명시한 함수를 통해서 데이터를 계산하고, 최소한의 상태만 atoms로 관리한다.
get 함수만 작성되면 읽기만 가능한 RecoilValueReadOnly 객체를 반환하고, set 함수도 작성되면 쓰기 가능한 RecoilState 객체를 반환한다.
function selector<T>({
key: string,
get: ({
get: GetRecoilValue
}) => T | Promise<T> | RecoilValue<T>,
set?: (
{
get: GetRecoilValue,
set: SetRecoilState,
reset: ResetRecoilState,
},
newValue: T | DefaultValue,
) => void,
dangerouslyAllowMutability?: boolean,
})
- get : 파생된 상태의 값을 평가하는 함수이다.
- 값을 직접 반환하거나 Promise, atom, selector를 반환할 수 있다.
- set? : set이 설정되면 selector는 쓰기 가능한 상태를 반환한다.
- get => 다른 atom이나 selector로부터 값을 가져오는 함수
- set => Recoil 상태의 값을 설정할 때 사용되는 함수
예시)
// 인자로 받은값을 set 하는 방법
const proxySelector = selector({
key: 'ProxySelector',
get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
set: ({set}, newValue) => set(myAtom, newValue),
});
// 조건에 따라 다른 값을 반환하는 방법
const transformSelector = selector({
key: 'TransformSelector',
get: ({get}) => get(myAtom) * 100,
set: ({set}, newValue) =>
set(myAtom, newValue instanceof DefaultValue ? newValue : newValue / 100),
});
상위의 atoms 또는 selectors가 업데이트되면 하위의 selector 함수도 다시 실행된다.
컴포넌트들은 selectors를 atoms처럼 구독할 수 있고, 구독하고 있던 selectors가 변경되면 다시 렌더링된다.
컴포넌트의 관점에서는 Selectors와 atoms가 동일한 인터페이스를 가지기 때문에 서로 대체할 수 있다.
예시) 온도 변환
import {atom, selector, useRecoilState} 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),
});
function TempCelsius() {
const [tempF, setTempF] = useRecoilState(tempFahrenheit);
const [tempC, setTempC] = useRecoilState(tempCelsius);
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>
</div>
);
연습하기
그냥,,,빵먹고 싶어서 실습한 사람,,,,총 2만 2천언 대것습니다,,.,
// App.js
import { useRecoilState, useRecoilValue } from "recoil";
import { donutCnt, saltyCnt, totalPriceSelector } from "./recoil";
function App() {
const [salty, setSalty] = useRecoilState(saltyCnt);
const [donut, setDonut] = useRecoilState(donutCnt);
const totalPrice = useRecoilValue(totalPriceSelector);
return (
<div className="App" style={{ padding: "20px" }}>
<h3>소금빵이 먹고싶어요</h3>
<h4>소금빵은 개당 3000원이에요@@</h4>
<h4>도넛은 개당 2500원이에요@@</h4>
<div style={{ display: "flex", gap: "15px" }}>
<h5>소금빵 몇개?</h5>
<input
onChange={(e) => {
setSalty(e.target.value);
}}
type="number"
placeholder="갯수를 입력하세요"
/>
</div>
<div style={{ display: "flex", gap: "15px" }}>
<h5>도넛 몇개?</h5>
<input
onChange={(e) => setDonut(e.target.value)}
type="number"
placeholder="갯수를 입력하세요"
/>
</div>
<div style={{ display: "flex", gap: "15px" }}>
<h5>
소금빵 {salty}개와 도넛 {donut}개의{" "}
</h5>
<h5>총 가격이에요 : {totalPrice} </h5>
</div>
</div>
);
}
export default App;
// recoil.js
import { atom, selector } from "recoil";
export const saltyCnt = atom({
key: "saltyCnt",
default: 0,
});
export const donutCnt = atom({
key: "donutCnt",
default: 0,
});
export const totalPriceSelector = selector({
key: "totalPrice",
get: ({ get }) => {
const salty = get(saltyCnt);
const donut = get(donutCnt);
return (salty * 3000 + donut * 2500);
},
});
'Web > React' 카테고리의 다른 글
SWR 활용하기 with React, TypeScript (2) | 2024.11.29 |
---|---|
[React] i18n으로 다국어 지원하기 (i18next, react-i18next) (0) | 2023.10.22 |
[React] React Query (0) | 2023.01.18 |
[React] Redux (0) | 2022.12.19 |
[React] ajax (0) | 2022.12.15 |
댓글