Web前端之路

理解React:Fiber架构和新旧生命周期

2021-01-25  本文已影响0人  nojsja

➣ React Fiber原理


React架构

React15遗留问题

StackReconciler

React16问题解决

FiberReconciler
<FiberNode> : {
    stateNode,    // 节点实例
    child,        // 子节点
    sibling,      // 兄弟节点
    return,       // 父节点
}
FiberTree

➣ React新旧生命周期


React16.3之前的生命周期

image
  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 });
    }
  }
}
  1. 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;
  }
}
  1. 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'});;
  }
  1. 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);
  }
  1. componentWillUnmount()
    此生命周期发生在组件卸载之前,组件生命周期中只会触发一次。开发者可以在此函数中执行一些数据清理重置、取消页面组件的事件订阅等。

React16.3之后的生命周期

image

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)
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 };
  }
}
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输入框组件的值还是上次遗留的输入。

function SimpleInput(props) {
  return <input onChange={props.onChange} value={props.attr} />;
}
/* 
  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}
/>


  1. componentDidMount()
    ...

  2. shouldComponentUpdate(nextProps, nexState)
    ...

  3. 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>
    );
  }
}
  1. componenetDidUpdate(prevProps, prevState, shot)
    此生命周期新增特性:getSnapshotBeforeUpdate的返回值作为此函数执行时传入的第三个参数。

  2. componenetWillUnmount
    ...

上一篇下一篇

猜你喜欢

热点阅读