Vue3的特性

141 阅读3分钟

Vue3的特性

Vue3 中的新增杀器 —— Composition API

Composition API : 一组低侵入式的、函数式的 API,它使我们能够更灵活地「组合」组件的逻辑。

Composition API 的灵感来自于 React Hooks ,是比 mixin 更强大的存在。它可以提高代码逻辑的可复用性,从而实现与模板的无关性;同时函数式的编程使代码的可压缩性更强。另外,把 Reactivity 模块独立开来,意味着 Vue3.0 的响应式模块可以与其他框架相组合。 如下图在较大组件的编写中, Composition-Api 可以把复杂组件的逻辑抽地更紧凑,而且可以将公共逻辑进行抽取。

image.png

Ref和reactive

ref数据响应式监听。ref 函数传入一个值作为参数,一般传入基本数据类型,返回一个基于该值的响应式Ref对象,该对象中的值一旦被改变和访问,都会被跟踪到,就像我们改写后的示例代码一样,通过修改 count.value 的值,可以触发模板的重新渲染,显示最新的值。

reactive是用来定义更加复杂的数据类型,但是定义后里面的变量取出来就不在是响应式Ref对象数据了,所以需要用toRefs函数转化为响应式数据对象

<template>
  <div>
    <h1>{{nameRef}}</h1>
    <h1>{{ageRef}}</h1>
    <button @click="sayNameRef">按钮</button>
  
    <h1>{{ name }}</h1>
    <h1>{{ age }}</h1>
    <button @click="sayName">按钮</button>
    <p ref="desc"></p>
  </div>
</template>

<script lang="ts">
import { computed, reactive, ref, toRefs, watch } from "vue";

interface DataProps {
  name: string;
  now: number;
  birthYear: number;
  age: number;
  sayName: () => void;
}

export default {
  name: "App",
  setup() {
    // ref写法
    const nameRef = ref('zhangsan')
    const birthYearRef = ref(2000)
    const nowRef = ref(2020)
    const ageRef = computed(()=>{
      return nowRef.value - birthYearRef.value
    })
    const sayNameRef = () =>{
      nameRef.value = 'I am ' + nameRef.value
    }
    
    // reactive写法
    const data: DataProps = reactive({
      name: "zhangsan",
      birthYear: 2000,
      now: 2020,
      sayName: () => {
        data.name = "I am " + data.name;
      },
      age: computed(() => {
        return data.now - data.birthYear;
      }),
    });

    const refData = toRefs(data)
    
    // 使用元素引用 (desc在上下文中存在,发现html中节点也有同名,就会把Dom元素指给desc这个变量)
    const desc = ref(null)
    // 监听参数(1、监听源 2、监听回调)
    watch(() => data.name, (val, oldVal) => {
        // 因为是ref对象
        const p = desc.value
        p.textContent = '显示的文本'
    })
    
    // 监听器
    
    return {
       nameRef,
       sayNameRef,
       ageRef,
       ...refData,
       // 起一个html中定义的ref
       desc
    };
  },
};
</script>


Teleport传送门

<template>
  <button @click="modalOpen = true">弹出一个全屏模态窗口</button>
  <teleport to="body">
    <div v-if="modalOpen" class="modal">
        <div>
        这个一个模态窗口
        <button @click="modalOpen = false">Close</button>
        </div>
    </div>
  </teleport>
</template>

<script>
export default {
    data(){
        return {
            modalOpen:false
        }
    }
}
</script>

Emits

vue3中组件发送的自定义事件需要定义在emits选项中:

  • 原生事件会触发两次,比如 click
  • 更好的指示组件工作方式
  • 对象形式事件校验(类似props),来校验事件要不要派发出去
<template>
  <div>
    <button @click="$emit('click')">OK</button>
  </div>
</template>
<script>
  export default {
    emits: ['clicks']
  }
</script>

Global Api 改为应用程序实例调用

vue2中有很多全局api可以改变vue的行为,比如Vue.component 等。这导致一些问题:

  • vue2没有app概念,new Vue()得到的根实例被作为app,这样的话所有创建的根实例是共享相同的全局配置,这在测试时会污染其他测试用例,导致测试变得困难。
  • 全局配置也导致没有办法在单页面创建不同全局配置的多个app实例。

