typescript 高级

Posted lin-fighting

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了typescript 高级相关的知识,希望对你有一定的参考价值。

1 类型推断

从右向左流动:

// let a: number 类型从右向左流动
  let a = 1;

通过return关键字推断返回值的类型 底部流出

 function add(a: number) 
    return a;
  

从左向右:

 //从左向右
  type S = (a: number) => void;
  const s: S = (a) => 
    // parameter) a: number
    console.log(a);
  ;

mixin:

 // mixin
  function mixin<T, U>(one: T, two: U) 
    const result = <T & U> // result需要满足T又要满足U
    for (let key in one) 
      //   不能将类型“T”分配给类型“T & U”。单个无法赋值联合,需要先转换
      //result[key] = one[key]
      (result as T)[key] = one[key]; //这里赋值T类型需要先断言result为T类型才能赋值。
    
    for (let key in two) 
      // result[key] = two[key]
      (result as U)[key] = two[key];
    
    return result;

    return  ...one, ...two ; // T & U
  
const x = mixin( name: "1" ,  age: 2 );
console.log(x.age, x.name);

2 其他内容

索引访问操作符
interface A 
    a: string;
    b: number;
  

  // const a: string
  const a: A["a"] = "1";
映射类型
interface A 
    a: string;
    b: number;
  
 
// 映射类型
  type ACOPY = 
    [key in keyof A]: A[key]; // 遍历A,然后值为A[key],相当于复制一个A
  ;
  type ACOPY1 = 
    [key in keyof A]?: A[key]; // Partial的源码
  ;
  type ACOPY2 = Partial<A>;

条件类型

type B<T> = T extends object ? string : number
let b: B<true> // number
let c: B<>  // string
分发:
let d: B<|true> // string | number // 满足string | number即可。

找到T中不包含U的部分

  type Diff<T, U> = T extends U ? never : T;
// type R = never|never|'c'
  type R = Diff<"a" | "b" | "c", "a" | "b">; // a是a|b的子类, b是a|b的子类,他们都会匹配never,而c则不会,

4 内置条件类型

Exclude<T,U>

从T中排除U

 type Exclude<T,U>  = T extends U ? never : T // Diff
Extract<T,U>

从T中取跟U一样的

 type Extract<T,U> = T extends U ? T : never
infer

推断, infer 的作用是让 TypeScript 自己推断,并将推断的结果存储到一个类型变量中,infer 只能用于 extends 语句中。

如ReturnType

 type ReturnType<T extends (...args: any) => any> = T extends (
    ...args: any
  ) => infer R
    ? R
    : any;

如果T满足与函数,那么就推断函数的返回值赋值给R,然后返回R。

 function test(a: string, b: number) 
    return  a: 1 ;
  
 // type Test1 =    a: number; 
  type Test1 = ReturnType<typeof test>;

再如参数: Paramters

 type Paramters<T extends (...args: any) => any> = T extends (
    ...args: infer P
  ) => any
    ? P
    : any;

如果T满足与函数,就推断参数的类型赋值给P,然后返回P , 返回值是一个元组,否则返回any

type Test2 = Paramters<typeof test>;
 // type Test2 = [a: string, b: number]
元组转联合
 type ElementOf<T> = T extends Array<infer P> ? P : never; // T是否是数组的子类,是的话推断出类型返回
 type Ttuple = [string | number];
 // string | number
 type Test3 = ElementOf<Ttuple>;

5 内置的工具类型

Partial变为可选 (+?)
  type Partial<T> = 
    [key in keyof T]+?: T[key]; //+? 变成可选项
  ;

深度变为可选:

 interface Test1 
    a: 1;
    b: 2;
  
  type test1 = Partial<Test1>;

  // 深度变为可选
  interface Test2 
    a: 1;
    b: 2;
    c: Test1;
  
  type test22 = Partial<Test2>;
  const test2: test22 = 
    // 类型“”缺少类型“Test1”中的以下属性: a, b
    c: ,
  ;

如上,c一旦存在,他的对象里面的属性是必选的。所以

type DeepPartial<T> = 
    [key in keyof T]+?: T[key] extends object ? DeepPartial<T[key]> : T[key]; //递归调用
  ;
// 每次都需要判断是否是一个对象,是的话继续调用DeepPartial
 type test33 = DeepPartial<Test2>;
  const test3: test33 = 
    c: ,
  ;

Require 可选变必选
// 可选变必选,并且只读
  type Require<T> = 
    readonly [key in keyof T]-?: T[key]; // -? 变成必选项
  ;
  interface Test4 
    name?: 1;
    a: 2;
  
  type test4 = Require<Test4>;
PIck只要某个属性
  type Pick<T, K extends keyof T> = 
    // 第二个选项必须是 keyof T的子类
    [key in K]: T[key];
  ;
Omit过滤某个属性

Omit,过滤某个属性,反其道而行,取T中不包含K的就行

 type FilterK<T, K> = Exclude<keyof T, K>; //取T中不包含K的属性
 type Omit<T, K extends keyof T> = Pick<T, FilterK<T, K>>;  // 取T中不包含K的
从T中过滤TU两个对象相同的属性
type Diff<T extends object, U extends object> = Pick<T, Exclude<keyof T, keyof U>>

排除T中跟U一样的属性,获取剩余的属性,再使用Pick获取。

从T中找出TU相同的属性
type InterSection<T extends object, U extends object> = Pick<
    T,
    Extract<keyof T, keyof U> & Extract<keyof U, keyof T>
  >;

取T中有U的,交上U中有T的属性,然后通过pick在T里面获取。

