---
# System prepended metadata

title: Zustand 筆記

---

# Zustand 筆記
## 初始化設定

```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)
  ), [])
  ...
```