Vue.js前端框架系统学习(11)——在组合式API中使用provide/inject
本文接着上一篇 Vue.js前端框架系统学习(9)——Provider/Inject 继续进行整理分析。在组合式API中使用provide/inject,也就是我们要解决的响应式的问题。首先我们需要学习几个概念。
组合式API
通过创建 Vue 组件,我们可以将界面中重复的部分连同其功能一起提取为可重用的代码段。仅此一项就可以使我们的应用在可维护性和灵活性方面走得更远。然而,我们的经验已经证明,光靠这一点可能是不够的,尤其是当你的应用变得非常大的时候——想想几百个组件。当处理这样的大型应用时,共享和重用代码变得尤为重要。
具体的例子可以点此查看。通俗的说,就是当组件变得很大的时候,会导致组件内容庞大,难以阅读和理解,从而加重了理解和维护的比重。所以才出现了组合式API这一概念。
setup
组件选项
在Vue组件中,把使用组合式API的位置称为setup。
新的 setup
组件选项在创建组件之前执行,一旦 props
被解析,就作为组合式 API 的入口点。
注意!!
由于在执行setup
时,组件实例尚未被创建,因此在setup
选项中没有this
。这意味着,除了props
之外,你将无法访问组件中声明的任何属性——本地状态、计算属性或方法。
setup
选项应该是一个接受 props
和 context
的函数。我们从 setup
返回的所有内容都将暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。
带 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
为我们的值创建了一个响应式引用。在整个组合式 API 中会经常使用引用的概念。
回到我们的例子,让我们创建一个响应式的 repositories
变量:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'
// 在我们的组件中
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,
required: true
}
},
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
方法中,它们彼此非常接近。剩下的就是在 mounted
钩子中调用 getUserRepositories
,并设置一个监听器,以便在 user
prop 发生变化时执行此操作。
个人分析理解:
该功能的目的很明确,就是当用户更改时同时刷新仓库的信息,也就是说仓库信息与用户信息是绑定在一块的。所以我们需要使用setup
组件作为组合式API的入口,同时setup
的执行要先于组件实例的创建,所以没法使用this,即我们无法访问除了props
之外的属性。创建完入口后,我们通过创建一个ref
函数声明响应式变量,使得该变量能在任何地方起作用。对应上面的 const repositories = ref([])
,将仓库信息声明为响应式变量,然后根据不同用户给这个变量赋值。
生命周期钩子注册在setup内部
为了使组合式 API 的功能比选项式 API 更加完整,我们还需要一种在 setup
中注册生命周期钩子的方法。这要归功于从 Vue 导出的几个新函数。组合式 API 上的生命周期钩子与选项式 API 的名称相同,但前缀为 on
:即 mounted
会看起来像 onMounted
。
这些函数接受一个回调,当钩子被组件调用时,该回调将被执行。
让我们将其添加到 setup
函数中:
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'
// 在我们的组件中
setup (props) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user)
}
onMounted(getUserRepositories) // 在 `mounted` 时调用 `getUserRepositories`
return {
repositories,
getUserRepositories
}
}
组合式API
组合式API上的生命周期钩子方法也就是在生命周期钩子前加上“on”的前缀即可。
这些生命周期钩子注册函数只能在 setup()
]期间同步使用,因为它们依赖于内部全局状态来定位当前活动实例 (此时正在调用其 setup()
的组件实例)。在没有当前活动实例的情况下调用它们将导致错误。
组件实例上下文也是在生命周期钩子的同步执行期间设置的,因此在生命周期钩子内同步创建的侦听器和计算属性也会在组件卸载时自动删除。
下表包含如何在 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 |
因为
setup
是围绕beforeCreate
和created
生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在setup
函数中编写。
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)
})
每当 counter
被修改时,例如 counter.value=5
,侦听将触发并执行回调 (第二个参数),在本例中,它将把 'The new counter value is:5'
记录到我们的控制台中。
现在我们将其应用到我们的示例中:
// 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 所做的变化做出反应。
有了这些变化,我们就把第一个逻辑关注点移到了一个地方。
参考资料
1.生命周期钩子。
2.组合式API。
3.介绍:什么是组合式API。