React中在useReducer内获取其他Provider值的实现及写法验证
React中useReducer内部能否获取Context值?
问题背景
在useContext的Provider内部使用useReducer时,需要借助另一个Provider的值来验证action,想明确:能不能在useReducer的reducer函数内部直接获取该Context值?还是必须通过参数传入?
错误尝试的代码示例
用户最初尝试直接在reducer里调用Context钩子,代码如下:
Reducer代码
export default function reducer(state: State, action: Action): State { switch(action.type) { case "set-amount": { const { amount } = action.state const { maxAmount } = useFooContext() // ❌ 违反React钩子规则,会报错 if (amount > maxAmount) // 预期的验证逻辑 return { ...state, amount } } } }
Provider代码
export function Provider(...) { const [state, dispatch] = useReducer(reducer, {}, initState) return ... }
错误原因
Reducer函数不属于React组件或自定义钩子的上下文,不符合React钩子的调用规则(钩子必须在组件/自定义钩子的顶层调用,且每次渲染调用顺序一致),所以直接在reducer里调用useFooContext()会触发React的钩子规则错误。
修改后的写法验证
用户根据建议尝试通过Ref传递Context值,代码如下,我们来分析可行性:
Provider代码
export default function Provider( ... ) { const foo = useFooContext() const fooRef = React.useRef(foo) const reducerFn = useMemo( () => { fooRef.current = foo return reducer( fooRef ) }, [foo]) const [state, dispatch] = useReducer( reducerFn, {}, initState) return ( ... ) }
Reducer代码
export default function(ref: React.MutableRefObject<Foo>) { return function(state: State, action: Action): State { ... if (action.state.amount > ref.current.maxAmount) { // 验证逻辑 } ... } }
结论:写法可行,但可优化
这种通过Ref传递Context值的方式是有效的,因为Ref的更新不会触发组件重渲染,且能让reducer获取到最新的Context值。不过可以做一些简化优化:
优化后的Provider代码
export default function Provider( ... ) { const foo = useFooContext() const fooRef = React.useRef(foo) // 每次组件渲染时更新Ref,确保值始终是最新的 React.useEffect(() => { fooRef.current = foo }, [foo]) // 用useCallback缓存reducer函数,避免不必要的重创建 const reducerFn = React.useCallback((state: State, action: Action) => { return reducer(fooRef, state, action) }, []) const [state, dispatch] = useReducer(reducerFn, {}, initState) return ( ... ) }
优化后的Reducer代码
export default function reducer(ref: React.MutableRefObject<Foo>, state: State, action: Action): State { switch(action.type) { case "set-amount": { const { amount } = action.state if (amount > ref.current.maxAmount) { // 比如超出最大值时强制设为最大值 return { ...state, amount: ref.current.maxAmount } } return { ...state, amount } } default: return state } }
另一种更直观的方案:dispatch时传入Context值
如果Context值变化不频繁,也可以直接在Provider里封装dispatch,把需要的Context值作为action的一部分传入:
Provider代码
export function Provider({ children }) { const { maxAmount } = useFooContext() const [state, dispatch] = useReducer(reducer, {}, initState) // 封装dispatch,自动带上maxAmount const customDispatch = React.useCallback((action) => { dispatch({ ...action, maxAmount }) }, [dispatch, maxAmount]) return ( <YourContext.Provider value={{ state, dispatch: customDispatch }}> {children} </YourContext.Provider> ) }
Reducer代码
export default function reducer(state: State, action: Action & { maxAmount?: number }): State { switch(action.type) { case "set-amount": { const { amount, maxAmount } = action if (amount > maxAmount) { return { ...state, amount: maxAmount } } return { ...state, amount } } default: return state } }
这种方案不需要使用Ref,逻辑更直观,适合大多数场景。
内容的提问来源于stack exchange,提问作者Sean
相关产品推荐
相关产品推荐

