Zustand 实现 React 全局状态管理

前言

之前发现在用Nextjs写组件间通信的时候写的非常折磨，要靠传递给共同的父组件，这样父组件传数据和回调函数给子组件，子组件再调用这样来互相传递，只能说这样的写法我学会了就不会想去用了。

随后我尝试兄弟组件间状态提升，虽然不那么麻烦了，但是共同的父组件还是要写一推，你还要去判断某组件和某组件在通信的时候谁是共同的父组件，如果中间跨了很多层就要逐级往下传props，还是很麻烦。

虽说Context API能一定程度上解决往下传太麻烦这个问题，但仍然不够简便，我想要的是在任意一个组件中，都能简单的调用某个/某些组件里的功能。

这样的话有两个选择，一是直接用事件总线（EventEmitter），或者用Zustand 或 Redux库。其中用事件总线的话，多种调用情况的处理就要用到多个useEffect:

useEffect(() => {
eventBus.on("update", setData);
return () => eventBus.off("update", setData);
}, []);

这样其实如果同一组件要处理多个事件的话，写起来就更麻烦，于是我自己用Zustand写了个broadcast系统，用起来就非常方便了。

文章写到一半我觉得单个Broadcast的作用还是太局限了，于是写了三套，一劳永逸了，作用如下

Hook 名称 | 主要用途 | 机制 | 适用场景
useBroadcast | 全局广播 | 一次性通知所有监听者 | 适用于简单的全局消息通知
useEvent | 事件管理 | 支持多种事件及监听器 | 适用于更复杂的事件管理
useFunction | 动态函数注册 | 存储和调用命名函数 | 适用于插件系统或动态调用函数

具体的函数都放在文末了，或者你也可以在Github Gist中查看。Zustand实现React全局状态管理

示例

广播

对于接收方:

import { useBroadcast } from '@/store/useBroadcast';
import { useEffect } from 'react';

export default function Receiver() {
const { registerCallback, unregisterCallback } = useBroadcast();

useEffect(() => {
const handleMessage = (message) => {
if (message.action == 'A') {
// DO A
}
if (message.action == 'B') {
// DO B
}
};
registerCallback(handleMessage);
return () => {
unregisterCallback();
};
}, [registerCallback, unregisterCallback]);

return;
}

对于发送方:

import { useBroadcast } from '@/store/useBroadcast';

export default function Sender() {
const { broadcast } = useBroadcast();
return broadcast({action:"A"})}>
}

简直是太优雅了，唯一的缺点是你可能得记住每个action是用来干什么的，毕竟这个注册了也没法给你关联起来。

示例中我是直接传递了个Object作为消息，实际上虽然传什么类型都行，我也推荐你统一传个Object，这样可用于指定的动作更多。

事件

对于接收方（监听事件）

import { useEvent } from '@/store/useEvent';
import { useEffect } from 'react';

export default function Receiver() {
const { on, off } = useEvent();

useEffect(() => {
const handleCustomEvent = (data) => {
if (data.action === 'A') {
// DO A
}
if (data.action === 'B') {
// DO B
}
};

on('customEvent', handleCustomEvent);

return () => {
off('customEvent', handleCustomEvent);
};
}, [on, off]);

return null;
}

对于发送方（触发事件）

import { useEvent } from '@/store/useEvent';

export default function Sender() {
const { emit } = useEvent();

return emit('customEvent', { action: 'A' })}> ;
}

猜你想问，事件和广播有什么区别？

简单来说，从结果看，它们能实现的功能基本上是相同的，都是一个组件调用，另外的组件执行相应操作。

区别在于“另外的组件”的数量，Broadcast会发往全局，任何一个注册了registerCallback的组件都会收到这个消息，如果你的消息是全局的这种就适合用Broadcast，其余情况下useEvent足以。

函数

对于提供方（注册函数）

import { useFunction } from '@/store/useFunction';
import { useEffect } from 'react';

export default function Provider() {
const { registerFunction } = useFunction();

useEffect(() => {
registerFunction('greet', (name) => {
return `Hello, ${name}!`;
});

registerFunction('sum', (a, b) => {
return a + b;
});
}, [registerFunction]);

return null;
}

对于调用方（调用已注册函数）

import { useFunction } from '@/store/useFunction';

export default function Caller() {
const { callFunction } = useFunction();

return (

console.log(callFunction('greet', 'Alice'))}>
Say Hello

console.log(callFunction('sum', 5, 10))}>
Add Numbers


);
}

非常适合在组件间相互调用函数，让我回到了我以前写原生Js函数时的感觉。

(你还可以用hasFunction来检查是否已注册了这个函数)

代码

pnpm i zustand

useBroadcast

// @/store/useBroadcast.js
import { create } from 'zustand';

export const useBroadcast = create((set, get) => ({
callbacks: [],

registerCallback: (callback) => {
set((state) => ({
callbacks: [...state.callbacks, callback],
}));
},

broadcast: (message) => {
get().callbacks.forEach((callback) => callback(message));
},

unregisterCallback: (callback) => {
set((state) => ({
callbacks: state.callbacks.filter((cb) => cb !== callback),
}));
},
}));

useEvent

// @/store/useEvent.js
import { create } from 'zustand';

export const useEvent = create((set, get) => ({
listeners: {},

on: (eventName, listener) => {
set((state) => ({
listeners: {
...state.listeners,
[eventName]: [...(state.listeners[eventName] || []), listener],
},
}));
},

emit: (eventName, ...args) => {
const listeners = get().listeners[eventName] || [];
listeners.forEach((listener) => listener(...args));
},

off: (eventName, listener) => {
set((state) => ({
listeners: {
...state.listeners,
[eventName]: (state.listeners[eventName] || []).filter((l) => l !== listener),
},
}));
},
}));

useFunction

// @/store/useFunction.js
import { create } from 'zustand';

export const useFunction = create((set, get) => ({
functions: {},

registerFunction: (name, fn) => {
set((state) => ({
functions: {
...state.functions,
[name]: fn,
},
}));
},

callFunction: (name, ...args) => {
const { functions } = get();
const targetFn = functions[name];

if (typeof targetFn === 'function') {
return targetFn(...args);
}
return null;
},

hasFunction: (name) => {
return typeof get().functions[name] === 'function';
},
}));