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