typescript如何快速上手应用于vue项目的书写申明?


🙂前言

typescript 如何快速上手应用于 vue 项目?vue 项目引入了 typescript 后应该如何书写申明?

小波基于自己写 taro3+vue3+tsx+pinia+pnpm 微信小程序 的实际开发做个总结。

当然主要是快速上手,还有些进阶方面的小波也还在学习中。

typescript 虽然相对以前工作方式表面增加了一定工作量,但同时也解决了以前写法容易忽视的bug或是代码不严谨,不优雅,同时装了相关 vscode 插件还能自动推导类型,语法提示,类型信息的预校验,着实写完一次后还是感觉棒棒的。


🧐typescript 申明的作用

  • 用来为已存在的 JS 库提供类型信息。
  • 声明文件可以让我们不需要将JS重构为TS,只需要加上声明文件就可以使用系统。
  • 在 TS 项目中使用第三方库时,就像在用 TS 一样,都会有代码提示、类型保护等机制,极大的提高了开发效率,降低了心智负担。
  • 类型声明在编译的时候都会被删除,不会影响真正的代码。

🧐typescript 必备 vscode 插件

  • volar 全家桶那是必须的
  • JSON to TS(快速把后端接口返回的json数据转成ts申明,当然有些地方可能还是需要自己二次稍稍修改)
  • ESLint

🤔typescript 如何引入项目

无论什么项目,首先安装 typescript 依赖

1
pnpm add typescript -D

如果是 vue, 安装 vue-tsc

对于单文件组件,你可以使用工具 vue-tsc 在命令行检查类型和生成类型声明文件。 参看 vue3 官网文档

1
pnpm add vue-tsc -D

如果是 node 服务端

1
pnpm add ts-node -D

🤔typescript 配置文件 tsconfig.json

以小波写的 taro3+vue3+tsx 微信小程序项目为例

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
52
53
54
55
56
57
58
{
"compilerOptions": {
// target 指的是 TypeScript transpile 出来想要生成什么 version 的 ECMAScript
"target": "es2017",
// 如果是 for Node.js 那么 module 的值是 "CommonJS" 或者 "ES2020".
"module": "commonjs",
// 把 .ts 里面的注释 comment 在 transpile 过程中移除. .js file 里面就看不到任何 comment 了
"removeComments": false,
// 常量枚举 preserveConstEnums 选项 (简单讲就是ts相关申明最后编译都不会到生产的js文件,这里单独对枚举做处理)
"preserveConstEnums": true,
// 有 2 个值, "Classic" 和 "Node". 默认是 Classic, 但凡你有用 node_modules 就设置成 Node 就对了.
"moduleResolution": "node",
// 想做到类似 C# 那种 Data Annotation Validation, 反射, 就需要借助 TypeScript 的 Decorators 功能. ***
"experimentalDecorators": true,
// “noImplicitAny”不允许使用隐式any类型,默认为false
"noImplicitAny": false,
// 和 module & moduleResolution 配合使用的
"allowSyntheticDefaultImports": true,
// 指 transpile 后的 .js 要 output 到哪里. 默认情况下是 output 到 .ts 的 sibling. 实际项目里没发现,不太理解?
"outDir": "lib",
// 检测Typescript中未使用的变量
"noUnusedLocals": true,
// 检测Typescript中未使用的变量 和 noUnusedLocals 配合使用
"noUnusedParameters": true,
// strictNullChecks 标志使处理 null 和 undefined 更加明确,让我们不必担心是否忘记处理 null 和 undefined。
"strictNullChecks": true,
// transpile 的 JavaScript 阅读非常不友好, runtime debug 就会很困难. 开启 sourceMap compiler 就会创建一个 .map 的 file
"sourceMap": true,
// 设置baseUrl来告诉编译器到哪里去查找模块。
"baseUrl": "./",
// 可以告诉编译器生成这个虚拟目录的roots
"rootDir": ".",
// TypeScript具有三种JSX模式:preserve(后续可被babel处理),react和react-native(相当于preserve)
"jsx": "preserve",
// 如果想 import js 文件那么需要在 tsconfig 设置 allowJs
"allowJs": true,
// 导入json
"resolveJsonModule": true,
// 我们需要让 TypeScript compiler 知道第三方包手动加的 .d.ts 这个 folder, 所以需要添加上这个配置项
"typeRoots":
"node_modules/@types"
],
// types 做用是进一步 filter, 配合 typeRoots 使用,这里值只有vue3才被纳入
"types": ["@tarojs/components/vue3"],
// config/index.js中别名 alias
"paths": {
// "@/*": ["src/*"],
"@/components/*": ["./src/components/*"],
"@/utils/*": ["./src/utils/*"],
// "Utils/*": ["./src/utils/*"]
}
},
// ts扫描的范围
"include": ["./src", "./types"],
// compileOnSave 是声明是否需要在保存时候自动触发 tsc 编译的字段
"compileOnSave": false,
}

