分享一篇关于Vuex的入门指南(TypeScript版)

2023-11-17 13:40:36 浏览数 (2)

Vuex是Vue的一个著名的状态管理库,而TypeScript为您的代码添加了数据类型,以便检测和避免错误,因此将两者一起使用是非常合理的,本文将向您展示如何做到这一点。

Vuex是为Vue.js开发的官方状态管理库。随着应用程序的扩展和组件数量的增加,处理共享状态变得越来越具有挑战性。为了应对这种复杂性,引入了Vuex。它提供了一种统一的方法来管理和更新状态,确保变化的一致性和可追溯性。

Vuex的创建受到了其他生态系统中的状态管理模式和实践的影响,比如React社区的Flux,但它专门为了与Vue无缝集成而构建。

TypeScript本质上是在JavaScript的基础上提供了一套有益的工具。它是由微软开发的一种强类型的JavaScript超集。TypeScript引入了静态类型到JavaScript中,这意味着你可以指定一个变量只能持有某种特定的原始类型,比如字符串、布尔值、数字等。如果你给变量赋予了未指定的类型,TypeScript编译器应该会抛出一个错误。它还允许定义更复杂的类型,比如接口和枚举。

编译时类型检查还有一个重要的优势,即在编译时捕获更多的错误,而不是在运行时,这也意味着在生产中有更少的错误。大多数JavaScript库也支持并与TypeScript兼容,包括增强集成开发环境(IDE)和代码编辑器的功能,从其静态类型系统中提供信息。

TypeScript还提供其他丰富的功能,例如在集成开发环境中的自动完成,以及在悬停在变量或函数上时提供的类型信息、预期参数、返回类型等。

与TypeScript集成的IDE具有重构的额外优势。例如,当变量名发生变化时,由于TypeScript类型检查,新名称会在整个代码库中得到更新。

TypeScript改善了开发者的体验,而Vuex特别受益于使用定义的类型来塑造和结构化状态,从而提高了整体状态管理的体验。

设置环境

要将Vuex与TypeScript集成,您需要先安装Vue(如果尚未安装),然后使用以下命令创建一个新的Vue项目:

代码语言:javascript复制
npm install -g @vue/cli


# Create a new project
vue create my-vue-ts-project

您将被提示选择Vue项目所需的功能。选择“手动选择功能”选项,然后选择Vuex和TypeScript。这将自动为您的应用程序引导使用TypeScript,并即时为您初始化一个Vuex存储。

安装完成后,使用以下命令导航到您的项目:

代码语言:javascript复制
# Install Vue CLI globally
cd my-vue-ts-project

您可以在您选择的任何集成开发环境中打开新创建的文件夹。

Typescript基础

在开始使用TypeScript与Vue之前,了解一些基本的TypeScript概念是必要的。TypeScript与基本的JavaScript语法相似,但添加了额外的功能,如静态类型。这意味着变量的类型在初始化时被定义。这有助于在编码过程中防止错误。下面给出了一些基本概念的解释:

自定义类型

TypeScript使您能够定义自定义类型,您可以在应用程序中使用这些类型。这确保了您的对象严格遵循您创建的任何自定义类型。例如:

代码语言:javascript复制
type Person = {
  name: string;
  age: number;
};

const personA: Person = {};
// Type '{}' is missing the following properties from type 'Person': name, age

在这里,您创建了一个自定义类型 Person ,并发现将一个类型为 Person 的变量赋值会导致错误,因为一个空对象没有属性 name 和 age 。正确的代码如下所示:

代码语言:javascript复制
type Person = {
  name: string;
  age: number;
};

const personA: Person = {
  name: "John",
  age: 20,
};

console.log(personA.name, personA.age); // John, 20

如果具有类型 Person 的变量同时具有属性 name 和 age ,TypeScript不会抛出任何错误。

接口

接口与类型相似,但一个关键区别是接口可以用来定义类,而类型不能。下面是使用TypeScript接口的示例:

代码语言:javascript复制
interface Person {
  name: string;
  age: number;
  getName(): string;
}

class Student implements Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  getName() {
    return this.name;
  }

  getAge() {
    return this.age;
  }
}
const personA: Student = new Student("Nana", 20);
console.log(personA.getName(), personA.getAge());

在这个例子中,接口 Person 定义了类 Student 。在这里,你创建了一个 Student 类的实例,并使用它的方法打印 name 和 age 属性。

TypeScript泛型

泛型允许您编写可重用的代码,可以应用于具有相同结构的不同类型。下面是一个示例:

代码语言:javascript复制
interface Shape {
  length: number;
  width: number;
}

class Rectangle implements Shape {
  length: number;
  width: number;
  constructor(length: number, width: number) {
    this.length = length;
    this.width = width;
  }
}

