TS

一、类型检测

0、type定义类型别名,方便多次复用

// type用于定义类型别名(type alias)
type IDType = string | number | boolean
type PointType = {
  x: number
  y: number
  z?: number
}
function printId(id: IDType) {

}
function printPoint(point: PointType) {
  
}

1、number、string、boolean 类型

let name: string = "why";
	let num1: number = 100;
	let flag: boolean = true;

2、array类型

  • array类型里只能放同一种类型,如:string[ ]代表数组里只能放string类型
const names1: Array<string> = []; // 不推荐
	const names2: string[] = []; // 推荐

3、tuple类型

  • tuple类型里可以放多种类型,但类型需要一一对应
const info: [string, number, number] = ["why", 18, 1.88]; // 类型需要	一一对应
	const name = info[0];
	console.log(name.length);
	const age = info[1];
	console.log(age.length); // 这里会报错,因为array类型无length方法
  • 因为tuple里面可以放多个类型,所以比较适合作为函数的返回值
// [any, (newState: any) => void] 为返回值类型
	function useState(state: any): [any, (newState: any) => void] {
	  let currentState = state;
	  const changeState = (newState: any) => {
	    console.log(newState);
 	 };
 	 return [currentState, changeState];
	}

4、object类型

const info: {
 	 name: string;
 	 age: number;
	} = {
 	 name: "why",
 	 age: 18,
	};
	console.log(info.name);

5、null 和 undefined

// null只能赋值null undefined只能赋值undefined
	let n1: null = null;
	let n2: undefined = undefined;
  • unknown的使用
let flag = true;
	let result: unknown; // 最好不要使用any
	if (flag) {
 	 result = 123;
	} else {
	  result = "abc";
	}
	console.log(result);

6、Symbol类型

const title1 = Symbol("title");
	const title2 = Symbol("title");

	const info = {
	  [title1]: "程序员",
	  [title2]: "老师",
	};
	// 取值
	console.log(info[title1]);

7、void类型

函数无返回值的类型就是void
// 默认就是void
	function sum(num1: number, num2: number) {
 	 	console.log(num1 + num2);
	}

8、never类型

never类型不匹配任何类型,所以赋任何类型的值都会报错
function handleMessage(message: string | number | boolean) {
  switch (typeof message) {
    case "string":
      console.log("string处理方式处理message");
      break;
    case "number":
      console.log("number处理方式处理message");
      break;
    // 防止给message增加类型后忘记编写相关代码
    // 打开以下备注 就不会报错;
    // case "boolean":
    //   console.log("boolean处理方式处理message");
    //   break;
    default:
      // never 类型不存在赋值过程  所以当未匹配的boolean类型的message赋值给check时会报错
      const check: never = message;
  }
}

handleMessage(true); //调用未匹配的boolean类型

9、any类型

在不想给某些JavaScript添加具体的数据类型时,可以使用any类型,便会跳过类型检测,和原生的JavaScript代码是一样的;
let message: any = "Hello World";
	message = 123;
	message = true;
	message = {};

10、字面量类型

字面量类型 ,值和类型是保持一致的,常结合联合类型使用
type Method = "GET" | "POST";  // 这样Method只能为这两个值
type Request = {
  url: string;
  method: Method;
};
function request(url: string, method: Method) {}
const options: Request = {
  url: "https://www.coderwhy.org/abc",
  method: "POST",
};

11、函数相关的类型

  • 函数的参数和返回值都需要确定类型,参数必须明确写出类型,返回值默认会自动推导;
function sum(num1: number, num2: number):number {
  return num1 + num2
}
// 参数也可以为对象,函数,相应的需要确定类型
function printPoint(point: {x: number, y: number}) {
  console.log(point.x);
  console.log(point.y)
}
printPoint({x: 123, y: 321})
  • 匿名函数的类型推导
const names = ["abc", "cba", "nba"]
// item根据上下文的环境推导出来的, 这个时候可以不添加的类型注解
// 上下文中的函数: 可以不添加类型注解
names.forEach(function(item) {
  console.log(item.split(""))
})
  • 对象类型也可以指定哪些属性是可选的,可以在属性的后面添加一个“?”
function printPoint(point: {x: number, y: number, z?: number}) {
  console.log(point.x)
  console.log(point.y)
  console.log(point.z)
}
printPoint({x: 123, y: 321})
printPoint({x: 123, y: 321, z: 111})
  • 联合类型
function printID(id: number | string | boolean) {
  // 使用联合类型的值时, 需要特别的小心
  // narrow: 缩小
  if (typeof id === "string") {
    // TypeScript帮助确定id一定是string类型
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}
printID(123);
printID("abc");
printID(true);

使用联合类型后因为一个参数可以取多个类型,所以需要先确定类型(即缩小类型),才能调用每个类型独有的方法与属性;
下面是类型缩小的几种方式:

// 1.typeof的类型缩小
type IDType = number | string;
function printID(id: IDType) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id);
  }
}

// 2.平等的类型缩小(=== == !== !=/switch)
type Direction = "left" | "right" | "top" | "bottom";
function printDirection(direction: Direction) {
  // 1.if判断
  // if (direction === 'left') {
  //   console.log(direction)
  // } else if ()
  // 2.switch判断
  // switch (direction) {
  //   case 'left':
  //     console.log(direction)
  //     break;
  //   case ...
  // }
}

// 3.instanceof
function printTime(time: string | Date) {
  if (time instanceof Date) {
    console.log(time.toUTCString());
  } else {
    console.log(time);
  }
}

class Student {
  studying() {}
}

class Teacher {
  teaching() {}
}

