一个简单案例教你如何用Typescript写Vuex

栏目: 编程语言 · 发布时间: 7年前

内容简介:相信很多人都像我一样,学习使用了本教程通过编写一个简单的demo讲解现在都9012了,所以我们直接使用vue-cli 3.x快速搭建系统。

相信很多人都像我一样,学习使用了 vuex 后,想把项目改写成 Typescript 。但是官方教程要么晦涩难懂,要么缺少鲜活的例子。我花了一天时间,总结出了一些经验。在此分享出来。

本教程通过编写一个简单的demo讲解 vuex 的实现方式,以及如何对基于 vue2.x 的已有项目进行Typescript重构。

项目初始化

现在都9012了,所以我们直接使用vue-cli 3.x快速搭建系统。

# 搭建项目
vue create vue2.x-vuex-typescript-demo

cd vue2.x-vuex-typescript-demo
# 引入vuex
vue add vuex
# 由于我实在不想写任何样式,所以我又加一个element
vue add element
复制代码

模块说明

为了用实际的代码解释 vuex 是如何搭建的,以及模块间的通讯方式,我用了一个很浅显的例子(应该比官方的例子明朗很多)

情景

男孩给女孩送花。

  1. 男孩每送出10朵花,女孩会表达感谢。
  2. 女孩的感谢会增加男孩的勇气值。
  3. 男孩可以向花店买花。

目录结构

你会发现默认的目录结构是这样的:

.
├── README.md
├── babel.config.js
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   ├── main.js
│   ├── plugins
│   │   └── element.js
│   └── store.js
└── yarn.lock

但是我们想让vuex变得模块化。所以我们改成以下的结构:

.
├── README.md
├── babel.config.js
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── HelloWorld.vue
│   ├── main.js
│   ├── plugins
│   │   └── element.js
│   └── store
│       ├── index.js
│       └── module
│           ├── boy.js
│           └── girl.js
└── yarn.lock
  1. index.jsstore 的主文件
  2. /module 下存放模块文件。 boy.js 是男孩模块, girl.js 是女孩模块

模块定义

boy.js

该模块定义了三个 action 方法。 action 通俗来说就是你想让模块做的事情,它们可以是异步或者同步的。所有对 state 的增删查改的逻辑都应该在这里,而 mutation 仅仅负责执行增删查改。

import { Message } from 'element-ui';
export default {
    namespaced: true,
    // state 的属性只能通过 mutation的方法进行修改
    state: {
        currentFlower: 50,
        braveScore: 0
    },
    mutations: {
        // 修改 state 的 currentFlower 的值
        updateCurrentFlower(state, payload) {
            state.currentFlower = state.currentFlower + payload
        },
        // 修改 state 的 braveScore 的值
        updateBraveScore(state, payload) {
            state.braveScore = state.braveScore + payload.score
        }
    },
    actions: {
        // 送花
        // 方法里 调用了 commit 和 state,需要在传参时声明
        sendFlower({ commit, state }, params) {
            if (!state.currentFlower) {
                Message({
                    showClose: true,
                    message: "没花可送了",
                    type: "warning"
                });
            } else {
                // 送出一朵花,自己的库存减 1
                commit('updateCurrentFlower', -params.sendNumber)
                // 女孩收到一朵花,女孩库存加 1。
                // 注意这里是跨模块调用,所以需要加上模块前缀 'girl/',并且 传入参数 {root:true} 表明通过根路径寻找目标函数。
                commit('girl/updateCurrentFlower', params.sendNumber, { root: true })
            }
        },
        // 受到鼓励
        beEncouraged({ commit }) {
            commit('updateBraveScore', { score: 10 })
        },
        // 买花
        // 方法里调用了 commit, dispatch。 dispatch跨模块调用根store的action,跟送花的commit一样,需要加上前缀和传入{root:true}
        buyFlower({ commit, dispatch }, params) {
            setTimeout(() => {
                dispatch('sellFlower', null, { root: true }).then(() => {
                    commit('updateCurrentFlower', params.buyNumber)
                }).catch(() => {
                    Message({
                        showClose: true,
                        message: "库存不足",
                        type: "warning"
                    });
                })
            }, 100)
        }
    }
}
复制代码

girl.js