class Square implements Shape {
  length: number;
  width: number;
  constructor(length: number) {
    this.length = length;
    this.width = length;
  }
}

function getArea<T extends Shape>(shape: T): number {
  return shape.length * shape.width;
}

const rectangle: Shape = new Rectangle(10, 5);
const rectangleArea = getArea(rectangle);

const square: Shape = new Square(7);
const squareArea = getArea(square);

console.log(rectangleArea, squareArea); // 50, 49

在上面的代码中,定义了一个接口 Shape 。使用了一个通用函数 getArea 来计算任何类型的 Shape 的面积。创建了两个独立的类 Rectangle 和 Square ,它们实现了 Shape 接口(它们是 Shape 的类型)。因此,可以通过一个单一的 getArea 通用函数来计算 Rectangle 和 Square 实例的面积。

现在你已经学习了一些TypeScript的基本概念,你将开始将这些概念应用于使用Vuex状态管理构建Vue应用程序。

入门指南

Vue-CLI会自动为您创建一个 store (如果您在添加项目时选择了 Vuex 作为附加功能)。否则,请在 src 目录中创建一个store,并添加一个 index.ts 文件。同时使用 npm i vuex 安装Vuex。将 index.ts 的内容替换为以下代码:

代码语言:javascript复制
import { createStore } from "vuex";
export interface State {
  count: number;
}

export default createStore<State>({
  state: { count: 0 },
  getters: {},
  mutations: {},
  actions: {},
  modules: {},
});

上述代码创建了一个名为 State 的接口。这定义了我们在 createStore 函数中使用的状态对象的形状。Vuex中的 createStore 函数表示全局状态以及如何在整个应用程序中访问它。注意,通用的 createStore<State> 允许您定义状态的形状。删除 count: 0 将会抛出错误,因为 state 对象将不匹配 State 接口。

要通过Options API使用 store ,请转到 main.ts 并添加以下代码:

代码语言:javascript复制
import { createApp } from "vue";
import App from "./App.vue";
import store, { State } from "./store";
import { Store } from "vuex";

declare module "@vue/runtime-core" {
  interface ComponentCustomProperties {
    $store: Store<State>;
  }
}
createApp(App).use(store).mount("#app");

declare module 重新定义了Vue运行时的 ComponentCustomProperties 。这是为了在Vue组件中访问 $store 属性而必要的。

请用以下代码替换 HelloWorld.vue 和 App.vue 组件

HelloWorld.vue

代码语言:javascript复制
<template>
  <div class="hello">
    <p>count: {{ count }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  computed: {
    count(): number {
      return this.$store.state.count;
    },
  },
});
</script>

App.vue

代码语言:javascript复制
<template>
  <HelloWorld />
</template>

<script lang="ts">
import HelloWorld from "./components/HelloWorld.vue";
import { defineComponent } from "vue";

export default defineComponent({
  components: { HelloWorld },
});
</script>

使用 npm run serve 运行服务器,并显示状态中的 count 属性(当前为0)。

Vuex Mutations

Mutations改变了存储在Vuex状态中的数据的值。突变是一组可以访问状态数据并对其进行更改的函数。请注意,在 store/index.ts 中,您有一个当前为空的 mutations 对象。

使用 mutations ,将 store/index.ts 代码调整为以下内容:

代码语言:javascript复制
import { createStore } from "vuex";

export interface State {
  count: number;
}

export default createStore<State>({
  state: { count: 0 },
  getters: {},
  mutations: {
    increment(state: State) {
      state.count  ;
    },
  },
  actions: {},
  modules: {},
});

上面的代码添加了一个 increment mutation,并将 State 接口作为参数。调用 mutation 会更新状态的 count 属性。要在 HelloWorld.vue 组件中使用它,请将代码替换为以下内容:

代码语言:javascript复制
<template>
  <div class="hello">
    <p>count: {{ count }}</p>
    <button @click="increment">Increase me</button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  computed: {
    count(): number {
      return this.$store.state.count;
    },
  },

  methods: {
    increment() {
      this.$store.commit("increment");
    },
  },
});
</script>

increment 方法是 HelloWorld.vue 组件的一种方法,每次调用时都会提交Vuex存储的 increment 变化。您将此方法附加到模板中按钮的 click 事件上。每次点击按钮时,存储中 count 属性的值都会更新。

Vuex Actions

Vuex的actions是一组方法,可以异步地更新Vuex存储的值。Vuex的mutations是同步的设计,不建议在Vuex的mutations中使用异步函数。要创建一个Vuex的action,请在您的 store/index.ts 中输入以下代码:

代码语言:javascript复制
import { createStore } from "vuex";

export interface State {
  count: number;
}

export default createStore<State>({
  state: { count: 0 },
  getters: {},
  mutations: {
    increment(state: State) {
      state.count  ;
    },
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => commit("increment"), 1000);
    },
  },
  modules: {},
});

