深入理解TypeScript
程序员文章站
2024-02-19 18:37:40
...
TypeScript是什么
TypeScript 是一种由微软开发的*和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
TypeScript是具有类型语法的JavaScript,是一种强类型编程语言,并且提供了最新的JavaScript特性。它经过代码编译后最终生成为JavaScript,能够在任何运行JavaScript的地方运行,比如浏览器、NodeJS、Electron、Deno等环境中。
TypeScript的作用
- 避免在开发阶段的一些问题(类型错误的方法等)
- 较好的语法提示(组件或者函数的必填/可选参数一目了然等)
- 形成类型思维
- 代码更健壮、更易读
- 减少团队协作的沟通和文档书写
基本类型
String
let name: string = '王同超'
Number
let age: number = 18
Boolean
let isStudy: boolean = true
Undefined Null
let myname: undefined = undefined
let myname: undefined = void 0
let myname: null = null
// strictNullChecks配置项可以配置undefined和null是否
// 是其他类型的子类型
Void
let name: void = undefined
let func: () => void = () => {}
Symbol
let myname: symbol = Symbol('王同超');
BigInt
let bigNumber: bigint = 10n
Array
let arr: number[] = [1, 2, 3]
// or
let arr: Array<string> = ['hello', 'wrold']
Tuple
元组类似于数组,与数组不同的是,数组只能定义固定类型的值而元组可以定义不同类型的值以及元素的数量
let tup: [number, string] = [18, '王同超']
// 这里定义的类型以及数量必须相同,否则会报错
Enum
1.数字枚举
enum Code{
success,
faild,
noAuth
}
// 编译成JavaScript为
var Code;
(function (Code) {
Code[Code["success"] = 0] = "success";
Code[Code["faild"] = 1] = "faild";
Code[Code["noAuth"] = 2] = "noAuth";
})(Code || (Code = {}));
2.字符串枚举
enum Code {
success = 'SUCESS',
faild = 'FAILD',
noAuth = 'NOAUTH'
}
// 编译成JavaScript为
var Code;
(function (Code) {
Code["success"] = "SUCESS";
Code["faild"] = "FAILD";
Code["noAuth"] = "NOAUTH";
})(Code || (Code = {}));
3.异构枚举
enum Code {
success,
faild = 'FAILD',
noAuth = 1,
error
}
// 编译成JavaScript为
var Code;
(function (Code) {
Code[Code["success"] = 0] = "success";
Code["faild"] = "FAILD";
Code[Code["noAuth"] = 3] = "noAuth";
Code[Code["error"] = 4] = "error";
})(Code || (Code = {}));
4.常量枚举
const enum Code {
success,
faild,
noAuth
}
console.log(Code.success)
// 编译成JavaScript为
console.log(0 /* success */);
Any
任何类型都是any的“子类型”,因此any也没成为类型系统的*类型(全局超级类型)
大量使用any类型会导致ts的类型保护形同虚设,在TypeScript 3.0 引入了unknown类型
let data: any
data = 18
data = '王同超'
data = () => { alert('No problem') }
// noImplicitAny 配置项能够配置是否允许隐式any(ts类型推断能够推断出类型)
Unknown
类似于any可以定义为任意类型,但是将unknown赋值给其他类型时会有限制,调用unknown类型数据方法也会报错
let data: unknown
data = 1
data = '王同超'
data = { name: '王同超' }
data = () => {}
let data1: unknown = data // 没问题
let data2: any = data // 没问题
let data: number = data // error
let data: string = data // error
data.name // error
Never
never类型表示永远不存在的值。常用在抛出异常或者没有返回值的函数等
// 场景1
function fail(msg: string): never {
throw new Error(msg);
}
// 场景2
function fn(x: string | number) {
if (typeof x === "string") {
// do something
} else if (typeof x === "number") {
// do something else
} else {
x; // has type 'never'!
}
}
// 场景3
switch(x: 1|2) {
case 1:
break
case 2:
break
default:
const exhaustiveCheck: never = x
break
}
字面量类型
let myname: 'wtc' = 'wtc'
let age: 18|19 = 18
接口
用来声明类型,是行为的抽象
// 声明类型
interface IStudy {
result: boolean
}
interface ITest {
name: string
readonly age: number // 只读
isDone?: boolean // 可选
goSleep: () => void
goStudy: (type: string) => IStudy
goEat: (x: number, y: string) => boolean
}
// 接口会自动进行合并
interface ITest {
goAway: () => void
}
let test: ITest = {
name: '王同超',
age: 18,
isDone: true,
goSleep: () => {},
goStudy: (type) => ({ result: true }),
goEat: function(x, y) {
this.age = '19' // error 因为age是只读属性不允许修改
return !!(x && y)
},
goAway: () => void
}
1.接口继承接口
interface ITest0 {
goAway(x: number): number
}
interface ITest1 {
name: string
}
// 单继承
interface ITest2 extends ITest1 {
age: number
}
// 多继承
interface ITest3 extends ITest1, ITest0 {}
let data: ITest2 = {
name: '王同超',
age: 18
}
let data1: ITest3 = {
name: '王同超',
goAway: (x) => x
}
2.接口继承类
class Test1 {
name = '王同超'
age = 18
goAway(x: number) {
return x
}
}
class Test2 {
goEat() {}
}
interface ITest2 extends Test1, Test2 {
// age: string 不能修改class中已有字段类型
mood: 'good'| 'bad'
}
let data: ITest2 = {
name: 'WTC',
age: 19,
goAway: (x) => x,
mood: 'good',
goEat: () => undefined
}
3.类实现接口/类型别名
interface ITest1 {
name: string
age: number
goAway(x: number): number
}
interface ITest2 {
mood: string
}
class Test implements ITest1, ITest2 {
name = '王同超'
age = 18
mood = 'good'
goAway(x: number) {
return x
}
}
4.类继承类实现接口
interface ITest {
name: string
age: number
goAway(x: number): number
}
class A {
name = '王同超'
}
// 如果父类已经实现,子组件可以选择不实现
class B extends A implements ITest {
age = 18
goAway(x: number) {
return x
}
}
联合类型
相当于或
interface ITest {
name: string | null // 定义name为字符串或者null
}
// 形参x的值可能为number类型或者undefined
function test1 (x: number | undefined) {}
交叉类型
相当于合并
interface ITest1 {
name: string
}
interface ITest2 {
age: number
}
type Itest = ITest1 & ITest2
let data: ITest1&ITest2 = {
name: '王同超',
age: 18
}
// or 等价
let data: Itest = {
name: '王同超',
age: 18
}
// 示例2 同名基础类型属性的合并,不同类型会合并为never类型
interface IA {
type: number
age: number
}
interface IB {
type: string
age: number
}
type IC = IA & IB
let data: IC = {
type: 1, // error Type 'number' is not assignable to type 'never'
age: 18
}
// 示例3 同名非基础类型属性的合并,会将属性进行合并
interface IA {
type: {
name: string
}
age: number
}
interface IB {
type: {
age: number
}
age: number
}
type IC = IA & IB
let data: IC = {
type: { // 这里将属性进行了合并
name: '王同超',
age: 18
},
age: 18
}
类型别名
相当于interface的作用,一般将多个类型重命名为单个
type ITest1 = string | number | undefined
interface ITest2 {
name: string
}
interface ITest3 {
age: number
}
type ITest4 = ITest2 & ITest3
let data: ITest4 = { name: '12', age: 1 }
类型断言
当你认为当前环境下的值的类型,比TypeScript类型推断的更准确时使用
let age: number = (myname as string).length
// or 最好不要在jsx中使用以下写法
let age: number = (<string>myname).length
function iTakeFoo(foo: 'foo') {}
const test = {
someProp: 'foo' as 'foo' // 不使用类型断言会被推断成string
}
iTakeFoo(test.someProp)
非空断言
function test(func: (() => void) | undefined) {
func!()
}
// 示例2
let test1: string | undefined | null
let test2: string = test1!
// 示例3 声明变量一定有值
let test1!: number
// TODO
let test2 = test1 * 2
索引签名
指定索引的类型,索引签名分为数字索引和字符串索引
// 字符串索引
interface IData {
[key: string]: string
}
// 数字索引
interface IData2 {
1: '王同超'
[key: number]: string
}
let data: IData = {}
data.name = '王同超'
类型保护
类型保护允许你使用更小范围下的对象类型
// typeof
function test1(x: string | number) {
if (typeof x === 'string') {
// 该作用域内x推断为string
// 这里可以使用string类型的方法,不能使用number类型方法
}
}
// instanceof
class Foo {
foo = 123;
common = '123';
}
class Bar {
bar = 123;
common = '123';
}
function doStuff(arg: Foo | Bar) {
if (arg instanceof Foo) {
console.log(arg.foo); // ok
console.log(arg.bar); // Error
}
if (arg instanceof Bar) {
console.log(arg.foo); // Error
console.log(arg.bar); // ok
}
}
// in
interface A {
x: number;
}
interface B {
y: string;
}
function doStuff(q: A | B) {
if ('x' in q) {
// q: A
} else {
// q: B
}
}
// 字面量类型保护
type Foo = {
kind: 'foo'; // 字面量类型
foo: number;
};
type Bar = {
kind: 'bar'; // 字面量类型
bar: number;
};
function doStuff(arg: Foo | Bar) {
if (arg.kind === 'foo') {
console.log(arg.foo); // ok
console.log(arg.bar); // Error
} else {
// 一定是 Bar
console.log(arg.foo); // Error
console.log(arg.bar); // ok
}
}
类型推断
TypeScript会自动根据代码推断出类型
interface IT1 {
type: 1,
name: '王同超'
}
interface IT2 {
type: 2,
age: 18
}
function test(data: IT1|IT2) {
// 自动推断为IT1类型
if (data.type === 1) {
console.log(data.name)
}
}
let myname = '王同超' // let myname: string
let age = '18' // let age: number
// function goAway(x: number, y: number): number
function goAway(x: number, y: number) {
return x + y
}
重载
// 示例1 函数重载
function test(x: number): void
function test(x: number, y: number): string
function test(x: number, y: number, z: number): number
function test(x: number, y?: number, z?: number): any {
if (y !== undefined) {
return String(y)
} else if (y !== undefined && z !== undefined) {
return x + y +z
}
}
test(1) // function test(x: number): void
test(1, 2) // function test(x: number, y: number): string
test(1, 2, 3) // function test(x: number, y: number, z: number): number
// 示例2 类重载
class A {
goSleep(x: number, y: number): void
goSleep(x: string, y: string): void
goSleep(x: number): void
goSleep(x: number|string, y?: number| string) {
if (y !== undefined) {
if (typeof y === 'number') {} else {}
} else {
if (typeof x === 'number') {} else {}
}
}
}
const AInstance = new A()
AInstance.goSleep(1)
泛型
在某些情况下并不需要依赖对象的实际类型,但是仍然想提供强制的类型约束时使用,为未来的可能存在的数据类型做扩展。
1.泛型接口
interface ITest<T> {
name: string
goAway: (x:T) => T
}
const data: ITest<string> = {
name: '王同超',
goAway(x) { // (property) ITest<string>.goAway: (x: string) => string
return x
}
}
data.goAway(1) // error
2.泛型类型别名
type ITest<T> = {
name: string
goAway: (x:T) => T
}
const data: ITest<string> = {
name: '王同超',
goAway(x) { // (property) ITest<string>.goAway: (x: string) => string
return x
}
}
data.goAway(1) // error
3.泛型函数
function test<T, K>(para: T):K[] {
// ToDo
return []
}
test<number, string>(1)
4.泛型类
泛型类不能约束静态成员
class A<T> {
myname: T[]
constructor() {
this.myname = []
}
// static goTes(): T {} // 泛型类不能约束静态成员
goAway(): T {
return this.myname[0]
}
}
new A<string>()
5.泛型的约束(继承)和默认值
interface ILength {
length: number;
}
function identity<T extends ILength, K=string>(arg: T, arg2: K): T {
console.log(arg.length); // 没问题
return arg;
}
identity('12', 12)
6.错误使用泛型
// 没有意义
interface ITest<T> {
(x: T): void
}
type ITest = <T>(x:T) => void
类
- public - 公共的(默认),类能使用,实例能使用,也能被继承
- static - 静态的,类能使用,实例不能使用,能够被继承
- readonly - 只读的,除了不能被修改其余和public相同
- private - 私有的,只能类本身使用,实例不能使用,也不能被继承(如果加在constrctor上,该类不能被实例化和继承)
- protected - 受保护的,只能在类和子类中使用,不能在实例中使用(如果加在constrctor上,该类不能被实例化,只能被继承)
class A {
public name: string
private age: number
protected mood = 'good'
readonly study = 'day'
constructor(name: string, age: number) {
this.name = name
this.age = age
}
sayAge() {
console.log(this.age)
}
static goEat() {
console.log('goEat')
}
}
A.goEat()
let AInstance = new A('ts', 18)
let a = AInstance.name
let b = AInstance.sayAge()
let c = AInstance.study
// AInstance.study = 'month' // error 只读属性不能被修改
// AInstance.age // error 私有属性,实例不能调用
// AInstance.mood // error 受保护属性,实例不能调用
class B extends A {
constructor() {
super('ts', 18)
}
goTest() {
// console.log(this.age) // error 私有变量不能被继承
console.log(this.mood) // 受保护变量可以被继承
}
}
B.goEat()
let BInstance = new B()
let d = BInstance.name
let e = BInstance.sayAge()
let f = BInstance.study
// BInstance.study = 'month' // error 只读属性不能被修改
抽象类
使用abstract
关键字声明的类,被称为抽象类。抽象类包含1个或者多个抽象方法不能被实例化,只能被继承。
// 抽象类中可以指定具体方法也可以只定义抽象方法,
// 指定了具体方法,子类不用再实现。
// 指定了抽象方法,子类需要实现。
abstract class A {
myname: string
age: number
constructor(name: string, age: number) {
this.myname = name
this.age = age
}
goSleep() {
console.log('goSleep')
}
abstract goAway(): void
}
class B extends A {
constructor(name: string, age: number) {
super(name, age)
}
goAway() {}
}
const BInstance = new B('王同超', 18)
BInstance.goSleep()
BInstance.goAway()
配置文件(tsconfig.js)
- files - 设置要编译的文件的名称
- include - 设置需要进行编译的文件,支持路径模式匹配
- exclude - 设置无需进行编译的文件,支持路径模式匹配
- extends - 扩展配置路径(可以继承其他配置)
- references - 引入其他项目
- compilerOptions - 设置与编译流程相关的选项
/* Visit https://aka.ms/tsconfig.json to read more about this file */
{
// "file": [],
// "extends": "",
// references: [{path: ''}]
"include": ["./src"],
"exclude": [],
"compilerOptions": {
/* Projects */
// "incremental": true, /* 开启增量编译 */
// "composite": true, /* 开启复合项目的强制约束 */
// "tsBuildInfoFile": "./", /* 指定增量编译文件(.tsbuildinfo)的存储位置 */
// "disableSourceOfProjectReferenceRedirect": true, /* 禁用复合项目时的源文件而不是声明文件 */
// "disableSolutionSearching": true, /* 忽略(编辑器对于)复合项目中的引用/跳转,提高响应能力 */
// "disableReferencedProjectLoad": true, /* 禁用所有项目的自动加载(如果项目较大,ts会将所有可用项目加载到内存中,开启此功能,仅会动态加载编辑器打开的文件) */
/* Language and Environment */
"target": "ES5", /* 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES7'/'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ESNext' */
// "lib": [], /* 指定要包含在编译中的库文件 */
// "jsx": "preserve", /* 指定 jsx 代码的生成: 'undefined'(default), 'preserve', 'react', 'react-jsx', 'react-jsxdev', 'react-native' */
// "experimentalDecorators": true, /* 开启装ESMAScript的装饰器支持 */
// "emitDecoratorMetadata": true, /* 开启装饰器元数据支持 */
// "jsxFactory": "", /* 指定jsx编译时的工厂函数 e.g. 'React.createElement' or 'h' */
// "jsxFragmentFactory": "", /* 指定jsx对空标签的编译 e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
// "reactNamespace": "", /* 指定createElement目标React调用的对象,默认为React */
// "noLib": true, /* 禁用包含任何库文件,开启后lib失效 */
// "useDefineForClassFields": true, /* 定义类字段,TS与最新TC39语法语法相同,但运行时行为不同。适用于ES2022和ESNext */
/* Modules */
"module": "CommonJS", /* 指定使用的模块系统 CommonJS, UMD, AMD, ES6, ES2015, ES2020, None, ESNext, System*/
// "rootDir": "./", /* 指定项目根目录 */
// "moduleResolution": "node", /* 指定模块解析策略 'node'(Node.js) or 'classic'(TypeScript 1.6 发布前使用) */
// "baseUrl": "./", /* 指定解析非相对模块名称的基目录 */
// "paths": {}, /* 指定模块名到基于 baseUrl 的路径映射的列表 */
// "rootDirs": [], /* 指定多个项目根目录 */
// "typeRoots": [], /* 指定类型声明文件夹,默认node_modules/@types */
// "types": [], /* 指定加载的声明文件包 */
// "allowUmdGlobalAccess": true, /* 允许模块访问UMD变量 */
// "resolveJsonModule": true, /* 允许导入json文件 */
// "noResolve": true, /* 禁止检查 `import`s, `require`s or `<reference>`导入的文件类型 */
/* JavaScript Support */
// "allowJs": true, /* 允许编译js文件 */
// "checkJs": true, /* 允许检查js文件错误 */
// "maxNodeModuleJsDepth": 1, /* 指定用于从node_module检查JavaScript文件的最大文件夹深度。仅适用于' allowJs: true' */
/* Emit */
// "declaration": true, /* 生成相应的.d.ts文件 */
// "declarationMap": true, /* 生成.d.ts的sourcemap文件 */
// "emitDeclarationOnly": true, /* 只生成.d.ts文件,不生成js文件 */
// "sourceMap": true, /* 生成js的sourcemap文件 */
// "outFile": "./", /* 将输出文件合并为一个文件*/
"outDir": "./dist", /* 指定输出目录 */
// "removeComments": true, /* 删除编译后的所有的注释 */
// "noEmit": true, /* 不生成输出文件 */
// "importHelpers": true, /* 允许从tslib导入辅助函数(允许后,可以减少代码体积,防止在每一处使用到的地方实现一遍) */
// "importsNotUsedAsValues": "remove", /* 指定import仅用作类型时的行为,可选值'remove', 'preserve', 'error' */
// "downlevelIteration": true, /* 允许更加准确的转换为低版本js代码(会导致代码量过多) */
// "sourceRoot": "", /* 指定调试器查找源代码的根路径 */
// "mapRoot": "", /* 指定调试器应定位source map文件路径 */
// "inlineSourceMap": true, /* 允许source map文件写入js行内 */
// "inlineSources": true, /* 允许将.ts的原内容作为字符串包含在source map中(需要设置sourceMap或inlineSourceMap) */
// "emitBOM": true, /* 编译后文件写入UTF-8字节顺序标记(最好不要开启,除非有充分理由) */
// "newLine": "crlf", /* 指定编译文件的换行符,可选参数 crlf, lf */
// "stripInternal": true, /* 允许禁用在JSDoc注释中包含' @internal '的声明 */
// "noEmitHelpers": true, /* 允许禁用tslib中的辅助函数,但需要自己实现相关函数 */
// "noEmitOnError": true, /* 允许报告了类型错误不再进行编译 */
// "preserveConstEnums": true, /* 允许保留常量枚举 */
// "declarationDir": "./", /* 指定声明文件的目录 */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* 允许合并默认导入,比如import * as React from "react"写成import React from "react" */
"esModuleInterop": true, /* 开启兼容通过esm方式导入cjs导出的代码 */
// "preserveSymlinks": true, /* 开启保留符号链接 */
"forceConsistentCasingInFileNames": true, /* 开启区分大小写文件名 */
/* Type Checking */
"strict": true, /* 开启严格类型检查 */
// "noImplicitAny": true, /* 开启隐式any报错 */
// "strictNullChecks": true, /* 开启严格null和undefined检查(不开启时,null和undefined是所有类型的子类型) */
// "strictFunctionTypes": true, /* 开启严格函数类型检查 */
// "strictBindCallApply": true, /* 开启对call, apply, bind的参数检查 */
// "strictPropertyInitialization": true, /* 开启严格属性初始化(开启后会检查类的构造函数中声明类型但未初始化变量是报错) */
// "noImplicitThis": true, /* 开启this为any时报错 */
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* 开启严格模式检查每个模块,并在每个文件里加入 'use strict' */
// "noUnusedLocals": true, /* 开启变量未使用时报错 */
// "noUnusedParameters": true, /* 开启函数参数未使用时报错 */
// "exactOptionalPropertyTypes": true, /* 开启严格的可选参数属性检查(开启后,可选参数不能被赋值为undefined) */
// "noImplicitReturns": true, /* 开启隐式返回值时报错(函数可能有返回值或可能无返回值时报错) */
// "noFallthroughCasesInSwitch": true, /* 开启禁止switch中的case贯穿写法 */
// "noUncheckedIndexedAccess": true, /* 开启索引签名中包含undefined */
// "noImplicitOverride": true, /* 开启子类覆盖父类方法事,需添加override,否则报错 */
// "noPropertyAccessFromIndexSignature": true, /* 开启获取索引签名对象的key时,未定义进行报错 */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* 开启允许永远不会执行的代码(开启后,if else 的之后不会执行的代码不会进行报错) */
/* Completeness */
// "skipDefaultLibCheck": true, /* 跳过默认库的声明文件类型检查*/
"skipLibCheck": true /* 跳过 .d.ts 声明文件的类型检查 */
}
}