【React】生命周期

1. 内容一

首先我使用class组件实现一个简单的界面:

//实现计时器
class Clock extends React.Component { //Clock组件中添加state,相比props state 是私有的,只属于当前组件
  constructor(props) {
    super(props);
    this.state = {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')
);

以上代码没有任何疑点,都是之前的博客当中常见的语法

2.内容二 、生命周期函数

在具有许多组件的应用程序中,当组件被销毁时释放所占用的资源是非常重要的。
在内容一的基础上在Clock 组件中添加生命周期函数,生命周期函数有以下几种:

  • Clock 组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)”。
  • 当 DOM 中 Clock 组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”。

我们可以为 class 组件声明一些特殊的方法,当组件挂载或卸载时就会去执行这些方法:

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);  //卸载的时候清楚定时器
  }

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

这些方法叫做“生命周期方法”。

3. 正确地使用 state

构造函数是唯一可以给 this.state 赋值的地方

3.1 不要直接修改state

// Wrong,不会渲染
this.state.comment = 'Hello';
//正确做法
this.setState({comment: 'Hello'});

3.2 State 的更新可能是异步的

//因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
//wrong 不会被渲染
this.setState({
  counter: this.state.counter + this.props.increment,
});
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));

3.3 State 的更新会被合并

当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state

例如,你的 state 包含几个独立的变量

constructor(props) {
    super(props);
    this.state = {
      posts: [],
      comments: []
    };
  }