export default {
    namespaced: true,
    state: {
        currentFlower: 0
    },
    mutations: {
        updateCurrentFlower(state, payload) {
            state.currentFlower = state.currentFlower + payload
        }
    },
    actions: {
        // 对男孩进行鼓舞
        encourage({ dispatch }, params) {
            dispatch('boy/beEncouraged', null, { root: true })
        }
    }
}
复制代码

index.js

import Vue from 'vue'
import Vuex from 'vuex'
// 引入模块
import boy from './module/boy'
import girl from './module/girl'
Vue.use(Vuex)

export default new Vuex.Store({
    // 根 state
    state: {
        flowersInStock: 10
    },
    // 根 mutations
    mutations: {
        updateFlowersInStock(state, payload) {
            state.flowersInStock = state.flowersInStock + payload
        }
    },
    // 根 actions
    actions: {
        sellFlower({ commit, state }, params) {
            return new Promise((resolve, reject) => {
                if (state.flowersInStock > 0) {
                    commit('updateFlowersInStock', -1)
                    resolve()
                } else {
                    reject()
                }
            })
        }
    },
    // 注册模块
    modules: {
        boy,
        girl
    }
})
复制代码

连接到vue组件

现在仓库的逻辑已经写好了,我们就可以在组件上使用了。实际上 vuex 仓库早在 main.js 被引入了 vue 实例里了。例如, this.$store.state.flowersInStock 即代表根 state 的属性值。但是这种写法太过繁琐,我们引入了 vuex 提供的 mapStatemapActionsmapMutations 进行映射。

boy.vue

<template>
    <div>
        <div>男孩</div>
        <div>手上有{{currentFlower}}朵花</div>
        <div>
            <el-button @click="sendFlower({sendNumber:1})">送花</el-button>
            <el-button @click="buyFlower({buyNumber:1})">买花</el-button>
        </div>
        <div>勇气值:{{braveScore}}</div>
    </div>
</template>
<script>
import { mapState, mapActions } from "vuex";
export default {
    computed: {
        // 你会发现state的映射放在了computed里面。这么做的好处是由于 Vuex 的状态存储是响应式的,从 store 实例中读取状态最简单的方法就是在计算属性中返回某个状态。
        // 通过映射,this.$store.state.currentFlower 就可以表示为 this.currentFlower
        ...mapState("boy", {
            currentFlower: state => state.currentFlower,
            braveScore: state => state.braveScore
        })
    },

    methods: {
        // actions 放在了methods里面。这不奇怪,因为actions跟mutations一样,都是vuex里面的方法。
        ...mapActions("boy", ["sendFlower", "buyFlower"])
    }
};
</script>
<style>
</style>
复制代码

很多人在刚开始用 vuex 都会记不住,究竟 stateactionsmutations 放哪里。其实很好记:

  • state 是属性,放 computed 里。
  • actionsmutations 是方法,放 methods 里。

girl.vue 同理,就不赘述了。下一步,我们开始用 Typescript 改写代码。

安装 Typescript

在安装之前,请一定要先做备份。因为安装后 App.vue 会被改写。

yarn add vuex-classß
vue add typescript
? Use class-style component syntax? (Y/n)  Yes
? Use Babel alongside TypeScript for auto-detected polyfills? (Y/n) Yes
复制代码

改写开始

你会发现所有 .js 文件都被改成 .ts 后缀了。这时候整个项目是跑不起来的。命令行控制台会爆出几十个 error 。事实上,在你没有把所有该改的地方改好之前,项目是不会跑通的。

index.ts

被改写的地方:

  • 引入 module 的方式。改为 import 对象中的一个属性
  • 定义了 store 的类别。
  • 新增了一个 RootState
import Vue from 'vue'
import Vuex, { StoreOptions } from 'vuex'
import { boy } from './module/boy'
import { girl } from './module/girl'
import { RootState } from './root-types';
Vue.use(Vuex)
const store: StoreOptions<RootState> = {
    // 里面的内容不用修改
    state: {
        flowersInStock: 10
    },
    modules: {
        boy,
        girl
    },
    mutations: {
        updateFlowersInStock(state, payload) {
            state.flowersInStock = state.flowersInStock + payload
        }
    },
    actions: {
        sellFlower({ commit, state }) {
            return new Promise((resolve, reject) => {
                if (state.flowersInStock > 0) {
                    commit('updateFlowersInStock', -1)
                    resolve()
                } else {
                    reject()
                }
            })
        }
    }
}
export default new Vuex.Store<RootState>(store)
复制代码

