recoil

Jinmin Kim·2022년 9월 24일
0

Recoil

1. atom

- atom - 일반 state

function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  return (
    <button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
      Click to Enlarge
    </button>
  );
}

컴포넌트에서 atom을 읽고 쓰려면 useRecoilState라는 훅을 사용한다. React의 useState와 비슷하지만, 상태가 컴포넌트 간에 공유될 수 있다는 차이가 있다.

useSetRecoilState() : atom의 setter

- atomfamily - 파라미터 + atom

2. selector

selector - atom + 다른 로직을 추가해서 가공해주는방법

  • get : 값 가져올때
  • set : 값 가져와서 다른 로직을태울때

위의 atoms 또는 selectors가 업데이트되면 하위의 selector 함수도 다시 실행된다. 컴포넌트들은 selectors를 atoms처럼 구독할 수 있으며 selectors가 변경되면 컴포넌트들도 다시 렌더링 된다.

selector는 readable이기에 useRecoilValue를 사용하여서 값을 읽을수있다.

function FontButton() {
  const [fontSize, setFontSize] = useRecoilState(fontSizeState);
  const fontSizeLabel = useRecoilValue(fontSizeLabelState);

  return (
    <>
      <div>Current font size: ${fontSizeLabel}</div>

      <button onClick={setFontSize(fontSize + 1)} style={{fontSize}}>
        Click to Enlarge
      </button>
    </>
  );
}

filteredTodoListState는 내부적으로 2개의 의존성 todoListFilterState와 todoListState을 추적한다. 그래서 둘 중 하나라도 변하면 filteredTodoListState는 재 실행된다.

const filteredTodoListState = selector({
  key: 'filteredTodoListState',
  get: ({get}) => {
    const filter = get(todoListFilterState);
    const list = get(todoListState);

    switch (filter) {
      case 'Show Completed':
        return list.filter((item) => item.isComplete);
      case 'Show Uncompleted':
        return list.filter((item) => !item.isComplete);
      default:
        return list;
    }
  },
});

selectorfamily (매개변수가 있는 쿼리)

  • 파라미터 + selector
const userNameQuery = selectorFamily({
  key: 'UserName',
  get: (userID) => async () => {
    const response = await myDBQuery({userID});
    if (response.error) {
      throw response.error;
    }
    return response.name;
  },
});

function UserInfo({userID}) {
  const userName = useRecoilValue(userNameQuery(userID));
  return <div>{userName}</div>;
}

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <UserInfo userID={1} />
          <UserInfo userID={2} />
          <UserInfo userID={3} />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

useRecoileState

비동기 사용

Promise를 리턴하거나 혹은 async 함수를 사용하기만 하면 됩니다. 의존성에 하나라도 변경점이 생긴다면, selector는 새로운 쿼리를 재평가하고 다시 실행시킬겁니다. 그리고 결과는 쿼리가 유니크한 인풋이 있을 때에만 실행되도록 캐시됩니다.

currentUserIDState의 값이 변경되면 변경된값을 가지고 다시 API를 날려서
값을 selectors 할수있게 한다.

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>;
}

suspense use

suspense를 사용하여서 api 같은것들이 불러오고잇을때에
대체할 화면을 보여준다.

function MyApp() {
  return (
    <RecoilRoot>
      <React.Suspense fallback={<div>Loading...</div>}>
        <CurrentUserInfo />
      </React.Suspense>
    </RecoilRoot>
  );
}

ErrorBoundary use

React <ErrorBoundary>
https://legacy.reactjs.org/docs/error-boundaries.html 참고

    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <CurrentUserInfo />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>

waitForAll (동시 요청)

자원을 많이 사용한다면 waitForAll과 같은 concurrent helper를 사용하여 병렬로 돌릴 수 있습니다.

const friendsInfoQuery = selector({
  key: 'FriendsInfoQuery',
  get: ({get}) => {
    const {friendList} = get(currentUserInfoQuery);
    const friends = get(
      waitForAll(friendList.map((friendID) => userInfoQuery(friendID))),
    );
    return friends;
  },
});

React Suspend를 사용하지 않는 비동기

보류중인 비동기 selector를 다루기 위해서 React Suspense를 사용하는 것이 필수는 아닙니다. useRecoilValueLoadable() hook을 사용하여 렌더링 중 상태(status)를 확인할 수도 있습니다.

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;
  }
}
profile
Let's do it developer

0개의 댓글