redux, recoil 그리고 jotai..❓

redux, recoil 그리고 jotai..❓

·

4 min read


jotai..❓

jotai(조타이라고 부릅니다)

대중적인 상태관리 도구는 redux이긴 하지만 다른 도구도 어느정도 지식이라도 알고 있으면 앞으로 기술을 적용하거나 프로젝트를 하게 될 때 규모에 따라서 선택 할 수 있을 것 같았습니다..(물론 개인적인 바램..ㅎ)

이제 jotai에 대해 짧은 여행을 떠나봅시다 😀

jotai 의 뜻

jotai

jotai 는 일본어로 '상태' 라는 뜻을 가지고 있습니다.

공식 문서 소개에 따르면

Jotai는 Recoil에서 영감을 받아 글로벌 React 상태 관리에 대한 원자적 접근 방식을 취합니다.

아톰과 렌더를 결합하여 빌드 상태는 아톰 종속성에 따라 자동으로 최적화됩니다. 이는 React 컨텍스트의 추가 재렌더링 문제를 해결하고, 메모이제이션의 필요성을 제거하며, 선언적 프로그래밍 모델을 유지하면서 유사한 개발자 경험을 신호에 제공합니다.

useState간단한 대체에서 복잡한 요구 사항이 있는 엔터프라이즈 TypeScript 애플리케이션으로 확장됩니다 . 또한 그 과정에서 도움이 되는 다양한 유틸리티와 통합 기능이 있습니다!

특징으로는

  • 매우 최소한의 API + TypeScript 기본 내장

  • 작은 번들 크기

  • 많은 추가 유틸리티 및 공식 통합

  • 리액트에서만 사용 가능(리액트 전용)

  • atomic한 상태 관리 방식으로 구성

  • Next.js, React Native 에서도 사용 가능

이제부터 사용법에 대해 살펴보겠습니다

recoil 처럼 값을 선언하기 위한 atom 생성

recoil의 atom 과는 달리 key를 선언 할 필요가 없습니다.

import React from "react";
import { atom } from "jotai";

const counter = atom(0);

export default function Counter() {
    return <div>counter</div>;
}

그리고 atom의 상태변화를 위해서는 useAtom 을 사용 합니다.

import React from "react";
import { atom, useAtom } from "jotai";

const counter = atom(0);

export default function Counter() {
    const [count, setCount] = useAtom(counter);

    return <div>{count}</div>;
}

포인트는 useState 사용법과 동일하다는 것 입니다!!

간단한 숫자 올리는 버튼을 만들어서 상태변화를 하려면

import React from "react";
import { atom, useAtom } from "jotai";

const counter = atom(0);

export default function Counter() {
    const [count, setCount] = useAtom(counter);

    const onUp = () => {
        setCount((prev) => prev + 1);
    };

    return (
        <div>
            <p>{count}</p>
            <button onClick={() => onUp()}>+</button>
        </div>
    );
}

생성된 값을 Read 만 싶을 때는 useAtomValue

import React from "react";
import { atom, useAtom, useAtomValue } from "jotai";

const counter = atom(0);
const readCounter = atom(1);

export default function Counter() {
    const [count, setCount] = useAtom(counter);
    const readCount = useAtomValue(readCounter);

    const onUp = () => {
        setCount((prev) => prev + 1);
    };

    return (
        <>
            <div>
                <p>{count}</p>
                <button onClick={() => onUp()}>+</button>
            </div>
            <div>
                <p>{readCount}</p>
            </div>
        </>
    );
}
  • 반대로 생성된 값을 write 만 싶을 때는 useSetAtom 를 사용 합니다.

get 파라미터를 이용하여 다른 atom의 값을 참조받는 법

import React from "react";
import { atom, useAtom, useAtomValue } from "jotai";

const counter = atom(0);
const readCounter = atom(1);
const getCounterAtom = atom((get) => get(counter) * 2);

export default function Counter() {
    const [count, setCount] = useAtom(counter);
    const readCount = useAtomValue(readCounter);
    const readGetCount = useAtomValue(getCounterAtom);

    const onUp = () => {
        setCount((prev) => prev + 1);
    };

    return (
        <>
            <div>
                <p>count : {count}</p>
                <button onClick={() => onUp()}>+</button>
            </div>
            <div>
                <p>readCount: {readCount}</p>
            </div>
            <div>
                <p>readGetCount: {readGetCount}</p>
            </div>
        </>
    );
}

다른 컴포넌트에서 atom의 값을 참조받아 API 호출해보기

  • tanstack query(구 react-query)를 같이 사용했습니다.
import React from "react";
import { atom, useAtom, useAtomValue } from "jotai";

export const counter = atom(0);
const readCounter = atom(1);
const getCounterAtom = atom((get) => get(counter) * 2);

export default function Counter() {
    const [count, setCount] = useAtom(counter);
    const readCount = useAtomValue(readCounter);
    const readGetCount = useAtomValue(getCounterAtom);

    const onUp = () => {
        setCount((prev) => prev + 1);
    };

    return (
        <>
            <div>
                <p>count : {count}</p>
                <button onClick={() => onUp()}>+</button>
            </div>
            <div>
                <p>readCount: {readCount}</p>
            </div>
            <div>
                <p>readGetCount: {readGetCount}</p>
            </div>
        </>
    );
}
import React from "react";
import { useQuery } from "@tanstack/react-query";
import { useAtomValue } from "jotai";

import { counter } from "./Counter";

export default function Post() {
    const id = useAtomValue(counter);
    const { data, isLoading, error } = useQuery(["post", id], () =>
        fetch(`https://jsonplaceholder.typicode.com/posts/${id}`).then(
            (response) => response.json()
        )
    );

    if (isLoading) {
        return <p>Loading...</p>;
    }

    if (error) {
        return <p>Error: {error.message}</p>;
    }

    console.log(data);

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

  1. counter 라는 atom의 값을 다른 컴포넌트에도 사용 할 수 있게 export 한 후

  2. 비동기 처리를 하는 컴포넌트에서 useAtomValue로 atom 의 상태 값을 참조만 할 목적이기에
    읽기 전용으로 불러왔습니다.

  3. 버튼을 누르면 counter의 값이 변하고 해당 상태를 참조하고 있는 API에 값이 업데이트 되면서 전달 됩니다.

추가로..

  • 추가로 jotai 자체적으로도 react-query를 지원하고

  • await/async 를 사용한 비동기 처리 또한 가능 합니다.

직접 react-query를 사용한 이유는 jotai-query를 이전 프로젝트에서 사용해볼때 비동기 처리를 도와주는 다양한 상태 값들 isLoading, error 등을 사용하지 못하여 효율성이 좋다고 생각하지 않았습니다.

개인적인 궁금중은 jotai를 사용하는 프로젝트는 useState가 필요할까 라는 생각을 드문드문 하고 있습니다..
(useAtom의 활용법에 대해 조금 더 생각해봐야 될 것 같습니다.)

정리

짧게나마 jotai 라는 상태관리 도구에 대해 살펴봤습니다.

사용해보면서 '간단하고, 쉽다' 라는 느낌을 많이 받았습니다. 또한 제공되는 유틸이 다양해서 상황에 맞게 쓴다면 좋은 도구가 될 것 같습니다. 항상 React 상태관리를 한다하면 redux를 공식처럼 사용하게 되는 경우가 많은데 오히려 recoil, jotai 등 프로젝트에 맞게 사용한다면 더 적은 코드 그리고 작은 번들 사이즈로 프로젝트를 관리 할 수 있을 거라고 생각 합니다.

지금까지 봤던 예시 코드 직접 확인하기

참고