然后你可以分别调用 setState() 来单独地更新它们:

 componentDidMount() {
    fetchPosts().then(response => {
      this.setState({
        posts: response.posts
      });
    });

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

4. 各种生命周期:

4.1 原生js 表达div的生命周期

//示例,展示一个div的全部生命周期过程
let app = document.getElementById("app");

//creatediv,只出现在内存中,不在页面中显示
let div = document.createElement("div");
div.style.border = "1px solid red";

let state = 0;

div.innerHTML = `
  <p>${state}</p>
  <button>+1</button>
  <button>die</button>
`;
 
//componentWillMount将要挂载conponent

//mount div,把div渲染到页面中
app.appendChild(div);


div.querySelector("button").onclick = () => {
  state += 1;
  //update div 
  div.querySelector("p").innerText = state;
};

div.querySelectorAll('button')[1].onclick = () => {
  div.querySelector("button").onclick = null  //事件监听都删除
  div.querySelectorAll('button')[1].onclick = null
  div.remove() //页面的所有内容都已删除
  div = null  //destroy div 卸载
}

4.2 React 表达div的生命周期

  • class组件创建的一个div的生命周期的完整过程由以下八个过程:

    1. 将要(挂载)Mount App : 使用componentWillMount函数
    2. 更新App 组件的内容 : render 函数
    3. (挂载)mount App 完毕 :使用componentDidMount函数
    4. 将要(更新)update state  : 使用componentWillUpdate函数
    5. 【注意】这一步不用再设函数了,state的变化会使组件会重新渲染,调用render函数(这时的state是最新值)
    6. 更新(update)组件完毕:使用componentDidUpdate函数
    7. 最后消除清理组建的时候 : 使用 componentWillUnmount函数(这个函数由其父级组件来调用)
    8. 特殊的函数,在哪里调用都可以,接受其它组件的props 的函数componentWillReceiveProps,不属于生命周期的,作用是更新父组件的内容
    
  • 相关

  1. 创建App :使用class组件
  2. 声明state变量 :constructor函数


    生命周期图解
  • 代码示例
    import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";

class Parent extends React.Component {
  constructor() {
    super();
    this.state = {
      hasChild: true
    };
  }
  onClick() {
    this.setState({
      hasChild: false
    });
  }
  callSon() {
    this.setState({
      word: "父级组件说:你还好吗"
    });
  }

  render() {
    return (
      <div>
        父级组件<button onClick={() => this.onClick()}>kill Child</button>
        <button onClick={() => this.callSon()}>call son</button>
        {this.state.hasChild ? <App word={this.state.word} /> : null}
      </div>
    );
  }
}

class App extends React.Component {
  onClick() {
    console.log("用户点击了");
    this.setState({
      n: this.state.n + 1
    });
  }
  constructor() {
    super();
    console.log("创建App");
    this.state = {
      n: 0,
      x: "不展示"   /*shouldComponentUpdate举例*/
    };
  }

  upDateX() {
    this.setState({
      x: this.state.x + "!"
    });
  }

  componentWillMount() {
    console.log("将要mount app");
  }

  //render 将内容填进去,更新内容
  render() {
    //等价于update
    console.log("填充/更新App 的内容");
    return (
      <div className="App">
        {this.state.n}
        <br />
        {this.props.word}
        <br />
        <button onClick={() => this.onClick()}>+1</button>
        <button
          onClick={() => {
            this.upDateX();
          }}
        >
          update x
        </button>
      </div>
    );
  }

  //页面中显示div的内容
  componentDidMount() {
    console.log("mount App 完毕");
  }

  componentWillUpdate() {
    console.log("update app 之前");
  }

  componentDidUpdate() {
    console.log("update app 之后");
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (nextState.n === this.state.n) {   /*shouldComponentUpdate举例*/
      return false;
    } else {
      return true;
    }
  }

  componentWillUnmount() {
    console.log("App快要死了");
  }

  componentWillReceiveProps() {
    console.log("顶层组件将要更新了");
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<Parent />, rootElement);

5. 页面中实现的动作

5.1 使用Ajax发起异步请求

请求数据可以放在任何一个生命周期函数中,原则上越早越好,一般是在componentDidMount中或者事件处理函数onClick中,不推荐写在render 函数中,不能用在constructor函数和componentWillMount中。

  1. 不能用在componentWillMount的原因:
    放到componentWillMount不好. React 下一代调和算法 Fiber 会通过开始或停止渲染的方式优化应用性能,其会影响到 componentWillMount 的触发次数。对于 componentWillMount 这个生命周期函数的调用次数会变得不确定,React 可能会多次频繁调用 componentWillMount。如果我们将 AJAX 请求放到 componentWillMount 函数中,那么显而易见其会被触发多次,自然也就不是好的选择。

  2. 不能用在constructor等其它函数的原因:
    如果我们将 AJAX 请求放置在生命周期的其他函数中,我们并不能保证请求仅在组件挂载完毕后才会要求响应。如果我们的数据请求在组件挂载之前就完成,并且调用了setState()函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在 componentDidMount 函数中进行 AJAX 请求则能有效避免这个问题

5.2 初始化state

state是在初始化之前是undefined,只要是初始化了就不能再初始化,以防覆盖有用信息,初始化state应该放在constructor里面,特殊情况下,在render函数执行时前被调用的生命周期函数中都可以,因为render调用时会使用state数据,所以可以初始化state的函数有constructor 、componentWillMount、render 函数都可以.

5.3 React(setState) 更新数据

声明周期函数还没有Mount之前(constructor和componentWillMount函数中)不能使用 setState,不能在render 函数中也不能使用setState更新数据。componentWillUpadate 、componentDidUpadate、componentWillUnmount中不能使用setState的更新。componentWillReceiveProps和componentDidMount中可以更新数据,在后者用的较多,用的最多的还是onClick事件处理函数中。

5.4 事件监听onClick(React已经做好了)

在这里没有讨论的价值。

5.5 【面试常考点】shouldComponentUpdate阻止更新state

  • shouldComponentUpdate 的作用:
    1. 允许我们手动地判断是否要进行组件更新
    2. 根据组件的应用场景设置函数的合理返回值能够帮我们避免不必要的更新。
  • 详解:
    shouldComponentUpdate(nextProps, nextState);通过使用 shouldComponentUpdate阻止更新state和props后决定是否重新渲染 ,他是一种询问,不是钩子,默认返回true,默认更新,写成return false才会组织更新。一般情况下写在componentWillUpdate后面。在上面代码实例中,有两个state值,一个在页面中显示另一个不显示,显示 的那个若是没有任何改变,页面就不会重新渲染,否则会渲染。

6. React生命周期面试题汇总

1. react 生命周期函数

一、初始化阶段:
getDefaultProps:获取实例的默认属性
getInitialState:获取每个实例的初始化状态
constructor :声明State变量
componentWillMount:组件即将被装载、渲染到页面上
render:组件在这里生成虚拟的DOM节点
componentDidMount:组件真正在被装载之后
二、运行中状态:
componentWillReceiveProps:组件将要接收到属性的时候调用
shouldComponentUpdate:组件接受到新属性或者新状态的时候(可以返回false,接收数据后不更新,阻止
render调用,后面的函数不会被继续执行了)
componentWillUpdate:组件即将更新不能修改属性和状态render:组件重新描绘
componentDidUpdate:组件已经更新三
销毁阶段:
componentWillUnmount:组件即将销毁

2. react 生命周期函数中性能优化(shouldComponentUpdate)

具有性能优化作用的是shouldComponentUpdate ,其作用是
1. 允许我们手动地判断是否要进行组件更新
2. 根据组件的应用场景设置函数的合理返回值能够帮我们避免不必要的更新,因Dom的描绘非常消耗性能

3. 用户点击按钮后react 生命周期函数setState合并状态后几个函数的调用顺序:

shouldComponentUpdate ——> componentWillUpdate ——> render ——> componentDidUpdate

4. 为什么虚拟 dom 会提高性能?

虚拟DOM 是相当于js在真是DOM中添加了缓存,利用DOM diff 算法避免了没有必要的DOM 操作,从而提高性能。

5. react diff 原理(常考,大厂必考)

  • 把树形结构按照层级分解,只比较同级元素。
  • 给列表结构的每个单元添加唯一的 key 属性,方便比较。
  • React 只会匹配相同 class 的 component(这里面的 class 指的是组件的名字)
  • 合并操作,调用 component 的 setState 方法的时候, React 将其标记为 dirty.到每一个事件循环结束, React 检查所有标记 dirty 的 component 重新绘制.
  • 选择性子树渲染。开发人员可以重写 shouldComponentUpdate 提高 diff 的性能。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容