我有一个页面,其中显示每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;
根据评论部分:
因此,看起来渲染中的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 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>
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句