关键字

  • Zustand
  • Jotai

Zustand

安装

1
cnpm install --save zustand

使用

API

1
2
3
4
const useStore = create((set, get, store) => ({
stateKey: initialValue, // 初始状态
actionMethod: () => set(...), // 用于更新状态的方法
}));

create 接受一个回调函数作为参数,该回调函数会返回一个 状态对象(store),这个状态对象可以包含:

  • 状态(state):数据,例如 count: 0
  • 方法(actions):用于更新状态的函数,例如 increment: () => set((state) => ({ count: state.count + 1 }))

回调函数的参数

create 的回调函数会接收 三个参数

1
2
3
4
5
const useStore = create((set, get, store) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
getCount: () => get().count,
}));
参数 作用
set 修改状态(类似 setState
get 获取当前状态
store 访问整个 store API(很少用)

useStore的返回值

create 返回的是一个 React Hook

1
2
3
4
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));

你可以在组件中这样使用:

1
2
3
4
5
6
7
8
9
function Counter() {
const { count, increment } = useStore();
return (
<div>
<h1>{count}</h1>
<button onClick={increment}>增加</button>
</div>
);
}

特点

  • useStore() 👉 订阅整个 store
  • useStore((state) => state.count) 👉 只订阅 count,提升性能

store 里定义全部的 state,然后在组件里选出一部分来用。

这个叫做 selector

状态变了之后,zustand 会对比 selector 出的状态的新旧值,变了才会触发组件重新渲染。

此外,这个 selector 还可以起到派生状态的作用,对原始状态做一些修改:

中间件

其实中间件并不是 zustand 自己实现的功能。

你看这个 create 方法的参数,它是一个接受 set、get、store 的三个参数的函数:

那我们可不可以包一层,自己拿到 get、set、store,对这些做一些修改,之后返回一个接受三个参数的函数呢?

比如这样:

1
2
3
4
5
6
7
8
9
10
11
function logMiddleware(func) {
return function(set, get, store) {

function newSet(...args) {
console.log('调用了 set,新的 state:', get());
return set(...args)
}

return func(newSet, get, store)
}
}

我接受之前的函数,然后对把 set、get、store 修改之后再调用它:

这样不就给 zustand 的 set 方法加上了额外的功能么?

这个就是中间件,和 redux 的中间件是一样的设计。

它并不需要 zustand 本身做啥支持,只要把 create 的参数设计成一个函数,这个函数接收 set、get 等函作为参数,那就自然支持了中间件。

zustand 内置了一些中间件,比如 immer、persist。

immer

1
2
3
4
5
6
7
8
9
10
11
12
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

const useStore = create(
immer((set) => ({
todos: [],
addTodo: (text) =>
set((state) => {
state.todos.push({ text, completed: false });
}),
}))
);

允许直接 修改对象或数组,Zustand 内部会自动进行不可变更新。

persist

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

const useStore = create(
persist(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}),
{
name: 'counter-storage', // 存储的 key
}
)
);

作用

  • 把状态存到 localStorage(或 sessionStorage)。
  • 刷新页面后,状态不会丢失。
stateCreator

在 Zustand 中,StateCreator 是用来定义 store 结构的一个函数类型。我们可以先使用 StateCreator 创建一个 stateCreator,然后再将其传递给 persist 进行状态持久化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { create, StateCreator } from 'zustand';
import { persist } from 'zustand/middleware';

// 定义 State 类型
interface CounterState {
count: number;
increment: () => void;
}

// 使用 StateCreator 创建一个 stateCreator
const counterStateCreator: StateCreator<CounterState> = (set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
});

// 使用 persist 包装 stateCreator
const useCounterStore = create(
persist(counterStateCreator, {
name: 'counter-storage', // 存储 key
getStorage: () => localStorage, // 指定存储方式
})
);

// 组件中使用
function Counter() {
const { count, increment } = useCounterStore();

return (
<div>
<h1>{count}</h1>
<button onClick={increment}>增加</button>
</div>
);
}

export default Counter;

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { useEffect } from "react";
import { create } from "zustand";

