# Zustand 筆記 ## 初始化設定 * little boilerplate * doesnt rely provider * faster than context * state merge by default ex. {...state,b:7} => {b:7} * extenable by default * little opinionated ```typescript import create from 'zustand' interface BearState { bears: number increase: (by: number) => void } //只要更動state就需要呼叫set參數 const useStore = create<BearState>()((set) => ({ bears: 0, increase: (by) => set((state) => ({ bears: state.bears + by })), })) ``` ### create 在zustand中透過set參數去改變你state中的狀態,zustand的create就像react的contextapi的Context.provider的value如下: ```typescript const TestContext = createContext<ContextValue | {}>({}) type Testprops = { children: React.ReactElement } function TestContextHOC({ children }: Testprops) { const [bears,setbears]=useState(0) function increase(){ setbears(bears+1) } return ( <TestContext.Provider value={{ bears, increase }}> {children} </TestContext.Provider> ) } ``` 相較於contextAPI的取值是用useContext,zustand取值的方式是用hook的方式 ### contextApi取值 ```typescript const {bears,increase} = useContext(TestContext) ``` ### zustand取值 ```typescript= //get everythings const state = useStore() //getBears const bearsCounts = useStore(state=>state.bears) //get increase function const increaseBears = useStore(state=>state.increase) ``` 你會發現兩者取值的方式很類似,二者最大的差別在於初始化設定不同, contextapi設定取來比較繁瑣,需要包一層HOC還有設定provider,取值還需要額外調用useContext,zustand把這一切過程簡單成一個hooks, 無須擔心你的provider要放哪裡才可以提取資料,zustand真的做到資料分離這件事。 ## Overwriting state 甚至你可以透過set的第二個參數去取代你的state方法如下,false為預設值取代,true代表你可以覆寫 ```typescript import omit from 'lodash-es/omit' const useStore = create((set) => ({ salmon: 1, tuna: 2, deleteEverything: () => set({}, true), // clears the entire store, actions included deleteTuna: () => set((state) => omit(state, ['tuna']), true), })) ``` ## Async actions 非同步涵式呼叫 ```typescript const useStore = create((set) => ({ fishies: {}, fetch: async (pond) => { const response = await fetch(pond) set({ fishies: await response.json() }) }, })) ``` ### Read from state in actions 雖然你可以透過set去設定值,單有時候你可能需要在action中拿state,這時你就可以用get function ```typescript const useStore = create((set, get) => ({ sound: "grunt", action: () => { const sound = get().sound // ... } }) ``` ### Reading/writing state and reacting to changes outside of components 有時候我們只要單純取值的話與原生的redux一樣有getState方法 ```typescript const useStore = create(() => ({ paw: true, snout: true, fur: true })) // Getting non-reactive fresh state const paw = useStore.getState().paw ``` ### zustand強大的訂閱事件 useStore像redux一樣在設計上是訂閱事件,所以會有subscribe的callbackfunction,與取消訂閱的方法,甚至比redux多一個destory的函數 ```typescript const useStore = create(() => ({ paw: true, snout: true, fur: true })) // Getting non-reactive fresh state const paw = useStore.getState().paw // Listening to all changes, fires synchronously on every change const unsub1 = useStore.subscribe(console.log) // Updating state, will trigger listeners useStore.setState({ paw: false }) // Unsubscribe listeners unsub1() // Destroying the store (removing all listeners) useStore.destroy() // You can of course use the hook as you always would const Component = () => { const paw = useStore((state) => state.paw) ... ``` ### subscribeWithSelector subscribeWithSelector可以透過訂閱事件去判別state狀態並console.log,subscribe第一個參數回傳結果是你要監聽的state資料,使用方法如下 : ```typescript import { subscribeWithSelector } from 'zustand/middleware' const useStore = create( subscribeWithSelector(() => ({ paw: true, snout: true, fur: true })) ) // Listening to selected changes, in this case when "paw" changes const unsub2 = useStore.subscribe((state) => state.paw, console.log) // Subscribe also exposes the previous value const unsub3 = useStore.subscribe( (state) => state.paw, (paw, previousPaw) => console.log(paw, previousPaw) ) // Subscribe also supports an optional equality function const unsub4 = useStore.subscribe( (state) => [state.paw, state.fur], console.log ) // Subscribe and fire immediately const unsub5 = useStore.subscribe((state) => state.paw, console.log, { fireImmediately: true, }) ``` ### unsub2結果 可以知道資料總數 ![](https://i.imgur.com/YhWNr5y.png) ### unsub4結果 ### ![](https://i.imgur.com/K1y3Iyt.png) Using zustand without React 甚至zustand與redux一樣可以在非框架的情況下使用,getState、setState等都是create封包的結果 ```typescript import create from 'zustand/vanilla' const store = create(() => ({ ... })) const { getState, setState, subscribe, destroy } = store ``` ### combinewhich 但你會發現今天如果state資料很多方法也不少時,你會不好取值,而且程式碼也會比較亂,zustand就提供一個combine方法讓你把state與action分開來如下 : ```typescript import create from 'zustand' import { combine } from 'zustand/middleware' const useStore = create( combine({ bears: 0 }, (set) => ({ increase: (by: number) => set((state) => ({ bears: state.bears + by })), })) ) ``` ### Using middlewares zustand可以結合redux devtool與redux persist這些都是zustand封包好的函數 ```typescript import create from 'zustand' import { devtools, persist } from 'zustand/middleware' interface BearState { bears: number increase: (by: number) => void } const useStore = create<BearState>()( devtools( persist((set) => ({ bears: 0, increase: (by) => set((state) => ({ bears: state.bears + by })), })) ) ) ``` ### Persist middleware persist的第二個參數可以設定你的key name甚至可以改變你storage要放sessionStorage或localStorage ```typescript import create from 'zustand' import { persist } from 'zustand/middleware' const useStore = create( persist( (set, get) => ({ fishes: 0, addAFish: () => set({ fishes: get().fishes + 1 }), }), { name: 'food-storage', // unique name getStorage: () => sessionStorage, // (optional) by default, 'localStorage' is used } ) ) ``` ## 高級用法 ### customer middlewares 你也可以透過middlewares封包你的createStore ```typescript const foo = (f: Function) => (set: Function, get: Function, store: StoreApi<BearsState>) => { //do something here... return f(set, get, store) } const createStore = () => create<BearsState>( foo( (set: Function, get: Function, store: Function) => ({ bears: 0, increaeasePopulation: () => set((state: BearsState) => ({ bears: state.bears + 1 })) }) ) ) ``` ### 使用middlewares中typescipt用法 以下是使用midleware會遇到的型別問題,只要在create中加入泛型就OK ```typescript import create from "zustand" interface BearState { bears: number increase: (by: number) => void } const useStore = create< BearState, [ ['zustand/persist', BearState], ['zustand/devtools', never] ] >(devtools(persist((set) => ({ bears: 0, increase: (by) => set((state) => ({ bears: state.bears + by })), }))) ``` ### 使用useRef取值減少渲染次數 透過subscribe可以偵測每一次render中的state是否有差異,來達到減少渲染的次數 ```typescript const useStore = create(set => ({ scratches: 0, ... })) const Component = () => { // Fetch initial state const scratchRef = useRef(useStore.getState().scratches) // Connect to the store on mount, disconnect on unmount, catch state-changes in a reference //auto catch value in ref without render twice and auto unSubscribe in useEffect useEffect(() => useStore.subscribe( state => (scratchRef.current = state.scratches) ), []) ... ```