zxpnet网站 zxpnet网站
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

zxpnet

一个爱学习的java开发攻城狮
首页
前端
后端服务器
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 大前端课程视频归档
  • html

  • js

  • 前端框架

  • 自动化构建

  • typescript

  • es6

  • bootstrap

  • layer

  • vue

    • 构建类型安全的 Vue 应用(Vue Typescript Vuex)
      • 主要步骤
        • 全局安装 vue cli
        • 使用 cil 初始化项目
        • 支持 tsx
        • 改造 vuex
        • 改造基础类型
        • module 示例
        • 在 component 中使用
      • 总结
      • 参考资料
    • vue常用开发组件
    • element-ui基础
    • vue常用场景
  • vue3

  • vuepress

  • hexo博客

  • 文档

  • biz业务

  • frontend
  • vue
shollin
2020-03-28
目录

构建类型安全的 Vue 应用(Vue Typescript Vuex)

提示

构建类型安全的 Vue 应用,基于:

  • @vue/cli: v4.2.3
  • typescript: v3.7.5
  • vue: v2.6.11
  • vue-tsx-support: v2.3.3
  • vuex: v3.1.2

PS:由于我接触 Vue 的时间还非常短,如果文章中有一些错误,或者你有更好的解决方案。请不吝赐教,我将万分感谢,谢谢!

# 主要步骤

# 全局安装 vue cli

npm install -g @vue/cli
1

# 使用 cil 初始化项目

vue create vue-ts-demo
1

然后选择 Manually select features, 最终所有初始配置项如下图: Manually select features

# 支持 tsx

针对一些有复杂渲染逻辑的组件,使用 jsx 的写法可谓是真香了,所以我们也相应的支持一下 tsx 的写法。

yarn add vue-tsx-support
1
// 在 main.ts 添加以下代码
import 'vue-tsx-support/enable-check'
1
2
// 新建 vue.config.js 添加以下代码
module.exports = {
  runtimeCompiler: true,
  configureWebpack: {
    resolve: {
      extensions: ['.js', '.vue', '.json', '.ts', '.tsx'],
    },
  },
}
1
2
3
4
5
6
7
8
9

至此,我们的项目已经支持了 tsx 。但是,默认情况下 vue-tsx-support 不允许使用未知的 prop, 举个栗子:

import Vue from 'vue'

const MyComponent = Vue.extend({
  props: { text: { type: String, required: true } },
  template: '<span>{{ text }}</span>',
})
1
2
3
4
5
6

我们有上面这样一个 component, 然后使用的时候就会得到一个错误。

// Compilation error(TS2339): Property `text` does not exist on type '...'
<MyComponent text="foo" />
1
2

除非我们可以保证,我们所有的 component 都是按照 vue-tsx-support 规范去创建的,否则我建议开启 allow-unknown-props.

// 新建 global.d.ts, 添加以下代码
import 'vue-tsx-support/options/allow-unknown-props'
1
2

完整支持 tsx 的代码(包括使用 tsx 改造 Home 和 About 组件)请参考这个commit (opens new window).
关于 vue-tsx-support 更详细的说明请参考文档 (opens new window).

# 改造 vuex

由于 vuex 的类型支持不够,在使用的时候,没有智能提示,也不能保证类型安全。所以我们需要对它进行一番改造。

# 改造基础类型

// 新建 module-type.ts, 给出一些基础的类型定义:
/** 提取出所有 module 的 state 类型 */
export interface State {}

/** 提取出所有 module 的 getters 类型 */
export type GettersFuncMap = {}

/** 提取出所有 module 的 mutations 类型 */
export type MutationsFuncMap = {}

/** 提取出所有 module 的 actions 类型 */
export type ActionsFuncMap = {}
1
2
3
4
5
6
7
8
9
10
11
12
// 新建 store-type.ts, 完成相关类型推导
import { GettersFuncMap, MutationsFuncMap, ActionsFuncMap, State } from './module-type'

/** Getter类型转换 Handler:GettersFuncMap => Getters */
export type GetterHandler<T extends { [key: string]: (...args: any) => any }> = {
  [P in keyof T]: ReturnType<T[P]>
}

/** 根据 GettersFuncMap 得到的 Getters 类型*/
export type Getters = GetterHandler<GettersFuncMap>

/** 将 MutationsFuncMap 的 key,value 转换成 Commit 函数的两个参数: type, payload */
export interface Commit {
  <T extends keyof MutationsFuncMap>(type: T, payload?: Parameters<MutationsFuncMap[T]>[1]): void
}

