为何每个React组件都需调用useDispatch?全局dispatch方案存疑
这个问题看似和Stack Overflow上的「Should I have one useDispatch per component?」重复,但实际提出了一种不同的实现思路:
常规写法示例
const ChildA = () => { const dispatch = useAppDispatch(); const incr = useCallback(() => { dispatch(increment()); }, [dispatch]); return <button onClick={incr}>ChildA</button>; }; const ChildB = () => { const dispatch = useAppDispatch(); const incr = useCallback(() => { dispatch(increment()); }, [dispatch]); return <button onClick={incr}>ChildB</button>; }; const ChildC = () => { const dispatch = useAppDispatch(); const incr = useCallback(() => { dispatch(increment()); }, [dispatch]); return <button onClick={incr}>ChildC</button>; }; export const MyApp = () => { const dispatch = useAppDispatch(); const count = useAppSelector((state) => state.counter.value); const incr = useCallback(() => { dispatch(increment()); }, [dispatch]); return ( <div> <button onClick={incr}>MyApp</button> <ChildA /> <ChildB /> <ChildC /> <div>{count}</div> </div> ); };
全局变量存储dispatch的写法示例
export const glo: { dispatch: ReturnType<typeof useAppDispatch>; } = { // @ts-ignore dispatch: null }; const ChildA = () => { const incr = useCallback(() => { glo.dispatch(increment()); }, []); return <button onClick={incr}>ChildA</button>; }; const ChildB = () => { const incr = useCallback(() => { glo.dispatch(increment()); }, []); return <button onClick={incr}>ChildB</button>; }; const ChildC = () => { const incr = useCallback(() => { glo.dispatch(increment()); }, []); return <button onClick={incr}>ChildC</button>; }; export const MyApp = () => { const dispatch = useAppDispatch(); glo.dispatch = dispatch; if (!glo.dispatch) { throw new Error("dispatch is falsy"); } const count = useAppSelector((state) => state.counter.value); const incr = useCallback(() => { glo.dispatch(increment()); }, []); return ( <div> <button onClick={incr}>MyApp</button> <ChildA /> <ChildB /> <ChildC /> <div>{count}</div> </div> ); };
提问者提到:当子组件达到30+时,全局变量写法看起来更简洁,那为什么仍要采用常规写法?
核心原因:全局写法的隐性问题
1. 组件失去独立性与可复用性
子组件直接绑死全局变量glo.dispatch,一旦脱离当前项目环境(比如单独写单元测试、在其他项目中复用),只要没初始化这个全局变量,组件直接报错。而常规写法中,每个组件通过useAppDispatch获取dispatch,自身是自洽的,能独立运行。
2. 多Store场景下必出问题
如果是微前端、多根组件这类存在多个Redux Store实例的场景,全局变量会被最后一次赋值的dispatch覆盖,导致部分组件用错Store,触发的action无法正确更新对应状态,排查起来极其麻烦。而useAppDispatch会自动关联当前组件所在的Context,完全不会有这个问题。
3. 类型安全形同虚设
为了让全局变量通过TypeScript检查,你用了@ts-ignore跳过类型校验,相当于主动放弃了TypeScript的保护。后续如果dispatch的类型发生变化,全局写法无法自动检测错误,只能等到运行时才暴露问题;常规写法则能通过类型提示提前发现。
4. 调试难度飙升
用全局dispatch的话,React DevTools里无法追踪到action是哪个组件触发的——所有调用都会显示成来自全局变量,调试时根本没法定位问题。而常规写法的dispatch操作会和组件树绑定,调试时能清晰看到触发来源。
5. 违背Redux的设计初衷
Redux用Context传递Store,就是为了避免全局变量带来的强耦合问题。直接用全局变量存dispatch,等于绕回了Redux原本要解决的全局状态管理痛点,完全没必要。
更简洁的规范写法
如果嫌常规写法重复代码多,其实有更优的替代方案,既符合规范又能减少代码量:
方案1:直接在事件回调中调用dispatch
React 18+对事件回调的重新创建做了优化,几乎不会有性能问题,完全可以不用useCallback包裹:
const ChildA = () => { const dispatch = useAppDispatch(); return <button onClick={() => dispatch(increment())}>ChildA</button>; };
方案2:封装自定义Hook
把重复逻辑抽成自定义Hook,一次封装到处用:
const useIncrement = () => { const dispatch = useAppDispatch(); return useCallback(() => dispatch(increment()), [dispatch]); }; // 子组件里直接用 const ChildA = () => { const incr = useIncrement(); return <button onClick={incr}>ChildA</button>; };
内容的提问来源于stack exchange,提问作者leodevbro

