기본 사용법
api는 동기 비동기가 다르지 않다.
리턴값만 프로미스로 변경해주면 된다.
const userState = selectorFamily({
key: 'user',
get: (userId: number) => async () => {
const userData = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`).then((res) => res.json())
return userData
},
})
해당 데이터는 promise이기 때문에, 정상적인 방법으론 렌더링할 수 없다.
이를 위해 React.Suspense를 사용한다.
해당 API를 사용하는 컴포넌트 상단을 Suspense로 감싸준다.
const UserData = ({userId}: {userId: number}) => {
const user = useRecoilValue(userState(userId))
if (!user) return null
return (
<div>
<Heading as="h2" size="md" mb={1}>
User data:
</Heading>
<Text>
<b>Name:</b> {user.name}
</Text>
<Text>
<b>Phone:</b> {user.phone}
</Text>
</div>
)
}
// 상단 컴포넌트
{userId !== undefined && (
<Suspense fallback={<div>Loading...</div>}>
<UserData userId={userId} />
</Suspense>
)}
Selector Family는 응답을 캐싱하며, 비동기 요청으로 데이터를 가져오기 때문에 효과적으로 여러 요청을 처리할 수 있다.
언제 selector / selectorFamily를 사용하는가
자바스크립트도 일단 const를 먼저 사용하듯
우선 atom으로 선언하고, 이후 파생은 다음 규칙에 따라 적용한다.
데이터 소스 (파생 원천) | 아래를 사용하세요 |
atom / selector | selector |
React (component prop / useState / Redux ) | selectorFamily |
Both | selectorFamily |
async getter내에서 비동기 atom 조회
외부에서 가져다 쓰는 selector는 atom 동기인지 비동기인지 상관없이 동일하게 get으로 가져다 쓴다.
(내부적으로 generator를 사용하고 있을 것이라 생각함)
const weatherState = selectorFamily({
key: 'weather',
get: (userId: number) => async ({get}) => {
get(weatherFetchIdState(userId))
const user = get(userState(userId)) // 동기적으로 호출한다.
const weather = await getWeather(user.address.city)
return weather
},
})
ErrorBoundary와 Data Refetch
ErrorBoundary
리코일과 suspense 덕택에 loading 플래그를 없앨 수 있었다.
이제 에러 처리를 컴포넌트에서 선언적으로 수행해보자.
먼저 아래의 라이브러리를 다운받는다.
(현재 리액트는 에러바운더리를 사용하려면 클래스 문법으로 직접 만들어야 한다. 만들어진걸 가져다 쓰자.)
import {ErrorBoundary, FallbackProps} from 'react-error-boundary'
// 4번 유저를 조회하면 에러를 리턴함.
const userState = selectorFamily({
key: 'user',
get: (userId: number) => async () => {
const userData = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`).then((res) => res.json())
if (userId === 4) throw new Error('User does not exist')
return userData
},
})
const ErrorFallback = ({error, resetErrorBoundary}: FallbackProps) => {
return (
<div>
<Heading as="h2" size="md" mb={1}>
Something went wrong
</Heading>
<Text mb={1}>{error.message}</Text>
<Button onClick={resetErrorBoundary}>Ok</Button>
</div>
)
}
// 사용 방법
<ErrorBoundary
FallbackComponent={ErrorFallback}
resetKeys={[userId]} // 해당 키 바뀌면 에러 없는것으로 처리
onReset={() => { // resetErrorBoundary
setUserId(undefined)
}}
>
{userId !== undefined && (
<Suspense fallback={<div>Loading...</div>}>
<UserData userId={userId} />
</Suspense>
)}
</ErrorBoundary>
Data Refetch
리코일의 get은 멱등성을 가진 연산이다. 즉, 같은 키로 여러번 조회하는 경우 항상 같은 결과를 리턴한다.
컴퓨팅에서 멱등 연산은 동일한 입력 매개변수로 두 번 이상 호출되는 경우 추가 효과가 없는 연산입니다.
예를 들어 집합에서 항목을 제거하는 것은 집합에 대한 멱등 연산으로 간주될 수 있습니다.
Idempotence: https://stackoverflow.com/questions/1077412/what-is-an-idempotent-operation
아톰키를 새로 만들어 데이터를 다시 가져와보자. atomEffect로 처리하는 방법을 추후에 알아볼 것이다.
논리는 다음과 같다.
weatherState는 userState와 weatherFetchIdState의 각 셀렉터/아톰에 의존한다.
refreshWeather를 사용하면 fetchId가 변경되어 weatherState가 업데이트 된다.
즉 아톰의 변경이 selector의 상태를 변경한다.
(atomFamily, selectorFamily 내부의 아톰은 전부 개별적이다.)
const weatherFetchIdState = atomFamily({
key: 'weatherFetchId',
default: 0,
})
const useRefreshWeather = (userId: number) => {
// 사용자 id를 키로 아톰을 가져온다.
const setFetchId = useSetRecoilState(weatherFetchIdState(userId))
// 아톰의 값를 변경한다.
return () => setFetchId((id) => id + 1)
}
// weatherState 각 셀렉터는 weatherFetchIdState의 각 사용자별 아톰(userState, fetchId)에 의존하고 있음.
const weatherState = selectorFamily({
key: 'weather',
get: (userId: number) => async ({get}) => {
// refetch를 위해 의존성 도입
get(weatherFetchIdState(userId))
const user = get(userState(userId))
const weather = await getWeather(user.address.city)
return weather
},
})
'FrontEnd' 카테고리의 다른 글
Recoil Pattern : Selector Composition (셀렉터 조합하기) (0) | 2022.05.15 |
---|---|
Recoil 패턴 : Intermediate Selectors (중간 선택자로 리렌더링 최적화) (0) | 2022.05.15 |
Lean Recoil 강의 정리 : Atom Families / Selector Families (0) | 2022.05.15 |
Lean Recoil 강의 정리 : Atoms And Selectors (0) | 2022.05.15 |
Recoil로 Todo List 만들기. (0) | 2022.05.14 |