React兄弟组件重渲染优化:Header触发List更新且不渲染App
解决思路与方案
核心问题很明确:useRef 的值变化不会触发组件重渲染,你修改 activeUser.current 后,App 组件不会重新渲染,导致传递给 List 的 test props 始终是旧值,自然无法更新 List 内容。
下面提供两种最优解决方案,适配不同场景:
方案一:利用路由参数直接获取用户(最简洁)
你的路由已经定义了 /:userName,完全可以通过路由参数匹配用户,不需要额外维护 activeUser 状态,从根源避免不必要的重渲染:
修改步骤:
- App 组件:移除状态相关逻辑,简化为路由容器:
export default function App() { console.log("App run") const todosArr = useRef<task_T_Model[]>([]) return ( <BrowserRouter> <div> <Heder /> <br /><br /><br /><br /><br /><br /><br /> <Routes> <Route path='/' element={<Navigate replace to="/Adir" />} /> <Route path='/:userName' element={<List />} /> </Routes> </div> </BrowserRouter> ) }
- Header 组件:仅保留路由跳转逻辑,无需额外回调:
export default function Heder() { return ( <AppBar id='heder-bar' > <Toolbar disableGutters> { usersArr.map((user) => ( <h1 key={user.userid}> <Link to={`/${user.userName}`}>{user.userName}</Link> </h1> )) } </Toolbar> </AppBar> ) }
- List 组件:通过路由参数匹配对应用户:
import { useParams } from 'react-router-dom' export default function List() { const { userName } = useParams<{ userName: string }>() const test = usersArr.find(user => user.userName === userName) console.log("list run") console.log(test) return ( <div> {Array(9).fill(0).map((_, idx) => ( <h1 key={idx}>{test?.userName}</h1> ))} </div> ) }
这种方式下,App 组件仅初始化渲染一次,Header 点击仅触发路由变化,List 组件根据路由参数自动更新内容,完全符合你的需求。
方案二:使用 React Context 隔离状态(适合复杂状态场景)
如果后续需要在多个组件共享 activeUser 状态,推荐用 Context 封装状态逻辑,避免 App 组件重渲染:
步骤:
- 创建 UserContext:
import { createContext, useContext, useState, useCallback } from 'react' type UserContextType = { activeUser: user_T_model | undefined setActiveUser: (user: user_T_model) => void } const UserContext = createContext<UserContextType | undefined>(undefined) export function UserProvider({ children }: { children: React.ReactNode }) { const [activeUser, setActiveUser] = useState<user_T_model | undefined>( usersArr.find(user => user.admin === true) ) const updateActiveUser = useCallback((user: user_T_model) => { setActiveUser(user) }, []) return ( <UserContext.Provider value={{ activeUser, setActiveUser: updateActiveUser }}> {children} </UserContext.Provider> ) } export function useUser() { const context = useContext(UserContext) if (!context) throw new Error('useUser must be used within a UserProvider') return context }
- 修改 App 组件:用
UserProvider包裹子组件,自身不再持有状态:
import { UserProvider } from './UserContext' export default function App() { console.log("App run") const todosArr = useRef<task_T_Model[]>([]) return ( <UserProvider> <BrowserRouter> <div> <Heder /> <br /><br /><br /><br /><br /><br /><br /> <Routes> <Route path='/' element={<Navigate replace to="/Adir" />} /> <Route path='/:userid' element={<List />} /> </Routes> </div> </BrowserRouter> </UserProvider> ) }
- Header 组件:通过自定义 Hook 获取状态更新函数:
import { useUser } from './UserContext' export default function Heder() { const { setActiveUser } = useUser() return ( <AppBar id='heder-bar' > <Toolbar disableGutters> { usersArr.map((user) => ( <h1 key={user.userid} onClick={() => setActiveUser(user)} > <Link to={`/${user.userName}`}>{user.userName}</Link> </h1> )) } </Toolbar> </AppBar> ) }
- List 组件:通过自定义 Hook 获取活跃用户:
import { useUser } from './UserContext' export default function List() { const { activeUser } = useUser() console.log("list run") console.log(activeUser) return ( <div> {Array(9).fill(0).map((_, idx) => ( <h1 key={idx}>{activeUser?.userName}</h1> ))} </div> ) }
这种方式下,App 组件仅渲染一次,只有订阅了 UserContext 的 Header 和 List 组件会在状态变化时重渲染,完美实现最小化重渲染的目标。
原方案失效原因
useRef设计初衷是存储无需触发重渲染的值,修改.current不会通知 React 更新组件。- 即使你修改了
shoulsListUpdate.current,由于 App 未重渲染,传递给 List 的testprops 始终是旧的activeUser.current,所以 List 不会更新。
内容的提问来源于stack exchange,提问作者adir
相关产品推荐
相关产品推荐

