➣ React Fiber原理


React架构

  • 1)Virtual DOM 层,形容页面长什么样
  • 2)Reconciler 层,负责调用组件生命周期办法,进行Diff运算等
  • 3)Renderer 层,依据不同的平台,渲染出相应的页面,如 ReactDOM 和 ReactNative

React15遗留问题

  • 1)浏览器的整体渲染是多线程的,包含GUI渲染线程、JS引擎线程、事件触发线程、定时触发器线程和异步http申请线程。页面绘制和JS运算是互斥的线程,两者不能同时进行。
  • 2)React15应用JS的函数调用栈(Stack Reconciler)递归渲染界面,因而在解决DOM元素过多的简单页面的频繁更新时,大量同步进行的工作(树diff和页面render)会导致界面更新阻塞、事件响应提早、动画卡顿等,因而React团队在16版本重写了React Reconciler架构。

React16问题解决

  • 1)Fiber Reconciler架构能够容许同步阻塞的工作拆分成多个小工作,每个工作占用一小段时间片,工作执行实现后判断有无闲暇工夫,有则继续执行下一个工作,否则将控制权交由浏览器以让浏览器去解决更高优先级的工作,等下次拿到工夫片后,其它子工作继续执行。整个流程相似CPU调度逻辑,底层是应用了浏览器APIrequestIdleCallback
  • 2)为了实现整个Diff和Render的流程可中断和复原,单纯的VirtualDom Tree不再满足需要,React16引入了采纳单链表构造的Fiber树,如下图所示。
  • 3)FiberReconciler架构将更新流程划分成了两个阶段:1.diff(由多个diff工作组成,工作工夫片耗费完后被可被中断,中断后由requestIdleCallback再次唤醒) => 2.commit(diff结束后拿到fiber tree更新后果触发DOM渲染,不可被中断)。右边灰色局部的树即为一颗fiber树,左边的workInProgress为两头态,它是在diff过程中自顶向下构建的树形构造,可用于断点复原,所有工作单元都更新实现之后,生成的workInProgress树会成为新的fiber tree。
  • 4)fiber tree中每个节点即一个工作单元,跟之前的VirtualDom树相似,示意一个虚构DOM节点。workInProgress tree的每个fiber node都保留着diff过程中产生的effect list,它用来寄存diff后果,并且底层的树节点会顺次向下层merge effect list,以收集所有diff后果。留神的是如果某些节点并未更新,workInProgress tree会间接复用原fiber tree的节点(链表操作),而有数据更新的节点会被打上tag标签。