root-types.ts

这是对根 state 的约束

export interface RootState {
    flowersInStock: number
}
复制代码

boy.ts

模块的改动是巨大的。

  • 新增了模块的 State 接口
  • 定义 mutations 的类为 MutationTree
  • 定义 actions 的类为 ActionTree
  • 定义模块的类为 Module
import { Message } from 'element-ui';
import { BoyState } from './module-types';
import { MutationTree, ActionTree, Module } from 'vuex';
import { RootState } from '../root-types';
const state: BoyState = {
    currentFlower: 50,
    braveScore: 0
}

// 传入的泛型可以通过查看源代码得知。
const mutations: MutationTree<BoyState> = {
    updateCurrentFlower(state, payload) {
        state.currentFlower = state.currentFlower + payload
    },

    updateBraveScore(state, payload) {
        state.braveScore = state.braveScore + payload.score
    }
}
const actions: ActionTree<BoyState, RootState> = {
    sendFlower({ commit, state }, params) {
        if (!state.currentFlower) {
            Message({
                showClose: true,
                message: "没花可送了",
                type: "warning"
            });
        } else {
            commit('updateCurrentFlower', -params.sendNumber)
            commit('girl/updateCurrentFlower', params.sendNumber, { root: true })
        }

    },
    buyFlower({ commit, dispatch }, params) {
        setTimeout(() => {
            dispatch('sellFlower', null, { root: true }).then(() => {
                commit('updateCurrentFlower', params.buyNumber)
            }).catch(() => {
                Message({
                    showClose: true,
                    message: "库存不足",
                    type: "warning"
                });
            })
        }, 100)
    },
    beEncouraged({ commit }) {
        commit('updateBraveScore', { score: 10 })
    }
}
export const boy: Module<BoyState, RootState> = {
    namespaced: true,
    state,
    mutations,
    actions
}
复制代码

boy.vue

vue 文件改动的地方也是很多的:

  • script 标签指定了 ts 语言
  • 使用 Component 修饰组件
  • export 组件 从 对象变为 类
  • 弃用 mapState 等方法,使用 StateActionMutation 修饰器绑定 vuex
  • 弃用 computedmethodsdata 等写法,使用 get + 方法 表示 computedmethods 里的方法直接被抽出来, data 的属性直接被抽出来。
<script lang="ts">
import { Vue, Component, Watch } from "vue-property-decorator";
import { State, Action, Mutation, namespace } from "vuex-class";
import { BoyState } from "../store/module/module-types";
@Component
export default class boyComponent extends Vue {
    @State("boy") 
    // 感叹号不能省略
    boyState!: BoyState;

    @Action("sendFlower", { namespace: "boy" })
    sendFlower: any;

    @Action("buyFlower", { namespace: "boy" })
    buyFlower: any;

    get currentFlower(): number {
        return this.boyState.currentFlower;
    }

    get braveScore(): number {
        return this.boyState.braveScore;
    }
}
</script>
复制代码

其他文件也是用类似的方法去改写。换汤不换药。

以上就是 Typescript 改写的例子。有些地方没有解释得很清楚,因为我也是一个小白啊,不懂的地方还是不要误导大家了。如果你的项目的逻辑比这个更复杂(肯定吧),而本项目没有覆盖到你的疑惑,你可以去看我的另一个改好的项目 Jessic


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

SCWCD Exam Study Kit Second Edition

SCWCD Exam Study Kit Second Edition

Hanumant Deshmukh、Jignesh Malavia、Matthew Scarpino / Manning Publications / 2005-05-20 / USD 49.95

Aimed at helping Java developers, Servlet/JSP developers, and J2EE developers pass the Sun Certified Web Component Developer Exam (SCWCD 310-081), this study guide covers all aspects of the Servlet an......一起来看看 《SCWCD Exam Study Kit Second Edition》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

随机密码生成器
随机密码生成器

多种字符组合密码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具