typescript-notes

1,TS 环境搭建

yarn 安装项目中的ts:yarn add typescript —dev
ts初始化项目:yarn tsc —init (会添加tsconfig.json文件)
如果配置了tsconfig.json文件,就需要只使用tsc命令,及yarn tsc,否则报错,因为此时tsc 的 配置文件开始工作了

2,标准库

TS的标准库就是内置对象的声明文件

3,ts的作用域

如何避免变量在全局的作用域:可以把文件导出为一个模块,这样就是模块作用域生效,不影响全局,但是其实在项目中,每个文件基本会以模块的形式去工作,所以会不存在问题

4,ts的Object类型

4.1)在js中,object不只是对象类型,而是泛指所有非原始对象类型,比如Array,function 也是 Object
所以interface 接口的使用是对所有Object类型生效,就可以看到interface 可以实现 Array 或者 Object,不单单是 function
4.2)定义普通的对象类型

  1. // 如果是定义普通的对象类型,需要使用的是对象字面量的形式
  2. const obj: {foo: number, bar: string} = {foo: 123, bar: '123123'}

而且实现的对象要与定义的对象类型完全一致
定义对象的更专业的方式是使用接口 interface

5,ts 的 数组类型

定义数组有两种方式
5.1)Array 泛型定义

  1. // 泛型定义数组
  2. const arr1: Array<number> = [1, 23,]
  3. const arr2: Array<T> = [123, '123'] //这里不能是 T,为什么?

5.2)使用元素类型和方括号

  1. // 元素类型定义数组
  2. const arr3: number[] = [1, 2, 3]

ts 使用强类型的优势:
1,类型约束,有利于限制传入参数,减少对其他类型的限制代码
2,减少注释,代码的声明就说明清楚了备注
比如显示 reduce 方法,就不能传入字符串,使用ts写就是

  1. // 实现 reduce 方法
  2. function sum(...args: number[]) { // number[] 表示定义数字数组的意思
  3. return args.reduce((prev, curv) => prev + curv, 0)
  4. }
  5. // sum(1, 2, 3, '123') // 不能传入字符串,直接提示出错
  6. sum(1,2,3)

6,元祖类型

6.1)定义元祖
就是明确元素数量和元素类型的数据
比如 react 里面 { name, setName} = useState
在 ts 中可以使用类似数组字面量的方法去定义元祖类型

  1. const tuple: [number, string] = [12, '12']
  2. const usestate: [string, object] = ['123', () => 'hello'] // 元祖实现的 useState

6.2)使用元祖

  1. const tuple1: [string, number] = ['joy', 12]
  2. const [nameT, age] = tuple1
  3. console.log('name', nameT);
  4. console.log('age', age);
  5. || 对应的js代码,就是 js 原生的js数组结构
  6. const tuple1 = ['joy', 12];
  7. const [nameT, age] = tuple1;
  8. console.log('name', nameT);
  9. console.log('age', age);

元祖一般用于一个函数中取返回多个类型的返回值,比如 react 的 useState 就是返回了一个变量和 一个函数

7,枚举对象

7.1)以前 js 实现有限制范围内的枚举

  1. // 以前js 使用对象去实现枚举
  2. // 但是为了先定义status 的限制
  3. const postStates = {
  4. draft: 0,
  5. finish: 1
  6. }
  7. const postInfo = {
  8. title: 'book1',
  9. content: 'asdasdasd',
  10. status: postStates.draft //2//1//0
  11. }

7.2)使用ts 实现枚举,及 枚举的默认累加 和 枚举的定义累加,及字符串数值

  1. // ts 的枚举
  2. enum PostStates {
  3. draft = 0,
  4. finish = 1,
  5. }
  6. // enum PostStates {
  7. // draft, // 即使没有设置,但是会默认从 0 开始累加
  8. // finish,
  9. // }
  10. enum PostStates {
  11. draft = 1, // 如果设置默认累加的初始值,后边也会在此基础上默认累加
  12. finish, // 2
  13. }
  14. enum PostStates {
  15. draft = 'aaa', // 可以设置为字符串,但因为不能默认累加,所以需要给定每个状态的值,较少见
  16. finish = 'bbb', // 2
  17. }
  18. const postInfo = {
  19. title: 'book1',
  20. content: 'asdasdasd',
  21. status: PostStates.draft //2//1//0
  22. }