<FiberNode> : {    stateNode,    // 节点实例    child,        // 子节点    sibling,      // 兄弟节点    return,       // 父节点}

➣ React新旧生命周期


React16.3之前的生命周期

1.componentWillMount()
此生命周期函数会在在组件挂载之前被调用,整个生命周期中只被触发一次。开发者通常用来进行一些数据的预申请操作,以缩小申请发动工夫,倡议的代替计划是思考放入constructor构造函数中,或者componentDidMount后;另一种状况是在在应用了内部状态治理库时,如Mobx,能够用于重置Mobx Store中的的已保留数据,代替计划是应用生命周期componentWilUnmount在组件卸载时主动执行数据清理。

2.componentDidMount()
此生命周期函数在组件被挂载之后被调用,整个生命周期中只触发一次。开发者同样能够用来进行一些数据申请的操作;除此之外也可用于增加事件订阅(须要在componentWillUnmount中勾销事件订阅);因为函数触发时dom元素曾经渲染结束,第三种应用状况是解决一些界面更新的副作用,比方应用默认数据来初始化一个echarts组件,而后在componentDidUpdate后进行echarts组件的数据更新。

3.componentWillReceiveProps(nextProps, nexState)
此生命周期产生在组件挂载之后的组件更新阶段。最常见于在一个依赖于prop属性进行组件外部state更新的非齐全受控组件中,非齐全受控组件即组件外部保护state更新,同时又在某个非凡条件下会采纳内部传入的props来更新外部state,留神不要间接将props齐全复制到state,否则应该应用齐全受控组件Function Component,一个例子如下:

class EmailInput extends Component {  state = { email: this.props.email };  render() {    return <input onChange={this.handleChange} value={this.state.email} />;  }  handleChange = e => his.setState({ email: e.target.value });  componentWillReceiveProps(nextProps) {    if (nextProps.userID !== this.props.userID) {      this.setState({ email: nextProps.email });    }  }}

4.shouldComponentUpdate(nextProps)
此生命周期产生在组件挂载之后的组件更新阶段。
值得注意的是子组件更新不肯定是因为props或state扭转引起的,也可能是父组件的其它局部更改导致父组件重渲染而使得以后子组件在props/state未扭转的状况下从新渲染一次。
函数被调用时会被传入行将更新的nextPropsnextState对象,开发者能够通过比照前后两个props对象上与界面渲染相干的属性是否扭转,再决定是否容许这次更新(return true示意容许执行更新,否则疏忽更新,默认为true)。常搭配对象深比拟函数用于缩小界面无用渲染次数,优化性能。在一些只须要简略浅比拟props变动的场景下,并且雷同的state和props会渲染出雷同的内容时,倡议应用React.PureComponnet代替,在props更新时React会主动帮你进行一次浅比拟,以缩小不必要渲染。

class EmailInput extends Component {  state = { email: this.props.email };  render() {    return <input onChange={this.handleChange} value={this.state.email} />;  }  handleChange = e => his.setState({ email: e.target.value });  shouldComponentUpdate(nextProps, nextState) {    if (      nextProps.userID === this.props.userID &&      nextState.email == this.state.email    ) return false;  }}

5.componenetWillUpdate(newProps, newState)
此生命周期产生在组件挂载之后的更新阶段。当组件收到新的props或state,并且shouldComponentUpdate返回容许更新时,会在渲染之前调此办法,不能够在此生命周期执行setState。在此生命周期中开发者能够在界面理论渲染更新之前拿到最新的nextPropsnextState,从而执行一些副作用:比方触发一个事件、依据最新的props缓存一些计算数据到组件内、平滑界面元素动画等:

 // 须要搭配css属性transition应用 componentWillUpdate : function(newProps,newState){    if(!newState.show)      $(ReactDOM.findDOMNode(this.refs.elem)).css({'opacity':'1'});    else      $(ReactDOM.findDOMNode(this.refs.elem)).css({'opacity':'0'});;  },  componentDidUpdate : function(oldProps,oldState){    if(this.state.show)      $(ReactDOM.findDOMNode(this.refs.elem)).css({'opacity':'1'});    else      $(ReactDOM.findDOMNode(this.refs.elem)).css({'opacity':'0'});;  }

6.componenetDidUpdate(prevProps, prevState)
此生命周期产生在组件挂载之后的更新阶段,组件首次挂载不会触发。当组件的props和state扭转引起界面渲染更新后,此函数会被调用,不能够在此生命周期执行setState。咱们应用它用来执行一些副作用:比方条件式触发必要的网络申请来更新本地数据、应用render后的最新数据来调用一些内部库的执行(例子:定时器申请接口数据动静绘制echarts折线图):

  ...  componentDidMount() {    this.echartsElement = echarts.init(this.refs.echart);    this.echartsElement.setOption(this.props.defaultData);    ...  }  componentDidUpdate() {    const { treeData } = this.props;    const optionData = this.echartsElement.getOption();    optionData.series[0].data = [treeData];    this.echartsElement.setOption(optionData, true);  }

7.componentWillUnmount()
此生命周期产生在组件卸载之前,组件生命周期中只会触发一次。开发者能够在此函数中执行一些数据清理重置、勾销页面组件的事件订阅等。

React16.3之后的生命周期

React16.3之后React的Reconciler架构被重写(Reconciler用于解决生命周期钩子函数和DOM DIFF),之前版本采纳函数调用栈递归同步渲染机制即Stack Reconciler,dom的diff阶段不能被打断,所以不利于动画执行和事件响应。React团队应用Fiber Reconciler架构之后,diff阶段依据虚构DOM节点拆分成蕴含多个工作工作单元(FiberNode)的Fiber树(以链表实现),实现了Fiber工作单元之间的任意切换和工作之间的打断及复原等等。Fiber架构下的异步渲染导致了componentWillMountcomponentWillReceivePropscomponentWillUpdate三个生命周期在理论渲染之前可能会被调用屡次,产生不可意料的调用后果,因而这三个不平安生命周期函数不倡议被应用。取而代之的是应用全新的两个生命周期函数:getDerivedStateFromPropsgetSnapshotBeforeUpdate

1.getDerivedStateFromProps(nextProps, currentState)

  • 1)定义

此生命周期产生在组件初始化挂载和组件更新阶段,开发者能够用它来代替之前的componentWillReceiveProps生命周期,可用于依据props变动来动静设置组件外部state。
函数为static动态函数,因而咱们无奈应用this间接拜访组件实例,也无奈应用this.setState间接对state进行更改,以此能够看出React团队想通过React框架的API式束缚来尽量减少开发者的API滥用。函数调用时会被传入行将更新的props和以后组件的state数据作为参数,咱们能够通过比照解决props而后返回一个对象来触发的组件state更新,如果返回null则不更新任何内容。

  • 2)滥用场景一:间接复制props到state下面

这会导致父层级从新渲染时,SimpleInput组件的state都会被重置为父组件从新传入的props,不论props是否产生了扭转。如果你说应用shouldComponentUpdate搭配着防止这种状况能够吗?代码层面上能够,不过可能导致前期shouldComponentUpdate函数的数据起源凌乱,任何一个prop的扭转都会导致从新渲染和不正确的状态重置,保护一个牢靠的shouldComponentUpdate会更难。

