首页 前端 Vue.js 正文

Vue和React对比学习之生命周期函数(Vue2、Vue3、老版React、新版React)


简介

VueReact是目前前端最火的两个框架。不管是面试还是工作可以说是前端开发者们都必须掌握的。

今天我们通过对比的方式来学习VueReact的生命周期这一部分。

本文首先讲述Vue2Vue3、老版React、新版React的生命周期,然后分析了老版本三个生命周期方法的问题,以及在新版本的替代方案。最后对比总结了VueReact在生命周期这部分的相同点和不同点。

希望通过这种对比方式的学习能让我们学习的时候印象更深刻,希望能够帮助到大家。

Vue2

vue2生命周期函数有

  1. beforeCreate

  2. created

  3. beforeMount

  4. mounted

  5. beforeUpdate

  6. updated

  7. beforeDestroy

  8. destroyed

  9. errorCaptured

  10. activated

  11. deactivated

引用vue官网的图,各个生命周期函数运行如下

Vue和React对比学习之生命周期函数(Vue2、Vue3、老版React、新版React)  第1张

下面我们来重点分析下各个函数。

Vue2生命周期函数分析

beforeCreate

beforeCreate在组件初始化的时候会运行,只会运行一次。可以在此函数里面调用后台接口获取数据。

此函数获取不到DOM元素。

created

created在组件初始化的时候会运行,只会运行一次。可以在此函数里面调用后台接口获取数据。

此函数能获取数据侦听、计算属性、方法、事件/侦听器的回调函数,但是依然获取不到DOM元素。

beforeMount

beforeMount在组件初始化的时候会运行,只会运行一次。可以在此函数里面调用后台接口获取数据。

该钩子在服务器端渲染期间不被调用。

mounted

mounted在组件初始化的时候会运行,只会运行一次。可以在此函数里面调用后台接口获取数据。

此函数能获取数据侦听、计算属性、方法、事件/侦听器的回调函数,还能获取到DOM元素。

该钩子在服务器端渲染期间不被调用。

beforeUpdate

beforeUpdate在数据发生改变后,DOM 被更新之前被调用。这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。

该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务器端进行

updated

updated在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。

注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等待整个视图都渲染完毕,可以在 updated 内部使用 vm.$nextTick

updated() {
  this.$nextTick(function () {
    // 仅在整个视图都被重新渲染完毕之后才会运行的代码
  })
}


该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务器端进行

beforeDestroy

beforeDestroy在卸载组件实例之前调用。在这个阶段,实例仍然是完全正常的。

该钩子在服务器端渲染期间不被调用。

destroyed

destroyed在卸载组件实例后调用。调用此钩子时,组件实例的所有指令都被解除绑定,所有事件侦听器都被移除,所有子组件实例被卸载。

该钩子在服务器端渲染期间不被调用

errorCaptured

errorCaptured(err: Error, instance: Component, info: string)在捕获一个来自后代组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

你可以在此钩子中修改组件的状态。因此在捕获错误时,在模板或渲染函数中有一个条件判断来绕过其它内容就很重要;不然该组件可能会进入一个无限的渲染循环。

错误传播规则

  • 默认情况下,如果全局的 config.errorHandler 被定义,所有的错误仍会发送它,因此这些错误仍然会向单一的分析服务的地方进行汇报。

  • 如果一个组件的继承链或父级链中存在多个 errorCaptured 钩子,则它们将会被相同的错误逐个唤起。

  • 如果此 errorCaptured 钩子自身抛出了一个错误,则这个新错误和原本被捕获的错误都会发送给全局的 config.errorHandler

  • 一个 errorCaptured 钩子能够返回 false 以阻止错误继续向上传播。本质上是说“这个错误已经被搞定了且应该被忽略”。它会阻止其它任何会被这个错误唤起的 errorCaptured 钩子和全局的 config.errorHandler

activated

activated在被 keep-alive 缓存的组件激活时调用。

该钩子在服务器端渲染期间不被调用。

deactivated

deactivated在被 keep-alive 缓存的组件失活时调用。

该钩子在服务器端渲染期间不被调用。

