본문 바로가기

React

React recoil Asynchronous (비동기처리)

반응형

recoil 을 사용하면서 가장 마음에 들었던 부분은 비동기 처리가 별도 라이브러리 없이 recoil 만으로 가능하다는 것이다.

 

recoil 의 selector 를 활용하면 쉽게 비동기 데이터를 가져올 수 있다.

 

이를 위해 recoil state 를 작성한다. (./src/states/board.js)

import { atom, selector } from 'recoil';
import axios from 'axios';

export const boardSearchState = atom({
    key: 'boardSearchState',
    default: {
        writer: '',
        title: '',
        content: '',
    }
});

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

export const boardListSelector = selector({
    key: 'boardListSelector',
    get: async ({ get }) => {
        get(forceReloadBoardListState);
        const searchParams = get(boardSearchState);

        const { data } = await axios.get('/api/v1/board', {
            params: searchParams
        });

        return data;
    },
    set: ({ set }) => {
        set(forceReloadBoardListState, Math.random());
    }
});
  • Line 4~11 : board 검색 조건에 대한 atom 객체
  • Line 13~16 : board 를 리로드하기 위한 atom 객체
  • Line 18~33 : board list 를 가져오기 위한 비동기 selector 객체
  • Line 21 : 리로드를 위해 selector 에서 구독
  • Line 22 : 검색조건 구독
  • Line 24~26 : 비동기 요청
  • Line 30~32 : selector 의 set 함수 이지만 내부에서 forceReloadBoardListState 객체의 상태 값을 랜덤 숫자로 변경. forceReloadBoardListState 를 구독하고 있는 selector 를 강제로 리로드 하기 위함.

비동기 selector 를 구독하는 컴포넌트 생성 (./src/components/Board.jsx)

import React, { Suspense } from 'react';
import { useRecoilValue } from 'recoil';
import { boardListSelector } from 'states/board';

const Board = () => {
    const boardList = useRecoilValue(boardListSelector);

    return (
        <Suspense fallback={<div>Loading...</div>}>
            <table>
                <tbody>
                    {boardList.map((row, idx) => (
                        <tr key={idx}>
                            <td>{row.number}</td>
                            <td>{row.title}</td>
                            <td>{row.writer}</td>
                            <td>{row.createdAt}</td>
                        </tr>
                    ))}
                </tbody>
            </table>
        </Suspense>
    );
}

export default Board;
  • Line 6 : selector 를 useRecoilValue 함수를 통해 구독
  • Line 9 : 비동기 selector 의 값을 사용하려면 React 에서 제공하는 Suspense 로 묶어야 함. 이 때, fallback 프로퍼티를 통해 아직 값을 가져오지 못했을 때 렌더링 할 컴포넌트를 제공해야 한다.

하지만 나는 Suspense 로 묶으면 화면이 렌더링 될 때, fallback 이 화면을 가려버려서 화면전환이 매끄럽지 못한것같다는 느낌을 받았다. 

 

그래서 나는 recoil 에서 제공하는 useRecoilValueLoadable 과 react 의 useMemo 를 사용해서 렌더링 하였다.

import React, { useMemo } from 'react';
import { useRecoilValueLoadable } from 'recoil';
import { boardListSelector } from 'states/board';

const Board = () => {
    const boardList = useRecoilValueLoadable(boardListSelector);

    const rows = useMemo(() => {
        return boardList?.state === 'hasValue' ? boardList?.contents : []
    }, [boardList]);

    return (
        <table>
            <tbody>
                {rows.map((row, idx) => (
                    <tr key={idx}>
                        <td>{row.number}</td>
                        <td>{row.title}</td>
                        <td>{row.writer}</td>
                        <td>{row.createdAt}</td>
                    </tr>
                ))}
            </tbody>
        </table>
    );
}

export default Board;

위 소스는 편의상 작성하였지만 상황에 따라 에러처리나 로딩 처리를 해주는 것이 좋을 것 같다.

※ Loadable 객체는 state 와 contents 프로퍼티를 가지고 있다.
state : atom 이나 selector 의 상태를 보여준다. (hasValue, hasError, loading)
contents : atom 이나 selector 의 값이며, 상태에 따라 다른 값을 갖고 있다. 
( hasValue - value , hasError - Error , loading - Promise )
  • Line 6 : useRecoilValueLoadable 을 통해 selector를 구독
  • Line 8~10 : react 의 useMemo Hook 을 통해 해당 selector 가 값이 있으면 데이터를 리턴하고, 아직 갖고 오지 못했으면 빈 배열([]) 을 리턴

또한, 목록 조회에 리로드 기능이 필요하다면 전에 만들었던 selector 의 set 함수를 그대로 호출하면 된다.

...
import { useRecoilStateLoadable } from 'recoil'
...

const [boardList, reload] = useRecoilStateLoadable(boardListSelector);

const handleClickReload = () => {
	reload();
};

...
생략
...

 

이와 같이 recoil 을 통한 비동기 요청 및 응답은 아주 간단한다.

하루빨리 recoil 이 정식으로 릴리즈 하는 날을 기대한다!

반응형