• Blog

  • Snippets

Updating State

June 4, 2024

Zustand

Zustand 공식문서를 참고하여 번역함.

# Flat Updates

Zustand를 사용하여 상태를 업데이트하는 것은 간단합니다! 제공된 set 함수를 새로운 상태와 함께 호출하면, 그 상태는 스토어의 기존 상태와 얕게 병합됩니다. 참고: 중첩된 상태에 대한 내용은 다음 섹션을 참조하세요.

import { create } from 'zustand'
type State = {
firstName: string
lastName: string
}
type Action = {
updateFirstName: (firstName: State['firstName']) => void
updateLastName: (lastName: State['lastName']) => void
}
// Create your store, which includes both state and (optionally) actions
const usePersonStore = create<State & Action>((set) => ({
firstName: '',
lastName: '',
updateFirstName: (firstName) => set(() => ({ firstName: firstName })),
updateLastName: (lastName) => set(() => ({ lastName: lastName })),
}))
// In consuming app
function App() {
// "select" the needed state and actions, in this case, the firstName value
// and the action updateFirstName
const firstName = usePersonStore((state) => state.firstName)
const updateFirstName = usePersonStore((state) => state.updateFirstName)
return (
<main>
<label>
First name
<input
// Update the "firstName" state
onChange={(e) => updateFirstName(e.currentTarget.value)}
value={firstName}
/>
</label>
<p>
Hello, <strong>{firstName}!</strong>
</p>
</main>
)
}

# Deeply nested object

깊은 상태 객체가 아래와 같다면:

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
}
}
}
})),

이 방법은 매우 번거로울 수 있습니다! 이제 삶을 더 편하게 만들어줄 몇 가지 대안을 살펴보겠습니다.

With Immer

많은 사람들이 중첩된 값을 업데이트하기 위해 Immer를 사용합니다. Immer는 React, Redux, 그리고 물론 Zustand와 같이 중첩된 상태를 업데이트해야 할 때 언제든지 사용할 수 있습니다!

깊게 중첩된 객체의 상태 업데이트를 간소화하기 위해 Immer를 사용할 수 있습니다. 예제를 살펴보겠습니다:

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

코드가 많이 줄었습니다! 여기 나열된 주의사항들을 꼭 확인하세요

With optics-ts

다른 옵션으로는 optics-ts가 있습니다:

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

Immer와 달리, optics-ts는 프록시나 mutation 문법을 사용하지 않습니다.

With Ramda

또한 Ramda를 사용할 수 있습니다:

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

ramda와 optics-ts 모두 타입과 함께 작동합니다.

CodeSandbox Demo

https://codesandbox.io/s/zustand-normal-immer-optics-ramda-updating-ynn3o?file=/src/App.tsx