Vue2周期函数调用顺序

下面我们分不同情况来进行详细分析

单组件初始化

beforeCreate -> created -> beforeMount -> mounted

单组件更新

beforeUpdate -> updated

单组件卸载

beforeDestroy -> destroyed

父子组件初始化

父组件beforeCreate -> 父组件created -> 父组件beforeMount -> 子组件beforeCreate -> 子组件created -> 子组件beforeMount -> 子组件mounted -> 父组件mounted

父组件更新data,此data没传递给子组件

父组件beforeUpdate -> 父组件updated

父组件更新data,此data传递给了子组件

父组件beforeUpdate -> 子组件beforeUpdate -> 子组件updated -> 父组件updated

子组件更新data

子组件beforeUpdate -> 子组件updated

父子组件卸载

父组件beforeDestroy -> 子组件beforeDestroy -> 子组件destroyed -> 父组件destroyed

Vue3

引用vue3官网的图,各个生命周期函数运行如下

Vue和React对比学习之生命周期函数(Vue2、Vue3、老版React、新版React)  第2张

Vue3生命周期函数分析

vue3没有删除vue2选项式写法的生命周期函数,这些都还全部保留并支持。

vue3新增了renderTrackedrenderTriggered两个生命周期方法。

vue3中销毁生命周期方法名也发生了变化,由beforeDestroydestroyed变为beforeUnmountunmounted,这样是为了更好的与beforeMountmounted 相对应。

vue3写在setup函数中生命周期方法名发生了改变,就是前面多加了on。并且在setup函数中不支持beforeCreate、created

如果beforeCreate、created以及setup都存在的话,生命周期函数的运行顺序是setup -> beforeCreate -> created

总结

vue2vue3选项式vue3(setup)
beforeCreatebeforeCreate
createdcreated
beforeMountbeforeMountonBeforeMount
mountedmountedonMounted
beforeUpdatebeforeUpdateonBeforeUpdate
updatedupdatedonUpdated
beforeDestroybeforeUnmountonBeforeUnmount
destroyedunmountedonUnmounted
errorCapturederrorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered
activatedactivatedonActivated
deactivateddeactivatedonDeactivated

前面的方法在vue3中除了更改了名称,功能都是没有改变的。所以我们重点说下新增的 renderTrackedrenderTriggered两个方法。

renderTracked

简单理解就是,首次渲染时,模板里面进行了哪些操作,以及该操作的目标对象和键。

如果有多个属性,这个方法会被触发多次。

我们来看例子

<template>
  <div>
    <div>{{ name }}</div>
    <div>user: {{ user.age }}</div>
  </div>
</template>
<script>
import {
  defineComponent,
  onRenderTracked,
  onRenderTriggered,
  ref,
  reactive,
} from "vue";
export default defineComponent({
  setup() {
    const name = ref("randy");
    const user = reactive({ age: 27 });

    onRenderTracked(({ key, target, type }) => {
      console.log("onRenderTracked", { key, target, type });
    });
    onRenderTriggered(({ key, target, type }) => {
      console.log("onRenderTriggered", { key, target, type });
    });

    return {
      name,
      user,
    };
  },
});
</script>


页面首次加载只会触发onRenderTracked方法。

因为模板里面用到了nameuser.age所以该方法会被触发两次输出{key: 'value', target: RefImpl, type: 'get'}{key: 'age', target: {age: 27}, type: 'get'}。因为nameref定义的,所以key始终是value,并且只是读操作,所以typegetuserreactive定义的,并且我们只使用了age属性所以keyage并且只是读操作,所以typeget

renderTriggered

简单理解就是,页面更新渲染时,模板里面进行了哪些操作,以及该操作的目标对象和键。

如果有多个属性被修改,这个方法会被触发多次。

我们来看例子

<template>
  <div>
    <div>{{ name }}</div>
    <button @click="changeName">changeName</button>
    <div>user: {{ user.age }}</div>
  </div>
