1. Teleport
Teleport (俗称传送门组件) 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下渲染了 HTML。
- Props:
to-string。
必须是有效的查询选择器或 HTMLElement (如果在浏览器环境中使用). 指定将在其中移动<teleport>内容的目标元素disabled-boolean。(可选属性)
用于禁用<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>
2. 片段
组件可以包含多个根节点
这要求开发者显式定义 attribute 应该分布在哪里
<!-- Layout.vue -->
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
3. 触发组件选项
emits
-
类型:
Array<string> | Object -
详细:
emits 可以是数组或对象,对象允许配置事件验证。
对象语法,每个 property 的值可以为
null或验证函数。 验证函数将接收传递给$emit调用的其他参数。如果this.$emit('foo',1)被调用,foo的相应验证函数将接收参数1。验证函数应返回布尔值,以表示事件参数是否有效。 -
用法:
export default {
emits: ['submit'],
created() {
this.$emit('submit')
},
}
验证抛出的事件
export default {
emits: {
// 没有验证
click: null,
// 验证 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
},
}
4. 单文件组件状态驱动的 CSS 变量
<template>
<div class="text">hello</div>
</template>
<script>
export default {
data() {
return {
color: 'red'
}
}
}
</script>
<style>
.text {
color: v-bind(color);
}
</style>
5. 单文件组件 <style scoped> 现在可以包含全局规则或只针对插槽内容的规则
<style scoped>
/* 深度选择器 */
:deep(.foo) {}
/* 插槽选择器 */
:slotted(.foo) {}
/* 全局选择器 */
:global(.foo) {}
</style>
6. 组合式 API
setup
一个组件选项,在组件被创建之前,props 被解析之后执行。它是组合式 API 的入口。
参数:
- {Data} props
- {SetupContext} context
- attrs Attribute (非响应式对象,等同于 $attrs)
- slots 插槽 (非响应式对象,等同于 $slots)
- emit 触发事件 (方法,等同于 $emit)
- expose 暴露公共 property (函数)
访问组件的 property:
执行 `setup` 时,你只能访问以下 property:
- `props`
- `attrs`
- `slots`
- `emit`
换句话说,你**将无法访问**以下组件选项:
- `data`
- `computed`
- `methods`
- `refs` (模板 ref)
注:props 是响应式的,你不能使用 ES6 解构,它会消除 prop 的响应性。
如果需要解构 prop,可以在 setup 函数中使用 toRefs 函数来完成此操作。
toRefs 可以在不丢失响应性的情况下对返回的对象进行解构/展开
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
reactive、ref
reactive
- 返回对象的响应式副本
用法:
const obj = reactive({ count: 0 })
obj.count++
console.log(obj.count) // 1
reactive 将解包所有深层的 refs,同时维持 ref 的响应性。
const count = ref(1)
const obj = reactive({ count })
// ref 会被解包
console.log(obj.count === count.value) // true
// 它会更新 `obj.count`
count.value++
console.log(count.value) // 2
console.log(obj.count) // 2
// 它也会更新 `count` ref
obj.count++
console.log(obj.count) // 3
console.log(count.value) // 3
ref
- 接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个
.valueproperty,指向该内部值。
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
对比
相同: 创建一个响应式对象
不同:
- reactive 接受入参必须是对象或者数组,而 ref 可以是对象、数组,也可以是一个单值(基本数据类型)
- 读取/赋值不一样,ref 必须从.value 属性中读取值
模板引用
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
const root = ref(null)
onMounted(() => {
// DOM 元素将在初始渲染后分配给 ref
console.log(root.value) // <div>This is a root element</div>
})
return {
root
}
}
}
</script>
computed 与 watch
computed
- 接受一个 getter 函数,并根据 getter 的返回值返回一个不可变的响应式 ref 对象。
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
- 或者,接受一个具有 get 和 set 函数的对象,用来创建可写的 ref 对象。
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: val => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
watch
- watch API 与选项式 API this.$watch (以及相应的 watch 选项) 完全等效。
参数:
- {string | Function} source
- {Function | Object} callback
- {Object} [options]
- {boolean} deep
- {boolean} immediate
- {string} flush
返回:{Function} unwatch
flush:
- 作用:flush 选项可以更好地控制回调的时间。
- 可选值: 'pre'、'post' 或 'sync'
- 默认值:'pre'
'pre': 在渲染前调用指定的回调。
‘post’: 在渲染后调用指定的回调。如果回调需要通过 $refs 访问更新的 DOM 或子组件,那么则使用该值。
'sync': 一旦值发生了变化,回调将被同步调用。(少用)
对于 'pre' 和 'post',回调使用队列进行缓冲。回调只被添加到队列中一次,即使观察值变化了多次。值的中间变化将被跳过,不会传递给回调。
1、侦听单一源
// 侦听一个 getter
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
// 直接侦听一个 ref
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
2、侦听多个源
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
与 watchEffect 相比,watch 允许我们:
- 惰性地执行副作用(即只有当被侦听的源发生变化时才执行回调);
- 更具体地说明应触发侦听器重新运行的状态;
- 访问被侦听状态的先前值和当前值。
watchEffect
- 立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
watchPostEffect
- watchEffect 的别名,带有 flush: 'post' 选项
watchSyncEffect
- watchEffect 的别名,带有 flush: 'sync' 选项。
生命周期钩子
import { onMounted, onUpdated, onUnmounted } from 'vue'
const MyComponent = {
setup() {
onMounted(() => {
console.log('mounted!')
})
onUpdated(() => {
console.log('updated!')
})
onUnmounted(() => {
console.log('unmounted!')
})
}
}
下表包含如何在 setup () 内部调用生命周期钩子:
| 选项式 API | Hook inside setup |
|---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
activated | onActivated |
deactivated | onDeactivated |
getCurrentInstance
getCurrentInstance 支持访问内部组件实例。
import { getCurrentInstance } from 'vue'
const MyComponent = {
setup() {
const internalInstance = getCurrentInstance()
internalInstance.appContext.config.globalProperties // 访问 globalProperties
}
}
7. 单文件组件组合式 API 语法糖
单文件组件<script setup>
是什么:是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖
优势:
- 简洁:更少的样板内容,更简洁的代码。
- 性能:更好的运行时性能 (其模板会被编译成与其同一作用域的渲染函数,没有任何的中间代理)。
- IDE:更好的 IDE 类型推断性能 (减少语言服务器从代码中抽离类型的工作)。
- TS:能够使用纯 Typescript 声明 props 和抛出事件。
用法:
- 任何在
<script setup>声明的顶层的绑定 (包括变量,函数声明,以及 import 引入的内容) 都能在模板中直接使用. - 使用自定义指令
defineProps声明propsdefineEmits声明emitsdefineExpose声明 要暴露出去的属性useSlots和useAttrs- 与普通的
<script>一起使用 - 使用自定义指令
注1:defineProps 和 defineEmits 都是只在 <script setup> 中才能使用的编译器宏。他们不需要导入且会随着 <script setup> 处理过程一同被编译掉。
注2:
使用 <script setup> 的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。
为了在 <script setup> 组件中明确要暴露出去的属性,使用 defineExpose 编译器宏
defineProps 和 defineEmits
<script setup>
const props = defineProps({
foo: String
})
const emit = defineEmits(['change', 'delete'])
</script>
defineExpose
<script setup>
import { ref } from 'vue'
const a = 1
const b = ref(2)
defineExpose({
a,
b
})
</script>
useSlots 和 useAttrs
<script setup>
import { useSlots, useAttrs } from 'vue'
const slots = useSlots()
const attrs = useAttrs()
</script>
使用自定义指令
限制:必须以 vNameOfDirective 的形式来命名本地自定义指令
<script setup>
const vMyDirective = {
beforeMount: (el) => {
// 在元素上做些操作
}
}
</script>
<template>
<h1 v-my-directive>This is a Heading</h1>
</template>
<script setup>
// 导入的指令同样能够工作,并且能够通过重命名来使其符合命名规范
import { myDirective as vMyDirective } from './MyDirective.js'
</script>
与普通的 <script> 一起使用
- 无法在
<script setup>声明的选项,例如 inheritAttrs 或通过插件启用的自定义的选项。 - 声明命名导出。
- 运行副作用或者创建只需要执行一次的对象。
注意:该场景下不支持使用 render 函数。请使用一个普通的 <script> 结合 setup 选项来代替。
<script>
// 普通 <script>, 在模块范围下执行(只执行一次)
runSideEffectOnce()
// 声明额外的选项
export default {
inheritAttrs: false,
customOptions: {}
}
</script>
<script setup>
// 在 setup() 作用域中执行 (对每个实例皆如此)
</script>
8. vuex
useStore
访问 State 和 Getter
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore()
return {
// 在 computed 函数中访问 state
count: computed(() => store.state.count),
// 在 computed 函数中访问 getter
double: computed(() => store.getters.double)
}
}
}
访问 Mutation 和 Action
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore()
return {
// 使用 mutation
increment: () => store.commit('increment'),
// 使用 action
asyncIncrement: () => store.dispatch('asyncIncrement')
}
}
}
9. vue-router
路由和当前路由:useRouter, useRoute
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
const router = useRouter()
const route = useRoute()
function pushWithQuery(query) {
router.push({
name: 'search',
query: {
...route.query,
},
})
}
},
}
导航守卫:onBeforeRouteLeave, onBeforeRouteUpdate
import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
export default {
setup() {
// 与 beforeRouteLeave 相同,无法访问 `this`
onBeforeRouteLeave((to, from) => {
const answer = window.confirm(
'Do you really want to leave? you have unsaved changes!'
)
// 取消导航并停留在同一页面上
if (!answer) return false
})
const userData = ref()
// 与 beforeRouteUpdate 相同,无法访问 `this`
onBeforeRouteUpdate(async (to, from) => {
//仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改
if (to.params.id !== from.params.id) {
userData.value = await fetchUser(to.params.id)
}
})
},
}
10. 其他变更
组件上 v-model 用法已更改,以替换 v-bind.sync
非兼容: 用于自定义组件时,v-model prop 和事件默认名称已更改:
- prop:value -> modelValue;
- 事件:input -> update:modelValue;
非兼容: v-bind 的 .sync 修饰符和组件的 model 选项已移除,可在 v-model 上加一个参数代替;
新增: 现在可以在同一个组件上使用多个 v-model 绑定;
新增: 现在可以自定义 v-model 修饰符。
vue 2.0: v-model 默认会利用名为 value 的 prop 和名为 input 的事件.
<my-component v-model="bookTitle"></my-component>
<!-- 是以下的简写: -->
<my-component :value="bookTitle" @input="bookTitle = $event"></my-component>
Vue.component('my-component', {
model: {
prop: 'title',
event: 'change'
},
props: {
title: String
},
template: `
<input
type="text"
:value="title"
@input="$emit('change', $event.target.value)">
`
})
<my-component :title.sync="bookTitle"></my-component>
<!-- 是以下的简写: -->
<my-component :title="bookTitle" @update:title="bookTitle = $event"></my-component>
vue 3.0: 默认情况下,组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件。
<my-component v-model:title="bookTitle"></my-component>
<!-- 是以下的简写: -->
<my-component
:title="bookTitle"
@update:title="bookTitle = $event"
></my-component
app.component('my-component', {
props: {
title: String
},
emits: ['update:title'],
template: `
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)">
`
})
<template v-for> 和非 v-for 节点上的 key 用法已更改
<template>可设置key;v-if/v-else/v-else-if不建议设置key,会自动生成唯一的key