zl程序教程

您现在的位置是:首页 >  其他

当前栏目

TypeScript基础知识

2023-06-13 09:13:14 时间

TypeScript

TypeScriptJavaScript的一个超集,支持ECMAScript6标准。

安装

终端运行如下命令,全局安装TypeScript

npm install -g typescript

安装完成后,运行如下命令,检查安装是否成功:

tsc -V

创建 tsconfig.json配置文件

tsc --init

编写TS程序

1.创建文01_index.ts,并写如下代码:

const str: string = 'Hello 甜甜';
console.log(str);

2.执行如下命令,会生成js文件01_index.js

tsc .\01_index.ts 

3.执行如下命令,查看输出内容:

node .\01_index.js

ts-node

ts-node工具可以让我们直接运行.ts文件

安装:

npm i -g ts-node // 全局安装ts-node

接下来直接运行ts文件:

ts-node .\01_index.ts

这样就也可以看到输出的内容了。

原始类型

String类型

let name: string = "甜甜";

Number类型

const count: number = 10;

布尔类型(boolean)

const flag: boolean = true;

undefined和null

注意:如果开启了严格模式,需要在tsconfig.json文件中修改为"strictNullChecks":false,这样,undefinednull可以赋值给其他类型,否则nullundefined只能给它们自己赋值。

let a: undefined = undefined;
let b: null = null;
let strdata: string = '甜甜';

strdata = null;
strdata = undefined;

undefined可以给void赋值

let c:void = undefined // 编译正确
let d:void = null // 编译报错

symbol类型

基本数据类型,可以创建独一无二的值。

let sym1 = Symbol();
let sym2 = Symbol('key'); // 可选的字符串key
let sym3 = Symbol('key');
sym2 === sym3; //false

对象类型

object、Object 以及 { }

  • object

看图可以发现,当赋值给object的类型为numberstringboolean时都会报错,而nullundefined因为我关闭了严格模式所以不会报错。

所以object 的引入就是为了解决对 Object 类型的错误使用,它代表所有非原始类型的类型,即数组、对象与函数类型这些

const tmp1: object = { name: 'linbudu' };
const tmp2: object = () => {};
const tmp3: object = [];
  • Object

表示拥有toStringhasOwnProperty方法的的类型,所以所有的原始类型、非原始类型都可以赋值给Object(严格模式下nullundefined不可以)

let object: Object;
object = 1;//正确
object = 'tiantian';//正确
object = true;//正确
object = null;//错误
object = undefined;//错误
object = {};//正确
  • {}:空对象类型,表示原始类型和非原始类型的集合

数组类型(Array)

const arr:number[]=[1,2,3];

const arr1: Array<number> = [1, 2, 3];

类(class)

class Man {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  sayHi(): void {
    console.log('Hi' + this.name);
  }
}
//使用new关键字来实例化类的对象
var obj = new Man('王五');
console.log('名字是' + obj.name); //名字是王五
obj.sayHi(); //Hi王五

类的继承:我们可以在创建类的时候继承一个已存在的类,这个已存在的类称为父类,继承它的类称为子类。

class Shape { 
   Area:number 
   
   constructor(a:number) { 
      this.Area = a 
   } 
} 
 
class Circle extends Shape { 
   disp():void { 
      console.log("圆的面积:  "+this.Area) 
   } 
}
  
var obj = new Circle(223); 
obj.disp()

函数

function add(x: number, y: number): number {
  return x + y;
}

console.log(add(1, 2)); //3

如果定义了参数,则必须传参,除非设置为可选,使用问号标识?

function add(x: number, y: number, z?: number): number {
  if (z) return x + y + z;
  else return x + y;
}

console.log(add(1, 2)); //3

注意:可选参数必须跟在必选参数后面。

也可以设置参数默认值,如果不传参则使用默认参数:

function add(x: number, y: number = 2): number {
  return x + y;
}

console.log(add(1)); //3

若不知道要向函数传入多少个参数,可以使用剩余参数来定义:

function addNumbers(...nums: number[]) {
  var i;
  var sum: number = 0;

  for (i = 0; i < nums.length; i++) {
    sum = sum + nums[i];
  }
  console.log('和为:', sum);
}
addNumbers(1, 2, 3); //6
addNumbers(10, 10, 10, 10, 10); //50

函数的最后一个命名参数 nums... 为前缀,它将成为一个由剩余参数组成的数组,索引值从0(包括)到 nums.length(不包括)。

如果给同一个函数提供多个函数类型定义,就会产生函数的重载,函数重载真正执行的是同名函数最后定义的函数体,在最后一个函数体定义之前全都属于函数类型定义,不能写具体的函数实现方法,只能定义类型