</template>
<script>
import {
  defineComponent,
  onRenderTracked,
  onRenderTriggered,
  ref,
  reactive,
} from "vue";
export default defineComponent({
  setup() {
    const name = ref("randy");
    const changeName = () => {
      name.value = "demi";
    };
    const user = reactive({ age: 27 });

    onRenderTracked(({ key, target, type }) => {
      console.log("onRenderTracked", { key, target, type });
    });
    onRenderTriggered(({ key, target, type }) => {
      console.log("onRenderTriggered", { key, target, type });
    });

    return {
      name,
      changeName,
      user,
    };
  },
});
</script>


我们点击changeName按钮来修改name,这里只会触发onRenderTriggered方法一次。并且输出{key: 'value', target: RefImpl, type: 'set'},因为是修改所以typeset

Vue3周期函数调用顺序

下面我们分不同情况来进行详细分析

单组件初始化

setup -> created -> onBeforeMount -> onRenderTracked -> onMounted

单组件更新

onRenderTriggered-> onBeforeUpdate -> onUpdated

单组件卸载

onBeforeDestroy -> onDestroyed

父子组件初始化

父组件setup -> 父组件onBeforeMount -> 父组件onRenderTracked -> 子组件setup -> 子组件onBeforeMount -> 子组件onRenderTracked -> 子组件onMounted -> 父组件onMounted

父组件更新data,此data没传递给子组件

父组件onRenderTriggered-> 父组件onBeforeUpdate ->父组件 onUpdated

父组件更新data,此data传递给了子组件

父组件onRenderTriggered -> 父组件onBeforeUpdate -> 子组件onBeforeUpdate -> 子组件onUpdated -> 子组件onUpdated

子组件更新data

子组件onRenderTriggered -> 子组件onBeforeUpdate -> 子组件onUpdated

父子组件卸载

父组件onBeforeDestroy -> 子组件onBeforeDestroy -> 子组件onDestroyed -> 父组件onDestroyed

老版本React

老版本react生命周期函数有

  1. constructor

  2. componentWillMount

  3. componentDidMount

  4. shouldComponentUpdate

  5. componentWillUpdate

  6. componentDidUpdate

  7. componentWillReceiveProps

  8. render

  9. componentWillUnmount

  10. componentDidCatch

老版本react各个生命周期函数运行如下

Vue和React对比学习之生命周期函数(Vue2、Vue3、老版React、新版React)  第3张

老版本React生命周期函数分析

constructor

constructor初始化阶段运行,只运行一次,用于初始数据。比如state

constructor() {
  super()
  this.state = {title: '生命周期函数'}
}


componentWillMount

componentWillMount 初始化阶段运行,只运行一次。在这里获取不到DOM元素。

componentDidMount

componentWillMount 初始化阶段运行,只运行一次。在这里可以获取到DOM元素。

异步请求推荐写在该函数中,比如请求后台获取数据。

shouldComponentUpdate(nextProps, nextState)

shouldComponentUpdate(nextProps, nextState)初始化阶段不运行,在组件更新时运行。

如果定义了该方法必须显示返回true或者falsetrue表示需要更新,false表示不需要更新,常用来做性能优化。

接收两个参数,分别是最新的props和最新的state,我们可以利用这点和当前的stateprops作比较来决定渲染或者不渲染来进行性能优化。

shouldComponentUpdate(nextProps, nextState) {
  // this.props和this.state还是之前的
  if (this.props.title !== nextProps.title) {
    return true;
  }
  if (this.state.name !== nextState.name) {
    return true;
  }
  return false;
}


componentWillUpdate(nextProps, nextState)

componentWillUpdate(nextProps, nextState)初始化阶段不运行,在组件将要更新时运行。

接收两个参数,分别是最新的props和最新的state,我们可以利用这点和当前的stateprops作比较来做些特殊逻辑处理。

componentDidUpdate(prevProps, prevState)

componentDidUpdate(prevProps, prevState)初始化阶段不运行,在组件更新完时运行。

接收两个参数,分别是之前的prevProps和之前的prevState。也就是说还可以获取上一次的propsstate,可以做一些特殊处理。

componentWillReceiveProps(nextProps)

