无需useRef:在useMemo的useCallback中获取最新状态的最优方案
不触发子组件重渲染同时获取父组件最新状态的替代方案
你需要在父组件无关状态(count)变化时,子组件不重渲染,但回调函数能获取到最新的count值,除了useRef外,还有以下几种可行方案:
方案1:利用useState函数式更新读取最新状态
这是最简洁的方案,不需要额外依赖,借助setCount的函数式参数获取最新状态,同时不修改状态:
export default function App() { const [count, setCount] = useState(0); const onEditCallback = useCallback((name: string) => { // 通过setCount的函数式参数拿到最新prevCount setCount(prevCount => { console.log(name, prevCount); return prevCount; // 不修改状态,仅读取最新值 }); }, []); const renderMyChildComponent = useMemo(() => { return ( <ChildComponent name={"Hello"} onEditButtonClicked={onEditCallback} /> ); }, [onEditCallback]); return ( <div className="App"> <h2>{count}</h2> <Button variant="primary" className="mr-1" onClick={() => setCount(prev => prev + 1)} // 这里也建议用函数式更新避免闭包问题 > Click me to increment </Button> {renderMyChildComponent} </div> ); }
核心逻辑:setCount的函数式参数会接收当前最新的状态值,我们在这个函数内部读取该值后返回原状态,既拿到了最新count,又不会触发状态更新。由于onEditCallback的依赖数组为空,不会随count变化而更新,子组件也就不会重渲染。
方案2:用useReducer管理状态
把状态迁移到useReducer,利用稳定的dispatch函数触发包含最新状态的操作:
// 定义action类型 type Action = | { type: 'INCREMENT' } | { type: 'LOG_COUNT'; payload: string }; // reducer处理逻辑 const reducer = (state: { count: number }, action: Action) => { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'LOG_COUNT': // 在reducer里直接访问最新的state.count console.log(action.payload, state.count); return state; default: return state; } }; export default function App() { const [state, dispatch] = useReducer(reducer, { count: 0 }); const onEditCallback = useCallback((name: string) => { // 仅触发日志action,依赖的dispatch是稳定的 dispatch({ type: 'LOG_COUNT', payload: name }); }, [dispatch]); const renderMyChildComponent = useMemo(() => { return ( <ChildComponent name={"Hello"} onEditButtonClicked={onEditCallback} /> ); }, [onEditCallback]); return ( <div className="App"> <h2>{state.count}</h2> <Button variant="primary" className="mr-1" onClick={() => dispatch({ type: 'INCREMENT' })} > Click me to increment </Button> {renderMyChildComponent} </div> ); }
核心逻辑:useReducer返回的dispatch函数是稳定的,不会随状态变化而更新,因此onEditCallback的依赖不会改变,子组件不会重渲染。所有需要访问最新状态的逻辑都放在reducer中,确保能拿到最新的count值。
方案3:React.memo配合稳定回调优化子组件
如果子组件是纯组件,用React.memo包裹子组件,同时保持回调稳定,就不需要用useMemo包裹子组件渲染:
首先修改子组件:
// 用React.memo包裹,浅比较props const ChildComponent = React.memo(({ name, onEditButtonClicked }) => { return <button onClick={() => onEditButtonClicked(name)}>Edit</button>; });
然后父组件:
export default function App() { const [count, setCount] = useState(0); const onEditCallback = useCallback((name: string) => { // 同样用方案1的方式获取最新count setCount(prevCount => { console.log(name, prevCount); return prevCount; }); }, []); return ( <div className="App"> <h2>{count}</h2> <Button variant="primary" className="mr-1" onClick={() => setCount(prev => prev + 1)} > Click me to increment </Button> <ChildComponent name={"Hello"} onEditButtonClicked={onEditCallback} /> </div> ); }
核心逻辑:React.memo会对比子组件的props变化,由于onEditCallback稳定、name是固定值,父组件count变化时,子组件props没有变化,因此不会重渲染。
内容的提问来源于stack exchange,提问作者sudarshan1933
相关产品推荐
相关产品推荐

