
inb4:这不是另一个使用Vue和TypeScript教程“设置”新项目的方法。 让我们深入研究更复杂的主题!
typescript
很棒。 Vue
很棒。 毫无疑问,很多人试图将它们捆绑在一起 。 但是,由于不同的原因,很难真正键入您的Vue
应用。 让我们找出存在的问题以及可以解决的问题(或至少将影响最小化)。
TLDR
我们有一个非常漂亮的模板 , Nuxt
完整键入了Nuxt
, Vue
, Vuex
和jest
。 只需安装它,一切将为您覆盖。 转到文档以了解更多信息。
正如我所说,由于以下三个原因,我不会指导您完成基本设置:
- 有很多关于它的现有教程
- 只需单击即可使用很多工具,例如
Nuxt
插件的Nuxt
和vue-cli
- 我们已经有了
wemake-vue-template
,其中我要谈论的所有设置都已经涵盖
组件类型
当您开始使用Vue
和typescript
并在键入类组件之后,第一个破破的期望是<template>
和<style>
标记仍未键入。 让我给你看一个例子:
<template> <h1 :class="$style.headr"> Hello, {{ usr }}! </h1> </template> <script lang="ts"> import Vue from 'vue' import Component from 'vue-class-component' import { Prop } from 'vue-property-decorator' @Component({}) export default class HelloComponent extends Vue { @Prop() user!: string } </script> <style module> .header { /* ... */ } </style>
我在这里做了两次输入错误: {{ usr }}
代替了{{ user }}
和$style.headr
代替了$style.header
。 typescript
会帮助我避免这些错误吗? 不,不会。
该如何解决? 好吧,这里有几个技巧。
键入模板
可以将Vetur
与vetur.experimental.templateInterpolationService
选项一起使用,以对模板进行类型检查。 是的,这只是基于编辑器的检查,尚不能在CI中使用。 但是, Vetur
团队正在努力提供一个CLI来允许这种情况。 如果您感兴趣,请跟踪原始问题 。

第二种选择是使用jest
两次写快照测试。 它将捕获很多基于模板的错误。 而且它在维护上非常便宜。
因此,这两种工具的组合为您提供了良好的开发人员体验,并提供了快速的反馈和可靠的方式来捕获CI中的错误。
打字风格
键入css-module
s还可以通过几种外部工具来完成:
这些工具的主要思想是获取css-module
s,然后.d.ts
创建.d.ts
声明文件。 然后,您的样式将被完全键入。 Nuxt
或Vue
仍未实现此功能 ,但是您可以继续解决此问题 。
但是,我并没有在项目中亲自使用任何这些工具。 对于具有大型代码库和许多样式的项目,它们可能很有用,但仅快照就可以了。
带有视觉回归测试的样式指南也有很大帮助。 @storybook/addon-storyshots
是此技术的一个很好的例子。
威克斯
下一件大事是Vuex
。 它具有一些内置的设计复杂性来进行键入:
const result: Promise<number> = this.$store.dispatch('action_name', { payload: 1 })
问题是'action_name'
可能不存在,采用其他参数或返回其他类型。 对于全类型应用程序,这不是您期望的。
现有的解决方案是什么?
Vuex级
vuex-class
是一组装饰器,可让您轻松地从基于类的组件访问Vuex
内部。
但是,它的类型不安全,因为它不会干扰状态,吸气剂,突变和动作的类型。

当然,您可以手动注释属性类型。

