Typescript - 基础篇

· 5 分钟阅读

基本概念#

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 表示只读属性, '|' 运算符表示联合类型}

类型名称通常遵循大驼峰命名规范, 如 TypeNameListDataType

指定 key 的类型
declare interface AnyObj {  [key: string]?: string}

declare 关键字是可选的,表示声明,通常情况下 declare interfaceinterface 并无区别, 只有在声明顶层 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']

基本类型#

字符串类型 string#

const a: string = ''

数值类型 number#

const a: number = 0

NaN 属于 number

布尔类型 boolean#

const a: boolean = false

symbol#

const a: symbol = Symbol()

对象类型 object#

const a: object = {}
const a: Record<string, unknown> = {} // 推荐
const a: { [key: string]: unknown } = {}

上面三种都是动态属性的对象类型声明,不包含详细属性的类型声明。

可以使用以下方法直接声明对象的详细属性:

const a: { key1: string; key2: number } = {  key1: '---',  key2: 0,}

为了更好的复用类型,可以通过 typeinterface 关键字来声明

数组类型 Array#

const a: Array<string> = []
const a: string[] = [] // 推荐

元组类型#

const a: [string, number, boolean] = ['1', 2, true]

函数类型 Function#

interface FnType {  (arg1: string): void}
// type 形式的函数类型声明相对于 interface 更加直观type FnType = (arg1: string) => void
function Foo(arg1?: string): string | undefined {  return arg1}

void & null & undefined#

type TypeName = voidtype TypeName = undefinedtype TypeName = null

这三者都表示空,void 同时兼容 undefinednull

any & unknown#

type 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}

这种注释可以在类型引用处带出详细注释,如鼠标移上提示,代码自动补全等。而 // 注释不会有这种效果

图 1

导出及引用#

与 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"]}

TS 类型闯关游戏

https://github.com/type-challenges/type-challenges