本文主要包括以下内容:
- v-model使用的变化
- 渲染函数API的变化
- 函数式组件使用变化
- 异步组件使用变化
model选项和v-bind.sync修饰符移除,统一为v-model参数形式
v-model变化
-
在vue2.0中,组件上使用
v-model相当于绑定valueprop 和input事件:<ChildComponent v-model="pageTitle" /> <!-- 等效于: --> <ChildComponent :value="pageTitle" @input="pageTitle = $event" /> -
在vue2.2中,加入model组件选项。如果将属性或事件名称改为其他名称,则需要
ChildComponent组件中添加model选项:<!-- ParentComponent.vue --> <ChildComponent v-model="pageTitle" /> <!-- 等效于: --> <ChildComponent :title="pageTitle" @change="pageTitle = $event" />// ChildComponent.vue export default { model: { prop: 'title', event: 'change' }, props: { // 这将允许 `value` 属性用于其他用途 value: String, // 使用 `title` 代替 `value` 作为 model 的 prop title: { type: String, default: 'Default title' } } }另一种方式是使用
v-bind.sync对某一个prop进行双向数据绑定,子组件可以使用
update:myPropNameemit事件将新value值传给父级:this.$emit('update:title', newValue)<ChildComponent :title.sync="pageTitle" /> <!-- 等效于: --> <ChildComponent :title="pageTitle" @update:title="pageTitle = $event" /> -
vue2中.sync和v-model功能有重叠,vue3做了统一:model选项和v-bind.sync修饰符移除,统一为v-model参数形式
自定义组件上的
v-model相当于传递了modelValueprop并接收抛出的update:modelValue事件<comp v-model="data"></comp> <!-- 等效于: --> <comp :modelValue="data" @update:modelValue="data=$event"></comp>app.component('comp', { template: ` <div @click="$emit('update:modelValue', 'new value')"> {{modelValue}} </div> `, props: ['modelValue'], })如果要绑定其他名称的prop,则可以通过传递一个argument给model:
<comp v-model:title="data"></comp> <!-- 等效于: --> <comp :title="data" @update:title="data=$event"></comp>
渲染函数API的变化
主要包括以下内容:
- 什么是
h() 3.x相较于2.x,渲染函数有哪些变化?
h函数
h()函数是一个用于创建VNode 的实用程序,更准确的应命名为createVNode(),但由于使用频繁和简洁需求,被简称为h(),函数接收三个参数:
// @returns {VNode}
h(
// {String | Object | Function | null} tag
// 一个 HTML 标签名、一个组件、一个异步组件,或者 null。
// 使用 null 将会渲染一个注释。
//
// 必需的。
'div',
// {Object} props
// 与 attribute、prop 和事件相对应的对象。
// 我们会在模板中使用。
//
// 可选的。
{},
// {String | Array | Object} children
// 子 VNodes, 使用 `h()` 构建,
// 或使用字符串获取 "文本 Vnode" 或者
// 有插槽的对象。
//
// 可选的。
[
'Some text comes first.',
h('h1', 'A headline'),
h(MyComponent, {
someProp: 'foobar'
})
]
)
渲染函数变得更加简单好用,修改主要有以下几点:
-
render函数不再传入
h参数,而是通过手动导入在
2.x中,render函数自动接收一个h函数作为参数:export default { render(h) { return h('div'); } }在
3.x中,h需要我们手动导入,而不是自动传参,而且render函数常用于setup函数内,这对于作用域内定义的响应式状态,函数以及传入setup()中的参数的访问更加便利import { h, reactive } from 'vue' export default { props: { counter: { type: Number, default: 0 } }, setup(props, {emit}) { const state = reactive({ count: props.counter }) function increment() { state.count++; emit('update:counter', state.count); } // 渲染函数 return () => h( 'div', { onClick: increment }, state.count ) } } -
扁平化的props参数结构
2.x中,domProps包含了由VNode属性构成的嵌套列表:{ class: ['color-tips'], style: { color: '#34495E' }, attrs: { title: '文本', target: '_blank', href: `www.baid.com`, }, domProps: { innerText: '内容', }, on: { click: () => { console.log('点击事件') }, }, key: 'xx' }3.x中,整体的VNode属性结构进行了扁平化处理,上述示例改写如下:{ class: ['color-tips'], style: { color: '#34495E' }, title: '文本', target: '_blank', href: `www.baid.com`, innerText: '内容', onClick: () => { console.log('点击事件') }, key: 'xx' } -
scopedSlots删掉了,统一到slots2.x通过scopedSlots获取插槽内容render() { // `<div><slot></slot></div>` return h('div', {}, this.$scopedSlots.default()) }3.x统一用slots访问静态插槽的内容,每个插槽都是一个VNode:render() { // `<div><slot></slot></div>` return h('div', {}, this.$slots.default()) }
vue3 破坏性变更-- 函数式组件的变化
函数式组件变化较大,主要有以下几点:
- 性能提升在
vue3中可以忽略不计,所以官方推荐使用状态组件 - 函数式组件仅能通过纯函数形式声明,接收
props和context(对象包含slots,attrs,emit)两个参数 非兼容变更: SFC中<template> 不能添加functional` 特性声明函数式组件非兼容变更:{functional: true}组件选项移除
2.x 中的函数式组件
在2.x 中,函数式组件主要有以下的优势:
1. 作为性能优化,函数式组件的初始化速度比有状态组件快得多
2. 返回多个根节点
3. 创建简单组件
示例:创建动态标题组件
// 函数组件 Fuctional.js
export default {
functional: true,
props: ['level'],
render(h, ctx) {
console.log('ctx', ctx);
return h(`h${ctx.props.level}`, ctx.data, ctx.children);
},
};
// 函数式组件示例使用 <template> Fuctional.vue
<template functional>
<component :is="`h${props.level}`" v-bind="attrs" v-on="listeners">
<div v-if="slots()">
<slot></slot>
</div>
</component>
</template>
<script>
export default { props: ['level'] };
</script>
// 父组件
<template>
<div>
<Functional :level="1">这是一个动态标题元素</Functional>
</div>
</template>
<script>
import Functional from '@/components/Functional';
export default { components: { Functional } };
</script>
控制台看到的效果:子组件中 ctx 为一个 FunctionalRenderContext 对象
3.x中的函数式组件
在vue3 中,有状态组件的性能提高到可以忽略不计的程度,而且状态组件还可以返回多个根节点,所以在vue2 函数式组件提到的优势荡然无存,只剩下简单组件创建的场景。
vue3中所有的函数式组件都是用普通函数创建,所以无需定义{ functional: true } 组件选项。该函数接收两个参数,props 和 context 。
此外,render 函数中不隐式提供h, 而是手动导入h。
对于SFCs 上使用functional 的迁移,只需要删除fucntional属性,将propos改为attrs,listeners 作为attr是的一部分传递,可删除
上述示例在vue3中的写法:
// 函数组件 Fuctional.js
import {h} from 'vue';
const Heading = (props, context) => {
return h(`h${props.level}`, context.attrs, context.slots)
}
Heading.props = ['level'];
export default Heading;
// 单文件组件
<template>
<component
v-bind:is="`h${$props.level}`"
v-bind="$attrs"
/>
</template>
<script>
export default {
props: ['level']
}
</script>
在控制台打印context ,结构更加清晰,包含attrs emit slots
原先函数式组件是为了性能提高而生,但vue3状态组件性能已经提高,函数式组件意义不大,vue3对函数式组件进行破坏性变化, 后续开发中,建议用状态组件编写
异步组件要求使用defineAsyncComponent方法创建
由于vue3 中函数式组件必须定义为纯函数,异步组件定义时有以下变化:
- 必须明确使用
defineAsyncComponent包裹,与函数式组件区分开来 component选项重名为loaderloader函数不在接受resolve和reject,且必须返回一个Promise
2.x 中写法:
// 不带配置
const asyncPage = () => import('./NextPage.vue')
// 带配置
const asyncPage = {
component: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
}
3.x 异步组件写法:
定义一个异步组件: defineAsyncComponent 包裹
import { definedAsyncCompnent} from 'vue';
// 不带配置
const asyncPage = definedAsyncCompnent(() => import('./NextPage.vue'))
带配置的异步组件,loader选项为之前的component选项
import { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'
const asyncPageWithOptions = defineAsyncComponent({
loader: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})
loader 函数不再接收 resolve 和 reject 参数,且必须始终返回 Promise
// 2.x 版本
const oldAsyncComponent = (resolve, reject) => {
/* ... */
}
// 3.x 版本
const asyncComponent = defineAsyncComponent(
() =>
new Promise((resolve, reject) => {
/* ... */
})
)