前言
我JS用的很顺手,为什么要用TS呢? 这不是会白白多了很多工作量吗???
相信很多小伙伴都有这个疑问,翻了网上大神的回答再结合工作中的经验,主要有以下三点:
-
面向项目:
-
TS - 面向解决大型项目,架构设计以及代码维护有一定的优势
- 比如某个函数的传参定义为
number
, 函数内部的逻辑也是基于number
类型开发的,这时候其他人传个string
,函数内部逻辑就会出错,TS
可以从起点就规避这种场景
- 比如某个函数的传参定义为
-
JS - 脚本化语言,简单场景且轻量化
-
-
自主检测
- TS -
编译时
检测,主动发现并纠正错误(静态类型弱类型) - JS -
运行时
报错(动态类型弱类型)
- TS -
-
运行流程
- TS - 依赖编译,依靠编译打包实时生成浏览器可以运行的js
- JS - 可直接在浏览器中运行
其实,以上都是次要的,主要现在很多公司的岗位都要求会
TS
,哈哈~~~
本篇参考了一些大佬的真传干货,汇总提炼出的TS
方面的知识点,看完可以上手常规的TS项目啦~~~
正文开始
武器库
在线直接写ts: 传送门
ts文档: 传送门
1,变量声明
五种基本类型
都在下面啦~~~
给变量定义了什么类型,值就要是什么类型,要进行统一的
let a: number = 1; //number小写
let b: string = "1";
let c: undefined = undefined;
let d: null = null;
let e: boolean = true;
如果不按照规范写的话,会报如下的错误:
2,枚举声明 - enum
2.1,数字类枚举
不赋值就是数字类枚举,默认从零开始,依次递增
enum Score {
BAD, // 0
NG, // 1
GOOD, // 2
PERFECT, // 3
}
let score:Score = Score.BAD;
BAD的值为 1,NG值为2
enum Score {
BAD = 1, // 1
NG, // 2
GOOD, // 3
PERFECT, // 4
}
2.2,字符串枚举
enum Score {
BAD = 'BAD',
NG = 'NG',
GOOD = 'GOOD',
PERFECT = 'PERFECT',
}
使用场景
enum Role {
Admin = "Admin",
User = "User",
Guest = "Guest",
}
type User = {
name: string;
role: Role;
};
function getUserRole(data: User): string {
switch (data.role) {
case Role.Admin:
return `Welcome Admin, ${data.name}! You have full access.`;
case Role.User:
return `Hello User, ${data.name}.`;
case Role.Guest:
return `Hello Guest, ${data.name}.`;
}
}
getUserRole({ name: "123", role: Role.Admin });
2.3,常量枚举(不常用)
在编译阶段会被删除
const enum Directions {
Up,
Down,
Left,
Right,
}
let directions1 = [
Directions.Up,
Directions.Down,
Directions.Left,
Directions.Right,
];
生成的代码 var directions = [0 /* Up /, 1 / Down /, 2 / Left /, 3 / Right */];
普通枚举
enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]
生成的代码 var directions = { Up: 0, Down: 1, Left: 2, Right: 3 },
2.4,反向映射(不常用)
指的是取值的时候,因为做了双向映射,所以 Score[0]
的值是 BAD
enum Score {
BAD,
NG,
GOOD,
PERFECT,
}
let scoName = Score[0]; // BAD
let scoVal = Score['BAD']; // 0
2.5,异构(不常用)
enmu Enum {
A, // 0
B, // 1
C = 'C',
D = 'D',
E = 6,
F, // 7 逐个递增,上面一个是数字类型6,所以它是7
}
3,Symbols声明
不可改变且唯一,一身傲骨宁折不弯就是它
let sym2 = Symbol("key");
let sym3 = Symbol("key");
sym2 === sym3; // false, symbols是唯一的
使用场景:用作对象的key
let sym = Symbol();
let symbolObj = {
[sym]: "value",
};
console.log(symbolObj[sym]); // "value"
有一些自己的功能函数
- Symbol.hasInstance
- Symbol.iterator
- Symbol.replace
- Symbol.match等
4,联合类型和交叉类型
他两兄弟可以一起记,差不多是个反义词
- 联合类型:或,满足多个类型中的一个
- 交叉类型:并且,满足多个类型
联合类型
let arr: Array<number | string> = [1, "1"];
如下图,arr可以是数值,可以是字符串,也可以同时是数值和字符串,就是不能是其他的
交叉类型
let arr: Array<number & string> = [1, "1"];
如下图, arr必须同时是数值和字符串 两种类型才可
5,定义数组
定义数组类型的两种方式
//普通数组
let arr1: number[] = [1, 1];
let arr2: Array<number> = [1, 1];
定义联合类型
,让数组的成员可以存在多种类型
// 联合类型(和元组的达成结果一样)
let arr3: Array<number | string> = [1, "1"];
6,定义元祖
数组合并了相同类型的对象,元祖合并了不同类型
的对象
let x: [number, string];
x = [5, "abc"];
let y: [number, string] = [5, "abc"];
7,定义对象
interface Phone {
name: string; //必填
size: number; //必填
color?: string; //选填
action(): void; //必填
[propName: string]: any; //任意属性
}
let obj: Phone = {
name: "诺基亚",
size: 100,
action: function () {},
};
let obj1: Phone = {
name: "诺基亚",
size: 100,
action: function () {},
color: "blue",
other: "测试",
};
7.1, [propName: string]: any;的使用场景
如果有很多参数定义,但是你不想全部都定义,那你就是可以定义几个主要的,其他用这个表示就行了。
还在疑惑为啥这里不解释下interface
的使用场景吗,下文有专门的区域
讲解的~~~
8,定义函数
8.1,函数入参的声明
// 第一种
function fuc1(a: string, b: string) {
return ["1", "2"];
}
fuc1('a', 'b')
// 第二种
interface MyParams {
x: number;
y: number;
}
function fuc2(params: MyParams) {
return ["1", "2"];
}
fuc2({x: 1, y: 2})
8.2,函数检查类型
场景:项目有一个公共函数传参类型(SearchFun),后续的函数继承这个接口后,都得按照这个规范来
interface SearchFun {
(a: number, b: number): boolean;
}
let fuc6: SearchFun = function (c: number, d: number): boolean {
return c > d;
};
fuc6(1, 2);
如果没按规则来的场景如下:
8.3,函数出参的声明
声明出参为字符串(): string
function fuc2(a: number | string, b: number): string {
return a + "" + b;
}
fuc2('1', 2)
9,定义类
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的;
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问;
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的;
- static 不需要实例化 调用类,直接调用
class Cat2 {
name: string;
color: string;
constructor(name, color) {
this.name = name;
this.color = color;
}
static eat() {
return "吃";
}
}
var c2 = new Cat2("黑猫", "黑");
c2.name;
Cat2.eat();
10,interface和type的异同
10.1,先来两句实际工作中的使用诀窍
:
-
写法类似,通常用 interface,其他无法满足需求的情况下用 type。
-
type不会声明合并,interface会声明合并。
10.2,异同之处:
type
-
可以描述对象和函数
-
可以被继承
-
可以声明基本类型,联合类型,交叉类型,元组
-
不会声明合并(重复声明会报错提示)
-
不会有索引签名问题
interface
-
可以描述对象和函数
-
可以被继承
-
不可以声明基本类型,联合类型,交叉类型,元组
-
会声明合并(声明过的接口可以再次声明,进行合并)
-
有索引签名问题
type
声明基本类型
,联合类型
,交叉类型
,数组
,元组
的案例也给小伙伴放下面了
type userName = string; // 基本类型
type userId = string | number; // 联合类型
type arr = number[]; // 数组
// 元组
type tuple = [string, boolean];
let tupleType: tuple;
tupleType = ["cluo", true];
// 对象类型
type Person = {
id: userId; // 可以使用定义类型
name: userName;
};
const user: Person = {
id: "901",
name: "椿"
};
// 泛型
type Tree<T> = { value: T };
10.3,声明合并
声明合并指的是可以声明多次,重名的会合并
interface Person { name: string }
interface Person { age: number }
let user: Person = {
name: "cluo",
age: 666,
};
喝口水,休息一会吧~~~
11,泛型
定义的时候不指定类型,用的时候指定类型
function fuc3<T>(a: T, b: T): T[] {
return [a, b];
}
fuc3<number>(1, 2);
// 或者
fuc3<string>('a', 'b');
// 或者
fuc3("a", "b");
上面的代码片段中,fuc3<T>
中的T
表示任意类型,供入参和出参使用。
用的时候可指定number
或者 string
,函数的入参和出参类型也会相应改变。
因为ts里有类型推论,自动推测传入的'a'
是字符串,把T
设为了string
11.1,不同类型的函数
function fuc4<T>(a: T, b: T): string {
return a + "" + b + c;
}
fuc4<number | string>(1, "2");
11.2,泛型约束
泛型不支持number,因为number没有length
错误的
function f4<T>(arg: T): T {
console.log(arg.length); // 错误: T不存在length属性 比如数值等
}
f4<number>(123)
正确的
interface LengthN {
length: number;
}
function myHello<T extends LengthN>(arg: T): T {
//约束了传参必须包含length属性的类型
// console.log(arg.length);
return arg;
}
myHello<string>("123");
12,类型断言
使用场景:通常发生在你比TS更知道某个值的类型
这时,就可以使用类型断言解决TS报错。
- 尖括号写法
let someValue:any = 'this is a string';
let strLength:number = (<string>someValue).length;
- as写法
let someValue:any = 'this is a string';
let strLength:number = (someValue as string).length;
下面的padding
根据不同场景可以为number
可以为string
,但是在下面的场景,我们自己知道肯定为number
function padLeft(value: string, padding: string | number) {
// 报错: Operator '+' cannot be applied to
// types 'string | number' and 'number'
return Array(padding + 1).join(" ") + value;
}
改为
function padLeft(value: string, padding: string | number) {
return Array(padding as number + 1).join(" ") + value;
}
13,类型守卫
书接上文,有些场景as写的过多未免有些劳累。于是,有了类型守卫
主要有三种
- typeof: 用于判断 "number","string","boolean"或 "symbol" 四种类型.
- instanceof : 用于判断一个实例是否属于某个类
- in: 用于判断一个属性/方法是否属于某个对象
function padLeft(value: string, padding: string | number) {
console.log((padding as number) + 3);
console.log((padding as number) + 2);
console.log((padding as number) + 5);
return Array((padding as number) + 1).join(' ') + value;
}
13.1,用typeof
可以改为如下:
function padLeft(value: string, padding: string | number) {
if (typeof padding === 'number') {
console.log(padding + 3); //正常
console.log(padding + 2); //正常
console.log(padding + 5); //正常
return Array(padding + 1).join(' ') + value; //正常
}
if (typeof padding === 'string') {
return padding + value;
}
}
13.2,instanceof的使用场景
class Man {
handsome = 'handsome';
}
class Woman {
beautiful = 'beautiful';
}
function Human(arg: Man | Woman) {
if (arg instanceof Man) {
console.log(arg.handsome);
console.log(arg.beautiful); // 会报错,因为Man这个类里没有beautiful
} else {
// 这一块中一定是 Woman
console.log(arg.beautiful);
}
}
13.3,in的使用场景
interface Teacher {
name: string,
courses: string[];
}
interface Student {
name: string,
startTime: Date;
}
type Course = Teacher | Student;
function startCourse(cls:Course) {
if ('courses' in cls) {
console.log('teacher', cls.courses);
}
if ('startTime' in cls) {
console.log('student', cls.startTime);
}
}
14,any, unknown, void, never
14.1,any
绕过所有类型检查 => 类型检测和编译筛查全部失效
let anyValue:any = 123;
anyValue = 'anyValue';
anyValue = false;
let value1:boolean = anyValue;
14.2,unknown
绕过赋值检查 => 禁止更改传递
let unKnownValue:unknown;
unKnownValue = true;
unKnownValue = 123;
unKnownValue = 'unKnownValue';
let value1:unknown = unKnownValue; // OK
let value2:any = unKnownValue; // OK
let value3:boolean = unKnownValue; // 报错
14.3,void
void(与any相反) => 声明函数返回值
function voidFunction():void {
console.log('no return');
}
14.4,never
永不返回任何东西 或者 永远抛出error
function error(msg:string):never {
throw new Error(msg);
}
function longlongloop():never {
while(true) {
// ……
}
}
15,常用内置工具类型
15.1,Record
定义一个对象的 key 和 value 类型
type AnimalType = 'cat' | 'dog' | 'frog';
type AnimalDescription { name: string, title: string }
const AnimalMap: Record<AnimalType, AnimalDescription> = {
cat: { name: '猫', title: 'cat' },
dog: { name: '狗', title: 'dog' },
frog: { name: '蛙', title: 'wa' },
};
15.2,Partial
生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为可选项
interface Foo {
name: string
age: number
}
type Bar = Partial<Foo>
// 相当于
type Bar = {
name?: string
age?: number
}
15.3,Required
生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为必选项
interface Foo {
name: string
age?: number
}
type Bar = Required<Foo>
// 相当于
type Bar = {
name: string
age: string
}
15.4,Readonly
生成一个新类型,T 中的 K 属性是只读的,K 属性是不可修改的。
interface Foo {
name: string
age: number
}
type Bar = Readonly<Foo>
// 相当于
type Bar = {
readonly name: string
readonly age: string
}
15.5,Pick
生成一个新类型,该类型拥有 T 中的 K 属性集 ; 新类型 相当于 T 与 K 的交集
白话:借别人家的粮食吃
interface Foo {
name: string;
age?: number;
gender: string;
}
type Bar = Pick<Foo, 'age' | 'gender'>
// 相当于
type Bar = {
age?: number
gender: string
}
15.6,Omit
和Pick
相反
生成一个新类型,该类型拥有 T 中除了 K 属性以外的所有属性
type Foo = {
name: string
age: number
}
type Bar = Omit<Foo, 'age'>
// 相当于
type Bar = {
name: string
}
15.7,Exclude
如果 T 是 U 的子类型则返回 never 不是则返回 T
取差集
type A = number | string | boolean
type B = number | boolean
type Foo = Exclude<A, B>
// 相当于
type Foo = string
15.8,Extract
和 Exclude 相反
取交集
type A = number | string | boolean
type B = number | boolean
type Foo = Extract<A, B>
// 相当于
type Foo = number | boolean
完结
这篇文章我尽力把我的笔记和想法放到这了,希望对小伙伴有帮助。
欢迎转载,但请注明来源。
最后,希望小伙伴们给我个免费的点赞,祝大家心想事成,平安喜乐。