枚举会入侵到代码,具体来说,其他的ts 的类型定义会在编译以后移除,但是枚举的代码不会,而是编译为js的一个双向指定的对象 —— 编译后如下:

  1. var PostStates;
  2. (function (PostStates) {
  3. PostStates[PostStates["draft"] = 1] = "draft"; // 枚举的代码编程对象的双向绑定
  4. PostStates[PostStates["finish"] = 2] = "finish";
  5. })(PostStates || (PostStates = {}));
  6. const postInfo = {
  7. title: 'book1',
  8. content: 'asdasdasd',
  9. status: PostStates.draft //2//1//0
  10. };

如果不需要 枚举的值可以这样的两者对应获取,可以在枚举值前写 const ,这样编译以后的代码就只是使用普通的常量枚举,所以编译以后的代码,定义枚举的代码就会删除,使用枚举的值的地方直接显示对应的常量

  1. const postInfo = {
  2. title: 'book1',
  3. content: 'asdasdasd',
  4. status: 1 /* draft */ // 常量枚举直接是常量值和对应的状态
  5. };

8,函数类型

8.1)函数声明的方法定义:

  1. function fun1(a: number, b: number, ...rest: number[]):number {
  2. return a + b
  3. }
  4. fun1(1, 2)

如果需要可以设置多个参数,就需要借助 es6的 … rest 操作符,然后后边添加数组的对象定义
8.2)函数表达式

  1. // 函数定义 - 添加函数限制,需要给赋值的那个变量也添加限定,
  2. // 但是这个限定就包括函数参数的和返回值的限定,需要使用一个箭头函数的方法定义
  3. const fun2:(a: number, b: number) => number = function (a:number, b: number): number {
  4. return a + b
  5. }
  6. fun2(1, 2)

9,任意类型

  1. // 任意类型 - 例如实现 stringfy, 就需要传入任何类型数据,所以需要使用 any 类型
  2. function stringify(value: any) {
  3. return JSON.stringify(value)
  4. }
  5. stringify('123213')
  6. stringify(1212)

any 任然是动态类型的值,所以还是可以接收任意类型的值

  1. let foo: any = 123
  2. foo = '123' // 任然是动态类型,所以定义了以后可以修改类型
  3. foo.bar() // any不做类型检查,所以在上面去调用任意的属性和方法,语法不会报错

所以 ts 对于 any 不会做任何类型检查,所以可以直接在上面去调用任意的属性和方法,语法不会报错,但是因为没有类型检查,所以涉及类型安全的问题,所以建议不要轻易使用 any,只是说兼容老的代码的时候会需要使用到

10,隐式类型推断

  1. let foo1 = 123 // 如果直接设置为数字,就把 foo1 隐式类型推断为数字,就不能赋值为字符串,除非类型使用 any
  2. foo1 = '123' // 会提示错误

如果 ts 无法推断变量的类型,就会默认为 any 类型,然后可以重新赋值不同类型的值

  1. let foo2
  2. foo2 = 123
  3. foo2 = '123' // 可以赋值,因为 foo2 会默认为 any 类型

虽然有隐式类型推断,但是仍然建议代码中明确变量的类型,这样便于维护代码,理解代码

11, 类型断言

有的时候 ts 无法推断类型,但是我们自己知道一定是什么类型,就可以使用 类型断言
两种断言方法:
11.1)as 断言:const num11 = res as number
11.2)<> 断言:const num22 = res

  1. const nums = [11, 2, 333, 444]
  2. const res = nums.find( i => i > 0) // find 找到第一个大于0的数字
  3. console.log('res', res);
  4. // 这里一定是一个大于0的数字,但是ts 中的 res 是一个number 和 undefined,
  5. // 因为ts 认为存在找不到的情况,所以 如果需要直接使用这个返回值,
  6. // const square = res * res // 就不能直接把这个返回值当成数字去使用,会提示错误
  7. // 使用断言
  8. const num11 = res as number // as 断言
  9. const square = num11 * num11 // 断言以后就不会报错了
  10. const num22 = <number>res // <>断言
  11. const square1 = num22 * num22

但是注意: 在jsx里面的时候会和之前的 <> 标签冲突,所以建议使用 as断言
断言可以不是强制类型转换,因为断言不是把一个类型转换为另外一个类型,类型转换是发生在代码运行时候的一个概念,而类型断言只是在编译时的概念,当代码编译过后,这个断言就不会存在

12,interface 接口

12.1)一般接口

  1. // interface
  2. interface IPost {
  3. name: string; // 可以 , 或 :
  4. title: string;
  5. status: number;
  6. }
  7. function printPost(post: IPost) {
  8. console.log(post.name)
  9. console.log(post.title)
  10. }

