React:梳理组件的生命周期

什么是生命周期

在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。

组件从 被创建被销毁 的过程称为组件的生命周期。

组件的生命周期可分成三个状态:

  • Mounting(挂载时)
  • Updating(更新时)
  • Unmounting(卸载时)

组件的生命周期可分为三个阶段:

  • Render: 用于计算当前的状态/更新信息,会根据产生的任务的优先级,安排任务的调度(schedule)
  • Pre-commit: commit 之前,可以获取当前 DOM 的快照(snap)
  • Commit: 把所有更新都 commit 到 DOM 树上

生命周期方法

图解

常见方法版

react-lifecycle-1.png
( 图片来源:projects.wojtekmaj.pl/react-lifec…

完整方法版

react-lifecycle.png

( 图片来源:projects.wojtekmaj.pl/react-lifec…

方法

– contructor

constructor(props);
复制代码

通常,在 React 中,构造函数仅用于以下两种情况:

  • 通过给 this.state 赋值对象来初始化内部 state。( 唯一可以直接修改 state 的地方)
  • 为事件处理函数绑定实例

constructor() 函数中不要调用 setState() 方法。

constructor(props) {
  super(props);
  // 不要在这里调用 this.setState()
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}
复制代码

– getDerivedStateFromProps

static getDerivedStateFromProps(props, state)
复制代码

这个方法是 “如何用 props 初始化 state ” 的最佳实践。

注意,该方法在每次 render 前都会被调用。

此方法适用于罕见的用例(表单控件获取默认值)。当 state 是从 props 获取时,就必须要维护两者的一致性,这将会增加复杂度和 bug

– shouldComponentUpate

shouldComponentUpdate(nextProps, nextState);
复制代码

propsstate 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用 forceUpdate() 时不会调用该方法。

此方法 仅作为性能优化 的方式而存在。它的工作一般可以由 PrueComponent 自动实现。

后续版本,React 可能会将 shouldComponentUpdate 仅视为提示,并且,当返回 false 时,仍可能导致组件重新渲染。

– render

render();
复制代码

用于描述 DOM 结构,组件中唯一必须实现的方法。

– getSnapshotBeforeUpdate

getSnapshotBeforeUpdate(prevProps, prevState);
复制代码

能在组件发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()

– componentDidMount

componentDidMount();
复制代码

在组件挂载后(插入 DOM 树中)立即调用。在这里可以安全操作 DOM 节点、发送ajax 请求(DOM 节点的初始化)或一些副作用的事情(订阅)

如果你在这里调用了 setState,它将触发额外渲染,虽然此渲染会发生在浏览器更新屏幕之前,但会导致性能问题。

– componentDidUpdate

componentDidUpdate(prevProps, prevState, snapshot);
复制代码

在更新后会被立即调用。首次渲染不会执行此方法。

⚠️ 注意, 如果直接调用 setState(),它必须被包裹在一个条件语句里,否则会导致死循环。

– componentWillUnmount

componentWillUnmount();
复制代码

在组件卸载及销毁之前直接调用,做一些资源释放操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。

⚠️ 注意:不应调用 setState(),因为该组件将永远不会重新渲染。

演示

Clock

用官方的经典 Clock 组件,可以清楚的展示出常见生命周期方法的应用:

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = { date: new Date() };
  }

  componentDidMount() {
    this.timerID = setInterval(() => this.tick(), 1000);
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  componentDidUpdate() {
    console.log("component did update!");
  }

  tick() {
    this.setState({
      date: new Date(),
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(<Clock />, document.getElementById("root"));
复制代码

在组件第一次挂载时,设置一个定时器;同时,在组件卸载时,清除定时器。

state.date 发生改变时,会引发组件的重新渲染。

getSnapshotBeforeUpdate 实践

以特殊方式处理滚动位置的聊天线程:因为每一条新消息的置顶设定,无法让页面固定在某处,所以在每次更新前都需要计算调整滚动条的位置。

class ScrollList extends PureComponent {
  state = {
    messages: [],
  };
  getSnapshotBeforeUpdate() {
    return this.rootNode.scrollHeight;
  }

  componentDidUpdate(prevProps, prevState, prevScrollHeight) {
    const scrollTop = this.rootNode.scrollTop;
    if (scrollTop < 5) return;
    this.rootNode.scrollTop =
      scrollTop + (this.rootNode.scrollHeight - prevScrollHeight);
  }

  render() {
    return (
      <div className="scroll-list" ref={(n) => (this.rootNode = n)}>
        {this.state.messages.map((msg) => (
          <div>{msg}</div>
        ))}
      </div>
    );
  }
}
复制代码

DOM 更新前,通过 getSnapshotBeforeUpdate 获取原来的元素内容高度( scrollHeight ),并作为第三个参数传给 componentDidUpdate

componentDidUpdate 中,通过添加元素内容高度差值( this.rootNode.scrollHeight - prevScrollHeight ),调整滚动条位置( scrollTop )。

参考资料

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享