仅更新一个状态变量时,所有组件都会重新渲染

布卢伊斯卡

我有一个页面,其中显示每180秒(3分钟)从API检索到的一些数据。当页面加载时,我有一个初始的useEffect钩子来调用retrieveData()函数。然后,使用下面的代码和一个名为elapsed的状态变量,我开始间隔180秒。每隔一秒钟,我都会更新一个进度条,该进度条从3分钟开始倒计时并显示剩余时间。这将使用经过的状态变量。

这是开始间隔的钩子:

useEffect(() => {
    const interval = setInterval(() => {
      if (elapsed < dataUpdateInterval) {
        setElapsed(elapsed + 1);
      } else {
        setElapsed(0);
        retrieveData();
      }
    }, 1000);
    return () => clearInterval(interval);
  }, [elapsed]);

我的问题是,为什么间隔的每个刻度都会重新渲染页面上的其余组件?我以为,由于我只更新经过的状态变量,因此只有那些组件会更新使用该状态变量的组件。我显示数据的表(存储在另一个状态变量中)应该每3分钟更新一次。

如果无法提供其他信息,我们很乐意提供。

更新1:在开发工具中,我得到重新渲染的原因是“挂钩已更改”

更新2:以下是使用此计时器的代码片段。StatusList和StatusListItem组件使用React.memo并且是功能组件。状态列表是一个IonList,而StatusListItem是一个顶级IonItem(来自Ionic)

import React, { useEffect, useState } from "react";
import { Layout } from "antd";
import StatusListItemNumbered from "./StatusListItemNumbered";
import { Container, Row, Col } from "react-bootstrap";
import StatusList from "./StatusList";
import { Progress } from "antd";
import moment from "moment";

const Content = React.memo(() => {
  const dataUpdateInterval = 180;

  const [elapsed, setElapsed] = useState(0);
  const [data, setData] = useState();

  const timeLeft = (interval, elapsed) => {
    let time = moment.duration(interval - elapsed, "seconds");
    return moment.utc(time.asMilliseconds()).format("mm:ss");
  };

  const retrieveData = () => {
    console.log("Retrieving data");
    SomeApi.getData().then(items => {
        setData(items);
    })
  };

  //The effect hook responsible for the timer
  useEffect(() => {
    setTimeout(() => {
      if (elapsed < dataUpdateInterval) {
        setElapsed(elapsed + 1);
      } else {
        setElapsed(0);
        retrieveData();
      }
    }, 1000);
  }, [elapsed]);

  //Retrieve data the very first time the component loads.
  useEffect(() => {
    retrieveData();
  }, []);

  //Component styling
  return (
    <Layout.Content style={{ padding: "20px 20px" }}>
      <div className={css(styles.siteLayoutContent)}>
        <Container className={css(styles.mainContainer)}>
          <Row className={css(styles.progressRow)}>
            <Col>
              <Progress
                style={{ marginLeft: "17px", width: "99%" }}
                strokeColor={{
                  "0%": "#108ee9",
                  "100%": "#87d068",
                }}
                percent={(100 / dataUpdateInterval) * elapsed}
                format={() => `${timeLeft(dataUpdateInterval, elapsed)}`}
              />
            </Col>
          </Row>
          <Row>
            <Col sm={6}>
              <StatusList listName="Data">
                {data &&
                  data.map((item, index) => {
                    return (
                      <StatusListItemNumbered
                        key={index}
                        value={item.count}
                        label={item.company}
                      />
                    );
                  })}
              </StatusList>
            </Col>
          </Row>
        </Container>
      </div>
    </Layout.Content>
  );
});

export default Content;
95faf8e76605e973

根据评论部分:

因此,看起来渲染中的data.map是元凶。为什么那会导致重新呈现我的列表项?一旦将data.map移到StatusList组件中,而不是将各项作为props.children传递进来,它便停止了重新渲染

对于OP,问题在于包装组件props.children的使用React.memo由于React.memo只有比较浅的道具,所以被React.memo包装的子组件仍将重新渲染。

请参阅下面的示例片段。只有1个重新渲染会在状态更新时触发,这是第ChildComponent一个作为对象传递的对象children

let number_of_renders = 0;

const ChildComponent = React.memo((props) => {
  number_of_renders++;
  React.useEffect(()=>{
    console.log("ChildComponent renders: " + number_of_renders)
  })
  return (
    <div></div>
  )
})