Record
type Record<K extends keyof any, T> = 
    // keyof的值只有三个  K extends string | number | symbol
    [P in K]: T;
  ;

经常使用的代表对象的就是

 type A = Record<"a" | "b", any>;   //type A =  a: any;  b: any; 
 type B = Record<string, any> ;   // type B = [x: string]: any;

6自定义高级类型

Proxy 代理
 type Proxy<T> = 
    get(): T;
    set(v: T): void;
  ;

  type Proxify<T> = 
    [key in keyof T]: Proxy<T[key]>;  //通过T【key】获取值,然后继续取Proxy<T>
  ;

 // Proxy  代理
  function proxify<T>(obj: T): Proxify<T> 
    let result = <Proxify<T>>;
    for (const key in obj) 
      type KeyType = typeof key;
      result[key] = 
        get: () => obj[key],
        set: (v: T[KeyType]) => (obj[key] = v),
      ;
    
    return result;
  

  const props = 
    name: "a",
    a: 10,
  ;
  type Props = typeof props;
  const newPorps = proxify<Props>(props);
  newPorps.a.get(); // a: Proxy<number>
  newPorps.name.set("2"); // name: Proxy<string>

反代理:从Proxify变为T

// 反代理
  function unProxify<T>(t: Proxify<T>): T 
    let result = <T>;
    for (const key in t) 
      result[key] = t[key].get();
    
    return result;
  
//  unProps  name: string; a: number; 
  const unProps = unProxify<Props>(newPorps);
  unProps.a; // a: number
  unProps.name; //name: string
OverWrite重写

有个需求,两个类型A,B,B中跟A相同的需要覆盖A的内容,

  type Porps =  a: number; b: number,d: string ;
  type newProps =  a: string; b: string; c: number ;
  type ReplaceProps = Overwrite<Porps, newProps>
//预期结果是: a:string, b: string, d: string

实现思路:

借助上面实现的找两个对象相同的,和找两个对象不同的。

// 从T找出跟U相同的
  type InterSection<T extends object, U extends object> = Pick<
    T,
    Extract<keyof T, keyof U> & Extract<keyof U, keyof T>
  >;
  //从T中找出跟U不同的
  type Diff<T extends object, U extends object> = Pick<
    T,
    Exclude<keyof T, keyof U>
  >;
  type Overwrite<T extends object, U extends object, I = Diff<T,U> & InterSection<U, T> >= Pick<I, keyof I>

这里重点就是第三个泛型I,他无需传值,通过传入的一二泛型获取值。这里的TU顺序很重要。

如第一个Diff<T,U>就是从T里面找出与U不相关的,那就是d: string了。再交上

InterSection<U,T>,从U当中找出与T相关的,就是从newProps找出与Porps相关的,就是a:string, b: string,所以最后得到的结果就是:

 type ReplaceProps = Overwrite<Porps, newProps>; // type ReplaceProps = a: string,b: string, d: string

7 模块与命名空间

文件模块:在ts文件的根级别位置含有import 或者 export,那么就会在这个文件创建一个本地的作用域

模块是ts中外部模块的简称,侧重于代码复用

一个模块里的变量函数类等在外部不可见,除非导出。

同个模块相同名字的命名空间会合并。

export namespace zoo 
     // 同个模块内部同样的名字的命名空间会合并 !!!
    export class Dog1

export namespace zoo 
   export class Dog1 //报错


8类型声明

类型声明可以让我们不需要将js重构为ts,只需要加上声明文件就可以

类型声明在编译的时候会删除

declare关键字表示声明的意思

// 声明命名空间,一个全局变量如果有很多子属性,就可以使用namesqpce。
declare namespace $ 
    function ajax(url: string):void
    let name: string
    namespace fn 
        function extend():void
    

$.fn.extend()
$.ajax('')

自己实现jq的声明文件:


对于jq,他的声明文件导出是export = Jquery, 他是ts的语法,表示可能是es导出,也可能是commonjs导出,具体实现看代码实现

而jq是commonjs导出的,这就导致我们需要import * as Jquery from ‘jquery’,转化以下。或者是tsconfig.json中esModuleInterop设为true,

表示转化es6的语法,才可以import Jquery from ‘jquery’

8 扩展类型

class enum既可以作为类型,也可以作为值

interface type只能作为类型,不能作为值

funciton var let const 只能作为值,不能作为类型

合并声明

同一名称的两个独立声明会被合并成一个单一声明

合并后的声明拥有原先两个声明的特性

可以通过接口合并的特性给一个第三方库扩展类型声明

// 使用命名空间扩展类,需要在类定义后面
namespace D 
    class Form 
        username: Form.Item
        test: Form.C
        d: Form.d //报错  //不可以作为类型
    

namespace Form 
    export type Item = string  // 定义类型
    export class C    // 类既可以作为类型,也可以作为值
    export let d : string //值不可以做为类型

const test1 = new Form()
const test2 = new Form.C()
Form.Item //不可以作为值
Form.d

    
 //  实用,扩展redux的store
namespace G 
    import ceateStore, Store from 'redux'
    type StoreExt = Store & ext: string // 只能通过type去扩展,不能通过namespace扩展
    const store: StoreExt = ceateStore(state=>state)
    store.ext


以上是关于typescript 高级的主要内容,如果未能解决你的问题,请参考以下文章

TypeScript 学习笔记 — 类型推断和类型保护

TypeScript——类型检查机制

TypeScript 从不类型推断

TypeScript 中的类型推断在特殊用例中无法正常工作

来自接口实现的 Typescript 泛型推断

TypeScript 类型推断/缩小挑战