本文主要包括以下内容:
- v-model使用的变化
- 渲染函数API的变化
- 函数式组件使用变化
- 异步组件使用变化
model选项和v-bind.sync修饰符移除,统一为v-model参数形式
v-model变化
-
在vue2.0中,组件上使用
v-model
相当于绑定value
prop 和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:myPropName
emit事件将新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
相当于传递了modelValue
prop并接收抛出的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
删掉了,统一到slots
2.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
选项重名为loader
loader
函数不在接受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) => {
/* ... */
})
)
复制代码