如何使用Vue.obsable(Vue 2)的首页滚动商店

111 阅读8分钟

现在我们已经看到了一些实现Vue.js商店的库选项(VuexPinea),我想花一点时间来谈谈一些家庭滚动的解决方案。虽然自家的解决方案可能更脆弱(如果匆忙完成的话),测试更少,标准化程度更低,但它们也提供了一个有用的定制机会,以及学习更多关于商店和挑战极限的机会。

首先,让我们为所有仍在使用Vue 2的人谈谈Vue.observable ,一旦我们看到这里采用的概念,那么在下一篇文章中使用Vue 3 Composition API作为商店就只是一个短暂的飞跃。

那么,我们应该从哪里开始在Vue 2中推出我们自己的商店呢?Vue 2.6暴露了一个奇妙的小方法,叫做observableVue.observable() ,这使得在Vue组件之外创建反应式数据成为可能,从而也使得有一个单一的真理(或状态)来源成为可能,可以在多个组件之间直接共享。

在我们继续之前,如果你还没有读过本系列的前几篇文章,我鼓励你回去至少看一下文章1.什么是存储2.Vuex,官方Vue.js商店,因为我们在这篇文章中不会过多地讨论原理,而是专注于实现。

安装和设置

与Vuex或Pinea不同的是,Vue.observable 是与核心Vue ^2.6.0一起提供的,因此没有外部软件包需要安装和设置。足够简单!这意味着我们可以直接进入定义。这意味着我们可以直接跳到定义我们的商店!

商店的定义

Vue.observable() 来定义一个商店,可以用很多不同的方法来处理。由于它是一个家庭卷的解决方案,它真的由你来决定你想要的结构,但重要的是,你的状态被传递到Vue.observable ,以使其反应,你的商店(或多个商店)被定义在他们自己的文件中,以使他们通过导入在各种组件中容易访问。当从其专用文件中导出商店时,我喜欢使用一个返回状态的函数,这样,一旦在组件中导入,它可以很容易地作为一个计算道具使用。

// store/loggedInUser.js
import Vue from 'vue'
const state = Vue.observable({})
export default ()=> state

如果你想获得额外的保护,确保状态永远不会被直接修改,而只是通过你的操作,你可以在从模块返回之前克隆状态。

// note both of these cloning methods have their limitations, 
// for a more comprehensive clone you can use a lib like lodash

// shallow clone
export default ()=> ({...state})
// or deep clone
export default ()=> JSON.parse(JSON.stringify(state))

虽然这段代码看起来有点低级,但你可以很容易地把它移到一个辅助函数中,在每个新创建的商店的默认输出中调用。

最后,在组件中访问商店现在就像从商店中导入它并将其设置为一个计算道具一样简单。

//AppComponent.vue
<script>
import loggedInUser from "@/store/loggedInUser";
export default{
  computed:{
    loggedInUser
  }
}
</script>

状态

现在我们已经定义了我们的商店,但它有点悲哀,因为它没有跟上任何状态。没有状态,商店就什么都不是,所以让我们添加一些。我们可以通过向传递给Vue.obsable的对象添加属性来做到这一点。就这样,我们的状态是被动的这就是Vue.observable 的强大之处!

// store/loggedInUser.js
import Vue from 'vue'
const state = Vue.observable({
  name: 'John Doe',
  email: '[email protected]', 
  username: 'jd123'
})

现在我们可以访问模板中的状态了。

// AppComponent.vue
<template>
  <h1>Hello, my name is {{loggedInUser.name}}</h1>
</template>
<script>
import loggedInUser from "@/store/loggedInUser";
export default{
  computed:{
    loggedInUser
  }
}
</script>

Learn Vue.js 3 With Vue School

这种方法非常直接,但如果你喜欢Vuex的mapState助手,你可以用几行代码来重新创建它。

// helpers/store.js
// note: this only supports accessing the desired prop via an array of strings 
export const mapState = (store, properties = [])=>{
  const computed = {}
  properties.forEach(prop => computed[prop] = ()=>store()[prop] )
  return computed
}
// AppComponent.vue
<template>
  <h1>Hello, my name is {{name}}</h1>
</template>
<script>
import loggedInUser from "@/store/loggedInUser";
import {mapState} from '@/helpers/store'
export default{
  computed:{
    ...mapState(loggedInUser, ['name']),
  }
}
</script>

获取器

Vue.observable 不仅可以处理我们的反应式状态,而且我们还可以将其与组件的计算选项结合起来,轻松创建获取器。在Vuex或Pinea中的getter只不过是一个函数,其结果可以根据它的依赖关系进行缓存,就像组件上的计算道具一样。因此,让我们创建一个依赖于某个状态的函数。

// store/loggedInUser.js
import Vue from 'vue'
const state = Vue.observable({
  name: 'John Doe',
  email: '[email protected]', 
  username: 'jd123',
  posts: ['post 1', 'post 2', 'post 3', 'post 4']
})

export const postsCount = () => state.posts.length