const App = () => {
  const [obj, setObj] = React.useState({ key: "tree", val: "narra" });
  
  const [primitiveData, setPrimitiveData] = React.useState(0)
  
  return (
    <React.Fragment>
      <button onClick={()=>setObj({ key: "tree", val: "narra" })}>Update Object</button>
      <button onClick={()=>setPrimitiveData(primitiveData + 1)}>Update Primitive Data</button>
      <ChildComponent>
        {obj}
      </ChildComponent>
      <ChildComponent>
        {obj.val}
      </ChildComponent>
      <ChildComponent>
        primitiveData
      </ChildComponent>
    </React.Fragment>
  )
}

ReactDOM.render(<App/>, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

简要讨论props.children在中的用法的问题React memohttps : //github.com/facebook/react/issues/14463


默认情况下,如果父级重新渲染,则子级组件也会触发它们各自的渲染。React的区别在于:尽管在这种情况下,子组件会重新渲染,但DOM不会更新。在以下示例中,重新渲染很明显,其中ChildComponent甚至没有道具,因此不使用父级的状态:

const ChildComponent = () => {
  React.useEffect(()=>{
    console.log("ChildComponent rerender")
  })
  return (
    <div>Child Component</div>
  )
}

const App = () => {
  const [state, setState] = React.useState(true);
  
  return (
    <React.Fragment>
      <button onClick={()=>setState(!state)}>Update State</button>
      <ChildComponent/>
    </React.Fragment>
  )
}

ReactDOM.render(<App/>, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

如果您不希望子组件不受父对象的状态更改影响而在子组件上触发渲染,则可以使用 React.memo

const ChildComponent = React.memo((props) => {
  React.useEffect(()=>{
    console.log("ChildComponent rerender")
  })
  return (
    <div>Child Component</div>
  )
})

const TheStateDependentChild = React.memo((props) => {
  React.useEffect(()=>{
    console.log("TheStateDependentChild rerender")
  })
  return (
    <div>TheStateDependentChild Component</div>
  )
})

const App = () => {
  const [state, setState] = React.useState(true);
  
  return (
    <React.Fragment>
      <button onClick={()=>setState(!state)}>Update State</button>
      <ChildComponent/>
      <TheStateDependentChild sampleProp={state}/>
    </React.Fragment>
  )
}

ReactDOM.render(<App/>, document.getElementById("root"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

仅更新几个状态变量时如何避免引用所有状态变量?

更新两个状态变量时,该组件被渲染两次是否正常?

React Hooks:如何根据另一个状态变量更新来更新状态变量

更新一个状态变量以更改另一状态变量中的数据

React:我如何从另一个带有状态变量的组件运行一个函数

两个状态变量改变但只想重新渲染一次

如何将带有状态变量的 jsx 导入另一个 React 组件?

当我使用history.push时,如何从一个组件到另一个组件获取状态变量?

Redux-在复选框切换时更新状态变量(和重新呈现组件)

输出组件循环并避免仅更新一个组件时重新渲染整个循环

为什么一个状态变量更新而另一个不更新?

如何将状态变量的值从一个组件传递给同级组件?

反应钩子状态变量在重新渲染后不更新

Vuex:如何使用基于另一个状态变量的值定义状态变量?

反应一个状态变量取决于其他多个状态变量

React:如何通过匹配另一个状态变量的值来修改状态变量

我应该在React组件中初始化一个空状态变量吗?

当在容器的状态树中仅修改一个子容器的状态时,为什么容器的所有PureComponent子容器都会更新?

React组件将状态变量作为参数,未在更新时呈现

React / JSX:我可以在另一个状态变量中使用一个状态变量吗?

如何通过反应状态变量数组并确定它是否具有一个真值

使用循环来响应渲染组件,但是当状态更新时,它会重新渲染所有内容

即使状态没有改变,React 组件在重新渲染时也会创建一个新的状态实例

从useCallback访问状态变量时,值不会更新

如何保存-恢复所有opengl状态变量

在一个组件的状态改变时重新渲染第二个组件

从一个同级组件分派 redux 操作时,如何让所有同级组件正确重新渲染?

Vuejs:从组件观察状态变量

React State 仅在设置无用的状态变量和必要的状态变量时更新