componentWillReceiveProps(nextProps)初始化阶段不运行,在组件更新之前运行。

接收一个参数nextProps,表示最新的props

组件自身state的变更引发的组件更新并不会触发该方法。

在父组件传递props给该组件,并且props发生改变时才会运行。

但是注意,如果父组件导致组件重新渲染,即使 props 没有更改,也会调用此方法。如果只想处理更改,请确保进行当前值与变更值的比较。

在这里你可以使用this.propsnextProps作比较来处理一些特殊逻辑。

render

render 渲染方法。在这里主要是书写页面元素和样式。

componentWillUnmount

componentWillUnmount在组件卸载时运行,可以在这里清除一些副作用,比如监听函数。定时器等等。

componentDidCatch(error, errorInfo)

componentDidCatch(error, errorInfo)子组件发生错误是被调用。

接收errorerrorInfo两个参数,error 表示抛出的错误。 errorInfo带有 componentStack key 的对象,其中包含有关组件引发错误的栈信息。

在这里可以用来把错误上传到服务器做错误日志。

老版本React周期函数调用顺序

下面我们分不同情况来进行详细分析

单组件初始化

constructor -> componentWillMount -> render -> componentDidMount

单组件更新state

shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate

单组件卸载

componentWillUnmount

父子组件初始化

父组件constructor -> 父组件componentWillMount -> 父组件render -> 子组件constructor -> 子组件componentWillMount -> 子组件render -> 子组件componentDidMount -> 父组件componentDidMount

这里不管父组件是否传递props给子组件生命周期函数都如上运行。

父组件更新state

父组件shouldComponentUpdate -> 父组件componentWillUpdate -> 父组件render -> 子组件componentWillReceiveProps -> 子组件shouldComponentUpdate -> 子组件componentWillUpdate -> 子组件render -> 子组件componentDidUpdate -> 父组件componentDidUpdate

这里不管父组件是否传递props给子组件生命周期函数都如上运行。也就是说父组件更新子组件的componentWillReceiveProps必会被触发。

子组件更新state

子组件shouldComponentUpdate -> 子组件componentWillUpdate -> 子组件render -> 子组件componentDidUpdate

父子组件卸载

父组件componentWillUnmount -> 子组件componentWillUnmount

新版本React

新版本主要是React 16+,因为在新版本做了很多调整。新版本react生命周期函数有

  1. constructor

  2. componentDidMount

  3. static getDerivedStateFromProps

  4. shouldComponentUpdate

  5. getSnapshotBeforeUpdate

  6. componentDidUpdate

  7. render

  8. componentWillUnmount

  9. componentDidCatch

  10. getDerivedStateFromError

版本说明

componentWillMountcomponentWillReceivePropscomponentWillUpdate 这三个生命周期因为经常会被误解和滥用,所以被称为 不安全(不是指安全性,而是表示使用这些生命周期的代码,有可能在未来的 React 版本中存在缺陷,可能会影响未来的异步渲染) 的生命周期。

