1
头图

尤大大:理论上来说,每一个 Vue 组件实例都已经在“管理”它自己的响应式状态了

🤦‍♂️:不会吧🤡!既然Vue本身具备状态管理的能力,我们还有必要引入Pinia🍍或者Vuex等状态管理工具吗?

Vue实例作为状态管理器应该怎么实现?按照vue官网我们来实践一次。

简单状态管理 😎

状态管理器

我们以Vue3为例,实现一个状态管理。首先创建一个名为auth.ts的ts文件,这文件将用来定义状态管理器。

import { reactive, readonly } from 'vue';

export interface Account {
  name: string;
}

export interface AuthStore {
  account: Account | null;
  isAuthed: boolean;
}

const auth = reactive<AuthStore>({
  isAuthed: false,
  account: null,
});

export const useAuthStore = () => {
  return {
    state: readonly(auth),
    actions: {
      login(account: Account) {
        auth.isAuthed = true;
        auth.account = account;
      },
      logout() {
        auth.isAuthed = false;
        auth.account = null;
      },
    },
  };
};

export default useAuthStore;

接下来创建两个组件Info.vueLogin.vue,在这两个组件中使用我们自定义的useAuthStore状态管理器。

Login.vue

使用import { useAuthStore } from '../auth';来引入这个store,通过useAuthStore()获取store实例。

<script setup lang="ts">
import { ref } from 'vue';

import { useAuthStore } from '../auth';

const username = ref('');
const { state, actions } = useAuthStore();
</script>

<template>
  <div>
    <div v-if="!state.isAuthed">
      <div>
        <span>用户名:</span>
        <input v-model="username" />
      </div>
      <button @click="actions.login({ name: username })">登录</button>
    </div>
    <button v-if="state.isAuthed" @click="actions.logout">退出</button>
  </div>
</template>

Info.vue

<script setup lang="ts">
import { useAuthStore } from '../auth';

const { state } = useAuthStore();
</script>

<template>
  <div>
    <div v-if="!state.isAuthed">
      <h1>请登录</h1>
    </div>
    <div v-if="state.isAuthed">
      <h1>欢迎:{{ state.account?.name }}</h1>
    </div>
  </div>
</template>

使用效果

Kapture 2023-06-25 at 21.35.52.gif

解读

使用reactive()是因为State是一个对象,当然也可以使用ref()。但是,就必须使用.value来访问数据,这并不是想要的效果。

为了实现单向数据流useAuthStore中的State采用Vue3的readonly API将状态对象置为只读的对象,这样避免了在使用该状态对象时直接操作State的情况。因此想要修改State就只能通过Actions,就像下图这样:

image.png

Vue2也可以么?😲

虽然 Vue2 中没有reactive()ref()API,但是事实是 Vue2 也实现简单的状态管理。利用 Vue2 中的Vue.observable()可以将一个普通对象转换为响应式对象,从而实现当State变更时驱动View更新。

🤔需要注意的是 Vue2 中没有 readonly() API,因此在这个例子中,我们直接使用 auth 作为状态。要确保状态不被意外修改,你需要确保只在 actions 对象中的方法内修改状态。

import Vue from 'vue';

export interface Account {
  name: string;
}

export interface AuthStore {
  account: Account | null;
  isAuthed: boolean;
}

const auth = Vue.observable<AuthStore>({
  isAuthed: false,
  account: null,
});

export const useAuthStore = () => {
  return {
    state: auth,
    actions: {
      login(account: Account) {
        auth.isAuthed = true;
        auth.account = account;
      },
      logout() {
        auth.isAuthed = false;
        auth.account = null;
      },
    },
  };
};

export default useAuthStore;

Login.vue

在vue2中将useAuthStore()解构进组件的data中即可。

<template>
  ...
</template>

<script>
import { useAuthStore } from '../auth';

export default {
  data() {
    const { state, actions } = useAuthStore();
    return {
      authState: state,
      login: actions.login,
      logout: actions.logout,
    };
  },
};
</script>

首先从 useAuthStore 文件中导入 useAuthStore 函数。然后,在组件的 data 选项中,我们调用 useAuthStore() 并将返回的 state 和 actions 解构。接下来,我们将 statelogin 和 logout 添加到组件的响应式数据中,以便在模板中使用。最后,在模板中,我们根据 authState.isAuthed 的值显示不同的内容,并使用 login 和 logout 方法处理按钮点击事件。

关于服务器端渲染 🧐

在 SSR 环境下,应用模块通常只在服务器启动时初始化一次。同一个应用模块会在多个服务器请求之间被复用,而我们的单例状态对象也一样。如果我们用单个用户特定的数据对共享的单例状态进行修改,那么这个状态可能会意外地泄露给另一个用户的请求。我们把这种情况称为跨请求状态污染

