前言
之前发现在用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 <button onClick={()=>broadcast({action:"A"})}></button>
}
简直是太优雅了,唯一的缺点是你可能得记住每个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 <button onClick={() => emit('customEvent', { action: 'A' })}></button>;
}
猜你想问,事件和广播有什么区别?
简单来说,从结果看,它们能实现的功能基本上是相同的,都是一个组件调用,另外的组件执行相应操作。
区别在于“另外的组件”的数量,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 (
<div>
<button onClick={() => console.log(callFunction('greet', 'Alice'))}>
Say Hello
</button>
<button onClick={() => console.log(callFunction('sum', 5, 10))}>
Add Numbers
</button>
</div>
);
}
非常适合在组件间相互调用函数,让我回到了我以前写原生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';
},
}));