但是,当状态,吸气剂,突变或动作的真实类型发生变化时,您将怎么办? 您将遇到隐藏的类型不匹配。
简单的vuex
这就是vuex-simple
帮助我们的地方。 实际上,它提供了一种完全不同的方式来编写Vuex
代码,这就是使其安全的原因。 让我们看一下:
import { Action, Mutation, State, Getter } from 'vuex-simple' class MyStore { // State @State() public comments: CommentType[] = [] // Getters @Getter() public get hasComments (): boolean { return Boolean(this.comments && this.comments.length > 0) } // Mutations @Mutation() public setComments (payload: CommentType[]): void { this.comments = updatedComments } // Actions @Action() public async fetchComments (): Promise<CommentType[]> { // Calling some API: const commentsList = await api.fetchComments() this.setComments(commentsList) // typed mutation return commentsList } }
稍后,可以像下面这样在您的Vuex
内部注册该类型的模块:
import Vue from 'vue' import Vuex from 'vuex' import { createVuexStore } from 'vuex-simple' import { MyStore } from './store' Vue.use(Vuex) // Creates our typed module instance: const instance = new MyStore() // Returns valid Vuex.Store instance: export default createVuexStore(instance)
现在我们有了100%的本地Vuex.Store
实例以及与其绑定的所有类型信息。 要在组件中使用这种类型的存储,我们可以只编写一行代码:
import Vue from 'vue' import Component from 'nuxt-class-component' import { useStore } from 'vuex-simple' import MyStore from './store' @Component({}) 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 }
现在,我们键入了可以在项目中安全使用的Vuex
。
当我们在商店定义中更改某些内容时,它会自动反映到使用该商店的组件中。 如果出现故障-我们会尽快知道。
也有不同的库执行相同的操作,但具有不同的API。 选择最适合您的东西。
API调用
正确设置Vuex
,需要用数据填充它。
让我们再次看一下我们的动作定义:
@Action() public async fetchComments (): Promise<CommentType[]> { // Calling some API: const commentsList = await api.fetchComments() // ... return commentsList }
我们怎么知道它将返回一个AuthorType
列表,而不是一个number
或一堆AuthorType
实例?
我们无法控制服务器。 服务器可能实际上违反了合同。 或者,我们可以简单地传递错误的api
实例,在URL中输入错误或其他。
我们如何安全? 我们可以使用运行时输入! 让我向您介绍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>
我们在这里做什么?
- 我们定义
ts.type
的实例, ts.type
包含当我们收到服务器的响应时需要在运行时中检查的字段 - 我们定义了一个用于注释的静态类型,而无需任何额外的模板
然后,我们可以使用它通过我们的api
调用:
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), response.data) }
借助io-ts-promise
,如果服务器的响应与ts.array(Comment)
类型不匹配,我们可以以失败状态返回Promise
。 它真的像验证一样工作。
fetchComments() .then((data) => /* ... */ .catch(/* Happens with both request failure and incorrect response type */)
此外,返回类型注释与.decode
方法同步。 而且您不能在这里随意胡扯:

结合运行时检查和静态检查,我们可以确保我们的请求不会因为类型不匹配而失败。
但是,为100%确保一切正常,我建议使用基于合同的测试:以pact
为例。 并使用Sentry
监视您的应用程序。
Vue路由器
下一个问题是: this.$router.push({ name: 'wrong!' })
无法按照我们想要的方式工作。
我要说的是,最好由编译器警告我们正在朝错误的方向布线,并且此路由不存在。
但是,这是不可能的。 并不能做很多事情:有很多动态路由,正则表达式,后备,权限等最终可能会破坏。 唯一的选择是测试每个this.$router
在您的应用程序中调用this.$router
。
vue-test-utils
说到测试,我没有任何借口,更不用说@vue/test-utils
,它在输入方面也存在一些问题。
当我们尝试使用typedStore
属性测试新的闪亮组件时,我们会发现实际上无法根据typescript
:

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

那就是所有问题的根源。 该怎么办?
您可以使用vuetype
生成YouComponent.vue.d.ts
类型,该类型将告诉您的测试已安装组件的确切类型。
您也可以跟踪此问题以获取进度。
但是,我不喜欢这个主意。 这些是测试,它们可能会失败。 没什么大不了的。
这就是为什么我坚持使用(wrapper.vm as any).whatever
方法的原因。 这为我节省了大量时间来编写测试。 但是会破坏开发人员的经验。
在这里做出自己的决定:
- 一直使用
vuetype
- 将其部分应用到具有最多测试量的最重要组件,并定期进行更新
- 使用
any
作为后备
结论
在过去的几年中, Vue
生态系统中的typescript
支持平均水平有所提高:
Nuxt
首先引入了nuxt-ts
,现在默认情况下提供了ts
构建Vue@3
将改进typescript
支持- 更多的第三方应用程序和插件将提供类型定义
但是,目前已经可以生产了。 这些只是需要改进的地方! 编写类型安全的Vue
代码确实可以改善您的开发人员体验,并让您可以专注于重要的工作,而无需费力地进行编译。
您最喜欢键入Vue
应用程序的黑客和工具是什么? 让我们在评论部分中讨论它。