티스토리 뷰

TIL

TIL 230322 Recoil Selector

2021bong 2023. 3. 22. 22:51

Recoil Selector

atoms나 다른 selectors를 입력으로 받아들이는 순수 함수(pure function)다. 상위의 atoms 또는 selectors가 업데이트되면 하위의 selector 함수도 다시 실행된다. 컴포넌트들은 selectors를 atoms처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 다시 렌더링된다.

 

Selectors는 상태를 기반으로 하는 파생 데이터를 계산하는 데 사용된다. 최소한의 상태 집합만 atoms에 저장하고 다른 모든 파생되는 데이터는 selectors에 명시한 함수를 통해 효율적으로 계산함으로써 쓸모없는 상태의 보존을 방지한다.

 

get 함수만 제공되면 Selector는 읽기만 가능한 RecoilValueReadOnly 객체를 반환한다. get 함수만 존재하는 Selectors는 useRecoilValue()를 사용해 읽을 수 있다. set 함수도 제공되면 Selector는 쓰기 가능한 RecoilState 객체를 반환한다. 보통의 RecoilState처럼 사용하면 된다.

 

동적 의존성

읽기만 가능한 selector는 의존성을 기준으로 selector의 값을 평가하는 get 메서드를 갖는다. 의존성 중 어떠한 것이 업데이트 되면 selector는 다시 평가된다. Selector를 평가할 때 실제로 사용하는 atom이나 selector를 기반으로 의존성이 동적으로 결정된다. 이전 의존성의 값에 따라 다른 추가적인 의존성을 동적으로 사용할 수 있다. 아래 예시에서 mySelector는 toggleState atom뿐만 아니라 toggleState에 의존하는 selectorA 또는 selectorB selector도 의존한다.

const toggleState = atom({key: 'Toggle', default: false});

const mySelector = selector({
  key: 'MySelector',
  get: ({get}) => {
    const toggle = get(toggleState);
    if (toggle) {
      return get(selectorA);
    } else {
      return get(selectorB);
    }
  },
});

 

간단한 Selector 사용 예시

// recoil state

// todo list에 대한 state
const todoListState = atom({
  key: 'todoListState',
  default: [],
});

// todo list의 필터 모드에 대한 state
const todoListFilterState = atom({
  key: 'todoListFilterState',
  default: 'Show All',
});
// selector 사용
import { selector } from 'recoil'; // import 해야한다.

const filteredTodoListState = selector({
  key: 'filteredTodoListState',
  get: ({get}) => {
    const filter = get(todoListFilterState); // 필요한 state를 받아옴
    const list = get(todoListState);

    switch (filter) {
      case 'Show Completed':
        return list.filter((item) => item.isComplete); // 받아 온 state에서 파생된 값
      case 'Show Uncompleted':
        return list.filter((item) => !item.isComplete);
      default:
        return list;
    }
  },
});

 

의존하는 state에서 파생된 여러 개의 값을 이용할 수도 있다.

// selector
const todoListStatsState = selector({
  key: 'todoListStatsState',
  get: ({get}) => {
    const todoList = get(todoListState);
    const totalNum = todoList.length;
    const totalCompletedNum = todoList.filter((item) => item.isComplete).length;
    const totalUncompletedNum = totalNum - totalCompletedNum;
    const percentCompleted = totalNum === 0 ? 0 : totalCompletedNum / totalNum;

    return {
      totalNum,
      totalCompletedNum,
      totalUncompletedNum,
      percentCompleted,
    };
  },
});
// component
function TodoListStats() {
  const {
    totalNum,
    totalCompletedNum,
    totalUncompletedNum,
    percentCompleted,
  } = useRecoilValue(todoListStatsState);

  const formattedPercentCompleted = Math.round(percentCompleted * 100);

  return (
    <ul>
      <li>Total items: {totalNum}</li>
      <li>Items completed: {totalCompletedNum}</li>
      <li>Items not completed: {totalUncompletedNum}</li>
      <li>Percent completed: {formattedPercentCompleted}</li>
    </ul>
  );
}

 

 

쓰기 가능한 Selector

양방향 selector는 입력 값을 매개변수로 받고 데이터 흐름 그래프를 따라 업스트림에서 변경사항을 전파하는 데 사용할 수 있다. 사용자가 selector를 새 값으로 설정하거나 selector를 재설정할 수 있기 때문에 입력 값은 selector가 나타내는 타입과 동일하거나 재설정 작업을 나타내는 DefaultValue 객체 중 하나이다.

const proxySelector = selector({
  key: 'ProxySelector',
  get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
  set: ({set}, newValue) => set(myAtom, newValue),
});

이 selector는 데이터를 변환하므로 입력 값이 DefaultValue인지 확인해야 한다.

const transformSelector = selector({
  key: 'TransformSelector',
  get: ({get}) => get(myAtom) * 100,
  set: ({set}, newValue) =>
    set(myAtom, newValue instanceof DefaultValue ? newValue : newValue / 100),
});
// set(의존하고 있는 state를, 입력받은값이 DefaultValue인지(resetter함수로 호출했는지) ? 맞으면 의존 state를 초기화 : 아니면 입력받은 값에 계산),

 

DefaultValue 타입은 set 콜백이 setter(업데이트) 함수를 통해 호출되었는지, resetter(초기화) 함수로 호출되었는지 구분해주는 역할을 한다. 컴포넌트에서 useSetRecoilState 를 사용해서 만들었다면 setter 함수가 되고, useResetRecoilState를 사용해서 만들었다면 resetter 함수가 된다. 참고 링크

export const kphState = selector<number>({
  key: 'kphState',
  get: ({ get }) => {
    const mph = get(mphState);
    return mph * 1.609;
  },
  set: ({ set, reset }, newValue) => {
    // selector의 resetter를 호출하면 newValue는 DefaultValue 인스턴스가 된다.
    if (newValue instanceof DefaultValue) {
      reset(mphState);
    } else if (typeof newValue === 'number') {
      set(mphState, newValue / 1.609);
    }
  },
});

set함수의 인자를 콘솔에 찍은 결과

+ set 함수에 인자로 넘어온 값을 콘솔에 찍어보면 get, set, reset이 있어서 구조분해 할당을 저렇게 하는게 가능하다.

 

// 예시
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>
  );
}

+ newValue는 get 함수의 리턴값 + 입력값이 들어오는 것 같다.

 

비동기 Selector

Selector는 또한 비동기 평가 함수를 가지고 있으며 Promise를 출력값으로 반환할 수 있다.

const myQuery = selector({
  key: 'MyQuery',
  get: async ({get}) => {
    return await myAsyncQuery(get(queryParamState));
  },
});
// 예시 1
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>
  );
}
// 예시 2
const currentUserNameQuery = selector({
  key: 'CurrentUserName',
  get: async ({get}) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    return response.name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameQuery);
  return <div>{userName}</div>;
}

 

리코일 셀렉터 공식 문서

리코일 코어 컨셉 공식 문서

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함