这将一个 incrementAsync 函数添加到 actions 对象中。它使用 setTimeout 在一秒后调用 increment 动作。这个 { commit } 解构了提供给Vuex动作的 store 参数。这样可以更简洁地提交到状态。

使用该操作,将 HelloWorld.vue 组件替换为以下内容:

代码语言:javascript复制
<template>
  <div class="hello">
    <p>count: {{ count }}</p>
    <button @click="increment">Increase me</button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  computed: {
    count(): number {
      return this.$store.state.count;
    },
  },

  methods: {
    increment() {
      this.$store.dispatch("incrementAsync");
    },
  },
});
</script>

你将 increment 函数替换为使用Vuex action而不是直接提交到状态。你会注意到,点击按钮后,1秒钟后状态中的 count 会更新。

Vuex Getters

Vuex的getters允许我们从原始状态计算出派生状态。它们是只读的辅助函数,允许我们推导出关于原始状态的更多信息。要使用Vuex的getters,请将以下代码添加到 store/index.ts :

代码语言:javascript复制
import { GetterTree, createStore } from "vuex";

export interface Getters extends GetterTree<State, State> {
  doubleCount(state: State): number;
  isEven(state: State): boolean;
}

const getters: Getters = {};

// Type '{}' is missing the following properties from type 'Getters': doubleCount, isEven

上面的代码定义了一个用于获取器的接口。它利用了TypeScript的强类型特性来确保你的获取器被正确定义。由于 getters 对象尚未完全实现以匹配 getters 接口,所以会出现错误。请使用以下代码完成:

代码语言:javascript复制
import { GetterTree, createStore } from "vuex";

export interface State {
  count: number;
}

export interface Getters extends GetterTree<State, State> {
  doubleCount(state: State): number;
  isEven(state: State): boolean;
}

const getters: Getters = {
  doubleCount(state: State) {
    return state.count * 2;
  },
  isEven(state: State) {
    return state.count % 2 == 0;
  },
};

export default createStore({
  state: { count: 0 },
  getters,
  mutations: {
    increment(state: State) {
      state.count  ;
    },
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => commit("increment"), 1000);
    },
  },
  modules: {},
});

该代码实现了 getters 对象,并将其设置为 createStore getters中的Vuex getters。继续在 HelloWorld.vue 组件中使用以下代码:

代码语言:javascript复制
<template>
  <div class="hello">
    <p>count: {{ count }}</p>
    <p>is even: {{ isEven }}</p>
    <p>double of count: {{ double }}</p>
    <button @click="increment">Increase me</button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  computed: {
    count(): number {
      return this.$store.state.count;
    },
    isEven(): boolean {
      return this.$store.getters.isEven;
    },
    double(): number {
      return this.$store.getters.doubleCount;
    },
  },

  methods: {
    increment() {
      this.$store.commit("increment");
    },
  },
});
</script>

这添加了额外的计算方法,返回getter函数。 isEven 确定 count 状态是否为偶数, doubleCount 计算count的两倍值。

Vuex Modules

模块允许将状态的不同部分分离,并允许将不同的逻辑进行分段。它还可以防止状态对象变得庞大且难以维护。要使用Vuex模块,请按照以下示例进行操作:

考虑这样一个场景,你想要构建一个简单的社交媒体应用。为了管理用户、帖子和评论的状态,你可以有一个类似下面的Vuex配置:

代码语言:javascript复制
import { createStore } from "vuex";

interface User {
  name: string;
}

interface Post {
  id: string;
  title: string;
  content: string;
}

interface Comment {
  postId: string;
  comment: string;
}
export interface State {
  user: User | null;
  posts: Post[];
  comments: Comment[];
}

export default createStore<State>({
  state: { user: null, posts: [], comments: [] },
  getters: {},
  mutations: {
    setUser(state: State, user: User) {
      state.user = user;
    },
    addPost(state: State, post: Post) {
      state.posts.push(post);
    },
    addComment(state: State, comment: Comment) {
      state.comments.push(comment);
    },
  },
  actions: {
    login({ commit }, user) {
      // Simulate user login
      commit("setUser", user);
    },
    createPost({ commit }, post) {
      // Simulate creating a post
      commit("addPost", post);
    },
    createComment({ commit }, comment) {
      // Simulate creating a comment
      commit("addComment", comment);
    },
  },
});

state , actions 和 mutations 即使没有实际实现,看起来已经很庞大了。Vuex模块有助于解决这个问题。下面是使用Vuex模块进行重构的代码示例:

代码语言:javascript复制
import { Module, createStore } from "vuex";

interface User {
  name: string;
}

interface Post {
  id: string;
  title: string;
  content: string;
}

interface Comment {
  postId: string;
  comment: string;
}

