1

1、老版本的生命周期以及按顺序执行的流程
Initialization (初始化阶段)
这些方法会在组件初始化的时候被调用,只跟实例的创建有关。

如果用 createReactClass 进行创建,则还有 getInitialState 这些生命周期函数,但很少使用,我们这里不提及。

static defaultProps{} (getDefaultProps())
定义默认 props ,会和父组件传下来的 props 进行合并,且以父组件中的 props 优先级更高,相当于 {...defaultProps, props} 。

static propTypes{} (getInitialState())
定义 props 的数据类型,可以帮助我们确定其有效性,减少许多开发中不必要的错误。

constructor()
在加载阶段前调用一次,进行 state 的初始化。

constructor(props){

super(props)

}

super(props) 用来调用父类的构建方法。

Mounting(加载阶段)
componentWillMount()
新版中为 UNSAFE_componentWillMount() 。

只在组件加载时调用,整个生命周期只调用一次,以后组件更新也不会调用,此时可以修改 state。

render()
react 中最重要的生命周期函数,创建虚拟 dom,并进行 diff 算法,更新 dom 树也在此进行。所以不应该在此改变组件的状态或者与浏览器进行交互。

注意:这个函数不能缺少,如果不创建虚拟 dom,可以 return null 。

componentDidMount()
组件加载完成后立即调用,整个生命周期只调用一次,可以获取到更新后的 DOM,在此处可以进行网络请求等。

Updating(更新阶段)
componentWillReceiveProps()
新版中为 UNSAFE_componentWillReceiveProps() 。

在组件加载后,如果有新的 props 传递过来,会先调用这个函数,可以在这里调用 setState() 修改 state 。

componentWillReceiveProps(nextProps)

shouldComponentUpdate()
react 中一个重要的性能优化点,组件接收到新的 props 或者 state ,返回 true 表示需要更新 dom,返回 false 阻止更新。

shouldComponentUpdate(nextProps, nextState)

componentWillUpdate()
新版中为 UNSAFE_componentWillUpdate() 。

组件加载时不调用,只有在组件需要更新(即 shouldComponentUpdate 返回 true )时调用。

componentWillUpdate(nextProps, nextState)

注意:不能在这个方法中调用 setState() 修改 state 。

否则会重新触发updating

render()
componentDidUpdate()
在组件更新完成后立即被调用,可以进行网络请求等。

componentDidUpdate(prevProps, prevState)

Unmouting(销毁阶段)
componentWillUnmount()
在组件被卸载和销毁之前调用,可以在这里处理任何必要的清理工作,比如解除定时器,取消已经发起的网络请求,清理在 componentDidMount 函数中创建的 DOM 元素。

componentWillUnmount()

Catching(捕获阶段)
componentDidCatch()
错误边界捕获,在v16.0刚推出的时候新增加的一个生命周期函数,用于捕获在 子组件树 中任意地方发生的JavaScript 错误,一个错误边界不能捕获它自己内部的错误。

componentDidCatch(error, info)

2、新版本中新增的生命周期以及对应的使用方法
首先,新版本中有三个生命周期被deprecate(去掉)了,他们分别是:

componentWillReceiveProps
componentWillMount
componentWillUpdate
getDerivedStateFromProps()

这个getDerivedStateFromProps是一个静态函数,所以函数体内不能访问this,简单说,就是应该一个纯函数,纯函数的话,输出完全由输入决定。

static getDerivedStateFromProps(nextProps, prevState) {

      //根据nextProps和prevState计算出预期的状态改变,返回结果会被送给setState

}

每当父组件引发当前组件的渲染过程时,getDerivedStateFromProps会被调用,这样我们有一个机会可以根据新的props和之前的state来调整新的state,如果放在三个被deprecate生命周期函数中实现比较纯,没有副作用的话,基本上搬到getDerivedStateFromProps里就行了;

注意:getDerivedStateFromProps无论是Mounting还是Updating,也无论是因为什么引起的Updating,全部都会被调用。

getSnapshotBeforeUpdate()

这函数会在render之后执行,而执行之时DOM元素还没有被更新,给了一个机会去获取DOM信息,计算得到一个snapshot,这个snapshot会作为componentDidUpdate的第三个参数传入。

getSnapshotBeforeUpdate(prevProps, prevState) {

     console.log('#enter getSnapshotBeforeUpdate');

     return 'foo';

 } 

componentDidUpdate(prevProps, prevState, snapshot) {

    console.log('#enter componentDidUpdate snapshot = ', snapshot);

}

getSnapshotBeforeUpdate把snapshot返回,然后DOM改变,然后snapshot传递给componentDidUpdate。

总结: 用一个静态函数getDerivedStateFromProps来取代被清除的几个生命周期函数,就是强制开发者在render之前只做无副作用的操作,而且能做的操作局限在根据props和state决定新的state,而已。
这是进一步施加约束,防止开发者乱来

Q&A:
1、Willmount or didmount?

之前想过为什么不在componentWillMount里写AJAX获取数据的功能,之前的观点是,componentWillMount在render之前执行,早一点执行早得到结果。

但是,要知道,在componentWillMount里发起AJAX,不管多快得到结果也赶不上首次render,

而且componentWillMount在服务器端渲染也会被调用到(当然,也许这是预期的结果),

这样的IO操作放在componentDidMount里更合适。在Fiber启用async render之后,更没有理由在componentWillMount里做AJAX,

因为componentWillMount可能会被调用多次,谁也不会希望无谓地多次调用AJAX吧。

其实这个问题react的开发者早就有想过,所以才运营而生了后面的新的生命周期,既然不想放在这些不好的生命周期里面,那都不如直接让他们干脆没办法做。

