在这个系列中,我们已经探索了各种官方和DIY的商店解决方案。在这个系列的最后,我们将看一下使用Vue 3的组成API的家庭卷的解决方案。
在我们继续之前,如果你还没有读过本系列的前几篇文章,我鼓励你回去至少看一下文章1.什么是商店和2.Vuex, the Official Vue.js Store,因为我们在这篇文章中不会过多地讨论原理,而是专注于实现。
安装和设置
与Vuex或Pinea不同的是,组合API与Vue 3一起发货,因此没有外部软件包需要安装和设置。如果你使用Vue 2,你可以安装composition-api Vue插件。
商店的定义
用composition API定义一个商店,可以有很多不同的方法。由于它是一个家庭卷的解决方案,它真的由你来决定你想要的具体结构,但重要的部分是你的状态被传递给reactive
方法,这样你的状态是反应性的。事实上,reactive
是Vue 3中重命名的等同于Vue.observable
的方法,以避免与RxJS的观察变量相混淆。
作为reactive
的替代品,你可以使用ref
,但在这种情况下我更喜欢reactive
。ref
通常用于原始值,如字符串或布尔,而reactive
用于对象。由于我们已经习惯于把状态看作是一个单一的实体,所以在一个状态对象下引用存储空间中的所有状态是很好的,因此我更喜欢reactive
。使用reactive
也意味着我在引用任何一个状态时不必使用.value
,这对我来说感觉更直观。
现在,我们可以只导出一个匿名函数,返回一个空对象。稍后,我们将具体说明我们从模块中导出哪些状态和动作。
// store/loggedInUser.js
import {reactive} from 'vue'
// state
const state = reactive({})
export default ()=>({})
复制代码
然后,在组件中访问商店,现在就像从商店中导入并调用导出的函数一样简单。
//AppComponent.vue
<script>
import useLoggedInUser from "./store/loggedInUser.js";
export default{
setup(){
const loggedInUser = useLoggedInUser()
}
}
</script>
复制代码
状态
现在我们已经定义了我们的商店,但它有点悲哀,因为它没有跟上任何状态。没有状态,商店就什么都不是,所以让我们添加一些。我们可以通过向传递给reactive
的对象添加属性来做到这一点。就这样,我们的状态就被反应出来了!
// store/loggedInUser.js
import {reactive} from 'vue'
const state = reactive({
name: 'John Doe',
email: '[email protected]',
username: 'jd123',
})
复制代码
现在我们将把我们想从loggedInUser
存储器中暴露的状态传递给被输出的对象。我们可以用一个状态属性来做这件事。
// store/loggedInUser.js
export default ()=>({
state // shorthand for state:state
})
复制代码
然而,这种模式虽然简洁,但有两个缺点。
- 这意味着我们只能在消费组件的状态属性下访问状态。
- 它不能防止状态直接从消费组件中被改变。
相反,我们可以为我们想公开的状态中的每个属性创建计算属性。
// store/loggedInUser.js
import {reactive, computed} from 'vue'
const state = reactive({
name: 'John Doe',
email: '[email protected]',
username: 'jd123',
})
export default () => ({
name: computed(()=> state.name),
email: computed(()=> state.email),
username: computed(()=> state.username),
posts: computed(()=> state.posts),
})
复制代码
这种方法比较啰嗦,但它有3个好处。
- 它允许我们直接访问消费组件中的状态,而不需要嵌套到状态属性中。
- 它可以防止消费组件直接改变状态。
- 它甚至允许我们选择要暴露的状态。
另外,如果你不关心选择暴露哪些状态,你可以创建一个辅助函数,循环浏览你的所有状态,并自动从它们中创建计算属性。
// helper.js
import {computed} from 'vue'
export const withState = (target, state)=>{
Object.keys(state).forEach(prop =>{
target[prop] = computed(()=> state[prop])
})
return target
}
复制代码
// store/loggedInUser.js
import {withState} from '../helper'
const state = reactive({
name: 'John Doe',
email: '[email protected]',
username: 'jd123',
})
export default () => withState({}, state)
复制代码
最后,我们可以在从setup函数返回loggedInUser后访问模板中的状态。
// AppComponent.vue
<template>
<h1>Hello, my name is {{name}}</h1>
</template>
<script>
import loggedInUser from "@/store/loggedInUser";
export default{
setup(){
const loggedInUser = useLoggedInUser()
return loggedInUser
}
}
</script>
复制代码
我们甚至可以在从setup函数返回loggedInUser时对它使用spread operator,如果我们需要向模板暴露任何其他变量的话。你可能会对在这里使用传播操作符犹豫不决,因为我们使用reactive
方法来定义状态。为什么呢?因为传播一个reactive
对象会破坏它的反应性。然而,注意到我们不是在传播状态,而是在传播从loggedInUser.js
模块返回的对象,其中状态被定义为计算道具。这意味着我们可以传播我们想要的一切
const somethingElse: ref('')
return {...loggedInUse, somethingElse}
复制代码
获取器
组合API不仅可以处理我们的反应式状态,而且我们还可以将其与组合API的computed
函数结合起来,轻松创建获取器。
// store/loggedInUser.js
const state = reactive({
//...
posts: ['post 1', 'post 2', 'post 3', 'post 4']
})
// getters
const postsCount = computed(() => state.posts.length)
复制代码
在定义了计算道具后,我们可以通过将其添加到导出的对象中,从商店中公开它。
// store/loggedInUser.js
export default () => withState({
postsCount
}, state)
复制代码
由于我们已经从setup函数中返回了loggedInUser,我们现在可以在模板中直接访问postCount。
// AppComponent.vue
<template>
<h1>Hello, my name is {{name}}</h1>
<p>I have {{ postsCount }} posts available.</p>
</template>
<script>
import loggedInUser from "@/store/loggedInUser";
export default{
setup(){
const loggedInUser = useLoggedInUser()
return loggedInUser
}
}
</script>
复制代码
行动
有了组合API,你当然会想定义动作来改变你的商店的状态。你可以采取很多方法来定义动作,但重要的是你要提供这些动作,以便为修改商店的状态提供一个可控的接口。
有了组合API,在定义动作时绝对没有新的概念需要学习。它们只是函数。它们不期望有任何特殊的参数,可以定义任何需要的参数,以直观的界面做出一个动作。另外,访问状态就像引用状态变量一样简单。
// store/loggedInUser.js
const state = reactive({
//...
posts: ['post 1', 'post 2', 'post 3', 'post 4']
})
//...
const insertPost = (post) => state.posts.push(post)
复制代码
为了暴露动作,我们只需要把它添加到导出的对象中。
// store/loggedInUser.js
export default () => withState({
//..
insertPost
}, state)
复制代码
然后在你的组件中,动作可以作为loggedInUser
上的一个方法,或者直接在模板中使用,因为我们已经从设置方法中返回了loggedInUser
。
// AppComponent.vue
<template>
<!-- ... -->
<input v-model="post" type="text" />
<button @click="insertPost(post)">Save</button>
</template>
<script>
import loggedInUser from "@/store/loggedInUser";
export default{
setup(){
const loggedInUser = useLoggedInUser()
const post = ref('')
return {...loggedInUser, post}
}
}
</script>
复制代码
仅仅阅读上面的代码,你可能感觉不到有什么不同,但当真正把组合API存储解决方案付诸实践时,动作感觉更自然,因为它们只是普通的Javascript函数。不需要调度或提交任何东西。
在模块中进行组织
按照目前的方向,我们可以像Pinia那样对待我们的商店:默认为模块化。我们可以为每个商店或模块创建新的特定域文件,只在需要时导入它们。另外,你也可以为你的商店做一个单一的入口点,并像Vuex那样对待它,但我不认为这样做有什么真正的好处,因此我们不会在本文中进一步探讨。
Vue开发工具
就devtools而言,组成API的方法并没有提供任何特殊的解决方案。完全没有可用的时间旅行,也没有专门的面板来查看你的商店的状态。然而,尽管如此,你仍然可以在Vue DevTools的 “setup “下查看组件的状态,只要你从setup函数中返回整个东西。作为奖励,你甚至可以看到有哪些操作可以使用。
值得注意的功能
为了总结作为商店解决方案的组合API,让我们快速回顾一下它最值得注意的特点,以帮助你决定实施最适合你和你的项目的商店。
- 不需要额外的库
- 可在Vue 3或Vue 2中使用composition api插件
- 可以根据自己的喜好进行定制
- 提供了学习更多关于商店的机会,并推动了商店的边界。
- 不需要对突变进行处理
- IDE对动作的支持,因为它们只是普通的Javascript。
从另一个角度看,由于Vue组合API是家庭式的,你使用它的缺点基本上和使用Vue.observable
(见本系列最后一篇文章)是一样的。然而,由于Composition API对官方解决方案的影响,如Pinea,甚至是即将推出的Vuex版本,你至少要尝试一下,这对你自己也是一种帮助。
总结
Composition API是一个强大的工具,不仅可以在组件之间完整地共享逻辑,甚至可以成为共享状态的强大解决方案。如果你想了解更多关于使用Composition API推出自己的商店的信息,Filip Rakowski也写了一篇关于这个主题的优秀文章。
关于Vue.js中商店解决方案的系列文章到此结束。如果你错过了任何文章,请随时回过头来阅读它们