Docs
Vue (读音 /vjuː/,类似于view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。
Advanced Guides
Reactivity
- 深入响应性原理
- 什么是响应性?
- 响应性是一种允许我们以声明式的方式去适应变化的一种编程范例。
2. vue如何追踪变化?
- 当把一个普通的 JavaScript 对象作为
data
选项传给应用或组件实例的时候,Vue 会使用带有 getter 和 setter 的处理程序遍历其所有 property 并将其转换为Proxy。这是 ES6 仅有的特性,但是我们在 Vue 3 版本也使用了Object.defineProperty来支持 IE 浏览器。 - proxy对象
3. 侦听器
- 每个组件实例都有一个相应的侦听器实例,该实例将在组件渲染期间把“触碰”的所有 property 记录为依赖项。之后,当触发依赖项的 setter 时,它会通知侦听器,从而使得组件重新渲染。
- 响应式原理
声明响应式状态
import { reactive } from 'vue'
// 响应式状态
const state = reactive({
count: 0
})
reactive相当于 Vue 2.x 中的Vue.observable()API,该 API 返回一个响应式的对象状态。该响应式转换是“深度转换”——它会影响嵌套对象传递的所有 property。
Vue 中响应式状态的基本用例是我们可以在渲染期间使用它。因为依赖跟踪的关系,当响应式状态改变时视图会自动更新。
这就是 Vue 响应式系统的本质。当从组件中的data()返回一个对象时,它在内部交由reactive()使其成为响应式对象。模板会被编译成能够使用这些响应式 property 的渲染函数。
创建独立的响应式值作为
refs
ref
会返回一个可变的响应式对象,该对象作为它的内部值——一个响应式的引用,这就是名称的来源。此对象只包含一个名为value
的 property:
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
Ref 展开:
当 ref 作为渲染上下文 (从setup ()中返回的对象) 上的 property 返回并可以在模板中被访问时,它将自动展开为内部值。不需要在模板中追加.value
访问响应式对象:
- 当
ref
作为响应式对象的 property 被访问或更改时,为使其行为类似于普通 property,它会自动展开内部值: - 如果将新的 ref 赋值给现有 ref 的 property,将会替换旧的 ref:
- Ref 展开仅发生在被响应式
Object
嵌套的时候。当从Array
或原生集合类型如Map
访问 ref 时,不会进行展开:
响应式状态解构
当我们想使用大型响应式对象的一些 property 时,可能很想使用ES6 解构来获取我们想要的 property:
import { reactive } from 'vue'
const book = reactive({
author: 'Vue Team',
year: '2020',
title: 'Vue 3 Guide',
description: 'You are reading this book right now ;)',
price: 'free'
})
let { author, title } = book
遗憾的是,使用解构的两个 property 的响应式都会丢失。对于这种情况,我们需要将我们的响应式对象转换为一组 ref。这些 ref 将保留与源对象的响应式关联:
import { reactive, toRefs } from 'vue'
const book = reactive({
author: 'Vue Team',
year: '2020',
title: 'Vue 3 Guide',
description: 'You are reading this book right now ;)',
price: 'free'
})
let { author, title } = toRefs(book)
title.value = 'Vue 3 Detailed Guide' // 我们需要使用 .value 作为标题,现在是 ref
console.log(book.title) // 'Vue 3 Detailed Guide'
使用
readonly
防止更改响应式对象
有时我们想跟踪响应式对象 (ref
或reactive
) 的变化,但我们也希望防止在应用程序的某个位置更改它。例如,当我们有一个被provide的响应式对象时,我们不想让它在注入的时候被改变。为此,我们可以基于原始对象创建一个只读的 Proxy 对象:
import { reactive, readonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// 在copy上转换original 会触发侦听器依赖
original.count++
// 转换copy 将导失败并导致警告
copy.count++ // 警告: "Set operation on key 'count' failed: target is readonly."
- 响应式计算和侦听
计算值
有时我们需要依赖于其他状态的状态——在 Vue 中,这是用组件计算属性处理的,以直接创建计算值,我们可以使用computed
方法:它接受 getter 函数并为 getter 返回的值返回一个不可变的响应式ref对象。
const count = ref(1)
const plusOne = computed(() => count.value++)
console.log(plusOne.value) // 2
plusOne.value++ // error
或者,它可以使用一个带有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
watchEffect
为了根据反应状态自动应用和重新应用副作用,我们可以使用watchEffect
方法。它立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
const count = ref(0)
watchEffect(() => console.log(count.value))
// -> logs 0
setTimeout(() => {
count.value++
// -> logs 1
}, 100)
1.停止侦听
当watchEffect
在组件的setup()函数或生命周期钩子被调用时,侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。
在一些情况下,也可以显式调用返回值以停止侦听:
const stop = watchEffect(() => {
/* ... */
})
// later
stop()
2.清除副作用
有时副作用函数会执行一些异步的副作用,这些响应需要在其失效时清除 (即完成之前状态已改变了) 。所以侦听副作用传入的函数可以接收一个 onInvalidate
函数作入参,用来注册清理失效时的回调。当以下情况发生时,这个失效回调会被触发:
- 副作用即将重新执行时
- 侦听器被停止 (如果在
setup()
或生命周期钩子函数中使用了watchEffect
,则在组件卸载时)
watchEffect(onInvalidate => {
const token = performAsyncOperation(id.value)
onInvalidate(() => {
// id has changed or watcher is stopped.
// invalidate previously pending async operation
token.cancel()
})
})
我们之所以是通过传入一个函数去注册失效回调,而不是从回调返回它,是因为返回值对于异步错误处理很重要。
在执行数据请求时,副作用函数往往是一个异步函数:
const data = ref(null)
watchEffect(async onInvalidate => {
onInvalidate(() => {...}) // 我们在Promise解析之前注册清除函数
data.value = await fetchData(props.id)
})
我们知道异步函数都会隐式地返回一个 Promise,但是清理函数必须要在 Promise 被 resolve 之前被注册。另外,Vue 依赖这个返回的 Promise 来自动处理 Promise 链上的潜在错误。
3.副作用刷新时机
Vue 的响应式系统会缓存副作用函数,并异步地刷新它们,这样可以避免同一个“tick” 中多个状态改变导致的不必要的重复调用。在核心的具体实现中,组件的update
函数也是一个被侦听的副作用。当一个用户定义的副作用函数进入队列时,默认情况下,会在所有的组件update
前执行:
<template>
<div>{{ count }}</div>
</template>
<script>
export default {
setup() {
const count = ref(0)
watchEffect(() => {
console.log(count.value)
})
return {
count
}
}
}
</script>
在这个例子中:
count
会在初始运行时同步打印出来- 更改
count
时,将在组件更新前执行副作用。
如果需要在组件更新后重新运行侦听器副作用,我们可以传递带有flush
选项的附加options
对象 (默认为'pre'
):
// fire before component updates
watchEffect(
() => {
/* ... */
},
{
flush: 'post'
}
)
flush
选项还接受 sync
,这将强制效果始终同步触发。然而,这是低效的,应该很少需要。
4.侦听器调试
onTrack
和onTrigger
选项可用于调试侦听器的行为。
- 当响应式 property 或 ref 作为依赖项被追踪时,将调用
onTrack
- 当依赖项变更导致副作用被触发时,将调用
onTrigger
这两个回调都将接收到一个包含有关所依赖项信息的调试器事件。建议在以下回调中编写debugger
语句来检查依赖关系:
watchEffect(
() => {
/* 副作用 */
},
{
onTrigger(e) {
debugger
}
}
)
onTrack
和 onTrigger
只能在开发模式下工作。
watch
watch
API 完全等同于组件侦听器property。watch
需要侦听特定的数据源,并在回调函数中执行副作用。默认情况下,它也是惰性的,即只有当被侦听的源发生变化时才执行回调。
- 与watchEffect比较,
watch
允许我们:- 懒执行副作用;
- 更具体地说明什么状态应该触发侦听器重新运行;
- 访问侦听状态变化前后的值。
1.侦听单个数据源
侦听器数据源可以是返回值的 getter 函数,也可以直接是ref
:
// 侦听一个 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]) => {
/* ... */
})
3.与 watchEffect
共享的行为
watch
与watchEffect
共享停止侦听,清除副作用(相应地onInvalidate
会作为回调的第三个参数传入)、副作用刷新时机和侦听器调试行为。
Composition API
介绍
用组件的选项 (data
、computed
、methods
、watch
) 组织逻辑在大多数情况下都有效。然而,当我们的组件变得更大时,逻辑关注点的列表也会增长。这可能会导致组件难以阅读和理解,尤其是对于那些一开始就没有编写这些组件的人来说。

