[Next.js/Jotai] Jotai 로 상태 관리하기 (Jotai 사용법)

Jotai 로 상태 관리하기 (Jotai 사용법)

서론

열심히 리액트 스터디 중이던 인피덕,

회사에서 사용할 프론트 기술 스택을 논의하다 최종적으로 Next.jsJotai 가 결정되는데…

인생 처음 접해보는 라이브러리와 프레임 워크를 정리해 보도록 하자.

Jotai 를 사용하는 방법 (공식 문서 발췌)

  • initalValue 로 간단하게 만들기

  • read 용으로 만들기

  • write 용으로 만들기

  • read, write 모두 가능하게 만들기

Jotai 를 검색했을 때 뜨는 글들이 거의 다 비슷한 내용이라 내 입맛대로 정리하기 위해 몇 자 적어본다.

스터디 중이라 틀린 부분이 있을 수 있으니 발견하시면 댓글 남겨주시기 바랍니다.

조타이가 조타잉~ 이라고 외치는 여러분이 되시길 바랍니다. 물론 저 부터…

본론

안녕하세요, 처음 뵙겠습니다. atom

atom 이란 녀석을 사용해서 선언

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

// atom 이라는 녀석으로 countAtom 이라는 atom value 를 하나 만들어 준다.
const countAtom = atom(0);

const App = () => {
  // 만든 countAtom 녀석을 useAtom 이라는 친구를 활용해 우리가 사용할 수 있도록 만들어 준다.
  // 튜플로 받는 이유: useAtom 이 튜플로 리턴함 (첫번째: 값, 두번째: 값 set 하는 친구)
  // 형식이 리액트의 setState 와 거의 매우 완전 대박 비슷
  const [count, setCount] = useAtom(countAtom);

  return (
    <>
      몇번 눌렀을까?: {count}
      <br />
      <button onClick={() => setCount((prev) => prev + 1)}>눌러보아라</button>
    </>
  );
};

export default App;

예제로 정말 아주 기본적이고 많이 사용되는 카운터를 만들어 보았다.

저 코드 그대로 복붙해서 실행해 보면 이상없이 count 가 1 씩 증가되는 걸 볼 수 있다.

사실 조타이 찾아볼 때 대부분의 예제가 카운터 예제라서 새로운 내용으로 만들어 보자 했는데 카운터로 만듦 ㅎ

atom 으로 값을 세팅할 때 가능한 애들

atom 으로 state 만들 때 값은 뭐뭐 넣을 수 있어요?

다 가능

...
const numAtom = atom(12);
const strAtom = atom("str");
const arrAtom = atom([1, 2, 3]);
const objAtom = atom({ name: "infiduk", blog: "https://infiduk.github.io/" })
...

조타이 신기한 점

(내가 리액트 state 관리를 제대로 못 해봐서 다른 상태 관리 라이브러리에도 있는 기능일 수 있지만) 조타이에는 read only, write only 를 위한 atom 을 만들 수 있는 신기한 기능이 있다고 한다.

기본적인 atom 정의

초기 값만 받아서 초기 값과 set 하는 친구를 리턴

export declare function atom<Value>(
  initialValue: Value
): PrimitiveAtom<Value> & WithInitialValue<Value>;

useAtom 정의

atom 의 값과 해당 atom 을 어느 범위까지 사용할 건지 보내고(optional), 해당하는 값과 값을 set 하는 함수를 리턴

export declare function useAtom<
  Value,
  Update,
  Result extends void | Promise<void>
>(
  atom: WritableAtom<Value, Update, Result>,
  scope?: Scope
): [Awaited<Value>, SetAtom<Update, Result>];

getter 용도로만 쓰기

아까 위의 코드를 복붙해 만들어 봤던 카운터 예제를 활용해서 테스트 해 보자.

먼저 get 을 활용한 예제부터 보도록 하자.

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

const countAtom = atom(0);
// atom 의 getter 를 활용해서 countAtom * 2 를 가져오는 녀석을 만들어 봤다.
const doubleCountAtom = atom((get) => get(countAtom) * 2);

const App = () => {
  const [count, setCount] = useAtom(countAtom);
  // 위의 countAtom 때와 동일하게 useState 의 역할을 하는 useAtom 이란 녀석으로 값을 가져와 준다.
  // 근데 왜 얘는 setDoubleCount 안 받음?
  // read only 용이라서 첫번째 리턴 값인 값만 있으면 되어 doubleCount 라는 이름으로 가져옴
  // 두번째 리턴 값은 never 형이기 때문에 받고 싶어도 받을 수 없음
  const [doubleCount] = useAtom(doubleCountAtom);

  return (
    <>
      몇번 눌렀을까?: {count}
      <br />
      누른 값 * 2: {doubleCount}
      <br />
      <button onClick={() => setCount((prev) => prev + 1)}>눌러보아라</button>
    </>
  );
};

export default App;

atomread only 의 형태로 사용할 때의 정의

read 하는 애만 받고 나도 atom 값만 주겠다.

export declare function atom<Value>(read: Read<Value>): Atom<Value>;

useAtom 정의

atom 과 사용할 범위에 대한 값을 받지만(optional) 값만 리턴 하겠다.

