背景
最近公司某些项目需要vue迁移到react技术栈,技术调研react的状态管理工具zustand,相较于redux简便不少,具体的计较可以查看 zustand官方文档,下面主要讲解下zustand的基本用法。
以下例子采用的是zustand@4.5.2版本
安装
npm install zustand
基础类型
前提:在src目录下创建store文件夹用于统一管理状态,store创建useTest.ts文件,内容如下
import { create } from "zustand";
interface Istore {
testName: string;
setTestName: (str: string) => void;
}
export const useTestStore = create<Istore>()((set) => ({
testName: "",
setTestName: (str: string) => set(() => ({ testName: str })),
}));
创建test.tsx主页面,和组件testA,testB组件,
import TestA from "./a";
import TestB from "./b";
const Test: React.FC = () => {
return (
<div>
<TestA></TestA>
<hr></hr>
<TestB></TestB>
</div>
);
};
export default Test;
import { useTestStore } from "@/store/useTest";
const TestA: React.FC = () => {
const { testName, setTestName } = useTestStore();
return (
<div>
<span>testName:{testName}</span>
<button onClick={() => setTestName("testA")}>更改testName</button>
</div>
);
};
export default TestA;
import { useTestStore } from "@/store/useTest";
const TestB: React.FC = () => {
const { testName, setTestName } = useTestStore();
return (
<div>
<span>testName:{testName}</span>
<button onClick={() => setTestName("testB")}>更改testName</button>
</div>
);
};
export default TestB;
此时可见两组间共享状态,一个变动另一个自动刷新
引用类型
useTest.ts
import { create } from "zustand";
interface Iinfo {
name: string;
age: number;
}
interface Istore {
testName: string;
userInfo: Iinfo;
setTestName: (str: string) => void;
setUserInfo: (data: Iinfo) => void;
setUserInfoName: (str: string) => void;
setUserInfoAge: (str: number) => void;
}
export const useTestStore = create<Istore>()((set) => ({
testName: "默认值",
setTestName: (str: string) => set(() => ({ testName: str })),
userInfo: {
name: "张三",
age: 18,
},
setUserInfo: (data: Iinfo) => set(() => ({ userInfo: data })),
setUserInfoName: (name: string) =>
set((state) => ({ userInfo: { ...state.userInfo, name } })),
setUserInfoAge: (age: number) =>
set((state) => ({ userInfo: { ...state.userInfo, age } })),
}));
testA组件
import { useTestStore } from "@/store/useTest";
const TestA: React.FC = () => {
const {
userInfo,
setUserInfo,
setUserInfoName,
setUserInfoAge,
testName,
setTestName,
} = useTestStore();
return (
<div>
<div>testName:{testName}</div>
<div>userInfoName:{userInfo.name}</div>
<div>userInfoAge:{userInfo.age}</div>
<button onClick={() => setTestName("testA")}>更改testName</button>
<button onClick={() => setUserInfo({ name: "更改了", age: 30 })}>
更改userInfo
</button>
<button onClick={() => setUserInfoName("李四")}>更改testName</button>
<button onClick={() => setUserInfoAge(90)}>更改testName</button>
</div>
);
};
export default TestA;
testB
import { useTestStore } from "@/store/useTest";
const TestB: React.FC = () => {
const { userInfo, testName, setTestName } = useTestStore();
return (
<div>
<div>testName:{testName}</div>
<div>userInfoName:{userInfo.name}</div>
<div>userInfoAge:{userInfo.age}</div>
<button onClick={() => setTestName("testB")}>更改testName</button>
</div>
);
};
export default TestB;
此时可见AB组件引用类型更新
action中获取state
在入有个计数器,每次点击,store加一,需要在action获取state,用法没变化
import { create } from "zustand";
interface Iinfo {
name: string;
age: number;
}
interface Istore {
count: number;
testName: string;
userInfo: Iinfo;
setTestName: (str: string) => void;
setUserInfo: (data: Iinfo) => void;
setUserInfoName: (str: string) => void;
setUserInfoAge: (str: number) => void;
setCount: () => void;
}
export const useTestStore = create<Istore>()((set) => ({
testName: "默认值",
setTestName: (str: string) => set(() => ({ testName: str })),
userInfo: {
name: "张三",
age: 18,
},
setUserInfo: (data: Iinfo) => set(() => ({ userInfo: data })),
setUserInfoName: (name: string) =>
set((state) => ({ userInfo: { ...state.userInfo, name } })),
setUserInfoAge: (age: number) =>
set((state) => ({ userInfo: { ...state.userInfo, age } })),
count: 0,
setCount: () => set((state) => ({ count: state.count + 1 })),
}));
监听变动
A组件变更了count计数器,B组件需要监听count变动,回调函数, 监听全部的store变化和特定的count变化,B组件代码如下,组件下载调用取消监听。useTestStore.subscribe返回值为取消监听,调用即可取消监听
import { useEffect } from "react";
import type { Istore } from "@/store/useTest";
import { useTestStore } from "@/store/useTest";
const TestB: React.FC = () => {
const { userInfo, testName, setTestName } = useTestStore();
// 在组件挂载时订阅 store 变化 只要stoor变化均可触发
useEffect(() => {
// 监听所有更改,每次更改时都会同步触发
const unsubscribeALL = useTestStore.subscribe(
(state: Istore, prevState: Istore) => {
console.log(state, prevState);
}
);
// 在组件卸载时取消订阅
return () => {
unsubscribeALL();
};
}, []);
// 在组件挂载时订阅 store 变化 仅仅监听count变化
useEffect(() => {
// 监听所有更改,每次更改时都会同步触发
const unsubscribeCount = useTestStore.subscribe(
(state: Istore, prevState: Istore) => {
if (state.count !== prevState.count) {
console.log(state, prevState);
}
}
);
// 在组件卸载时取消订阅
return () => {
unsubscribeCount();
};
}, []);
// const unsubscribe = useTestStore.subscribe(
// (state) => {
// console.log("Count has changed:", state.count);
// },
// (prevState: Istore, nextState: Istore) => {
// return prevState.count !== nextState.count;
// }
// );
// allwatcher();
// 获取非响应式的新状态
// const countPaw = useTestStore.getState().count;
return (
<div>
<div>testName:{testName}</div>
<div>userInfoName:{userInfo.name}</div>
<div>userInfoAge:{userInfo.age}</div>
{/* <div>countPaw:{countPaw}</div> */}
<button onClick={() => setTestName("testB")}>更改testName</button>
<button onClick={() => allwatcher()}>停止监听</button>
</div>
);
};
export default TestB;
选择性订阅 useShallow
在 Zustand 中,useShallow 是一个用于选择性地订阅 store 中状态变化的自定义钩子。可以选择拼装自己需要的状态,有点类似于vue的computed, 注意两种写法
import { useEffect } from "react";
import { useShallow } from "zustand/react/shallow";
import type { Istore } from "@/store/useTest";
import { useTestStore } from "@/store/useTest";
const TestB: React.FC = () => {
const { userInfo, testName, setTestName, count } = useTestStore();
// 在组件挂载时订阅 store 变化 只要stoor变化均可触发
useEffect(() => {
// 监听所有更改,每次更改时都会同步触发
const unsubscribeALL = useTestStore.subscribe(
(state: Istore, prevState: Istore) => {
console.log(state, prevState);
}
);
// 在组件卸载时取消订阅
return () => {
unsubscribeALL();
};
}, []);
// 在组件挂载时订阅 store 变化 仅仅监听count变化
useEffect(() => {
// 监听所有更改,每次更改时都会同步触发
const unsubscribeCount = useTestStore.subscribe(
(state: Istore, prevState: Istore) => {
if (state.count !== prevState.count) {
console.log(state, prevState);
}
}
);
// 在组件卸载时取消订阅
return () => {
unsubscribeCount();
};
}, []);
// 自定义组装数据1
const { count1, userInfo1 } = useTestStore(
useShallow((state) => ({ count1: state.count, userInfo1: state.userInfo }))
);
// 自定义组装数据2
// const [count1, testName1] = useTestStore(
// useShallow((state) => [state.count, state.testName])
// )
return (
<div>
<div>testName:{testName}</div>
<div>count:{count}</div>
<div>userInfoName:{userInfo.name}</div>
<div>userInfoAge:{userInfo.age}</div>
<div>count1:{count1}</div>
<div>userInfo1.name:{userInfo1.name}</div>
{/* <div>countPaw:{countPaw}</div> */}
<button onClick={() => setTestName("testB")}>更改testName</button>
</div>
);
};
export default TestB;
action异步设置
注意:setUserInfoNameAsync异步设置state
import { create } from "zustand";
interface Iinfo {
name: string;
age: number;
}
function delayedPromise(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve("异步处理名称");
}, 300);
});
}
export interface Istore {
count: number;
testName: string;
userInfo: Iinfo;
setTestName: (str: string) => void;
setUserInfo: (data: Iinfo) => void;
setUserInfoName: (str: string) => void;
setUserInfoNameAsync: () => void;
setUserInfoAge: (str: number) => void;
setCount: () => void;
}
export const useTestStore = create<Istore>()((set) => ({
testName: "默认值",
setTestName: (str: string) => set(() => ({ testName: str })),
userInfo: {
name: "张三",
age: 18,
},
setUserInfo: (data: Iinfo) => set(() => ({ userInfo: data })),
setUserInfoName: (name: string) =>
set((state) => ({ userInfo: { ...state.userInfo, name } })),
setUserInfoNameAsync: async () => {
const nameStr = await delayedPromise();
set((state) => ({
userInfo: { ...state.userInfo, name: nameStr },
}));
},
setUserInfoAge: (age: number) =>
set((state) => ({ userInfo: { ...state.userInfo, age } })),
count: 0,
setCount: () => set((state) => ({ count: state.count + 1 })),
}));
引入immer 简化复杂对象操作
对用用惯vue的来说,react每次更改state需要先创建副本,在赋值setState操作起来麻烦,zustand借助immer来简化操作
npm i immer -S
修改useTest.ts
import { produce } from "immer";
import { create } from "zustand";
interface Iinfo {
name: string;
age: number;
}
interface ComplexObjectType {
nested: {
value: string;
};
array: number[];
}
function delayedPromise(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve("异步处理名称");
}, 300);
});
}
export interface Istore {
count: number;
testName: string;
userInfo: Iinfo;
setTestName: (str: string) => void;
setUserInfo: (data: Iinfo) => void;
setUserInfoName: (str: string) => void;
setUserInfoNameAsync: () => void;
setUserInfoAge: (str: number) => void;
setCount: () => void;
ImmerObject: ComplexObjectType;
updateImmerObject: (updater: (draft: ComplexObjectType) => void) => void;
}
export const useTestStore = create<Istore>()((set) => ({
testName: "默认值",
setTestName: (str: string) => set(() => ({ testName: str })),
userInfo: {
name: "张三",
age: 18,
},
setUserInfo: (data: Iinfo) => set(() => ({ userInfo: data })),
setUserInfoName: (name: string) =>
set((state) => ({ userInfo: { ...state.userInfo, name } })),
setUserInfoNameAsync: async () => {
const nameStr = await delayedPromise();
set((state) => ({
userInfo: { ...state.userInfo, name: nameStr },
}));
},
setUserInfoAge: (age: number) =>
set((state) => ({ userInfo: { ...state.userInfo, age } })),
count: 0,
setCount: () => set((state) => ({ count: state.count + 1 })),
// immer初始状态
ImmerObject: {
nested: {
value: "Immer",
},
array: [1, 2, 3],
},
// 使用 immer 的 produce 函数来更新复杂对象
updateImmerObject: (updater: (draft: ComplexObjectType) => void) =>
set(
produce((draft) => {
updater(draft.ImmerObject);
})
),
}));
A组件
import { useTestStore } from "@/store/useTest";
const TestA: React.FC = () => {
const {
userInfo,
count,
setUserInfo,
setUserInfoName,
setUserInfoAge,
testName,
setTestName,
setCount,
setUserInfoNameAsync,
ImmerObject,
updateImmerObject,
} = useTestStore();
const handleUpdate = () => {
updateImmerObject((draft) => {
draft.nested.value = "ImmerObject change";
draft.array.push(4);
});
};
return (
<div>
<div>testName:{testName}</div>
<div>userInfoName:{userInfo.name}</div>
<div>userInfoAge:{userInfo.age}</div>
<div>count:{count}</div>
<p>ImmerObject Value: {ImmerObject.nested.value}</p>
<p>ImmerObject Array: {ImmerObject.array.join(", ")}</p>
<button onClick={() => setTestName("testA")}>更改testName</button>
<button onClick={() => setUserInfo({ name: "更改了", age: 30 })}>
更改userInfo
</button>
<button onClick={() => setUserInfoName("李四")}>更改userInfoName</button>
<button onClick={() => setUserInfoAge(90)}>更改userInfoAge</button>
<button onClick={setCount}>更改count</button>
<button onClick={setUserInfoNameAsync}>setUserInfoNameAsync</button>
<button onClick={handleUpdate}>Update Immer Object</button>
</div>
);
};
export default TestA;
实现永久缓存,对于用户信息等状态,需要永久存储,否则一刷新就没了,
新建一个useUser.ts的store,devtools``persist实现local缓存
import { create } from "zustand";
import { devtools, persist } from "zustand/middleware";
interface Iinfo {
id: string;
name: string;
token: string;
}
interface BearState {
userInfo: Iinfo;
setUserInfo: (data: Iinfo) => void;
}
export const useUserStore = create<BearState>()(
devtools(
persist(
(set) => ({
userInfo: {
id: "",
name: "",
token: "",
},
setUserInfo: (data: Iinfo) => set(() => ({ userInfo: data })),
}),
{
name: "userInfo",
}
)
)
);
结尾
以上就是zustand基础用法,入门而已,全部代码在文档末尾
import TestA from "./a";
import TestB from "./b";
const Test: React.FC = () => {
return (
<div>
<TestA></TestA>
<hr></hr>
<TestB></TestB>
</div>
);
};
export default Test;
import { produce } from "immer";
import { create } from "zustand";
interface Iinfo {
name: string;
age: number;
}
interface ComplexObjectType {
nested: {
value: string;
};
array: number[];
}
function delayedPromise(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve("异步处理名称");
}, 300);
});
}
export interface Istore {
count: number;
testName: string;
userInfo: Iinfo;
setTestName: (str: string) => void;
setUserInfo: (data: Iinfo) => void;
setUserInfoName: (str: string) => void;
setUserInfoNameAsync: () => void;
setUserInfoAge: (str: number) => void;
setCount: () => void;
ImmerObject: ComplexObjectType;
updateImmerObject: (updater: (draft: ComplexObjectType) => void) => void;
}
export const useTestStore = create<Istore>()((set) => ({
testName: "默认值",
setTestName: (str: string) => set(() => ({ testName: str })),
userInfo: {
name: "张三",
age: 18,
},
setUserInfo: (data: Iinfo) => set(() => ({ userInfo: data })),
setUserInfoName: (name: string) =>
set((state) => ({ userInfo: { ...state.userInfo, name } })),
setUserInfoNameAsync: async () => {
const nameStr = await delayedPromise();
set((state) => ({
userInfo: { ...state.userInfo, name: nameStr },
}));
},
setUserInfoAge: (age: number) =>
set((state) => ({ userInfo: { ...state.userInfo, age } })),
count: 0,
setCount: () => set((state) => ({ count: state.count + 1 })),
// 初始状态
ImmerObject: {
nested: {
value: "Immer",
},
array: [1, 2, 3],
},
// 使用 immer 的 produce 函数来更新复杂对象
updateImmerObject: (updater: (draft: ComplexObjectType) => void) =>
set(
produce((draft) => {
updater(draft.ImmerObject);
})
),
}));
import { useTestStore } from "@/store/useTest";
const TestA: React.FC = () => {
const {
userInfo,
count,
setUserInfo,
setUserInfoName,
setUserInfoAge,
testName,
setTestName,
setCount,
setUserInfoNameAsync,
ImmerObject,
updateImmerObject,
} = useTestStore();
const handleUpdate = () => {
updateImmerObject((draft) => {
draft.nested.value = "ImmerObject change";
draft.array.push(4);
});
};
return (
<div>
<div>testName:{testName}</div>
<div>userInfoName:{userInfo.name}</div>
<div>userInfoAge:{userInfo.age}</div>
<div>count:{count}</div>
<p>ImmerObject Value: {ImmerObject.nested.value}</p>
<p>ImmerObject Array: {ImmerObject.array.join(", ")}</p>
<button onClick={() => setTestName("testA")}>更改testName</button>
<button onClick={() => setUserInfo({ name: "更改了", age: 30 })}>
更改userInfo
</button>
<button onClick={() => setUserInfoName("李四")}>更改userInfoName</button>
<button onClick={() => setUserInfoAge(90)}>更改userInfoAge</button>
<button onClick={setCount}>更改count</button>
<button onClick={setUserInfoNameAsync}>setUserInfoNameAsync</button>
<button onClick={handleUpdate}>Update Immer Object</button>
</div>
);
};
export default TestA;
import { useEffect } from "react";
import { useShallow } from "zustand/react/shallow";
import type { Istore } from "@/store/useTest";
import { useTestStore } from "@/store/useTest";
const TestB: React.FC = () => {
const { userInfo, testName, setTestName, count, ImmerObject } =
useTestStore();
// 在组件挂载时订阅 store 变化 只要stoor变化均可触发
useEffect(() => {
// 监听所有更改,每次更改时都会同步触发
const unsubscribeALL = useTestStore.subscribe(
(state: Istore, prevState: Istore) => {
console.log(state, prevState);
}
);
// 在组件卸载时取消订阅
return () => {
unsubscribeALL();
};
}, []);
// 在组件挂载时订阅 store 变化 仅仅监听count变化
useEffect(() => {
// 监听所有更改,每次更改时都会同步触发
const unsubscribeCount = useTestStore.subscribe(
(state: Istore, prevState: Istore) => {
if (state.count !== prevState.count) {
console.log(state, prevState);
}
}
);
// 在组件卸载时取消订阅
return () => {
unsubscribeCount();
};
}, []);
// 自定义组装数据1
const { count1, userInfo1 } = useTestStore(
useShallow((state) => ({ count1: state.count, userInfo1: state.userInfo }))
);
// 自定义组装数据2
// const [count1, testName1] = useTestStore(
// useShallow((state) => [state.count, state.testName])
// )
return (
<div>
<div>testName:{testName}</div>
<div>count:{count}</div>
<div>userInfoName:{userInfo.name}</div>
<div>userInfoAge:{userInfo.age}</div>
<div>count1:{count1}</div>
<div>userInfo1.name:{userInfo1.name}</div>
<p>ImmerObject Value: {ImmerObject.nested.value}</p>
<p>ImmerObject Array: {ImmerObject.array.join(", ")}</p>
{/* <div>countPaw:{countPaw}</div> */}
<button onClick={() => setTestName("testB")}>更改testName</button>
</div>
);
};
export default TestB;