interface的代码在编译以后会消失,ts 中的 interface只是对象的约束,所以没有实际的意义
12.2)可选成员,只读属性
可选成员: 类似给这个属性添加了 string 属性 和 undefined 属性

  1. interface IPost {
  2. readonly name: string; // 可以 , 或 :
  3. title?: string;
  4. status: number;
  5. }
  6. const p1: IPost = {
  7. name: 'joy',
  8. // title: 'qeqwe' 可以省略
  9. status: 1
  10. }
  11. // p1.name = 'judy' 报错

12.3)interface 任意属性

  1. interface ICache {
  2. [key: string]: string
  3. }
  4. const content1: ICache = {}
  5. content1.foo = '123'
  6. const content2: ICache = {
  7. title: '123123'
  8. }

[key: string]: string 可以预留任何的 key 和 属性

13,类 class

13.1)定义类
在 es6之前js使用的是函数和原型去实现的类,然后在 es6 中有了专门的 class

  1. class Person {
  2. constructor(name, age) {
  3. this.name = name // 以前的 js 添加属性直接在 constructor 里面定义,但是在ts 中这样定义会报错
  4. //提示错误:Person没有name属性, Property 'name' does not exist on type 'Person'
  5. }
  6. }

在 ts 中需要明确的声明类的属性,不能直接添加,需要在类中声明属性,就直接在类中定义,是es7新增的方法
所以在ts 中需要优先定义属性,然后给属性设置类型,还可以设置一个初始值
注意:在ts中类属性必须要有一个默认初始值或者在构造函数中去初始化,否则在类中定义的属性会提示错误
13.2)类的访问修饰符

  1. private 私有属性:外部能不访问,只有类的内部可以访问,并且不能被子类继承 ``typescript class Person { name: string private age: number constructor(name: string, age: number) { this.name = name this.age = age } say(msg: string) { console.log( I am ${this.name}, and i’m ${this.age}, ${msg}`) } }

const tom = new Person(‘tom’, 12) console.log(tom.name) // 可以拿到 name console.log(tom.age) // 因为 age 是 private 所以外部能不访问,只有类的内部可以访问

// 例2:子类不能访问父类的私有属性和方法 class Student extends Person { constructor(name: string, age: number) { super(name, age) console.log(this.age) console.log(this.gender) } }

  1. 2. public 公共属性:定义的属性和方法默认是公共属性,外部可以访问和修改,所以一般省略
  2. 2. protected 受保护属性:只允许在子类当中去访问对应的成员
  3. ```typescript
  4. // 例一:
  5. class Person {
  6. name: string
  7. private age: number
  8. protected gender: boolean
  9. constructor(name: string, age: number) {
  10. this.name = name
  11. this.age = age
  12. this.gender = true
  13. }
  14. say(msg: string) {
  15. console.log(` I am ${this.name}, and i'm ${this.age}and ${this.gender ? 'boy' : 'girl'}, ${msg}`)
  16. }
  17. }
  18. const tom = new Person('tom', 12)
  19. console.log(tom.gender) // protected 属性在外部也是无法访问
  20. // 例二: 使用子类拿到 protected 属性
  21. class Person {
  22. name: string
  23. private age: number
  24. protected gender: boolean
  25. constructor(name: string, age: number) {
  26. this.name = name
  27. this.age = age
  28. this.gender = true
  29. }
  30. say(msg: string) {
  31. console.log(` I am ${this.name}, and i'm ${this.age}and ${this.gender ? 'boy' : 'girl'}, ${msg}`)
  32. }
  33. }
  34. class Student extends Person {
  35. constructor(name: string, age: number) {
  36. super(name, age)
  37. console.log(this.gender) // 子类可以拿到父类中的 protected属性
  38. }
  39. }
  40. const tom = new Person('tom', 12)
  41. console.log(tom.gender) // protected 属性在外部也是无法访问

13.3)构造函数的访问类型
如果给 constructor 添加 private,这个类就不能在外部去实例化也不能被继承
只能在类的内部添加一个静态的方法去创建这个类型的实例,因为private只能在类的内部去访问

  1. class Animal {
  2. name: string
  3. age: number
  4. private constructor(name: string, age: number){
  5. this.name = name
  6. this.age = age
  7. }
  8. sayHi(msg: string) {
  9. console.log(`this is ${this.name}, and it's ${this.age}, ${msg}`)
  10. }
  11. }
  12. class Dog extends Animal { // 提示出错,私有的constructor 不能继承
  13. }
  14. const dog = new Animal('popy', 12) // 提示出错,私有的constructor 不能实例化
  1. static :静态方法
    可以在静态类型的方法中去实例化 private 的 构造函数,然后让外部访问到 —— (单例模式?有什么好处) ``typescript class Animal { name: string age: number private constructor(name: string, age: number){ this.name = name this.age = age } sayHi(msg: string) { console.log(this is ${this.name}, and it’s ${this.age}, ${msg}`) }

    static create(name: string, age: number) { return new Animal(name, age) } }

const dog = Animal.create(‘popy’, 12) // 外部可以实例化 static 的构造函数

  1. 如果是给构造函数 protected 这个类也是不能在外部被实例化的,但是可以被子类继承(与 static 的区别)<br />13.4)类的只读属性<br />关键字: readonly
  2. ```typescript
  3. class Person {
  4. name: string
  5. private age: number
  6. protected readonly gender: boolean
  7. constructor(name: string, age: number) {
  8. this.name = name
  9. this.age = age
  10. this.gender = true
  11. }
  12. say(msg: string) {
  13. console.log(` I am ${this.name}, and i'm ${this.age}and ${this.gender ? 'boy' : 'girl'}, ${msg}`)
  14. }
  15. }
  16. const tom = new Person('tom', 12)
  17. console.log(tom.gender) // protected 属性在外部也是无法访问
  18. tom.gender = false // 不能修改,因为是 只读属性
  19. // Cannot assign to 'gender' because it is a read-only property.

13,类和接口的区别

类和类之前也有共同的特征,这些特征可以使用 interface 去抽象,比如一个类实现了一个协议,另外一个类也实现了相同的协议,这个相同的协议就可以抽象为 interface

  1. interface RunAndEat {
  2. eat(food: string): void
  3. run(dis: number): void
  4. }
  5. class Person implements RunAndEat {
  6. eat(food: string): void {
  7. console.log('human eat' + food)
  8. }
  9. run(dis: number): void {
  10. console.log('直立行走' + dis)
  11. }
  12. }
  13. class Animal implements RunAndEat {
  14. eat(food: string): void {
  15. console.log('趴着吃' + food)
  16. }
  17. run(dis: number): void {
  18. console.log('爬行' + dis)
  19. }
  20. }

但是在其他语言建议接口的定义最好功能简单,这个 RunAndEat 就实现了两个方法的功能,如果有一个电动车的类,有 run 的方法,但是没有eat 的方法就需要重新写一个接口,所以建议接口就简单 —— (也叫函数式接口?)

  1. interface Run {
  2. eat(food: string): void
  3. }
  4. interface Eat {
  5. run(dis: number): void
  6. }
  7. class Person implements Run, Eat {
  8. eat(food: string): void {
  9. console.log('human eat' + food)
  10. }
  11. run(dis: number): void {
  12. console.log('直立行走' + dis)
  13. }
  14. }
  15. class Animal implements Run, Eat {
  16. eat(food: string): void {
  17. console.log('趴着吃' + food)
  18. }
  19. run(dis: number): void {
  20. console.log('爬行' + dis)
  21. }
  22. }

14,抽象类

14.1)抽象类理解:
抽象类和接口类似,也是约束子类当中一定要有某一个成员,但是不同的是抽象类可以包含一些具体的实现,但是接口就只是成员的抽象,不包含实现,一般对于大的类,建议是使用抽象类,比如刚刚的动物类,就应该是一个抽象类,因为动物只是一个泛指,并不具体它的下面一定有一些更细化的分类,比如 狗,猫,
14.2)使用的关键字: abstract
如果是一个抽象类就只能继承,不能使用 new 的方法去创建实例化,必须使用子类去继承这个类型

  1. // 例1:
  2. abstract class Animal {
  3. eat(food: string) {
  4. console.log('eat ' + food)
  5. }
  6. }
  7. const ani = new Animal() // 提示错误不能创建抽象类的实例:Cannot create an instance of an abstract class.

14.3)抽象类中可以定义抽象方法
抽象方法不需要具体的方法,如果父类有一个抽象方法就需要子类去实现这个抽象的方法
实例化的对象就有抽象类的方法和子类的方法

  1. abstract class Animal {
  2. eat(food: string) {
  3. console.log('eat ' + food)
  4. }
  5. abstract run(dis: number): void // 如果是抽象的方法,就不能实现,只能在子类中取实现
  6. }
  7. class Dog extends Animal {
  8. run(dis: number): void { // 子类继承一个抽象的类中有抽象的方法,子类就一定需要去实现这个抽象的方法
  9. console.log('run' + dis)
  10. }
  11. }
  12. const ani = new Dog()
  13. ani.eat('meat') // 实例化的对象就有抽象类的方法和子类的方法
  14. ani.run(12)

15,泛型

泛型是在定义函数,接口,类的时候没有指定具体的类型,而是在使用的时候才传入具体类型的特征,比如泛型定义数组,在定义后,使用的时候传入类型,这样的目的是极大的程度复用代码

  1. // 创建数字类型的数组,函数返回数字类型的数组 number[]
  2. function createNumberArray(len: number, val: number): number[] {
  3. // es6 的生成数组的方法
  4. const arr = Array(len).fill(val) // Array 创建的是 any类型的数组,
  5. // Array的提示:(arrayLength?: number | undefined) => any[]
  6. // any[] any类型的数组
  7. return arr
  8. }

Array 创建的是 any类型的数组,Array的提示:(arrayLength?: number | undefined) => any[] (any[] any类型的数组),如果要指定数组的类型,需要通过泛型参数的方法去传递一个泛型

  1. function createNumberArray(len: number, val: number): number[] {
  2. const arr = Array<number>(len).fill(val) // es6 的生成数组的方法
  3. return arr
  4. }

这个时候Array就会提示是一个 数字类型数组 (arrayLength: number) => number[] (数字类型数组)
Array 就是一个泛型类,因为在定义Array的时候不知道用Array 使用的时候传入什么类型
如果创建字符类的数组

  1. // 创建字符类型的数组
  2. function createStringArray(len: number, val: string): string[] {
  3. const arr = Array<string>(len).fill(val)
  4. return arr
  5. }

所以这样代码就比较冗余,明明就是修改了参数的类型,然后修改函数返回的类型,所以可以把类型作为一个参数传入,就是泛型参数,就是函数名称,然后把参数中不明确的类型都使用代替

  1. //把类型变成一个参数
  2. // <T> 尖括号里面放的是泛型参数 T,单独的一个 T 就是类型的参数, T[] 泛型型的数组 类似 any[]和 number[]
  3. function createArray<T>(len: number, val: T): T[] {
  4. const arr = Array<T>(len).fill(val)
  5. return arr
  6. }
  7. const arr2 = createArray<string>(12, 'tt')
  8. // <string> 放的类型的参数值,12是传入的len的number,'tt'表示传入的字符串,因为val参数是泛型型的,没有限制,要靠传入才知道类型

问题:any 是泛型么?不是,any 是定义时,泛型是使用时

16,类型声明

16.1)第三方模块的 声明文件

  1. import {camelCase} from 'loadsh';
  2. declare function camelCase(input: string): string // 这样使用camelCase就有类型了
  3. // 安装以后提示找不到类型声明文件:Could not find a declaration file for module 'loadsh'.
  4. const res = camelCase('heelo type')
  5. // 使用的是后也不能找到camelCase的类型提示,就是因为之前说的没有类型文件 —— 可以自己写类型声明

这个 declare 就是为了兼容以前的js模块没有类型声明的,在ts中可以重新添加这个函数方法的类型声明

ts - 初始

1,类型注释

在给函数传递参数的时候,需要指定参数的类型,如果类型冲突,则报错
例如,参数指定string类型

  1. function greeter(person: string) {
  2. return "Hello, " + person;
  3. }
  4. let user = [0, 1, 2];
  5. document.body.textContent = greeter(user);

由于类型传递错误,所以报错:

  1. error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.

2,接口及接口继承

  1. // 定义一个接口,强化了类型
  2. interface IPerson {
  3. firstName:string,
  4. lastName:string,
  5. sayHi: ()=>string
  6. }
  7. // 实例化一个接口
  8. var customer:IPerson = {
  9. firstName:"Tom",
  10. lastName:"Hanks",
  11. sayHi: ():string =>{return "Hi there"}
  12. }
  13. // 实例化一个接口
  14. var employee:IPerson = {
  15. firstName:"Jim",
  16. lastName:"Blakes",
  17. sayHi: ():string =>{return "Hello!!!"}
  18. }
  19. console.log("Employee 对象 ")
  20. console.log(employee.firstName)
  21. console.log(employee.lastName)

3,强类型函数

  1. function add(x: string, y: string, z?:string): string{
  2. return x + y;
  3. }

在TypeScript的函数中我们可以给每个参数提供类型注解,还能为函数提供返回类型注解(在参数列表后的”:类型”),同时TypeScript可以在参数名右边加上一个?表示可选参数。

4,泛型

ts是静态类型,所以使用泛型可以给静态类型提供一个动态机会
动静态语言的区别:
动静态,是指一个变量能不能存别的类型的数据
强弱类型:
类型不同不能计算,这叫强类型

  1. function itself<T>(a: T):T {
  2. return a
  3. }
  4. console.log(itself(true)) // true
  5. console.log(itself(1)) // 1
  6. console.log(itself('1')) // '1'
  7. console.log(itself([1, 2, 3])) // [1, 2, 3]