export declare function useAtom<Value>(
  atom: Atom<Value>,
  scope?: Scope
): [Awaited<Value>, never];

setter 도 안 써보면 섭섭하지

set 을 활용한 예제도 알아보자.

write only 형태로 쓸 때는 어떻게 사용할까?

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

const countAtom = atom(2);
const doubleCountAtom = atom((get) => get(countAtom) * 2);

// 이 친구는 only set 용이기 때문에 초기 값을 null 로 주고 write 하는 애만 세팅한 케이스
// set 메소드 안의 내용: countAtom 의 값을 countAtom 값 + value 로 할 거임
// countAtom 의 타입이 number 라서 value 의 타입을 number 로 줬음: 안 주면 unknown 타입이라고 빨간 줄 뱉어냄
const setterCountAtom = atom(null, (get, set, value: number) =>
  set(countAtom, get(countAtom) + value)
);

const App = () => {
  const [count, setCount] = useAtom(countAtom);
  const [doubleCount] = useAtom(doubleCountAtom);

  // 이 atom 친구는 가져올 값이 없어 첫 번째 란은 비워두고 (초기 값 null)
  // 두 번째 값인 set 하는 함수만 가져오도록 하자.
  const [, setOnlySetCount] = useAtom(setterCountAtom);

  return (
    <>
      몇번 눌렀을까?: {count}
      <br />
      누른 값 * 2: {doubleCount}
      <br />
      <button onClick={() => setCount((prev) => prev + 1)}>눌러보아라</button>
      <br />
      <br />
      {/* 위에서 value 를 받아 countAtom + value 로 값을 세팅하기로 했으니 3 이라는 값을 넘겨주도록 하자. */}
      <button onClick={() => setOnlySetCount(3)}>
        이거 누르면 3씩 올라가니까 눌러라
      </button>
    </>
  );
};

export default App;

atomwrite only 의 형태로 사용할 때의 정의

export declare function atom<
  Value,
  Update,
  Result extends void | Promise<void> = void
>(
  initialValue: Value,
  write: Write<Update, Result>
): WritableAtom<Value, Update, Result> & WithInitialValue<Value>;

(useAtom 은 일반적인 형태로 사용할 때와 동일)

get 할래요? set 할래요? 둘 다 할래요!

그냥 가장 기본적이고 간단하게 사용할 때와 동일한 사용 방법인데,

set 메소드를 먼저 커스텀해 줄 수 있는 부분이 특징인 듯 하다.

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

const countAtom = atom(0);
const doubleCountAtom = atom((get) => get(countAtom) * 2);
const onlySetCountAtom = atom(null, (get, set, value: number) =>
  set(countAtom, get(countAtom) + value)
);

// 아까 getter 사용한 거에 setter 만 추가한 거임 (setter 사용한 거에 getter 만 추가한 거임)
// 해당 코드는 countAtom 이라는 atom 값을 2로 바꿔 버린다는 내용이다.
const customCountAtom = atom(
  (get) => get(countAtom),
  (_, set) => set(countAtom, 2)
);

const App = () => {
  const [count, setCount] = useAtom(countAtom);
  const [doubleCount] = useAtom(doubleCountAtom);
  const [, setOnlySetCount] = useAtom(onlySetCountAtom);

  // 마찬가지로 우리가 만든 소중한 customCountAtom 을 사용하기 위해 useAtom 으로 가져온다.
  const [customCount, setCustomCount] = useAtom(customCountAtom);

  return (
    <>
      몇번 눌렀을까?: {count}
      <br />
      누른 값 * 2: {doubleCount}
      <br />
      <button onClick={() => setCount((prev) => prev + 1)}>눌러보아라</button>
      <br />
      <br />
      <button onClick={() => setOnlySetCount(3)}>
        이거 누르면 3씩 올라가니까 눌러라
      </button>
      <br />
      <br />
      커스텀 누른 값: {customCount}
      <br />
      {/* 이미 함수가 정의되어 있으니 아래처럼 함수의 이름만 써 줘도 정상 작동한다. */}
      {/* 함수 실행 문: setCustomCount() 의 형태로 쓰면 렌더링 될 때 한번 호출되니 주의하자. */}
      {/* 무슨 일이 있어도 실행 문의 형태로 쓰고 싶으면 익명 함수를 호출하는 형태로 바꿔서 쓰면 된다. (위의 setCount 형식, 파람스를 받지 않기 때문에 파람스는 필요없음) */}
      <button onClick={setCustomCount}>눌러보아라</button>
    </>
  );
};

export default App;

atom 을 위의 코드 처럼 사용할 때의 정의

export declare function atom<
  Value,
  Update,
  Result extends void | Promise<void> = void
>(
  read: Read<Value>,
  write: Write<Update, Result>
): WritableAtom<Value, Update, Result>;

(useAtom 은 일반적인 형태로 사용할 때와 동일)

이제 여러분은 조타이 공식 문서의 atom 을 사용하는 4 가지 방법을 다 알게 되었습니다. 짝짝짝


참고

이 글의 저작권은 Attribution-NonCommercial 4.0 International 라이센스를 따릅니다. Attribution-NonCommercial 4.0 International