KNOW-HOW/Recoil

레퍼런스 API - 유틸 & 기타 Hook

코리안심슨 2023. 9. 25. 16:24

atomFamily(options)

주요 요소

* key : atomFamily를 식별하기 위한 고유한 문자열

* default : 각 atom의 초기값을 설정. 여러 타입의 값이나 함수를 사용할 수 있습니다.

* effect : Atom Effects의 배열이나 콜백 함수를 선택적으로 사용할 수 있습니다.

* dangerouslyAllowMutability : 상태 변경에 따른 리렌더링을 위한 설정입니다.

 

기능과 특성

* atomFamily는 여러 atom을 동적으로 생성할 수 있는 유틸리티입니다.

* 매개변수를 받아서 각각의 atom에 대한 상태를 설정할 수 있습니다.

* atomFamily는 전체 어플리케이션에서 고유한 키를 가집니다.

* 매개변수는 값 동등성에 따라 atom을 구분하므로 원시 타입이나 단순한 객체를 사용해야 합니다.

 

사용 사례

* UI 요소가 동적으로 추가되고 각 요소에 상태가 필요한 경우 사용할 수 있습니다.

 

구독과 지속성

* 각 atom은 개별적으로 구독됩니다. 따라서 한 요소의 상태가 변경되면, 해당 요소만 리렌더링 됩니다.

* 지속성을 위해 고유한 키를 사용하며, 원시 타입이나 단순한 객체를 매개변수로 사용해야 합니다.

const elementPositionStateFamily = atomFamily({
  key: 'ElementPosition',
  default: [0, 0],
});

function ElementListItem({elementID}) {
  const position = useRecoilValue(elementPositionStateFamily(elementID));
  return (
    <div>
      Element: {elementID}
      Position: {position}
    </div>
  );
}

 

atomFamily()는 간단한 atom()으로써 거의 동일한 옵션을 사용합니다. 그러나 기본값을 매개변수화 할 수도 있습니다. 즉, 매개 변수값을 받아 실제 기본값을 반환하는 함수를 제공할 수 있습니다.

const myAtomFamily = atomFamily({
  key: ‘MyAtom’,
  default: param => defaultBasedOnParam(param),
});

 

또는, selector 대신 selectorFamily를 사용하면 default selector에서도 매개 변수 값에 접근할 수 있습니다

const myAtomFamily = atomFamily({
  key: ‘MyAtom’,
  default: selectorFamily({
    key: 'MyAtom/Default',
    get: param => ({get}) => {
      return computeDefaultUsingParam(param);
    },
  }),
});

selectorFamily(options)

개요

'selectorFamily'는 Recoil에서 매개변수를 받을 수 있는 selector를 만들어주는 함수입니다. 이는 기존의 'selector'와 비슷하지만, 여러 인스턴스의 selector를 쉽게 관리할 수 있도록 도와줍니다.

 

주요 옵션

* key : selector를 식별하기 위한 고유한 문자열

* get : 값을 반환하는 함수

* set : 선택적으로 값을 설정하는 함수

 

특징

* 'selectorFamily'는 매개변수를 통해 동적으로 selector를 생성할 수 있습니다.

* 이 함수를 사용하면, 같은 매개변수에 대해서는 메모이즈된 selector 인스턴스를 반환합니다.

* 매개변수는 원시 타입 혹은 직렬화 가능한 타입이어야 합니다.

const myNumberState = atom({
  key: 'MyNumber',
  default: 2,
});

const myMultipliedState = selectorFamily({
  key: 'MyMultipliedNumber',
  get: (multiplier) => ({get}) => {
    return get(myNumberState) * multiplier;
  },
  set: (multiplier) => ({set}, newValue) => {
    set(myNumberState, newValue / multiplier);
  },
});

 

비동기 쿼리 Example

* 'selectorFamily'는 쿼리에 매개변수를 전달하는데에도 유용합니다.

const myDataQuery = selectorFamily({
  key: 'MyDataQuery',
  get: (queryParameters) => async ({get}) => {
    const response = await asyncDataRequest(queryParameters);
    if (response.error) {
      throw response.error;
    }
    return response.data;
  },
});

 

