尤大大在今年春节时发布了Vite 2.0版本,为了紧跟尤大大的脚步,同时也是对前端主流框架之一的Vue.js的3.0版本的学习。
Vue3肯定是为了弥补上个版本的一些不足。但相比于Vue2,Vue3到底更新了什么呢?它有哪些新的特性?它相对于Vue2的优势是什么?Vue3如何进行使用?
本文就将带着大家提前体验一下Vue3,以上的问题也会一一得到解答,大家也可以从中感受一下Vue3的魅力。
新特性
Compossition API
setup 函数是 Compsition API 的入口函数,我们的变量、方法都是在该函数里定义的
<template>
<p>{{ count }}</p>
<p>{{msg}}-------{{doubuleConter}}</p>
<p ref="desc"></p>
</template>
<script>
//从VUe中引入函数
import {ref, computed, onMounted, onUnmounted, reactive, toRefs, watch } from 'vue'
export default{
setup(){
//Count相关逻辑
let {count,doubuleConter} = getCount()
//ref声明单值响应式
const msg = ref('message')
//ref也可对页面同值的ref元素引用
const desc = ref(null)
//监听
watch(count,(val,oldval)=>{
const p = desc.value
p.textContent = `值从${oldval}to${val}`
})
return {
count,
doubuleConter,
desc,
msg
}
}
}
function getCount(){
//reactive 可以创建响应式的对象
const state = reactive({
count: 0 ,
//computed 计算属性
doubuleConter:computed(()=>state.count * 2)
})
let timer
onMounted(()=>{
timer = setInterval(()=>{
state.count++
},1000)
})
onUnmounted(()=>{
clearInterval(timer)
})
//toRefs可以对响应式的对象进行解构
return toRefs(state)
}
</script>
Teleport
传送门组件提供一种简洁的方式可以指定它里面内容的父元素。
<template>
<div>
<button @click="modalOpen = true">打开模态框</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
我是一个模态框 挂载在body下面
<button @click="modalOpen = false">关闭</button>
</div>
</div>
</teleport>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const modalOpen = ref(false);
return { modalOpen };
},
};
</script>
<style scoped>
.modal {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.modal div {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: white;
width: 300px;
height: 300px;
padding: 5px;
}
</style>
Fragments
vue3中组件可以拥有多个根
<template>
<header>...</header>
<main>...</main>
<footer>...</footer>
</template>
- Emits Compontent Option
vue3中组件的发送的自定义事件需要在emits选项中:- 与原生事件重名会触发两次,比如click
- 更好的指示组价工作方式
- 对象形式事件校验
<template>
<div @click="$emit('click')">
<p>自定义事件</P>
</div>
</template>
<script>
export default{
emits:['click']
}
</script>
自定义渲染器 Custom render
vue3中支持自定义渲染器(render):这个API可以用来自定义渲染逻辑。
createRender API用于创建自定义渲染器
破坏性变化
Global API改为应用程序实例调用
vue2中有很多全局api可以改变vue的行为,比如Vue.component等。这导致一些问题:
- vue2中没有app概念,new Vue()得到的根实例被作为app,这样的话所有创建的根实例是共享相同的全局配置,这在测试时会污染其他测试用例,导致测试变得困难
- 全局配置也导致没有办法在单页面创建不同全局配置的多个app实例。
vue3使用createApp返回app实例,由它暴露一系列全局api
import { createApp }from "vue";
//实例方法自定义全局组件comp
const app = createApp({})
.component("comp", { render: () => h("div", "i am comp") })
.mount("#app");
2.x Global API | 3.x Instance API(app) |
---|---|
Vue.config | app.config |
Vue.config.productionTip | 移除 |
Vue.config.ignoredElements | app.config.isCustomElement |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.filter | 移除 |
Global and internal APIs重构可做摇树优化
vue2中不少global-api是作为静态函数直接挂在构造函数上的,例如Vue.nextTick(),如果我们从未在代码中使用过他们,就会形成所谓的dead code,这类的global-api造成的dead code无法使用Webpack的tree-shaking排除掉。
import Vue from 'vue'
Vue.nextTick(()=>{
//something
})
vue3中做了相应的变化,将他们抽取成为独立函数,这样打包工具的摇树优化可以将这些dead code排除掉。
import { nextTick }from 'vue'
nextTick(()=>{
//something
})
受影响的api:
- Vue.nextTick
- Vue.observable
- Vue.version
- Vue.compile
- Vue.set
- Vue.delete
v-model使用变化
model选项和v-bind的sync修饰符被移除,统一为v-model参数形式。vue2中.sync和v-model功能有重叠,容易混淆,vue3做了统一.。
//父组件
<div>
<p>{{ data }}</p>
<comp v-model="data"></comp>
<!-- 或者 -->
<comp v-model:modelValue="data"></comp>
</div>
//子组件
<template>
<div @click="$emit('update:modelValue', modelValue + 1)">
data:{{ modelValue }}
</div>
</template>
<script>
export default {
props: {
modelValue: {
type: Number,
default: 0,
},
},
};
</script>
子组件中的model选项移除
渲染函数API修改
渲染函数变得更简单好用了,主要有以下几点修改:
- 不再传入h函数,需要手动导入
- 拍平的props结构
- scopedSlot删掉了,统一到slots
函数式组件仅能通过简单函数方式创建
函数式组件的变化主要有以下几点:
- 性能提升在vue3中可忽略不计,所以vue3中推荐使用状态组件。
- 函数时组件仅能通过纯函数形式声明,接受props 和 context 两个参数。
- SFC中
<template>
不能添加functional
特性声明函数时组件。 {functional:true}
组件选项移除。
<script>
import {h} from 'vue'
// 函数式组件
function Heading(props, context) {
console.log(context);
return h(`h${props.level}`, context.attrs, context.slots);
}
Heading.props = ["level"]
export default Heading
</script>
异步组件要求使用defineAsyncComponent方式创建
由于vue3中函数式组件必须定义为纯函数,异步组件定义是有如下变化:
- 必须明确使用defineAsyncComponent包裹
- component选项重命名为loader
- Loader函数不在接受resolve and reject 且必须返回一个Promise
定义一个异步组件,loader选项是以前的compontent
import { defineAsyncComponent } from 'vue'
const asyncPage = defineAsyncComponent({
loader:()=>import('./NextPage.vue')),
delay:200,
timeout:3000,
errorComponent:ErrorCompontent,
loadingCompontent:LoadingCompontent
}
组件data选项应该总是声明为函数
vue3中data选项统一为函数形式,返回响应式数据。
createApp({
data(){
return {
msg:'message'
}
}
}).mount('#app')
自定义组件白名单
vue3中自定义元素检测发生在模板编译时,如果要添加vue之外的自定义元素,需要在编译器选项中设置isCustomElement选项。
使用构建工具时,模板都会用vue-loader预编译,在vite.config.js中配置它提供的vueCompilerOption即可:
export default defineConfig({
vueCompilerOption:{
isCustomElement:tag=>tag === 'plastic-button'
}
})
is属性仅限于用在component标签上
vue3中设置动态组件时,is属性仅能用于compontent标签上
<compentent is="comp"></compentent >
dom内的模板解析使用v-is代替
<table>
<tr v-is="'comp'"></tr>
</table >
$scopedSlots属性被移除,都用¥slots代替
vue3中统一普通插槽和作用域插槽到$slots,具体变化如下:
- 插槽均以函数形式暴露
- $scopedSlot移除
函数形式访问插槽内容:
<script>
import {h} from 'vue'
export default {
props: {
to: {
type: String,
required: true,
},
},
render() {
return h("a", { href: this.to }, this.$slots.default());
},
};
</script>
注意修改$slots,xx 为 $slots.xx()
自定义指令API和组件一致
vue3中指令api和组件保持一致:
- bind —> beforeMount
- inserted —> mounted
- beforeUpdate:new! 元素自身更新前调用,和组件生命周期钩子很像
- update —> removed! 和update基本相同因此被移除,使用update代替。
- componentUpdate —> undated
- beforeUnmount new!和组件生命周期钩子相似,元素将要被移除之前调用。
- unbind —> unmounted
<p v-highlight="'yellow'">自定义指令</p>
<script>
createApp(App).directive('highlight',{
beforeMount(el,binding,vnode){
el.style.background = binding.value
}
}).mount('#app')
</script>
一些transition类名更改:
- v-enter —> v-enter-from
- v-leave —> v-leave-from
<template>
<div id="demo">
<button @click="show = !show">Toggle</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
</template>
<script>
export default {
data() {
return {
show: true,
};
},
};
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>
watch和$watch
watch选项和$watch不在支持 . 分隔符字符串表达式,使用计算属性作为其参数
this.$watch(() => this.foo.bar,(v1,v2) => {
console.log(this.foo.bar)
})
其他小改变
- 来自 mixin 的 data 选项现在为浅合并
<TransitionGroup>
不再默认渲染包裹元素- 当侦听一个数组时,只有当数组被替换时,回调才会触发,如果需要在变更时触发,则需要指定 deep 选项
- 没有特殊指令的标记 (v-if/else-if/else、v-for 或 v-slot) 的
<template>
现在被视为普通元素,并将生成原生的<template>
元素,而不是渲染其内部内容。 - 在 Vue 2.x 中,应用根容器的 outerHTML 将替换为根组件模板 (如果根组件没有模板/渲染选项,则最终编译为模板)。Vue 3.x 现在使用应用容器的 innerHTML,这意味着容器本身不再被视为模板的一部分。
移除API
- 移除keyCode作为v-on修饰符
vue2中可以使用keyCode指代某个按键,vue3不在支持
<!-- keycodee方式不再支持,可读性太差 -->
<input v-on:keyup.13="submit">
<!-- 只能使用alias方式 -->
<input v-on:keyup.enter="submit">
- $on , $off and $once移除
上述三个方法被移除,可以使用第三方库mitt实现
//创建emitter
const emitter = mitt()
//发送事件
emitter.emit('foo','fooooo')
//监听事件
emitter.on('foo',msg => console.log(msg))
- Filter移除
- 内联模板 attributes移除