태그
목차

상태 업데이트하기

생성일: 2024-03-18

수정일: 2024-03-18

Flat 업데이트

Zustand로 상태를 업데이트하는 것은 간단하다! 새 상태와 함께 제공된 set 함수를 호출하면 스토어에 있는 기존 상태와 얕게 병합된다.

import { create } from 'zustand';

type State = {
  firstName: string;
  lastName: string;
};

type Action = {
  updateFirstName: (firstName: State['firstName']) => void;
  updateLastName: (lastName: State['lastName']) => void;
};

// 상태 및 (선택 사항) 액션을 모두 포함하는 스토어를 생성한다.
const usePersonStore = create<State & Action>((set) => ({
  firstName: '',
  lastName: '',
  updateFirstName: (firstName) => set(() => ({ firstName: firstName })),
  updateLastName: (lastName) => set(() => ({ lastName: lastName })),
}));

function App() {
  // 필요한 상태 및 액션을 선택한다. 이 경우 firstName 과 updateFirstName 이다.
  const firstName = usePersonStore((state) => state.firstName);
  const updateFirstName = usePersonStore((state) => state.updateFirstName);

  return (
    <main>
      <label>
        First name
        <input
          // "firstName" 상태를 업데이트한다.
          onChange={(e) => updateFirstName(e.currentTarget.value)}
          value={firstName}
        />
      </label>

      <p>
        Hello, <strong>{firstName}!</strong>
      </p>
    </main>
  );
}

깊게 중첩된 객체

다음과 같은 깁게 중첩된 객체가 있는 경우:

type State = {
  deep: {
    nested: {
      obj: { count: number };
    };
  };
};

중첩 상태를 업데이트하려면 프로세스가 불변으로 완료되도록 하기 위해 약간의 노력이 필요하다.

일반적인 접근법

React나 Redux와 유사하게, 일반적인 접근 방식은 상태 객체의 각 레벨을 복사하는 것이다. 이는 스프레드 연산자 ... 를 사용하여 새 상태 값과 수동으로 병합하여 수행된다.

normalInc: () =>
  set((state) => ({
    deep: {
      ...state.deep,
      nested: {
        ...state.deep.nested,
        obj: {
          ...state.deep.nested.obj,
          count: state.deep.nested.obj.count + 1
        }
      }
    }
  })),

너무 번거롭다! 삶을 더 편하게 만들어줄 몇 가지 대안을 살펴보자.

Immer

많은 사람들이 중첩된 값을 업데이트할 때 Immer를 사용한다. Immer는 React, Redux는 물론 Zustand에서도 중첩된 상태를 업데이트해야 할 때 언제든지 사용할 수 있다!

Immer를 사용하면 깊게 중첩된 객체에 대한 상태 업데이트 시간을 단축할 수 있다.

immerInc: () =>
  set(produce((state: State) => { ++state.deep.nested.obj.count })),

코드가 많이 간소화 되었다! 하지만 여기에 나열된 문제점에 유의한다.

optics-ts

optics-ts를 사용하는 방법도 있다.

opticsInc: () =>
  set(O.modify(O.optic<State>().path("deep.nested.obj.count"))((c) => c + 1)),

Immer와 달리 optics-ts는 프록시나 뮤테이션 구문을 사용하지 않는다.

Ramda

Ramda를 사용하는 방법도 있다.

ramdaInc: () =>
  set(R.over(R.lensPath(["deep", "nested", "obj", "count"]), (c) => c + 1)),