recoil은 redux 보다 과연 좋을까❓

recoil은 redux 보다 과연 좋을까❓

·

4 min read

React 상태관리 히스토리 포스팅을 먼저 참고하시면 좋습니다 👋

포스팅에서 사용한 코드들은 Repositories 주소를 걸어놓겠습니다. 👉 https://github.com/Choi-HyunHo/recoil-study

RecoilRoot

컴포넌트에서 Recoil 의 state 를 사용하기 위해서는 redux를 사용하기전에 Provider 를 씌우듯 부모에 RecoilRoot를 선언해야 합니다.

  • 최상위 root에 선언을 했습니다.
import Counter from "./Counter";
import NumberText from "./NumberText";

// recoil 관련 코드 import
import { RecoilRoot } from "recoil";

function App() {
    return (
        <RecoilRoot>
            <Counter />
            <NumberText />
        </RecoilRoot>
    );
}

export default App;

Atoms

Atoms는 state의 단위이며 업데이트와 구독이 가능합니다.
atom 값을 읽는 컴포넌트들은 암묵적으로 atom을 구독합니다.

  • atom에 변화가 생기면 atom을 구독하는 모든 컴포넌트가 리렌더링이 됩니다.
import { atom } from "recoil";

export const counterValue = atom({
    key: "counterState", // 전역적으로 유일해야 함
    default: 0, // 초기 값
});
  • key : 내부적으로 atom을 식별 할 때 사용하는 고유한 문자열

  • default : atom의 초기값

💡 atom을 설정할 때 비동기 함수를 사용하기 위해서는 selectors를 사용해야 합니다.

useRecoilState

Atoms 및 Selectors와 상호작용하기 위해 자주 사용되는 hooks입니다.

  • useRecoilState(): atom을 읽고, 쓰기 위해 사용

다음으로 위의 counterValue 를 사용해서 초기값을 다른 컴포넌트에서 상태변화를 발생시키겠습니다.

import React from "react";
import { useRecoilState } from "recoil";
import { counterValue } from "./atom";

export default function Counter() {
    const [value, setValue] = useRecoilState(counterValue);

    const add = () => {
        setValue((prev) => prev + 1);
    };

    const minus = () => {
        setValue((prev) => prev - 1);
    };

    return (
        <div>
            <button onClick={() => add()}>+</button>
            <p>{value}</p>
            <button onClick={() => minus()}>-</button>
        </div>
    );
}

자 그럼, 값을 변경하고 다음 값은 무엇입니다. 라고 사용자에게 표시할 수 있을까요?

  • Selectors에 대해 살펴봅시다.

Selectors

위에서 잠깐 나왔던 selectors를 알아보겠습니다.

selector는 전역 상태 값을 기반으로 어떤 계산을 통해 파생된 상태(derived state)를 반환하는 순수함수입니다.

  • get : 함수만 제공되면 selector는 읽기만 가능한 RecoilValueReadOnly 객체를 반환합니다.

  • set : 함수 또한 제공되며 (optional) selector는 쓰기 가능한 RecoilState 객체를 반환합니다.

selector는 기본적으로 값을 자체적으로 캐싱 합니다.
입력된 적 있는 값을 기억하고, 이 값이 재호출되면 캐싱된 결과를 바로 보여주기 때문에
비동기 데이터를 다루는 측면에서 유리 합니다.

import { atom, selector } from "recoil";

export const counterValue = atom({
    key: "counterState",
    default: 0,
});

export const counterNextValue = selector({
    key: "counterNextState",
    get: ({ get }) => {
        return get(counterValue) + 1;
    },
});

useRecoilValue

  • useRecoilValue(): atom을 읽기만 할 때 사용
import React from "react";
import { useRecoilValue } from "recoil";
import { counterNextValue } from "./atom";

export default function NumberText() {
    const nextCount = useRecoilValue(counterNextValue);
    return <p>다음 카운터는 {nextCount} 입니다.</p>;
}

그 외의 다른 hooks

  • useSetRecoilState() : atom을 쓰려고만 할 때 사용.

  • useResetRecoilState() : atom을 default 값으로 초기화 할 때 사용.

useResetRecoilState

import React from "react";
import { useRecoilValue, useResetRecoilState } from "recoil";
import { counterNextValue, counterValue } from "./atom";

export default function NumberText() {
    const nextCount = useRecoilValue(counterNextValue);
    const resetCounter = useResetRecoilState(counterValue); // 위의 atom 참고
    return (
        <>
            <p>다음 카운터는 {nextCount} 입니다.</p>
            <button onClick={resetCounter}>Reset</button>
        </>
    );
}

Recoil 의 비동기 처리 방법

1. 기본적인 async/await

selector 에 async/await를 사용해서 API를 불러오는 비동키 코드 예시 입니다.

  • API는 랜던하게 사진을 불러올 수 있는 OPEN API 를 사용하겠습니다.(https://dog.ceo/)
import { atom, selector } from "recoil";

export const dogData = selector({
    key: "dogData",
    get: async () => {
        const res = await fetch("https://dog.ceo/api/breeds/image/random");
        const data = await res.json();
        return data.message;
    },
});
import React from "react";
import { useRecoilValue } from "recoil";
import { dogData } from "./atom";

export default function Dog() {
    const dogImg = useRecoilValue(dogData);

    return (
        <div style={{ display: "flex", justifyContent: "center" }}>
            <img src={dogImg} alt="" width="20%" height="auto" />
        </div>
    );
}

image

2. selectorFamily()

selectorFamily 는 매개변수를 이용해 비동기 호출을 할 때 사용 합니다.

import { selectorFamily } from "recoil";

export const nameBreed = selectorFamily({
    key: "breed",
    get: (breed) => async () => {
        const res = await fetch(
            `https://dog.ceo/api/breed/${breed}/images/random`
        );
        const data = await res.json();
        return data.message;
    },
});
import Counter from "./Counter";

// recoil 관련 코드 import
import { RecoilRoot } from "recoil";
import NumberText from "./NumberText";
import Dog from "./Dog";

function App() {
    return (
        <RecoilRoot>
            <Counter />
            <NumberText />
            <Dog breed="pug" />
        </RecoilRoot>
    );
}

export default App;
import React from "react";
import { useRecoilValue } from "recoil";
import { dogData, nameBreed } from "./atom";

export default function Dog({ breed }) {
    // const dogImg = useRecoilValue(dogData);
    const dogImg = useRecoilValue(nameBreed(breed));

    return (
        <div style={{ display: "flex", justifyContent: "center" }}>
            <img src={dogImg} alt="" width="20%" height="auto" />
        </div>
    );
}

image

정리

recoil 을 간단하게 정리해봤습니다.
이번 포스팅 말고도 기능이 더 많기때문에 recoil을 처음 사용하게 된다면 참고하면 좋을 듯 합니다👍

마지막으로 대중적인 redux 와 짧게나마 비교를 해보자면

장점

  • 세팅이 간단합니다.

  • 비동기 요청이 직관적 입니다.

단점

  • 안정성(지속적인 업데이트 여부 확인 필요)

  • 개발자 도구의 부재

  • 부족한 레퍼런스(redux가 너무 앞도적으로 자료가 많습니다..)

참고