Skip to content

订阅

subscribe 可以订阅一个状态,当状态变化时,会触发回调函数。

只要 store 的 state 发生变化,就会触发回调函数。

const useCountStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
export default useCountStore;

外部订阅

useCountStore.subscribe((state) => {
console.log("Count changed:", state.count);
});

组件内部订阅

useEffect(() => {
useCountStore.subscribe((state) => {
console.log("count changed:", state.count);
});
}, []);

比如观察 age 的变化,如果使用选择器的写法, age 每次更新都会重新渲染组件,这会导致组件的频繁渲染:

import { create } from "zustand";
const useUserStore = create((set) => ({
name: "张三",
age: 18,
}));
export default useUserStore;
import useUserStore from "@/store/user";
import { useShallow } from "zustand/shallow";
export default function Home() {
// age 变化时,组件都会重新渲染
const { age } = useUserStore(
useShallow((state) => ({
age: state.age,
})),
);
return <div>{age}</div>;
}

性能优化:采用订阅的模式, age 变化的时候,会调用回调函数,但是不会重新渲染组件:

import useUserStore from "@/store/user";
import { useState } from "react";
export default function Home() {
const [status, setStatus] = useState("未知");
// 只会更新一次组件
useUserStore.subscribe((state) => {
if (state.age >= 18) {
setStatus("成年人");
} else {
setStatus("未成年");
}
});
return <div>{status}</div>;
}

持续优化:目前的订阅只要是 store 内部任意的 state 发生变化,都会触发回调函数,我们希望只订阅 age 的变化,可以使用中间件 subscribeWithSelector 订阅单个状态:

import { create } from "zustand";
import { subscribeWithSelector } from "zustand/middleware";
const useUserStore = create(
subscribeWithSelector((set) => ({
name: "张三",
age: 18,
})),
);
export default useUserStore;
import useUserStore from "@/store/user";
import { useState } from "react";
export default function Home() {
const [status, setStatus] = useState("未知");
useUserStore.subscribe(
(state) => state.age,
(age, prevAge) => {
if (age >= 18) {
setStatus("成年人");
} else {
setStatus("未成年");
}
},
);
return <div>{status}</div>;
}

subscribe 会返回一个取消订阅的函数,可以手动取消订阅:

const unSubscribe = useUserStore.subscribe((state) => {
console.log("监听到状态变化", state);
});
// 取消订阅
unSubscribe();

当使用了subscribeWithSelector中间件时,会多出来第三个参数options

import useUserStore from "@/store/user";
import { useState } from "react";
export default function Home() {
const [status, setStatus] = useState("未知");
const unSubscribe = useUserStore.subscribe(
(state) => state.age,
(age, prevAge) => {
if (age >= 18) {
setStatus("成年人");
} else {
setStatus("未成年");
}
},
{
// 比较函数:默认是浅比较,如果需要深比较,可以传入一个比较函数
equalityFn: (a, b) => a === b,
// 默认是false,如果设置为true,则会在订阅时立即调用一次回调函数,传入当前状态和undefined作为参数
fireImmediately: true,
},
);
return <div>{status}</div>;
}