allowSyntheticDefaultImports 生动解释参考 TypeScript Tips (1) - allowSyntheticDefaultImports[4]

提示

通常装好 typescript 后,项目根目录还有个types文件夹,里面的 global.d.ts表示全局的申明。

不需要通过export导出和Import导入,这是typescript的默认规则

如果要自己页面私有使用的申明,则自己在页面文件夹建个x.d.ts,同时需要使用export导出和Import导入配合使用

当然在业务逻辑页面x.ts或者x.tsx里写申明也是可以的,但是小波建议还是单独写在专用申明文件里吧。


😚typescript 申明vue项目实例书写规则

布尔值

1
let isDone: boolean = false;

数字

1
let decLiteral: number = 6;

字符串

1
let name: string = "bob";

数组

[]表示数组,则类型放在前面

1
let list: number[] = [1, 2, 3];

Array表示数组,则类型用断言放在后面

1
let list: Array<number> = [1, 2, 3];

提示

<>类型断言有两种形式。 其一是“尖括号”语法:

1
2
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

as语法

1
const list = [] as string[]

两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。

元组 tuple

算是数组的扩展吧,相当于 const 常量一样,定死了数组位置和值

1
2
let x: [string, number] = [ '111', 111, 222]
//不能将类型“[string, number, number]”分配给类型“[string, number]”。源具有 3 个元素,但目标仅允许 2 个。ts(2322)

枚举

enum类型是对 JavaScript 标准数据类型的一个补充。 像 C# 等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字。

默认:从0开始递增

1
2
3
4
enum Color {Red, Green, Blue}
let c: Color = Color.Green
console.log(c)
// 1

也可以自己指定初始值

1
2
3
4
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green
console.log(c)
// 2

场景1:字符串枚举,后端可能返回的json名称不标准或者不统一,可以通过枚举进行重新规范

1
2
3
4
5
6
7
8
9
10
11
12
enum jsonType {
userName = 'name',
userId = 'id',
userNick = 'nick'
}
// 假定一个拿到的返回值
const c = 'nick' as jsonType;
// 这里则可以使用ts枚举申明来进行判断
if(c === jsonType.userNick) {
console.log(333, c)
}
// 333 "nick"

any

有时候,我们会想要为那些在编程阶段还不清楚类型的变量指定一个类型。 这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。 这种情况下,我们不希望类型检查器对这些值进行检查而是直接让它们通过编译阶段的检查。 那么我们可以使用 any类型来标记这些变量:

小波个人项目中使用,通常是第三方库方法返回无法更好完成ts申明的情况可以先用any来解决报错,后续代码完成后ts会根据上下文推导成正确的申明再去除any

例如 vue3 的inject获取全局变量时

1
2
// 获取系统相关信息
const customGlobalData: any = inject('$customGlobalData')

void

某种程度上来说,void类型像是与any类型相反,它表示没有任何类型。