class SimpleInput extends Component {  state = { attr: ''  };  render() {    return <input onChange={(e) => this.setState({ attr: e.target.value })} value={this.state.attr} />;  }  static getDerivedStateFromProps(nextProps, currentState) {    // 这会笼罩所有组件内的state更新!    return { attr: nextProps.attr };  }}
  • 3)应用场景: 在props变动后选择性批改state
class SimpleInput extends Component {  state = { attr: ''  };  render() {    return <input onChange={(e) => this.setState({ attr: e.target.value })} value={this.state.attr} />;  }  static getDerivedStateFromProps(nextProps, currentState) {    if (nextProps.attr !== currentState.attr) return { attr: nextProps.attr };    return null;  }}

可能导致的bug:在须要重置SimpleInput组件的状况下,因为props.attr未扭转,导致组件无奈正确重置状态,体现就是input输入框组件的值还是上次遗留的输出。

  • 4)优化的应用场景一:应用齐全可控的组件

齐全可控的组件即没有外部状态的性能组件,其状态的扭转齐全受父级props管制,这种形式须要将本来位于组件内的state和扭转state的逻辑办法抽离到父级。实用于一些简略的场景,不过如果父级存在太多的子级状态治理逻辑也会使逻辑冗余复杂化。

function SimpleInput(props) {  return <input onChange={props.onChange} value={props.attr} />;}
  • 5)优化的应用场景二:应用有key值的齐全可控的组件

如果咱们想让组件领有本人的状态治理逻辑,然而在适当的条件下咱们又能够管制组件以新的默认值从新初始化,这里有几种办法参考:

/*   1. 设置一个惟一值传入作为组件从新初始化的标记     通过比照属性手动让组件从新初始化*/class SimpleInput extends Component {  state = { attr: this.props.attr, id=""  }; // 初始化默认值  render() {    return <input onChange={(e) => this.setState({ attr: e.target.value })} value={this.state.attr} />;  }  static getDerivedStateFromProps(nextProps, currentState) {    if (nextProps.id !== currentState.id)      return { attr: nextProps.attr, id: nextProps.id };    return null;  }}/*  2. 设置一个惟一值作为组件的key值     key值扭转后组件会以默认值从新初始化  */class SimpleInput extends Component {  state = { attr: this.props.attr  }; // 初始化默认值  render() {    return <input onChange={(e) => this.setState({ attr: e.target.value })} value={this.state.attr} />;  }}<SimpleInput  attr={this.props.attr}  key={this.props.id}/>/*  3. 提供一个内部调用函数以供父级间接调用以重置组件状态     父级通过refs来拜访组件实例,拿到组件的外部办法进行调用  */class SimpleInput extends Component {  state = { attr: this.props.attr  }; // 初始化默认值  resetState = (value) => {    this.setState({ attr: value });  }  render() {    return <input onChange={(e) => this.setState({ attr: e.target.value })} value={this.state.attr} />;  }}<SimpleInput  attr={this.props.attr}  ref={this.simpleInput}/>

2.componentDidMount()
...

3.shouldComponentUpdate(nextProps, nexState)
...

4.getSnapshotBeforeUpdate(prevProps, prevState)
此生命周期产生在组件初始化挂载和组件更新阶段,界面理论render之前。开发者能够拿到组件更新前的prevPropsprevState,同时也能获取到dom渲染之前的状态(比方元素宽高、滚动条长度和地位等等)。此函数的返回值会被作为componentWillUpdate周期函数的第三个参数传入,通过搭配componentDidUpdate能够齐全代替之前componentWillUpdate局部的逻辑,见以下示例。

class ScrollingList extends Component {  constructor(props) {    super(props);    this.listRef = React.createRef();  }  getSnapshotBeforeUpdate(prevProps, prevState) {    // 判断是否在list中增加新的items     // 捕捉滚动地位以便咱们稍后调整滚动地位。    if (prevProps.list.length < this.props.list.length) {      const list = this.listRef.current;      return list.scrollHeight - list.scrollTop;    }    return null;  }  componentDidUpdate(prevProps, prevState, snapshot) {    // 调整滚动地位使得这些新items不会将旧的items推出视图    // snapshot是getSnapshotBeforeUpdate的返回值)    if (snapshot !== null) {      const list = this.listRef.current;      list.scrollTop = list.scrollHeight - snapshot;    }  }  render() {    return (      <div ref={this.listRef}>{/* ...list items... */}</div>    );  }}

5.componenetDidUpdate(prevProps, prevState, shot)
此生命周期新增个性:getSnapshotBeforeUpdate的返回值作为此函数执行时传入的第三个参数。

6.componenetWillUnmount
...