vue3中使用createApp返回app实例,由它暴露一系列全局api

import { createApp, h } from 'vue'
const app = createApp({})
            .component('comp', { render: () => h('div','111')})
            .mount('#app')
2.x Global API3.x Instance API
Vue.configapp.config
Vue.config.productionTipremoved
Vue.config.ignoredElementsapp.config.isCustomElement
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use
Vue.filterremoved

Global and internal Api 重构为可做摇树优化

vue2中不少global-api是作为静态函数直接挂在构造函数上的,例如Vue.nextTick(),如果我们从未在代码中用过它们,就会形成所谓的dead code,这类global-api造成的dead code无法使用webpack的tree-shaking排除掉。

import Vue from 'vue'

Vue.nextTick(() => {

})

vue3中做了相应的变化,将它们抽取成为独立函数,这样打包工具的摇树优化就可以将这些dead code排除掉。

import { nextTick } from 'vue'

nextTick(() => {

})

受影响api:

  • Vue.nextTick
  • Vue.observable (replaced by Vue.reactive)
  • Vue.version
  • Vue.compile (only in full builds)
  • Vue.set (only in compat builds 浏览器不兼容proxy,回退到Object.defineProty,这样才可能有set和delete这两个方法,否则不会有)
  • Vue.delete (only in compat builds)

model选项和v-bind的sync修饰符被移除,统一为v-model参数形式

vue2中.sync和v-model功能有重叠,容易混淆,vue3做了统一。

vue2中

1.1 v-model的作用

  • 前提是props是单向数据流,我们不能直接在子组件去改变父组件传过来的props,v-model是可以方便我们来改变该值;
  • 无需在父组件去监听函数去改变v-model改变的值;
  • 父组件仍可以监听发送的事件;
  • 比较适用于表单值修改比较频繁的时候;

1.2 v-model使用方法

  • 在子组件中定义model和props
  • 通过发送在model下定义的事件名通知v-model绑定值的改变
<com1 v-model="num"></com1>
<!--它等价于-->
<com1 :value="num" @input="(val)=>this.num=val"></com1>
  model: {
    prop: 'value',
    event: 'change'//自定义改变值的事件,之后我们可以通过发送该事件通知v-model绑定值的改变
  },
  props: {
    /**
     * 价格设置值
     * @model
     */
    value: {
      type: Number,
      default: 0
    }
  },
  methods:{
    change (value) {
      //这样可以直接将父组件v-model绑定的值改变
      this.$emit('change', value * 1)
    }
  }
复制代码

2.1 .sync的作用

  • 通常我们需要通过事件机制,子组件发送一个事件,父组件监听修改值
  • .sync可以省去监听这一步

2.2 .sync的用法

  • 在父组件传props的时候后面添加一个.sync修饰符
  • 通过在子组件中发送"update:prop"事件直接更新prop
// 正常父传子: 
<com1 :a="num" :b="num2"></com1>

// 加上sync之后父传子: 
<com1 :a.sync="num" .b.sync="num2"></com1> 

// 它等价于
<com1 
  :a="num" @update:a="val=>num=val"
  :b="num2" @update:b="val=>num2=val"></com1> 

// 相当于多了一个事件监听,事件名是update:a,回调函数中,会把接收到的值赋值给属性绑定的数据项中。
复制代码
//子组件
this.$emit('update:prop', prop)
//父组件
<div :prop.sync="prop" />
//可以实现prop的联动

vue3中

简单用法

<div id="app">
    <h3>{{data}}</h3>
    <comp v-model="data"></comp>
    <!--等效于-->
    <comp :modelValue="data" @update:modelValue="data=$event"></comp>
</div>
// 自组件的model选项不复存在了
app.component('comp',{
    template: `
        <div @click="$emit('update:modelValue', 'newValue')">
        {{modelValue}}
        </div>
    `,
    props: ['modelValue'],
})

重命名用法

<div id="app">
    <h3>{{data}}</h3>
    <comp v-model:counter="data"></comp>
    <!--等效于-->
    <comp :counter="data" @update:counter="data=$event"></comp>
</div>
// 自组件的model选项不复存在了
app.component('comp',{
    template: `
        <div @click="$emit('update:counter', 'newValue')">
        {{counter}}
        </div>
    `,
    props: ['counter'],
})