当一个函数没有返回值时,你通常会见到其返回值类型是 void

1
const handleLike = (item: commentType): void => {}

null 和 undefined

TypeScript里,undefinednull两者各自有自己的类型分别叫做undefinednull

void相似,它们的本身的类型用处不是很大:

小波个人总结就是尽量少用这个吧,原来 js 因为这个属性写判断已经很恶心了。

1
2
3
// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;

never

never类型表示的是那些永不存在的值的类型。

例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型;

变量也可能是 never类型,当它们被永不为真的类型保护所约束时。

Typescript报错 类型“any”的参数不能赋给类型“never”的参数,更多时候小波遇见的场景,通常就是定义了变量却未申明,后续又对变量操作则容易出现这样的提示

1
2
3
4
const tempArray = []
array.forEach(item => {
tempArray.push(item)
});

object

object表示非原始类型,也就是除numberstringbooleansymbolnullundefined之外的类型。

例如小波在使用 nutui 组件的 nut-tabbar 时。组件返回的 item ,因为是组件固定返回所以项目中也未使用 interface 定义接口,那就直接用 object

1
2
3
4
5
<nut-tabbar
onTabSwitch={handleTabSwitch}
>
...
</nut-tabbar>
1
2
3
const handleTabSwitch = (item?: object, index?: string): void => {
...
}

接口 interface

通常使用在后端返回接口的值做申明

简单场景:函数的参数这样不需要公共使用则直接书写对象

1
2
3
4
5
const eventChannel = current.getOpenerEventChannel()
// 接收 A页面的 events 中的 pushDateToComment 传递的数据
eventChannel.on('pushDateToComment', (res: { commentTotal: number }) => {
state.commentTotal = res.commentTotal
})

公共场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Shape {
color: string;
}
interface listObjType extends Shape {
list?: Array<listType>
total: string
readonly listCount: number
getSuccess: boolean
}
interface listType {
img: string
title: string
description: string
like: number
avatar: string
author: string
visite: number
category: string
id: string
createdAt: string
}

提示

extends表示继承,当然也可以继承多个

1
2
3
4
5
6
7
8
9
10
11
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface listObjType extends Shape, PenStroke {
total: string
listCount: number
getSuccess: boolean
}

? 表示 可选属性,也就是绑定申明的对象,即使没有list这个值也不会报错

readonly 表示 只读

1
2
3
4
5
6
7
// 只要进行了1次绑定值,则后续都无法修改
const tempJson: listObjType = {
...
listCount: 1
}
tempJson.listCount = 10
// 报错:无法为“listCount”赋值,因为它是只读属性。ts(2540)
函数类型,可索引的类型,类类型,混合类型

这几个进阶类别可以自己查阅下官方文档[3],像函数类型我自己觉得没必要还用Interface来写,这不是完全给自己增加难度系数么,明明直接用type就行

函数

场景1:直接申明

1
2
3
const handleClickSmile = (item: { icon: string; text?: string }): void => {
state.textareaVal += item.icon
}

场景2:外部引用

1
2
3
4
5
6
7
8
// x.d.ts文件
export type handleSearchType = (quickSearchValue: string) => void

// x.ts文件 业务代码
import { handleSearchType } from '../types/index'
const handleSearch: handleSearchType = quickSearchValue => {
...
}

当然参数同样都是支持 | 可选参数和默认参数

1
2
3
const handleClickSmile = (item: { icon: string | number = 1; text?: string }): void => {
state.textareaVal += item.icon
}

this和箭头函数

简单讲,非箭头函数中的this值取决于这个函数是怎样被调用的,大概理解会是向上级查询

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
52
53
54
55
56
// 默认场景
const circle = {
radius: 10,
getRadius() {
console.log(11111111, this.radius)
}
}
circle.getRadius() // 打印 10
// 使用临时变量self
const circle2 = {
radius: 10,
outerDiameter() {
const self = this
const innerDiameter = function () {
console.log(22222, self.radius)
}
innerDiameter()
}
}
circle2.outerDiameter() // 打印 10