React 16.3 版本:为不安全的生命周期引入别名 UNSAFE_componentWillMountUNSAFE_componentWillReceivePropsUNSAFE_componentWillUpdate。(旧的生命周期名称和新的别名都可以在此版本中使用

React 16.3 之后的版本:为 componentWillMountcomponentWillReceivePropscomponentWillUpdate 启用弃用警告。(旧的生命周期名称和新的别名都可以在此版本中使用,但旧名称会记录DEV模式警告

React 17.0 版本: 推出新的渲染方式——异步渲染( Async Rendering),提出一种可被打断的生命周期,而可以被打断的阶段正是实际 dom 挂载之前的虚拟 dom 构建阶段,也就是要被去掉的三个生命周期 componentWillMountcomponentWillReceivePropscomponentWillUpdate。(从这个版本开始,只有新的“UNSAFE_”生命周期名称将起作用

总体来说新版本的react生命周期函数就是去除了三个不安全函数

  1. componentWillMount

  2. componentWillReceiveProps

  3. componentWillUpdate

新增了三个生命周期函数

  1. static getDerivedStateFromProps

  2. getSnapshotBeforeUpdate

  3. static getDerivedStateFromError

新版本react各个生命周期函数运行如下

Vue和React对比学习之生命周期函数(Vue2、Vue3、老版React、新版React)  第4张

下面我们重点分析这三个新方法

新生命周期函数分析

static getDerivedStateFromProps(props, state)

static getDerivedStateFromProps(props, state)在组件初始化和组件更新时都会被调用。

接收stateprops两个参数,在这里可以通过返回一个对象来更新组件自身的state,或者返回 null 来表示接收到的 props 没有变化,不需要更新 state

请注意该方法是一个静态方法,所以该生命周期钩子内部没有this,所以无法通过使用 this 获取组件实例的属性/方法。

该生命周期钩子的作用: 将父组件传递过来的 props 映射 到子组件的 state 上面,这样组件内部就不用再通过 this.props.xxx 获取属性值了,统一通过 this.state.xxx 获取。映射就相当于拷贝了一份父组件传过来的 props ,作为子组件自己的状态。注意:子组件通过 setState 更新自身状态时,不会改变父组件的 props

// 老版本通过该方法里更新state
componentWillReceiveProps(nextProps) {  
  if (nextProps.translateX !== this.props.translateX) {
    this.setState({ 
      translateX: nextProps.translateX, 
    }); 
  } 
}

// 代替componentWillReceiveProps
// 通过返回对象来更新state
static getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.translateX !== prevState.translateX) {
    return {
      translateX: nextProps.translateX,
    };
  }
  return null;
}


getSnapshotBeforeUpdate(prevProps, prevState)

getSnapshotBeforeUpdate(prevProps, prevState)在组件更新时被调用。被调用于 render 之后、更新 DOMrefs 之前。

此生命周期钩子必须有返回值,返回值将作为 componentDidUpdate 第三个参数。必须和 componentDidUpdate 一起使用,否则会报错。

在这里this.propsthis.state是最新的,可以和该函数的参数prevProps, prevState作比较,进行逻辑处理。

该生命周期钩子的作用: 它能让你在组件更新 DOMrefs 之前,从 DOM 中捕获一些信息(例如滚动位置)。

getSnapshotBeforeUpdate(prevProps, prevState) {
  // 这里的state和props已经是最新的了
  // console.log(this.props, this.state);
  return 456;
}

componentDidUpdate(prevProps, prevState, snapshot) {
  // 第三个参数 snapshot 是 getSnapshotBeforeUpdate返回值
  console.log(snapshot); // 456
}


static getDerivedStateFromError(error)

static getDerivedStateFromError(error)会在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state

可能有人会问,这个跟componentDidCatch方法有什么区别呢?

static getDerivedStateFromError(error)在渲染DOM之前调用,当我们遇到子组件出错的时候可以渲染备用UI,常用作错误边界。而componentDidCatch是在DOM渲染完后才会调用,可以用来输出错误信息或上传一些错误报告。

比如我们可以定义一个错误边界组件,在子组件出错的时候显示错误提示,不至于页面不渲染。

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;
  }
}


新版本React周期函数调用顺序

下面我们分不同情况来进行详细分析

单组件初始化

constructor -> getDerivedStateFromProps -> render -> componentDidMount

单组件更新state

getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate

单组件卸载

componentWillUnmount

父子组件初始化

父组件constructor -> 父组件getDerivedStateFromProps -> 父组件render -> 子组件constructor -> 子组件getDerivedStateFromProps -> 子组件render -> 子组件componentDidMount -> 父组件componentDidMount

父组件更新state

父组件getDerivedStateFromProps -> 父组件shouldComponentUpdate -> 父组件render -> 子组件getDerivedStateFromProps -> 子组件shouldComponentUpdate -> 子组件render -> 子组件getSnapshotBeforeUpdate -> 父组件getSnapshotBeforeUpdate -> 子组件componentDidUpdate -> 父组件componentDidUpdate

这里不管父组件是否传递props给子组件生命周期函数都如上运行。也就是说父组件更新子组件的getDerivedStateFromProps必会被触发。

子组件更新state

