安装
npm install zustand
创建一个store文件夹在文件下下面创建对应的业务模块比如全局管理price.ts
ts
体验AI代码助手
代码解读
复制代码
import { create } from "zustand";
// 定义一个接口,用于描述状态管理器的状态和操作
interface PriceStore {
price: number;
incrementPrice: () => void;
decrementPrice: () => void;
resetPrice: () => void;
getPrice: () => number;
}
// 创建一个状态管理器,使用 create 函数,传入一个函数,返回一个对象
/**
*
* @param set 用于更新状态
* @param get 用于获取状态
* @returns 返回一个对象,对象中的方法可以用于更新状态
*/
const usePriceStore = create<PriceStore>((set, get) => ({
price: 0, // 初始状态
incrementPrice: () => set((state) => ({ price: state.price + 1 })), // 更新状态
decrementPrice: () => set((state) => ({ price: state.price - 1 })), // 更新状态
resetPrice: () => set({ price: 0 }), // 重置状态
getPrice: () => get().price, // 获取状态
}));
export default usePriceStore;
- 在组件中使用
tsx
体验AI代码助手
代码解读
复制代码
import usePriceStore from './store/price';
export const App = () => {
//直接解构使用即可 把他当做一个hook使用
const { price, incrementPrice, decrementPrice, resetPrice, getPrice } = usePriceStore();
return (
<div>
<p>价格: {price}</p>
<button onClick={incrementPrice}>增加</button>
<button onClick={decrementPrice}>减少</button>
<button onClick={resetPrice}>重置</button>
<button onClick={getPrice}>获取</button>
</div>
)
}
immer在Zustand中的使用方法
引入注意是从zustand/middleware/immer引入,而不是immer
ts
体验AI代码助手
代码解读
复制代码
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
- 首先从zustand中间件引入
immer - 然后注意结构
create()(immer())这里是两个括号而不是放在create里面了
ts
体验AI代码助手
代码解读
复制代码
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface User {
gourd: {
oneChild: string,
twoChild: string,
threeChild: string,
fourChild: string,
fiveChild: string,
sixChild: string,
sevenChild: string,
},
updateGourd: () => void
}
//注意结构发生了变化!!!
const useUserStore = create<User>()(immer(((set) => ({
//创建葫芦娃
gourd: {
oneChild: '大娃',
twoChild: '二娃',
threeChild: '三娃',
fourChild: '四娃',
fiveChild: '五娃',
sixChild: '六娃',
sevenChild: '七娃',
},
updateGourd: () => set((state) => {
state.gourd.oneChild = '大娃-超进化' //这儿不需要状态合并了需要修改什么值直接改就行了
state.gourd.twoChild = '二娃-谁来了'
state.gourd.threeChild = '三娃-我来了'
})
}))))
export default useUserStore;
状态简化
回忆一下我们在使用zustand时,是这样引入状态的(如下),通过解构的方式引入状态,但是这样引入会引发一个问题,例如A组件用到了 hobby.basketball 状态,而B组件 没有用到 hobby.basketball 状态,但是更新hobby.basketball这个状态的时候,A组件和B组件都会重新渲染,这样就导致了不必要的重渲染,因为B组件并没有用到hobby.basketball这个状态。
tsx
体验AI代码助手
代码解读
复制代码
const { name, age, hobby, setHobbyRap, setHobbyBasketball } = useUserStore()
return (
<div className="left">
<h1>A组件</h1>
<div>
<h3>{name}</h3>
<div>年龄:<span>{age}</span></div>
<div>爱好1:<span>{hobby.sing}</span></div>
<div>爱好2:<span>{hobby.dance}</span></div>
<div>爱好3:<span>{hobby.rap}</span></div>
<div>爱好4:<span>{hobby.basketball}</span></div>
<button onClick={() => setHobbyRap('只因你太美')}>改变爱好rap</button>
<button onClick={() => setHobbyBasketball('篮球')}>改变爱好basketball</button>
</div>
</div>
)
状态选择器
所以为了规避这个问题,我们可以使用状态选择器,状态选择器可以让我们只选择我们需要的部分状态,这样就不会引发不必要的重渲染。
tsx
体验AI代码助手
代码解读
复制代码
const name = useUserStore((state) => state.name)
const age = useUserStore((state) => state.age)
const rap = useUserStore((state) => state.hobby.rap)
const basketball = useUserStore((state) => state.hobby.basketball)
useShallow
你以为这样就结束了? 并没有,你可以想一下如果一个属性很多,例如100个,那我们写起来岂不是要疯了,但是你用解构的话他又会造成不必要的重渲染,真是生与死轮回不止,这时候我们就可以使用useShallow来避免这个问题。
useShallow只检查顶层对象的引用是否变化,如果顶层对象的引用没有变化(即使其内部属性或子对象发生了变化,但这些变化不影响顶层对象的引用),使用 useShallow 的组件将不会重新渲染
tsx
体验AI代码助手
代码解读
复制代码
import { useShallow } from 'zustand/react/shallow';
const { rap, name } = useUserStore(useShallow((state) => ({
rap: state.hobby.rap,
name: state.name
})))
代码获取
store/user.ts
ts
体验AI代码助手
代码解读
复制代码
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
interface User {
name: string,
age: number,
hobby: {
sing: string,
dance: string,
rap: string,
basketball: string,
}
setHobbyRap: (rap: string) => void,
setHobbyBasketball: (basketball: string) => void
}
const useUserStore = create<User>()(immer((set) => ({
name: '坤坤',
age: 18,
hobby: {
sing: '坤式唱腔',
dance: '坤式舞步',
rap: '坤式rap',
basketball: '坤式篮球'
},
setHobbyRap: (rap: string) =>set((state) => {
state.hobby.rap = rap
}),
setHobbyBasketball: (basketball: string) => set((state) => {
state.hobby.basketball = basketball
})
})))
export default useUserStore;
A component
tsx
体验AI代码助手
代码解读
复制代码
import '../index.css'
import useUserStore from '../../store/user';
export default function Left() {
console.log('A组件渲染')
const { name, age, hobby, setHobbyRap, setHobbyBasketball } = useUserStore()
return (
<div className="left">
<h1>A组件</h1>
<div>
<h3>{name}</h3>
<div>年龄:<span>{age}</span></div>
<div>爱好1:<span>{hobby.sing}</span></div>
<div>爱好2:<span>{hobby.dance}</span></div>
<div>爱好3:<span>{hobby.rap}</span></div>
<div>爱好4:<span>{hobby.basketball}</span></div>
<button onClick={() => setHobbyRap('只因你太美')}>改变爱好rap</button>
<button onClick={() => setHobbyBasketball('篮球')}>改变爱好basketball</button>
</div>
</div>
)
}
B component
tsx
体验AI代码助手
代码解读
复制代码
import '../index.css'
import useUserStore from '../../store/user';
import { useShallow } from 'zustand/react/shallow';
export default function Right() {
console.log('B组件渲染')
const { rap, name } = useUserStore(useShallow((state) => ({
rap: state.hobby.rap,
name: state.name
})))
return (
<div className="right">
<h1>B组件</h1>
<div>
<div>姓名:<span>{name}</span></div>
<div>rap:<span>{rap}</span></div>
</div>
</div>
)
}
css
css
体验AI代码助手
代码解读
复制代码
.left {
width: 50%;
height: 100%;
border: 1px solid rgb(19, 204, 148);
height: 300px;
margin:30px;
padding: 20px;
border-radius: 10px;
}
.right {
width: 50%;
height: 100%;
border: 1px solid rgb(214, 35, 35);
height: 300px;
margin:30px;
padding: 20px;
border-radius: 10px;
}
.left button {
margin: 10px;
padding: 10px;
}
.right button {
margin: 10px;
padding: 10px;
}
中间件
zustand 的中间件是用于在状态管理过程中添加额外逻辑的工具。它们可以用于日志记录、性能监控、数据持久化、异步操作等。
自定义编写中间件
我们实现一个简易的日志中间件,了解其中间件的实现原理, zustand的中间件是一个高阶函数,你问我有多高,三四楼那么高啦
ts
体验AI代码助手
代码解读
复制代码
const logger = (config) => (set, get, api) => config((...args) => {
console.log(api)
console.log('before', get())
set(...args)
console.log('after', get())
}, get, api)
参数解释:
-
config (外层函数参数)
- 类型:函数 (set, get, api) => StoreApi
- 作用:原始创建 store 的配置函数,由用户传入。中间件需要包装这个函数。
-
set (内层函数参数)
- 类型:函数 (partialState) => void
- 作用:原始的状态更新函数,用于修改 store 的状态。
-
get (内层函数参数)
- 类型:函数 () => State
- 作用:获取当前 store 的状态值。
-
api (内层函数参数)
- 类型:对象 StoreApi
- 作用:包含 store 的完整 API(如 setState, getState, subscribe, destroy 等方法)。
使用示例
ts
体验AI代码助手
代码解读
复制代码
const useUserStore = create<User>()(immer(logger((set) => ({
name: '坤坤',
age: 18,
hobby: {
sing: '坤式唱腔',
dance: '坤式舞步',
rap: '坤式rap',
basketball: '坤式篮球'
},
setHobbyRap: (rap: string) => set((state) => {
state.hobby.rap = rap
}),
setHobbyBasketball: (basketball: string) => set((state) => {
state.hobby.basketball = basketball
})
}))))
devtools
devtools 是 zustand 提供的一个用于调试的工具,它可以帮助我们更好地管理状态。
- 需要安装浏览器插件
Redux DevTools下载地址 - 在浏览器中打开
Redux DevTools插件,并连接到当前页面
ts
体验AI代码助手
代码解读
复制代码
const useUserStore = create<User>()(
immer(
devtools((set) => ({
name: '坤坤',
age: 18,
hobby: {
sing: '坤式唱腔',
dance: '坤式舞步',
rap: '坤式rap',
basketball: '坤式篮球'
},
setHobbyRap: (rap: string) => set((state) => {
state.hobby.rap = rap
}),
setHobbyBasketball: (basketball: string) => set((state) => {
state.hobby.basketball = basketball
})
}),
{
enabled: true, // 是否开启devtools
name: '用户信息', // 仓库名称
}
)
)
)
persist
persist 是 zustand 提供的一个用于持久化状态的工具,它可以帮助我们更好地管理状态,默认是存储在 localStorage 中,可以指定存储方式
ts
体验AI代码助手
代码解读
复制代码
const useUserStore = create<User>()(
immer(
persist((set) => ({
name: '坤坤',
age: 18,
hobby: {
sing: '坤式唱腔',
dance: '坤式舞步',
rap: '坤式rap',
basketball: '坤式篮球'
},
setHobbyRap: (rap: string) => set((state) => {
state.hobby.rap = rap
}),
setHobbyBasketball: (basketball: string) => set((state) => {
state.hobby.basketball = basketball
})
}),
{
name: 'user', // 仓库名称(唯一)
storage: createJSONStorage(() => localStorage), // 存储方式 可选 localStorage sessionStorage IndexedDB 默认localStorage
partialize: (state) => ({
name: state.name,
age: state.age,
hobby: state.hobby
}) // 部分状态持久化
}
)
)
)
清空缓存Api, 在页面中添加一个按钮,点击按钮清空缓存,在增加persist中间件之后会自动增加一个clearStorage方法,用于清空缓存。
ts
体验AI代码助手
代码解读
复制代码
import useUserStore from '../../store/user';
const App = () => {
const clear = () => {
useUserStore.persist.clearStorage()
}
return <div onClick={clear}>清空缓存</div>
}
订阅
zustand 的 subscribe,可以订阅一个状态,当状态变化时,会触发回调函数。
订阅一个状态
只要store 的 state 发生变化,就会触发回调函数,另外就是这个订阅可以在组件内部订阅,也可以在组件外部订阅,如果在组件内部订阅需要放到useEffect中,防止重复订阅。
tsx
体验AI代码助手
代码解读
复制代码
const store = create((set) => ({
count: 0,
}));
//外部订阅
store.subscribe((state) => {
console.log(state.count);
});
//组件内部订阅
useEffect(() => {
store.subscribe((state) => {
console.log(state.count);
});
}, []);
案例
比如我们需要观察年龄的变化,大于等于26 就提示可以结婚了,小于26 就提示还不能结婚,如果使用选择器的写法,age每次更新都会重新渲染组件,这样就会导致组件的频繁渲染。
tsx
体验AI代码助手
代码解读
复制代码
const store = create((set) => ({
age: 0,
}));
//组件里面 age 每次更新都会重新渲染组件
const { age } = useStore(useShallow((state) => ({
age: state.age,
})));
性能优化,采用订阅的模式,age 变化的时候,会调用回调函数,但是不会重新渲染组件。
tsx
体验AI代码助手
代码解读
复制代码
const store = create((set) => ({
age: 0,
}));
const [status,setStatus] = useState('单身')
//只会更新一次组件
useStore.subscribe((state) => {
if(state.age >= 26){
setStatus('结婚')
}else{
setStatus('单身')
}
});
return <div>{status}</div>
持续优化,目前的订阅只要是store内部任意的state发生变化,都会触发回调函数,我们希望只订阅age的变化,可以使用中间件subscribeWithSelector 订阅单个状态。
tsx
体验AI代码助手
代码解读
复制代码
const store = create(subscribeWithSelector((set) => ({
age: 0,
name: '张三',
})));
const [status,setStatus] = useState('单身')
//订阅age的变化 并且组件渲染一次
useStore.subscribe(state => state.age, (age,prevAge) => {
if(age >= 26){
setStatus('结婚')
}else{
setStatus('单身')
}
});
补充用法
- subscribe 会返回一个取消订阅的函数,可以手动取消订阅。
tsx
体验AI代码助手
代码解读
复制代码
const unSubscribe = useStore.subscribe((state) => {
console.log(state.age);
});
unSubscribe(); //取消订阅
2. 当你使用了subscribeWithSelector中间件的时候会多出来第三个参数options
- equalityFn 比较函数
- fireImmediately 是否立即触发
tsx
体验AI代码助手
代码解读
复制代码
const unSubscribe = useStore.subscribe(state => state.age, (age,prevAge) => {
console.log(age,prevAge);
}, {
equalityFn: (a, b) => a === b, // 默认是浅比较,如果需要深比较,可以传入一个比较函数
fireImmediately: true, // 默认是false,如果需要立即触发,可以传入true
});