/**
* 1. boolean,number,string
* 2. undefined,null默认情况是所有类型的子集,所以可以给任意类型赋值null,undefined而不报错。(如果指定了--strictNullChecks标记,null和undefined就只能
* 赋值给void和他们各自,不然会报错)
* 3. any:不清楚什么类型,可以使用any类型,这些值可能来自于动态的内容,比如来自用户输入或第三方代码库。(不建议使用any,不然ts就失去了意义)
* 4. unknow:这个类型代表任何类型,是一个安全类型,他让ts可以把潜在的风险暴露出来,声明为unknow的类型做任何事情都是不合法的,只有配合类型断言
* 才能成功。例子:function devide(param: unknow) { return param / 2 }//会报错,因为param可能不是数字类型,这个时候需要类型断言配合
* 比如: function devide(param: unknow) { return param as number / 2 }这样就成功了
* 5. void: 表示没有任何类型。比如函数没有任何明确返回值,默认返回void类型 function welcome(): void { console.log('hello') }//void可以赋值undefined
* 6. never:这个类型表示那些用不存在的值的类型,有些情况值永远不存在,比如抛出异常的函数,或者无限循环的函数,例子: function fn(msg: string): never { throw new Error(msg) }
* 7. 数组类型: let list: number[] = [1,2,3]//数字类型数组,只能存放数字类型,其余类型会报错
* 8. 元组类型: let list: [number,string] = [1,'2']//元祖类型的数组,已经定义好各个索引的值的类型,比如符合类型定义,不然会报错
* 值超出定义个数也会报错,但是push的时候超出定义个数不会报错。如果push没有定义的数据类型会报错
* 9. 函数类型: ts定义函数类型需要定义输入参数类型和输出类型。输出类型也可以忽略,因为ts可以根据返回语句自动推断出返回值类型。
* 例子: function add(x:number,y:number):number { return x + y }
* 如果没有返回值,可以默认返回void, 例子: function welcom():void { console.log('hello') }
* 9-1: 可选参数:参数后面加个问号,证明这个参数可以不传。(可选参数必须放在函数参数的末尾)function d(a:number,b?:number):void {} d(1,2)或d(1)
* 9-2: 默认参数:和js一样的写法,可以不放在参数最后的位置,但是如果不在最后一个参数的位置,传值的时候没值的情况必须要传undefined,否则会以为少传一个位置的参数
* 例子: function fn(a:number = 100, b:number):void {} fn()
* 9-3: 函数赋值:在ts中给变量赋值的时候,因为有类型的限制,所以不能赋值原类型外的值,所以函数类型也一样,只能赋值相同类型的函数
* 10. interface: 接口(是ts设计出来用于定义对象类型的,可以对对象的形状进行描述。定义interface的时候一般首字母大写)
* 例子: interface Person { //必填的属性少写多写都会报错
* name: string
* age: number
* }
* const p1:Person = {
* name: 'hhh',
* age: 11
* }
* 10-1. 可选属性,通用,属性名后+?
* 10-2. 只读属性,如果希望某个属性不被改变,可以在属性前面写readonly 属性名,这个只读属性只能赋值一次,之后无法改变,改变会报错 例子: interface Person { readonly id: number }
* 10-3. interface可以用来描述函数类型 例子:
* interface IFunc {
* (x:number,y:number): number
* }
* const add:IFunc = (num1,num2) => {
* return num1 + num2
* }
* 10-4. 自定义属性(属性必须和类型定义的时候完全一致,如果一个对象上有多个不确定的属性,可以用这个自定义属性,定义属性的key类型和值类型)
* 例子: interface Read {
* [ propName: string ]:string
* }
* const obj: Read = {
* a: 'hello',
* b: 'ooo'
* }
* (如果属性key被定义为数值类型,那么对象就会变成一个类数组,因为key变成了数值,就和下标差不多了,这应该也是对象的键最好不是数值类型的原因)
* (所以interface可以描述很多类型,比如描述一个函数,并且另外添加一些属性)
* 11. 类(ts中通过public,private,prtected三个修饰符来增强js的类。好像这些修饰符并不能真的限制访问,只是在ts层面会报错,但真的编译为js的话,也是可以显示的。。)
* 11-1:public(公开的属性和方法,实例和子类都可以访问)
* 11-2:private(私有的属性和方法,只有当前类自己可以访问,子类和实例都不能访问)
* 11-4: static (静态属性或方法,子类实例都访问不到,只有用当前类才能调用)
* 11-3:protected(私有的属性和方法,子类可以访问,实例不能访问)
* 例子: class Person {
* public name: string
* static age: number
* public constructor(name: string) {
* this.name = name
* this.age = age
* }
* protected speak() {
* console.log(this.name)
* }
* private talk() {
* console.log(this.name)
* }
* }
* 11-5:abstract(抽象类,1:抽象类不允许实例化,会报错。 2:抽象类中的抽象方法必须被子类实现,子类不定义这个父类的方法,也会报错)
* 抽象类的用法是定义一个基类,声明共有属性和方法,拿去被继承。(优点是可以抽离出事物的共性,有利于代码的复用)
* 例子:
* abstract class Animal {
* constructor(name:string) {
* this.name = name
* }
* public name: string
* public abstract sayHi():void
* }
* class Dog extends Animal {
* constructor(name: string) {
* super(name)
* }
* public sayHi() {
* console.log('wang')
* }
* }
* class Cat extends Animal {
* constructor(name: string) {
* super(name)
* }
* public sayHi() {
* console.log('miao')
* }
* }
* 11-6: interface也可以约束class,需要implements(实现的意思)关键字
* 例子:
* interface Mu { //interface定义class的形状
* playMu(): void
* }
* class CallPhone implements Mu { //使用implements来实现被定义的形状,interface上定义的属性方法都要在这里实现,不然会报错
* playMu() {}
* }
* (
* 这里讲的不太清楚
* 1.interface定义的类,在有new的时候new好像是构造函数,这个时候想用interface就需要使用implements关键字,并且声明的属性是静态属性。
* 2.没有new的时候,属性好像就成了普通属性,并且调用的时候可以直接用const x: interfaceKey = class className{来调用}
* )
* 12. 枚举(enum,可以依次递增并且反向映射的类型,递增从初始值开始,初始值没有的话就是从0开始)
* 例子:
* enum Direction {
* up = 6,
* down,
* left,
* right
* }//从初始值6开始递增,Direction.up = 6,Direction.down = 7,Direction.left = 8,Direction.right = 9
* //反向映射 Direction[6] = 'up',Direction[7] = 'down'依此类推
* 12-1:枚举的值也可以手动赋值(
* enum p {
* buy = 100,
* send = 10,
* end = 3
* }
* )
* 12-2: 计算成员(没看出作用)
* 12-3: 字符串枚举(就是把递增的数值手动赋值成字符串了,说是可以更有语义化,方便调试)
* 12-4: 常量枚举,const enum Test { A: 'ppp' } (常量枚举在编译的时候更快,编译形成的代码也更简洁。使用常量枚举的时候不可以使用计算成员,因为常量是固定的)(暂时看不出什么用)
* (枚举的意义在于,可以定义一些带名字的常量集合,清晰地表达意图和语义,更容易的理解代码和调试。
* 常用于和后端联调时,区分后端返回的一些代表状态语义的数字或字符串,降低阅读代码时的心智负担)
* 13.类型推断(如果写ts时,定义变量却没有给类型,那么ts会根据上下文自动推断数据类型,并且根据该类型对代码加以约束)
* (如果声明了数据,却没赋值也没给类型,那么ts会判断为any。如果声明数据没给类型,但是却赋值了,ts会根据这个值的类型为定义类型去约束代码。)
* (既然用了ts,就尽量定义好类型,不要依靠类型推断)
* 14. ts 8种内置类型:string,number,boolean,undefined,null,object,bigint,symbol
* 14-1. DOM和BOM,比如 HTMLElement,NodeList,MouseEvent
*
* (
* 枚举(递增,手动赋值,常量枚举),
* 类(public,private,protected,interface定义class,abstract抽象类),
* interface(定义对象或函数),
* 一些类型,never,void,undefined,null,string,number,object,boolean,unknow,any
* )
*/
* 高级
* 1.联合类型:let num: number | string = '' //代表num的类型可以是number或者string
* (在联合类型的时候,调用该变量的方法时,这个方法需要是几种联合类型的类型上都包含的方法,不然就需要使用if判断,讲类型分开,各自调用方法)inter
* 2.交叉类型:如果要对对象进行扩展,可以用交叉类型&
* (比如Person有name和age属性,而Student在name和age的基础上还有grade属性,就可以这么写
* interface Person {
* name: string
* age: number
* }
* type Student = Person & { grade: number }
* //Student也是一个interface
* )
* 3.类型别名:(给一种或多种组合起来的类型重新命名,
* type Name = string;
* type S = number;
* type All = Name | Number;
* const x: All = 'ppp' //这个All就是类型别名定义的类型
* )
* 3-1. 类型别名和interface的区别:
* 1.interface是用于定义对象类型的,可以对对象的形状进行描述
* 2.type是类型别名,用于给各种类型定义别名,,让ts更简洁,清晰
* 3.type可以声明基本类型,联合类型,交叉类型,元祖,interface不行
* 4.interface可合并重复声明,type不行
* 5.interface可以通过重复声明来合并属性,type不行,type重复定义会报错
* 6.组合类型或交叉类型的用type,extends或implements用interface
* 4.类型断言(as,不是很理解)(
有两种方式
1.<类型>值 <string>v.length //意思就是确定v是string类型,可以执行string类型相关的任何操作
2.值 as 类型 return (v as string).length 意义同上
(在jsx里只认可第二种,所以推荐使用第二种)
)
* 5.字面量类型(用一些固定的值当做类型,例子: type BtnStatus = 'mini' | 'small' | 'normal' | 'large')
* (promise类型,Promise<T> 定义了一个T形状的Promise类型)
* 6.泛型 (中心思想应该是通过形参来控制类型,让类型更灵活,应该各种类型都可以使用形参,只要是声明类型的地方都可以用形参来代替,所以任何声明类型的地方都可以使用泛型,目前是这么认为的)
* 例子:
* class testClass<T> {
private data: T[] = []
constructor() {
this.data
}
pushFunc(data: T): void {
this.data.push( data )
}
pop(): T | undefined {
return this.data.pop()
}
}
const obj = new testClass<string>()
obj.pushFunc('sss')
console.log(obj)
const obj2 = new testClass<number>()
obj2.pushFunc(222)
console.log(obj2)
* Array<number>
* 6-1. type来写泛型没有讲清楚
* 6-2. interface(
* 例子:
* interface Print<T = string> {
* (arg: T): T
* }
* const myFunc: Print<number> = function<T>(arg: T): T {
* return arg
* }
* const myFunc2: Print = function<T>(arg: T): T {
* return arg
* }
* (这是interface的泛型声明,可以在声明的时候给形参一个默认值,也可以不给)
* 6-3. 处理多个函数参数(可以通过解构赋值的方法)
* 例子(两种):
* 1. interface SwapSet<T,U> { //定义一个函数泛型来方便调用去约束函数
* (arr: [T,U]): [U,T]
* }
* const swap: SwapSet<number,string> = function<T,U>(arr: [T,U]): [U,T] { //使用函数泛型
* return [arr[1],arr[0]]
* }
*
* 2. function swapFunc<T,U>(arr: [T,U]): [U,T] { //定义一个泛型约束的函数
* return [arr[1],arr[0]]
* }
* 6-4. 约束泛型(说白了就是通过给泛型的形参添加一些特殊的属性,来限制形参的类型)
* 例子:
* interface lengthInterface {
* length: number //通过length属性讲形参限制为具有length属性的js类型,比如字符串,数组
* }
* function testFunc<T extends lengthInterface>(arg: T): void {
* console.log(T.length) //如果不通过length的interface给形参添加限制,这里访问length属性就会报错,因为ts不确定这里的具体数据类型,所以有些类型可能没有length属性,ts不想看到这种情况发生,就会报错
* }
* )
* 7. keyof(获取目标的所有key,生成一个用这些key组成的联合类型,来约束目标)
* 例子: interface IPerson {
* name:string
* age: number
* }
* type Test = keyof IPerson; //'name' | 'age'
* 8. T[K] (表示接口T的属性K所代表的类型,‘索引访问’)
* 例子: interface IPserson {
* name: string
* age: number
* }
* let type1: IPerson['name'] = 'ddd' //string
* 9. extends (泛型约束,T extends U,表示泛型变量T可以通过继承某个类型(这里是U)来获得某些属性(U里的所有属性))
* 例子: interface ILength {
* length: number
* }
* function getLength<T extends ILength>(arg: T): T {
* console.log(arg.length)//因为T继承了ILength的length属性,所以约束力包含了length属性,这里访问length属性也不会报错
* return arg
* }
* 10. in(对联合类型进行遍历)
* 例子:
* type Person = "name" | "school" | "major"
* type obj = {
* [p in Person]: string //这个[]的作用应该类似于...外面包的[]
* }//type obj = { name: string; school: string; major: string; }
* 11. Partial(Partial<T>将T的所有属性映射为可选的,就是在每个key的后面加上?)
* 实现原理(通过keyof拿到目标所有key的联合类型,in操作符对联合类型进行遍历,便利的时候在末尾加上?)
* interface crtItem {
* name: string
* age: number
* }
* //type after = Partial<crtItem> //正常使用
* type obj<C> = { //原理
* [K in keyof C]?: C[K]
* }
* 12. Readonly (Readonly<T>将T的所有属性设置为只读类型)
* 原理和Partial一样,通过keyof in进行遍历,遍历生成的时候在key前面加入readonly
* interface obj {
* name: string
* age: number
* }
* //Readonly<obj>
* type crtAfter<C> = {//原理
* readonly [ K in keyof C ]: C[K]
* }
* 13. Pick (用于抽取对象子集,挑选一组属性并组成一个新的类型)
* 例子:
* interface IPerson {
* name: string
* age: number
* sex: string
* }
* //type IPersonAfter = Pick<IPerson, 'name' | 'sex'> //正常调用
* type IPersonAfter2<CO,CL extends keyof CO> = {
* [ K in CL ]: CO[K]
* }
* (搞不清楚exnteds继承一些属性后,嗷,应该确实是增加上面的属性,之前哪里之所以无法添加别的属性,是因为在extends之前,发起extends的对象是空的,所以
* 那里extends之后才只能添加extends的目标拥有的属性)
* 14. 条件类型:(extends用作条件判断来完成一系列操作)
* 14-1. extends(可以配合三目运算符进行一些条件的判断,比如exclude和include)
* 例子: T extends U ? X : Y //若类型T可以被赋值给类型U,那么结果类型就是X类型,否则就是Y类型
* T可以赋值给U的前提是U包含T(这个包含好像只能是联合类型的包含),传入一个确定的值的时候ts可以根据三目运算符给出准确的答案,而如果传了一个联合类型进去,ts也不知道怎么办,所以会
* 把三目运算符的两个值通过联合类型的方式返回
* 例子:
* type AB<T> = T extends 'x' ? 'a' : 'b'
* type All = AB<'x' : 'y'>//非确定条件,可能是x或y
* //这个时候ts也不知道怎么办,只能返回一个'a' | 'b'来作为值
* 14-2. exclude(意思是不包含,Exclude<T,U>会返回联合类型T中不包含联合类型U的部分)
* 例子: type Test = Exclude<'a'|'b'|'c','a'> //得到 'b' | 'c' (原理应该是借助never和extends来实现的,但是现在没有想清楚)
* 14-3. Extract (取交集,取两个目标中的交集)
* 例子: type Test = Extract<'a' | 'b', 'b' | 'c'> //获得'b' (原理应该是借助never和extends来实现的,但是现在没有想清楚)
* 15. 类型体操:通过类型之间的计算得到新的类型的过程,就是类型体操,不是官方术语,是论坛戏称。(写业务代码的话不太必要去死记硬背,了解逻辑即可,遇到了再去查去积累)
* 16. declare (当使用第三方库时,很多第三方库不是用ts写的,我们需要引用他的声明文件(.d.ts结尾的文件),才能获得对应的代码补全,接口提示等功能。)
* (declare关键字等于告诉ts编译器,这个变量的类型已经在其他地方定义了,你直接拿去用,别报错)
* 17. .d.ts文件 (一般来说单独存放声明语句的文件),把.d.ts文件放在项目目录中,其他所有.ts文件就都可以获得Vue的类型定义
* 18. 自己写ts声明文件
* 加入现在新项目用了ts,要在新项目中继续使用之前不是ts的一些js文件时,有两种选择:
* 1. 用ts重写,新项目引入重写的js文件
* 2. 直接引入老的js文件,给他写声明文件
* 如果选择添加声明文件,这个文件有两种存放方式
* 1. 存放在node_modules/@types/xxx/index.d.ts文件中,这样存放文件的话不需要额外配置,但是node_modules目录不稳定,代码也不会放在仓库,所以统一代码和版本比较不容易,所以不推荐
* 2. 在项目里创建一个目录来专门存放这类说明文件(比如types目录),这种方式需要配置下tsconfig.json中的paths和baseUrl字段。
19. 属性值后面加上! 表示告诉ts这个属性一定有值。
例子:obj.value!.header = 'fff' //告诉tsobj.value这个属性一定有值