react生命周期流程和事件处理
组件从创建到被销毁的过程称为组件的生命周期,React的生命周期可分为三个阶段:挂载阶段、更新阶段、卸载阶段。不同的生命周期阶段对应了不同的生命周期函数。
挂载阶段
这个阶段组件被创建,执行初始化,并挂载到DOM中,流程依次是:(1)constructor(2)componentWillMount(3)render(4)componentDidMount
- constructor
这是ES6 class中的构造方法,组件被创建就会调用组件的构造方法,这个方法会传入一个props参数,此参数是父组件传入的参数对象,如果父组件没有传入对应的属性而组件自身定义了默认属性,那么这个props指向的就是组件的默认属性,必须要在这个方法中首先调用super(props)才能保证props传入组件使用。
在这里,类的数据类型就是函数,类本身就指向构造函数,假设有个类为A,所以通过typeof检测A的数据类型是function,而这条语句A === A.prototype.constructor
结果也是true的。当有个class类为B,继承自A,此时在子类B中调用super()相当于A.prototype.constructor.call(this)
,super虽然代表了父类A的构造函数,但是返回的是子类B的实例,即super内部的this指的是B的实例;这只是super做为函数调用的时候,当它做为对象使用的时候,相当于指向父类的原型A.prototype
。
- componentWillMount
组件挂载前调用,且只会被调用一次,此方法中使用this.setState()
不会引起组件的重新渲染。
- render
这是定义组件必须要使用的方法,可以根据state和props返回一个React元素,注意,它只是一个纯函数,不能在内部执行有副作用的操作,也不负责DOM的渲染。
- componentDidMount
组件挂载完之后调用,且只会被调用一次,这时候DOM已经在页面挂载了,因此依赖DOM的操作可以在这里完成,还可以用于向服务端请求数据,在这调用this.setState()
会引起组件的重新渲染。
更新阶段
组件挂载到DOM之后,组件的props和state会引起组件的更新,更新阶段的生命周期有:(1)componentWillReceiveProps(2)shouldComponentUpdate(3)componentWillUpdate(4)render(5)componentDidUpdate
- componentWillReceiveProps
这个方法只在props引起的组件更新过程中才会起作用,而单纯的state引起的组件更新是不会触发的,方法的参数nextProps是父组件传递给当前组件新的Props,也就是说,当父组件render方法被调用,引起组件的更新,但此时并不能保证传递给子组件的props发生改变,也有可能和之前的props相等,因此,在此方法中通常需要对比nextProps和this.props来决定是否执行后续操作。
- shouldComponentUpdate
这个方法决定是否继续执行更新过程,默认返回true,组件会继续更新,当返回false时,后续的(3)(4)(5)将不会执行,一般通过nextProps、nextState和当前的props、state对比来减少组件不必要的渲染,从而优化性能。
- componentWillUpdate
组件更新前执行的操作,一般很少用到,由于方法冗余,在后续版本逐渐移除,并且推出了函数组件操作状态的hook。注意(2)(3)方法中不能使用setState,否则会引起循环调用问题,render永远无法被调用,组件无法正常渲染。
- componentDidUpdate
组件更新后调用,可以作为操作更新后的DOM的地方,参数prevProps、prevState代表更新前的props和state。
卸载阶段
组件卸载过程只有一个生命周期函数:componentWillUnmount,在个方法在组件卸载前调用,通常执行一些清理操作,比如清除定时器,清除手动创建爱你的DOM元素等。
在React中,事件的命名采用驼峰命名法,事件响应函数必须以对象的形式赋值给事件属性,其实,React事件和DOM事件在使用上几乎没啥差别,但需要注意以下两点:
- DOM事件中可以通过回调函数返回false来阻止事件的默认行为,而在React中,必须要显式的调用事件对象的preventDefault方法来阻止事件的默认行为。
- 如果在某些场景下必须要用到DOM提供的原生事件,可以通过React事件对象的nativeEvent属性获取。
下面我们来看下在React中定义时间处理函数的几种方式:
1.使用箭头函数
1 | class Mycomponent extends React.Component{ |
由上面的案例可以看出,我们是可以直接在事件处理的大括号内直接定义函数语句,但这样最大的问题是,当代码逻辑比较复杂的时候,一方面是代码区块看起来比较混乱臃肿;另一方面,在每次render调用的时候,都会重新创建一个新的事件处理函数,给程序带来额外的开销,因为任何一个状态的变更都会引起组件的重新渲染。不过,大多数情况下是不必考虑这点性能问题的。
2.使用组件方法绑定this
1 | class Mycomponent extends React.Component{ |
这样的写法写起来麻烦,而且存在和方式一同样的问题。
3.属性初始化语法
1 | import React, {useState} from 'react'; |
此方法结合ES7中的属性初始化语法和React Hook用法,不用手动绑定this,也没有重复渲染的问题,代码简洁可观,是现阶段(2020-6)广泛采用的一种方式。