React 애플리케이션 상태 관리를 위한 라이브러리
Facebook에서 개발한 상태 관리 라이브러리로 atom 이라는 단위로 상태를 정의
하고 이를 이용해 컴포넌트 사이에서 데이터를 공유하며 상태를 업데이트한다.
간편한 상태관리: 간편하게 상태를 정의하고 관리할 수 있다.
최적화된 리렌더링: 내부적으로 최적화되어 있기 때문에 필요한 경우에만 리렌더링된다.
서로 다른 selector가 같은 atom을 참조하고 setter의 결과로 원본 atom에 변화가 있더라도, 또다른 selector가 참조하고 있는 필드가 변경되지 않았다면 recoil이 이를 판단해서 영리하게 리렌더링을 일으키지 않는다.
복잡한 애플리케이션에 적합: 복잡한 상태 관리를 효과적으로 다루는데 적합하다.
: 앱의 상태를 정의하는 단위로 atom 함수를 통해 생성한다. atom의 값을 읽는 컴포넌트들은 암묵적으로 atom을 구독하기 때문에 atom에 어떤 변화가 있으면 그 atom을 구독하는 모든 컴포넌트가 재 렌더링 되는 결과가 발생한다.
기본형
function atom<T>({ key: string, // 내부적으로 atom을 식별하는데 사용되는 고유한 문자열 default: T | Promise<T> | RecoilValue<T>, // atom의 초깃값 또는 Promise 또는 동일한 타입의 값을 나타내는 다른 atom이나 selector }): RecoilState<T>
ex)
import { atom } from "recoil";
const userState = atom({
key : 'userState',
default : null,
})
: 상태 변화를 만들어 내는 함수로 다른 atom이나 selector로부터 값을 계산한다.
기본형
function selector<T>({ key: string, // 내부적으로 atom을 식별하는데 사용되는 고유한 문자열 get: ({ get: GetRecoilValue // selector는 읽기만 가능한 상태를 반환한다. }) => T | Promise<T> | RecoilValue<T>, set?: ( // selector는 쓰기 가능한 상태를 반환한다. { get: GetRecoilValue, set: SetRecoilState, reset: ResetRecoilState, }, newValue: T | DefaultValue, ) => void, })
ex)
import { selector } from "recoil";
const userNameState = selector({
key : 'userNameState',
get : ({get}) => {
const user = get(userState);
return user ? user.name : 'Guest';
}
})
Atom 값을 읽고 업데이트하는데 사용
되는 훅으로 useState과 비슷한 구조이지만 전역 상태를 다룬다는 점에서 차이가 있다. 상태가 업데이트 되었을 때 리렌더링을 하도록 컴포넌트를 구독한다.import {atom, useRecoilState, RecoilRoot} from "recoil";
const countState = atom({
key : 'countState',
default : 0,
});
function Counter() {
const [count, setCount] = useRecoilState(countState);
return(
<div>
<p>Count : {count} </p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
);
}
단순히 Atom 값을 읽는데에 사용
되며 상태 업데이트를 트리거하지 않는다. import {selector, useRecoilValue, RecoilRoot} from 'recoil';
const userNameState = selector({
key : 'userNameState',
get : ({get}) => {
const user = get(userState);
return user ? user.name : 'Guest';
},
});
function UserInfo() {
const userName = useRecoilValue(userNameState);
return <p>Welcome, {userName}!</p>
}
Atom 값을 업데이트
하는 setter 함수를 반환한다.import {atom, useSetRecoilState, RecoilRoot} from 'recoil';
const countState = atom({
key : 'countState',
default : 0,
});
function Counter() {
const setCount = useSetRecoilState(countState);
return(
<div>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>Increment</button>
</div>
);
}
Atom 값을 초기값으로 리셋
하는 함수를 반환한다.import {atom, useResetRecoilState, RecoilRoot} from 'recoil';
const countState = atom({
key : 'countState',
default : 0,
});
function ResetButton() {
const resetCount = useResetRecoilState(countState);
return(
<div>
<button onClick={resetCount}>Reset Count</button>
</div>
);
}
npm install recoil
yarn add recoil
export const NextProvider = ({ children }: Props) => {
return (
<RecoilRoot>
<QueryClientProvider client={queryClient}>
...
</QueryClientProvider>
</RecoilRoot>
);
};
: 기존 Map, Marker에 전달하던 상태 및 상태 관리 함수는 모두 삭제 후 전역으로 상태 관리하도록 적용
• mapState : 지도의 기본 상태를 저장
• currentStoreState : 현재 선택한 맛집 상태를 저장
• locationState : 현재 위치 및 zoom 상태를 저장
// atom/index
export const mapState = atom<any>({
key: "map",
default: null,
dangerouslyAllowMutability: true,
});
export const currentStoreState = atom<StoreType | null>({
key: "store",
default: null,
});
export const locationState = atom<LocationType>({
key: "location",
default: {
lat: DEFAULT_LAT,
lng: DEFAULT_LNG,
zoom: DEFAULT_ZOOM,
},
});
// components/map
export default function Map({ lat, lng, zoom }: MapProps) {
const setMap = useSetRecoilState(mapState);
const location = useRecoilValue(locationState);
export default function Marker({ store }: MarkerProps) {
const map = useRecoilValue(mapState);
// components/markers
export default function Markers({ stores }: MarkerProps) {
const map = useRecoilValue(mapState);
const setCurrentStore = useSetRecoilState(currentStoreState);
const [location, setLocation] = useRecoilState(locationState);
: 기존 SearchFilter에 전달하던 상태 및 상태 관리 함수는 모두 삭제 후 전역으로 상태 관리하도록 적용
searchState : 검색 상태를 저장
// atom/index
export const searchState = atom<SearchType | null>({
key: "search",
default: null,
});
export default function SearchFilter() {
const [search, setSearch] = useRecoilState(searchState);
return (
...
<input
type="search"
onChange={(e) => setSearch({ ...search, q: e.target.value })}
...
/>
</div>
<select
onChange={(e) => setSearch({ ...search, district: e.target.value })}
...
>
💡