案例源代码: https://github.com/danielhuoo/vue2.x-vuex-typescript-demo
前言
相信很多人都像我一样,学习使用了vuex
后,想把项目改写成Typescript
。但是官方教程要么晦涩难懂,要么缺少鲜活的例子。我花了一天时间,总结出了一些经验。在此分享出来。
本教程通过编写一个简单的demo讲解vuex
的实现方式,以及如何对基于vue2.x
的已有项目进行Typescript重构。
项目初始化
现在都9012了,所以我们直接使用vue-cli 3.x快速搭建系统。
1 | # 搭建项目 |
模块说明
为了用实际的代码解释vuex
是如何搭建的,以及模块间的通讯方式,我用了一个很浅显的例子(应该比官方的例子明朗很多)
情景
男孩给女孩送花。
- 男孩每送出10朵花,女孩会表达感谢。
- 女孩的感谢会增加男孩的勇气值。
- 男孩可以向花店买花。
目录结构
你会发现默认的目录结构是这样的:
. ├── 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
index.js
是store
的主文件/module
下存放模块文件。boy.js
是男孩模块,girl.js
是女孩模块
模块定义
boy.js
该模块定义了三个action
方法。action
通俗来说就是你想让模块做的事情,它们可以是异步或者同步的。所有对state
的增删查改的逻辑都应该在这里,而mutation
仅仅负责执行增删查改。
1 | import { Message } from 'element-ui'; |
girl.js
1 | export default { |
index.js
1 | import Vue from 'vue' |
连接到vue组件
现在仓库的逻辑已经写好了,我们就可以在组件上使用了。实际上vuex
仓库早在main.js
被引入了vue
实例里了。例如,this.$store.state.flowersInStock
即代表根state
的属性值。但是这种写法太过繁琐,我们引入了vuex
提供的 mapState
、mapActions
和 mapMutations
进行映射。
boy.vue
1 | <template> |
很多人在刚开始用vuex
都会记不住,究竟state
、actions
和mutations
放哪里。其实很好记:
state
是属性,放computed
里。actions
和mutations
是方法,放methods
里。
girl.vue
同理,就不赘述了。下一步,我们开始用Typescript
改写代码。
安装Typescript
在安装之前,请一定要先做备份。因为安装后App.vue
会被改写。
1 | yarn add vuex-class |
改写开始
你会发现所有.js
文件都被改成.ts
后缀了。这时候整个项目是跑不起来的。命令行控制台会爆出几十个error
。事实上,在你没有把所有该改的地方改好之前,项目是不会跑通的。
index.ts
被改写的地方:
- 引入
module
的方式。改为import
对象中的一个属性 - 定义了
store
的类别。 - 新增了一个
RootState
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34import 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
的约束
1 | export interface RootState { |
boy.ts
模块的改动是巨大的。
- 新增了模块的
State
接口 - 定义
mutations
的类为MutationTree
- 定义
actions
的类为ActionTree
- 定义模块的类为
Module
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56import { 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
等方法,使用State
、Action
、Mutation
修饰器绑定vuex
- 弃用
computed
、methods
、data
等写法,使用get + 方法
表示computed
,methods
里的方法直接被抽出来,data
的属性直接被抽出来。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25<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。