欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

升级技巧:深入理解TS的高级类型 - 泛型实例解析

最编程 2024-07-27 21:05:39
...

# 类型别名 type
  • 类型别名就是给已有的类型取一个新名字,并不会新建类型
  • 类型别名:可以用于原始值联合类型交叉类型元组, 其他任何需要手写的类型
  • 错误信息、鼠标悬停时,不会使用别名,而是直接显示为所引用的类型
  • 别名不能被extends和implements
  • 给原始类型取别名通常没什么用
  • 如果你无法通过接口来描述一个类型并且需要使用联合类型或元组类型,交叉类型,这时通常会使用类型别名。
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;  // type可以用于联合类型
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

# 泛型-类型别名
type Tree<T> = {
    value: T;          // value是T类型
    left: Tree<T>;     // 在类型别名的属性中引用自己
    right: Tree<T>;
}


类型别名也可以是泛型,类型参数在别名右侧传入

# 类型别名和交叉类型一起使用
  • 与交叉类型一起使用,我们可以创建出一些十分稀奇古怪的类型。
type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
    name: string;
}

var people: LinkedList<Person>;  相当于 { name: string } & { next: { name: string } & {next...}}
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;



    ----
    type TP<P> = P & { next?: TP<P> }
    interface IP {
      name: string;
    }
    const a = (age: TP<IP>) => {
      return age;
    }
    window.console.log(a({ name: 'wang', next: { name: ''}}))

# 交叉类型 ( & )

交叉类型 intersection types是将多个类型合并成一个类型

# 联合类型 ( | )

联合类型表示一个值可以是几种类型之一

  • 如果一个值是联合类型,我们只能访问此联合类型的所有类型里共有的成员
interface Bird {
    fly()
    layEggs()
}
interface Fish {
    swim()
    layEggs()
}
let pet = getPet() // getPet()的返回值类型是`Bird | Fish` 
pet.layEggs() // 允许
pet.swim() // 报错



函数的返回值类型是 Bird | Fish
我们唯一可以确定的是,不管是Bird还是Fish,都有layEggs()方法
所以访问pet.layEggs()不会报错,不是共有的成员则可能类型不符合,导致会报错

# 类型保护与区分类型

联合类型可以让一个值可以为不同的类型,但随之带来的问题就是访问非共同方法时会报错。那么该如何区分值的具体类型,以及如何访问共有成员?
(1) 使用类型断言
  • 类型断言有两种语法 <类型>值值 as 类型
let someValue: any = "this is a string";  ---------------------- 是一个any类型
let strLength: number = (<string>someValue).length; ------------ 断言成string类型


let someValue: any = "this is a string";
let strLength: number = (someValue as string).length; ---------- jsx中使用as语法
let pet = getSmallPet();
// 每一个成员访问都会报错
if (pet.swim) {   -------------------- 报错,因为pet可能没有swim属性
    pet.swim();
}
else if (pet.fly) { ------------------- 同样报错
    pet.fly();
}



let pet = getSmallPet();
if ((<Fish>pet).swim) { ---------------- 断言成Fish类型,就肯定有 swim 属性
    (<Fish>pet).swim();
}
else {
    (<Bird>pet).fly();
}
(2) 使用类型保护

使用类型断言,需要多次判断十分麻烦。所以使用类型保护

  • 什么是类型保护: 这种param is SomeType的形式,就是类型保护,它用来明确一个联合类型变量的具体类型
  • 类型谓词 谓词为 parameterName is Type这种形式,parameterName必须是来自于当前函数签名里的一个参数名。
function isFish(pet: Fish | Bird): pet is Fish {
    return (<Fish>pet).swim !== undefined;
}


// 'swim' 和 'fly' 调用都没有问题了
if (isFish(pet)) {
    pet.swim();
}
else {
    pet.fly();
}

# typeof类型保护

typeof 只能用于 number, string, boolean, symbol(只有这几种类型会被认为是类型保护)

# instanceof类型保护 ---- 用于类

# 索引类型

# 索引类型查询操作符 ( keyof )
# 索引访问操作符 ( T[K] )

对于任何类型 T, keyof T的结果为 T上已知的公共属性名的联合
1)首先,使用keyof关键字,它是索引类型查询操作符,它能够获得任何类型T上已知的公共属性名的联合。如例子中,keyof T相当于'name' | 'age'
2)然后,K extends keyof T表明K的取值限制于'name' | 'age'
3)而T[K]则代表对象里相应key的元素的类型

  public componentDidMount() {
    interface P {
      name: string;
      age: number;
    }
    const people: P = {
      age: 20,
      name: 'wang',
    };
    const fn: <P, T extends keyof P>(p: P, t: T[]) => Array<P[T]> = (p, t) => {
      return t.map(item => p[item])
    };
    const res = fn(people, ['age']);
    window.console.log(res)
  }