接下来,我们可以像导入状态那样导入它,并将其设置为一个计算道具。将其设置为计算道具不仅允许我们从模板中访问它,而且不需要使用括号,而且它还会根据它所依赖的值(它的依赖关系)自动缓存函数的结果,这些值来自于用Vue.observable 定义的状态。

// AppComponent.vue
<template>
  <h1>Hello, my name is {{ name }}</h1>
  <p>I have {{ postsCount }} posts available.</p>
</template>
<script>
import {
  default as loggedInUser,
  postsCount
} from "@/store/loggedInUser";
import {mapState} from '@/helpers/store'

export default{
  computed:{
    ...mapState(loggedInUser, ['name']),
    postsCount
  }
}
</script>

行动

通过Vue.observable ,你当然可以定义动作。如果你使用了前面描述的克隆方法,事实上,这将是你能够改变你的状态的唯一方法,不管是故意的还是意外的。你可以采取很多方法来定义动作,但重要的是你要提供这些动作,以便为修改商店的状态提供一个可控的接口。我将在这里把它们作为单独导出的函数从商店文件中暴露出来,因为这使它们很容易被分配为方法,并使动作的来源清晰。

// store/loggedInUser.js
import Vue from 'vue'
const state = Vue.observable({
  // ...
  posts: ['post 1', 'post 2', 'post 3', 'post 4']
})
export const insertPost = (post)=> state.posts.push(post)

然后在你的组件中,在导入动作后,你可以在你喜欢的地方调用它。最重要的是,由于它只是普通的Javascript,任何支持智能提示/自动完成的IDE都能更智能地使用它。

// AppComponent.vue
<template>
  <!-- ... -->
  <input v-model="post" type="text" />
  <button @click="createNewPost(post)">Save</button>
</template>
<script>
import {
  // ...
  insertPost
} from "@/store/loggedInUser";

export default{
  //...
  data(){
    return { post: '' }
  }
  methods:{ 
    createNewPost(post){
      // do whatever here you'd like, maybe validate post and then...
      insertPost(post)
    }
 }
}
</script>

如果你只需要能够从模板中调用动作,你可以通过对象属性速记法直接将其分配为一个方法。

// AppComponent.vue
<template>
  <!-- ... -->
  <input v-model="post" type="text" />
  <button @click="insertPost(post)">Save</button>
</template>
<script>
import {
  // ...
  insertPost
} from "@/store/loggedInUser";

export default{
  //...
  data(){
    return { post: '' }
  }
  methods:{ insertPost }
}
</script>

光看上面的代码,你可能感觉不到有什么不同,但当真正把Vue.observable 方法付诸实践时,动作就会感觉更自然,因为它们只是普通的Javascript函数。不需要调度或提交任何东西。

在模块中进行组织

按照我们目前的方向,我们可以像Pinia那样对待我们的商店:默认为模块化。我们可以为每个商店或模块创建新的特定域文件,只在需要时导入它们。另外,你也可以为你的商店做一个单一的入口点,并像Vuex那样对待它,但我不认为这样做有什么真正的好处,因此我们不会在本文中进一步探讨。

Vue开发工具

就开发工具而言,Vue.observable() 的方法确实提供了最少的支持。完全没有时间旅行,也没有专门的面板来查看你的商店的状态。然而,尽管如此,你仍然可以在你导入的组件的计算道具上查看状态,由于你已经导入了状态并将其分配给了计算道具,这实际上只是意味着你在一个不是专门用于商店的面板中查看状态。

值得注意的功能

为了总结Vue.observable 作为一个商店的解决方案,让我们快速回顾一下它最值得注意的特点,以帮助你决定实施最适合你和你的项目的商店。

  • 不需要额外的库
  • 可用于Vue 2 (^2.6.0)
  • 可以根据自己的喜好进行定制
  • 提供了学习更多关于商店的机会,并突破了商店的界限
  • 不需要对突变进行处理
  • IDE支持动作,因为它们只是普通的Javascript。

缺点

尽管有这么多值得注意的功能,我想如果我不提及它的一些(潜在的破坏性)缺点,那就是失职。

  • 如此精细的定制能力可能会导致混乱和分析瘫痪(甚至在写这篇文章时,我花了大量的时间来思考我可以采取的所有不同方法)
  • 如果不小心,有可能实施不当,在实际功能中可能失败。
  • 缺乏标准化和文档可能会导致团队混乱
  • 只适用于Vue ^2.6.0(没有Vue 3或早期Vue 2)。
  • 缺乏对Devtools的支持可能会很烦人

总结

虽然Vuex和Pinia提供了更多标准化的解决方案,但Vue.observable ,提供了简单性和根据你的确切需求和品味定制商店的能力。我不建议在一个有多个团队成员的大型项目中使用它,因为它缺乏标准化,但对于小型的单一开发者项目,它可以成为一个快速的轻量级解决方案,如果做得好,可以扩展并成为工作的乐趣。在下一课中,我们将继续家庭滚动商店,只是下一次使用最新和最伟大的:Vue 3 Composition API。

Learn Vue.js 3 With Vue School