深入理解 react 生命周期

react.png

挂载阶段 (使用 class 的方式)

constructor

  • 组件挂载之前,会调用它的构造函数,通常在这个阶段会处理两种情况:
    1. 给 this.state 赋值对象初始化内容的 state
    2. 事件处理函数 绑定实例
  constructor(props) {
    super(props)
    this.state = {count : 0}
    this.handleClick = this.handleClick.bind(this)
  }

static getDerivedStateFromProps(props, state)

  • 会在调用 render 方法之前调用,并在初始化挂载及后续更新时会被调用,会返回一个对象来更新 state, 如果返回 null 则不更新任何内容。

  • getDerivedStateFromProps 存在只有一个目的:让组件在 props 变化时更新 sate

  // After
  class ExampleComponent extends React.Component {
    // 在构造函数中初始化 state,
    // 或者使用属性初始化器。
    state = {
      isScrollingDown: false,
      lastRow: null,
    };

    static getDerivedStateFromProps(props, state) {
      if (props.currentRow !== state.lastRow) {
        return {
          isScrollingDown: props.currentRow > state.lastRow,
          lastRow: props.currentRow,
        };
      }

      // 返回 null 表示无需更新 state。
      return null;
    }
  }
  • 为什么不直接将上一个 props 作为参数传递给 getDerivedStateFromProps ?

    • prevProps 参数在第一个调用 getDerivedStateFromProps(实例化之后)时为 null,需要在每次访问 prevProps 时添加 if-not-null 检测
    • 在未来的版本中,不传递上一个 props 给这个方法是为了释放内存。(如果 React 无需传递上一个 props 给生命周期,那么它就无需保存上一个 props 对象在内存中)
      简单理解为,如果在开发中不需要使用 props 传递参数并赋值给 state,那么优化考虑 react 并没有为我们在内部实现,而是开放一个选项,让我们则需而选
  • 它能实现什么作用 ?

    • state 的值在任何时候都取于 props。
    • 当然我们也可以使用简单的方式,在使用时,在子组件中直接用 props.xxx
  • 我想到的使用场景,经常在一些业务中会使用的,如果需要在 子组件中,第一次渲染的值,来自于父组件的props,而后面更新无需用到,可以考虑用这种方式。

  // 父组件
class App extends Component {
  state = {
    value: '默认值'
  }
  render() {
    return (
      <div>
        <Input1 value={this.state.value}/>
      </div>
    )
  }
}

// 子组件
class Input1 extends Component {
  constructor(props) {
    super(props)
    this.state = {
      value: '',
      refresh: false
    }
  }
  static getDerivedStateFromProps(props, state) {
    if (state.value === '' && !state.refresh) {
      return {
        value: props.value,
        refresh: true
      }
    }
    return null
  }
  onChange = (value) => {
    this.setState({
      value: value.target.value
    })
  }
  render() {
    return (
      <input value={this.state.value} onChange={this.onChange}></input>
    )
  }
}
  • 弊端 (派生状态会导致代码冗余),并使组件难以维护,替代方案: -> 来自 react 官网
    • 如果你需要执行副作用(例如,数据提取或动画)以响应 props 中的更改,请改用 componentDitUpdate
    • 如果只想在 prop 更改时重新计算某些数据,请使用 memoization helper 代替
    • 如果你想在 prop 更改时重置某些state,考虑使用完全受控或使用 key 使组件完全不受控替代。
    • 具体可参考 https://zh-hans.reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html

render()

  • render 是 class 组件中唯一必须实现的方法
  • 当 render 被调用时,会检查 props 和 state 的变化并返回一下类型之一:
    • React 元素,通常通过 jsx 创建。
    • 数组或 fragments。
    • Portals。 可以渲染子节点到不同的 DOM 子树中。
    • 字符串或数值类型。在 DOM 中会被渲染为文本节点
    • 布尔类型或者 null,什么都不渲染。

注意 如果 shouldComponentUpdate 返回 false,则不会调用 render

  • componentDidMount

    • 组件挂载后(插入 DOM树中)立即调用。依赖于 DOM 节点初始化放在这里,如果要网络请求数据,也放在这里。

    • 可以在这里方法里,添加订阅,但不要忘记在 componentWillUnmount 里取消订阅

    注意 可以在 componentDidMount 里直接使用 setState ,但会额外渲染,此渲染会发生在浏览器更新屏幕之前。因此保证了即使在 render() 渲染两次的情况下,用户不会看到中间状态。 慎用改方式,因为它会导致性能问题。

更新阶段

static getDerivedStateFromProps()

  • 同 挂载阶段一样

shouldComponentUpdate()

  • shouldComponentUpdate(nextProps, nextState) 优化组件性能

  • 根据 shouldComponentUpdate 的返回值,判断 react 组件更新是否受当前 state 或 props 的更改影响。默认是 state 每次改变都会让组件重新渲染。

  • 当 props 或 state 发生变化时,shouldComponentUpdate 会在渲染之前被调用,默认值返回 true,首次渲染或使用 forceUpdate 时不会调用。

  • 如果是浅比较,应该使用 PureComponent ,而不是手动编写 shouldComponentUpdate,因为可能会产生 bug.

  • shouldComponentUpdate 默认返回是 true,如果返回 false 告知 react 可以跳过更新,注意:返回false 并不会阻止子组件在 state 更改时重新渲染。(也就是说,父组件使用 shouldComponentUpdate,不会影响子组件 state 更新时,重新渲染子组件)

  • 如果 返回 false,则不会调用 render 和 componentDidUdpate()。

