Vue 3 引入了 Composition API,此后它席卷了整个社区。在我看来,Composition API 的唯一最佳特性是能够将反应状态和功能提取到它们自己的可重用模块或“可组合”中。
那么,什么是 Vue.js Composable?您几乎可以将可组合物视为选项 API 的混合 API 等价物。它们提供了一种定义与任何特定组件分离的反应式数据和逻辑的方法。不仅如此,他们还做得更好……好多了。此外,他们还做得更多。
让我们先看看可组合和混合是如何相似的。就像 mixin 一样,可组合允许我们提取反应性数据、方法和计算属性,并轻松地在多个组件之间重用它们。
如果可组合物和混合器具有相同的目的,那么当我们已经有了混合器时,为什么还要引入可组合物呢?2个原因:
Mixins = 数据源模糊
使用 mixin 最终会掩盖反应性数据和方法的来源,尤其是当多个 mixin 用于单个组件或一个 mixin 已在全球注册时。
//MyComponent.vue import ProductMixin from './ProductMixin' import BrandMixin from './BrandMixin' import UserMixin from './UserMixin' export default{ mixins:[ProductMixin, BrandMixin, UserMixin], created(){ // Where in the world did name come from? // Let me look through each of the registered mixins to find out // Oh man, it's not in any of them... // It must be from a globally registered mixin console.log(this.site) // Oh, I got lucky here turns out the first mixin I inspected has the name console.log(this.name) } }
Composables = 数据和函数的透明来源
然而,使用可组合物,我们可以准确地知道我们的可重用数据和函数来自哪里。那是因为我们必须导入可组合项,然后显式地使用解构来获取我们的数据和函数。
//MyComponent.vue import useProduct from './useProduct' import useBrand from './useBrand' import useUser from './useUser' export default{ setup(){ const { name } = useProduct() return { name } } created(){ // Where in the world did name come from? // ah, it's not in setup anywhere... this doesn't exist and is an error console.log(this.site) // Oh, nice I can see directly in setup this came from useProduct console.log(this.name) } }
Mixins = 命名冲突的风险
使用上面相同的 mixin 示例,如果 2 个 mixin 实际定义了一个name
数据属性会怎样?结果将是来自最后一个 mixin 的数据将获胜,而任何其他 mixin 中的数据将丢失。
//MyComponent.vue import ProductMixin from './ProductMixin' // name = AirMax import BrandMixin from './BrandMixin' // name = Nike import UserMixin from './UserMixin' // name = John Doe export default{ mixins:[ProductMixin, BrandMixin, UserMixin], created(){ // Actually I'm not so lucky, // yeah I found the name in ProductMixin // but turns out UserMixin had a name too console.log(this.name) // John Doe } }
Composables = 没有命名冲突的风险
然而,对于可组合物,情况并非如此。Composables 可以公开具有相同名称的数据或函数,但是消费组件可以随意重命名这些变量。
//MyComponent.vue import useProduct from './useProduct' // name = AirMax import useBrand from './useBrand' // name = Nike import useUser from './useUser' // name = John Doe export default{ setup(){ const { name: productName } = useProduct() const { name: brandName } = useBrand() const { name: userName } = useUser() return { productName, brandName, userName } } created(){ // Yay! Nothing is lost and I can get the name of each of the things // together in my component but when writing the composables // I don't have to think at all about what variable names might collide // with names in other composables console.log(this.productName) console.log(this.brandName) console.log(this.userName) } }
通常我们希望可重用模块(mixin 或可组合的)能够直接更改某些反应性数据的值,而无需将这种能力授予消费组件。
Mixins = 不能保护自己的反应性数据
采取RequestMixin
的例子。
// RequestMixin.js export default { data(){ return { loading: false, payload: null } }, methods:{ async makeRequest(url){ this.loading = true const res = await fetch(url) this.payload = await res.json() this.loading = false } } }
在这种情况下,我们可能不希望消费组件随意更改loading
or的值payload
。但是,使用 mixin 这是不可能的。Mixin 没有保护数据的机制。
Composables = 可以保护自己的反应性数据
现在将其与编写为可组合的相同逻辑进行比较。
// useRequest.js import { readonly, ref } from "vue"; export default () => { // data const loading = ref(false); const payload = ref(null); // methods const makeRequest = async (url) => { loading.value = true; const res = await fetch(url); payload.value = await res.json(); }; // exposed return { payload: readonly(payload), //notice the readonly here loading: readonly(loading), // and here makeRequest }; };
在这个可组合中,我们可以随意更改加载和有效载荷的值,但是一旦我们将它们暴露给任何消费组件,我们就会使它们只读。好甜!
可组合的最后一项能力是 mixin 所没有的,非常酷。也许我最喜欢的部分之一是它真的很简单。使用 mixins 定义的所有数据将始终为使用它的每个新组件实例重置。
//CounterMixins.js export default{ data(){ return { count: 0 } }, methods:{ increment(){ this.count ++ } } }
对于上述 mixin,每个组件的计数将始终从 0 开始,并且使用 mixin 增加组件中的计数不会增加使用 mixin 的另一个组件中的计数。
我们可以使用可组合实现相同的功能。
//useCounter.js import {ref, readonly} from 'vue' export default () => { const count = ref(0) const increment = ()=> count.value++ return { count: readonly(count), increment } }
很多时候,这是期望的行为。然而有时,我们希望反应式数据在所有组件之间同步,并且更像是在 Vuex 之类的东西中定义的全局状态。
我们如何使用可组合来做到这一点?通过一个简单的换行。
//useCounter.js import {ref, readonly} from 'vue' const count = ref(0) export default () => { const increment = ()=> count.value++ return { count: readonly(count), increment } }
您看得出来差别吗?我们所做的只是将 的定义移到count
导出函数之外。现在每次调用 increment 时,无论它是从哪个组件调用的,它都引用相同的 count 变量,因为 count 是在导出函数的范围之外定义的。
有许多问题可以用来解决。例如,您可以有一个useAuthUser
可组合的或useCart
可组合的。基本上,您可以将此技术用于在整个应用程序中应该是全局的任何数据。
总之,可组合的意图通常与 mixin 的意图相同:能够从组件中提取可重用的逻辑以实现可重用性。然而,在实践中,mixin 最终会达不到要求,而可组合物却能出色地完成这项工作。