安装
// NPM
npm install zustand
创建一个 store并使用它
// store.js
import { create } from 'zustand'
export const useStore = create(
(set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
updateBears: (newBears) => set({ bears: newBears }),
})
)
// component.jsx
import { useStore } from 'store'
function BearCounter () {
const bears = useStore((state) => state.bears)
return <h1>{bears} around here...</h1>
}
create中的异步函数可直接使用async/await的方式去调用,不需要额外下载中间件。
多层state
如果state是简单结构时,可以直接像上面代码中直接赋值:
set((state) => ({ bears: state.bears + 1 }))
如果state是深层嵌套结构时,必须使用扩展运算符(...)先将原来数据拷贝,然后再做操作:
export const useStore = create(
(set) => ({
cats: {
bigCats: 0,
smallCats: 0
},
increaseBigCats: () => set(
(state) => {
cats: {
...state.cats,
bigCats: state.cats.bigCats + 1
}
}
),
})
)
如果希望不使用扩展运算符,直接对state中的值操作需要使用immer
get方法使用
当我们想在state同级目录下使用state中的值时,可以使用get方法
export const useStore = create(
(set, get) => ({
cats: {
bigCats: 0,
smallCats: 0
},
summary: () => {
const total = get().cats.bigCats + get().cats.smallCats
return total
}
})
)
immer安装
// NPM
npm install immer
immer使用
export const useStore = create(
immer(
(set) => ({
cats: {
bigCats: 0,
smallCats: 0
},
increaseBigCats: () => set(
(state) => {
cats: {
state.cats.bigCats++
}
}
),
})
)
)
Selector 使用
问题
当我们要使用store中值的时候需要按照如下方式:
const summary = catStore(state => state.summary)
const increaseBigCats = catStore(state => state.increaseBigCats)
const increaseSmallCats = catStore(state => state.increaseSmallCats)
只需要引入某几个数据的时候倒是无所谓,但如果我们需要引入多条数据时就会很麻烦。
下面的写法确实省事,不过只会引起页面重渲染。当项目中多处需要使用store的时候会导致越来越卡。
const { summary, increaseBigCats, increaseSmallCats } = catStore()
解决办法
详细内容可以参考官方文档,这里提供直接处理方式。
- 创建一个
createSelectors util
// utils/createSelectors.js
export const createSelectors = (_store) => {
let store = _store
store.use = {}
for (let k of Object.keys(store.getState())) {
; (store.use)[k] = () => store((s) => s[k])
}
return store
}
- 在
store文件中用createSelector包裹create
// store.js
import { createSelectors } from './createSelectors'
export const useStore = createSelectors(
create(
...code
)
)
- 组件中使用
// component.jsx
import { useStore } from 'store'
export const App () {
const increaseBigCats = useStore.use.increaseBigCats()
const increaseSmallCats = useStore.use.increaseSmallCats()
const cats = useStore.use.cats()
return (
<div>{cats.smallCats}</div>
<div>
<button onClick={increaseBigCats}>increaseBigCats</button>
<button onClick={increaseSmallCats}>increaseSmallCats</button>
</div>
)
}
useStore.use只能返回第一层的数据,如果state中结构比较复杂需要认为xxx.xxx.xxx的方式去调用
- 如果想一次性返回多个
state可以使用下面的写法
// component.jsx
import { useStore } from 'store'
import { shallow } from 'zustand/shallow'
export const App = () => {
const { increaseBigCats, increaseSmallCats, cats } = useStore(
state => ({
increaseBigCats: state.increaseBigCats,
increaseSmallCats: state.increaseSmallCats,
cats: state.cats,
}),
shallow
)
return (
<div>
<div>smallCats: { cats.smallCats }</div>
<div>
<button onClick={increaseBigCats}>increaseBigCats</button>
<button onClick={increaseSmallCats}>increaseSmallCats</button>
</div>
</div>
)
}
这里的shallow是zustand/shallow提供给我们的比较函数。
上面写法里,useStore中返回的对象每次都是重新产生的,它会去帮我们比较每一次第一层的值是否一样。
如果两次比较的值都是一样的,它会默认两个对象是相等的,则不会使得组件重渲染。
如果不添加shallow函数,则每次store中的值改变后,会导致组件重渲染。
这里提供给我们的
shallow只会比较第一层的值,如果有需要可以自己写一个比较函数
devtools使用
安装Redux DevTools
谷歌科学上网后直接在应用商店搜索下载即可
使用Redux DevTools
devtools有两个参数。
- 原先
store中的内容 devtools配置,详情看这里
// store.js
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
export const useStore = create(
devtools(
(set, get) => ({...code}),
{
// 配置文件
...options
}
)
)
数据持久化
zustand配置数据持久化不需要我们去手动的localStorage.getItem/setItem,他给我们提供了一个中间件persist。
persist有两个参数
-
原先
store中的内容 -
persist配置项,这里的配置项是必填参数。完整文档查看这里
// store.js
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
export const useStore = create(
persist(
(set, get) => ({...code}),
{
/**
* 配置文件,这只展示常用的几个
* name: 'self-store', // storage唯一标识
* storage: createJSONStorage(() => sessionStorage), // 修改存储位置
* partialize: state => ({ xxx: state.xxx }) // 只缓存 xxx
*/
...options
}
)
)
清除缓存
// component.jsx
import { useStore } from 'store'
export const App () {
return (
<div>
<button onClick={useStore.persist.clearStorage}>clear storage</button>
</div>
)
}
我们可以直接使用对应store的persist.clearStorage方法,即可清除对应缓存。
要注意的是,该方法只会清除对应
storage,而不会清除对应memory。如果有
reset的需求建议直接在store中添加一个reset方法。
immer、devtools和persist顺序
当我们既要用immer又要用devtools和persist时,按照以下顺序就可以了
// store.js
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
import { devtools, persist } from 'zustand/middleware'
export const useStore = create(
immer(
devtools(
persist(
(set, get) => ({ ...code })
)
)
)
)
subscribe
我对于subscribe的理解就是它相当于Vue中的watchEffect。
文档中有写,最好把subscribe和useEffect结合使用,以便在卸载时自动取消订阅
// component.jsx
import { useEffect } from 'react'
import { useStore } from 'store'
export const App = () => {
useEffect(() => {
const unsub = useStore.subscribe((state, prevState) => {
console.log('state >>> ', state)
console.log('prevState >>> ', prevState)
})
return unsub
}, [])
return (
<div></div>
)
}
subscribeWithSelector
如果说subscribe是watchEffect,那subscribeWithSelector我觉得就是watch,可以订阅部分state的变化。具体详情可以去 github 上看,这里就不赘述了。
唯一要注意的是如果要使用subscribeWithSelector,需要在store中用subscribeWithSelector包裹原先的store,其他的都和subscribe大差不差。
// store.hs
import { create } from 'zustand'
import { subscribeWithSelector } from 'zustand/middleware'
export const useStore = create(
subscribeWithSelector(
(set, get) => ({ ...code })
)
)
subscribeWithSelector 在中间中的顺序
// store.js
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware'
export const useStore = create(
immer(
devtools(
subscribeWithSelector(
persist(
(set, get) => ({ ...code })
)
)
)
)
)
若有问题欢迎指正。