一个大型组件的示例,其中逻辑关注点是按颜色分组。
这种碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
如果我们能够将与同一个逻辑关注点相关的代码配置在一起会更好。而这正是 Composition API 使我们能够做到的。
Composition API 基础
既然我们知道了为什么,我们就可以知道怎么做。为了开始使用 Composition api,我们首先需要一个可以实际使用它的地方。在 Vue 组件中,我们将此位置称为 setup
。
setup 组件选项
新的setup
组件选项在创建组件之前执行,一旦props
被解析,并充当合成 API 的入口点。
由于在执行setup
时尚未创建组件实例,因此在setup
选项中没有this
。这意味着,除了props
之外,你将无法访问组件中声明的任何属性——本地状态、计算属性或方法。
setup
选项应该是一个接受props
和context
的函数,我们将在稍后讨论。此外,我们从setup
返回的所有内容都将暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。
// src/components/UserRepositories.vue
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
setup(props) {
console.log(props) // { user: '' }
return {} // 这里返回的任何内容都可以用于组件的其余部分
}
// 组件的“其余部分”
}
现在让我们从提取第一个逻辑关注点开始 (在原始代码段中标记为“1”)。
1.从假定的外部 API 获取该用户名的仓库,并在用户更改时刷新它
我们将从最明显的部分开始:
- 仓库列表
- 更新仓库列表的函数
- 返回列表和函数,以便其他组件选项可以访问它们
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
// 在我们的组件内
setup (props) {
let repositories = []
const getUserRepositories = async () => {
repositories = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories // 返回的函数与方法的行为相同
}
}
这是我们的出发点,但它还不能工作,因为我们的 repositories
变量不是被动的。这意味着从用户的角度来看,仓库列表将保持为空。我们来解决这个问题!
带 ref 的响应式变量
在 Vue 3.0 中,我们可以通过一个新的ref
函数使任何响应式变量在任何地方起作用,如下所示:
import { ref } from 'vue'
const counter = ref(0)
ref
接受参数并返回它包装在具有value
property 的对象中,然后可以使用该 property 访问或更改响应式变量的值:
import { ref } from 'vue'
const counter = ref(0)
console.log(counter) // { value: 0 }
console.log(counter.value) // 0
counter.value++
console.log(counter.value) // 1
在对象中包装值似乎不必要,但在 JavaScript 中保持不同数据类型的行为统一是必需的。这是因为在 JavaScript 中,Number
或 String
等基本类型是通过值传递的,而不是通过引用传递的:

在任何值周围都有一个包装器对象,这样我们就可以在整个应用程序中安全地传递它,而不必担心在某个地方失去它的响应式。
提示:换句话说,
ref
对我们的值创建了一个
响应式引用。使用
引用的概念将在整个 Composition API 中经常使用。
回到我们的例子,让我们创建一个响应式的repositories
变量:
// 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
都将发生变化,视图将更新以反映更改。我们的组件现在应该如下所示:
// src/components/UserRepositories.vue
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories
}
},
data () {
return {
filters: { ... }, // 3
searchQuery: '' // 2
}
},
computed: {
filteredRepositories () { ... }, // 3
repositoriesMatchingSearchQuery () { ... }, // 2
},
watch: {
user: 'getUserRepositories' // 1
},
methods: {
updateFilters () { ... }, // 3
},
mounted () {
this.getUserRepositories() // 1
}
}
生命周期钩子注册内部 setup
为了使 Composition API 的特性与选项 API 相比更加完整,我们还需要一种在setup
中注册生命周期钩子的方法。这要归功于从 Vue 导出的几个新函数。Composition API 上的生命周期钩子与选项 API 的名称相同,但前缀为on
:即mounted
看起来像onMounted
。
这些函数接受在组件调用钩子时将执行的回调。
让我们将其添加到 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
}
}
现在我们需要对 user
prop 所做的更改做出反应。为此,我们将使用独立的 watch
函数。
watch 响应式更改
就像我们如何使用watch
选项在组件内的user
property 上设置侦听器一样,我们也可以使用从 Vue 导入的watch
函数执行相同的操作。它接受 3 个参数:
- 一个响应式引用或我们想要侦听的 getter 函数
- 一个回调
- 可选的配置选项
下面让我们快速了解一下它是如何工作的
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
有关watch
的详细信息,请参阅我们的深入指南。
现在我们将其应用到我们的示例中:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'
// 在我们组件中
setup (props) {
// 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// 更新`prop.user ` 到 `user.value`访问引用值
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// 在用户prop的响应式引用上设置一个侦听器
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
你可能已经注意到在我们的setup
的顶部使用了toRefs
。这是为了确保我们的侦听器能够对user
prop 所做的更改做出反应。
有了这些变化,我们就把第一个逻辑关注点移到了一个地方。我们现在可以对第二个关注点执行相同的操作——基于 searchQuery
进行过滤,这次是使用计算属性。
独立的 computed
属性
与ref
和watch
类似,也可以使用从 Vue 导入的computed
函数在 Vue 组件外部创建计算属性。让我们回到我们的 counter 例子:
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
在这里,computed
函数返回一个作为computed
的第一个参数传递的 getter 类回调的输出的一个只读的响应式引用。为了访问新创建的计算变量的value,我们需要像使用ref
一样使用.value
property。
让我们将搜索功能移到 setup
中:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'
// in our component
setup (props) {
// 使用 `toRefs` 创建对props的 `user` property 的响应式引用
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// 更新`props.user ` 到 `user.value`访问引用值
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// 在用户prop的响应式引用上设置一个侦听器
watch(user, getUserRepositories)
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(
repository => repository.name.includes(searchQuery.value)
)
})
return {
repositories,
getUserRepositories,
searchQuery,
repositoriesMatchingSearchQuery
}
}
对于其他的逻辑关注点我们也可以这样做,但是你可能已经在问这个问题了——这不就是把代码移到 setup
选项并使它变得非常大吗?嗯,那是真的。这就是为什么在继续其他任务之前,我们将首先将上述代码提取到一个独立的组合函数。让我们从创建 useUserRepositories
开始:
// src/composables/useUserRepositories.js
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'
export default function useUserRepositories(user) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
然后是搜索功能:
// src/composables/useRepositoryNameSearch.js
import { ref, onMounted, watch, toRefs } from 'vue'
export default function useRepositoryNameSearch(repositories) {
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(repository => {
return repository.name.includes(searchQuery.value)
})
})
return {
searchQuery,
repositoriesMatchingSearchQuery
}
}
现在在单独的文件中有了这两个功能,我们就可以开始在组件中使用它们了。以下是如何做到这一点:
// src/components/UserRepositories.vue
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import { toRefs } from 'vue'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
setup (props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
return {
// 因为我们并不关心未经过滤的仓库
// 我们可以在 `repositories` 名称下暴露过滤后的结果
repositories: repositoriesMatchingSearchQuery,
getUserRepositories,
searchQuery,
}
},
data () {
return {
filters: { ... }, // 3
}
},
computed: {
filteredRepositories () { ... }, // 3
},
methods: {
updateFilters () { ... }, // 3
}
}
此时,你可能已经知道了这个练习,所以让我们跳到最后,迁移剩余的过滤功能。我们不需要深入了解实现细节,因为这不是本指南的重点。
// src/components/UserRepositories.vue
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
setup(props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
const {
filters,
updateFilters,
filteredRepositories
} = useRepositoryFilters(repositoriesMatchingSearchQuery)
return {
// 因为我们并不关心未经过滤的仓库
// 我们可以在 `repositories` 名称下暴露过滤后的结果
repositories: filteredRepositories,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
}
我们完成了!
请记住,我们只触及了 Composition API 的表面以及它允许我们做什么。要了解更多信息,请参阅深入指南。
Setup
参数:
使用setup
函数时,它将接受两个参数:
props
context
让我们更深入地研究如何使用每个参数。
Props
setup
函数中的第一个参数是props
。正如在一个标准组件中所期望的那样,setup
函数中的props
是响应式的,当传入新的 prop 时,它将被更新。
// MyBook.vue
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
WARNING
但是,因为props
是响应式的,你 不能使用 ES6 解构,因为它会消除 prop 的响应性。
如果需要解构 prop,可以通过使用setup
函数中的toRefs
来安全地完成此操作。
// MyBook.vue
import { toRefs } from 'vue'
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
上下文
传递给setup
函数的第二个参数是context
。context
是一个普通的 JavaScript 对象,它暴露三个组件的 property:
// MyBook.vue
export default {
setup(props, context) {
// Attribute (非响应式对象)
console.log(context.attrs)
// 插槽 (非响应式对象)
console.log(context.slots)
// 触发事件 (方法)
console.log(context.emit)
}
}
context
是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着你可以安全地对
context
使用 ES6 解构。
// MyBook.vue
export default {
setup(props, { attrs, slots, emit }) {
...
}
}
attrs
和 slots
是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x
或 slots.x
的方式引用 property。请注意,与 props
不同,attrs
和 slots
是非响应式的。如果你打算根据 attrs
或 slots
更改应用副作用,那么应该在 onUpdated
生命周期钩子中执行此操作。
访问组件的 property
执行setup
时,组件实例尚未被创建。因此,你只能访问以下 property:
props
attrs
slots
emit
换句话说,你将无法访问以下组件选项:
data
computed
methods
结合模板使用:
如果setup
返回一个对象,则可以在组件的模板中像传递给setup
的props
property 一样访问该对象的 property:
<!-- MyBook.vue -->
<template>
<div>{{ readersNumber }} {{ book.title }}</div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup() {
const readersNumber = ref(0)
const book = reactive({ title: 'Vue 3 Guide' })
// expose to template
return {
readersNumber,
book
}
}
}
</script>
注意,从setup
返回的refs在模板中访问时是被自动解开的,因此不应在模板中使用.value
。
使用渲染函数
setup
还可以返回一个渲染函数,该函数可以直接使用在同一作用域中声明的响应式状态:
// MyBook.vue
import { h, ref, reactive } from 'vue'
export default {
setup() {
const readersNumber = ref(0)
const book = reactive({ title: 'Vue 3 Guide' })
// Please note that we need to explicitly expose ref value here
return () => h('div', [readersNumber.value, book.title])
}
}
使用 this
在setup()
内部,this
不会是该活跃实例的引用,因为setup()
是在解析其它组件选项之前被调用的,所以setup()
内部的this
的行为与其它选项中的this
完全不同。这在和其它选项式 API 一起使用setup()
时可能会导致混淆。
生命周期钩子
你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。
下表包含如何在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 |
TIP
因为setup
是围绕beforeCreate
和created
生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在setup
函数中编写。
这些函数接受一个回调函数,当钩子被组件调用时将会被执行:
// MyBook.vue
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
提供/注入
我们也可以在 Composition API 中使用 provide/inject。两者都只能在当前活动实例的 setup()
期间调用。
设想场景
假设我们要重写以下代码,其中包含一个 MyMap
组件,该组件使用 Composition API 为 MyMarker
组件提供用户的位置。
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import MyMarker from './MyMarker.vue'
export default {
components: {
MyMarker
},
provide: {
location: 'North Pole',
geolocation: {
longitude: 90,
latitude: 135
}
}
}
</script>
<!-- src/components/MyMarker.vue -->
<script>
export default {
inject: ['location', 'geolocation']
}
</script>
使用 Provide
在 setup()
中使用 provide
时,我们首先从 vue
显式导入 provide
方法。这使我们能够调用 provide
时来定义每个 property。
provide
函数允许你通过两个参数定义 property:
- property 的 name (
<String>
类型) - property 的 value
使用MyMap
组件,我们提供的值可以按如下方式重构:
使用注入
在 setup()
中使用 inject
时,还需要从 vue
显式导入它。一旦我们这样做了,我们就可以调用它来定义如何将它暴露给我们的组件。
inject
函数有两个参数:
- 要注入的 property 的名称
- 一个默认的值 (可选)
使用MyMarker
组件,可以使用以下代码对其进行重构:
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
return {
userLocation,
userGeolocation
}
}
}
</script>
响应性
添加响应性
为了增加提供值和注入值之间的响应性,我们可以在提供值时使用 ref 或 reactive。
使用 MyMap
组件,我们的代码可以更新如下:
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue
export default {
components: {
MyMarker
},
setup() {
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
provide('location', location)
provide('geolocation', geolocation)
}
}
</script>
现在,如果这两个 property 中有任何更改,MyMarker
组件也将自动更新!
修改响应式 property
当使用响应式提供/注入值时,建议尽可能,在提供者内保持响应式 property 的任何更改。
例如,在需要更改用户位置的情况下,我们最好在MyMap
组件中执行此操作。
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue
export default {
components: {
MyMarker
},
setup() {
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
provide('location', location)
provide('geolocation', geolocation)
return {
location
}
},
methods: {
updateLocation() {
this.location = 'South Pole'
}
}
}
</script>
然而,有时我们需要在注入数据的组件内部更新注入的数据。在这种情况下,我们建议提供一个方法来负责改变响应式 property。
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide, reactive, ref } from 'vue'
import MyMarker from './MyMarker.vue
export default {
components: {
MyMarker
},
setup() {
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
const updateLocation = () => {
location.value = 'South Pole'
}
provide('location', location)
provide('geolocation', geolocation)
provide('updateLocation', updateLocation)
}
}
</script>
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'
export default {
setup() {
const userLocation = inject('location', 'The Universe')
const userGeolocation = inject('geolocation')
const updateUserLocation = inject('updateUserLocation')
return {
userLocation,
userGeolocation,
updateUserLocation
}
}
}
</script>
最后,如果要确保通过provide
传递的数据不会被注入的组件更改,我们建议对提供者的 property 使用readonly
。
<!-- src/components/MyMap.vue -->
<template>
<MyMarker />
</template>
<script>
import { provide, reactive, readonly, ref } from 'vue'
import MyMarker from './MyMarker.vue
export default {
components: {
MyMarker
},
setup() {
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
const updateLocation = () => {
location.value = 'South Pole'
}
provide('location', readonly(location))
provide('geolocation', readonly(geolocation))
provide('updateLocation', updateLocation)
}
}
</script>
模板引用
在使用组合 API 时,响应式引用和模板引用的概念是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样声明 ref 并从setup ()返回:
<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>这是根元素</div>
})
return {
root
}
}
}
</script>
这里我们在渲染上下文中暴露 root
,并通过 ref="root"
,将其绑定到 div 作为其 ref。在虚拟 DOM 补丁算法中,如果 VNode 的 ref
键对应于渲染上下文中的 ref,则 VNode 的相应元素或组件实例将被分配给该 ref 的值。这是在虚拟 DOM 挂载/打补丁过程中执行的,因此模板引用只会在初始渲染之后获得赋值。
作为模板使用的 ref 的行为与任何其他 ref 一样:它们是响应式的,可以传递到 (或从中返回) 复合函数中。
JSX 中的用法
export default {
setup() {
const root = ref(null)
return () =>
h('div', {
ref: root
})
// with JSX
return () => <div ref={root} />
}
}
v-for
中的用法
Composition API 模板引用在v-for
内部使用时没有特殊处理。相反,请使用函数引用执行自定义处理:
<template>
<div v-for="(item, i) in list" :ref="el => { if (el) divs[i] = el }">
{{ item }}
</div>
</template>
<script>
import { ref, reactive, onBeforeUpdate } from 'vue'
export default {
setup() {
const list = reactive([1, 2, 3])
const divs = ref([])
// 确保在每次更新之前重置ref
onBeforeUpdate(() => {
divs.value = []
})
return {
list,
divs
}
}
}
</script>
渲染机制和优化
虚拟 DOM
现在我们知道了侦听器是如何更新组件的,你可能会问这些更改最终是如何应用到 DOM 中的!也许你以前听说过虚拟 DOM,包括 Vue 在内的许多框架都使用这种方式来确保我们的接口能够有效地反映我们在 JavaScript 中更新的更改
我们用 JavaScript 复制了一个名为 Virtual Dom 的 DOM,我们这样做是因为用 JavaScript 接触 DOM 的计算成本很高。虽然用 JavaScript 执行更新很廉价,但是找到所需的 DOM 节点并用 JS 更新它们的成本很高。所以我们批处理调用,同时更改 DOM。
虚拟 DOM 是轻量级的 JavaScript 对象,由渲染函数创建。它包含三个参数:元素,带有数据的对象,prop,attr 以及更多,和一个数组。数组是我们传递子级的地方,子级也具有所有这些参数,然后它们可以具有子级,依此类推,直到我们构建完整的元素树为止。
如果需要更新列表项,可以使用前面提到的响应式在 JavaScript 中进行。然后,我们对 JavaScript 副本,虚拟 DOM 进行所有更改,并在此与实际 DOM 之间进行区分。只有这样,我们才能对已更改的内容进行更新。虚拟 DOM 允许我们对 UI 进行高效的更新!
Vue 2 中的更改检测警告
由于 JavaScript 的限制,有些 Vue 无法检测的更改类型。但是,有一些方法可以规避它们以维持响应性。
对于对象
Vue 无法检测到 property 的添加或删除。由于 Vue 在实例初始化期间执行 getter/setter 转换过程,因此必须在data
对象中存在一个 property,以便 Vue 对其进行转换并使其具有响应式。例如:
var vm = new Vue({
data: {
a: 1
}
})
// `vm.a` 现在是响应式的
vm.b = 2
// `vm.b` 不是响应式的
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用Vue.set(object,propertyName,value)
方法向嵌套对象添加响应式 property:
Vue.set(vm.someObject, 'b', 2)
你还可以使用vm.$set
实例方法,这也是全局Vue.set
方法的别名:
this.$set(this.someObject, 'b', 2)
有时你可能需要为已有对象赋值多个新 property,比如使用Object.assign()
或_.extend()
。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。
// 而不是 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
对于数组
Vue 不能检测以下数组的变动:
- 当你利用索引直接设置一个数组项时,例如:
vm.items[indexOfItem] = newValue
- 当你修改数组的长度时,例如:
vm.items.length = newLength
例如:
var vm = new Vue({
data: {
items: ['a', 'b', 'c']
}
})
vm.items[1] = 'x' // 不是响应式的
vm.items.length = 2 // 不是响应式的
为了解决第一种问题,以下两种方式都可以实现和vm.items[indexOfItem] = newValue
相同的效果,同时也将在响应性系统内触发状态更新:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
你也可以使用 vm.$set
实例方法,该方法是全局方法 Vue.set
的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
为了解决第二种问题,你可以使用splice
:
vm.items.splice(newLength)
声明响应式 property
由于 Vue 不允许动态添加根级响应式 property,所以你必须在初始化实例前声明所有根级响应式 property,哪怕只是一个空值:
var vm = new Vue({
data: {
// 声明 message 为一个空值字符串
message: ''
},
template: '<div>{{ message }}</div>'
})
// 之后设置 `message`
vm.message = 'Hello!'
如果你未在 data 选项中声明 message
,Vue 将警告你渲染函数正在试图访问不存在的 property。
这样的限制在背后是有其技术原因的,它消除了在依赖项跟踪系统中的一类边界情况,也使组件实例能更好地配合类型检查系统工作。但与此同时在代码可维护性方面也有一点重要的考虑:data
对象就像组件的状态结构 (schema)。提前声明所有的响应式 property,可以让组件代码在未来修改或给其他开发人员阅读时更易于理解。
异步更新队列
可能你还没有注意到,Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then
、MutationObserver
和 setImmediate
,如果执行环境不支持,则会采用 setTimeout(fn, 0)
代替。
例如,当你设置 vm.someData = 'new value'
,该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新。多数情况我们不需要关心这个过程,但是如果你想基于更新后的 DOM 状态来做点什么,这就可能会有些棘手。虽然 Vue.js 通常鼓励开发人员使用“数据驱动”的方式思考,避免直接接触 DOM,但是有时我们必须要这么做。为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)
。这样回调函数将在 DOM 更新完成后被调用。例如:
<div id="example">{{ message }}</div>
var vm = new Vue({
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // 更改数据
vm.$el.textContent === 'new message' // false
Vue.nextTick(function() {
vm.$el.textContent === 'new message' // true
})
在组件内使用vm.$nextTick()
实例方法特别方便,因为它不需要全局Vue
,并且回调函数中的this
将自动绑定到当前的组件实例上:
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function() {
return {
message: 'not updated'
}
},
methods: {
updateMessage: function() {
this.message = 'updated'
console.log(this.$el.textContent) // => 'not updated'
this.$nextTick(function() {
console.log(this.$el.textContent) // => 'updated'
})
}
}
})
因为$nextTick()
返回一个 Promise 对象,所以你可以使用新的ES2017 async/await语法完成相同的事情:
methods: {
updateMessage: async function () {
this.message = 'updated'
console.log(this.$el.textContent) // => 'not updated'
await this.$nextTick()
console.log(this.$el.textContent) // => 'updated'
}
}
API Reference
Application Config
errorHandler
- 类型:Function
- 默认:undefined
- 用法
app.config.errorHandler = (err, vm, info) => {
// 处理错误
// `info` 是 Vue 特定的错误信息,比如错误所在的生命周期钩子
}
指定一个处理函数,来处理组件渲染方法执行期间以及侦听器抛出的未捕获错误。这个处理函数被调用时,可获取错误信息和应用实例。
错误追踪服务 Sentry 和 Bugsnag 使用此选项提供官方集成。
warnHandler
- 类型:Function
- 默认:undefined
- 用法
app.config.warnHandler = function(msg, vm, trace) {
// `trace` 是组件的继承关系追踪
}
为 Vue 的运行时警告指定一个自定义处理函数。注意这只会在开发环境下生效,在生产环境下它会被忽略。
globalProperties
- 类型:[key: string]: any
- 默认:undefined
- 用法
app.config.globalProperties.foo = 'bar'
app.component('child-component', {
mounted() {
console.log(this.foo) // 'bar'
}
})
添加可以在应用程序内的任何组件实例中访问的全局 property。属性名冲突时,组件的 property 将具有优先权。
这可以代替 Vue 2.x Vue.prototype
扩展:
// 之前(Vue 2.x)
Vue.prototype.$http = () => {}
// 之后(Vue 3.x)
const app = Vue.createApp({})
app.config.globalProperties.$http = () => {}
isCustomElement
- 类型:(tag: string) => boolean
- 默认:undefined
- 用法
// 任何以“ion-”开头的元素都将被识别为自定义元素
app.config.isCustomElement = tag => tag.startsWith('ion-')
指定一个方法,用来识别在 Vue 之外定义的自定义元素(例如,使用 Web Components API)。如果组件符合此条件,则不需要本地或全局注册,并且 Vue 不会抛出关于Unknown custom element
的警告。
注意,所有原生 HTML 和 SVG 标记不需要在此函数中匹配——Vue 解析器自动执行此检查。
optionMergeStrategies
- 类型:{ [key: string]: Function }
- 默认:{}
- 用法
const app = Vue.createApp({
mounted() {
console.log(this.$options.hello)
}
})
app.config.optionMergeStrategies.hello = (parent, child, vm) => {
return `Hello, ${child}`
}
app.mixin({
hello: 'Vue'
})
// 'Hello, Vue
为自定义选项定义合并策略。
合并策略选项分别接收在父实例和子实例上定义的该选项的值作为第一个和第二个参数,引用上下文实例被作为第三个参数传入。
- 参考:自定义选项合并策略
performance
- 类型:boolean
- 默认:false
- 用法
设置为true
以在浏览器开发工具的 performance/timeline 面板中启用对组件初始化、编译、渲染和更新的性能追踪。只适用于开发模式和支持performance.markAPI 的浏览器。
Application API
component
- 参数:
- {string} name
- {Function | Object} [definition]
- 如果传入
definition
参数,返回应用实例。 - 如果不传入
definition
参数,返回组件定义。
注册或检索全局组件。注册还会使用给定的name
参数自动设置组件的name
。
- 示例:
import { createApp } from 'vue'
const app = createApp({})
// 注册一个选项对象
app.component('my-component', {
/* ... */
})
// 检索注册的组件(始终返回构造函数)
const MyComponent = app.component('my-component', {})
- 参考:组件基础
config
- 用法:
包含应用配置的对象。
- 示例:
import { createApp } from 'vue'
const app = createApp({})
app.config = {...}
- 参考:应用配置
directive
- 参数:
{string} name
{Function | Object} [definition]
- 如果传入
definition
参数,返回应用实例。 - 如果不传入
definition
参数,返回指令定义。
注册或检索全局指令。
- 示例:
import { createApp } from 'vue'
const app = createApp({})
// 注册
app.directive('my-directive', {
// 指令具有一组生命周期钩子:
// 在绑定元素的父组件挂载之前调用
beforeMount() {},
// 绑定元素的父组件挂载时调用
mounted() {},
// 在包含组件的 VNode 更新之前调用
beforeUpdate() {},
// 在包含组件的 VNode 及其子组件的 VNode 更新之后调用
updated() {},
// 在绑定元素的父组件卸载之前调用
beforeUnmount() {},
// 卸载绑定元素的父组件时调用
unmounted() {}
})
// 注册 (功能指令)
app.directive('my-directive', () => {
// 这将被作为 `mounted` 和 `updated` 调用
})
// getter, 如果已注册,则返回指令定义
const myDirective = app.directive('my-directive')
指令钩子传递了这些参数:
el
指令绑定到的元素。这可用于直接操作 DOM。
binding
包含以下 property 的对象。
instance
:使用指令的组件实例。value
:传递给指令的值。例如,在v-my-directive="1 + 1"
中,该值为2
。oldValue
:先前的值,仅在beforeUpdate
和updated
中可用。值是否已更改都可用。arg
:参数传递给指令 (如果有)。例如在v-my-directive:foo
中,arg 为"foo"
。modifiers
:包含修饰符 (如果有) 的对象。例如在v-my-directive.foo.bar
中,修饰符对象为{foo: true,bar: true}
。dir
:一个对象,在注册指令时作为参数传递。例如,在以下指令中
app.directive('focus', {
mounted(el) {
el.focus()
}
})
dir
将会是以下对象:
{
mounted(el) {
el.focus()
}
}
vnode
上面作为 el 参数收到的真实 DOM 元素的蓝图。
prevNode
上一个虚拟节点,仅在beforeUpdate
和updated
钩子中可用。
Note
除了el
之外,你应该将这些参数视为只读,并且永远不要修改它们。如果你需要跨钩子共享信息,建议通过元素的自定义数据属性集进行共享。
- 参考:自定义指令
mixin
- 参数:
{Object} mixin
- 返回:应用实例
- 用法:
在整个应用范围内应用混入。一旦注册,它们就可以在当前的应用中任何组件模板内使用它。插件作者可以使用此方法将自定义行为注入组件。不建议在应用代码中使用。
- 参考:全局混入
mount
- 参数:
{Element | string} rootContainer
{boolean} isHydrate
将应用实例的根组件挂载在提供的 DOM 元素上。
- 示例:
<body>
<div id="my-app"></div>
</body>
import { createApp } from 'vue'
const app = createApp({})
// 做一些必要的准备
app.mount('#my-app')
- 参考:生命周期图示
provide
- 参数:
{string | Symbol} key
value
设置一个可以被注入到应用范围内所有组件中的值。组件应该使用 inject
来接收提供的值。
从 provide
/inject
的角度来看,可以将应用程序视为根级别的祖先,而根组件是其唯一的子级。
该方法不应该与 provide 组件选项或组合式 API 中的 provide 方法混淆。虽然它们也是相同的 provide
/inject
机制的一部分,但是是用来配置组件提供的值而不是应用提供的值。
通过应用提供值在写插件时尤其有用,因为插件一般不能使用组件提供值。这是使用 globalProperties 的替代选择。
Noteprovide
和inject
绑定不是响应式的。这是有意为之。不过,如果你向下传递一个响应式对象,这个对象上的 property 会保持响应式。
- 示例:
向根组件中注入一个 property,值由应用提供。
import { createApp } from 'vue'
const app = createApp({
inject: ['user'],
template: `
<div>
{{ user }}
</div>
`
})
app.provide('user', 'administrator')
- 参考:Provide / Inject
unmount
- 参数:
{Element | string} rootContainer
- 用法:在提供的 DOM 元素上卸载应用实例的根组件。
- 示例:
<body>
<div id="my-app"></div>
</body>
import { createApp } from 'vue'
const app = createApp({})
// 做一些必要的准备
app.mount('#my-app')
// 挂载5秒后,应用将被卸载
setTimeout(() => app.unmount('#my-app'), 5000)
use
- 参数:
{Object | Function} plugin
...options (可选)
安装 Vue.js 插件。如果插件是一个对象,它必须暴露一个 install
方法。如果它本身是一个函数,它将被视为安装方法。
该安装方法将以应用实例作为第一个参数被调用。传给 use
的其他 options
参数将作为后续参数传入该安装方法。
当在同一个插件上多次调用此方法时,该插件将仅安装一次。
- 参考: 插件
Global API
createApp
返回一个提供应用上下文的应用实例。应用实例挂载的整个组件树共享同一个上下文。
const app = Vue.createApp({})
你可以在 createApp
之后链式调用其它方法,这些方法可以在应用 API 中找到。
- 参数
该函数接收一个根组件选项对象作为第一个参数:
const app = Vue.createApp({
data() {
return {
...
}
},
methods: {...},
computed: {...}
...
})
使用第二个参数,我们可以将根 prop 传递给应用程序:
const app = Vue.createApp(
{
props: ['username']
},
{ username: 'Evan' }
)
<div id="app">
<!-- 会显示 'Evan' -->
{{ username }}
</div>
- 类型声明
interface Data {
[key: string]: unknown
}
export type CreateAppFunction<HostElement> = (
rootComponent: PublicAPIComponent,
rootProps?: Data | null
) => App<HostElement>
h
返回一个”虚拟节点“,通常缩写为VNode:一个普通对象,其中包含向 Vue 描述它应在页面上渲染哪种节点的信息,包括所有子节点的描述。它的目的是用于手动编写的渲染函数:
render() {
return Vue.h('h1', {}, 'Some title')
}
- 参数
接收三个参数:type
,props
和 children
- type
- 类型:
String | Object | Function
- 详细:HTML 标签名、组件或异步组件。使用返回 null 的函数将渲染一个注释。此参数是必需的。
2. props
-
- 类型:
Object
- 详细:一个对象,与我们将在模板中使用的 attribute、prop 和事件相对应。可选。
- 类型:
3. children
-
- 类型:
String | Array | Object
- 详细:
- 类型:
子代 VNode,使用h()
生成,或者使用字符串来获取“文本 VNode”,或带有插槽的对象。可选。
h('div', {}, [
'Some text comes first.',
h('h1', 'A headline'),
h(MyComponent, {
someProp: 'foobar'
})
])
defineComponent
从实现上看,defineComponent
只返回传递给它的对象。但是,就类型而言,返回的值有一个合成类型的构造函数,用于手动渲染函数、TSX 和 IDE 工具支持。
- 参数:具有组件选项的对象
import { defineComponent } from 'vue'
const MyComponent = defineComponent({
data() {
return { count: 1 }
},
methods: {
increment() {
this.count++
}
}
})
或者是一个setup
函数,函数名称将作为组件名称来使用
import { defineComponent, ref } from 'vue'
const HelloWorld = defineComponent(function HelloWorld() {
const count = ref(0)
return { count }
})
defineAsyncComponent
创建一个只有在需要时才会加载的异步组件。
- 参数
对于基本用法,defineAsyncComponent
可以接受一个返回 Promise
的工厂函数。Promise 的 resolve
回调应该在服务端返回组件定义后被调用。你也可以调用 reject(reason)
来表示加载失败。
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
app.component('async-component', AsyncComp)
当使用局部注册时,你也可以直接提供一个返回Promise
的函数:
import { createApp, defineAsyncComponent } from 'vue'
createApp({
// ...
components: {
AsyncComponent: defineAsyncComponent(() =>
import('./components/AsyncComponent.vue')
)
}
})
对于高阶用法,defineAsyncComponent
可以接受一个对象:
defineAsyncComponent
方法还可以返回以下格式的对象:
import { defineAsyncComponent } from 'vue'
const AsyncComp = defineAsyncComponent({
// 工厂函数
loader: () => import('./Foo.vue')
// 加载异步组件时要使用的组件
loadingComponent: LoadingComponent,
// 加载失败时要使用的组件
errorComponent: ErrorComponent,
// 在显示 loadingComponent 之前的延迟 | 默认值:200(单位 ms)
delay: 200,
// 如果提供了 timeout ,并且加载组件的时间超过了设定值,将显示错误组件
// 默认值:Infinity(即永不超时,单位 ms)
timeout: 3000,
// 定义组件是否可挂起 | 默认值:true
suspensible: false,
/**
*
* @param {*} error 错误信息对象
* @param {*} retry 一个函数,用于指示当 promise 加载器 reject 时,加载器是否应该重试
* @param {*} fail 一个函数,指示加载程序结束退出
* @param {*} attempts 允许的最大重试次数
*/
onError(error, retry, fail, attempts) {
if (error.message.match(/fetch/) && attempts <= 3) {
// 请求发生错误时重试,最多可尝试 3 次
retry()
} else {
// 注意,retry/fail 就像 promise 的 resolve/reject 一样:
// 必须调用其中一个才能继续错误处理。
fail()
}
}
})
- 参考:动态和异步组件
resolveComponent
WARNINGresolveComponent
只能在render
或setup
函数中使用。
如果在当前应用实例中可用,则允许按名称解析component
。
返回一个Component
。如果没有找到,则返回undefined
。
const app = Vue.createApp({})
app.component('MyComponent', {
/* ... */
})
import { resolveComponent } from 'vue'
render() {
const MyComponent = resolveComponent('MyComponent')
}
- 参数
接受一个参数:name
- name
- 类型:
String
- 详细:已加载的组件的名称。
- 类型:
resolveDynamicComponent
WARNINGresolveDynamicComponent
只能在render
或setup
函数中使用。
允许使用与 <component :is="">
相同的机制来解析一个 component
。
返回已解析的 Component
或新创建的 VNode
,其中组件名称作为节点标签。如果找不到 Component
,将发出警告。
import { resolveDynamicComponent } from 'vue'
render () {
const MyComponent = resolveDynamicComponent('MyComponent')
}
- 参数:
接受一个参数:component
- component
- 类型:
String | Object (组件的选项对象)
- 详细:有关详细信息,请参阅动态组件上的文档。
- 类型:
resolveDirective
WARNINGresolveDirective
只能在render
或setup
函数中使用。
如果在当前应用实例中可用,则允许通过其名称解析一个directive
。
返回一个 Directive
。如果没有找到,则返回 undefined
。
const app = Vue.createApp({})
app.directive('highlight', {})
import { resolveDirective } from 'vue'
render () {
const highlightDirective = resolveDirective('highlight')
}
- 参数
接受一个参数:name
- name
- 类型:
String
- 详细:已加载的指令的名称。
- 类型:
withDirectives
WARNINGwithDirectives
只能在render
或setup
函数中使用。
允许将指令应用于 VNode。返回一个包含应用指令的 VNode。
import { withDirectives, resolveDirective } from 'vue'
const foo = resolveDirective('foo')
const bar = resolveDirective('bar')
return withDirectives(h('div'), [
[foo, this.x],
[bar, this.y]
])
- 参数
接受两个参数:vnode
和 directives
。
1. vnode
-
- 类型:
vnode
- 详细: 已加载的指令的名称。
- 类型:
2. directives
-
- 类型:Array
- 详细:
一个指令数组。
每个指令本身都是一个数组,最多可以定义 4 个索引,如以下示例所示。
[directive]
- 该指令本身。必选。
const MyDirective = resolveDirective('MyDirective')
const nodeWithDirectives = withDirectives(h('div'), [[MyDirective]])
[directive, value]
- 上述内容,再加上分配给指令的类型为any
的值。
const MyDirective = resolveDirective('MyDirective')
const nodeWithDirectives = withDirectives(h('div'), [[MyDirective, 100]])
[directive, value, arg]
- 上述内容,再加上一个string
参数,比如:在v-on:click
中的click
。
const MyDirective = resolveDirective('MyDirective')
const nodeWithDirectives = withDirectives(h('div'), [
[MyDirective, 100, 'click']
])
[directive, value, arg, modifiers]
- 上述内容,再加上定义任何修饰符的key: value
键值对Object
。
const MyDirective = resolveDirective('MyDirective')
const nodeWithDirectives = withDirectives(h('div'), [
[MyDirective, 100, 'click', { prevent: true }]
])
createRenderer
createRenderer 函数接受两个泛型参数: HostNode
和 HostElement
,对应于宿主环境中的 Node 和 Element 类型。
例如,对于 runtime-dom,HostNode 将是 DOM Node
接口,HostElement 将是 DOM Element
接口。
自定义渲染器可以传入特定于平台的类型,如下所示:
import { createRenderer } from 'vue'
const { render, createApp } = createRenderer<Node, Element>({
patchProp,
...nodeOps
})
- 参数
接受两个参数:HostNode
和 HostElement
。
- 1. HostNode
- 类型:
Node
- 详细:宿主环境中的节点。
- 类型:
Element
- 详细:宿主环境中的节点。
nextTick
将回调推迟到下一个 DOM 更新周期之后执行。在更改了一些数据以等待 DOM 更新后立即使用它。
import { createApp, nextTick } from 'vue'
const app = createApp({
setup() {
const message = ref('Hello!')
const changeMessage = async newMessage => {
message.value = newMessage
await nextTick()
console.log('Now DOM is updated')
}
}
})
提上全部内容来自于vue文档(Vue.js)
仅供自己学习!