小菜鸡作者第一次学TypeScript写的笔记,主要是记录一下,哪里写的不好或者不对的话欢迎留言,一起进步
一、什么是TypeScript
(官网)TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript
超集:比如我们现在的 es5 和 es6,我们都知道es6是es5的升级,es6 扩展了很多语法,新增了很多的内容,那么对于es5 来说es6 就是它的超集。
二、搭建ts的运行环境
- 先按照node.js
- 安装 TypeScript
//全局安装
$ npm i typescript -g
$ cnpm i typescript -g
$ yarn global add typescript
- 测试是否安装成功
$ tsc -v
- 编译ts文件
$ tsc 文件名.ts
(编译完成后如果原ts文件报错,可通过tsc --init生成tsconfig.json文件解决报错问题)
安装ts-node在终端查看编译结果
// 安装
$ npm install -g ts-node
$ cnpm install -g ts-node
// 测试
$ ts-node 文件名.ts
(在实际开发中使用的话好像有坑www.jianshu.com/p/cbd3bcdbb…)
三、TypeScript 基础类型
let a: boolean = true; //Boolean 类型
let num: number = 10;//Number 类型
let name: string = "kai";//String 类型
let a:null = null;//null类型
......
3-1、Any 类型
在 TypeScript 中,任何类型都可以被归为 any 类型
let text: any = 666;
text = "semlinker";
text = false;
3-2、Unknown 类型
类似于 any 类型,但是 unknown 类型的值只能赋值给 any 类型和 unknown 类型
let val: unknown;
val = 1; // ok
val = 'kai'; // ok
val = true; // ok
......
let val2: unknown = val; // ok
let val3: any = val; // ok
let val4: string = val; // 报错
let val5: number = val; // 报错
......
3-3、数组的类型
let arr: number[] = [1, 2, 3];//数组定义了什么类型,那么这个数组里面的值也就是相对应类型的值
let arr: (number|string)[]=['1','2']//数组中存在两种类型的写法
// 二维数组
let arr:[string,number][]=[['1',2],
['1',2]
]
// 元组(限定数组每个值的固定类型)
let arr: [string, number];
arr = ['kai', 1]; // 运行正常
arr = [1, 'kai']; // 报错
3-4、类型别名 type
// 数组中多个对象
let obj:{name:string,phone:number}[] = [
{name:'1',phone:110}
];
// 当数值很多时可以使用类型别名
// 类型别名:type 名称 = 对应的内容
type objType = {
name:string,
phone:number,
age:number
}
let obj:objType[] = [{...}]
3-5、对象的类型和interface接口
const obj:{key:类型}={key:类型}//定义对象的每个key值的类型
//可以通过interface来进行检验(建议首字母大写,用分号)
interface Object{
readonly name:string; // 只读属性: readonly
age:number;
phone?:number;//不一定会有的属性:?
[propnName:string](key的类型):any(值) //有时候值很多,不可能一个一个的去定义,就可以使用任意属性来解决这个问题
}
let obj:Object = {
name:'kai',
age:18,
site:'宝安区'
}
// 泛型接口
interface Object<T> {
(arg: T): T;
}
3-6、函数的类型
function info(): 返回值类型 {
return :'fun'
}
// 函数中形参类型注解:
// fn(a:number)
// fn({a,b}:{a:string,b:number})
// 箭头函数
const 函数名:()=>函数返回值类型 =()=>{return 返回值}
3-7、Never 类型
never 类型表示的是那些永不存在的值的类型。主要使用在那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。
3-8、| 联合类型
- 基础类型联合
let a: string | number;
a = 1; // ok
a= "2"; // ok
- 对象类型联合 对象联合类型只能访问联合中共同拥有的成员
interface info{
name: string,
age: number,
fun(): void
}
interface info2{
name: string,
age: number,
}
declare function infoFun(): info | info2;
let infoData = infoFun();
infoData.name = 'kai'; // ok
infoData.fun();//error 非共同成员
3-9、& 交叉类型
多种类型的集合,交叉类型将具有所联合对象的所有成员
interface info{
name: string,
age: number
}
interface info2{
sex: string
}
declare function infoFun(): info & info2;
let infoData = infoFun();
infoData.name = 'kai'; // ok
infoData.sex = '男'; // ok
3-10、interface 和 type 的区别
用 interface 描述数据结构,用 type 描述类型关系
相同点
- 都可以描述一个对象或者函数
// interface
interface User {
name: string
age: number
}
interface SetUser {
(name: string, age: number): void;
}
// type
type User = {
name: string
age: number
};
type SetUser = (name: string, age: number)=> void;
- 都可以extends interface 和 type 都可以拓展,并且两者并不是相互独立的,也就是说 interface 可以 extends type , type 也可以 extends interface 。 虽然效果差不多,但是两者语法不同。
// interface extends interface
interface Name {
name: string;
}
interface User extends Name {
age: number;
}
// type extends type
type Name = {
name: string;
}
type User = Name & { age: number };
// interface extends type
type Name = {
name: string;
}
interface User extends Name {
age: number;
}
// type extends interface
interface Name {
name: string;
}
type User = Name & {
age: number;
}
不同点
- type 可以声明基本类型别名,联合类型,元组等类型,而且可以使用 typeof 获取实例的 类型进行赋值,而 interface 不行
- interface 能够声明合并
interface User {
name: string
age: number
}
interface User {
sex: string
}
/*
User 接口为 {
name: string
age: number
sex: string
}
*/
3-11、Void 和 Never 的区别
void 表示没有任何类型, never 表示永远不存在的值的类型。
// 返回never的函数必须存在无法达到的终点
function fun1(): never {
while (true) {
}
}
// 这个函数不能申明其返回值类型是 never
function fun2(): void {
console.log("123");
}
3-12、Enum 类型
使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。
- 数字枚举
enum Direction {
Up = 1,
Down,
Left,
Right
}
let val: Direction = Direction.Down; // 2
如上,当我们不给 Up 初始化为 1 时,其余的成员会从 0 开始自动增长,初始化为 1 后,其余的成员会从 1 开始自动增长
- 字符串枚举
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
-
在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
-
由于字符串枚举没有自增长的行为,字符串枚举可以很好的序列化。 换句话说,如果你正在调试并且必须要读一个数字枚举的运行时的值,这个值通常是很难读的 - 它并不能表达有用的信息,字符串枚举允许你提供一个运行时有意义的并且可读的值,独立于枚举成员的名字。
- 常量枚举
常量枚举是使用 const 关键字修饰的枚举,常量枚举会使用内联语法,不会为枚举类型编译生成任何 JavaScript。
const enum Direction {
Up,
Down,
Left,
Right
}
let val: Direction = Direction.Up;
// ES5
var val = 0 /* Up */;
- 异构枚举 异构枚举的成员值是数字和字符串的混合。
enum Enum {
A,
B,
C = "C",
D = "D",
E = 8,
F,
}
console.log(Enum.A) //输出:0
console.log(Enum[0]) // 输出:A 数字枚举相对字符串枚举多了 “反向映射”
四、TypeScript 类
class:
class Person{}
consot obj:Person = new Person()
4-1、类的三种访问修饰符
public [pʌblɪk]:公有(公开的),可以在任何地方被访问到
private [ˈpraɪvɪt]:私有的,不能在声明它的类的外部调用或者访问
protected [prəˈtektɪd]:受保护的,和private类似,区别在于它可以在子类访问
class Parent{
public name: string;
private age: number;
protected text: string;
constructor(){
this.name = 'kai'
this.age = 18
this.text = '喝水'
}
}
class child extends Parent{
fun(){
return this.text
}
}
let obj = new Parent()
console.log(obj.name) // kai
console.log(obj.age) // 报错
console.log(obj.text) // 报错
let childObj = new child()
console.log(childObj.fun()) // 喝水
4-2、super
super 在子类的 constructor 中调用,是在子类中执行了父类的构造函数,其实是无关父类原型的,如果想到改变父类的原型方法,可以直接重名覆盖,如果想利用父类原型方法,也可以用 super.method 来引用。如果你不在 constructor 里写 super 并传入相应参数,那么相当于只继承原型方法。每个类自己的 constructor 其实就是定义自身的属性和方法,而不是原型上的。可以直接使用 this.abc 来添加, this 指自己, super 指父类。子类继承的时候不写 constructor ,则默认会把父类自身的属性和方法生成到子类。
4-3、存取器
因为在 class 中定义的属性默认为 public ,在任何地方逗可以被访问到,但是有的时候我们又不想我们的属性在任何地方都可以访问到,设置为 private 外部又访问不到,所以就有了 getter 和 setter ,它们的意义在于我可以保护到原来的值,同时在 getter 和 setter 里面去对我们在外部想获取的数据进行处理
class Person {
private name: string;
constructor(){
this.name = '我是谁'
}
get name1(): string {
return this.name;
}
set name1(text: string) {
this.name = text
}
}
let obj = new Person()
console.log(obj.name1) // 我是谁
obj.name1 = '是我'
console.log(obj.name1) // 是我
4-4、抽象类
使用 abstract 关键字声明的类,我们称之为抽象类。抽象类只要用于把共同的东西抽象出去,抽象类不能被实例化(不能 new ),因为它里面包含一个或多个抽象方法。所谓的抽象方法,是指不包含具体实现的方法
abstract class Person {
constructor(public text: string){}
// 抽象方法
abstract say(text2: string) :void;
}
class Developer extends Person {
constructor(text: string) {
super(text);
}
say(text2: string): void {
console.log(`${this.text} 会让我 ${text2}`);
}
}
const lolo = new Developer("下班");
lolo.say("快乐"); // 下班会让我快乐
五、TypeScript 泛型
泛型 是允许同一个函数接受不同类型参数的一种模板。相比于使用 any 类型,使用泛型来创建可复用的组件要更好,因为泛型会保留参数类型,便于我们后续的维护和阅读。
// 不用泛型的情况下的函数
function identity(arg: any): any {
return arg;
}
// 泛型的情况下的函数
function identity<T>(arg: T): T {
return arg;
}
// 两种使用方法
let output = identity<string>("myString");
let output = identity("myString");
根据上面的代码,使用 any 类型会导致这个函数可以接收任何类型的arg参数,这样我们将无法确认最终返回值的类型,而使用 泛型 的情况下,我们给identity方法添加了类型变量 T 。 T 帮助我们捕获用户传入的类型(比如: string ),之后我们就可以使用这个类型。 之后我们再次使用了 T 当做返回值类型。现在我们可以知道参数类型与返回值类型是相同的了。 这样我们就可以跟踪函数里使用的类型的信息。
T 在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。
function identity <T, U>(value: T, name: U) : T {
console.log(name);
return value;
}
console.log(identity<Number, string>(18, "kai"));
5-1、泛型类
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
(类的静态属性不能使用这个泛型类型)
5-2、泛型操作符
- typeof typeof 操作符可以用来获取一个变量声明或对象的类型。
interface Person {
name: string;
age: number;
}
const sem: Person = { name: 'kai', age: 33 };
type Sem = typeof sem; // -> Person
- keyof keyof 操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
interface Person {
name: string;
age: number;
}
type K1 = keyof Person; // "name" | "age"
type K3 = keyof { [x: string]: Person }; // string | number
- in in 用来遍历枚举类型。
type Keys = "a" | "b" | "c"
type Obj = {
[p in Keys]: any
} // -> { a: any, b: any, c: any }
- infer 在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。
type ReturnType<T> = T extends (
...args: any[]
) => infer R ? R : any;
以上代码中 infer R 就是声明一个变量来承载传入函数签名的返回值类型,简单说就是用它取到函数返回值的类型方便之后使用。
- extends 有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
loggingIdentity(3); // 报错;因为这个泛型函数被定义了约束,所以它不再是适用于任意类型
loggingIdentity({length: 10, value: 3}); // 这时我们需要传入符合约束类型的值,必须包含必须的属性
- Partial Partial 的作用就是将某个类型里的属性全部变为可选项 ? 。 定义:
/**
* node_modules/typescript/lib/lib.es5.d.ts
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
在以上代码中,首先通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P ,最后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选。
示例:
interface Todo {
title: string;
description: string;
}
function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
return { ...todo, ...fieldsToUpdate };
}
const todo1 = {
title: "Learn TS",
description: "Learn TypeScript",
};
const todo2 = updateTodo(todo1, {
description: "Learn TypeScript Enum",
});
在上面的 updateTodo 方法中,我们利用 Partial 工具类型,定义 fieldsToUpdate 的类型为 Partial ,即:
{
title?: string | undefined;
description?: string | undefined;
}