[译] 一张图弄明白 Vuex 里该存放什么样的数据

12,834 阅读7分钟

原文: markus.oberlehner.net/blog/should…

大多数人刚上手 Vuex 的时候,首先都想知道,应该往其中存放什么样的数据呢?在对这个问题给出答案的过程中,很多人(包括我)先是来到了“一股脑放进去”的阶段。但是在遭遇了首次障碍后,你很快就会领悟到:这可不是在 Vue.js 应用中管理数据的完美方案啊。

在本文中我将尝试回答诸如“Vuex 在何种情景下是个称手的解决方案”,以及“何时用其他方式更好些”的这类问题。

I. 首先,为何使用 Vuex ?

Vue.js 为我们提供了响应式的 data 属性 -- 这是一种开箱即用的处理状态的强大方式,也能向子组件中传递数据。

export default {
  name: 'MyComponent',
  data() {
    return {
      someValue: 'Hello World',
    };
  },
}
<template>
  <div class="MyComponent">
    <some-component :some-value="someValue"></some-component>
  </div>
</template

如果在开发一个相当简单的应用,或者你要做的全部事情就是利用 Vue.js 的某些魔力来替换应用的一些(原本是服务端渲染)部分,那么你确实根本用不着 Vuex。

反之,如果要开发一个大体量的单页应用,你就可能遇到在应用中的两处迥然不同的地方需要同样一份数据的状况。这就是像 Vuex 这样的集中式状态管理工具时不时起到大作用的时候了。

II. 把数据存入 Vuex 的理由

那么把数据存入一个集中式的 Vuex store 中有哪些理由呢?

2-1. 数据对多个(独立的)组件来说必须是可访问的

把数据放在 Vuex 这种集中式 store 里面的第一个用例,那就是,因为数据必须被应用中的多个地方访问到,而这些地方很可能是毫不相干(并不是父组件子组件那么简单)的若干组件。一个例子是用某些自定义设置项去配置应用的外观或在具体的某处应该使用什么日期格式。

2-2. 集中式的 API / 数据获取逻辑

我们还是搬出久经考验的 To-Do 应用作为例子:你要从一个 API 中请求得到包含所有 To-Do 项的列表,又要按时间排序显示所有项目,也有页面是只显示其中的特定分类的。借助 Vuex,你可以只获取一次全部 To-Do 项并存储在 store 中,然后在应用中的每个组件中访问这些数据,哪怕它们分布在不同路由中也行。另一种方法是当用户导航到特定分类的路由时再请求特定的 To-Do 项;根据应用的性质,这可能也说得通。

2-3. 客户端的持久化应用状态

感谢 vuex-persistedstate 这样的 Vue.js 插件,在浏览器中用 Vuex 管理持久化状态变得非常容易了。这使得处理用户保持离线这样的复杂状况变得简单。

III. 不把数据存入 Vuex 的理由

如果你已经决定了使用 Vuex 管理应用中的状态,那么每次增加一个新组件,你就得做一次是否将它的状态存入 Vuex 的判断。如果你是 Vuex 的新手,很可能会禁不住用 Vuex 做所有的事 -- 既然有这个玩意,为啥不用呢?

3-1. 复杂性

尽管 Vuex 比其同类工具更简单些,但相比于直接使用组件本地的状态还是太麻烦了。你要评估其额外的复杂度和集中式状态带来的好处哪个更值得。

3-2. 维护成本

在组件中使用 Vuex 总是意味着有维护成本的。基于此,我推荐你将使用组件的本地状态作为默认项,而只在有充分理由时才选择性的用 Vuex。

IV. Vuex 之外的存储数据替代方案

既然说 Vuex 有那么些的缺点,那么当我们判断其并非最佳方案时有哪些替代品呢?

4-1. 向下传递的 props

往往最简单的方法就是最好的方法。如果能用从父组件向子组件传递的 props 解决问题,你就绝对应该那么干。

4-2. provide / inject

一个少有人知的 Vue.js 特性是 provide / inject。它用于需要从一个祖先组件向其所有子孙组件传递数据的场景。

官方文档中的基础示例:

// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// 子孙组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

一个典型的例子是 accordion 组件,可能由一个主要的 AppAccordion 组件、表示每个折叠项的若干 AppAccordionItem 子组件,及表示折叠项主体的 AppAccordionBody 孙组件组成。provide / inject 使得从主组件向孙组件传递数据成为可能。在各级组件直接互相依赖的情形下(AppAccordionBody 在脱离 AppAccordion 组件的情况下无法使用),这种模式比起使用 Vuex 来简单又高效。

4-3. 从 API / Apollo 获取数据

让我们回顾一个 2-2 中提及的 Vuex 正面例证:有着多个分类的 To-Do 应用。其实相比于一次性获取并存储一个用户所有(未完成的)To-Do 项,更好的一种实现可能是只获取开头的 20 条用于入口页面的渲染。若用户导航到了特定分类页面,则触发一次新的请求,以从 API 中获取对应分类的开头 20 条。如果用户访问了之前打开过的分类,我们既可以重新请求一次新鲜的数据,也可以实现某种缓存(Apollo 就提供了开箱即用的缓存机制)。

译注:GraphQL 是由 Facebook 创造的用于描述复杂数据模型的一种查询语言,是一种用于前后端数据查询方式的规范。Apollo 是基于 GraphQL 的全栈解决方案集合。从后端到前端提供了对应的 lib 使得开发使用 GraphQL 更加的方便;一个可用于 Vue 的插件是 vue-apollo.netlify.com/

4-4. portals

乍一看,PortalVue 插件貌似和状态管理怎么也扯不上关系。但有些状况下一个 portal 可以直接访问组件的状态,而不用通过集中式的 store。一种典型的例子可能是个 modal 对话框,用来确认用户不是误触了删除按钮:

<template>
  <button class="AppDeleteButton" @click="modal = true">
    删除
    <portal to="modals" v-if="modal">
      <app-modal>
        你可想好了啊?
        <button @click="delete(item.id)"></button>
        <button>算了</button>
      </app-modal>
    </portal>
  </button>
</template

可见当 AppDeleteButton 组件被点击时,就显示其包含的 modal。相比于不使用 ProtalVue 插件时要分离书写按钮和弹窗并通过 store 全局访问 id 数据,例子中这种方式就能直接在 model 组件中访问 AppDeleteButton 的内部属性值了。

V. 你们要的图

为了便于决策,将以上内容总结为下图:

VI. 总结

记住在软件开发中没有放之四海而皆准的完全之策。每件事都有各自的情景,很多文章中的某种技术能在特定情况下工作良好,但对于你的特殊用例可能也玩不转。

要对新的(也包括旧的)处事方法保持开放的胸襟,也不要惧怕尝试 -- 即便在应用中共享状态的某些方法并不适用,至少你也学到了何时不去用它,并且任何时候都是重构代码的好时候。



--End--

搜索 fewelife 关注公众号

转载请注明出处