디스트럭처링 Example

const formState = atom({
  key: 'formState',
  default: {
    field1: "1",
    field2: "2",
    field3: "3",
  },
});

const formFieldState = selectorFamily({
  key: 'FormField',
  get: field => ({get}) => get(formState)[field],
  set: field => ({set}, newValue) =>
    set(formState, prevState => {...prevState, [field]: newValue}),
});

const Component1 = () => {
  const [value, onChange] = useRecoilState(formFieldState('field1'));
  return (
    <>
      <input value={value} onChange={onChange} />
      <Component2 />
    </>
  );
}

const Component2 = () => {
  const [value, onChange] = useRecoilState(formFieldState('field2'));
  return (
    <input value={value} onChange={onChange} />
  );
}

atom & atomFamily & selector & selectorFamily

항목 atom atomFamily selector selectorFamily
설명 하나의 상태를 표현 동일한 구조를 가지는 여러 상태를 동적으로 표현 상태를 계산하거나 다른 상태에 의존 매개변수를 받아 동적으로 상태를 계산하거나 다른 상태에 의존
사용 단일 상태 관리 동일한 형태의 상태가 어려 개 필요할 때 단순 상태 변환, 파생 상태 동적 파생 상태, 매개변수에 따른 상태 변환
메모이제이션 지원 안 함 지원 지원 안 함 지원
매개 변수 지원 안 함 지원 지원 안 함 지원
// atom 예시: 사용자 이름을 저장
const userNameState = atom({
  key: 'UserName',
  default: ''
});


// atomFamily 예시: 여러 개의 Todo 아이템을 동적으로 관리
const todoStateFamily = atomFamily({
  key: 'TodoItem',
  default: ''
});

// TodoItem 컴포넌트
function TodoItem({ id }) {
  const [text, setText] = useRecoilState(todoStateFamily(id));
  return <input value={text} onChange={(e) => setText(e.target.value)} />;
}
// selector 예시: 사용자 이름의 길이를 계산
const userNameLengthState = selector({
  key: 'UserNameLength',
  get: ({ get }) => {
    const userName = get(userNameState);
    return userName.length;
  }
});


// selectorFamily 예시: Todo 아이템의 상태를 기반으로 필터링
const filteredTodoStateFamily = selectorFamily({
  key: 'FilteredTodo',
  get: (filter) => ({ get }) => {
    const todoText = get(todoStateFamily(filter.id));
    return todoText.includes(filter.text);
  }
});

// 필터링된 TodoItem 컴포넌트
function FilteredTodoItem({ id, filterText }) {
  const isFiltered = useRecoilValue(filteredTodoStateFamily({ id, text: filterText }));
  return isFiltered ? <TodoItem id={id} /> : null;
}

constSelector(constant)

* 'constSelector'는 상수 값을 가진 Recoil 상태를 만드는 유틸리티입니다. 'RecoilValue'나 'RecoilValueReadOnly'와 같은 인터페이스를 가집니다. 즉, 상수 값을 'RecoilValue' 형태로 다루고 싶을 때 유용하게 사용됩니다.

 

* 메모이제이션 : 이 'selector'는 기준값 동등성(value equality)을 기준으로 메모이제이션됩니다. 즉, 'constSelector()'가 동일한 값으로 여러 번 호출될 경우, 동일한 'selector' 인스턴스가 반환됩니다. 이렇게 함으로써, 불필요한 리렌더링이나 상태 변경을 방지할 수 있습니다.

 

* 사용 사례
: 상수 값을 다른 Recoil 상태와 동일한 방식으로 관리하고 싶을 때
: 상수 값을 다른 'selector'의 계산에 재사용하고 싶을 때

type MyInterface = {
  queryForStuff: RecoilValue<Thing>,
  ...
};

const myInterfaceInstance1: MyInterface = {
  queryForStuff: selectorThatDoesQuery,
};

const myInterfaceInstance2: MyInterface = {
  queryForStuff: constSelector(thing),
};

errorSelector(message)

항상 제공된 에러를 발생시키는 selector입니다.

