Typescript - 基础篇
#
基本概念Typescript
赋予了 Javascript
静态类型检测的能力。
#
特性- Typescript 可以自动推断出大多数类型。
- 所有有效的 JavaScript 代码同时也是有效的 TypeScript 代码。
- 类型检查的错误不会阻塞运行生成的 JavaScript。
- 声明的类型不会打包到 dist 文件中,编译完成后的 Javascript 代码不包含任何类型系统相关的代码。
#
优点- 自动类型推断,一切可溯源。
- IDE 代码补全增强,降低心智负担。
- 第三方库不看文档直接打点就用。
- 映射后端接口,确保字段的准确性。
- 依然可以保持动态类型特性,可以通过 断言或
any
绕过类型检测。 - 类型即文档,通过
ts-doc
自动生成文档 - 易于维护。
- 随便翻一个函数,不用看上下文,就知道参数类型,返回值类型。
- 规范化约束整个团队。
- 易于重构,变量使用 F2 全局重命名
#
缺点- 开发成本
- 学习成本
- 开发机资源消耗
#
类型声明#
interface 关键字interface TypeName { a: string b?: string // ?: 表示非必须 readonly c: string | number // readonly 表示只读属性, '|' 运算符表示联合类型}
类型名称通常遵循大驼峰命名规范, 如
TypeName
,ListDataType
。
declare interface AnyObj { [key: string]?: string}
declare
关键字是可选的,表示声明,通常情况下declare interface
与interface
并无区别, 只有在声明顶层namespace
类型的时候才有明显差异,后面会讲到。
#
interface 继承使用 interface 关键字声明类型时可以继承另一个类型。
interface TypeName2 extends TypeName { c: number // 重写属性 c 为 number 类型 d: number // 增加属性 d}
TypeName2
将包含 TypeName
中所有的属性,并将属性 c
重写为 number
类型,增加属性 d
。
等同于:
interface TypeName2 { a: string b?: string readonly c: number d: number}
继承的类型可以有多个,用 ',' 分隔。
interface TypeName3 extends TypeName1, TypeName2, AnyObj {}
继承属性的重写受到原始类型的约束
#
type 关键字除 interface
外还可以使用 type TypeName = *
来声明类型, 字面量*
只能为合法 TS 类型。
type TypeName = stringtype TypeName = TypeNametype TypeName1 = TypeNametype TypeName2 = { a: string b: string c: string}
使用 type
关键字不能使用 extends
继承其它类型,但可以通过 &
运算符达到相似的效果:
type TypeNameAll = TypeName2 & { x: number x: number z: number}
等同于:
type TypeNameAll = { a: string b: string c: string x: number x: number z: number}
#
interface 声明与 type 声明的区别interface
可以使用 extends 来继承多个类型,而type
可以用&
运算符合并多个类型。type
声明可以直接指定所有类型,而interface
则以对象类型为主。type
声明可以使用typeof
类型推断,interface
不行type
支持联合类型映射,interface
不行
#
类型引用类型引用以 :
为主。?:
表示可能为空,只能在函数形参中使用。
const a: string = ''const b: TypeName = {}const c: TypeName[] | TypeName = [] // 类型联合const d: TypeName1 & TypeName2 = {} // 类型合并
const fn = (arg1: string, arg2?: TypeName) => {}
// 可选参数不能在必要参数前面,否则会报错。
普通变量可以通过联合类型来表示可能为空:
const a: string | undefined = void 0
#
自动类型推断TS 可以自动通过字面量推断出变量类型:
const a = '' // stringconst b = { a: '1', b: 2 } // {a:string; b:number}
// 数组中所有类型一致,推断为 number[]const c = [0, 1, 2, 3] // number[]
// 数组中有多个类型,推断为联合数组const c = [0, 1, '2', '3'] // (string | number)[]
// 通过断言为静态值,可以将数组推断为元组const c = [0, 1, '2', '3'] as const // [0, 1, '2', '3']
#
基本类型#
字符串类型 stringconst a: string = ''
#
数值类型 numberconst a: number = 0
NaN
属于 number
#
布尔类型 booleanconst a: boolean = false
#
symbolconst a: symbol = Symbol()
#
对象类型 objectconst a: object = {}
const a: Record<string, unknown> = {} // 推荐
const a: { [key: string]: unknown } = {}
上面三种都是动态属性的对象类型声明,不包含详细属性的类型声明。
可以使用以下方法直接声明对象的详细属性:
const a: { key1: string; key2: number } = { key1: '---', key2: 0,}
为了更好的复用类型,可以通过
type
和interface
关键字来声明
#
数组类型 Arrayconst a: Array<string> = []
const a: string[] = [] // 推荐
#
元组类型const a: [string, number, boolean] = ['1', 2, true]
#
函数类型 Functioninterface FnType { (arg1: string): void}
// type 形式的函数类型声明相对于 interface 更加直观type FnType = (arg1: string) => void
function Foo(arg1?: string): string | undefined { return arg1}
#
void & null & undefinedtype TypeName = voidtype TypeName = undefinedtype TypeName = null
这三者都表示空,
void
同时兼容undefined
和null
#
any & unknowntype TypeName = anytype TypeName = unknown
any 和 unknown 都允许分配任何类型,不同点在于,any 放弃了所有的类型检测,分配和被分配都不受任何约束,而 unknown 只在分配时不受约束,被分配时依然会受到约束。
#
never永远不会有值的类型
type TypeName = never
#
联合类型多个类型的联合
type TypeName = 1 | 2 | '3' | '4' | AnyType
#
泛型泛型可以允许类型之间进行参数传递
type TypeName<T, R> = (arg: T) => R
#
类型推断function Foo(arg1: string, arg2: TypeName) { // ... return string}
type FooType = typeof Foo
使用 typeof
关键字可以进行主动类型推断,最后得到的 FooType
:
type FooType = Foo(arg1: string, arg2: TypeName) => string
#
故障排除技巧#
断言断言可以直接给字面量分配类型
const a = {} as TypeName
#
非空断言 !.const a: string | undefined = ''
a!.length
#
可选链操作符 ?.可选链操作符-MDN是 ES2020 中的语法 (不是 TS 特性), 可以用来快速解决类型为空的问题,由于是在运行时添加判断,也比较安全。
const a: string | undefined = ''
a?.length
#
any被分配 any 类型的变量可以在类型系统中完全逃逸
const a: any = ''
// 这些都不会报错a.lengtha.xxxa.bbb()
非不得已不要使用
#
@ts-ignore使用 // @ts-ignore
可以取消文件下一行的 TS 错误提示
// @ts-ignorewindow.baiduAuth = baiduAuth
非万不得已不要使用
#
注释类型相关的注释使用 /** */
形式, 可以在 IDE 中获得更好的支持
示例:
/** * 自定义路由参数 */interface RouteParams { /** 页面标题 */ title?: string /** 是否显示导航栏 默认:true */ showNavbar?: boolean /** 路由的 name,全局唯一 */ name: string /** 自定义参数, 视情况而定 */ type?: string}
这种注释可以在类型引用处带出详细注释,如鼠标移上提示,代码自动补全等。而 //
注释不会有这种效果
#
导出及引用与 Javascript 类似,TS 类型可以通过 import
关键字导入,export
导出。
导出:
export type TypeName = string
导入:
import { TypeName } from './types'
// import type 可以导入推断后的类型import type { TypeName } from './types'
#
全局导出方法一: 创建一个以 .d.ts
为后缀的文件,里面声明的类型可以被全局访问。
方法二: 使用 declare global
declare global { interface TypeName { // ... }}
在
.d.ts
文件中一旦使用了import
导入了其他模块,那么该文件会被识别为模块,不会自动导出类型到全局,需要使用declare global
手动将类型暴露到全局
#
tsconfig通常以 tsconfig.json
文件的形式存在在项目根目录, 主要用处是控制编译器以什么样的规则编译 ts 代码
示例:
{ "compilerOptions": { "target": "es5", "module": "commonjs", "strict": true, "jsx": "react", "importHelpers": true, "moduleResolution": "node", "resolveJsonModule": true, "experimentalDecorators": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "suppressImplicitAnyIndexErrors": true, "sourceMap": true, "locale": "zh-CN", "baseUrl": ".", "types": ["webpack-env", "node"], "paths": { "@/*": ["src/*"], "@root/*": ["*"] }, "lib": ["esnext", "dom", "dom.iterable", "scripthost", "es5"] }, "include": ["types", "src"], "exclude": ["node_modules"]}