2022-10-21 13:25:59



inb4: This is not another "setting up" a new project with Vue and TypeScript tutorial. Let's do some deep dive into more complex topics!

inb4:这不是另一个使用Vue和TypeScript教程“设置”新项目的方法。 让我们深入研究更复杂的主题!

typescript is awesome.Vue is awesome. No doubt, that a lot of peopletry to bundle them together. But, due to different reasons, it is hard toreally type yourVue app. Let's find out what are the problems and what can be done to solve them (or at least minimize the impact).

typescript很棒。Vue很棒。 毫无疑问,很多人试图将它们捆绑在一起 。 但是,由于不同的原因,很难真正键入您的Vue应用。 让我们找出问题所在以及可以解决的方法(或至少将影响最小化)。


We havethis wonderful template withNuxt,Vue,Vuex, andjest fully typed. Just install it and everything will be covered for you. Goto the docs to learn more.

我们有一个非常漂亮的模板Nuxt完整键入了NuxtVueVuexjest 。 只需安装它,一切将为您覆盖。 转到文档以了解更多信息。

And as I said I am not going to guide you through the basic setup for three reasons:


  1. There are a lot of existing tutorials about it

  2. There are a lot of tools to get started with a single click likeNuxt andvue-cli withtypescript plugin


  3. We already havewemake-vue-template where every bit of setup that I going to talk about is already covered

    我们已经有了wemake-vue-template ,其中我要谈论的所有设置都已涵盖

组件类型(Component typings)

The first broken expectation when you start to work withVue andtypescript and after you have already typed your class components is that<template> and<style> tags are still not typed. Let me show you an example:

当您开始使用Vuetypescript并在键入类组件之后,第一个破破的期望是<template><style>标记仍未键入。 让我给你看一个例子:

  <h1 :class="$style.headr">
    Hello, {{ usr }}!

<script lang="ts">
import Vue from 'vue'
import Component from 'vue-class-component'
import { Prop } from 'vue-property-decorator'

export default class HelloComponent extends Vue {
  user!: string

<style module>
.header { /* ... */ }

I have made two typos here:{{ usr }} instead of{{ user }} and$style.headr instead of$style.header. Willtypescript save me from these errors? Nope, it won't.

我在这里做了两个错别字:{{ usr }}而不是{{ user }}$style.headr而不是$style.headertypescript会帮助我避免这些错误吗? 不,不会。

What can be done to fix it? Well, there are several hacks.

该如何解决? 好吧,这里有几个技巧。

键入模板(Typing the template)

One can useVetur withvetur.experimental.templateInterpolationService option to type-check your templates. Yes, this is only an editor-based check and it cannot be used inside the CI yet. But,Vetur team is working hard to provide a CLI to allow this. Trackthe original issue in case you are interested.

可以将Veturvetur.experimental.templateInterpolationService选项一起使用,以对模板进行类型检查。 是的,这只是基于编辑器的检查,尚不能在CI中使用。 但是,Vetur团队正在努力提供CLI以允许这样做。 如果您有兴趣,请跟踪原始问题


The second option is two write snapshot tests withjest. It will catch a lot of template-based errors. And it is quite cheap in the maintenance.

第二种选择是使用jest两次写快照测试。 它将捕获很多基于模板的错误。 而且它在维护上非常便宜。

So, the combination of these two tools provides you a nice Developer Experience with fast feedback and a reliable way to catch errors inside the CI.


打字风格(Typing styles)

Typingcss-modules is also covered by several external tools:

键入css-module s也可以通过几种外部工具来完成:

The main idea of these tools is to fetchcss-modules and then create.d.ts declaration files out of them. Then your styles will be fully typed. It is still not implemented forNuxt orVue, but you can tractthis issue for progress.

这些工具的主要思想是获取css-module s,然后.d.ts创建.d.ts声明文件。 然后,您的样式将被完全键入。NuxtVue仍未实现该Nuxt ,但是您可以继续解决这个问题

However, I don't personally use any of these tools in my projects. They might be useful for projects with large code bases and a lot of styles, but I am fine with just snapshots.

但是,我并没有在项目中亲自使用任何这些工具。 对于具有大型代码库和许多样式的项目,它们可能很有用,但仅快照就可以了。

Styleguides with visual regression tests also help a lot.@storybook/addon-storyshots is a nice example of this technique.



The next big thing isVuex. It has some built-in by-design complexity for typing:

下一件大事是Vuex 。 它具有一些内置的设计复杂性来进行键入:

const result: Promise<number> = this.$store.dispatch('action_name', { payload: 1 })

The problem is that'action_name' might no exist, take other arguments, or return a different type. That's not something you expect for a fully-typed app.

问题在于'action_name'可能不存在,采用其他参数或返回其他类型。 对于全类型应用程序,这不是您期望的。

What are the existing solutions?



vuex-class is a set of decorators to allow easy access from your class-based components to theVuex internals.


But, itis not typed safe since it cannot interfere with the types of state, getters, mutations, and actions.



Of course, you can manually annotate types of properties.


vuex-class annotated

But what are you going to do when the real type of your state, getters, mutations, or actions will change? You will have a hidden type mismatch.

但是,当状态,吸气剂,突变或动作的真实类型发生变化时,您将怎么办? 您将遇到隐藏的类型不匹配。


That's wherevuex-simple helps us. It actually offers a completely different way to write yourVuex code and that's what makes it type safe. Let's have a look:

这就是vuex-simple帮助我们的地方。 实际上,它提供了一种完全不同的方式来编写Vuex代码,这就是使其安全的原因。 我们来看一下:

import { Action, Mutation, State, Getter } from 'vuex-simple'

class MyStore {