function work(p: Student | Teacher) {
  if (p instanceof Student) {
    p.studying();
  } else {
    p.teaching();
  }
}

const stu = new Student();
work(stu);

// 4. in
type Fish = {
  swimming: () => void;
};

type Dog = {
  running: () => void;
};

function walk(animal: Fish | Dog) {
  if ("swimming" in animal) {
    animal.swimming();
  } else {
    animal.running();
  }
}

const fish: Fish = {
  swimming() {
    console.log("swimming");
  },
  // 不能加running方法
  // running(){
  //   console.log("running")
  // }
};

walk(fish);

12、类型断言(缩小类型范围)

  • 使用DOM元素特有的属性和方法时
const el = document.getElementById("why") as HTMLImageElement;
el.src = "url地址";
  • 使用子类特有的属性和方法时
class Person {}

class Student extends Person {
  studying() {}
}

function action(p: Person) {
  // p.studying()  会报错
  (p as Student).studying(); // 只有学生才能调用studying
}

const stu = new Student();
action(stu);

二、各种操作符

1、! 非空断言操作符

x! 将从 x 值域中排除 null 和 undefined;
function myFunc(maybeString: string | undefined | null) {
  // Type 'string | null | undefined' is not assignable to type 'string'.
  // Type 'undefined' is not assignable to type 'string'. 
  const onlyString: string = maybeString; // Error
  const ignoreUndefinedAndNull: string = maybeString!; // Ok
}

2、可选链"?."

遇到null或者undefined时可以立即停止表达式的运行
const val = a?.b;
// 编译之后
var val = a === null || a === void 0 ? void 0 : a.b;

如果a为null或者void 0,会直接返回void 0,而不会接着执行a.b;
因此,以后我们可以不需要编写形如

if( a && a.b){
}

这样的保护性代码。更好的写法是:

if( a?.b){
}

唯一需要注意的是,?. 与 && 运算符并不100%等价。&& 专门用于检测 falsy 值,比如空字符串、0、NaN、null 和 false 等。而 ?. 只会验证对象是否为 null 或 undefined,对于 0 或空字符串来说,并不会出现 “短路”;

3、空值合并运算符(??)

  • 空值合并运算符(??)是一个逻辑运算符。当左侧操作数为 null 或 undefined 时,其返回右侧的操作数。否则返回左侧的操作数。
  • 与逻辑或(||)操作符不同,逻辑或会在左操作数为 falsy 值时返回右侧操作数。也就是说,如果你使用 || 来为某些变量设置默认的值时,你可能会遇到意料之外的行为。比如为 falsy 值(’’、NaN 或 0)时。
const foo = null ?? 'default string';
console.log(foo); // 输出:"default string"

const baz = 0 ?? 42;
console.log(baz); // 输出:0

以上 TS 代码经过编译后,会生成以下 ES5 代码:

"use strict";
var _a, _b;
var foo = (_a = null) !== null && _a !== void0 ? _a : 'default string';
console.log(foo); // 输出:"default string"

var baz = (_b = 0) !== null && _b !== void0 ? _b : 42;
console.log(baz); // 输出:0

三、函数详解

1、函数的可选类型

type SubtractsFnType = (num1: number, num2?: number) => number

const subtracts: SubtractsFnType = (a1: number, a2?: number) => {
    if (a2) {
        return a1 - a2
    } else {
        return a1

    }

}
console.log(subtracts(5));
console.log(subtracts(5, 1));

export{}

2、函数的默认值

// 必传参数 - 有默认值的参数 - 可选参数
function foo(y: number, x: number = 20) {
  console.log(x, y)
}

foo(30)
export{}

3、函数的剩余参数

function sum(initalNum: number, ...nums: number[]) {
  let total = initalNum
  for (const num of nums) {
    total += num
  }
  return total
}

console.log(sum(20, 30))
console.log(sum(20, 30, 40))
console.log(sum(20, 30, 40, 50))

4、this相关

  • this的默认推导
// this是可以被推导出来 info对象(TypeScript推导出来)
const info = {
  name: "why",
  eating() {
    console.log(this.name + " eating")
  }
}

info.eating()

export {}
  • this的不明确类型
type ThisType = { name: string };

function eating(this: ThisType, message: string) {
  console.log(this.name + " eating", message);
}

const info = {
  name: "why",
  eating: eating,
};

// 隐式绑定 info
info.eating("哈哈哈");

// 显示绑定
eating.call({name: "kobe"}, "呵呵呵")
eating.apply({name: "james"}, ["嘿嘿嘿"])

5、函数重载

在TypeScript中,如果我们编写了一个add函数,希望可以对字符串和数字类型进行相加,应该如何编写呢?

  1. 通过类型缩小
function add(a1: number | string, a2: number | string) {
  if (typeof a1 === "number" && typeof a2 === "number") {
    return a1 + a2
  } else if (typeof a1 === "string" && typeof a2 === "string") {
    return a1 + a2
  }
  // return a1 + a2;
}
add(10, 20)
  1. 通过函数重载
// 函数的重载: 函数的名称相同, 但是参数不同的几个函数, 就是函数的重载

/* 1.函数类型的声明 */
function add(num1: number, num2: number): number; // 没函数体
function add(num1: string, num2: string): string;

/* 2.函数体的实现  不能直接被调用  必须符合上面声明的类型才行 */
function add(num1: any, num2: any): any {
  if (typeof num1 === "string" && typeof num2 === "string") {
    return num1.length + num2.length;
  }
  return num1 + num2;
}
const result = add(20, 30);
const result2 = add("abc", "cba");
console.log(result);
console.log(result2);

// 必须和重载函数匹配 不然会报错
// add({name: "why"}, {age: 18})

export {};