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

构建类型安全的 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

使用 cil 初始化项目

vue create vue-ts-demo

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

支持 tsx

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

yarn add vue-tsx-support
// 在 main.ts 添加以下代码
import 'vue-tsx-support/enable-check'
// 新建 vue.config.js 添加以下代码
module.exports = {
  runtimeCompiler: true,
  configureWebpack: {
    resolve: {
      extensions: ['.js', '.vue', '.json', '.ts', '.tsx'],

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

import Vue from 'vue'
const MyComponent = Vue.extend({
  props: { text: { type: String, required: true } },
  template: '<span>{{ text }}</span>',

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

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

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

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

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

改造 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 = {}
// 新建 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<
// 导出全局的 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

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

// 新建 global.d.ts, 添加以下代码
import { Store } from '@/store/store-type'
declare module 'vue/types/vue' {
  interface Vue {
    _store: Store
// 修改 main.ts
const app = new Vue({
  router,
  store,
  render: h => h(App),
}).$mount('#app')
Vue.prototype._store = app.$store

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

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,

然后,我们需要修改一下 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

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

component 中使用

@Component
export default class HelloWorld extends Vue {
  test(): void {
     * auto tip test
     * (property) State.test: {
     *    users: User[];
     *    isCached: boolean;