(1) fn是一个泛型函数
(2) 传入两个类型参数,fn函数的第一个参数是P类型,第二个参数是T类型的数组
(3) 索引类型查询  ---  keyof P 是P上已知的公共属性名的联合类型,即 age | name 
(4) 索引访问      ---  函数的返回值Array<P[T]>是类型为 P接口对应的T属性的类型 组成的数组
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key]
}
let obj = {
    name: 'RuphiLau',
    age: 21,
    male: true
}
let x1 = getProperty(obj, 'name') // 允许,x1的类型为string
let x2 = getProperty(obj, 'age') // 允许,x2的类型为number
let x3 = getProperty(obj, 'male') // 允许,x3的类型为boolean
let x4 = getProperty(obj, 'hobby') // 报错:Argument of type '"hobby"' is not assignable to parameter of type '"name" | "age" | "male"'.


# 索引类型和字符串索引签名

keyof和 T[K]与字符串索引签名进行交互。 如果你有一个带有字符串索引签名的类型,那么 keyof T会是 string。 并且 T[string]为索引签名的类型:

interface Map<T> {
    [key: string]: T;
}
let keys: keyof Map<number>;   // string
let value: Map<number>['foo']; // number

# 映射类型 - 从旧类型中创建新类型

它的语法与索引签名的语法类型,内部使用了 for .. in。 具有三个部分:

  1. 类型变量 K,它会依次绑定到每个属性。
  2. 字符串字面量联合的 Keys,它包含了要迭代的属性名的集合。
  3. 属性的结果类型。
interface Person {
    name: string
    age: number
}
type Readonly<T> = {
    readonly [P in keyof T]: T[P];   
}
type Partial<T> = {
    [P in keyof T]?: T[P];
}
type PersonPartial = Partial<Person>;
type ReadonlyPerson = Readonly<Person>;




-------------
解析:
type Readonly<T> = {
    readonly [P in keyof T]: T[P];   // 类似 for...in循环属性名,然后用索引访问操作符得到属性的类型
}
相当于
type Readonly<T> = {
  readonly name: string
  readonly age: number 
}
  public componentDidMount() {
    interface P {
      name: string;
      age: number;
    }
    type NewP<P> = {
      [K in keyof P]?: P[K]  // 映射类型,这里把P类型的属性,变为了可选属性
    };
    const b: NewP<P> = {
      age: 20,
    }
    window.console.log(b);
  }

我们还可以写出更多的通用映射类型,如:

// 可为空类型
type Nullable<T> {
    [P in keyof T]: T[P] | null
}

// 包装一个类型的属性
type Proxy<T> = {
    get(): T
    set(value: T): void
}
type Proxify<T> = {
    [P in keyof T]: Proxy<T[P]>
}
function proxify(o: T): Proxify<T> {
    // ...
}
let proxyProps = proxify(props)

https://www.ruphi.cn/archives/266/

# 泛型函数
function identity<T>(arg: T): T {
    return arg;
}



调用时,可以传入类型参数,也可以使用类型推论
let output = identity<string>("myString");  // type of output will be 'string'
let output = identity("myString");  // type of output will be 'string'