export interface State {
  user: User | null;
  posts: Post[];
  comments: Comment[];
}

export interface UserModuleState {
  user: User | null;
}

export interface PostModuleState {
  posts: Post[];
}

export interface CommentModuleState {
  comments: Comment[];
}

const userModule: Module<UserModuleState, State> = {
  state: () => ({ user: null }),
  mutations: {
    setUser(state: UserModuleState, user: User) {
      state.user = user;
    },
  },
  actions: {
    login({ commit }, user) {
      // Simulate user login
      commit("setUser", user);
    },
  },
};

const postModule: Module<PostModuleState, State> = {
  state: () => ({ posts: [] }),
  mutations: {
    addPost(state: PostModuleState, post: Post) {
      state.posts.push(post);
    },
  },
  actions: {
    createPost({ commit }, post) {
      // Simulate creating a post
      commit("addPost", post);
    },
  },
};

const commentModule: Module<CommentModuleState, State> = {
  state: () => ({ comments: [] }),
  mutations: {
    addComment(state: CommentModuleState, comment: Comment) {
      state.comments.push(comment);
    },
  },
  actions: {
    createComment({ commit }, comment) {
      // Simulate creating a comment
      commit("addComment", comment);
    },
  },
};

export default createStore<State>({
  modules: {
    userModule,
    postModule,
    commentModule,
  },
});

你会注意到 user 、 post 和 comments 的逻辑已经分别放在不同的 Modules 中。每个 Modules 都有自己的 state 、 actions 和 mutations 。

建议将每个模块存储在自己独立的文件中,以促进关注点分离和每个模块的更小、紧密相关的紧凑代码。

Vuex模块也可以包含内部模块,在官方Vuex文档中可以探索到很多有关这个强大功能的内容。

在Vuex中常用的模式

探索一些增强您的TypeScript代码的最佳实践和实用策略。这些技巧将指导您进行更易维护的TypeScript开发。

辅助函数

主要的 store 不必包含你的 actions 和 mutations 的功能。辅助函数可以分离到不同的模块中,并从那里导入。

Vuex Mappers

而不是在每个操作或 mutation的组件中添加 methods ,Vuex提供了帮助函数,直接将 actions 、 mutations 或 getters 映射到组件的 methods 或 computed 。在前面的示例中,我们在组件的 methods 或计算属性 object 中调用了存储的 dispatch 或 commit 方法。

代码语言:javascript复制
import { createStore } from "vuex";

export interface State {
  count: 0;
}

export default createStore<State>({
  state: { count: 0 },
  mutations: {
    increment(state: State) {
      state.count  ;
    },
  },
});

这段代码是设置Vuex存储的先前示例,但您将在 mapMutations 和 mapState 组件中使用名为Vuex助手的工具,如下所示:

代码语言:javascript复制
<template>
  <div class="hello">
    <p>count: {{ count }}</p>
    <button @click="increment">Increase me</button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { mapMutations, mapState } from "vuex";

export default defineComponent({
  computed: {
    ...mapState(["count"]),
  },
  methods: {
    ...mapMutations(["increment"]),
  },
});
</script>

不要创建一个计算属性来访问状态,而是使用名为 mapState 的Vuex辅助函数直接将其映射到计算对象中。您在列表中将要访问的状态属性的名称( count )作为字符串指定,并将其作为参数添加到 mapState 函数中。

同样地,你对使用Vuex的 mapMutations 的 mutation 函数也做了同样的事情。

潜在的陷阱和解决方案

TypeScript确保更好的代码实践。你可能会遇到像 TypeErrors 这样的问题,即你想要使用的值与你需要的函数的类型不匹配。一个快速的解决方案是将你的类型指定为 any ,这将允许使用任何类型。但要小心不要过多地使用它,而是确保清晰的接口定义。

结束

在本文中,您探索了将TypeScript与Vuex集成的各种方法,并观察了TypeScript强类型系统的好处以及它如何在错误发生之前帮助预防错误。您还熟悉了Vuex存储库是什么,以及 states , mutations , actions 和 getters 。

最后,你学会了如何使用Vuex模块来拆分你的状态管理系统,以应对需要的情况。

本文旨在为您提供一个平台,以使用Vuex构建更干净、更健壮的应用程序。使用TypeScript作为一种强大的工具,可以在错误变成重大问题之前消除它们。

建议您在使用TypeScript构建更多项目的过程中,通过阅读Vuex的官方文档来深入了解并利用其优势。

由于文章内容篇幅有限,今天的内容就分享到这里,文章结尾,我想提醒您,文章的创作不易,如果您喜欢我的分享,请别忘了点赞和转发,让更多有需要的人看到。同时,如果您想获取更多前端技术的知识,欢迎关注我,您的支持将是我分享最大的动力。我会持续输出更多内容,敬请期待。

0 人点赞