typescript-notes
1,TS 环境搭建
yarn 安装项目中的ts:yarn add typescript —dev
ts初始化项目:yarn tsc —init (会添加tsconfig.json文件)
如果配置了tsconfig.json文件,就需要只使用tsc命令,及yarn tsc,否则报错,因为此时tsc 的 配置文件开始工作了
2,标准库
3,ts的作用域
如何避免变量在全局的作用域:可以把文件导出为一个模块,这样就是模块作用域生效,不影响全局,但是其实在项目中,每个文件基本会以模块的形式去工作,所以会不存在问题
4,ts的Object类型
4.1)在js中,object不只是对象类型,而是泛指所有非原始对象类型,比如Array,function 也是 Object
所以interface 接口的使用是对所有Object类型生效,就可以看到interface 可以实现 Array 或者 Object,不单单是 function
4.2)定义普通的对象类型
// 如果是定义普通的对象类型,需要使用的是对象字面量的形式const obj: {foo: number, bar: string} = {foo: 123, bar: '123123'}
而且实现的对象要与定义的对象类型完全一致
定义对象的更专业的方式是使用接口 interface
5,ts 的 数组类型
定义数组有两种方式
5.1)Array 泛型定义
// 泛型定义数组const arr1: Array<number> = [1, 23,]const arr2: Array<T> = [123, '123'] //这里不能是 T,为什么?
5.2)使用元素类型和方括号
// 元素类型定义数组const arr3: number[] = [1, 2, 3]
ts 使用强类型的优势:
1,类型约束,有利于限制传入参数,减少对其他类型的限制代码
2,减少注释,代码的声明就说明清楚了备注
比如显示 reduce 方法,就不能传入字符串,使用ts写就是
// 实现 reduce 方法function sum(...args: number[]) { // number[] 表示定义数字数组的意思return args.reduce((prev, curv) => prev + curv, 0)}// sum(1, 2, 3, '123') // 不能传入字符串,直接提示出错sum(1,2,3)
6,元祖类型
6.1)定义元祖
就是明确元素数量和元素类型的数据
比如 react 里面 { name, setName} = useState
在 ts 中可以使用类似数组字面量的方法去定义元祖类型
const tuple: [number, string] = [12, '12']const usestate: [string, object] = ['123', () => 'hello'] // 元祖实现的 useState
6.2)使用元祖
const tuple1: [string, number] = ['joy', 12]const [nameT, age] = tuple1console.log('name', nameT);console.log('age', age);|| 对应的js代码,就是 js 原生的js数组结构const tuple1 = ['joy', 12];const [nameT, age] = tuple1;console.log('name', nameT);console.log('age', age);
元祖一般用于一个函数中取返回多个类型的返回值,比如 react 的 useState 就是返回了一个变量和 一个函数
7,枚举对象
7.1)以前 js 实现有限制范围内的枚举
// 以前js 使用对象去实现枚举// 但是为了先定义status 的限制const postStates = {draft: 0,finish: 1}const postInfo = {title: 'book1',content: 'asdasdasd',status: postStates.draft //2//1//0}
7.2)使用ts 实现枚举,及 枚举的默认累加 和 枚举的定义累加,及字符串数值
// ts 的枚举enum PostStates {draft = 0,finish = 1,}// enum PostStates {// draft, // 即使没有设置,但是会默认从 0 开始累加// finish,// }enum PostStates {draft = 1, // 如果设置默认累加的初始值,后边也会在此基础上默认累加finish, // 2}enum PostStates {draft = 'aaa', // 可以设置为字符串,但因为不能默认累加,所以需要给定每个状态的值,较少见finish = 'bbb', // 2}const postInfo = {title: 'book1',content: 'asdasdasd',status: PostStates.draft //2//1//0}
枚举会入侵到代码,具体来说,其他的ts 的类型定义会在编译以后移除,但是枚举的代码不会,而是编译为js的一个双向指定的对象 —— 编译后如下:
var PostStates;(function (PostStates) {PostStates[PostStates["draft"] = 1] = "draft"; // 枚举的代码编程对象的双向绑定PostStates[PostStates["finish"] = 2] = "finish";})(PostStates || (PostStates = {}));const postInfo = {title: 'book1',content: 'asdasdasd',status: PostStates.draft //2//1//0};
如果不需要 枚举的值可以这样的两者对应获取,可以在枚举值前写 const ,这样编译以后的代码就只是使用普通的常量枚举,所以编译以后的代码,定义枚举的代码就会删除,使用枚举的值的地方直接显示对应的常量
const postInfo = {title: 'book1',content: 'asdasdasd',status: 1 /* draft */ // 常量枚举直接是常量值和对应的状态};
8,函数类型
8.1)函数声明的方法定义:
function fun1(a: number, b: number, ...rest: number[]):number {return a + b}fun1(1, 2)
如果需要可以设置多个参数,就需要借助 es6的 … rest 操作符,然后后边添加数组的对象定义
8.2)函数表达式
// 函数定义 - 添加函数限制,需要给赋值的那个变量也添加限定,// 但是这个限定就包括函数参数的和返回值的限定,需要使用一个箭头函数的方法定义const fun2:(a: number, b: number) => number = function (a:number, b: number): number {return a + b}fun2(1, 2)
9,任意类型
// 任意类型 - 例如实现 stringfy, 就需要传入任何类型数据,所以需要使用 any 类型function stringify(value: any) {return JSON.stringify(value)}stringify('123213')stringify(1212)
any 任然是动态类型的值,所以还是可以接收任意类型的值
let foo: any = 123foo = '123' // 任然是动态类型,所以定义了以后可以修改类型foo.bar() // any不做类型检查,所以在上面去调用任意的属性和方法,语法不会报错
所以 ts 对于 any 不会做任何类型检查,所以可以直接在上面去调用任意的属性和方法,语法不会报错,但是因为没有类型检查,所以涉及类型安全的问题,所以建议不要轻易使用 any,只是说兼容老的代码的时候会需要使用到
10,隐式类型推断
let foo1 = 123 // 如果直接设置为数字,就把 foo1 隐式类型推断为数字,就不能赋值为字符串,除非类型使用 anyfoo1 = '123' // 会提示错误
如果 ts 无法推断变量的类型,就会默认为 any 类型,然后可以重新赋值不同类型的值
let foo2foo2 = 123foo2 = '123' // 可以赋值,因为 foo2 会默认为 any 类型
虽然有隐式类型推断,但是仍然建议代码中明确变量的类型,这样便于维护代码,理解代码
11, 类型断言
有的时候 ts 无法推断类型,但是我们自己知道一定是什么类型,就可以使用 类型断言
两种断言方法:
11.1)as 断言:const num11 = res as number
11.2)<> 断言:const num22 = res
const nums = [11, 2, 333, 444]const res = nums.find( i => i > 0) // find 找到第一个大于0的数字console.log('res', res);// 这里一定是一个大于0的数字,但是ts 中的 res 是一个number 和 undefined,// 因为ts 认为存在找不到的情况,所以 如果需要直接使用这个返回值,// const square = res * res // 就不能直接把这个返回值当成数字去使用,会提示错误// 使用断言const num11 = res as number // as 断言const square = num11 * num11 // 断言以后就不会报错了const num22 = <number>res // <>断言const square1 = num22 * num22
但是注意: 在jsx里面的时候会和之前的 <> 标签冲突,所以建议使用 as断言
断言可以不是强制类型转换,因为断言不是把一个类型转换为另外一个类型,类型转换是发生在代码运行时候的一个概念,而类型断言只是在编译时的概念,当代码编译过后,这个断言就不会存在
12,interface 接口
12.1)一般接口
// interfaceinterface IPost {name: string; // 可以 , 或 :title: string;status: number;}function printPost(post: IPost) {console.log(post.name)console.log(post.title)}
interface的代码在编译以后会消失,ts 中的 interface只是对象的约束,所以没有实际的意义
12.2)可选成员,只读属性
可选成员: 类似给这个属性添加了 string 属性 和 undefined 属性
interface IPost {readonly name: string; // 可以 , 或 :title?: string;status: number;}const p1: IPost = {name: 'joy',// title: 'qeqwe' 可以省略status: 1}// p1.name = 'judy' 报错
12.3)interface 任意属性
interface ICache {[key: string]: string}const content1: ICache = {}content1.foo = '123'const content2: ICache = {title: '123123'}
[key: string]: string 可以预留任何的 key 和 属性
13,类 class
13.1)定义类
在 es6之前js使用的是函数和原型去实现的类,然后在 es6 中有了专门的 class
class Person {constructor(name, age) {this.name = name // 以前的 js 添加属性直接在 constructor 里面定义,但是在ts 中这样定义会报错//提示错误:Person没有name属性, Property 'name' does not exist on type 'Person'}}
在 ts 中需要明确的声明类的属性,不能直接添加,需要在类中声明属性,就直接在类中定义,是es7新增的方法
所以在ts 中需要优先定义属性,然后给属性设置类型,还可以设置一个初始值
注意:在ts中类属性必须要有一个默认初始值或者在构造函数中去初始化,否则在类中定义的属性会提示错误
13.2)类的访问修饰符
- 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) } }
2. public 公共属性:定义的属性和方法默认是公共属性,外部可以访问和修改,所以一般省略2. protected 受保护属性:只允许在子类当中去访问对应的成员```typescript// 例一:class Person {name: stringprivate age: numberprotected gender: booleanconstructor(name: string, age: number) {this.name = namethis.age = agethis.gender = true}say(msg: string) {console.log(` I am ${this.name}, and i'm ${this.age}and ${this.gender ? 'boy' : 'girl'}, ${msg}`)}}const tom = new Person('tom', 12)console.log(tom.gender) // protected 属性在外部也是无法访问// 例二: 使用子类拿到 protected 属性class Person {name: stringprivate age: numberprotected gender: booleanconstructor(name: string, age: number) {this.name = namethis.age = agethis.gender = true}say(msg: string) {console.log(` I am ${this.name}, and i'm ${this.age}and ${this.gender ? 'boy' : 'girl'}, ${msg}`)}}class Student extends Person {constructor(name: string, age: number) {super(name, age)console.log(this.gender) // 子类可以拿到父类中的 protected属性}}const tom = new Person('tom', 12)console.log(tom.gender) // protected 属性在外部也是无法访问
13.3)构造函数的访问类型
如果给 constructor 添加 private,这个类就不能在外部去实例化也不能被继承
只能在类的内部添加一个静态的方法去创建这个类型的实例,因为private只能在类的内部去访问
class Animal {name: stringage: numberprivate constructor(name: string, age: number){this.name = namethis.age = age}sayHi(msg: string) {console.log(`this is ${this.name}, and it's ${this.age}, ${msg}`)}}class Dog extends Animal { // 提示出错,私有的constructor 不能继承}const dog = new Animal('popy', 12) // 提示出错,私有的constructor 不能实例化
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 的构造函数
如果是给构造函数 protected 这个类也是不能在外部被实例化的,但是可以被子类继承(与 static 的区别)<br />13.4)类的只读属性<br />关键字: readonly```typescriptclass Person {name: stringprivate age: numberprotected readonly gender: booleanconstructor(name: string, age: number) {this.name = namethis.age = agethis.gender = true}say(msg: string) {console.log(` I am ${this.name}, and i'm ${this.age}and ${this.gender ? 'boy' : 'girl'}, ${msg}`)}}const tom = new Person('tom', 12)console.log(tom.gender) // protected 属性在外部也是无法访问tom.gender = false // 不能修改,因为是 只读属性// Cannot assign to 'gender' because it is a read-only property.
13,类和接口的区别
类和类之前也有共同的特征,这些特征可以使用 interface 去抽象,比如一个类实现了一个协议,另外一个类也实现了相同的协议,这个相同的协议就可以抽象为 interface
interface RunAndEat {eat(food: string): voidrun(dis: number): void}class Person implements RunAndEat {eat(food: string): void {console.log('human eat' + food)}run(dis: number): void {console.log('直立行走' + dis)}}class Animal implements RunAndEat {eat(food: string): void {console.log('趴着吃' + food)}run(dis: number): void {console.log('爬行' + dis)}}
但是在其他语言建议接口的定义最好功能简单,这个 RunAndEat 就实现了两个方法的功能,如果有一个电动车的类,有 run 的方法,但是没有eat 的方法就需要重新写一个接口,所以建议接口就简单 —— (也叫函数式接口?)
interface Run {eat(food: string): void}interface Eat {run(dis: number): void}class Person implements Run, Eat {eat(food: string): void {console.log('human eat' + food)}run(dis: number): void {console.log('直立行走' + dis)}}class Animal implements Run, Eat {eat(food: string): void {console.log('趴着吃' + food)}run(dis: number): void {console.log('爬行' + dis)}}
14,抽象类
14.1)抽象类理解:
抽象类和接口类似,也是约束子类当中一定要有某一个成员,但是不同的是抽象类可以包含一些具体的实现,但是接口就只是成员的抽象,不包含实现,一般对于大的类,建议是使用抽象类,比如刚刚的动物类,就应该是一个抽象类,因为动物只是一个泛指,并不具体它的下面一定有一些更细化的分类,比如 狗,猫,
14.2)使用的关键字: abstract
如果是一个抽象类就只能继承,不能使用 new 的方法去创建实例化,必须使用子类去继承这个类型
// 例1:abstract class Animal {eat(food: string) {console.log('eat ' + food)}}const ani = new Animal() // 提示错误不能创建抽象类的实例:Cannot create an instance of an abstract class.
14.3)抽象类中可以定义抽象方法
抽象方法不需要具体的方法,如果父类有一个抽象方法就需要子类去实现这个抽象的方法
实例化的对象就有抽象类的方法和子类的方法
abstract class Animal {eat(food: string) {console.log('eat ' + food)}abstract run(dis: number): void // 如果是抽象的方法,就不能实现,只能在子类中取实现}class Dog extends Animal {run(dis: number): void { // 子类继承一个抽象的类中有抽象的方法,子类就一定需要去实现这个抽象的方法console.log('run' + dis)}}const ani = new Dog()ani.eat('meat') // 实例化的对象就有抽象类的方法和子类的方法ani.run(12)
15,泛型
泛型是在定义函数,接口,类的时候没有指定具体的类型,而是在使用的时候才传入具体类型的特征,比如泛型定义数组,在定义后,使用的时候传入类型,这样的目的是极大的程度复用代码
// 创建数字类型的数组,函数返回数字类型的数组 number[]function createNumberArray(len: number, val: number): number[] {// es6 的生成数组的方法const arr = Array(len).fill(val) // Array 创建的是 any类型的数组,// Array的提示:(arrayLength?: number | undefined) => any[]// any[] any类型的数组return arr}
Array 创建的是 any类型的数组,Array的提示:(arrayLength?: number | undefined) => any[] (any[] any类型的数组),如果要指定数组的类型,需要通过泛型参数的方法去传递一个泛型
function createNumberArray(len: number, val: number): number[] {const arr = Array<number>(len).fill(val) // es6 的生成数组的方法return arr}
这个时候Array就会提示是一个 数字类型数组 (arrayLength: number) => number[] (数字类型数组)
Array 就是一个泛型类,因为在定义Array的时候不知道用Array 使用的时候传入什么类型
如果创建字符类的数组
// 创建字符类型的数组function createStringArray(len: number, val: string): string[] {const arr = Array<string>(len).fill(val)return arr}
所以这样代码就比较冗余,明明就是修改了参数的类型,然后修改函数返回的类型,所以可以把类型作为一个参数传入,就是泛型参数,就是函数名称,然后把参数中不明确的类型都使用代替
//把类型变成一个参数// <T> 尖括号里面放的是泛型参数 T,单独的一个 T 就是类型的参数, T[] 泛型型的数组 类似 any[]和 number[]function createArray<T>(len: number, val: T): T[] {const arr = Array<T>(len).fill(val)return arr}const arr2 = createArray<string>(12, 'tt')// <string> 放的类型的参数值,12是传入的len的number,'tt'表示传入的字符串,因为val参数是泛型型的,没有限制,要靠传入才知道类型
问题:any 是泛型么?不是,any 是定义时,泛型是使用时
16,类型声明
16.1)第三方模块的 声明文件
import {camelCase} from 'loadsh';declare function camelCase(input: string): string // 这样使用camelCase就有类型了// 安装以后提示找不到类型声明文件:Could not find a declaration file for module 'loadsh'.const res = camelCase('heelo type')// 使用的是后也不能找到camelCase的类型提示,就是因为之前说的没有类型文件 —— 可以自己写类型声明
这个 declare 就是为了兼容以前的js模块没有类型声明的,在ts中可以重新添加这个函数方法的类型声明
ts - 初始
1,类型注释
在给函数传递参数的时候,需要指定参数的类型,如果类型冲突,则报错
例如,参数指定string类型
function greeter(person: string) {return "Hello, " + person;}let user = [0, 1, 2];document.body.textContent = greeter(user);
由于类型传递错误,所以报错:
error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.
2,接口及接口继承
// 定义一个接口,强化了类型interface IPerson {firstName:string,lastName:string,sayHi: ()=>string}// 实例化一个接口var customer:IPerson = {firstName:"Tom",lastName:"Hanks",sayHi: ():string =>{return "Hi there"}}// 实例化一个接口var employee:IPerson = {firstName:"Jim",lastName:"Blakes",sayHi: ():string =>{return "Hello!!!"}}console.log("Employee 对象 ")console.log(employee.firstName)console.log(employee.lastName)
3,强类型函数
function add(x: string, y: string, z?:string): string{return x + y;}
在TypeScript的函数中我们可以给每个参数提供类型注解,还能为函数提供返回类型注解(在参数列表后的”:类型”),同时TypeScript可以在参数名右边加上一个?表示可选参数。
4,泛型
ts是静态类型,所以使用泛型可以给静态类型提供一个动态机会
动静态语言的区别:
动静态,是指一个变量能不能存别的类型的数据
强弱类型:
类型不同不能计算,这叫强类型
function itself<T>(a: T):T {return a}console.log(itself(true)) // trueconsole.log(itself(1)) // 1console.log(itself('1')) // '1'console.log(itself([1, 2, 3])) // [1, 2, 3]