可以把泛型变量作为类型的一部分
function loggingIdentity<T>(arg: T[]): T[] {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

# 泛型函数类型
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: <T>(arg: T) => T = identity;
<T>(arg: T) => T  -------------------------------- 泛型函数的函数类型




我们也可以使用不同的泛型参数名,只要在数量上和使用方式上能对应上就可以。
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: <U>(arg: U) => U = identity;




我们还可以使用带有调用签名的对象字面量来定义泛型函数:(!!!)
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: {<T>(arg: T): T} = identity;

# 泛型接口
interface GenericIdentityFn {
    <T>(arg: T): T;
}
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn = identity;




把泛型参数当作整个接口的一个参数。 这样我们就能清楚的知道使用的具体是哪个泛型类型
这样接口里的其它成员也能知道这个参数的类型了。
interface GenericIdentityFn<T> {
    (arg: T): T;
}
function identity<T>(arg: T): T {
    return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
















# Event对象中的 this , target 和 currentTarget

结论:
(1) this始终等于currentTarget即事件的监听函数所绑定的节点对象
(2) target指的是最初触发事件的监听函数的节点对象

  • target:指向 ( 最初触发事件的节点对象 )。
  • currentTarget: 指向 正在执行的 ( 监听函数所绑定的节点对象 )。
  • this:指向 (绑定监听函数的节点对象)

实例:

<div onClick={this.go}> // ------ 事件的监听函数绑定的节点对象是 father 所在节点
   father
   <div>child</div>
</div>


当我们点击child的时候:
e.target指的就是最初点击触发事件监听函数的节点,即child 所在的节点
e.currentTarget = this 指的是事件监听函数所绑定的节点,即 father 所在的节点

# Event对象实例方法中的 preventDefault
  • e.preventDefault() 取消浏览器对当前事件的默认行为
  • 生效的前提是 cancelable 属性为truecancelable是只读属性,表示事件是否可以取消
    实例:
<input name="Fruit" type="radio" value="" onClick={this.goRadio}/> 

  public goRadio = (e: React.MouseEvent) => {
    window.console.log(e.cancelable);  // 只读属性,查看事件否可以取消
    if ( e.cancelable ) { // 如果事件可以取消
      e.preventDefault(); // 阻止事件的默认行为,注意生效的前提一定是 cancelable为 true
    }
  }


单选框的默认行为是点击选中,但是如果在cancelable为true的情况下,
使用 e.preventDefault()则会阻止默认行为, 使得单选框不能被选中

# Event对象中得 stopPropagation 和 stopImmediatePropagation

  • e.stopPropagation() 阻止事件在DOM中继续传播,防止再触发定义在别得节点上得监听函数,但是不包括当前节点上的其他监听函数
  • e.stopImmediatePropagation()阻止同一事件其他监听函数被调用,不管监听函数是在当前节点还是其他节点

# 属性操作的标准方法
  • getAttribute()
  • setAttribute()
  • hasAttribute()
  • removeAttribute()
    getAttribute()返回当前元素节点指定的属性,如果属性不存在,返回false
    setAttribute()为当前元素节点新增属性,如果同名属性已存在,则相当于编辑已存在的属性

# dataset属性
<div id="mydiv" data-foo="bar">

var n = document.getElementById('mydiv');
n.dataset.foo // bar
n.dataset.foo = 'baz'


也可以通过 setAttribute('data-foo')操作该属性
注意,data-后面的属性名有限制,
只能包含字母、数字、连词线(-)、点(.)、冒号(:)和下划线(_)。
而且,属性名不应该使用A到Z的大写字母,比如不能有data-helloWorld这样的属性名,而要写成data-hello-world。

# prototype

js继承机制的思想:原型对象的所有属性和方法,都能被实例对象共享。

  • 每个函数都有一个prototype属性,指向一个对象
  • 对于构造函数来说,生成实例对象的时候,prototype属性会成为 实例对象的 原型对象,原型对象的属性不是实例对象自身的属性,只要修改原型对象,变动就会立马体现在所有实例对象上
  • 如果实例对象自身就有某个属性和方法,就不会到原型对象上查找
  • 原型对象的作用,就是定义所有实例对象所共享的属性和方法
  • 所有对象都有自己的原型对象,任何对象都继承了Object.prototype,Object.prototype的原型是null,原型链到此终止

# constructor

prototype对象默认有一个constructor属性,默认指向prototype对象所在的构造函数

  • constructor属性定义在prototype对象上,所以constructor属性被所有实列对象所继承
  • constructor属性的作用是:可以得知某个实例对象,到底是由哪个构造函数产生的
  • 有了constructor就可以从一个实例新建另一个实例
  • 修改了原型对象,一定要同时修改constructor属性的指向

# instanceof

instanceof返回一个boolean值,表示对象是否为某个构造函数实例

  • instanceof 运算符 ( 左边是实例对象 ) ,( 右边是构造函数 )
  • instanceof检查整个原型链,所以同一个实例对象可能对多个构造函数返回true
  • instanceof的原理,是检查右边的构造函数的prototype属性,是否在左边实例对象的原型链上,特殊情况:(就是左边实例对象的原型链上只有null对象,这时,instanceof判断就是失真)
  • instanceof运算符的一个作用,就是判断值得类型
instanceof 运算符,只能用于对象,不能用于原始类型得值
typeof 则不能判断出具体得的对象类型


对于null和undefined,instanceof总是返回false

# getPrototypeof()

Object.getPrototypeof() 返回参数对象的原型,这是获得原型对象的标准方法

# setPrototypeof()

Object.setPrototypeof(a, b)为参数对象设置原型,返回参数对象

  • 有两个参数: 第一个是现有对象,第二个是原型对象
var a = {};
var b = {x: 1};
Object.setPrototypeOf(a, b); // 把a对象的原型设置成b

Object.getPrototypeOf(a) === b // true  Object.getPrototypeof()得到参数对象的原型
a.x // 1
var F = function () {
  this.foo = 'bar';
};
var f = new F();
// 等同于
var f = Object.setPrototypeOf({}, F.prototype); //Object.setPrototypeof(a,b)返回a,并把b设置成a的原型
F.call(f);




new命令实质上是以上过程
1)把空对象的原型设置成构造函数的prototype属性
2)将构造函数内部的 this 绑定到 空对象上,并执行构造函数

# create

Object.create( ) 以参数对象为原型,返回实例对象

# isPrototypeOf

实例对象的 isPrototypeof 方法,用来判断该对象是否是参数对象的原型

var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);

o2.isPrototypeOf(o3) // true     ---- o2是否是o3的原型
o1.isPrototypeOf(o3) // true     ---- o1是否是o3的原型




var o2 = Object.create(o1)
实际上相当于
var o2 = object.create = function(obj) {
  function X() {};  创建一个空的构造函数
  X.prototype = obj; 将构造函数的prototype指向 参数对象
  return new X(); 返回实例对象赋值给o2
}

推荐阅读