  // State

  public comments: CommentType[] = []

  // Getters

  public get hasComments (): boolean {
    return Boolean(this.comments && this.comments.length > 0)

  // Mutations

  public setComments (payload: CommentType[]): void {
    this.comments = updatedComments

  // Actions

  public async fetchComments (): Promise<CommentType[]> {
    // Calling some API:
    const commentsList = await api.fetchComments()
    this.setComments(commentsList) // typed mutation
    return commentsList

Later this typed module can be registered inside yourVuex like so:


import Vue from 'vue'
import Vuex from 'vuex'
import { createVuexStore } from 'vuex-simple'

import { MyStore } from './store'


// Creates our typed module instance:
const instance = new MyStore()

// Returns valid Vuex.Store instance:
export default createVuexStore(instance)

Now we have a 100% nativeVuex.Store instance and all the type information bundled with it. To use this typed store in the component we can write just one line of code:

现在,我们有了一个100%本机Vuex.Store实例,并捆绑了所有类型信息。 要在组件中使用这种类型的存储,我们可以只编写一行代码:

import Vue from 'vue'
import Component from 'nuxt-class-component'
import { useStore } from 'vuex-simple'

import MyStore from './store'

export default class MyComponent extends Vue {
  // That's all we need!
  typedStore: MyStore = useStore(this.$store)

  // Demo: will be typed as `Comment[]`:
  comments = typedStore.comments

Now we have typedVuex that can be safely used inside our project. When we change something inside our store definition it is automatically reflected to the components that use this store. If something fails — we know it as soon as possible.

现在,我们输入了可以在项目中安全使用的Vuex 。 当我们在商店定义中更改某些内容时,它会自动反映到使用该商店的组件中。 如果出现故障-我们会尽快知道。

There are also different libraries that do the same but have different API. Choose what suits you best.

也有不同的库执行相同的操作,但具有不同的API。 选择最适合您的东西。

API调用(API calls)

When we haveVuex correctly setup, we need to fill it with data. Let's have a look at our action definition again:

正确设置Vuex ,需要用数据填充它。 让我们再次看一下我们的动作定义:

public async fetchComments (): Promise<CommentType[]> {
  // Calling some API:
  const commentsList = await api.fetchComments()
  // ...
  return commentsList

How can we know that it will really return a list ofCommentType and not a singlenumber or a bunch ofAuthorType instances?


We cannot control the server. And the server might actually break the contract. Or we can simply pass the wrongapi instance, make a typo in the URL, or whatever.

我们无法控制服务器。 服务器实际上可能违反合同。 或者,我们可以简单地传递错误的api实例,在URL中输入错误或其他。

How can we be safe? We can use runtime typing! Let me introduceio-ts to you:

我们如何安全? 我们可以使用运行时输入! 让我向您介绍io-ts

import * as ts from 'io-ts'

export const Comment = ts.type({
  'id': ts.number,
  'body': ts.string,
  'email': ts.string,

// Static TypeScript type, that can be used as a regular `type`:
export type CommentType = ts.TypeOf<typeof Comment>

What do we do here?


  1. We define an instance ofts.type with fields that we need to be checked in runtime when we receive a response from server


  2. We define a static type to be used in annotation without any extra boilerplate


And later we can use it ourapi calls:


import * as ts from 'io-ts'
import * as tPromise from 'io-ts-promise'

public async fetchComments (): Promise<CommentType[]> {
  const response = await axios.get('comments')
  return tPromise.decode(ts.array(Comment),

With the help ofio-ts-promise, we can return aPromise in a failed state if the response from server does not match ats.array(Comment) type. It really works like a validation.

借助io-ts-promise ,如果服务器的响应与ts.array(Comment)类型不匹配,我们可以以失败状态返回Promise 。 它真的像验证一样工作。

   .then((data) => /* ... */
   .catch(/* Happens with both request failure and incorrect response type */)

Moreover, return type annotation is in sync with the.decode method. And you cannot put random nonsense there:

此外,返回类型注释与.decode方法同步。 而且您不能在这里随意胡扯:


With the combination of runtime and static checks, we can be sure that our requests won't fail because of the type mismatch. But, to be 100% sure that everything works, I would recommend using contract-based testing: have a look atpact as an example. And monitor your app withSentry.

结合运行时检查和静态检查,我们可以确保我们的请求不会因为类型不匹配而失败。 但是,为100%确保一切正常,我建议使用基于合同的测试:以pact为例。 并使用Sentry监视您的应用程序。

Vue路由器(Vue Router)

The next problem is thatthis.$router.push({ name: 'wrong!' }) does not work the way we want to.

下一个问题是this.$router.push({ name: 'wrong!' })无法按照我们想要的方式工作。

I would say that it would be ideal to be warned by the compiler that we are routing to the wrong direction and this route does not exist. But, it is not possible. And not much can be done: there are a lot of dynamic routes, regex, fallbacks, permissions, etc that can eventually break. The only option is to test eachthis.$router call in your app.

我要说的是,最好由编译器警告我们正在朝错误的方向布线,并且该路由不存在。 但是,这是不可能的。 不能做的事情很多:最终可能会破坏很多动态路由,正则表达式,后备,权限等。 唯一的选择是测试应用程序中的每个this.$router调用。


Speaking about tests I do not have any excuses not to mention@vue/test-utils that also has some problems with typing.

说到测试,我没有任何借口,更不用说@vue/test-utils ,它在输入方面也存在一些问题。

When we will try to test our new shiny component withtypedStore property, we will find out that we actually cannot do that according to thetypescript:



Why does this happen? It happens becausemount() call does not know anything about your component's type, because all components have aVueConstructor<Vue> type by default:

为什么会这样? 发生这种情况是因为mount()调用对您的组件类型VueConstructor<Vue> ,因为默认情况下所有组件都具有VueConstructor<Vue>类型:


That's where all the problems come from. What can be done? You can usevuetype to produceYouComponent.vue.d.ts typings that will tell your tests the exact type of the mounted component.

那就是所有问题的根源。 该怎么办? 您可以使用vuetype生成YouComponent.vue.d.ts类型,这些类型将告诉您的测试所安装组件的确切类型。

You can also trackthis issue for the progress.


But, I don't like this idea. These are tests, they can fail. No big deal. That's why I stick to(wrapper.vm as any).whatever approach. This saves me quite a lot of time to write tests. But spoils Developer Experience a little bit.

但是,我不喜欢这个主意。 这些是测试,它们可能会失败。 没什么大不了的。 这就是为什么我坚持使用(wrapper.vm as any).whatever方式的原因。 这为我节省了大量时间来编写测试。 但是会破坏开发人员的经验。

Make your own decision here:


  • Usevuetype all the way


  • Partially apply it to the most important components with the biggest amount of tests and update it regularly

  • Useany as a fallback



The average level oftypescript support inVue ecosystem increased over the last couple of years:


  • Nuxt firstly introducednuxt-ts and now shipsts builds by default

    Nuxt首先引入了nuxt-ts ,现在默认情况下提供了ts构建

  • Vue@3 will have improvedtypescript support


  • More 3rd-party apps and plugins will provide type definitions


But, it is production ready at the moment. These are just things to improve! Writing type-safeVue code really improves your Developer Experience and allows you to focus on the important stuff while leaving the heavy-lifting to the compiler.

但是,目前已经可以生产了。 这些只是需要改进的地方! 编写类型安全的Vue代码确实可以改善您的开发人员体验,并让您可以专注于重要的内容,而无需费力地进行编译。

What are your favourite hacks and tools to typeVue apps? Let's discuss it in the comment section.

您最喜欢键入Vue应用程序的黑客和工具是什么? 让我们在评论部分中讨论它。



  • 作者:cullen2012
  • 原文链接:
    更新时间:2022-10-21 13:25:59