【问题标题】:Avoiding re-renders for sibling React components with array of state stored in parent避免使用存储在父级中的状态数组重新渲染兄弟 React 组件
【发布时间】:2020-12-03 23:17:56
【问题描述】:

我正在渲染视频中的一系列帧,每个帧都在自己的组件中。帧作为状态存储在名为 Timeline 的父组件中。

由于我需要从子组件中操作帧数组的元素,因此我传入了索引为 i 的回调函数,并带有一个闭包。为了避免单个帧的变化影响所有帧,我在每个Frame 周围使用了memo 高阶组件。

尽管如此,我发现每次选择帧时,都会重新渲染所有帧。使用 whyDidyouUpdate 钩子 (https://usehooks.com/useWhyDidYouUpdate/),我将其归结为我的回调在每次渲染时都会发生变化。

我尝试做onClick={useCallback(() => selectFrame(i), [])},但框架根本没有改变颜色,这表明这在某种程度上阻碍了回调正常运行。

let Frame = memo(({frame, ...}) => (
    <img style={{border: frame.selected ? "green" : "red";}} />
);

let Timeline = () => {

    let [frames, setFrames] = useState([
        { id: "00000", selected: True, ...more image information },
        { id: "00001", selected: True, ...more image information },
        { id: "00002", selected: True, ...more image information },
    ]);

    let selectFrame = (i) => {
        let newFrames = [...frames];
        newFrames[i].mask = base64;
        setFrames(newFrames);
    }

    return (
        <div>
        {frames.map((f, i) => (
            <Frame
                frame={f}
                key={f.id}
                onClick={() => selectFrame(i)}
            />
        )}
        </div>
    ));
}

对处理此问题的正确方法有什么建议吗?谢谢

编辑:Drew 下面的解决方案为我指明了正确的方向。正确的语法是:

const selectFrame = (i) => useCallback(() =>
    setFrames(frames.map((f, j) => (
        (i === j) ? {...f, selected: !f.selected} : f
    ))), []);

您需要为每个函数设置不同的记忆回调。

【问题讨论】:

  • 我倾向于这只是适当的 React 行为。您正在传递一个回调,当它触发时,它会导致它被传递到的组件中重新呈现。如果您想完全避免这种情况,您可能只需要通过回调最常见的祖先/父级来提升您正在操作的状态/行为
  • 如果它是一个共享的有状态行为(不是上下文的),它可能会成为一个自定义 Hook...

标签: javascript reactjs react-hooks


【解决方案1】:

memo HOC 只是简单地对 props 进行浅层比较,每次映射 frames 时都会创建一个 new onClick 回调。转换为包含索引并返回的柯里化回调,并使用功能状态更新,并使用useCallback 挂钩记忆回调。

const Timeline = () => {

    const [frames, setFrames] = useState([
        { id: "00000", selected: True, ...more image information },
        { id: "00001", selected: True, ...more image information },
        { id: "00002", selected: True, ...more image information },
    ]);

    const selectFrame = useCallback(index => () => {
        setFrames(frames => frames.map((frame, i) => i === index ? {
          ...frame,
          mask: base64;
        } : frame));
    }, []);

    return (
        <div>
        {frames.map((f, i) => (
            <Frame
                frame={f}
                key={f.id}
                onClick={selectFrame(i)}
            />
        )}
        </div>
    ));
}

【讨论】:

  • 成功了!您能否详细说明为什么状态更新需要起作用?
  • @CristobalSciutto 我想它并不绝对需要是功能状态更新,但它肯定是一种模式,可以减少错误代码。如果有任何机会两个或多个状态更新可以在单个渲染周期中排队(即两个帧'onClick 快速点击),那么对于普通更新,所有排队的更新都使用相同的 current 状态与先前更新计算的状态。我还应该指出newFrames[i].mask = base64; 被认为是一种状态突变,因此创建了一个新的框架对象{ ...frame, mask: base64 }
猜你喜欢
  • 2018-04-05
  • 2022-06-29
  • 1970-01-01
  • 2020-05-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-13
  • 1970-01-01
相关资源
最近更新 更多