atom()
atom은 Recoil의 상태를 표현한다.
atom() 함수는 쓰기 가능한 RecoilState 객체를 반환한다.
function atom<T>({
key: string,
default: T | Promise<T> | RecoilValue<T>,
effects_UNSTABLE?: $ReadOnlyArray<AtomEffect<T>>,
dangerouslyAllowMutability?: boolean,
}): RecoilState<T>
* key
: 내부적으로 atom을 구별하기 위한 이름.
: 이 이름은 전체 앱에서 다른 atom과 selector와 겹치면 안 된다 = 고유한 이름이어야 한다.
* default
: atom이 처음 만들어질 때 가지는 초기값. 일반 값이 될수도 있고 Promise 또는 다른 atom이나 selector가 될수도 있다.
* effects_UNSTABLE
: 이것은 atom에 추가적인 기능을 부여할 수 있는 옵션. 아직 안정화 된 기능은 아님.
* dangerouslyAllowMutability
: atom의 값이 변하는 것을 어떻게 다룰지에 대한 설정.
: 기본적으로 atom은 값이 변하면 이를 알아차리고 관련된 컴포넌트를 다시 렌더링 한다. 그러나 이 옵션을 사용하면 atom의 값이 바뀌어도 Recoil이 알아차리지 못하게 할 수 있다.
atom과 상호작용하기 위해 자주 사용되는 Hooks
* useRecoilState()
: 이 Hook은 'atom'의 값을 읽고 쓰기 위해 사용된다. 컴포넌트가 'atom'에 등록되어, 값이 변경되면 컴포넌트도 다시 렌더링 된다.
* useRecoilValue()
: 이 Hook은 'atom'의 값을 읽기만 할 때 사용됩니다. 읽기 전용이라 쓸 수는 없습니다.
* useSetRecoilState()
: 이 Hook은 'atom'에 값을 쓰기만 할 때 사용됩니다. 읽을 수는 없습니다.
* useResetRecoilState()
: 이 Hook은 'atom'을 처음 상태(초기값)로 되돌릴 때 사용됩니다.
* useRecoilCallBack()
: 컴포넌트가 등록되지 않고 'atom'의 값을 읽어야 하는 특별한 경우에 이 Hook을 사용할 수 있다.
비동기 함수와 atom
: 현재 'atom'을 설정할 때 비동기 함수는 사용할 수 없고, 그 대신 'selector'를 사용해야 한다.
atom과 객체, 함수
: 'atom'은 'Promise'나 'RecoilValues'를 직접 저장할 수 없지만, 이들을 포함하는 객체는 저장할 수 있다.
: 또한 함수를 저장할 때는 순수 함수를 사용하고, 업데이터 형태의 'setter'를 사용해야 할 수도 있다.
ex) set(myAtom, () => myFunc)
import {atom, useRecoilState} from 'recoil';
const counter = atom({
key: 'myCounter',
default: 0,
});
function Counter() {
const [count, setCount] = useRecoilState(counter);
const incrementByOne = () => setCount(count + 1);
return (
<div>
Count: {count}
<br />
<button onClick={incrementByOne}>Increment</button>
</div>
);
}
selector()
Recoil에서는 함수나 파생된 상태를 나타내기 위해 'selector'라는 것을 사용합니다.
이는 주어진 입력에 대해 항상 동일한 출력을 내는 "순수 함수"입니다.
읽기만 가능한 'RecoilValueReadOnly' 객체나, 쓰기도 가능한 '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,
})
type ValueOrUpdater<T> =
| T
| DefaultValue
| ((prevValue: T) => T | DefaultValue);
type GetRecoilValue = <T>(RecoilValue<T>) => T;
type SetRecoilState = <T>(RecoilState<T>, ValueOrUpdater<T>) => void;
type ResetRecoilState = <T>(RecoilState<T>) => void;
* key : 'selector'를 식별하기 위한 고유한 문자열입니다. 이는 전체 애플리케이션에서 고유해야합니다.
* get : 이 함수는 파생된 상태의 값을 계산합니다. 이는 직접 값을 반환할 수도 있고, 'Promise'나 다른 'atom', 'selector'를 반환할 수도 있습니다.
* set?: 이 속성이 설정되면, 'selector'는 쓰기 가능한 상태를 반환합니다. 사용자가 값을 설정하거나 재설정할 때 사용됩니다.
* dangerouslyAllowMutability: 이 옵션은 'selector'가 순수 함수로 동작하는 것을 보장하기 위해 사용됩니다. 경우에 따라 이 옵션을 재정의해야 할 수도 있습니다.
// 단순한 정적인 의존성이 있는 Selector
const mySelector = selector({
key: 'MySelector',
get: ({get}) => get(myAtom) * 100,
});
동적 의존성
* 읽기만 가능한 Selector : 이러한 'selector'sms 'get' 메서드를 사용하여 의존성을 기준으로 값을 계산합니다.
* 의존성 업데이트 : 의존성 중 하나라도 값이 변경되면 'selector'는 다시 평가됩니다.
* 동적 의존성 : 'selector'를 평가할 때 실제로 어떤 'atom'이나 'selector'에 의존하는지에 따라 의존성이 동적으로 결정됩니다. 이 의미는, 한 번 평가할 때마다 의존하는 'atom'이나 'selector'가 달라질 수 있다는 것입니다.
* 자동 업데이트 : Recoil은 자동으로 데이터 흐름 그래프를 업데이트하여 'selector'가 현재 의존하는 'atom'과 'selector'만을 구독하도록 합니다.
쓰기 가능한 Selector
* 양방향 Selector : 이 'selector'는 값을 읽을 수도 있고 쓸 수도 있습니다. 사용자가 'selector'의 값을 새로 설정하거나 재설정할 수 있습니다.
* 입력 값 : 'selector'가 나타내는 타입과 동일하거나, 재설정 작업을 나타내는 'DefaultValue' 객체 중 하나가 될 수 있습니다.
ex1) 이 selector는 기본적으로 atom을 감싸고 'hi'라는 extraField를 추가합니다.
set 메서드를 통해 업스트림의 atom까지 설정과 재설정 작업이 전달됩니다.
const proxySelector = selector({
key: 'ProxySelector',
get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
set: ({set}, newValue) => set(myAtom, newValue),
});
ex2) 이 selector는 데이터를 100배로 변환합니다.
set 메서드에서는 값이 DefaultValue인지 확인한 후에, 그렇지 않다면 값을 100으로 나눕니다.
const transformSelector = selector({
key: 'TransformSelector',
get: ({get}) => get(myAtom) * 100,
set: ({set}, newValue) =>
set(myAtom, newValue instanceof DefaultValue ? newValue : newValue / 100),
});
비동기 Selector
Selector는 또한 비동기 평가 함수를 가지고 있으며 Promise를 출력값으로 반환할 수 있다. 자세한 정보는 이 가이드를 보면 된다.
const myQuery = selector({
key: 'MyQuery',
get: async ({get}) => {
return await myAsyncQuery(get(queryParamState));
},
});
동기 예시
import {atom, selector, useRecoilState, DefaultValue} from 'recoil';
const tempFahrenheit = atom({
key: 'tempFahrenheit',
default: 32,
});
const tempCelcius = selector({
key: 'tempCelcius',
get: ({get}) => ((get(tempFahrenheit) - 32) * 5) / 9,
set: ({set}, newValue) =>
set(
tempFahrenheit,
newValue instanceof DefaultValue ? newValue : (newValue * 9) / 5 + 32,
),
});
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>
);
}
비동기 예시
import {selector, useRecoilValue} from 'recoil';
const myQuery = selector({
key: 'MyDBQuery',
get: async () => {
const response = await fetch(getMyRequestUrl());
return response.json();
},
});
function QueryResults() {
const queryResults = useRecoilValue(myQuery);
return <div>{queryResults.foo}</div>;
}
function ResultsSection() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<QueryResults />
</React.Suspense>
);
}
Loadable
* Loadable 객체 : Recoil의 'atom'이나 'selector'의 현재 상태를 나타냅니다. 이 상태는 값이 있을 수도, 에러 상태일 수도, 아니면 비동기 작업이 아직 끝나지 않았을 수도 있습니다.
* Loadable state : 'Loadable'의 현재 상태를 나타내며, 'hasValue', 'hasError', 'loading' 중 하나입니다.
* contents: 'Loadable'이 대표하는 값. 상태가 'hasValue'이면 실제 값, 'hasError'이면 에러 객체, 'loading'이면 Promise를 얻을 수 있습니다.
function UserInfo({userID}) {
const userNameLoadable = useRecoilValueLoadable(userNameQuery(userID));
switch (userNameLoadable.state) {
case 'hasValue':
return <div>{userNameLoadable.contents}</div>;
case 'loading':
return <div>Loading...</div>;
case 'hasError':
throw userNameLoadable.contents;
}
}
// return example
{
state: 'hasValue',
contents: 0,
}
아직 불안정하지만 제공하고 있는 도우미 메서드
* getValue(), toPromise(), valueMaybe(), valueOrThrow(), map()
useRecoilState(state)
* useRecoilState 훅 : 이 훅은 Recoil 상태를 관리하는 데 사용되며, 첫번째 요소는 상태의 현재 값이고, 두번째 요소는 상태를 업데이트 하는 'setter' 함수 입니다.
* 구독 : 이 훅을 사용하면 React 컴포넌트는 자동으로 해당 상태에 구독되어, 상태가 변경되면 리렌더링 됩니다.
* useState()와의 차이 : 이 훅은 기본적으로 React의 'useState()' 훅과 유가하지만, Recoil 상태를 인수로 받는 차이가 있습니다.
* 에러와 비동기: 이 훅은 상태가 에러를 가지고 있거나, 비동기로 해결되고 있는 경우에도 관리할 수 있습니다. (링크)
useRecoilValue(state)
* 이 훅은 읽기 전용 'selector'와 쓰기 가능한 'selector' 또는 'atom'에서 모두 작동합니다. 따라서 컴포넌트가 상태를 단순히 읽을 수 있게 하려는 경우에도 이 훅을 사용할 수 있습니다.
* 컴포넌트 구독 : 이 훅을 사용하면 해당 상태에 대한 구독이 설정되고, 상태가 변경될 때마다 컴포넌트가 리렌더링 됩니다.
* 에러 및 비동기 처리 : 이 훅은 상태에 에러가 있거나, 비동기 작업이 진행 중인 경우에도 그 정보를 제공합니다.
import {atom, selector, useRecoilValue} from 'recoil';
const namesState = atom({
key: 'namesState',
default: ['', 'Ella', 'Chris', '', 'Paul'],
});
const filteredNamesState = selector({
key: 'filteredNamesState',
get: ({get}) => get(namesState).filter((str) => str !== ''),
});
function NameDisplay() {
const names = useRecoilValue(namesState);
const filteredNames = useRecoilValue(filteredNamesState);
return (
<>
Original names: {names.join(',')}
<br />
Filtered names: {filteredNames.join(',')}
</>
);
}
useSetRecoilState(state)
* useSetRecoilState 훅 : 이 훅은 쓰기 가능한 Recoil 상태('atom' 혹은 쓰기 가능한 'selector')를 업데이트하기 위한 'setter' 함수를 반환합니다.
* 사용 시나리오 : 이 훅은 컴포넌트가 상태를 읽지 않고 오직 쓰기만 할 때 유용합니다. 이 훅을 사용하면, 값이 바뀔 때 컴포넌트를 다시 그리지 않아도 됩니다.
import {atom, useSetRecoilState} from 'recoil';
const namesState = atom({
key: 'namesState',
default: ['Ella', 'Chris', 'Paul'],
});
function FormContent({setNamesState}) {
const [name, setName] = useState('');
return (
<>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
<button onClick={() => setNamesState(names => [...names, name])}>Add Name</button>
</>
)}
// This component will be rendered once when mounting
function Form() {
const setNamesState = useSetRecoilState(namesState);
return <FormContent setNamesState={setNamesState} />;
}
useResetRecoilState(state)
* useResetRecoilState 훅 : 이 훅은 주어진 Recoil 상태('atom' 또는 쓰기 가능한 'selector')를 초기값으로 리셋하는 함수를 반환합니다.
* 사용 시나리오 : 이 훅은 컴포넌트가 상태의 변화를 추정하지 않고 단순히 상태를 초기값으로 되돌릴 때 유용합니다. 즉, 상태가 바뀌더라도 컴포넌트가 리렌더링되지 않습니다.
useRecoilStateLoadable(state)
* useRecoilStateLoadable 훅 : 이 훅인 비동기 'selector'의 값을 읽어올 때 사용되며, 리턴 값으로는 'Loadable' 객체와 'setter' 콜백 함수를 제공합니다.
* 구독 : 이 훅도 사용하면 자동으로 해당 상태에 컴포넌트가 구독됩니다.
* Loadable 객체 : 이 객체는 상태가 무엇인지('hasValue', 'hasError', 'loading')와 그에 따른 실제 값을 타나냅니다.
* 비동기 처리 : 이 훅은 'useRecoilState'와 다르게 비동기 상황에서 Error나 Promise를 던지지 않습니다. 대신 상태와 값에 대한 정보를 'Loadble' 객체로 제공합니다.
function UserInfo({userID}) {
const [userNameLoadable, setUserName] = useRecoilStateLoadable(userNameQuery(userID));
switch (userNameLoadable.state) {
case 'hasValue':
return <div>{userNameLoadable.contents}</div>;
case 'loading':
return <div>Loading...</div>;
case 'hasError':
throw userNameLoadable.contents;
}
}
useRecoilValueLoadable(state)
* useRecoilValueLoadable 훅 : 이 훅은 비동기 'selector' 또는 'atom'의 값을 읽을 때 사용됩니다. 이 훅은 'Loadble' 객체를 반환해 상태 정보를 제공합니다.
* 구독 : 이 훅을 사용하면, 해당 상태에 대한 변화를 컴포넌트가 자동으로 알 수 있습니다. 즉, 상태가 변하면 컴포넌트가 자동으로 다시 그려집니다.(리렌더링)
* Error 및 Promise 처리 : 이 훅은 'useRecoilValue'와 달리, 비동기 상황에서도 Error나 Promise를 던지지 않습니다. 대신 상태 정보를 'Loadble' 객체로 알려줍니다.
* Loadable 객체 : 이 객체는 상태의 현재 상황을 나타냅니다. 'hasValue', 'hasError', 'loading' 상태값이 있고, 그에 따라 실제 값, 에러 객체, 또는 Promise 정보가 담겨 있습니다.
function UserInfo({userID}) {
const userNameLoadable = useRecoilValueLoadable(userNameQuery(userID));
switch (userNameLoadable.state) {
case 'hasValue':
return <div>{userNameLoadable.contents}</div>;
case 'loading':
return <div>Loading...</div>;
case 'hasError':
throw userNameLoadable.contents;
}
}
Loadable 정리
Aspect | Loadable | useRecoilStateLoadable | useRecoilValueLoadable |
목적 | Recoil 값(atom 또는 selector)의 현재 상태를 나타냄. | 비동기 selector 또는 atom의 값을 읽고, 상태 설정도 가능 | 비동기 selector 또는 atom의 값을 읽음 |
구독 | N/A 객체이므로 훅이 아님 |
컴포넌트를 상태에 암묵적으로 구독 | 컴포넌트를 상태에 암묵적으로 구독 |
Error & Promise Handling | 상태를 캡슐화 'hasValue' 'hasError' 'loading' |
Error나 Promise를 던지지 않고, Loadable 객체를 제공 | Error나 Promise를 던지지 않고, Loadable 객체를 제공 |
Returns | 'state', 'contents' 등의 객체 속성을 통해 상태 정보를 제공 | [Loadable, setter 함수] 형태의 튜플을 반환 |
Loadable 객체를 반환 |
useGetRecoilValueInfo()
- 아직 UNSTABLE함.
useRecoilRefresher()
- 아직 UNSTABLE함.
isRecoilValue(value)
* value이 atom이나 selector일 경우 true를 반환하고 그렇지 않을 경우 false를 반환한다.
import {atom, isRecoilValue} from 'recoil';
const counter = atom({
key: 'myCounter',
default: 0,
});
const strCounter = selector({
key: 'myCounterStr',
get: ({get}) => String(get(counter)),
});
isRecoilValue(counter); // true
isRecoilValue(strCounter); // true
isRecoilValue(5); // false
isRecoilValue({}); // false
'KNOW-HOW > Recoil' 카테고리의 다른 글
레퍼런스 API - 유틸 & 기타 Hook (0) | 2023.09.25 |
---|