Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Python爬虫

  • 前端教程

    • 团队规范

    • Project

      • Axios用法与原理
      • css布局技巧
      • 深度学习平台术语
      • 谈谈前端天花板问题
      • 一个程序员的成长之路
      • Markdown-It 解析原理
      • minipack源码解析
      • PostCSS
      • Electron工程踩坑记录
      • H5 Video踩坑记录
      • Puppeteer翻页爬虫
      • 重构你的javascript代码
      • RxJS入门实践
      • 官网脚手架思考与实践
      • Stylelint样式规范工具
      • TypeScript开发Vue应用
      • Typescript tsconfig.json全解析
      • Vue项目TypeScript指南
        • [#](#_1-typescript优势) 1. TypeScript优势
        • [#](#_2-typescript类型) 2. TypeScript类型
          • [#](#_2-1-interface-type) 2.1 Interface \& type
          • [#](#_2-2-联合类型) 2.2 联合类型
          • [#](#_2-3-数组类型) 2.3 数组类型
          • [#](#_2-4-元祖) 2.4 元祖
          • [#](#_2-5-可选属性) 2.5 可选属性
          • [#](#_2-6-任意属性) 2.6 任意属性
          • [#](#_2-7-简写类型) 2.7 简写类型
          • [#](#_2-8-泛型) 2.8 泛型
          • [#](#_2-9-keyof) 2.9 keyof
          • [#](#_2-10-key-in-keyof-t) 2.10 key in keyof T
        • [#](#_3-typescript-vue环境配置) 3. TypeScript Vue环境配置
        • [#](#_4-typescript-vue使用) 4. TypeScript Vue使用
          • [#](#_4-1-vue-class-component) 4.1 vue-class-component
          • [#](#_4-2-vue-property-decorator) 4.2 vue-property-decorator
          • [#](#_4-3-vuex-class) 4.3 Vuex-Class
        • [#](#_5-typescript-描述文件) 5. TypeScript 描述文件
          • [#](#_5-1-全局变量-应用端补充) 5.1 全局变量 - 应用端补充
          • [#](#_5-2-npm包-应用端补充) 5.2 npm包 - 应用端补充
          • [#](#方法一-文件位置随意-源码中指定-declare-module-xxx) 方法一: 文件位置随意,源码中指定 declare module xxx
          • [#](#方法二-源码直接export-但文件位置有要求) 方法二:源码直接export,但文件位置有要求
          • [#](#_5-3-扩展npm包-应用端补充) 5.3 扩展npm包 \(opens new window\) - 应用端补充
          • [#](#_5-4-发布npm包-npm源码端补充) 5.4 发布npm包 - npm源码端补充
          • [#](#_1-自动生成声明文件) 1. 自动生成声明文件 \(opens new window\)
          • [#](#_2-发布声明文件) 2. 发布声明文件 \(opens new window\)
        • [#](#_6-tsconfig-json) 6. tsconfig.json
        • [#](#参考文章) 参考文章
      • TypeScript在Vue2.x中的坑
      • Vue Dialog弹窗解决方案
      • Vue JSX插件依赖及语法实践
      • Webpack 模块打包原理
      • Webpack4 配置详解
      • Webpack4 devServer配置详解
      • Webpack3.x升级Webpack4指南
    • JS

    • NodeJS

    • Vue

    • React

    • 效率工具

    • 读书笔记

  • 教程
  • 前端教程
  • Project
wangmings
2022-07-19
目录

Vue项目TypeScript指南

# # Vue项目TypeScript指南

# # 1. TypeScript优势

  • Javascript的超集,完美兼容js
    • 从核心语言方面和类概念的模塑方面对 JavaScript 对象模型进行扩展。
    • 拓展了js的语法,相比较于es6,它多了装饰器、私有属性、getter/setter、抽象类。(后续TC39可能都会加上这些特性)
  • 强类型语言(最重要特性之一),易于重构与理解。
    • 类型系统
    • 编译时的静态类型检查
  • 强大IDE支持,VSCode前端必备
  • 微软大厂保证
  • 社区统一共识,npm下载量非常高,复杂业务不再慌。

# # 2. TypeScript类型

  • 原始类型

    • boolean
    • number
    • string
  • 特殊类型

    • any 类型检查器不检查
    • void 通常见于函数没有返回值时
    • null
    • undefined
    • object 除number,string,boolean,symbol,null或undefined之外的类型。
  • 数组

    • T[]
    • Array<T> 泛型写法
  • 补充

    • 联合类型 属性为多种类型之一
      let name: string | number = 123
      let names: (string | number)[] = [123, '123']
      let names: Array<string | number> = [123, '123']
      let funcs: Array<() => string> = [() => {return '123'}]
      
1
2
3
4
5
  • 元组类型 如 let nameNumber: [string, number] = ['Jenny', 221345]
  • 枚举enum 对JavaScript标准数据类型的一个补充
  • 接口interface
    • ?: 可选属性
    • readonly 只读属性
    • 额外的属性检查
    • 内联类型注解 let name: {first: string;second: string;}
  • 函数
  • 类(跟ES6类类似,但早于ES6)
    let isDone: boolean = false;
    let decLiteral: number = 6;
    let name: string = "bob";
    // 联合类型
    let name: string | number = 1 // string or number options
    // 数组
    let list: number[] = [1, 2, 3];
     // 元祖
    let x: [string, number] = ['hello', 10];
    
    
    enum Color {Red = 1, Green = 2, Blue = 4}
    let c: Color = Color.Green; // 2
    
    let func = (item: string): void => console.log(1)
    function func(item: string): void {}
    
    // interface接口
    interface SquareConfig {
      color?: string; // 可选属性
      width?: number;
      readonly x: number; // 只读属性(除初次赋值,就不能修改)
      [propName: string]: any; // 额外的属性检查
    }
    // 内联类型注解
    let name: {
      first: string;
      second: string;
    } = {
      first: 'John',
      second: 'Doe'
    }
    
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

# # 2.1 Interface & type

两者基本没什么差别,平时开发能用Interface尽量用

    interface Person {
        name: string;
        age: number;
    }
    
    type Person = {
        name: string;
        age: number;
    }
    
    type Animal = Person | string
    
1
2
3
4
5
6
7
8
9
10
11
12

# # 2.2 联合类型

    interface Person {
        name: string;
        age : number | string;
    }
    
1
2
3
4
5

# # 2.3 数组类型

    interface Person {
        name: string;
        age : number;
        schools: string[];
    }
    
1
2
3
4
5
6

# # 2.4 元祖

    let tom: [string, number] = ['Tom', 25];
    
1
2

# # 2.5 可选属性

    interface Person {
        name: string;
        age ?: number;
    }
    
1
2
3
4
5

# # 2.6 任意属性

    interface Person {
        name: string;
        age: number;
        [key: string] : string;
    }
    
1
2
3
4
5
6

# # 2.7 简写类型

    interface Person {
        name: string;
        age: number;
        attr : { label: string; value: string; color?: string; tips?: string }
    }
    
1
2
3
4
5
6

# # 2.8 泛型

    export interface PagingResponseMsg<T> {
      code: number;
      message: string;
      data: T;
      totalCount?: number; // 数据总条数
      pageNo?: number; // 当前页码
      pageSize?: number; // 页大小
      pageCount?: number; // 总页数
    }
    
1
2
3
4
5
6
7
8
9
10

# # 2.9 keyof

    const todo = {
        id: 1,
        name: 'james',
        address: 'shisanjia'
    }
    
    type K = keyof todo // "id" | "name" | "address"
    
1
2
3
4
5
6
7
8
    const todo = {
        id: 1,
        name: 'james',
        address: 'shisanjia'
    }
    
    # K 将是T返回的union类型中的一种
    # 并且返回值为 K[T] 类型
    function prop<T, K extends keyof T>(obj: T, key: K) {
        return obj[key];
    }
    
    prop(todo, 'name')
    prop(todo, 'gender') // ts报错
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# # 2.10 key in keyof T

    interface IPoint {
        x: number
        y: number
    }
    
    type Name<T> = { [P in keyof T]: T[P]}
    
    type real = Name<IPoint> // {x: number, y: number}
    type test2 = Name<{Job: string, Job2: string}> // {Job: string, Job2: string}
    
1
2
3
4
5
6
7
8
9
10

# # 3. TypeScript Vue环境配置

vue-cli3.0安装时有typescript选项,可以非常便捷的在vue项目中应用上typescript环境。其实现方式是通过cli-plugin-typescript (opens new window) (opens new window)插件。如果想知道其过程,可以看其源码或者笔者之前改造的基于vue-cli2.x项目博客文章:TypeScript开发Vue应用。

# # 4. TypeScript Vue使用

TS除了类型系统以及IDE提示外,最重要特性之一就是可以使用装饰器。使用装饰器可以用极简的代码代替以前冗长的代码。以下介绍在Vue 2.x工程项目(Vue3.0计划原生支持Typescript,所以将来或许存在变数)中,必备的三个工具包。

# # 4.1 vue-class-component

vue-class-component (opens new window) (opens new window)是官方维护的TypeScript装饰器,它是基于类的 API,Vue对其做到完美兼容。因为是vue官方出的,所以可以保证其稳定性,但缺点是特性太少了,只有三个feature:

  • Component 官方提供的Component装饰器
  • mixins
  • createDecorator 官方提供的创建装饰器函数。vue-property-decorator/vuex-class库中的各个属性/方法装饰器底层都是调用该函数
    import Vue from "vue";
    import Component from "vue-class-component";
    
    @Component({
      props: {
        propMessage: String
      },
      components: {},
      filters: {},
      directive: {}
    })
    export default class App extends Vue {
      // data
      name:string = 'Simon Zhang'
      helloMsg = 'Hello, ' + this.propMessage // use prop values for initial data
    
      // computed
      get MyName():string {
        return `My name is ${this.name}`
      }
    
      // lifecycle hook
      mounted() {
        this.sayHello()
      }
    
      // methods
      sayHello():void {
        alert(`Hello ${this.name}`)
      }
    }
    
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

# # 4.2 vue-property-decorator

vue-property-decorator (opens new window) (opens new window)完全基于vue-class-component,但它扩展了很多特性,极大的方便Vue的写法。它包含7个装饰器以及1个函数:

  • @Prop
  • @Watch
  • @Component (provided by vue-class-component)
  • @Emit
  • @Model
  • @Inject
  • @Provide
  • Mixins (the helper function named mixins provided by vue-class-component)
    import { Vue, Component, Prop, Watch } from 'vue-property-decorator'
    
    @Component
    export default class YourComponent extends Vue {
      @Prop(Number) propA!: number
      @Prop({ default: 'default value' }) propB!: string
      @Prop([String, Boolean]) propC!: string | boolean
    
      @Watch('person', { immediate: true, deep: true })
      onPersonChanged1(val: Person, oldVal: Person) { }
      /* equal 
        watch: {
        'person': [
          {
            handler: 'onPersonChanged1',
            immediate: true,
            deep: true
          }
        ]
      }
      */
    
      @Emit()
      returnValue() {
        return 10
      }
      /* equal 
        returnValue() {
          this.$emit('return-value', 10)
        }
      */
    }
    
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

更多详细用法看vue-property-decorator README (opens new window) (opens new window),讲解的非常清晰易懂

# # 4.3 Vuex-Class

vuex-class (opens new window) (opens new window)是基于基于vue-class-component对Vuex提供的装饰器。它的作者同时也是vue-class-component的主要贡献者,质量还是有保证的。但不知道vue3.0出来后是否会有官方维护的针对Vuex的TypeScript装饰器。

  • @State
  • @Getter
  • @Mutation
  • @Action
  • namespace
    import Vue from 'vue'
    import Component from 'vue-class-component'
    import {
      State,
      Getter,
      Action,
      Mutation,
      namespace
    } from 'vuex-class'
    
    const someModule = namespace('path/to/module')
    
    @Component
    export class MyComp extends Vue {
      // 多种方式
      @State('foo') stateFoo
      @State(state => state.bar) stateBar
      @Getter('foo') getterFoo
      @Action('foo') actionFoo
      @Mutation('foo') mutationFoo
      // 子模块处理
      @someModule.Getter('foo') moduleGetterFoo
    
      // 如果参数省略,则使用属性名
      @State foo
      @Getter bar
      @Action baz
      @Mutation qux
    
      created () {
        this.stateFoo // -> store.state.foo
        this.stateBar // -> store.state.bar
        this.getterFoo // -> store.getters.foo
        this.actionFoo({ value: true }) // -> store.dispatch('foo', { value: true })
        this.mutationFoo({ value: true }) // -> store.commit('foo', { value: true })
        this.moduleGetterFoo // -> store.getters['path/to/module/foo']
      }
    }
    
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

注意:使用vuex-class等库,需要在tsconfig.json配置中打开TypeScript装饰器。建议在工程目录中设置如下三个配置:experimentalDecorators、strictFunctionTypes、strictPropertyInitialization

    {
      "compilerOptions": {
        // 启用装饰器.启用 vue-class-component 及 vuex-class 需要开启此选项,设置值为true
        "experimentalDecorators": true,
        // 启用 vuex-class 需要开启此选项,设置值为false
        "strictFunctionTypes": false,
        // 是否必须要有初始值。vuex-class最好开启此项,不然所有的@State等装饰器都需要设置初始值。设置值为false
        "strictPropertyInitialization": false,
      }
    }
    
1
2
3
4
5
6
7
8
9
10
11

# # 5. TypeScript 描述文件

typescript的描述文件,以d.ts结尾的文件名,比如xxx.d.ts。大部分编辑器能识别d.ts文件,当你写js代码的时候给你智能提示。declare 全局声明,使得ts可以找到并识别出。

在我们尝试给一个 npm 包创建声明文件之前,需要先看看它的声明文件是否已经存在。一般来说,npm 包的声明文件可能存在于两个地方:

  • 与该 npm 包绑定在一起。判断依据是 package.json 中有 types 字段,或者有一个 index.d.ts 声明文件。这种模式不需要额外安装其他包,是最为推荐的,所以以后我们自己创建 npm 包的时候,最好也将声明文件与 npm 包绑定在一起。
  • 发布到 @types 里。我们只需要尝试安装一下对应的 @types 包就知道是否存在该声明文件,安装命令是 npm install @types/foo --save-dev。这种模式一般是由于 npm 包的维护者没有提供声明文件,所以只能由其他人将声明文件发布到 @types 里了。

假如以上两种方式都没有找到对应的声明文件,那么我们就需要自己为它写声明文件了。

# # 5.1 全局变量 - 应用端补充

针对的是在应用端,无import写法 + 补充npm包的全局变量,(通过 <script> 标签引入第三方库,注入全局变量)。

    // 变量
    declare var aaa:number|string
    
    //函数
    // id has type number | string | undefined
    // ?表示非必须。 !?表示一定要有值
    declare function getName(id?:number|string):string 
    declare function render(callback?:()=>void): string
    
    // 类
    declare class Person {
        static maxAge: number //静态变量
        static getMaxAge(): number //静态方法
    
        constructor(name: string, age: number)  //构造函数
        getName(id: number): string
    }
    
    // 调用:
    // 假设已存在window.aaa,window.getName,window.person。
    // declare好以上就可以直接调用而不会报ts错误
    getName(aaa) // 不会报错
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

对象嵌套使用declare namespace 关键字

# # 5.2 npm包 - 应用端补充

针对的是在应用端,import写法 + 补充npm包的变量(适用于:通过 import foo from 'foo' 导入,符合 ES6 模块规范)

核心:只有在声明文件中使用 export 导出,然后在使用方 import 导入后,才会应用到这些类型声明。

# # 方法一: 文件位置随意,源码中指定 declare module xxx

    // types/any.d.ts
    declare module "abcde" {
        // 导出需要的变量、函数(匹配export导出)
        export let a: number
        export function b(): number
        export namespace c{
            let cd: string
        }
    }
    
    let aaa = require('abcde');
    aaa.b()
    
1
2
3
4
5
6
7
8
9
10
11
12
13
    // 导出是函数本身
    declare module "app" {
        function aaa(some:number):number
        export=aaa
    }
    // 调用
    let app = app();
    app(some)
    
1
2
3
4
5
6
7
8
9

# # 方法二:源码直接export,但文件位置有要求

指定到对应的@types/moduleName/index.d.ts文件,自动去该文件找出export,此时就不需要declare module 'moudleName'了

    // types/abcde/index.d.ts
    export let a: number
    export function b(): number
    
1
2
3
4

总结:d.ts文件(A.d.ts)文件放到哪个目录里,如果是模块化的话那就放到和源码(A.js)文件同一个目录下,如果是全局变量的话理论上放到哪里都可以。————以上说的是未在tsconfig.json 文件里面特殊配置过。

# # 5.3 扩展npm包 (opens new window) (opens new window) - 应用端补充

有时通过 import 导入一个模块插件,可以改变另一个原有模块的结构。此时如果原有模块已经有了类型声明文件,而插件模块没有类型声明文件(最常见的是自定义扩展Vue.prototype.$xxx),就会导致类型不完整,缺少插件部分的类型。

ts 提供了一个语法 declare module,它可以用来扩展原有模块的类型。

    // 如果是需要扩展原有模块的话,需要在类型声明文件中先引用原有模块,再使用 declare module 扩展原有模块
    import Vue from "vue"; // 记得import Vue,引入原有模块
    
    declare module 'vue/types/vue' {
      interface Vue {
        $openDialog: Function;
        $closeDialog: Function;
      }
    }
    
1
2
3
4
5
6
7
8
9
10

在全局变量的声明文件中,是不允许出现 import, export 关键字的。一旦出现了,那么他就会被视为一个 npm 包或 UMD 库,就不再是全局变量的声明文件了。

# # 5.4 发布npm包 - npm源码端补充

# # 1. 自动生成声明文件 (opens new window) (opens new window)

如果库的源码本身就是由 ts 写的,那么在使用 tsc 脚本将 ts 编译为 js 的时候,添加 declaration 选项,就可以同时也生成 .d.ts 声明文件了。此时每个ts文件都会生成.d.ts文件,使得使用方可以单独import每一个ts子文件。

# # 2. 发布声明文件 (opens new window) (opens new window)

  1. 如果声明文件是通过 tsc 自动生成的,那么无需做任何其他配置,只需要把编译好的文件也发布到 npm 上,使用方就可以获取到类型提示了(因为每一个ts文件,都有对应的.d.ts文件)。
  2. 如果是手动写的声明文件,那么需要满足以下条件之一,才能被正确的识别:
    1. 给 package.json 中的 types 或 typings 字段指定一个类型声明文件地址
    2. 在项目根目录下,编写一个 index.d.ts 文件
    3. 针对入口文件(package.json 中的 main 字段指定的入口文件),编写一个同名不同后缀的 .d.ts 文件

使用ts书写源码时,自动生成同时,也可以手动设置d.ts文件。

    declare module 'xxx'
    
    export * from '../lib' // lib是源码入口
    
    
1
2
3
4
5

# # 6. tsconfig.json

这个章节内容有点多,另开Typescript tsconfig.json全解析专题

# # 参考文章

  • 如何编写一个 d.ts 文件 (opens new window) (opens new window)
  • Declaration Merging (opens new window) (opens new window)
  • vue-typescript-dpapp-demo (opens new window) (opens new window)
  • typescript-book (opens new window) (opens new window)
  • templates/module-d-ts (opens new window) (opens new window)
  • Vue2.5+ Typescript 引入全面指南 - Vuex篇 (opens new window) (opens new window)
  • Typescript-tsconfig.json (opens new window) (opens new window)
编辑 (opens new window)
Typescript tsconfig.json全解析
TypeScript在Vue2.x中的坑

← Typescript tsconfig.json全解析 TypeScript在Vue2.x中的坑→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式