Vue3新特性梳理

更多前端学习笔记请戳:https://github.com/6fa/WebKnowledge

新特性总结

vue3和vue2对比:

  • 重写了响应式系统、重写了虚拟DOM的实现,性能上得到了提升
  • 新推出组合式API(composition API),使维护组件代码变得更简单
  • 新增了一些功能,如Teleport、Suspense、片段
  • 修改和优化了一些API(生命周期、动画类名),同时移除了一些API(如$children、$listeners、filter)
  • 有更好的TypeScript支持

同时Vue3是向下兼容的,可以使用大部分的Vue2特性。

创建应用实例

从如何创建一个Vue实例说起,Vue2通过new Vue( )创建实例,而Vue3通过Vue.createApp( )创建应用实例:

counter: {{counter}}

注意应用实例和组件实例的区别:
1.应用实例(app)

  • Vue.createApp( )返回一个应用实例
  • 应用实例上有很多方法,比如app.component( )注册全局组件,app.directive( )注册全局指令等。而Vue2中它们是全局API,如Vue.component( )
  • 应用实例上的方法大多返回同一应用实例 (mount除外),所以可以链式操作
Vue.createApp({})
  .component('SearchInput', SearchInputComponent)
  .directive('focus', FocusDirective)
  .use(LocalePlugin)

2.组件实例(vm)

  • 传递给createApp( )的选项用于配置根组件
  • 将应用实例挂载到一个DOM元素上,返回根组件实例(vm)

应用挂载

将应用挂载到DOM元素上时,Vue3有一点小改变:

  • 有设置template时,Vue2将模板内容替换目标元素,而Vue3将模板内容作为子元素插入


    

Vue2



    

Vue3

  • 无template时,Vue2将目标元素的outerHTML当作template,Vue3将目标元素的innerHTML当作template

{{msg}}

//vue3 Vue.createApp({}).mount('#app') //则模板是id为app的元素的innerHTML:

{{msg}}

//即this.$el指向h1这个DOM元素 //vue2 new Vue({ el:"#app" }) //模板是id为app的元素的outerHTML:

{{msg}}

//即this.$el指向div这个DOM元素

生命周期

钩子函数

Vue3的钩子函数与Vue2相比,将beforeDestory、destoryed修改为beforeUnmount、unmounted,增加了renderTracked、renderTriggered、errorCaptured:

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeUnmount
  • unmounted
  • renderTracked
  • renderTriggered
  • errorCaptured
errorCaptured (新增)
  • 当捕获到一个后代组件的错误时,会调用errorCaptured

    errorCaptured(err, errComponent, errInfo){ 
     //err:错误对象
     //errComponent:发生错误的后代组件实例
     //errInfo:包含错误来源的信息
     return true/false 
    }
  • 函数返回false可以阻止错误继续向上传递(但还是会传递给app.config.errorHandler)
renderTracked (新增,状态跟踪,调试用)
  • 此事件告诉你哪个操作跟踪了组件以及该操作的目标对象和键
  • 跟踪虚拟 DOM 重新渲染时调用,可以看作收集依赖时触发,就是渲染依赖了那些数据,当数据变化时会触发,接收一个event对象
  • 状态跟踪在一开始渲染页面时就会触发,触发顺序:beforeMounted - renderTracked - mounted
  • 页面更新也会触发,触发顺序:beforeUpdate - renderTracked - updated

    //event对象
    {
     effect: {...},
     key: "girls",               //操作的键
     target: {girls:{...},...},  //操作的目标对象
     type: "get"                 //哪个操作跟踪了组件
    }
renderTriggered (新增,状态触发,调试用)
  • 当虚拟DOM重新渲染时触发。和renderTracked类似,接收event对象,此事件告诉你是什么操作触发了重新渲染,以及该操作的目标对象和键
  • 操作页面的显示的数据(引起虚拟DOM重渲染)就会触发,触发顺序:renderTriggered - beforeUpdate

setup生命周期

setup函数里面设置生命周期函数,是为了使组合式API的功能和选项式API一样完整。

  • setup函数内部的钩子函数基本和选项式一样,只是没有beforeCreate、created,并且在函数前面加上on。
  • 因为setup函数会在beforeCreate之前就执行,即组件创建之前执行。所有beforeCreate、created的代码应该写在setup中
  • 因为还没组件实例,setup中避免使用this,也不能访问以下选项:

    • data
    • computed
    • methods
  • 只能访问以下属性(通过第一个参数props,第二个参数context):

    • props
    • attrs
    • slots
    • emit
      Vue3新特性梳理_第1张图片

组件 & 选项

注册组件