子组件getDerivedStateFromProps -> 子组件shouldComponentUpdate -> 子组件render -> 子组件getSnapshotBeforeUpdate -> 子组件componentDidUpdate

父子组件卸载

父组件componentWillUnmount -> 子组件componentWillUnmount

老版本React生命周期问题

上面已经介绍了,新版本react删除了componentWillMountcomponentWillReceivePropscomponentWillUpdate三个方法。下面我们来说说这三个为什么会被删除,以及在新版生命周期函数中用什么来替代。

componentWillMount

首屏无数据导致白屏

React 应用中,许多开发者为了避免第一次渲染时页面因为没有获取到异步数据导致的白屏,而将数据请求部分的代码放在了 componentWillMount 中,希望可以避免白屏并提早异步请求的发送时间。

但事实上在 componentWillMount 执行后,第一次渲染就已经开始了,所以如果在 componentWillMount 执行时还没有获取到异步数据的话,页面首次渲染时也仍然会处于没有异步数据的状态。换句话说,组件在首次渲染时总是会处于没有异步数据的状态,所以不论在哪里发送数据请求,都无法直接解决这一问题。而关于提早发送数据请求,官方也鼓励将数据请求部分的代码放在组件的 constructor 中,而不是 componentWillMount

事件订阅

另一个常见的用例是在 componentWillMount 中订阅事件,并在 componentWillUnmount 中取消掉相应的事件订阅。但事实上 React 并不能够保证在 componentWillMount 被调用后,同一组件的 componentWillUnmount 也一定会被调用。

一个当前版本的例子如服务端渲染时,componentWillUnmount 是不会在服务端被调用的,所以在 componentWillMount 中订阅事件就会直接导致服务端的内存泄漏。

另一方面,在未来 React 开启异步渲染模式后,在 componentWillMount 被调用之后,组件的渲染也很有可能会被其他的事务所打断,导致 componentWillUnmount 不会被调用。而 componentDidMount 就不存在这个问题,在 componentDidMount 被调用后,componentWillUnmount 一定会随后被调用到,并根据具体代码清除掉组件中存在的事件订阅。

新版本替代方案

将现有 componentWillMount 中的代码迁移至 componentDidMount 即可。

componentWillReceiveProps

更新由 props 决定的 state 及处理特定情况下的回调

在老版本的 React 中,如果组件自身的某个 state 跟其 props 密切相关的话,需要在 componentWillReceiveProps 中判断前后两个 props 是否相同,如果不同再将新的 props 更新到相应的 state 上去。这样做一来会破坏 state 数据的单一数据源,导致组件状态变得不可预测,另一方面也会增加组件的重绘次数。

类似的业务需求也有很多,如一个可以横向滑动的列表,当前高亮的 Tab 显然隶属于列表自身的状态,但很多情况下,业务需求会要求从外部跳转至列表时,根据传入的某个值,直接定位到某个 Tab。

一个简单的例子如下:

// before
componentWillReceiveProps(nextProps) {  
  if (nextProps.translateX !== this.props.translateX) {
    this.setState({ 
      translateX: nextProps.translateX, 
    }); 
  } 
}

// after
static getDerivedStateFromProps(nextProps, prevState) {
  if (nextProps.translateX !== prevState.translateX) {
    return {
      translateX: nextProps.translateX,
    };
  }
  return null;
}


乍看下来这二者好像并没有什么本质上的区别,但这却是笔者认为非常能够体现 React 团队对于软件工程深刻理解的一个改动,即 React 团队试图通过框架级别的 API 来约束或者说帮助开发者写出可维护性更佳的 JavaScript 代码。为了解释这点,我们再来看一段代码:

// before
componentWillReceiveProps(nextProps) {
  if (nextProps.isLogin !== this.props.isLogin) {
    this.setState({ 
      isLogin: nextProps.isLogin,   
    });
  }
  if (nextProps.isLogin) {
    this.handleClose();
  }
}


<template>
  <div>
    <div>{{ name }}</div>
    <div>user: {{ user.age }}</div>
  </div>
