Why zustand over redux?
- Simple and un-opinionated
- Makes hooks the primary means of consuming state
- Doesn't wrap your app in context providers
- Can inform components transiently (without causing render)
Why zustand over context?
- Less boilerplate
- Renders components only on changes
- Centralized, action-based state management
zustand 설치
yarn add zustand
zustand 사용하기
First create a store
import { create } from "zustand";
const useBear = create((set) => ({
number: 0,
increaseNumber: () => set((state) => ({ number: state.number + 1 })),
deleteNumber: () => set((state) => ({ number: 0 })),
}));
export { useBear };
Then bind your components, and that's it!
import "./App.css";
import { useBear } from "./store/store";
function App() {
const { number, increaseNumber, deleteNumber } = useBear();
return (
<div>
<div>{number}</div>
<button onClick={increaseNumber}>추가</button>
<button onClick={deleteNumber}>되돌리기</button>
</div>
);
}
export default App;
Persist middleware
The Persist middleware enables you to store your Zustand state in a storage (e.g., Localstorage , Asyncstorage , IndexedpB , etc.), thus persisting it's data.
Simple example
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
const useFishStore = create(
persist(
(set, get) => ({
fishes: 0,
addAFish: () => set({ fishes: get().fishes + 1 }),
}),
{
name: 'food-storage', // unique name
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
}
)
)
import { create } from "zustand";
import "./App.css";
import { createJSONStorage, persist } from "zustand/middleware";
// import { useNumberStore } from "./stores/store";
function App() {
const useNumberStore = create(
persist(
(set, get) => ({
number: 0,
addNumber: () => set({ number: get().number + 1 }),
undoNumber: () => set({ number: get().number * 0 }),
}),
{
name: "number_storage", // unique name
storage: createJSONStorage(() => localStorage), // (optional) by default, 'localStorage' is used
}
)
);
const { number, addNumber, undoNumber } = useNumberStore();
return (
<div>
<div>{number}</div>
<button onClick={addNumber}>추가</button>
<button onClick={undoNumber}>확인</button>
</div>
);
}
export default App;
Hydration and asynchronous storages
To explain what is the "cost" of asynchronous storages, you need to understand what is hydration.
In a nutshell, hydration is a process of retrieving persisted state from the storage and merging it with the current state.
The Persist middleware does two kinds of hydration: synchronous and asynchronous. If the given storage is synchronous (e.g., localStorage), hydration will be done synchronously. On the other hand, if the given storage is asynchronous (e.g., AsyncStorage), hydration will be done asynchronously (shocking, I know!).
But what's the catch? With synchronous hydration, the Zustand store will already have been hydrated at its creation. In contrast, with asynchronous hydration, the Zustand store will be hydrated later on, in a microtask.
Why does it matter? Asynchronous hydration can cause some unexpected behaviors. For instance, if you use Zustand in a React app, the store will not be hydrated at the initial render. In cases where your app depends on the persisted value at page load, you might want to wait until the store has been hydrated before showing anything. For example, your app might think the user is not logged in because it's the default, but in reality the store has not been hydrated yet.
API
Version: >=3.6.3
The Persist API enables you to do a number of interactions with the Persist middleware from inside or outside of a React component.
getOptions
Type: () => Partial<PersistOptions>
Returns: Options of the Persist middleware
For example, it can be used to obtain the storage name:
useBoundStore.persist.getOptions().name
setOptions
Type: (newOptions: Partial<PersistOptions>) => void
Changes the middleware options. Note that the new options will be merged with the current ones.
For instance, this can be used to change the storage name:
useBoundStore.persist.setOptions({
name: 'new-name',
})
Or even to change the storage engine:
useBoundStore.persist.setOptions({
storage: createJSONStorage(() => sessionStorage),
})
clearStorage
Type: () => void
Clears everything stored under the name key.
useBoundStore.persist.clearStorage()
rehydrate
Type: () => Promise<void>
In some cases, you might want to trigger the rehydration manually. This can be done by calling the rehydrate method.
await useBoundStore.persist.rehydrate()
hasHydrated
Type: () => boolean
This is a non-reactive getter to check if the storage has been hydrated (note that it updates when calling rehydrate).
useBoundStore.persist.hasHydrated()
onHydrate
Type: (listener: (state) => void) => () => void
Returns: Unsubscribe function
This listener will be called when the hydration process starts.
const unsub = useBoundStore.persist.onHydrate((state) => {
console.log('hydration starts')
})
// later on...
unsub()
onFinishHydration
Type: (listener: (state) => void) => () => void
Returns: Unsubscribe function
This listener will be called when the hydration process ends.
const unsub = useBoundStore.persist.onFinishHydration((state) => {
console.log('hydration finished')
})
// later on...
unsub()
'내일배움캠프[4기_Reac트랙] > TIL' 카테고리의 다른 글
내일배움캠프 React트랙 69일차 회고 (2022.02.04) (0) | 2023.02.03 |
---|---|
내일배움캠프 React트랙 68일차 회고 (2022.02.03) (0) | 2023.02.03 |
내일배움캠프 React트랙 57일차 회고 (2022.01.19) (0) | 2023.01.19 |
내일배움캠프 React트랙 56일차 회고 (2022.01.17) (0) | 2023.01.18 |
내일배움캠프 React트랙 55일차 회고 (2022.01.16) (0) | 2023.01.16 |