注册组件的方法与vue2有略微差别。

vue2:

//全局注册
Vue.component("component-name", {
  //...选项
})

new Vue({
  el:"..."
})

//局部注册
const componentA = {}
const componentB = {}


new Vue({
  el:"...",
  components:{
    "component-a": componentA,
    "component-b": componentB
  }
})

vue3:

//全局注册
const app = Vue.createApp({...})
                        
app.component("name",{...})   //注册组件是应用实例的方法

//局部注册
const componentA = {}
const componentB = {}

const app = Vue.createApp({
  //...
  components:{
    "component-a":componentA,
    "component-b":componentB,
  }
})

app.mount("#app") //挂载

Mixin的合并行为

在vue2中使用Mixin,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先:

const Mixin = {
  data() {
    return {
      user: {
        name: 'Jack',
        id: 1
      }
    }
  }
}

const CompA = {
  mixins: [Mixin],
  data() {
    return {
      user: {
        id: 2
      }
    }
  }
}

在vue2中生成的$data:

{
  "user": {
    "id": 2,
    "name": "Jack"
  }
}

而vue3只是浅层合并:

{
  "user": {
    "id": 2
  }
}

新增选项:emits

  • 和props选项类似,props接收父组件传来的数据,emits接收组件可触发的事件:
  

父组件

  • 和props一样,emits也可以接收一个对象,允许配置事件验证
// 对象语法
app.component('reply-form', {
  emits: {
    // 没有验证函数
    click: null,

    // 带有验证函数
    submit: payload => {
      if (payload.email && payload.password) {
        return true
      } else {
        console.warn(`Invalid submit event payload!`)
        return false
      }
    }
  }
})
  • Vue强烈建议使用使用emits记录每个组件所触发的所有事件,因为Vue3移除了.native修饰符,没有用emits记录的事件会监听器会被放到组件的$attrs,且默认绑定到组件的根元素。
  • 可以使用inheritAttrs:false,让事件还是绑定在设置监听器的元素本身上

异步组件

在vue2中,异步组件通过一个返回promise的函数来创建:

Vue.component('async-component', () => import('./async-componnet.vue'))

在vue3中,异步组件需要通过defineAsynComponent定义:

import { defineAsyncComponent } from 'vue'
app.component('async-component', defineAsyncComponent(()=>import('./async-componnet.vue'))) 

app.component('async-component', defineAsyncComponent(()=>{
    return new Promise((resolve,reject)=>{
        setTimeout(()=>{                                    //模拟异步
            resolve({
                template:`
这是一个异步组件
` }) },3000) }) }))

组件用带选项的写法时,component应该改为loader:

import {defineAsyncComponent} from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'

const asyncComponent =  {
  loader: defineAsyncComponent(()=>import('./async-componnet.vue')),
  delay: 200,
  timeout: 3000,
  errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
}

Suspense

Suspense可以将异步组件提升到组件树中,当异步请求未完成时显示加载中页面,在异步请求完成后即显示该异步组件内容,简化了自己手写处理异步逻辑、显示不同页面的步骤。

用法:

  • 在模板中,用将想显示的内容包裹
  • 有两个插槽:default和fallback,只能接收一个直接子节点。在default插槽里放置异步组件

  
  

  
  

asyncComponent应当在setup中返回一个resolve了结果的promise

export default defineComponent({
  setup(){
      return new Promise((resolve,reject)=>{
       axios.get('...').then((rawData)=>{
         resolve({result:rawData})
       })
     })
   }
  
  
  //或者使用 async await,async函数返回值会被Promise.resolve包装成一个期约对象
  async setup(){
    const rawData = await axios.get('https://apiblog.jspang.com/default/getGirl') 
    return {result:rawData.data}
  }
})

Teleport

一些组件,如占满全屏的对话框,如果包裹在其他组件中,就变得容易被干扰,因为样式也将包裹在其它组件中,定位容易变得混乱。
对于这些特殊的组件,既想存在于父组件的逻辑中,又想独立于父组件从而更方便某些操作,就可以使用Teleport瞬移组件:teleport组件允许将组件挂载在任意DOM上。

使用:
假如一个组件,想挂载到id为modal的元素上,则需要被包裹,且要用to属性指明挂载点

//modal组件 modal.vue

// app.vue


模板

删除过滤器

在vue2中可以使用过滤器对模板中的数据进行一些格式化:

{{ message | capitalize }}
export default{
  //...
  filters:{
    capitalize(val){
      if(!val) return ''
      return val.toLowerCase()
    }
  }
})

在vue3中删除了过滤器语法,可以用计算属性、方法调用替换。

片段

在vue2中,