render()

  • 同挂载时

getSnapshotBeforeUpdate()

  • getSnapshotBeforeUpdate(prevProps, prevState)
  • 最近一次渲染输出(提交到DOM节点)之前调用,它能在组件发生更改之前从 DOM 中获取一些信息(例如:滚动位置)。此生命周期返回的任何值将作为参数传递给 componentDidUpdate
  • 它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等。
  • 应返回 snapshot 的值(或 null)。

componentDidUpdate()

componentDidUpdate(prevProps, prevState, snapshot)

  • componentDidUpdate 会在更新后被立即调用,首次渲染不会执行此方法。

  • 当组件更新后,可以在此生命周期中对 DOM 进行操作,如果对前后更新的 props 进行了比较,也可以选择进行网络请求。

  • 注意 componentDidUpdate() 中直接调用 setState(), ·它必须被包裹在一个条件语句中· 否则可能会导致死循环,还会额外导致组件重新渲染,会影响组件性能。

注意 shouldComponentUpdate() 返回值是 false,则不会调用componentDidUpdate()

卸载阶段

componentWillUnmount()

  • 会在组件卸载及销毁之前直接调用,一般此处执行必要的清理操作,例如 清除 timer,清除订阅等。
  • componentWillUnmount 中不应该调用 setState(), 因为该组件不会重新渲染,组件实例卸载后,将永远不会再挂载。
      componentWillUnmount() {
        // 清除自定义事件
        window.removeEventListener('xxx', this.handleClick)
      }
    

错误处理

  • 当渲染过程,生命周期或子组件的构造函数抛出错误时,会调用的方法

static getDerivedStateFromError()

  • 会在后代组件抛出错误后被调用,它将抛出的错误作为参数,并返回一个值以更新 state
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显降级 UI
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      // 你可以渲染任何自定义的降级  UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

注意:注意 getDerivedStateFromError() 会在渲染阶段调用,因此不允许出现副作用。 如遇此类情况,请改用 componentDidCatch()。

componentDidCatch()

  • 此生命周期在后代抛出错误后被调用。接受两个参数:

    1. error -> 抛出的错误
    2. info -> 带有 componentStack key 的对象。
  • componentDidCatch() 会在“提交”阶段被调用,因此允许执行副作用。 它应该用于记录错误之类的情况

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // 更新 state 使下一次渲染可以显示降级 UI
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    // "组件堆栈" 例子:
    //   in ComponentThatThrows (created by App)
    //   in ErrorBoundary (created by App)
    //   in div (created by App)
    //   in App
    logComponentStackToMyService(info.componentStack);
  }

  render() {
    if (this.state.hasError) {
      // 你可以渲染任何自定义的降级 UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}

其他 API

forceUpdate()

  • component.forceUpdate(callback)

  • 默认情况下,当组件的 state 或 props 发生变化时,组件将重新渲染,如果 render 方法依赖于其他数据,则可以调用 forceUpdate() 强制让组件重新渲染。

  • 如果调用 forceUpdate 将导致组件直接调用 render 方法,会跳过该组件的 shouldComponentUpdate 方法,当子组件会正常触发 生命周期方法。

  • 建议应该避免使用 forceUpdate,尽量在 render 中使用 props 和 state

Class 属性

  • defaultProps 可以为 Class 组件添加默认 props。这一般用于 props 未赋值,但又不能为 null 的情况。
  class CustomButton extends React.Component {
    // ...
  }

  CustomButton.defaultProps = {
    color: 'blue'
  }

Fiber

  • Fiber 是 React 16 对 React 核心算法的一次重写
  • Fiber 会使同步的渲染过程变成异步的
  • Fiber 会将一个大的更新任务拆解为许多的小任务

Fiber 架构的重要特性就是可以被打断的异步渲染模式,根据 “能否被打断” 这一标准 React16 的生命周期被划分为了 render 和 commit 两个阶段
render 阶段在执行过程中允许被打断 而 commit 阶段则总是同步执行的。

为什么 React16 之后废除了以下几个生命周期

  • componentWillMount
  • componentWillUpdate
  • componentWillReceiveProps

因为这些生命周期是 render 阶段,在执行过程中是允许被打断的,而被打断后“再次会重新执行这些生命周期”,所以会出现重复,如果开发者滥用,会出现很多 BUG, 所以废除了。

React 16 改造生命周期的主要动机

  • 是为了配合 Fiber 架构带来的异步渲染机制
  • 针对生命周期中长期被滥用的部分推出了具有强制性的最佳实践
  • 确保了 Fiber 机制下数据和视图的安全性
  • 同时也确保了生命周期方法的行为更加纯粹、可控、可预测

每天进步一点点 💪

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容