感觉跟 pinia 有点像…
一、基本使用
1.1 编写、组织代码
-
store
- appleStore.ts
const { create } from 'zustand' type AppleStateType = { price:number, count:number, increment:()=>void, decremnent:()=>void, getTotal:()=>number } const useAppleStore = create<AppleStateType>()((set,get)=>({ // 状态 price:7.0, count:10, //动作 increment(){ set(state =>({ ...state, count:state.count + 1 })) }, decrement(){ set(state=>({ ...state, count:state.count - 1 })) }, getTotal(){ return get().price * get().count } })) export default useAppleStore
-
第一层不需要解构
-
set 中传入箭头函数,箭头函数返回一个对象,set 为设置状态
-
get 为获取当前状态
-
ts 语法中在泛型后面加上一个()
1.2 使用
- App.tsx
import useAppStore from './store/appleStore'
const Child1 = ()=>{
const price = useAppleStore(state=>state.price)
const count = useAppleStore(state=>state.count)
const increment = useAppleStore(state=>state.increment)
const decrement = useAppleStore(state=>state.decrement)
const getTotal = useAppleStore(state=>state.getTotal)
return(
<>
<h1>单价:{price}</h1>
<h2>数量:{count}</h2>
<h3>金额:{getTotal()}</h3>
<button onClick={decrement}>-1</button>
<button onClick={increment}>+1</button>
</>
)
}
const App = ()=>{
return(
<Child1 />
)
}
二、异步
2.1 编写、组织代码
-
store
- appleStore.ts
const { create } from 'zustand' type AppleStateType = { price:number, count:number, increment:()=>void, decremnent:()=>void, getTotal:()=>number, doubleCount:()=>Promise<undefined> } const useAppleStore = create<AppleStateType>()((set,get)=>({ // 状态 price:7.0, count:10, //动作 increment(){ }, decrement(){ }, getTotal(){ }, async doubleCount(){ const rate = await Promise.resolve(2) set(state=>({ ...state, count:state.count * rate })) } })) export default useAppleStore
2.2 使用
- App.tsx
import useAppStore from './store/appleStore'
const Child1 = ()=>{
const price = useAppleStore(state=>state.price)
const count = useAppleStore(state=>state.count)
const increment = useAppleStore(state=>state.increment)
const decrement = useAppleStore(state=>state.decrement)
const getTotal = useAppleStore(state=>state.getTotal)
const doubleCount = useAppleStore(state=>state.doubleCount)
return(
<>
<h1>单价:{price}</h1>
<h2>数量:{count}</h2>
<h3>金额:{getTotal()}</h3>
<button onClick={decrement}>-1</button>
<button onClick={increment}>+1</button>
<button onClick={doubleCount}>*2</button>
</>
)
}
const App = ()=>{
return(
<Child1 />
)
}
三、immer 中间件
3.1 安装
- 安装:
npm i immer
3.2 使用
-
store
- appleStore.ts
- 包裹 create 后面
- set 中返回代码块,不是对象
- 使用 immer 之后直接覆盖就行,不需要重新创造一个对象
const { create } from 'zustand' import {immer} from 'zustand/middleware/immer' type AppleStateType = { price:number, count:number, increment:()=>void, decremnent:()=>void, getTotal:()=>number, doubleCount:()=>Promise<undefined> } const useAppleStore = create<AppleStateType>()(immer( (set,get)=>({ // 状态 price:7.0, count:10, //动作 increment(){ set(state=>{state.count += 1}) }, decrement(){ set(state=>{state.count -= 1}) }, getTotal(){ }, async doubleCount(){ const rate = await Promise.resolve(2) set(state=>{count *= rate}) } }) )) export default useAppleStore
- appleStore.ts
四、useShallow
4.1 编写代码
-
store
- appleStore.ts
const { create } from 'zustand' import {immer} from 'zustand/middleware/immer' type AppleStateType = { price:number, count:number, color:string, increment:()=>void, decremnent:()=>void, getTotal:()=>number, doubleCount:()=>Promise<undefined> } const useAppleStore = create<AppleStateType>()(immer( (set,get)=>({ // 状态 price:7.0, count:10, color:red, //动作 increment(){ set(state=>{state.count += 1}) }, decrement(){ set(state=>{state.count -= 1}) }, getTotal(){ }, async doubleCount(){ const rate = await Promise.resolve(2) set(state=>{count *= rate}) } }) )) export default useAppleStore
4.2 使用
- App.tsx
- 注意:
const {price,color} = useAppleStore()
- 表示获取到了每一项,虽然只解构出来了 price 和 color
- 意味着,在 Child1 组件中对 store 中的 count 等其他属性进行刷新,那么 Child2 组件也会进行刷新
- 表示获取到了每一项,虽然只解构出来了 price 和 color
- 注意:
import useAppStore from './store/appleStore'
const Child2 = ()=>{
const {price,color} = useAppleStore()
return(
<>
<h1>单价:{price}</h1>
<h2>颜色:{color}</h2>
</>
)
}
const App = ()=>{
return(
<Child2 />
)
}
- App.tsx
- 让 useAppleStore 传入一个箭头函数,箭头函数返回一个对象
- 使用 useShallow 包裹箭头函数
import {useShallow} from 'zustand/react/shallow'
import useAppStore from './store/appleStore'
const Child2 = ()=>{
const {price,color} = useAppleStore(useShallow(state=>({
price:state.price,
color:state.color
})))
return(
<>
<h1>单价:{price}</h1>
<h2>颜色:{color}</h2>
</>
)
}
const App = ()=>{
return(
<Child2 />
)
}
五、调试工具
5.1 前提
- 安装扩展插件:redux-devtools
5.2 使用
-
store
- appleStore.ts
- 使用 devtools 进行包裹
- 第二个参数为配置选项
- 能否被观察
- 名称定义
- 第二个参数为配置选项
- 使用 devtools 进行包裹
const { create } from 'zustand' import {immer} from 'zustand/middleware/immer' import {devtools} from 'zustand/middleware/immer' type AppleStateType = { price:number, count:number, color:string, increment:()=>void, decremnent:()=>void, getTotal:()=>number, doubleCount:()=>Promise<undefined> } const useAppleStore = create<AppleStateType>()(immer( devtools( (set,get)=>({ // 状态 price:7.0, count:10, color:red, //动作 increment(){ set(state=>{state.count += 1}) }, decrement(){ set(state=>{state.count -= 1}) }, getTotal(){ }, async doubleCount(){ const rate = await Promise.resolve(2) set(state=>{count *= rate}) } }) ,{enabled:true,name:'Apple Store'}) )) export default useAppleStore
- appleStore.ts
六、持久化存储
6.1 中间件的顺序
- immer
- devtools
- 参数
- 回调:返回一个状态
- 配置对象
- 默认情况下存储到 localStorage
- name 设置 key
- subscribeWithSelector
- persist(set、get 的回调,{name:‘…’})
- 参数
- devtools
6.2 全部持久化存储
-
store
- appleStore.ts
const { create } from 'zustand' import {immer,persist} from 'zustand/middleware/immer' import {devtools} from 'zustand/middleware/immer' type AppleStateType = { } const useAppleStore = create<AppleStateType>()(immer( devtools( persist( (set,get)=>({ // 状态 price:7.0, count:10, color:red, //动作 increment(){ set(state=>{state.count += 1}) }, decrement(){ set(state=>{state.count -= 1}) }, getTotal(){ }, async doubleCount(){ const rate = await Promise.resolve(2) set(state=>{count *= rate}) } }) ,{name:'myAllpeStore'}) ,{enabled:true,name:'Apple Store'}) )) export default useAppleStore
6.3 仅存储指定属性
-
配置选项中增加
partialize:(state)=>({price:state.price})
- 返回哪个状态就保存哪一个状态
- 只对 price 进行持久化保存
-
store
- appleStore.ts
const { create } from 'zustand' import {immer,persist} from 'zustand/middleware/immer' import {devtools} from 'zustand/middleware/immer' type AppleStateType = { } const useAppleStore = create<AppleStateType>()(immer( devtools( persist( (set,get)=>({ // 状态 price:7.0, count:10, color:red, //动作 increment(){ set(state=>{state.count += 1}) }, decrement(){ set(state=>{state.count -= 1}) }, getTotal(){ }, async doubleCount(){ const rate = await Promise.resolve(2) set(state=>{count *= rate}) } }) ,{name:'myAllpeStore',partialize:(state)=>({price:state.price})}) ,{enabled:true,name:'Apple Store'}) )) export default useAppleStore
6.4 指定某些属性不存储
-
仍然借助 partialize
- 对应箭头函数返回一个对象
- fromEntries:将 [ [key,value] ] 变成对象的形式 { key:value}
- entries:将对象的 key 和 value 整合到一个元组中,再将所有的元组加入到数组中进行返回(二维数组)
- fromEntries:将 [ [key,value] ] 变成对象的形式 { key:value}
- 对应箭头函数返回一个对象
-
store
- appleStore.ts
- 指定不要 price
const { create } from 'zustand' import {immer,persist} from 'zustand/middleware/immer' import {devtools} from 'zustand/middleware/immer' type AppleStateType = { } const useAppleStore = create<AppleStateType>()(immer( devtools( persist( (set,get)=>({ }) ,{name:'myAllpeStore',partialize:(state)=> Object.fromEntries( Object.entries(state).filter(([key])=>!['price'].includes(key)) ) }) ,{enabled:true,name:'Apple Store'}) )) export default useAppleStore
- appleStore.ts
6.5 指定存储位置
-
在配置选项中进行配置
-
store
- appleStore.ts
const { create } from 'zustand' import {immer,persist} from 'zustand/middleware/immer' import {devtools} from 'zustand/middleware/immer' type AppleStateType = { } const useAppleStore = create<AppleStateType>()(immer( devtools( persist( (set,get)=>({ }) ,{name:'myAllpeStore',partialize:(state)=> Object.fromEntries( Object.entries(state).filter(([key])=>!['price'].includes(key)) ),storage:createJSONStorage(()=>sessionStorage) }) ,{enabled:true,name:'Apple Store'}) )) export default useAppleStore
七、subscribe
7.1 基本使用
- App.tsx
- 问题所在
- 因为 Child2 组件中使用了 count,因此当 Child1 中更新了 count 之后,Child2 会重新渲染
- Child2 依赖于 count 的值来进行渲染判断,但是其实没有必要每次都渲染的,因为 count 的值还是 < 7,直接返回上一次的"有点少"就够了
- 问题所在
import {useShallow} from 'zustand/react/shallow'
import useAppStore from './store/appleStore'
const Child2 = ()=>{
const {price,color} = useAppleStore(useShallow(state=>({
price:state.price,
color:state.color,
count:state.count
})))
return(
<>
<h1>单价:{price}</h1>
<h2>颜色:{color}</h2>
<h1>{count > 7 :'很多':'有点少'}</h1>
</>
)
}
const App = ()=>{
return(
<Child2 />
)
}
- 解决方案:使用订阅机制
- 会执行代码,但是不会让组件二重新渲染
- 要求只订阅一次:使用 useEffect
- 定义变量 text 设置文字
import {useShallow} from 'zustand/react/shallow'
import {useEffect} from 'react'
import useAppStore from './store/appleStore'
const Child2 = ()=>{
const [text,setText] = useState('有点少')
const {price,color} = useAppleStore(useShallow(state=>({
price:state.price,
color:state.color,
})))
useEffect(()=>{
const cancelSubscribe = useAppleStore.subscribe((newState,prevState)=>{
console.log('状态改变——这里执行')
if(state.count >= 7 && prevState.count < 7){
setText('很多')
}else if(state.count < 7 && prevState.count >= 7){
setText('太少')
}
})
return cancelSubscribe
},[])
return(
<>
<h1>单价:{price}</h1>
<h2>颜色:{color}</h2>
<h1>{text}</h1>
</>
)
}
const App = ()=>{
return(
<Child2 />
)
}
7.2 监听指定状态
-
实现颗粒度更小的监听
-
需要添加中间件:subscribeWithSelector
-
store
- appleStore.ts
const { create } from 'zustand' import {immer,persist,subscribeWithSelector} from 'zustand/middleware/immer' import {devtools} from 'zustand/middleware/immer' type AppleStateType = { } const useAppleStore = create<AppleStateType>()(immer( devtools( subscribeWithSelector( persist( (set,get)=>({ }) ,{name:'myAllpeStore',partialize:(state)=> Object.fromEntries( Object.entries(state).filter(([key])=>!['price'].includes(key)) ) }) ) ,{enabled:true,name:'Apple Store'}) )) export default useAppleStore
-
App.tsx
- 开始订阅的时候也要执行一次
- 设置变量
import {useShallow} from 'zustand/react/shallow'
import {useEffect} from 'react'
import useAppStore from './store/appleStore'
const Child2 = ()=>{
const [text,setText] = useState('有点少')
const {price,color} = useAppleStore(useShallow(state=>({
price:state.price,
color:state.color,
})))
useEffect(()=>{
const cancelSubscribe = useAppleStore.subscribe(
state=>state.count,
(count,prevCount)=>{
if(count >= 7 && (prevCount < 7 || count === prevCount)){
setText('很多')
}else if(count < 7 && (prevCount >= 7 || count === prevCount)){
setText('太少')
}
},
{ equalityFn:shallow,
fireImmediately:true
}
)
return cancelSubscribe
},[])
return(
<>
<h1>单价:{price}</h1>
<h2>颜色:{color}</h2>
<h1>{text}</h1>
</>
)
}
const App = ()=>{
return(
<Child2 />
)
}
八、getState、setState
8.1 使用场景
- 只需要在一个组件中使用修改状态的动作,不需要其他组件实现,不放在统一的位置
8.2 使用
- App.tsx
import useAppStore,{decrement,increment,getTotal,doubleCount} from './store/appleStore'
const Child1 = ()=>{
const price = useAppleStore(state=>state.price)
const count = useAppleStore(state=>state.count)
// const increment = useAppleStore(state=>state.increment)
//const decrement = useAppleStore(state=>state.decrement)
// const getTotal = useAppleStore(state=>state.getTotal)
// const doubleCount = useAppleStore(state=>state.doubleCount)
const myLocalAction = ()=>{
useAppStore.setState((state)=>{state.count += 5})
}
return(
<>
<h1>单价:{price}</h1>
<h2>数量:{count}</h2>
<h3>金额:{getTotal()}</h3>
<button onClick={decrement}>-1</button>
<button onClick={increment}>+1</button>
<button onClick={doubleCount}>*2</button>
<button onClick={myLocalAction}>Child1 独有的方法</button>
</>
)
}
const App = ()=>{
return(
<Child1 />
)
}
8.3 简化代码
- 将动作写在外边
const { create } from 'zustand'
import {immer,persist} from 'zustand/middleware/immer'
import {devtools} from 'zustand/middleware/immer'
type AppleStateType = {
}
const useAppleStore = create<AppleStateType>()(immer(
devtools(
persist(
(set,get)=>({
})
,{name:'myAllpeStore',partialize:(state)=>
Object.fromEntries(
Object.entries(state).filter(([key])=>!['price'].includes(key))
),storage:createJSONStorage(()=>sessionStorage)
})
,{enabled:true,name:'Apple Store'})
))
export default useAppleStore
export const increment =()=>{
useAppleStore.setState((state)=>{state.count+=1})
}
export const decrement =()=>{
useAppleStore.setState((state)=>{state.count-=1})
}
export const getTotal =()=>{
return useAppleStore.getState().price * useAppleStore.getState().count
}
export const doubleCount = async ()=>{
const rate = await Promise.resolve(2)
useAppleStore.setState((state)=>{state.count *= rate})
}