// 直接使用 this 的问题
const circle3 = {
radius: 10,
outerDiameter() {
const innerDiameter = function() {
console.log(333, this.radius);
};
innerDiameter();
}
};
// circle3.outerDiameter(); // 打印NaN 直接报错 Cannot read property 'radius' of undefined

// 使用.bind(this)
const circle4 = {
radius: 10,
outerDiameter() {
let innerDiameter = function() {
console.log(444, this.radius);
};
innerDiameter = innerDiameter.bind(this);
innerDiameter();
}
};
circle4.outerDiameter(); // 打印 10
// 使用箭头函数
const circle5 = {
radius: 10,
outerDiameter() {
const innerDiameter = () => {
console.log(555, this.radius);
};
innerDiameter();
}
};
circle5.outerDiameter(); // 打印 10

.bind() 更多说明参考 JS【详解】函数 .bind()[6]

declare

  1. declare声明的变量和模块后,其他地方不需要引入,就可以直接使用了,和export一起使用放在同一个 x.d.ts 则需要导出

  2. 同时也是区分 js 和 ts 的标识

  3. interfacetype不需要使用declare

  4. declare 的申明变量通常用在申明文件中做判断,例如 typeof

    1
    2
    3
    4
    declare const ANIMATION = "animation";
    export declare interface TransitionProps extends BaseTransitionProps<Element> {
    type?: typeof TRANSITION | typeof ANIMATION;
    }

type

type作用就是给类型起一个新名字,支持基本类型、联合类型、元祖及其它任何你需要的手写类型,常用于联合类型

1
2
3
4
5
6
7
8
9
10
11
//基本类型
type test = number;
let num: test = 10;
// 对象
type userOjb = {name:string}
// 函数
type getName = ()=>string
// 元组
type data = [number,string]
// 联合类型
type numOrFun = Second | getName
type和interface的区别
  1. 和接口一样,用来描述对象或函数的类型(小波个人还是建议用interface)

    1
    2
    3
    4
    5
    type User = {
    name: string
    age: number
    };
    type SetUser = (name: string, age: number)=>void;
  2. 接口的继承 extends type可以通过交叉实现,当然interface也能同时 extends type的申明,type也能交叉interface的申明

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // type 交叉 interface
    interface Name1 {
    name: string;
    }
    type User = Name1 & {
    age: number;
    }

    // type 交叉 == interface 的 extends
    type Name2 = {
    name: string;
    }
    type User = Name2 & {age: number}

    // interface extends type
    interface User extends Name2 {
    age: number;
    }

映射类型
1
2
3
4
5
6
7
8
type Keys = "name" | "sex"
type DuKey = {
[Key in Keys]: string //类似 for ... in
}
let stu: Dukey = {
name: 'wang'
sex: 'man'
}

重载,剩余参数,泛型,命名空间,声明合并等请自己查看下官方文档[3]或问问度娘,小波晚点再更新,也还在学习中…


🙂小波用到的相关参考资料链接


关注廿壴(GANXB2)微信公众号

『旅行者』,帮小波关注一波公众号吧。

小波需要100位关注者才能申请红包封面设计资格,万分感谢!

关注后可微信小波,前66的童鞋可以申请专属红包封面设计。

THE END
作者
chopin gump chopin gump
小尾巴
Stay Hungry, Stay Foolish「求知若饥, 虚心若愚」 — 廿壴(GANXB2)
许可协议
typescript如何快速上手应用于vue项目的书写申明?
https://blog.ganxb2.com/11259.html
微信

微信

支付宝

支付宝

- ( ゜- ゜)つロ 填写QQ邮箱自动获取头像亦更快获得回复通知
评论区显示“刷新”,多点几下或过会儿再来康康 _(≧∇≦」∠)_