const myAtom = atom({
  key: 'My Atom',
  default: errorSelector('Attempt to use Atom before initialization'),
});

noWait(state)

제공된 'atom' 또는 'selector'의 현재 상태에 대한 Loadable 객체를 반환하는 selector helper 입니다.

 

이 함수는 잠재적인 비동기 종속의 현재 상태를 빠르게 가져올 수 있도록 해줍니다. 다른 Recoil selector와 hook에서 함께 사용할 수 있습니다.

 

차이점

* 'useRecoilValueLoadable()'은 hook이며, 컴포넌트 내에서만 사용할 수 있습니다.
* 'noWait()'은 selector를 반환하기 때문에, 다른 Recoil selector에서도 사용할 수 있습니다.

 

주요 특징

* 에러가 발생하거나 종속이 보류 중인 경우에도 프로그램이 중단되지 않습니다.
* 비동기 처리 상태를 Loadable 객체로 반환합니다.

 

사용 케이스

* 비동기 작업의 현재 상태를 다른 Recoil slector에서 알고 싶을 때
* 여러 비동기 작업의 현재 상태를 한 번에 확인하고 싶을때
* 에러 핸들링을 별도로 구현하고 싶을때

const asyncDataSelector = selector({
  key: 'asyncData',
  get: async () => {
    const response = await fetchData();
    return response.data;
  },
});

const statusSelector = selector({
  key: 'status',
  get: ({get}) => {
    const loadable = get(noWait(asyncDataSelector));
    return loadable.state;  // 'hasValue', 'hasError', or 'loading'
  },
});
const myQuery = selector({
  key: 'MyQuery',
  get: ({get}) => {
    const loadable = get(noWait(dbQuerySelector));

    return {
      hasValue: {data: loadable.contents},
      hasError: {error: loadable.contents},
      loading: {data: 'placeholder while loading'},
    }[loadable.state];
  }
})

waitForAll(dependencies)

'waitForAll'은 여러 비동기 종속성을 동시에 처리할 수 있는 도구입니다. 이것은 Recoil의 동시성(concurrency) helper 중 하나로, 여러 Recoil 상태값을 한번에 가져올 수 있습니다.

* 'waitForAll'은 튜플 배열이나 객체에 명명된 종속성을 입력으로 받습니다.
* 반환값은 'RecoilValueReadOnly' 타입이며, 이를 통해 여러 상태값을 읽을 수 있습니다.

 

이 helper는 Recoil hook과 함께 사용될 수 있으며, 다른 Recoil selector의 종속성으로도 사용될 수 있습니다.

// React 컴포넌트에서 사용하는 예
function FriendsInfo() {
  const [friendA, friendB] = useRecoilValue(
    waitForAll([friendAState, friendBState])
  );
  return (
    <div>
      Friend A Name: {friendA.name}
      Friend B Name: {friendB.name}
    </div>
  );
}



// Recoil selector에서 사용하는 예
const friendsInfoQuery = selector({
  key: 'FriendsInfoQuery',
  get: ({get}) => {
    const {friendList} = get(currentUserInfoQuery);
    const friends = get(waitForAll(
      friendList.map(friendID => userInfoQuery(friendID))
    ));
    return friends;
  },
});

waitForAllSettled(dependencies)

요청된 종속성 현재 상태에 대한 Loadables 집합을 반환하는 동시성(concurrency) helper입니다. 최소한 모든 종속성이 값 상태 또는 오류 상태가 될 때까지 기다립니다.

waitForAllSettled()는 waitForNone()과 비슷하지만, 종속성이 로딩 상태에 있는 동안 대기한다는 점이 다릅니다.


waitForNone(dependencies)

waitForNone()은 주어진 종속성들의 현재 상태 정보를 담은 Loadable의 집합을 즉시 반환합니다. 종속성들은 배열이나 객체 형태로 제공될 수 있습니다. 이 helper는 'waitForAll()'과 비슷하지만, 즉시 반환한다는 점과 각각의 종속성에 대한 Loadable을 반환한다는 점에서 차이가 있습니다. 'noWait()'과 비슷하지만, 한 번에 여러 종속성을 요청할 수 있다는 점에서 다릅니다.

