Vue3的特性
Vue3 中的新增杀器 —— Composition API
Composition API : 一组低侵入式的、函数式的 API,它使我们能够更灵活地「组合」组件的逻辑。
Composition API 的灵感来自于 React Hooks ,是比 mixin 更强大的存在。它可以提高代码逻辑的可复用性,从而实现与模板的无关性;同时函数式的编程使代码的可压缩性更强。另外,把 Reactivity 模块独立开来,意味着 Vue3.0 的响应式模块可以与其他框架相组合。
如下图在较大组件的编写中, Composition-Api 可以把复杂组件的逻辑抽地更紧凑,而且可以将公共逻辑进行抽取。
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 API | 3.x Instance API |
|---|---|
| Vue.config | app.config |
| Vue.config.productionTip | removed |
| Vue.config.ignoredElements | app.config.isCustomElement |
| Vue.component | app.component |
| Vue.directive | app.directive |
| Vue.mixin | app.mixin |
| Vue.use | app.use |
| Vue.filter | removed |
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参数实现
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))