부모state를 변경할 수 있는 setState함수를
자식 컴포넌트에 props로 전달하며 드릴링
. (depth가 깊어질 경우 비효율적) ,자식 컴포넌트에서 전달받은 setState함수는 부모컴포넌트의 state를 변경시킬 수 있다.
prop드릴링을 방지를 위한, State management tool
( redux, recoil ..)사용
atom은 결국 컴포넌트의 state를
manageable
하게원격
으로 공유하기 위해 사용하는 것.
npx create-react-app [디렉토리명]
npm i recoil 또는 yarn add recoil
<RecoilRoot>
컴포넌트는 Recoil의 hooks를 사용하는 모든 구성 요소의 조상
이어야 한다.store를 별도로 생성해줘야 하는 Redux와 달리 리코일은
RecoilRoot만 제공해도 자동으로 store가 생성됩니다
.
import React from 'react';
import {
RecoilRoot,
atom,
selector,
useRecoilState,
useRecoilValue,
} from 'recoil';
function App() {
return (
<RecoilRoot>
<CharacterCounter />
</RecoilRoot>
);
}
<RecoilRoot>
컴포넌트로 래핑해주는 과정이 필요하다.store(state저장소)
와 유사한 개념으로, 상태의 단위key
로 구분됨.읽는
컴포넌트들은 암묵적으로 atom을 구독
하고, 해당 atom에 변화가 생긴다면 이를 구독하는 모든 컴포넌트들이 리렌더링
된다.atom({key: 'unique key' ,default: 'default state' })
로 생성
default state
를 포함한다.atom({객체세팅})
으로 선언key
와 default
프로퍼티는 필수로 선언해야한다.원시형데이터
타입과 더불어 객체나 배열
같은 complex타입도 atom으로 사용할 수 있다.{key: , default: 초기값
}참고: 현재 atom을 설정할 때
Promise
을 지정할 수 없다는 점에 유의해야 한다. 비동기 함수를 사용하기 위해서는selector()
를 사용한다.
state.js (atom 선언파일)
export const cookieState = atom({
key: 'cookieState',
default: []
});
Cookie.js
import React from 'react'
import { cookieState } from '../../state';
import { useRecoilState } from 'recoil';
const Cookie = () => {
const [cookies, setCookies] = useRecoilState(cookieState);
//atom의 key값으로 호출: 1.아톰state값, 2.상태변경함수 구조분해할당
//이제 cookies, setCookies라는 변수를 활용할 수 있다.
return(
<div>
{cookie.map(cookie => (
<Card
cookies={cookie}
key={cookie.id}
idx={cookie.id}
/>
))}
</div>
);
}
export default Cookie;
const [cookies, setCookies] = useRecoilState(cookieState);
구조분해할당
으로 atom의 state와 state를 set
하는 함수를 각각 받아올 수 있다.
전역상태(Atoms, Selector)
를 get/set 하기 위해 Recoil에서 제공하는 Hooks들을 사용한다. 기본적으로 아래 4가지가 크게 사용된다.
Hook의 인자에는 전역상태인 atom(혹은 Selector)를 넣어준다.(: Recoil에서 atom과 selector는 동일한 훅으로 다뤄준다.
useRecoilState()
: useState() 와 유사하다.
전역상태의 state값,setter함수를 반환한다.
`const [state, setState] = useRecoilState(atom|selector)` 형태로 구조분해 할당하여 사용한다.
useRecoilValue()
: 전역상태의 state상태값만을 반환한다.
useSetRecoilState()
: 전역상태의 setter 함수만을 반환한다.
useResetRecoilState()
: 전역상태를 default(초기값)으로 Reset 하기 위해 사용된다.
reset한 default값이 반환된다.
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { cookieState } from '../../state';
const cookies = useRecoilValue(cookieState);
//state값
const setCookies =
useSetRecoilState(cookieState);
//setState함수
const resetCookies = useResetRecoilState(cookieState);
//state초기화후 초기값
파생된
(atom데이터를 재활용한) 데이터 조각파생 데이터를 만들어주는 역할
순수함수
atom이 처리 불가능한 promise같은 비동기데이터 반환가능
selector는
1.
아톰의 데이터를 활용해 (getter)파생된 데이터를 반환하거나2.
아톰의 데이터를 활용하지 않더라도 특정 데이터를 반환할 수 있다.
이두 가지 용도로 사용된다
.
예제1)
import "./App.css";
import {
RecoilRoot,
atom,
selector,
useRecoilState,
DefaultValue,
useResetRecoilState,
} from "recoil";
const tempFahrenheit = atom({
key: "tempFahrenheit",
default: 32,
});
//아톰
const tempCelcius = selector({
key: "tempCelcius",
// get: ({ get }) => ((get(tempFahrenheit) - 32) * 5) / 9,
get: ()=> 30, ///...selector의 get메서드 리턴값이 selector의 상태값
set: ({ set }, newValue) =>
set(
tempFahrenheit, //아톰 state
// newValue instanceof DefaultValue ? newValue : (newValue * 9) / 5 + 32
3 //아톰state값을 고정값 3으로 set해주고, 추후 구조분해할당된 setter호출시 무조건적으로 3으로 변경된다.
),
});
// 아톰을참조하는 셀렉터
function TempCelcius() {
const [tempF, setTempF] = useRecoilState(tempFahrenheit);
const [tempC, setTempC] = useRecoilState(tempCelcius);
const resetTemp = useResetRecoilState(tempCelcius);
const addTenCelcius = () => setTempC(tempC + 10);
const addTenFahrenheit = () => setTempF(tempF + 10);
const reset = () => resetTemp();
return (
<div>
Temp (Celcius): {tempC}
<br />
Temp (Fahrenheit): {tempF}
<br />
<button onClick={addTenCelcius}>Add 10 Celcius</button>
<br />
<button onClick={addTenFahrenheit}>Add 10 Fahrenheit</button>
<br />
<button onClick={reset}>Reset</button>
</div>
);
}
function App() {
return (
<div className="App">
<RecoilRoot>
<TempCelcius></TempCelcius>
</RecoilRoot>
</div>
);
}
export default App;
key
값이 필요하다. useRecoilState(selector) state값이 된다
.useRecoilState(selector)
의 구조분해할당한 setter함수
가 된다.특정객체
가 전달되는데,{get}
을 파라미터에 넣어주어 구조분해할당
으로 전역상태(atom,selector)의 값을 참조하는 get()
함수를 사용할수 있다.( {set} , newValue )
두개의 인자가 전달되며, set: ({set},newValue)=>set(prevAtomState,newValue)로 형태로 set해준다.const proxySelector = selector({
key: 'ProxySelector',
get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
set: ({set}, newValue) => set(myAtom, newValue),
//myAtom -> newValue
});
예제2
import { atom, selector, useRecoilState, useRecoilValue} from 'recoil';
export default function Counter() {
const [count,setCount] = useRecoilState(countState);
//countState아톰의 state 참조
const oddEven = useRecoilValue(oddEvenState);
// oddEvenState셀렉터의 derived state참조
return(
<div className='counter'>
Count: {count} / 홀짝: {oddEven}
<br />
<button onClick={()=> setCount(count - 1)}> 1 감소</button>
<button onClick={()=> setCount(count + 1)}> 1 증가</button>
</div>
)
}
atom과 selector는 구분없이 동일한 Hook으로 다뤄준다!
suspense
와 함께 사용해야 에러가 발생하지 않는다.<React.Suspense>
: 컴포넌트를 완전히 렌더링할 수 있을 때까지 렌더링을 멈춰두는 기능(fallback prop으로 임시 렌더링컴포넌트를 전달한다)
export defualt function App(){
return(
<div className="App">
<h1>Random Cat</h1>
<p>페이지를 새로 고침할 때마다 랜덤한 고양이 사진을 보여줘야옹</p>
<RecoilRoot>
<React.Suspense fallback={null}>
<RandomCat/>
//비동기 selector포함 컴포넌트
</React.Suspense>
</RecoilRoot>
</div>
)
}
비동기처리가 이뤄지는 selector가 있는 컴포넌트
를 React.Suspense로 감싸줍니다.fallback prop
에는 비동기처리가 완료되기까지 보여줄 컴포넌트를 전달합니다.위와같이 비동기selector에 suspense를 이용하면 비동기 상태처리를 간단하게 할 수 있다.
useRecoilValueLoadable
훅 활용하기 (switch(Loadable.state)로 렌더링할 컴포넌트를 세밀하게 조정해줄 수 있음
)비동기 셀렉터의 값 참조
export defualt function App(){
return(
<div className="App">
<h1>Random Cat</h1>
<p>페이지를 새로 고침할 때마다 랜덤한 고양이 사진을 보여줘야옹</p>
<RecoilRoot>
<RandomCat/>
//비동기 selector포함 컴포넌트
</RecoilRoot>
</div>
)
}
useRecoilValue
=> useRecoilValueLoadable
훅으로 바꿔줍니다.Loadable객체
를 반환합니다.state
프로퍼티를 통해 데이터처리 상태를 확인하고<string>
contents
프로퍼티를 통해 실제 콘텐츠를 가져올 수 있습니다.export default function RandomCat(){
//비동기처리 selector를 포함한 컴포넌트
const photoUrlLoadable = useRecoilValueLoadable(randomCat);
//비동기 셀렉터로 호출 -> 로더블객체 생성, 로더블 state & contents활용가능
switch (photoUrlLoadable.state){
case 'hasValue':
content = <img src={phtoUrlLoadable.contents}> alt="random cat" />; //리액트 엘리먼트 변수에 할당
break; //탈출
case 'hasError':
content = '데이터를 불러오는 중 에러가 발생했습니다';
break;
case 'loading':
default:
content = 'loading...';
}
return <div className="random-cat">{content}</div>;
}
selector란 구독하고 있는 atom에 변화가 생길 때마다 새로운 값을 리턴해주는 순수 함수이다. 즉, get을 통해 가져온 값은 의존성을 갖고 있어,
구독하는 값이 변할 때마다 새로운 값을 갱신한다
, 위와 반대로 get메서드에 async와 await을 추가함으로서 비동기 처리시구독하는 atom 값이 변하지 않을 경우, 동일한 값을 리턴한다
. 즉, get()의 파라미터가 동일하다면, 캐시된 값을 리턴한다.
get참조값
에 대해 캐싱된 값을 반환해준다.export cosnt keywordState = atom({
key: 'keywordState',
default: '',
})
export const animeList = selector({
key: 'animeList',
get: async ({get})=>{
const keyword = get(keywordState)
///...사용한 아톰에 자동으로 의존성이 걸린다.(값을 캐싱한다)
if(!keyword || keyword.length < 3) {
return [];
}
}
})
const modalsAtomFamily = atomFamily<ModalInfo, ModalId>({
key: "modalsAtomFamily",
default: (id) => ({
id,
isOpen: false,
title: "",
}),
})
아래와 같이 atomFamily함수에 id를 전달하여 호출하면 해당 id를 활용한 atom이 생성된다.
const [myModal, setMyModal] = useRecoilState(modalsAtomFamily("myModal"))
const [yourModal, setYourModal] = useRecoilState(modalsAtomFamily("yourModal"))
예를 들어, 현재 열려 있는 모달들을 한 번에 닫는 등의 작업 등은 atomFamily 만으로는 수행할 수 없을 것입니다. 따라서, atomFamily와 동시에 atomFamily를 통해 생성된 Atom의 key를 별도로 관리해주는 작업이 필요합니다
export const modalIdsAtom = atom<ModalId[]>({
key: "modalIdsAtom",
default: [],
})
//결국 새 모달을 생성하고자 할 때에는 아래와 같이 두 단계의 작업이 필요하다는 의미입니다.
const setModalIdsAtom = useSetRecoilState(modalIdsAtom) //setter함수 취득
/* 1. atomFamily로 모달 Atom 생성 */
const myModal = modalsAtomFamily("myModal")
/* 2. 생성한 Atom의 key를 별도의 배열에 넣기 */
setModalIdsAtom((prev) => [...prev, modalIdsAtom])
Recoil의 selector는 set 값을 이용해 쓰기 가능한 상태(writable state)를 정의할 수 있습니다. set은 특정한 타입의 파라미터를 받는데, 이 타입의 파라미터를 이용해 다른 Recoil Atom을 업데이트하는 용도로 사용할 수 있습니다.
- selector와 selectorFamily <-> atom , atomFamily관계와 동일하다.
- 즉, selectorFamily는 한 파라미터(위 예시에서는 ModalId)를 받아 이 파라미터를 이용해 작업을 수행하는 selector를 리턴하는 팩토리 함수를 리턴합니다.
- selectorFamily로 만든 셀렉터생성함수는 호출 인자값으로 get,set메서드에 인자를 전달할 수 있다.
export const modalsSelectorFamily = selectorFamily<ModalInfo, ModalId>({
key: "modalsSelectorFamily",
get: (modalId) => ({ get }) => get(modalsAtomFamily(modalId)),
set: (modalId) => ({ get, set, reset }, modalInfo) => {
if (modalInfo instanceof DefaultValue) {
reset(modalsAtomFamily(modalId))
set(modalIdsAtom, (prevValue) => prevValue.filter((item) => item !== modalId))
return
}
set(modalsAtomFamily(modalId), modalInfo)
set(modalIdsAtom, (prev) => Array.from(new Set([...prev, modalInfo.id])))
},
})
selectorFamily에서 get,set메서드에는 기존 selector의 메서드와 달리 (호출값) =>
을 전달하는 과정이 추가된다.
get
내부의 syntax를 살펴보면 생기는 의문이 있습니다. Recoil은 동일한 atomFamily로 생성된 Atom들을 구분하기 위해 atomFamily에 넘겨준 파라미터를 내부적인 ID로 이용합니다
const myModal = useRecoilValue(modalsAtomFamily("myModal"))
const notMyModal = useRecoilValue(modalsAtomFamily("myModal"))
// myModal과 notMyModal은 같은 Atom의 값을 가리키게 될 것입니다.
set
값에서는 첫 번째 파라미터로 set 내부에서 다른 Recoil 상태를 읽거나 업데이트할 때 사용하는 get, set, reset 함수를 가져옵니다. 두 번째 파라미터는 특정한 값을 받는데, 이 값을 이용해 다른 상태를 읽거나 업데이트 할 수 있습니다.