React状态更新时组件数组重复初始化如何解决
我在开发训练计划创建表单的周视图模块时,会先生成包含一周所有天的组件数组,通过currentDay状态控制具体渲染哪一天的内容。目前遇到的问题是:每次点击日期标签切换currentDay状态时,这个天数组都会被重新初始化——我在日期项的生成逻辑里加了console.log,每次切换都能看到全量日期的打印输出。
我期望的效果是:一次性生成所有天对应的组件数组,后续切换日期时直接从缓存的数组里取对应项渲染,不需要每次切换都重新创建整个数组。
目前已经尝试过的方案:
- 抽离拆分
WeekView、DayView独立组件 - 使用
useMemo钩子做缓存,但没有达到预期效果
复现代码如下:
import React, { useContext } from 'react'; import { ProgramContext } from '../state/context'; import { setCurrentDay } from '../state/reducer'; import { Tabs, TabWindow, WindowTab } from '../styled'; import DayView from './DayView'; const WeekView: React.FC = () => { const { state, dispatch } = useContext(ProgramContext); const handleDayClick = (index: number) => () => { if (index === state.currentDay) return; dispatch(setCurrentDay(index)); }; const days = [...Array(state.daysPerWeek).keys()] .map(i => i + state.currentWeek * state.daysPerWeek) .map(day => { console.log(day); return ( <DayView key={day} day={day} /> ) }); return ( <TabWindow> <Tabs> {[...Array(state.daysPerWeek).keys()].map(day => { return ( <WindowTab active={state.currentDay === day} key={day} onClick={handleDayClick(day)} > {`Dag ${(day % state.daysPerWeek) + 1}`} </WindowTab> ) })} </Tabs> {days[state.currentDay]} </TabWindow> ) }; export default WeekView;
你之前用useMemo没生效,核心原因是依赖项配置错误——这个天数组只和state.daysPerWeek、state.currentWeek两个值有关,和你切换的currentDay完全无关。如果你之前给useMemo传的依赖包含了整个state对象、或者误加了currentDay作为依赖,就会导致每次切换日期都重算数组,缓存失效。只要把这两个关联值作为依赖项传入useMemo,切换currentDay触发重渲染时就不会重新执行数组生成逻辑。
额外做两个优化可以进一步减少不必要的性能损耗:
- 把标签点击的回调函数用
useCallback缓存,避免每次渲染生成新的函数实例传给子组件 - 给
DayView组件包裹React.memo,只有传入的day属性变化时才重新渲染对应日期的组件
修改后的完整代码:
import React, { useContext, useMemo, useCallback } from 'react'; import { ProgramContext } from '../state/context'; import { setCurrentDay } from '../state/reducer'; import { Tabs, TabWindow, WindowTab } from '../styled'; import DayView from './DayView'; const WeekView: React.FC = () => { const { state, dispatch } = useContext(ProgramContext); // 缓存点击回调,dispatch本身是稳定不变的,仅当currentDay变化时更新回调 const handleDayClick = useCallback((index: number) => { if (index === state.currentDay) return; dispatch(setCurrentDay(index)); }, [dispatch, state.currentDay]); // 天数组仅在每周天数、当前周变化时重新生成,切换同周内日期不会触发重算 const days = useMemo(() => { return [...Array(state.daysPerWeek).keys()] .map(i => i + state.currentWeek * state.daysPerWeek) .map(day => { console.log('初始化天组件:', day); return ( <DayView key={day} day={day} /> ) }); }, [state.daysPerWeek, state.currentWeek]); return ( <TabWindow> <Tabs> {[...Array(state.daysPerWeek).keys()].map(day => { return ( <WindowTab active={state.currentDay === day} key={day} onClick={() => handleDayClick(day)} > {`Dag ${(day % state.daysPerWeek) + 1}`} </WindowTab> ) })} </Tabs> {days[state.currentDay]} </TabWindow> ) }; export default WeekView;
注意:
DayView组件需要用React.memo包裹导出,写法参考export default React.memo(DayView),这样非当前展示的DayView不会因为父组件重渲染而跟着重渲染,性能提升会更明显。
改完之后可以验证:只有首次加载、切换周的时候才会打印天组件初始化的log,切换同周内的不同日期时,不会再触发全量的log打印,天数组只会在依赖变化时重新生成。
内容的提问来源于stack exchange,提问作者ola_bandola