新增类型

枚举类型(enum)

Enum枚举类型用于定义数值集合,使用枚举我们可以定义一些带名字的常量。

使用枚举可以清晰地表达意图或创建一组有区别的用例。

  • 普通枚举

初始值默认为0其余成员按顺序自动增长,可以理解为数组下标:

enum week {
  周日,
  周一,
  周二,
  周三,
  周四,
  周五,
  周六,
}

const monday: week = week.周一;
console.log('星期' + monday); // 星期1

可以设置一个初始值

enum week {
  周日 = 7,
  周一,
  周二,
  周三,
  周四,
  周五,
  周六,
}

const monday: week = week.周一;
console.log('星期' + monday); // 星期8
  • 字符串枚举
enum week {
  Sun = '周日',

  Mon = '周一',

  Tue = '周二',

  Wed = '周三',

  Thu = '周四',

  Fri = '周五',

  Sat = '周六',
}

const monday: week = week.Mon;
console.log(monday); // 周一
  • 常量枚举

const关键字进行修饰就是常量枚举,整个枚举会在编译阶段会删除。

const enum week {
  Sun = '周日',

  Mon = '周一',

  Tue = '周二',

  Wed = '周三',

  Thu = '周四',

  Fri = '周五',

  Sat = '周六',
}

const monday: week[] = [week.Mon];
console.log(monday); // [ '周一' ]

元组类型(tuple)

数组类型只能定义内部全为同类型的数组,对不内部不同类型的数组使用元组类型定义。

const tuple: [number, string] = [1, 'tiantian'];
console.log(tuple); //[ 1, 'tiantian' ]

注意:元组类型只能表示一个已知元素数量和类型的数组,长度已指定,越界访问会提示错误。例如,一个数组中可能有多种类型,数量和类型都不确定,那就直接any[]。

any类型

any(任何)会跳过类型检查器对值的检查,任何值都可以赋值给any类型

let value: any = 1;
value = '甜甜';
value = [];
value = {};

void类型

无效的,一般用来告诉别人这个函数没有返回值。

function sayHello(): void {
  console.log('Hello 甜甜!');//不会打印内容
}

never类型

表示永不存在的值类型,一般指总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式及函数中执行无限循环的代码(死循环)的返回值类型。

// 异常
function error(msg: string): never {
  // 正常编译
  throw new Error(msg);
}

// 死循环
function loopForever(): never {
  // 正常编译
  while (true) {}
}

Unknown类型

any一样,所有类型都可以分配给他。

  let value: unknown = 1;
  value = "tiantian"; // 正常编译
  value = true; // 正常编译

any的区别:

  • 任何类型的值可以赋值给any,同时any类型的值也可以赋值给任何类型。
  • unknown 任何类型的值都可以赋值给它,但它只能赋值给unknownany

类型操作

联合类型

联合类型用|分隔,表示取值可以为多种类型中的一种

let status:string|number
status='to be or not to be'
status=1

类型别名/自定义类型

类型别名用来给一个类型起个新名字,它只是起了一个新名字,并没有创建新类型。类型别名常用于联合类型。

type count = number | number[];
function hello(value: count) {}

类型别名可以实现扩展:

type MyType = {
  name:string;
  say(): void;
}
type MyType2 = MyType & {
  sex:string;
}

此时MyType2包含了namesay()sex三个属性。

交叉类型

交叉类型就是跟联合类型相反,用&操作符表示,交叉类型就是两个类型必须存在。

interface IpersonA{
  name: string,
  age: number
}
interface IpersonB {
  name: string,
  gender: string
}

let person: IpersonA & IpersonB = { 
    name: "师爷",
    age: 18,
    gender: "男"
};

类型推论

如果没有明确的指定类型,那么 TypeScript 会依照类型推论的规则推断出一个类型。

let x=1;
x=true;//报错

这里推论的代码为:

let x: number = 1;
x = true; // 报错

当第一次定义的时候没有赋值,则无论之后是否赋值,都会被推断成any类型而完全不被类型检查。

类型断言

指定更具体的类型,即手动指定一个值的类型。

//方法1:
let strLength: number = (<string>str).length;

//方法2:
let strLength: number = (str as string).length;

非空断言

使用进行断言操作,对手是非null非undefined

const a: number | undefined = undefined;
const b: number = a!; //即b的值不会为 null 或 undefined
console.log(b); // undefined

虽然在 TS 代码中,我们使用了非空断言,使得 const b: number = a!; 语句可以通过 TypeScript 类型检查器的检查。但在生成的 ES5 代码中,! 非空断言操作符被移除了,所以在浏览器中执行以上代码,在控制台会输出 undefined

