使用useReducer在其他组件更新状态时组件未重渲染,原因何在?
问题:ComponentA调用dispatch后ComponentB未重渲染的原因及预期性分析
我有三个文件:store.js定义reducer与初始状态,ComponentA.js负责分发状态变更,ComponentB.js监听状态变更并渲染状态。但在ComponentA中调用dispatch后,ComponentB并未重渲染,这是为什么?该行为是否符合预期?
相关代码
// store.js import { useReducer } from "react"; const initialState = { firstName: "", lastName: "", }; function reducer(state, { fieldName, fieldValue }) { return { ...state, [fieldName]: fieldValue, }; } export function useForm() { return useReducer(reducer, initialState); }
// ComponentA.js import { useForm } from "./store"; export function ComponentA() { const [{ firstName }, dispatch] = useForm(); return ( <input type="text" value={firstName} onChange={(e) => { dispatch({ fieldName: "firstName", fieldValue: e.target.value, }); }} /> ); }
// ComponentB.js import { useForm } from "./store"; export function ComponentB() { const [{ firstName }] = useForm(); return <p>{firstName}</p>; }
// App.js import { ComponentA } from "./ComponentA"; import { ComponentB } from "./ComponentB"; import "./styles.css"; export default function App() { return ( <div className="App"> <h1>Hello CodeSandbox</h1> <h2>Start editing to see some magic happen!</h2> <ComponentA /> <ComponentB /> </div> ); }
原因分析
核心问题在于**useForm每次调用都会创建独立的useReducer实例**:
ComponentA和ComponentB各自导入并调用useForm(),两个组件拥有完全独立的状态副本,彼此没有关联。- 当
ComponentA调用dispatch时,只会更新自己内部的状态,ComponentB的状态仍然是初始的{firstName: ""},因此不会触发重渲染。
是否符合预期
该行为完全符合React Hook的设计预期:
useReducer(及所有React状态Hook)的核心特性是组件级状态隔离,每个组件调用Hook时都会创建独立的状态上下文,默认不会在组件间共享状态。如果需要共享状态,需手动通过状态提升、Context API或状态管理库(如Redux)实现。
解决方案:用Context共享状态
借助React Context API,可将useReducer的状态和dispatch函数共享给子组件,实现状态同步:
修改后的store.js
import { useReducer, createContext, useContext } from "react"; const initialState = { firstName: "", lastName: "", }; function reducer(state, { fieldName, fieldValue }) { return { ...state, [fieldName]: fieldValue, }; } const FormContext = createContext(); export function FormProvider({ children }) { const [state, dispatch] = useReducer(reducer, initialState); return ( <FormContext.Provider value={{ state, dispatch }}> {children} </FormContext.Provider> ); } export function useForm() { return useContext(FormContext); }
修改后的App.js
import { ComponentA } from "./ComponentA"; import { ComponentB } from "./ComponentB"; import { FormProvider } from "./store"; import "./styles.css"; export default function App() { return ( <FormProvider> <div className="App"> <h1>Hello CodeSandbox</h1> <h2>Start editing to see some magic happen!</h2> <ComponentA /> <ComponentB /> </div> </FormProvider> ); }
修改后的ComponentA.js
import { useForm } from "./store"; export function ComponentA() { const { state: { firstName }, dispatch } = useForm(); return ( <input type="text" value={firstName} onChange={(e) => { dispatch({ fieldName: "firstName", fieldValue: e.target.value, }); }} /> ); }
修改后的ComponentB.js
import { useForm } from "./store"; export function ComponentB() { const { state: { firstName } } = useForm(); return <p>{firstName}</p>; }
修改后,ComponentA和ComponentB共享同一个状态实例,调用dispatch时会同步更新所有消费该Context的组件,ComponentB即可正确重渲染。
内容的提问来源于stack exchange,提问作者Daniel Nakamashi
相关产品推荐
相关产品推荐

