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



我有一个页面,显示每180秒(3分钟(从API检索的一些数据。当页面加载时,我有一个初始的useEffect钩子来调用retrieveData((函数。然后,使用下面的代码和一个名为"消逝"的状态变量,我开始了一个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是顶级(来自Ionic(的IonItem

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;

根据评论部分:

所以看起来渲染中的data.map是罪魁祸首。为什么导致重新呈现我的列表项?一旦我移动了数据.map传入StatusList组件,而不是将项作为props.children,它停止重新渲染

在OP的情况下,问题是关于在React.memo封装的组件上使用props.children。由于React.memo只对道具进行了较浅的比较,因此React.memo封装的子组件仍将重新渲染。

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

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.childrenReact memo中的用法的问题:https://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>

我将从依赖数组中删除elapsed开始,因为您每秒钟都会无缘无故地运行钩子。然后我们可能必须看到整个组件,要么将其粘贴在这里,要么使用类似的东西https://codesandbox.io/对于最小再现示例

最新更新