接口

当一个对象类型被多次使用时,一般会使用接口(interface)来描述对象的类型,达到复用的目的。

//接口
interface IPerson {
  name: string;
  age: number;
  sayHi(): void;
}

//使用
let person: IPerson = {
  name: 'tiantain',
  age: 19,
  sayHi() {},
};

//复用
let person1: IPerson = {
  name: '员老师',
  age: 18,
  sayHi() {},
};

可选属性

上方的代码,如果你在使用接口的时候,没有设置接口可选或者只读它默认会要求全部都要传参。

我们可以通过添加?来设置接口为可选属性

//接口
interface IPerson {
  name: string;
  age?: number;
  sayHi(): void;
}

//使用
let person: IPerson = {
  name: 'tiantain',
  sayHi() {},
};

索引签名

如果我们希望一个接口除了必选和可选属性外还允许有其他的任意属性,则可以使用索引签名的形式来实现。

//接口
interface IPerson {
  name: string;
  age?: number;
  [prop: string]: any;//  prop字段必须是 string类型 or number类型。 值是any类型,也就是任意的
}

注意:一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

接口继承

如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用。

原来写法:

interface Point1 {
  x: number;
  y: number;
}
interface Point2 {
  x: number;
  y: number;
  z: number;
}

继承写法:

interface Point1 {
  x: number;
  y: number;
}
interface Point2 extends Ponint1 {
  z: number;
}

使用extends继承关键字实现了接口Point2继承Point1,继承之后Point2就拥有了Point1的所有属性和方法,此时Point2同时有xyz三个属性。

接口与类型别名的区别

type MyTYpe = {
  name: string;
  say(): void;
}

interface MyInterface {
  name: string;
  say(): void;
}
  • 相同点:
    • 都可以给对象指定类型
    • 都允许扩展,interfaceextends 来实现扩展,type 使用 & 实现扩展
  • 不同点:
    • 接口:只能为对象指定类型,可以合并声明。
    • 类型别名:不仅可以为对象指定类型,实际上可以为任意类型指定别名。但是不能合并声明。

泛型

泛型是可以保证类型安全的前提下,让函数等与多种类型一起工作,从而实现复用,常用于:函数、接口、类中。

:创建一个函数,传入什么数据返回数据本身,也就是参数和返回值类型相同。

function id(value: number): number {
  return value;
}
console.log(id(10)); //10

可以上面这么写,但是该函数只接收number类型,而无法用于其他类型。

这时候我们可能想,可以使用any,但是这样就失去了TS的类型保护,类型不安全。

基本使用

function id<T>(arg: T): T {
  return arg;
}

console.log(id('甜甜'));

语法:在函数名称的后面添加<>(尖括号),尖括号中添加类型变量,比如此时添加的T

这里这个T,是一种特殊类型的变量,它处理类型而不是值。

它就相当于一个类型容器,能够捕获用户提供的类型,因为T是类型,因此可以将其作为函数参数和返回值的类型,表示参数和返回值具有相同的类型。而类型变量T,可以是任意合法的变量名称。

具体类型

1.可以直接在使用时给它定义类型

console.log(id<string>('甜甜')); // 定义 T 为 string 类型

2.直接利用typescript的类型推断

console.log(id('甜甜'));

多个参数

并不是只能定义一个类型变量,我们可以引入希望定义的任何数量的类型变量。

function id<T, U>(arg: [T, U]): [T, U] {
  return arg;
}
console.log(id(['甜甜', 18]));

typescript会自动给我们推断出输入、返回的类型。

泛型约束

如图,因为泛型T不一定包含属性length,所以当我们内部使用泛型变量的时候,不知道它是那种类型,所以不能操作它的属性和方法。

此时,我们可以使用extends关键字来对泛型进行约束:

function getLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length); //2  3  5
  return arg;
}

const str = getLength('甜甜');
const arr = getLength([1, 2, 3]);
const obj = getLength({ length: 5 });

console.log(str); //甜甜
console.log(arr); //[ 1, 2, 3 ]
console.log(obj); //{ length: 5 }

这样,无论你是什么类型,只要具有length属性,都可以操作到。

泛型接口

我们可以在定义接口的时候指定泛型。

在接口名称的后面添加<类型变量>,那么这个接口就变成了泛型接口。

interface KeyValue<T, U> {
  key: T;
  value: U;
}

const person1: KeyValue<string, number> = {
  key: '甜甜',
  value: 18,
};
const person2: KeyValue<number, string> = {
  key: 20,
  value: '甜甜',
};