const useXxxStore = create((set) => ({
aaa: "",
bbb: "",
updateAaa: (value) => set(() => ({ aaa: value })),
updateBbb: (value) => set(() => ({ bbb: value })),
}));

export default function App() {
const updateAaa = useXxxStore((state) => state.updateAaa);
const aaa = useXxxStore((state) => state.aaa);
useEffect(() => {
useXxxStore.subscribe((state) => {
console.log(useXxxStore.getState());
});
}, []);
return (
<div>
<input onChange={(e) => updateAaa(e.currentTarget.value)} value={aaa} />
<Bbb></Bbb>
</div>
);
}

function Bbb() {
return (
<div>
<Ccc></Ccc>
</div>
);
}

function Ccc() {
const aaa = useXxxStore((state) => state.aaa);
return <p>hello, {aaa}</p>;
}

额外方法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 直接获取当前状态 在非 React 组件环境 访问状态
console.log(useStore.getState().count);

// 直接更新状态(不会触发 re-render)在 非 React 组件环境 更新状态
useStore.setState({ count: 100 });

// 订阅状态变化(适用于非 React 组件)
const unsubscribe = useStore.subscribe((state) => {
console.log("新的 count:", state.count);
});

// 取消订阅
unsubscribe();

Jotai

Jotai 是一个 react 的状态管理库,主打原子化。

在 jotai 里,每个状态都是独立的原子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { atom, useAtom } from 'jotai'; 

const aaaAtom = atom (0);

const bbbAtom = atom(0);

function Aaa() {
const [aaa, setAaa]= useAtom(aaaAtom);

console.log('Aaa render...')
return <div>
aaa: {aaa}
<button onClick={() => setAaa(aaa + 1)}>加一</button>
</div>
}

function Bbb() {
const [bbb, setBbb]= useAtom(bbbAtom);

console.log('Bbb render...')

return <div>
bbb: {bbb}
<button onClick={() => setBbb(bbb + 1)}>加一</button>
</div>
}

export default function App() {
return <div>
<Aaa></Aaa>
<Bbb></Bbb>
</div>
}

派生atom

状态可以组合,产生派生状态:

可写atom

如果你需要创建一个 计算 & 可修改atom,可以这样:

1
2
3
4
5
6
7
const countAtom = atom(0);

// 创建一个可写的 atom
const doubleCountAtom = atom(
(get) => get(countAtom) * 2, // 读取 countAtom 的值
(get, set, newValue) => set(countAtom, newValue / 2) // 修改 countAtom
);

📌 解释

  • 读取时doubleCountAtom 等于 countAtom * 2
  • 写入时:如果 set(doubleCountAtom, 10),那么 countAtom 变成 5

在组件中使用:

1
2
3
4
5
6
7
8
9
function DoubleCounter() {
const [doubleCount, setDoubleCount] = useAtom(doubleCountAtom);
return (
<div>
<h2>Double Count: {doubleCount}</h2>
<button onClick={() => setDoubleCount(20)}>设置为 20</button>
</div>
);
}

这类似于 Vue 里的 computed,既能读取,也能修改!

异步atom

Jotai 可以直接处理异步数据:

1
2
3
4
const userAtom = atom(async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
return await response.json();
});

📌 解释

  • userAtom 是一个 异步 atom
  • 它会自动请求数据,并在完成后更新状态

在组件中使用:

1
2
3
4
function UserInfo() {
const [user] = useAtom(userAtom);
return <h2>User: {user?.name}</h2>;
}

自动处理异步请求,类似于 SWR / React Query!

持久化atom

Jotai 提供了 atomWithStorage,可以让 atom **自动存到 localStorage**:

1
2
3
4
import { atomWithStorage } from 'jotai/utils';

// 创建一个持久化 atom
const themeAtom = atomWithStorage("theme", "light");

作用

  • 默认值是 "light"
  • 状态存储在 localStorage 里(key = "theme"
  • 刷新页面不会丢失状态!

在组件中使用:

1
2
3
4
5
6
7
8
function ThemeToggle() {
const [theme, setTheme] = useAtom(themeAtom);
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
主题:{theme}
</button>
);
}

超级简单的持久化方案,不用 persist 额外配置!