当然, CounterLabel 重新渲染是正常的,因为 count 发生了变化,自然要重新渲染;但是对于 List 而言,就完全是不必要的更新了,因为它的渲染与 count 无关。 尽管 React 并不会在 reconciliation 阶段真的更新 DOM,毕竟完全没变化,但是仍然会执行 diffing 阶段来对前后的树进行对比,这仍然存在性能开销。
还记得 render 执行过程中的 diffing 和 reconciliation 阶段吗?前面讲过的东西在这里碰到了。
因此,为了避免不必要的 diffing 开销,我们应当考虑将特定的状态值放到更低的层级或组件中(与 React 中所说的「提升」概念刚好相反)。在这个例子中,我们可以通过将 count 放到 CounterLabel 组件中管理来解决这个问题。
Tip #2:合并状态更新
因为每次状态更新都会触发新的 render 调用,那么更少的状态更新也就可以更少的调用 render 了。
我们知道,React class 组件有 componentDidUpdate(prevProps, prevState) 的钩子,可以用来检测 props 或 state 有没有发生变化。尽管有时有必要在 props 发生变化时再触发 state 更新,但我们总可以避免在一次 state 变化后再进行一次 state 更新这种操作:
- import React from "react";
- import ReactDOM from "react-dom";
-
- function getRange(limit) {
- let range = [];
-
- for (let i = 0; i < limit; i++) {
- range.push(i);
- }
-
- return range;
- }
-
- class App extends React.Component {
- state = {
- numbers: getRange(7),
- limit: 7
- };
-
- handleLimitChange = e => {
- const limit = e.target.value;
- const limitChanged = limit !== this.state.limit;
-
- if (limitChanged) {
- this.setState({ limit });
- }
- };
-
- componentDidUpdate(prevProps, prevState) {
- const limitChanged = prevState.limit !== this.state.limit;
- if (limitChanged) {
- this.setState({ numbers: getRange(this.state.limit) });
- }
- }
-
- render() {
- return (
- <div>
- <input
- onChange={this.handleLimitChange}
- placeholder="limit"
- value={this.state.limit}
- />
- {this.state.numbers.map((number, idx) => (
- <p key={idx}>{number} </p>
- ))}
- </div>
- );
- }
- }
-
- const rootElement = document.getElementById("root");
- ReactDOM.render(<App />, rootElement);
这里渲染了一个范围数字序列,即范围为 0 到 limit。只要用户改变了 limit 值,我们就会在 componentDidUpdate 中进行检测,并设定新的数字列表。
毫无疑问,上面的代码是可以满足需求的,但是,我们仍然可以进行优化。
(编辑:ASP站长网)
|