函数式组件仅能通过函数方式创建,functional选项废弃

函数式组件变化较大,主要有以下几点:

  • 函数组件的性能提升在vue3中可忽略不计,所以vue3中推荐使用状态组件
  • 函数时组件仅能通过纯函数形式声明,接口props和context两个参数
  • SFC中<template> 不能添加 functional特性声明函数是组件(例如之前element组件中就是这样定义)
  • {functional: true} 组件选项移除

在vue3中声明一个函数组件

import {h} from 'vue'
const Top = (props, context) => {
    // 打印context有三个属性,分别是 attrs,emit,slots
    // h标签的特性都在context.attrs
    return h(`h${props.level}`, context.attrs,context.slots)
}
Top.props=['level']
export default Top

异步组件要求使用 defineAsyncComponent 方法创建

由于vue3中函数式组件必须为纯函数,异步组件定义时有如下变化:

  • 必须明确使用defineAsyncComponent 包裹(用于区分函数组件)
  • component 选项重命名为 loader
  • Loader 函数不在接受 resolve and reject 且必须返回一个Promise
import { defineAsyncComponent } form 'vue'
// 不带配置的异步组件
const asyncPage = defineAsyncComponent(()=> import('./about.vue'))
// 带配置的异步组件(loader选项时以前的componetn)
import ErrorCom from './ErrorCom.vue'
import LoadingCom from './LoadingCom.vue'
const asyncPage = defineAsyncComponent({
    loader:()=> import('./about.vue'),
    delay:200,
    titmeout:3000,
    errorComponent: ErrorCom,
    loadingComponent:LoadingCom
})

组件data选项应该总是声明为函数

vue3中data铉锡那个偷偷难过一位函数形式,返回响应式数据

createApp({
    data() {
        return {
            list: []
        }
    }
}).mount('#app')

自定义组件白名单

vue3中自定义元素检测发生在模版编译时,如果要添加一些vue之外的自定义元素,需要在编译器选项中设置 isCustomElelment选项。 使用构建工具时,模板都会用vue-loader预编译,设置它提供的compilerOpttions即可:

// vue.config.js
rules:[
    {
        test: /\.vue$/,
        use: 'vue-loader',
        optitons:{
            compilerOptions:{
                isCustomElement: tag => tag === 'lzx-button'
            }
        }
    }
    // ...
]


// vite.config.js
module.exports = {
    vueCompilerOptions: {
        isCustomElement: tag => tag === 'lzx-button'
    }
}

自定义指令API和组件保持一致

vue3中指令api和组件保持一致,具体表现在:

  • bind ——> beforeMount
  • insertted ——> mounted
  • beforeUpdate : 新增的api 元素自身更新前调用,和组件生命周期钩子很像
  • update : 废弃,因为和之前的componentUpdated基本相同
  • componentUpdated ——> updated
  • beforeUnmount : 新增的api 元素自身更新前调用,和组件生命周期钩子很像
  • unbind ——> unmounted

transition类名变更

  • v-enter ——> v-enter-from
  • v-leave ——> v-leave-from

组件watch选项和实例方法$watch不在支持点分隔符字符串路径

以 . 分割的表达式不再被watch和watch支持,可以使用计算函数作watch支持,可以使用计算函数作watch参数实现

this.$watch(() => this.foo.bar, (newVal,oldVal) => {

})

Vue 2.x中应用程序根容器的outerHTML会被根组件的模板替换(或被编译为template),Vue3.x现在使用根容器的innerHTML取代

如果vue2中#app的元素在vue编译完模版后会直接替换#app元素,vue3不会替换放在#app里面

keyCode 作为v-on 修饰符被移除

vue2中可以使用keyCode指代某个按键,vue3不再支持。

<!-- keyCode方式不再被支持(可读性太差) -->
<input v-on:keyup.13="submit"/>

<!-- 只能使用alias方式 -->
<input v-on:keyup.enter="submit"/>

$on,$off and $once 移除

上述3个方法被认为不应该由vue提供,因此被移除了,可以使用其他三方库实现。

1、<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>
2、npm i mitt -S

// 创建emittter
const emitter = mitt()
// 发送事件
emitter.emit('message', 'lzx')
// 监听事件
emitter.on('message', msg => console.log(msg))