본문 바로가기
SPA/React

[React] recoil 사용하기

by J4J 2021. 10. 24.
300x250
반응형

안녕하세요. J4J입니다.

 

이번 포스팅은 recoil 사용하는 방법에 대해 적어보는 시간을 가져보려고 합니다.

 

 

 

Recoil이란?

 

recoil이란 react에서 사용할 수 있는 자체적인 상태 관리로 기존에 사용되던 redux, mobx와 같이 외부 라이브러리에 의존하지 않고 react 스럽게 개발할 수 있도록 도와줍니다.

 

 - redux에 대한 추가적인 정보는 여기를

 - mobx에 대한 추가적인 정보는 여기를 참고해주세요.

 

 

 

실제 찾아보거나 계산해본것은 아니지만 react에서 현재까지 가장 많이 사용되고 있는 상태 관리는 redux라고 생각합니다.

 

하지만 redux에는 보일러 플레이트라는 많은 개발자분들이 불만을 토로하는 문제가 있습니다.

 

store를 구성하기 위해서는 복잡하고 많은 코드들을 구성해야 되서 사용하기 번거롭지만 개발 편리성과 효율성을 위해서는 redux를 사용해야만 했습니다.

 

마치 계륵같은 존재라고도 갑자기 생각이 드네요.

 

 

 

이런 상황속에서 드디어 react 자체적인 상태 관리로 recoil이란 것이 등장했습니다.

 

간단하게 사용해본 결과 redux, mobx보다 충분히 더 매력적인 포인트들이 있다고 보였기 때문에 지금까지 습득한 지식에 대해 정리를 해보려고 합니다.

 

 

 

Recoil 설정

 

우선 간단하게 설정을 한 뒤 상태관리가 정상적으로 동작될 수 있는지를 확인해보겠습니다.

 

테스트 코드를 작성해볼 것은 위에 redux, mobx에 링크를 걸어둔 것과 동일한 예시를 타입 스크립트를 이용하여 작성해보겠습니다.

 

 

 

[ 1. 패키지 설치 ]

 

$ npm install recoil

 

 

 

[ 2. index.tsx에 RecoilRoot 추가 ]

 

import * as React from 'react';
import ReactDom from 'react-dom';
import App from './App';
import { RecoilRoot } from 'recoil'

ReactDom.render(
    <RecoilRoot>
        <App />
    </RecoilRoot>, 
    document.querySelector('#root')
);

 

 

반응형

 

 

[ 3. /src/modules/recoil에 numberRecoil.tsx 생성 ]

 

import { atom } from 'recoil'

// atom → 전역적으로 사용될 state를 의미
export const _num = atom<number>({
    key: '_num', // state를 구분하기 위한 key값
    default: 0, // state의 초기 값
})

 

 

 

[ 4. App.tsx 내용 수정 ]

 

import * as React from 'react';
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { _num } from './modules/recoil/numberRecoil';

const App = (): JSX.Element => {
    
    const [num, setNum] = useRecoilState<number>(_num); // getter와 setter를 동시에 가져올 경우
    // const num = useRecoilValue<number>(_num); // getter만 가져올 경우
    // const setNum = useSetRecoilState<number>(_num); // setter만 가져올 경우

    const onClickIncrease = () => {
        setNum(num+3); // 3만큼 증가
    }

    const onClickDecrease = () => {
        setNum(num-2); // 2만큼 감소
    }

    return (
        <div>
            <p>현재 값: {num}</p>

            <button onClick={onClickIncrease}>증가</button>
            <button onClick={onClickDecrease}>감소</button>
        </div>
    )
}

export default App;

 

 

 

코드를 위와 같이 작성하고 실행해보면 다음과 같은 실행 결과를 확인할 수 있습니다.

 

실행 결과

 

 

 

※ 추가 → 만약 타입 스크립트 사용하시는 분들 중 위와 같이 설정을 했는데도 정상적으로 동작되지 않는다면 tsconfig.json 파일의 "jsx" 값을 확인해보셔야 할 것 같습니다.

 

"preserve"로 되어있다면 "react"로 변경하시길 바랍니다.

 

 

 

커스텀 Recoil

 

제가 올렸던 redux, mobx의 코드와 위의 recoil 코드를 비교해보면 recoil이 보다 편리하고 복잡하지 않게 사용될 수 있다는 것을 느끼실 수 있을 겁니다.

 

또한 기존에 hooks를 사용하셨던 분들이라면 useState와 유사한 형태를 띠기 때문에 더 친근하게 느껴지실 수도 있을 겁니다.

 

하지만 recoil 코드를 작성하고 제가 처음 느꼈던 것은 store와 관련된 공통 작업은 어떻게 정의를 해야 될까? 였습니다.

 

예를 들면 A, B파일에서 공통적으로 값이 10이상일 경우와 10 미만일 경우 처리되는 게 다르다고 할 때 atom에서는 getter, setter밖에 제공을 해주지 않아서 관련 코드를 A, B파일에 둘 다 작성해야 된다는 생각을 했습니다.

 