* 활용 : 이 유틸은 UI를 점진적으로 업데이트 할 때 유용합니다. 예를 들어, 여러 레이어로 이루어진 차트를 그릴 때 각 레이어의 데이터 로딩 상태에 따라 다르게 보여줄 수 있습니다. 데이터가 로딩 중이면 스피너를 보여주고 데이터가 로딩 완료되면 그 레이어를 그립니다. 만약 데이터 로딩에 문제가 생기면 오류 메시지를 보여줄 수 있습니다.

이렇게 하면 사용자는 데이터가 준비되는 대로 차트를 볼 수 있으며, 어떤 부분에서 문제가 생겼는지도 알 수 있습니다.


waitForAny(dependencies)

'waitForAny()'는 주어진 종속성들 중에서 적어도 하나 이상이 사용 가능해질 때까지 기다린 후, 해당 종속성들의 현재 상태 정보를 담은 Loadable의 집합을 반환합니다. 종속성들은 배열이나 객체 형태로 제공될 수 있습니다. 이 helper는 'waitForNone()'과 비슷하지만, 적어도 하나의 종속성이 사용 가능해질 때까지 기다린다는 점에서 다릅니다.

* 활용 : 이 도우미는 여러 데이터를 동시에 로딩할 때 유용하게 사용될 수 있습니다. 예를 들어, 여러 카테고리의 뉴스를 불러올 때, 하나의 카테고리 뉴스가 로딩되면 먼저 보여줄 수 있습니다. 이렇게 하면 사용자는 더 빨리 정보를 얻을 수 있습니다.


  waitForAll waitForAllSettled waitForNone waitForAny
Behavior 모든 종속성이 사용 가능해질 때까지 기다림 모든 종속성이 값 상태나 오류 상태가 될 때까지 기다림 즉시 반환, 종속성의 현재 상태를 알려줌 하나 이상의 종속성이 사용 가능해질 때까지 기다림
Returns 모든 종속성의 값 모든 종속성의 현재 상태
(값, 로딩, 오류)
모든 종속성의 현재 상태
(값, 로딩, 오류)
점진적으로 UI를 업데이트 할 때
Use Case 모든 데이터가 필요한 경우 데이터의 로딩 상태나 오류 정보가 필요한 경우 점진적으로 UI를 업데이트 할 때 최소한 하나의 데이터가 빨리 필요한 경우

useRecoilTransaction()

* UNSTABLE


useRecoilCallback()

'useRecoilCallback'은 Recoil에서 제공하는 특별한 hook입니다. 이 hook은 'useCallback()'과 유사하나, Recoil 상태에 대한 작업을 수행하는 데 특화되어 있습니다. 이 hook을 사용하면 다음과 같은 일을 할 수 있습니다.

1. Recoil 상태를 비동기적으로 읽기
2. 렌더링 없이 비동기 연산을 수행
3. 부수효과(side-effects)를 실행하여 Recoil 상태를 읽거나 변경
4. 렌더링 시점에 어떤 atom이나 selector가 업데이트가 필요한 지 모르는 경우 동적으로 업데이트
5. 데이터를 미리 가져오기 (Pre-fetching)

 

콜백 인터페이스

* snapshot: 현재 Recoil 상태의 읽기 전용 스냅샷을 제공합니다.
* gotoSnapshot: 전역 상태를 특정 스냅샷으로 업데이트 합니다.
* set: atom이나 selector의 값을 설정합니다.
* reset: atom이나 selector의 값을 초기화합니다.

import {atom, useRecoilCallback} from 'recoil';

const itemsInCart = atom({
  key: 'itemsInCart',
  default: 0,
});

function CartInfoDebug() {
  const logCartItems = useRecoilCallback(({snapshot}) => async () => {
    const numItemsInCart = await snapshot.getPromise(itemsInCart);
    console.log('Items in cart: ', numItemsInCart);
  });

  return (
    <div>
      <button onClick={logCartItems}>Log Cart Items</button>
    </div>
  );
}