</template>
<script>
import {
  defineComponent,
  onRenderTracked,
  onRenderTriggered,
  ref,
  reactive,
} from "vue";
export default defineComponent({
  setup() {
    const name = ref("randy");
    const user = reactive({ age: 27 });

    onRenderTracked(({ key, target, type }) => {
      console.log("onRenderTracked", { key, target, type });
    });
    onRenderTriggered(({ key, target, type }) => {
      console.log("onRenderTriggered", { key, target, type });
    });

    return {
      name,
      user,
    };
  },
});
</script>0


通常来讲,在 componentWillReceiveProps 中,我们一般会做以下两件事,一是根据 props 来更新 state,二是触发一些回调,如动画或页面跳转等。在老版本的 React 中,这两件事我们都需要在 componentWillReceiveProps 中去做。而在新版本中,官方将更新 state 与触发回调重新分配到了 getDerivedStateFromPropscomponentDidUpdate 中,使得组件整体的更新逻辑更为清晰。而且在 getDerivedStateFromProps 中还禁止了组件去访问 this.props,强制让开发者去比较 nextProps 与 prevState 中的值,以确保当开发者用到 getDerivedStateFromProps 这个生命周期函数时,就是在根据当前的 props 来更新组件的 state,而不是去做其他一些让组件自身状态变得更加不可预测的事情。

新版本替代方案

将现有 componentWillReceiveProps 中的代码根据更新 state 或回调,分别在 getDerivedStateFromPropscomponentDidUpdate 中进行相应的重写即可。

componentWillUpdate

处理因为 props 改变而带来的副作用

componentWillReceiveProps 类似,许多开发者也会在 componentWillUpdate 中根据 props 的变化去触发一些回调。但不论是 componentWillReceiveProps 还是 componentWillUpdate,都有可能在一次更新中被调用多次,也就是说写在这里的回调函数也有可能会被调用多次,这显然是不可取的。

componentDidMount 类似,componentDidUpdate 也不存在这样的问题,一次更新中 componentDidUpdate 只会被调用一次,所以将原先写在 componentWillUpdate 中的回调迁移至 componentDidUpdate 就可以解决这个问题。

在组件更新前读取 DOM 元素状态

componentWillUpdate 是在组件更新前被调用,读取当前某个 DOM 元素的状态,并在 componentDidUpdate 中进行相应的处理。但在 React 开启异步渲染模式后,render 阶段和 commit 阶段之间并不是无缝衔接的,也就是说在 render 阶段读取到的 DOM 元素状态并不总是和 commit 阶段相同,这就导致在 componentDidUpdate 中使用 componentWillUpdate 中读取到的 DOM 元素状态是不准确的。

componentWillUpdate 不同,getSnapshotBeforeUpdate 会在最终的 render 之后被调用(但是此时还没有真正更新真实DOM),也就是说在 getSnapshotBeforeUpdate 中读取到的 DOM 元素状态与之前的肯定是一样的。

getSnapshotBeforeUpdate 返回一个值。这个值会被最为第三个参数被传入到 componentDidUpdate 中,然后我们就可以在 componentDidUpdate 中去更新组件的状态,而不是在 getSnapshotBeforeUpdate 中直接更新组件状态。

官方提供的一个例子如下:

<template>
  <div>
    <div>{{ name }}</div>
    <div>user: {{ user.age }}</div>
  </div>
</template>
<script>
import {
  defineComponent,
  onRenderTracked,
  onRenderTriggered,
  ref,
  reactive,
} from "vue";
export default defineComponent({
  setup() {
    const name = ref("randy");
    const user = reactive({ age: 27 });

    onRenderTracked(({ key, target, type }) => {
      console.log("onRenderTracked", { key, target, type });
    });
    onRenderTriggered(({ key, target, type }) => {
      console.log("onRenderTriggered", { key, target, type });
    });

    return {
      name,
      user,
    };
  },
});
</script>1


新版本替代方案

将现有的 componentWillUpdate 中的回调函数迁移至 componentDidUpdate

如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至 getSnapshotBeforeUpdate,把计算值返回出来。然后在 componentDidUpdate 通过第三个参数获取到值,然后统一触发回调或更新状态。