如果使用 SSR,则需要避免所有请求共享同一存储。在这种情况下,需要为每个请求创建一个单独的存储并提供/注入它。

// app.js (在服务端和客户端间共享)
import { createSSRApp } from 'vue'
import { createStore } from './store.js'

// 每次请求时调用
export function createApp() {
  const app = createSSRApp(/* ... */)
  // 对每个请求都创建新的 store 实例
  const store = createStore(/* ... */)
  // 提供应用级别的 store
  app.provide('store', store)
  // 也为激活过程暴露出 store
  return { app, store }
}

优势与不足

优势

  • 简单易学:对于初学者和小型项目来说,这种方法更容易理解和实现。它不需要引入额外的库或学习新的概念。
  • 轻量级:由于不需要引入额外的库,这种方法在体积上更轻量级,对于那些对性能有严格要求的项目来说,这可能是一个优势。
  • 灵活性:这种方法允许开发人员根据项目需求自由地调整状态管理结构。这种灵活性可能适用于一些具有特殊需求的项目。

不足

  • 缺乏结构和约束:这种方法没有强制执行任何特定的结构或约束,这可能导致不一致的代码和难以维护的项目。当多个开发人员协同工作时,这可能会导致问题。
  • 缺乏调试工具:与像 Vuex 或 Pinia 这样的专门的状态管理库相比,这种方法没有提供调试工具,这可能会使调试和追踪状态变更更加困难。
  • 可扩展性:对于大型应用程序,这种简单的状态管理可能不够强大,因为它可能无法很好地处理复杂的状态逻辑和多个状态模块。
  • 性能优化:这种方法可能无法提供像 Vuex 或 Pinia 这样的库所提供的性能优化,例如,缓存计算属性。

🚀简单状态管理 vs Pinia🚀

开发 Vue 应用时,状态管理是一个重要的考虑因素。Vue 自身提供了一些状态管理工具,如 ref 和 reactive,但在某些情况下,引入专门的状态管理库(如 Pinia 或 Vuex)可能会带来更多的便利和优势。那么,在什么情况下你真的需要 Pinia?让我们来总结一下。

使用 Vue 自身的状态管理

在以下场景下,使用 Vue 自身的状态管理就可以完美解决问题:

  1. 当应用的规模较小,组件层级较浅时,Vue 自身的状态管理可以很好地处理状态。
  2. 当组件之间的状态共享较少,且状态变化较简单时,Vue 的响应式系统足以应对这些需求。
  3. 当应用的状态变化逻辑较为简单,易于维护时,Vue 的状态管理可以很好地解决问题。

在这些场景下,使用 Vue 自身的状态管理,如 ref 和 reactive,可以满足应用的需求,而无需引入额外的状态管理库。

这样的小型项目存在吗?

小型项目通常具有以下特点:

  1. 功能有限:项目的功能和需求相对较少,不需要复杂的状态管理。
  2. 规模较小:项目的代码量和组件数量较少,易于维护。
  3. 开发周期短:项目的开发和发布周期相对较短。
  4. 团队规模较小:负责项目的开发人员数量较少。

这些小型项目可能包括个人博客、简历网站、小型企业网站、原型和概念验证等。

何时考虑使用 Pinia

选择是否一开始就使用 Pinia 取决于项目的需求和预期的复杂性。以下是一些建议:

  1. 如果您预计项目将迅速增长并变得复杂,那么从一开始就使用 Pinia 可能是一个明智的选择。这样,您可以从一开始就利用 Pinia 提供的强大功能、更好的开发体验和更强的约定。
  2. 如果项目是一个小型项目,且预计不会变得很复杂,那么可以从简单的状态管理方法开始。这样,您可以减少项目的依赖和包大小,同时保持灵活性。然后,根据项目的发展情况,您可以在需要时迁移到 Pinia。
  3. 如果您的团队已经熟悉 Pinia 或类似的状态管理库,那么从一开始就使用 Pinia 可能会使团队更加高效。

总之,在决定是否从一开始就使用 Pinia 时,您应该权衡项目的需求、预期的复杂性和团队的经验。如果您认为 Pinia 可以为您的项目带来长期的好处,那么从一开始就使用它是合理的。

最后

没有最好的架构,只有最合适的选择。对于小型项目和初学者,简单的状态管理方法可能是一个合适的选择。然而,在大型、复杂的应用程序中,使用像 Pinia 这样的专门的状态管理库可能更加合适,因为它们提供了更强大的功能、更好的开发体验和更强的约定。

关于项目是否应该使用第三方的状态管理库,完全取决于项目自身和开发团队的选择!

如果您有不同的看法,以自身看法为准


youth君
186 声望76 粉丝

技术一般,我搞前端!