-
Notifications
You must be signed in to change notification settings - Fork 0
Recoil 알고 사용하자! (공식문서 극 요약)
- 작성자: @jiyong1
- 작성일: 2021.11.27
- Description: atom 만 사용하지 말고, 제공하는 기능을 알고 사용하자!
현재 필요한 속성인지 의문..
Recoil의 hooks
를 사용하는 모든 컴포넌트의 조상이어야 한다. 여러개의 루트가 존재할 수 있는다. 각각의 루트 안에서는 구별되는 값들을 가질 것이다. (scope??)
- initializeState? : 원자 상태를 초기화하는 함수, 비동기적으로 초기화를 위한 것이 아니다.
- override? : 기본값은
true
로 설정되어 있다. 이 속성은 루트가 여러개 존재하는 경우에만 상관이 있다.-
true
: 해당 루트는 새로운 Recoil Scope를 생성한다. -
false
: 해당 루트는 단지 자식 렌더링 외에 다른 기능을 수행하지 않기 때문에 그의 자식들은 가장 가까운 조상 RecoilRoot의 Recoil 값에 access할 것이다.
-
atom
은 Recoil의 상태를 표현한다. atom 함수는 writable한 RecoilState
를 반환한다.
function atom<T>({
key: string,
default: T | Promise<T> | RecoilValue<T>,
// 아직 발전중인 API라서 _UNSTABLE이 붙어있습니다.
effects_UNSTABLE?: $ReadOnlyArray<AtomEffect<T>>,
dangerouslyAllowMutability?: boolean,
})
-
key
: 내부적으로 atom을 식별하기 위해 사용되는 고유한 문자열, 이 문자열은 App 전체의 다른 atom, selector에 대해 고유해야 합니다. -
default
: atom의 초기값 또는 Promise 또는 동일한 타입의 값을 나타내는 다른 atom, selector -
effects
: atom을 위한 선택적인 Atom Effects 배열- react의
useEffect
처럼 상태가 변경되었을 때 실행하는 함수들의 집합 같음.. - 각 함수들은 인자로 한 객체가 제공된다.
- 디버깅에 용이하지 않을까라는 짧은 생각..
- 참고
- react의
-
dangerouslyAllowMutability
: 해당 상태를 구독하고 있는 컴포넌트는 상태가 변경되었을 시 리렌더링이 일어나게 되는데 만약 이를 .. 뭔소린지 잘 모르겠습니다- 마치 데이터베이스의 트랜잭션 이런 이야기인가..
-
useRecoilState()
: atom을 읽고 쓰려고 할 때 사용되는 hook- 이 hook은 atom에 컴포넌트를 등록한다. (구독)
-
useRecoilValue()
: atom을 읽기만 할 때 사용되는 hook- 이 hook은 atom에 컴포넌트를 등록한다. (구독)
-
useSetRecoilState()
: atom에 쓰려고만 할 때 사용되는 hook -
useResetRecoilState()
: atom을 초깃값으로 초기화 할때 사용되는 hook
현재 atom을 설정할 때 Promise
을 지정할 수 없다는 점에 유의해야 한다. 비동기 함수를 사용하기 위해서는 selector를 사용한다.
selector
는 Recoil에서 함수나 파생된 상태를 나타낸다. 주어진 종속성 값 집합에 대해 항상 동일한 값을 반환하는 순수함수라고 생각하면 된다.
get
함수만 제공된다면 Selector는 읽기만 가능한 객체를 반환한다.
set
함수 또한 제공되면 Selector는 쓰기 가능한 객체를 반환한다.
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
})
-
key
: atom의 key와 동일 -
get
: 파생된 상태의 값을 평가하는 함수, 값을 직접 반환하거나Promise
, 같은 유형의 다른 atom이나 selector를 반환할 수 있다.- 매개변수
get
: 다른 atom이나 selector로부터 값을 가져오는데 사용되는 함수. 이 함수에 전달된 모든 atom과 selector는 암시적으로 selector에 대한 의존성 목록에 추가된다. 즉, 의존되는 값이 변경되면 재평가된다.
- 매개변수
-
set?
- 매개변수
get
: 다른 atom, select로부터 값을 가져온다. 위와 다르게 atom이나 selector를 구독하지 않는다. - 매개변수
set
: .. 어렵.
- 매개변수
Selector는 비동기 평가 함수를 가지고 있으며 Promise
를 출력값으로 반환할 수 있다.
const myQuery = selector({
key: 'MyQuery',
get: async ({get}) => {
return await fetchDataFromUser(get(userId));
}
})
selector는 의존성에 하나라도 변경점이 생긴다면 (위에서는 userId
) 재평가하고 다시 실행시킬 겁니다. 그리고 결과는 유니크한 인풋이 있을 때에만 실행되도록 캐시됩니다.
Recoil은 보류중인 (pending) 데이터를 다루기 위해 React Suspense
와 함께 동작하도록 디자인되어 있습니다.
function MyApp() {
return (
<RecoilRoot>
<React.Suspense fallback={<div>Loading</div>}>
<CurrentUserInfo />
</React.Suspense>
</RecoilRoot>
);
}
또한 요청 중 에러가 발생한다면 ErrorBoundary
로 잡을 수 있습니다.
파생된 데이터과 관련없이 다른 매개변수를 기반으로 데이터 fetching이 필요할 때가 있습니다. 이 때 selectorFamily
helper를 사용합니다.
const userDataQuery = selectorFamily({
key: 'UserData',
// 주목 !!!
get: (userId) => async () => {
const response = await fetchDataFromUser(userId);
if (response.error) {
throw response.error;
}
return response.data;
}
});
function UserInfo({userId}) {
// Read-Only and subscribe
const userName = useRecoilValue(userDataQuery(userId));
// ...Component
}
쿼리를 selector로 모델링하면 파생된 상태가 변경됨에 따라 새롭게 데이터를 가져오게 됩니다. (요약한 내용입니다.. 그냥 이런말을 하고자 하는 듯..)
여러개의 데이터를 기본적으로 직렬적으로 fetching하게 됩니다. 검색이 빠르다면 그것도 괜찮지만, 자원을 많이 사용한다면 waitForAll
과 같은 concurrent helper를 사용하여 병렬로 돌릴 수 있습니다.
const userDataQuery = selectorFamily({
key: 'UserData',
get: (userId) => async () => {
const response = await fetchDataFromUser(userId);
if (response.error) {
throw response.error;
}
return response.data;
}
});
const usersDataQuery = selector({
key: 'UsersData',
get: ({get}) => {
// 유저의 정보를 가져온다.
const userList = get(users);
const usersData = get(
waitForAll(userList.map(({ userId }) => userDataQuery(userId)));
);
return usersData
}
})
waitForNone
helper를 사용하여 일부 데이터로 추가적인 UI 업데이트를 할 수 있습니다.
const usersDataQuery = selector({
key: 'UsersData',
get: ({get}) => {
// 유저의 정보를 가져온다.
const userList = get(users);
const usersDataLoadables = get(
waitForNone(userList.map(({ userId }) => userDataQuery(userId)));
);
return usersDataLoadables
.filter(({ state }) => state==='hasValue')
.map(({ contents }) => contents);
}
})
성능적인 문제로 렌더링 이전에 데이터를 받아오고 싶을 수 있습니다. 일단 생략..
useRecoilValueLoadable()
hook을 사용하여 렌더링 중 status를 확인할 수도 있습니다.
function UserInfo({userID}) {
const userDataLoadable = useRecoilValueLoadable(userDataQuery(userID));
switch (userDataLoadable.state) {
case 'hasValue':
return <div>{userNameLoadable.contents}</div>;
case 'loading':
return <div>Loading...</div>;
case 'hasError':
throw userNameLoadable.contents;
}
}
- Loadable : Recoil
atom
혹은selector
의 상태를 대표합니다. 아래의 인터페이스를 제공합니다.-
state: atom 혹은 selector의 최신 상태입니다. 가능한 값은
hasValue
,hasError
, 혹은loading
입니다. -
contents: Loadable에 의해서 대표되는 값입니다. 만약 상태가 hasValue 라면, 이는 실제 값입니다. 만약 상태가 hasError 라면 이는 던져진 Error 객체입니다. 그리고 만약 상태가 'loading'이라면 toPromise()를 사용하여 값의 Promise를 얻을 수 있습니다.
-
ㅣoadable은 최신 상태에 접근하기 위한 도우미 메서드를 가지고 있습니다. 이 API는 아직 불안정하다고 합니다. (생략..)
-
Selector는 종속된 상태 혹은 패밀리의 매개변수에 따라 일관된 값을 제공합니다. (캐싱)
변경가능한 데이터를 다루기 위해서 몇가지 패턴을 사용할 수 있습니다.
-
종속성 추가하기
const userInfoQueryRequestIDState = atomFamily({ key: 'UserInfoQueryRequestID', default: 0, }); const userInfoQuery = selectorFamily({ key: 'UserInfoQuery', get: (userID) => async ({get}) => { get(userInfoQueryRequestIDState(userID)); // 종속성 추가 const response = await myDBQuery({userID}); if (response.error) { throw response.error; } return response; }, }); function useRefreshUserInfo(userID) { setUserInfoQueryRequestID = useSetRecoilState( userInfoQueryRequestIDState(userID), ); return () => { setUserInfoQueryRequestID((requestID) => requestID + 1); }; } function CurrentUserInfo() { const currentUserID = useRecoilValue(currentUserIDState); const currentUserInfo = useRecoilValue(userInfoQuery(currentUserID)); const refreshUserInfo = useRefreshUserInfo(currentUserID); return ( <div> <h1>{currentUser.name}</h1> {/* 버튼 클릭시 종속된 requestID를 1증가 시킴으로써 selector가 새롭게 데이터를 가져오게 된다 */} <button onClick={refreshUserInfo}>Refresh</button> </div> ); }
-
Atom 사용하기 (어렵.. 생략..)