对比总结

VueReact的生命周期函数我们都介绍完了,下面我们来总结下它们的相同点和不同点。

相同点

  1. VueReact生命周期函数基本类似,组件的创建、更新、卸载都有对应的函数。都很完善,能监控到组件从创建到消亡各个阶段。

  2. 组件创建 更新 销毁都符合洋葱模型

Vue和React对比学习之生命周期函数(Vue2、Vue3、老版React、新版React)  第5张

不同点

  1. Vue多了缓存的生命周期函数 activated、deactivated和用于开发调试的renderTrackedrenderTriggered生命周期函数。

  2. Vue生命周期函数除了errorCaptured都是没有参数的。而React很多生命后期函数都有参数并且能访问到旧的或新的propsstate

  3. React父组件更新,子组件一定都会更新渲染,除非自己手动优化。而在Vue中这一部分是已经实现了的。也就是说,除非子组件依赖父组件的数据改变了,否则子组件是不会重新渲染的。但是React需要自己手动优化,比如继承PureComponent或者实现shoouldComponent方法,来手动优化。

  4. Vue中,更新操作都已经完全封装好,所以数据改变就一定会重新渲染,没办法阻止。但是在React中能通过shoouldComponent方法来决定是否需要渲染,这块是比Vue更灵活的。

  5. Vue在组件报错的时候页面还是会渲染,只是引发出错的地方可能数值不对。所以在Vue中没有错误边界这个说法,不需要定义错误边界组件自定义处理渲染错误。

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!

原文:https://juejin.cn/post/7101531970194112543

打赏
海报

本文转载自互联网,旨在分享有价值的内容,文章如有侵权请联系删除,部分文章如未署名作者来源请联系我们及时备注,感谢您的支持。

转载请注明本文地址:https://www.shouxicto.com/article/5353.html

相关推荐

Vue3 中如何加载动态菜单?

Vue3 中如何加载动态菜单?

简介Vue和React是目前前端最火的两个框架。不管是面试还是工作可以说是前端开发者们都必须掌握的。今天我们通过对比的方式来学习Vue和...

Vue.js 2022.07.20 0 467

vue-amap引入高德JS API的原理

vue-amap引入高德JS API的原理

简介Vue和React是目前前端最火的两个框架。不管是面试还是工作可以说是前端开发者们都必须掌握的。今天我们通过对比的方式来学习Vue和...

Vue.js 2022.06.01 0 747

Vue3 新特性

Vue3 新特性

简介Vue和React是目前前端最火的两个框架。不管是面试还是工作可以说是前端开发者们都必须掌握的。今天我们通过对比的方式来学习Vue和...

Vue.js 2022.06.01 0 728

50+Vue经典面试题源码级详解(24)

   Vue 3.0的设计目标是什么?做了哪些优化?    分析    还是问新特性,陈述典型新特性,分析其给你带来的变化即可。    思路    从以...

Vue.js 2022.06.01 0 712

发布评论

ainiaobaibaibaibaobaobeishangbishibizuichiguachijingchongjingdahaqiandaliandangaodw_dogedw_erhadw_miaodw_tuzidw_xiongmaodw_zhutouganbeigeiliguiguolaiguzhanghahahahashoushihaixiuhanheixianhenghorse2huaixiaohuatonghuaxinhufenjiayoujiyankeaikeliankouzhaokukuloukunkuxiaolandelinileimuliwulxhainiolxhlikelxhqiuguanzhulxhtouxiaolxhwahahalxhzanningwennonuokpinganqianqiaoqinqinquantouruoshayanshengbingshiwangshuaishuijiaosikaostar0star2star3taikaixintanshoutianpingtouxiaotuwabiweifengweiquweiwuweixiaowenhaowoshouwuxiangjixianhuaxiaoerbuyuxiaokuxiaoxinxinxinxinsuixixixuyeyinxianyinyueyouhenghengyuebingyueliangyunzanzhajizhongguozanzhoumazhuakuangzuohenghengzuoyi
支付宝
微信
赞助本站