typescript使用指北
项目配置
项目配置主要要使用项目配置里的typescript版本,而非电脑全局版本. 避免版本不一样, 造成开发,编译产生的结果不一样. 配置方法:
1. 切换到`.ts`文件 2. 在vs code 地址栏下方, 点击 `Typescript:Version`扩展项, 然后点击`Use Workspace Version`选项. 或者在项目根目录.vscode文件中的setting.json文件中增加配置项: `typescript.tsdk: "node_modules/typescript/lib"` 复制代码
类型
集合类型
string, number, boolean, symbol, null, undefined, bigint
字面量类型
使用一个字面量作为类型. 比如this is string
TypeScript 支持是那种字面量类型: 字符串字面量类型, 数字字面量类型, 布尔字面量类型. 对应的字符串字面量, 数字字面量, 布尔字面量分别拥有与其值一辆的字面量类型.
let specifiedStr: 'this is string' = 'this is string' let specifiedNum: 1 = 1 let specifiedBoolan: false = false let str: string = 'this is string' specifiedStr = str; //ts(2322), 类型string不能赋值给类型this is string str = specifiedStr; // ok 复制代码
字面量类型是集合类型的子类型, 它是集合类型的一种更具体的表达.
使用let和const定义的变量值相同, 但是类型不一致
// 不可变更的常量 const strConst = 'this is string' // 可变变量 let str = 'this is string' 复制代码
使用let声明了一个可变变量, 使用const声明了一个不可变变量.
在缺省类型注解的情况下, 不可变变量的类型为赋值字面量的类型, 可变变量的类型会转换为赋值字面量类型的父类型(比如const str的类型为'this is string', let num = 1, num的类型为number).
. 未缺省情况下, const声明变量的类型即声明的类型.
类类型
声明类的时候, 其实我们也同时声明了一个特殊的类型(确切的将是一个接口类型), 这个类型的名字就是类名, 表示类实例的类型; 在定义类的时候, 我们声明的除构造函数外所有属性, 方法的类型就是这个特殊类型的成员.
派生类如果包含一个构造函数, 则必须在构造函数中调用super方法, 这是Typescript强制执行的一条重要规则.
访问修饰符: public, private, protected
只读修饰符: readonly
存取器: getter, setter
class Dog { private lastName: string = 'stack' get myLastName() { return this.lastNmae } set myLastName(name: string) { if (name === 'xxx') {this.lastName = name} else { console.error('xmxmxm') } } } 复制代码
静态属性: 实例属性和方法,只有类在实例化时才会被初始化. 静态属性可以通过类名直接调用
class MyArray { static dispalyName = 'myArray' static isArray (obj: unknown) { return Object.prototype.toString.call(obj).slice(8, -1) === 'Array' } } console.log(MyArray.displayName) // myArray console.log(MyArray.isArray([])) // true 复制代码
基于静态属性的特性, 我们往往会把与类相关的常量, 不依赖实例this上下文的属性和方法定义为静态属性, 从而避免数据冗余, 进而提升运行性能.
抽象类
一种不能被实例化, 仅能被子类继承的特殊类.
我们可以使用抽象类定义派生类需要实现的属性和方法, 同时也可以定义其他被继承的默认属性和方法.
抽象类与接口区别
接口只能定义成员和类型, 抽象类可以定义成员,类型和方法实现
abstract class Adder { abstract x: number; abstract y: number; // 需要继承实现的 abstract add(): number; displayName = 'Adder'; // 不需要继承实现 addTwice(): number { return (this.x + this.y) * 2 } } class NumberAdder extends Adder { x: number; // 基类使用abstract修饰, 子类必须重新声明 y: number; add(): number { return this.x + this.y } } 复制代码
使用
Number、String、Boolean、Symbol包装类型与基础类型不同,注意区分
ts(2322)是一个静态类型检查的错误,在注解的类型和赋值的类型不同的时候就会抛出这个错误
数组类型的值只有显示添加了元组类型注解后(或者使用 as const,声明为只读元组),TypeScript 才会把它当作元组,否则推荐出来的类型就是普通的数组类型
any 除非有重组的理由, 否则应尽量避免使用any, 并且开启禁用隐式any的设置
unkown(3.0+) 在类型上更安全. 比如可以加将任意类型的值复制给unknown, 但anknown类型的值只能复制给unknown或any. 使用unknown后, typescript会对它做类型检测. 但是, 如果不缩小类型(type narrowing), 我们对unknown的任何操作都会出现错误
void, undefined, null 用到的不多, 可以将undefined的值或类型时undefined的变量赋值给void类型, 反过是void但值是undefined的变量不能赋值给undefined类型.
let undeclared:undefined = undefined let unusable:void = undefined unusable = undeclared // ok undeclared = unusable // ts:2322 复制代码
概念
类型推断, 上下文推断
ts可以根据赋值类型或者上下文,推断出变量的类型
赋值推断
let x1 = 42; //number let x2: number = x1; //ok 复制代码
上下文推断
type Adder = (a:number, b:number) => number; const add:Adder = (a, b) => a + b // 推断出a,b为number类型 复制代码
字面量类型拓宽 Literal widening
可变变量的类型转换为父类型的设计我们称之为"literal widening", 也就是字面量类型拓宽.
const str = 'this is string' // 类型为'this is string' const str1 = 'this is string' // 类型为string specifiedStr = str; // ts(2322) str = specifiedStr 复制代码
应用
// 字符串字面量类型 type Direction = 'up' | 'down' // 布尔字面量类型 type isEnable = true | false // 数字字面量类型 type margin: 0 | 2 | 4 复制代码
类型拓宽(Type Widening)
非严格模式下, TypeScript 对某些特定类型有类似字面量类型拓宽的设计. 比如对null和undefined的类型拓宽. 通过let, var声明的变量如果满足为显示声明类型注解且被赋予了null或者undefined值, 则推断出这些变量的类型为any.
let x = null; // 类型扩宽为any let y = undefined; // 类型拓宽为any type X = typeof x; // X 为any类型 复制代码
类型缩小 Type Narrowing
通过某些操作, 将变量的类型有一个较为宽泛的集合缩小到较小, 较明确的集合.
let func = (param: any) => { if (typeof param === 'string') { // 明确param为string类型, 因此可以调用String原型链上的方法 return param.toUpperCase() } else if (typeof param === 'number') { return param.toFixed(2) } return null } 复制代码
联合类型
使用|表示联合类型(string | undefined
, 表示为string或undefined类型)
交叉类型
freshness
鼠标分别放在x1, x2, x3, x4 上方, x1, number, x2 string, x3 number, x4 number
interface P1 { name: string } interface P2 extends P1 { age: number } function convert(x: P1): number function convert(x: P2): string; function convert(x: P1 | P2): any {} const x1 = convert({name: ''}) // number const x2 = convert({name: '', age: 0}) // string const x3 = convert({name: ''} as P1) // number const x4 = convert({name: '', age: 0} as P2) // number 复制代码
关于x2和x4使用断言和不使用断言的解释:
函数重载是从上往下匹配, 针对x4, 因为P2继承P1, 所以类型为P2的参数和类型为P1的参数一样匹配到第一个重载, 进而推断出来的是number. 为了避免这种情况, 可以将covert的两个重载函数声明调换位置.
TS 里对于对象字面量有一个 freshness 的概念,使用断言是为了避开它,如果不使用断言,匹配会对比对象的每一个属性,因此 x2 就匹配不到 P1,而会匹配上 P2。
作者:岩酱
链接:https://juejin.cn/post/7018872966481969182