물론 이런 상황같은 경우는 이후에 알게 된 selector를 이용해도 될 것 같지만 최근 공부하고 있는 svelte의 store사용 방식을 참고하여 저만의 커스텀 recoil을 만들어서 해결해봤습니다.

 

 

728x90

 

 

[ 1. numberRecoil.tsx파일 수정 ]

 

import { atom, useRecoilState } from 'recoil'

// atom → 전역적으로 사용될 state를 의미
export const _num = atom<number>({
    key: '_num', // state를 구분하기 위한 key값
    default: 0, // state의 초기 값
})

// 커스텀 recoil
export const _customNum = () => {
    const [num, setNum] = useRecoilState<number>(_num);

    return {
        num: num,
        increase: () => {
            if(num >= 10) { // num이 10이상일 경우
                setNum(num+1);
            } else { // num이 10미만일 경우
                setNum(num+3);
            }
        },
        decrease: () => {
            if(num >= 10) { // num이 10이상일 경우
                setNum(num-2);
            } else { // num이 10미만일 경우
                setNum(num-1);
            }
        }
    }
}

 

 

 

[ 2. App.tsx파일 수정 ]

 

import * as React from 'react';
import { _customNum } from './modules/recoil/numberRecoil';

const App = (): JSX.Element => {

    const customNum = _customNum();

    return (
        <div>
            <p>현재 값: {customNum.num}</p>

            <button onClick={customNum.increase}>증가</button>
            <button onClick={customNum.decrease}>감소</button>
        </div>
    )
}

export default App;

 

 

 

Selector

 

recoil에서 selector는 파생된 상태의 일부를 나타내는 것으로 순수함수로 구성되어야 하며 만들어진 atom들의 동적 처리를 도와줍니다.

 

예를 들면 데이터를 가져와 사용할 때 store에 저장된 num값을 num*num으로 처음부터 가져와서 사용할 수 있게 한다든가 또는 store에 값을 저장할 때 위의 커스텀 recoil과 같이 저장될 수 있도록 도와줍니다.

 

파일을 다시 한번 더 수정하여 보여드리겠습니다.

 

 

 

[ 1. numberRecoil.tsx파일 수정 ]

 

import { atom, selector, useRecoilState } from 'recoil'

// atom → 전역적으로 사용될 state를 의미
export const _num = atom<number>({
    key: '_num', // state를 구분하기 위한 key값
    default: 0, // state의 초기 값
})

// selector
export const _selectorNum = selector<number>({
    key: '_selectorNum', // selector를 구분하기 위한 key값
    get: ({get}) => { // getter
        const num = get(_num); // _num에 해당하는 atom 가져오기

        return num*num; // num*num으로 리턴
    },
    set: ({get, set}, payload) => { // setter
        const num = get(_num); // _num에 해당하는 atom 가져오기
        if(num >= 10 && payload === 1) { // num이 10이상이고 증가해야 될 경우
            set(_num, num+1); // _num에 해당하는 atom에 num+1을 저장
        } else if(num < 10 && payload === 1) { // num이 10미만이고 증가해야 될 경우
            set(_num, num+3); // _num에 해당하는 atom에 num+3을 저장
        } else if(num >= 10 && payload === -1) { // num이 10이상이고 감소해야 될 경우
            set(_num, num-2); // _num에 해당하는 atom에 num-2를 저장
        } else if(num < 10 && payload === -1) { // num이 10미만이고 감소해야 될 경우
            set(_num, num-1);// _num에 해당하는 atom에 num-1을 저장
        }
    }
})

 

 

 

 

[ 2. App.tsx파일 수정 ]

 

import * as React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { _num, _selectorNum } from './modules/recoil/numberRecoil';

const App = (): JSX.Element => {

    const num = useRecoilValue<number>(_num);
    const [selectorNum, setSelectorNum] = useRecoilState<number>(_selectorNum);

    return (
        <div>
            <p>현재 값: {num}</p>
            <p>현재 selector 값: {selectorNum}</p>

            <button onClick={() => setSelectorNum(1)}>증가</button>
            <button onClick={() => setSelectorNum(-1)}>감소</button>
        </div>
    )
}

export default App;

 

 

 

코드를 작성하고 실행해보면 다음과 같은 결과를 확인할 수 있습니다.

 

실행 결과

 

 

 

추가적으로 다른 분들이 작성해둔 글을 확인해보면 비동기 처리, suspense 활용 등과 관련된 것들을 볼 수 있었는데 궁금하신 분들은 여기를 참고해주시길 바랍니다.

 

 

 

 

 

 

 

이상으로 recoil 사용하는 방법에 대해 간단하게 알아보는 시간이었습니다.

 

읽어주셔서 감사합니다.

 

 

 

728x90
반응형

댓글