Vue3新特性&重大改变(部分)
1. 新特性
1.1. Composition API
在Vue2中,开发者通过使用Vue Option API,将组件逻辑分布在不同的组件options当中(data,computed,methods,watch等)。当组件变得 越来越大,组件的职责也越来越多,我们不得不将这些职责的代码拆分在组件options当中,假如你想了解某一个职责,阅读代码时需要在不同 options之间“跳跃”。这严重降低了代码的可读性和可维护性。
Vue3带来了Composition API来解决这个问题,允许开发者根据代码职责来组织代码。
1.1.1. setup 组件选项
通过setup组件选项,我们可以声明一个函数,它会在组件被创建前执行,它是composition api的入口。
setup接受两个参数:
- props
- context
- 一个普通js对象
- 暴露了3个组件属性
// MyBook.vue
export default {
setup(props, context) {
// Attributes (Non-reactive object)
console.log(context.attrs)
// Slots (Non-reactive object)
console.log(context.slots)
// Emit Events (Method)
console.log(context.emit)
}
}
setup返回一个对象,此对象可以被组件访问
实例
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
// in our component
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
return {
repositories, // 返回的reactive变量能够直接在模版中以及其他组件选项中访问
getUserRepositories // 返回的函数相当于methods
}
}
*由于setup在执行时组件尚未创建,无法访问this。除了props,剩下任何组件选项都无法访问。
1.1.2. 使用ref声明响应式变量
在setup当中,我们可以通过ref来声明响应式变量:
实例
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
// in our component
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories
}
}
当我们调用getUserRepositories后,repositories会被改变,并相应更新模版。
*setup内部读取/修改响应式变量时,需要通过value属性。
1.1.3. setup中注册生命周期钩子
Vue提供了注册生命周期钩子的接口。on+钩子名
*不需要beforeCreate和created生命周期钩子!因为setup的执行时机与这两个钩子类似,不需要专门声明这两个钩子。
实例
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'
// in our component
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`
return {
repositories,
getUserRepositories
}
}
1.1.4. setup中声明watcher
Vue也提供了注册watcher的接口。
实例
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
watch接受3个参数:
- 一个响应式引用或一个getter函数
// watching a getter
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// directly watching a ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
- 回调
- 配置对象(可选)
- deep
- immediate
- flush
1.1.5. setup中声明computed函数
computed返回一个read-only的响应式引用:
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
1.2. Teleport
用于将组件模版添加到DOM树的任意位置。
最常见的例子就是全屏的modal。当某个嵌套较深的子组件中存在一个全屏modal时,该modal不得不依据离自己最近的position不为static的父 组件进行定位,然而我们的预期是modal基于body进行定位。 Vue2的解决办法是在mounted钩子中手动将modal对应的DOM元素插入到body,但在Vue3中我们可以利用Teleport特性,优雅地将指定模版添加到 DOM树的任意位置。
app.component('modal-button', {
template: `
<button @click="modalOpen = true">
Open full screen modal! (With teleport!)
</button>
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">Close</button>
</div>
</div>
</teleport>
`,
data() {
return {
modalOpen: false
}
}
})
利用teleport标签告诉Vue,将以下模版编译为的DOM元素“传送”到body元素下。
结果就是modal将会成为body的子元素。
1.2.1. 用teleport包裹一个Vue组件
当teleport中包含Vue组件时,虽然该组件会被“传送”到DOM树的其他位置,但该组件仍然会和当前组件保持逻辑父子组件的关系!
1.2.2. 多个teleport指向同一个目标
被传送的元素会按照teleport声明的顺序,被依次渲染到目标元素下:
<teleport to="#modals">
<div>A</div>
</teleport>
<teleport to="#modals">
<div>B</div>
</teleport>
<!-- result-->
<div id="modals">
<div>A</div>
<div>B</div>
</div>
1.2.3. API
teleport标签接受2个props:
// 必须是有效的query selector
to: {
type: String,
required: true,
}
// 用于禁用传送功能,包裹的元素会在原地渲染。允许我们通过判断某个条件来开启传送
disabled: {
type: Boolean,
required: false,
}
1.3. Fragments
在Vue2中,组件只能声明一个根节点,否则会报错,通常我们会使用一个额外的div去包裹所有子组件,然而在Vue3中,组件支持多个根节点!
1.3.1. Vue2语法
<!-- Layout.vue -->
<template>
<div>
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template>
1.3.2. Vue3语法
<!-- Layout.vue -->
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
1.4. Emits Component Option
类似props option,Vue3加入了emits option,用于在组件中声明该组件会触发的事件信息,主要有两个目的:
1.方便开发者了解当前组件支持的事件,一目了然
*当emits option中声明了native event时,将会使用组件触发的事件,而不是native event
app.component('custom-form', {
emits: ['inFocus', 'submit']
})
2.支持对事件参数进行校验
app.component('custom-form', {
emits: {
// No validation
click: null,
// Validate submit event
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
methods: {
submitForm() {
this.$emit('submit', { email, password })
}
}
})
1.5. SFC style
1.5.1. 深度伪元素
Vue2中的深度选择器(>>> 和 /deep/)被废弃 我们迎来了新的深度选择器:
<style scoped>
/* deep selectors */
::v-deep(.foo) {}
/* shorthand */
:deep(.foo) {}
</style>
1.5.2. 插槽伪元素
父元素通过slot传人的元素默认不会受子元素样式的影响,但如果子元素希望修改slot元素,可以利用新的::v-slotted伪元素来选择:
<style scoped>
/* targeting slot content */
::v-slotted(.foo) {}
/* shorthand */
:slotted(.foo) {}
</style>
1.5.3. 全局伪元素
我们可以通过新的::v-global()伪元素,在任意组件的标签中声明全局样式:
<style scoped>
/* one-off global rule */
::v-global(.foo) {}
/* shorthand */
:global(.foo) {}
</style>
2. 重大改变(部分)
2.1. 模版指令
2.1.1. v-model
2.1.1.1. 改动
我们知道v-model是一个语法糖,在Vue2中的转化关系为:
<ChildComponent v-model="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
在Vue3中,对默认的props名称做了修改,转化关系为 :
<ChildComponent v-model="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
2.1.1.2. 改动
Vue2中我们可以利用.sync modifier,允许子组件通知父组件修改props:
// 子组件
this.$emit('update:title', newValue)
<!-- 父组件 -->
<ChildComponent :title.sync="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
Vue3中废弃了.sync modifier,但允许我们传递一个参数给v-model,转化关系为: *这里的title是prop name,pageTitle是需要绑定的数据 *当我们声明了参数后效果与.sync相同
<ChildComponent v-model:title="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
2.1.1.3. 改动
Vue2中提供了几种v-model的modifier,例如trim、number等,Vue3支持我们声明自定义modifier
2.2. key
当v-for声明在template上时,key也声明在template上(而不是在子元素上)
2.3. v-if和v-for在相同元素上的使用
在Vue2中,当v-if与v-for在相同元素上使用时,v-for具有更高的优先级
在Vue3中,当v-if与v-for在相同元素上使用时,v-if具有更高的优先级
**最好避免将v-if与v-for在相同元素上使用
2.4. v-bind=“object”的优先级
在Vue2中,单独绑定的prop具有更高的优先级:
<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="red"></div>
在Vue3中,props声明的顺序将影响props合并的结果:
<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="blue"></div>
<!-- template -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- result -->
<div id="red"></div>
2.5. v-on:event.native modifier被移除
在Vue2中,.native modifier用于监听浏览器原生事件,而不是组件通过emit触发的事件:
<my-component
v-on:close="handleComponentEvent"
v-on:click.native="handleNativeClickEvent"
/>
在Vue3中,因为组件添加了emit option,当我们没有在emit option中声明某个事件,则事件监听器默认监听原生事件:
<!-- 子组件 -->
<script>
export default {
emits: ['close']
}
</script>
<!-- 父组件 -->
<my-component
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
2.6. v-for中的ref
在Vue2中,当ref属性与v-for指令同时出现时,相应的this.$refs.elementList属性为一个包含多个ref的数组:
<div v-for="item in list" ref="elementList"></div>
在Vue3中,当ref属性与v-for指令同时出现时,vue不再自动生成一个包含多个ref的数组,但我们可以传递一个函数给ref属性,用于达成相 同的效果:
<div v-for="item in list" :ref="setItemRef"></div>
export default {
data() {
return {
itemRefs: []
}
},
methods: {
setItemRef(el) {
if (el) {
this.itemRefs.push(el)
}
}
},
beforeUpdate() {
this.itemRefs = []
},
updated() {
console.log(this.itemRefs)
}
}
*itemRefs不一定是数组,可以是其他数据类型