console.log(person1); //{ key: '甜甜', value: 18 }
console.log(person2); //{ key: 20, value: '甜甜' }
  • 接口的类型变量对接口中所有其他成员可见,也就是接口中的所有成员都可以使用类型变量。
  • 使用泛型接口时,需要显式指定具体的类型,上述代码的KeyValue<string, number>

实际上,JS中的数组在TS中就是一个泛型接口,当我们在使用数组时,TS会根据数组的不同类型,来自动将类型变量设置为响应的类型。(Ctrl+鼠标左键查看具体类型信息)

泛型类

class Test<T> {
  value: T;
  add: (x: T, y: T) => T;
}
//new实例
let myTest = new Test<number>();
myTest.value = 0;
myTest.add = function (x, y) {
  return x + y;
};
console.log(myTest.value); //0
console.log(myTest.add(1, 2)); //3

别名

type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];

默认类型

可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。

function createArray<T = string>(length: number, value: T): Array<T> {
  let result: T[] = [];
  for (let i = 0; i < length; i++) {
    result[i] = value;
  }
  return result;
}

工具类型

typeof

typeof关键字除了可以做类型保护,还可以实现推出效果。

let Ps = {
  name: '甜甜',
  age: 18,
};
type People = typeof Ps;
function getName(p: People): string {
  return p.name;
}
console.log(getName(Ps)); //甜甜

keyof

用来获取一个对象接口中的所有key

interface Person {
  name: string;
  age: number;
  gender: 'male' | 'female';
}

type PersonKey = keyof Person; //type PersonKey = 'name'|'age'|'gender';

function getValueByKey(p: Person, key: PersonKey) {
  return p[key];
}
let val = getValueByKey({ name: '甜甜', age: 18, gender: 'female' }, 'gender');
console.log(val); // female

in

用来遍历枚举类型,类似于for..in

type roles = 'tester' | 'developer' | 'manager';
const staffCount: { [k in roles]: number } = {
  tester: 100,
  developer: 200,
  manager: 300,
};
console.log(staffCount);

infer

在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。

也就是说可以用它取到函数返回值的类型方便之后使用。

type ReturnType<T> = T extends (
  ...args: any[]
) => infer R ? R : any;

索引访问符

T[K] 在TS里称作索引访问操作符(indexed access operator)。它可以为我们准确解析目标对象上的对应属性的正确类型。

interface Person {
  name: string;
  age: number;
}

type x = Person["name"]; // x is string

内置工具类型

Required

将类型的属性变成必选,当缺少属性时,就会报错。

interface Person {
    name?: string,
    age?: number,
    hobby?: string[]
}

const user: Required<Person> = {
    name: "甜甜",
    age: 18,
}

Partial

Required 相反,将所有属性转换为可选属性

interface Person {
  name: string;
  age: number;
}

const user: Partial<Person> = {
  name: '甜甜',
};

明显缺少属性,缺不会报错。

Exclude

Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉,剩余的属性构成新的类型。

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number

Extract

Exclude 相反,Extract<T,U> 从 T 中提取出 U。

type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () =>void

Readonly

把数组或对象的所有属性值转换为只读的,这就意味着这些属性不能被重新赋值。

interface Person {
  name: string;
  age: number;
  gender?: "male" | "female";
}

let p: Readonly<Person> = {
  name: "hello",
  age: 10,
  gender: "male",
};
p.age = 11;

Record

Record<K extends keyof any, T> 的作用是将 K 中所有的属性的值转化为 T 类型。

type Property = 'key1'|'key2'
type Person = Record<Property, string>;

const p: Person = {
  key1: "hello",
  key2: "甜甜",
};

Pick

从某个类型中挑出一些属性出来

type Person = {
  name: string;
  age: number;
  gender: string;
};

type P1 = Pick<Person, 'name' | 'age'>; // 此时的属性:{ name: string; age: number; }

const user: P1 = {
  name: '甜甜',
  age: 18,
};

Omit

与Pick相反,从某个类型中除去一些属性

interface Person {
  name: string,
  age: number,
  gender: string
}
type P1 = Omit<Person, "age" | "gender">//去除了age、gender属性
const user:P1  = {
  name: '甜甜'
}

配置文件介绍

生成配置文件(tsconfig.json):

tsc --init

tsconfig.jsonTypeScript 项目的配置文件,包含 TypeScript 编译的相关配置,通过更改编译配置项,我们可以让 TypeScript 编译出 ES6ES5node 的代码。

{
  "compilerOptions": {

    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件做为单独的模块 (与 'ts.transpileModule' 类似).

    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
  }
}