2、什么时候用forceUpdate?

forceUpdate就是重新render。有些变量不在state上,但是你又想达到这个变量更新的时候,刷新render;

或者state里的某个变量层次太深,更新的时候没有自动触发render。

这些时候都可以手动调用forceUpdate自动触发render。所以建议使用immutable来操作state,redux等flux架构来管理state。

3、父组件的状态变化怎么影响子组件的生命周期?

shouldComponentUpdate函数接受nextProps和nextState作为参数。

你可以根据props里面设的值或者state里面设的值来判断需不需要更新。例如判断this.state.count是否等于nextState.count。

4、为什么在react componentWillUpdate中调用setstate会造成循环调用?

如果在shouldComponentUpdate和componentWillUpdate中调用了setState,

此时this._pendingStateQueue != null,则performUpdateIfNecessary方法就会调用updateComponent方法进行组件更新。

但是updateComponent方法又会调用shouldComponentUpdate和componentWillUpdate,

因此造成循环调用,使得浏览器内存占满后崩溃。

5、函数式组件有没有生命周期?为什么?

因为函数式组件没有继承React.Component,由于生命周期函数是React.Component类的方法实现的

所以没继承这个类,自然就没法使用生命周期函数了

6、PureComponent和Component有什么区别?

   PureComponent优势:

      不需要开发者自己实现shouldComponentUpdate,就可以进行简单的判断来提升性能。

   PureComponent缺点:

      React.PureComponent 通过props和state的浅对比来实现 shouldComponentUpate()。

在PureComponent中,如果包含比较复杂的数据结构,可能会因深层的数据不一致而产生错误的否定判断,导致界面得不到更新。

PureComponent 在使用 shouldComponentUpdate的处理是由 PureComponent 自身来处理,而不是由用户来控制,

所以我们在 PureComponent 中如果复写此生命周期回调函数,React 会提示我们错误的。告诉我们不允许重写该方法。

7、为什么PureComponent比较复杂的数据结构,可能会因深层的数据不一致而产生错误的否定判断?

JavaScript 中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,

改变新的对象将影响到原始对象。

  foo={a: 1};

  bar=foo;

  bar.a=2

你会发现此时 foo.a 也被改成了 2。

为了解决这个问题,一般的做法是使用 shallowCopy(浅拷贝)或 deepCopy(深拷贝)来避免被修改,但这样做造成了 CPU 和内存的浪费。

原因在于js使用的是引用赋值,新的对象简单引用了原始对象,改变新对象虽然影响了原始对象,

但对象的地址还是一样,使用===比较的方式相等。

而在PureComponent中,会被判定prop相等而不触发render()。

避免此类问题最简单的方式是,避免使用值可能会突变的属性或状态,而是使用副本来返回新的变量

8、react中的setstate是同步的还是异步的?Rax呢?

由React控制的事件处理程序,以及生命周期函数调用setState不会同步更新state 。

React控制之外的事件中调用setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等。

大部分开发中用到的都是React封装的事件,比如onChange、onClick、onTouchMove等,这些事件处理程序中的setState都是异步处理的。

Rax的setState是同步的

9、React是怎样控制异步和同步的?

在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates

判断是直接更新 this.state 还是放到队列中延时更新,

而 isBatchingUpdates 默认是 false,表示 setState 会同步更新 this.state;

但是,有一个函数 batchedUpdates,该函数会把 isBatchingUpdates 修改为 true,

而当 React 在调用事件处理函数之前就会先调用这个 batchedUpdates将isBatchingUpdates修改为true,

这样由 React 控制的事件处理过程 setState 不会同步更新 this.state。

10、setState的一些用法?

(1)多个setState调用会合并处理:

在某些处理程序中调用了两次setState,但是render只执行了一次。

因为React会将多个this.setState产生的修改放在一个队列里进行批延时处理。

(2)参数为函数的setState用法

假如setState更新state后我希望做一些事情,

而setState可能是异步的,那我怎么知道它什么时候执行完成。

所以setState提供了函数式用法

接收两个函数参数,第一个函数调用更新state,第二个函数是更新完之后的回调。

case:

handleClick() {
this.setState({

count: this.state.count + 1

})
}
以上操作存在潜在的陷阱,不应该依靠它们的值来计算下一个状态。

handleClick() {
this.setState({

count: this.state.count + 1

})
this.setState({

count: this.state.count + 1

})
this.setState({

count: this.state.count + 1

})
}
最终的结果只加了1
因为调用this.setState时,并没有立即更改this.state,
所以this.setState只是在反复设置同一个值而已,上面的代码等同于这样

handleClick() {
const count = this.state.count

this.setState({

count: count + 1

})
this.setState({

count: count + 1

})
this.setState({

count: count + 1

})
}
count相当于一个快照,所以不管重复多少次,结果都是加1。

第一个函数接收先前的状态作为第一个参数,将此次更新被应用时的props做为第二个参数。

increment(state, props) {
return {

count: state.count + 1

}
}

handleClick() {
this.setState(this.increment)
this.setState(this.increment)
this.setState(this.increment)
}
对于多次调用函数式setState的情况,React会保证调用每次increment时,state都已经合并了之前的状态修改结果。

也就是说,第一次调用this.setState(increment),

传给increment的state参数的count是10,第二调用是11,第三次调用是12,最终handleClick执行完成后的结果就是this.state.count变成了13。

值得注意的是:在increment函数被调用时,this.state并没有被改变,

依然要等到render函数被重新执行时(或者shouldComponentUpdate函数返回false之后)才被改变,

因为render只执行一次。


清河一只猹
4 声望0 粉丝

前端工程师