/** 将 ActionsFuncMap 的 key,value 转换成 Dispatch 函数的两个参数: type, payload */
export interface Dispatch {
  <T extends keyof ActionsFuncMap>(type: T, payload?: Parameters<ActionsFuncMap[T]>[1]): Promise<
    any
  >
}

// 导出全局的 Store 类型
export interface Store {
  state: State
  getters: Getters
  commit: Commit
  dispatch: Dispatch
}

// 导出全局的 ActionContext 类型
export interface ActionContext<S, G> {
  dispatch: Dispatch
  commit: Commit
  state: S
  getters: G
  rootState: State
  rootGetters: Getters
}
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

最后,由于无法直接覆盖全局的 $store 类型,所以另辟蹊径:

// 新建 global.d.ts, 添加以下代码
import { Store } from '@/store/store-type'

declare module 'vue/types/vue' {
  interface Vue {
    _store: Store
  }
}
1
2
3
4
5
6
7
8
// 修改 main.ts
Vue.prototype._store = store

new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')
1
2
3
4
5
6
7
8

至此,我们已经完成了基础的类型推导工作,下面来让我们创建一个示例。

# module 示例

import { User } from '@/interfaces'
import { ActionContext, GetterHandler } from '../store-type'

const state = {
  users: [] as User[],
  isCached: true,
}

type StateType = typeof state

const getters = {
  getUsers(state: StateType): User[] {
    return state.users
  },
  getAgeCount(state: StateType): number {
    return state.users.reduce((prev, curr) => prev + curr.age, 0)
  },
  getIsCached(state: StateType): boolean {
    return state.isCached
  },
}

const mutations = {
  reset(state: StateType): void {
    state.users = []
    state.isCached = true
  },
  setUsers(state: StateType, users: User[]): void {
    state.users = users
  },
  setIsCached(state: StateType, isCached: boolean): void {
    state.isCached = isCached
  },
}

type GettersType = GetterHandler<typeof getters>
type UserContext = ActionContext<StateType, GettersType>

const actions = {
  updateState({ commit }: UserContext, payload: StateType) {
    commit('setUsers', payload.users)
    commit('setIsCached', payload.isCached)
  },
}

export const test = {
  state,
  getters,
  mutations,
  actions,
}
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

然后,我们需要修改一下 module-type.ts

import { test, test2 } from './modules/test'

export interface State {
  test: typeof test.state
  test2: typeof test2.state
}

export type GettersFuncMap = typeof test.getters & typeof test2.getters

export type MutationsFuncMap = typeof test.mutations & typeof test2.mutations

export type ActionsFuncMap = typeof test.actions & typeof test2.actions
1
2
3
4
5
6
7
8
9
10
11
12

也就是说后续我们有新的 module, 也同样需要改动这几行代码。

# 在 component 中使用

@Component
export default class HelloWorld extends Vue {
  test(): void {
    /**
     * auto tip test
     * (property) State.test: {
     *    users: User[];
     *    isCached: boolean;
     *  }
     */
    this._store.state
    // Argument of type '"xxx"' is not assignable to parameter of type '"updateState"'.
    this._store.dispatch('xxx')
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

如图所示,自动提示和类型检查都是正程工作的: auto tips type check

完整改造 vuex 的代码见commit (opens new window).

# 总结

至此,我们的改造就完成了,接下来我们就可以好好享受类型安全带来的愉快开发体验了。当前还遗留的两个没有解决的问题:

  • 无法覆盖默认的 $store 类型,只能通过新增一个 _store 去使用,对于强迫症患者可能还是有点不舒服。
  • 每次新增 module, 都需要修改 module-type.ts 文件。可以考虑写个脚本,读取 modules 目录下的文件,自动修改这个文件。

对于以上两个问题,希望能有更好的解决方案。

同时,我们也期待下 Vue 3.0 的快点到来吧,原汁原味的 typescript 支持,会比我们这种骚操作式的改造方案要靠谱,也更加通用一些。
最后,还记得那句名言吗?Any application that can be written in JavaScript, will eventually be written in JavaScript
那么,我现在希望的是:Any application that can be written in JavaScript, will eventually be written in Typescript

# 参考资料

本文完整代码见vue-ts-demo (opens new window)
@vue/cli 更详细的配置见Vue CLI (opens new window)
vuex 改造方案来自vuex-typescript-demo (opens new window)

#Vue#Typescript#Vuex
layer弹出层组件
vue常用开发组件

← layer弹出层组件 vue常用开发组件→

最近更新
01
国际象棋
09-15
02
成语
09-15
03
自然拼读
09-15
更多文章>
Theme by Vdoing | Copyright © 2019-2023 zxpnet | 粤ICP备14079330号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式