composition api
composition api为vue应用提供更好的逻辑复用和代码组织。
<template>
<div>
<p>counter: {
{counter}}</p>
<p>doubleCounter: {
{doubleCounter}}</p>
<p ref="desc"></p>
</div>
</template>
<script>
import {
reactive,
computed,
watch,
ref,
toRefs,
onMounted,
onUnmounted,
} from "vue";
export default {
setup() {
const data = reactive({
counter: 1,
doubleCounter: computed(() => data.counter * 2),
});
let timer
onMounted(() => {
timer = setInterval(() => {
data.counter++
}, 1000);
})
onUnmounted(() => {
clearInterval(timer)
})
const desc = ref(null)
watch(()=>data.counter, (val,oldVal)=>{
// console.log(`counter change from ${oldVal} to ${val}`);
desc.value.textContent = `counter change from ${oldVal} to ${val}`
})
return {...toRefs(data), desc};
},
};
</script>
setup 是 Vue3.x 新增的一个选项, 他是组件内使用 Composition API的入口。
使用setup时,它接受两个参数:
- props: 组件传入的属性
- context
setup 中接受的props是响应式的, 当传入新的 props 时,会及时被更新。由于是响应式的, 所以不可以使用 ES6 解构,解构会消除它的响应式。 错误代码示例, 这段代码会让 props 不再支持响应式
// demo.vue
export default defineComponent ({
setup(props, context) {
const { name } = props
console.log(name)
},
})
setup接受的第二个参数context,context提供了最常用的三个属性:attrs、slot 和emit,分别对应 Vue2.x 中的 a t t r 属 性 、 s l o t 插 槽 和 attr属性、slot插槽 和 attr属性、slot插槽和emit发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。
生命周期
Vue3.0 新增了setup,这个在前面我们也详细说了, 然后是将 Vue2.x 中的beforeDestroy名称变更成beforeUnmount; destroyed 表更为 unmounted,作者说这么变更纯粹是为了更加语义化,因为一个组件是一个mount和unmount的过程。其他 Vue2 中的生命周期仍然保留。
上边生命周期图中并没包含全部的生命周期钩子, 还有其他的几个, 全部生命周期钩子如
beforeCreate和created被setup替换了(但是 Vue3 中你仍然可以使用, 因为 Vue3 是向下兼容的, 也就是你实际使用的是 vue2 的)。其次,钩子命名都增加了on; Vue3.x 还新增用于调试的钩子函数onRenderTriggered和onRenderTricked
import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate, onUpdated,
onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked,
onRenderTriggered} from "vue";
export default defineComponent({
// beforeCreate和created是vue2的
beforeCreate() {
console.log("------beforeCreate-----");
},
created() {
console.log("------created-----");
},
setup() {
console.log("------setup-----");
// vue3.x生命周期写在setup中
onBeforeMount(() => {
console.log("------onBeforeMount-----");
});
onMounted(() => {
console.log("------onMounted-----");
});
// 调试哪些数据发生了变化
onRenderTriggered((event) => {
console.log("------onRenderTriggered-----", event)
})
}
});
reactive、ref 与 toRefs
Vue3.x 可以使用reactive和ref来进行数据定义。
通过ref()定义,需通过xx.value来获取值。reactive不能代理基本类型,例如字符串、数字、boolean 等。
toRefs 用于将一个 reactive 对象转化为属性全部为 ref 对象的普通对象。
Teleport
传送门组件提供一种简洁的方式可以指定它里面内容的父元素。
<template>
<button @click="modalOpen = true">
弹出一个全屏模态窗口</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
这是一个模态窗口!
我的父元素是"body"!
<button @click="modalOpen = false">Close</button>
</div>
</div>
</teleport>
</template>
<script>
export default {
data() {
return {
modalOpen: true
}
},
};
</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>
<div id='teleport-box'></div>
<img alt="Vue logo" src="./assets/logo.png" />
<Bin v-model="counter" />
<Tel />
</template>
Emits Component Option
Vue 3 目前提供一个 emits选项,和现有的props选项类似。这个选项可以用来定义组件可以向其父组件触发的事件。
vue3中组件发送的自定义事件需要定义在emits选项中:
- 原生事件会触发两次,比如click
- 更好的指示组件工作方式
- 对象形式事件校验
Global API 改为应用程序实例调用
vue2中有很多全局api可以改变vue的行为,比如Vue.component等。这导致一些问题:
vue2没有app概念,new Vue()得到的根实例被作为app,这样的话所有创建的根实例是共享相同的全局配置,这在测试时会污染其他测试用例,导致测试变得困难。
全局配置也导致没有办法在单页面创建不同全局配置的多个app实例。
vue3中使用createApp返回app实例,由它暴露一系列全局api
import { createApp } from 'vue'
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 | 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 APIs重构为可做摇树优化
vue2中不少global-api是作为静态函数直接挂在构造函数上的,例如Vue.nextTick(),如果我们从未在代码中用过它们,就会形成所谓的dead code,这类global-api造成的dead code无法使用webpack的tree-shaking排除掉。
import Vue from 'vue'
Vue.nextTick(() => {
// something something DOM-related
})
vue3中做了相应的变化,将它们抽取成为独立函数,这样打包工具的摇树优化可以将这些dead code排除掉。
import { nextTick } from 'vue'
nextTick(() => {
// something something DOM-related
})
受影响api:
- Vue.nextTick
- Vue.observable (用 Vue.reactive 替换)
- Vue.version
- Vue.compile (仅完整构建版本)
- Vue.set (仅兼容构建版本)
- Vue.delete (仅兼容构建版本)
v-model 参数
默认情况下,组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件。我们可以通过向 v-model 传递参数来修改这些名称:
<my-component v-model:title="bookTitle"></my-component>
子组件将需要一个 title prop 并发出 update:title 要同步的事件:
app.component('my-component', {
props: {
title: String
},
emits: ['update:title'],
template: `
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)">
`
})
多个 v-model 绑定
<user-name
v-model:first-name="firstName"
v-model:last-name="lastName"
></user-name>
app.component('user-name', {
props: {
firstName: String,
lastName: String
},
emits: ['update:firstName', 'update:lastName'],
template: `
<input
type="text"
:value="firstName"
@input="$emit('update:firstName', $event.target.value)">
<input
type="text"
:value="lastName"
@input="$emit('update:lastName', $event.target.value)">
`
})
model选项和v-bind的sync 修饰符被移除,统一为v-model参数形式
vue2中.sync和v-model功能有重叠,容易混淆,vue3做了统一。
<div id="app">
<h3>{
{data}}</h3>
<comp v-model="data"></comp>
</div>
app.component('comp', {
template: `
<div @click="$emit('update:modelValue', 'new value')">
i am comp, {
{modelValue}}
</div>
`,
props: ['modelValue'],
})
渲染函数API修改
渲染函数变得更简单好用了,修改主要有以下几点:
不再传入h函数,需要我们手动导入;拍平的props结构。scopedSlots删掉了,统一到slots
import {h} from 'vue'
render() {
const emit = this.$emit
const onclick = this.onclick
return h('div', [
h('div', {
onClick() {
emit('update:modelValue', 'new value')
}},
`i am comp, ${this.modelValue}`
),
h('button', {
onClick(){
onclick()
}},
'buty it!'
)
])
},
异步组件要求使用defineAsyncComponent 方法创建
由于vue3中函数式组件必须定义为纯函数,异步组件定义时有如下变化:
- 必须明确使用defineAsyncComponent包裹
- component 选项重命名为 loader
- Loader 函数不在接收 resolve and reject 且必须返回一个Promise
import { defineAsyncComponent } from 'vue'
// 不带配置的异步组件
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))
带配置的异步组件,loader选项是以前的component
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'
// 待配置的异步组件
const asyncPageWithOptions = defineAsyncComponent({
loader: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
// 加载异步组件时要使用的组件
loadingComponent: LoadingComponent,
// 加载失败时要使用的组件
errorComponent: ErrorComponent,
})
自定义指令API和组件保持一致
vue3中指令api和组件保持一致,具体表现在:
- bind → beforeMount
- inserted → mounted
- beforeUpdate: new! 元素自身更新前调用, 和组件生命周期钩子很像
- update → removed! 和updated基本相同,因此被移除之,使用updated代替。
- componentUpdated → updated
- beforeUnmount new! 和组件生命周期钩子相似, 元素将要被移除之前调用。
- unbind → unmounted
const app = Vue.createApp({})
app.directive('highlight', {
beforeMount(el, binding, vnode) {
el.style.background = binding.value
}
})
<p v-highlight="yellow">Highlight this text bright yellow</p>
组件watch选项和实例方法$watch不再支持点分隔符字符串路径
以.分割的表达式不再被watch和watch支持,可以使用计算函数作为watch支持,可以使用计算函数作为watch参数实现。
this.$watch(() => this.foo.bar, (v1, v2) => {
console.log(this.foo.bar)
})
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提供,因此被移除了,可以使用其他三方库实现。
<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>
// 创建emitter
const emitter = mitt()
// 发送事件
emitter.emit('foo', 'foooooooo')
// 监听事件
emitter.on('foo', msg => console.log(msg))
Filters移除
vue3中移除了过滤器,请调用方法或者计算属性代替。
keyCode方式不再被支持 -->
### on,off and $once 移除
上述3个方法被认为不应该由vue提供,因此被移除了,可以使用其他三方库实现。
// 创建emitter
const emitter = mitt()
// 发送事件
emitter.emit(‘foo’, ‘foooooooo’)
// 监听事件
emitter.on(‘foo’, msg => console.log(msg))
### Filters移除
vue3中移除了过滤器,请调用方法或者计算属性代替。