4. 비동기 데이터 쿼리

Redux 에선 비동기 데이터 쿼리를 위해 redux-thunk, redux-observable, redux-saga 등의 Middleware 를 사용해야 합니다.

Middleware 는 action 을 dispatch 하고 reducer 에서 상태 업데이트를 하기 전 비동기 처리(예: 네트워크 요청, setTimeout)를 하는 중간자 역할이라고 보면 됩니다.

Redux Middleware 는 비동기 처리를 위해 여전히 강력한 도구지만 이를 위해 추가되는 boilerplate 코드가 많아지는 것은 무시할 수 없습니다.

당연히도 앱의 규모가 커질 수록 복잡도가 늘어나고 코드 양이 더욱 방대해집니다.

이와는 다르게 Recoil 에선 동기 / 비동기 함수 모두 selector 에서 처리할 수 있습니다.

단, React 의 render() 함수가 동기이기 때문에 promise 가 resolve 되기 전에 렌더링 할 수가 없습니다.

이때 대기중인 데이터를 처리하기 위해 Recoil 은 React Suspense 와 함께 사용되도록 디자인되어있습니다.

아래의 예처럼 컴포넌트를 Suspense 로 감싸서 대기중인 하위 항목들을 잡아내고 fallback UI 를 대신 렌더합니다.

// atom.js
export const todoIdState = atom({
  key: "todoIdState",
  default: 1
});

export const todoItemQuery = selector({
  key: "todoItemQuery",
  get: async ({ get }) => {
    const id = get(todoIdState);

    const response = await axios.get(
        `https://jsonplaceholder.typicode.com/todos/${id}`
    );

    return response.data;
  }
});

// App.js
import { RecoilRoot } from "recoil";
import { Suspense } from "react";

import Container from "./container";

export default function App() {
  return (
      <RecoilRoot>
        <Suspense fallback={() => <p>Loading...</p>}>
          <Container />
        </Suspense>
      </RecoilRoot>
  );
}

// container/index.js
import { todoItemQuery } from "../atom";
import { useRecoilValue } from "recoil";

const Container = () => {
  const data = useRecoilValue(todoItemQuery);

  return <div>{data.title}</div>;
};

export default Container;

파라미터에 따라 비동기 데이터 요청

파라미터를 기반으로 쿼리하고 싶을 땐 selectorFamily 를 사용할 수 있습니다.

// atom.js
import axios from 'axios';
import { selectorFamily } from 'recoil';

export const todoItemQuery = selectorFamily({
  key: "todoItemQuery",
  get: (id) => async () => {
    const response = await axios.get(
        `https://jsonplaceholder.typicode.com/todos/${id}`
    );

    return response.data;
  }
});


// App.js
import { RecoilRoot } from "recoil";
import { Suspense } from "react";

import Container from "./container";

export default function App() {
  return (
      <RecoilRoot>
        <Suspense fallback={() => <p>Loading...</p>}>
          <Container id={1} />
        </Suspense>
      </RecoilRoot>
  );
}


// container/index.js
import { todoItemQuery } from "../atom";
import { useRecoilValue } from "recoil";

const Container = ({ id }) => {
  const data = useRecoilValue(todoItemQuery(id));

  return <div>{data.title}</div>;
};

export default Container;

Last updated