typescript 难点梳理

1.new关键字在类型中的使用

泛型

在泛型里使用类类型

在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型。比如,

function create<T>(c: {new(): T; }): T {//这边的new()不好理解
    return new c();
}

一个更高级的例子,使用原型属性推断并约束构造函数与类实例的关系。

class BeeKeeper {
    hasMask: boolean;
}

class ZooKeeper {
    nametag: string;
}

class Animal {
    numLegs: number;
}

class Bee extends Animal {
    keeper: BeeKeeper;
}

class Lion extends Animal {
    keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!

查了不少资料,比较好的解释是what is new() in Typescript?意思就是create函数的参数是构造函数没有参数的T类的类型,同理,createInstance函数的参数是构造函数没有参数的A类的类型。
带着疑问写了测试代码:


vscode依然报错,仔细想下,createInstance函数return new c();这句话是类的实例化,所以传进来的参数c是个类,而不是类的实例,故要使用(c: new () => A)标明c是个类,而不是(c: Animal)类的实例,从下面的调用也可以看出传递的是类而不是实例。
我们知道js里面是没有类的,ES6里面的class也只是个语法糖,编译后依然为一个function。所以去修饰一个class也就是修饰一个function,但是修饰的是构造函数,所以这边加以区别,前面有个new。


接口

类静态部分与实例部分的区别

这边同样用到了关键字new()
第一个例子报错了,
官方解释:
当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。 你会注意到,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误:


这里因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。
因此,我们应该直接操作类的静态部分。 看下面的例子,我们定义了两个接口, ClockConstructor为构造函数所用和ClockInterface为实例方法所用。 为了方便我们定义一个构造函数 createClock,它用传入的类型创建实例。

interface ClockConstructor {
    new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
    tick:()=>void;
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {//这个和泛型中使用类类型相同,
    return new ctor(hour, minute);//需要类型为ClockInterface的两个参数的构造器类,只是二者写法有点区别
}

class DigitalClock implements ClockInterface {//这边实现的接口不能是直接的构造器
    constructor(h: number, m: number) { }
    tick() {
        console.log("beep beep");
    }
}
class AnalogClock implements ClockInterface {
    constructor(h: number, m: number) { }
    tick() {
        console.log("tick tock");
    }
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);

因为createClock的第一个参数是ClockConstructor类型,在createClock(AnalogClock, 7, 32)里,会检查AnalogClock是否符合构造函数签名。

再结合react官方接口的书写,

interface Component<P = {}, S = {}> extends ComponentLifecycle<P, S> { }
    class Component<P, S> {//这里全部是实例方法和属性
        constructor(props?: P, context?: any);

        // Disabling unified-signatures to have separate overloads. It's easier to understand this way.
        // tslint:disable:unified-signatures
        setState<K extends keyof S>(f: (prevState: S, props: P) => Pick<S, K>, callback?: () => any): void;
        setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;
        // tslint:enable:unified-signatures

        forceUpdate(callBack?: () => any): void;
        render(): JSX.Element | null | false;

        // React.Props<T> is now deprecated, which means that the `children`
        // property is not available on `P` by default, even though you can
        // always pass children as variadic arguments to `createElement`.
        // In the future, if we can define its call signature conditionally
        // on the existence of `children` in `P`, then we should remove this.
        props: Readonly<{ children?: ReactNode }> & Readonly<P>;
        state: Readonly<S>;
        context: any;
        refs: {
            [key: string]: ReactInstance
        };
    }

 interface ComponentClass<P = {}> {
        new (props?: P, context?: any): Component<P, ComponentState>;//此处对Component做了修饰,规定react的构造函数类型
        propTypes?: ValidationMap<P>;//下面几个全部是静态方法和属性
        contextTypes?: ValidationMap<any>;
        childContextTypes?: ValidationMap<any>;
        defaultProps?: Partial<P>;
        displayName?: string;
    }

为什么写了interface Component又写了class Component,interface Component没有写里面具体的实例方法和属性,而写了同名的class Component,是不是意味着class Component就是interface的具体实现,这里是index.d.ts文件,应该在.ts文件里面不能这么写

到此new()关键字在类型中的使用基本搞清楚了。

类装饰器
function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
    return class extends constructor {
        newProperty = "new property";
        hello = "override";
    }
}

@classDecorator
class Greeter {
    property = "property";
    hello: string;
    constructor(m: string) {
        this.hello = m;
    }
}

console.log(new Greeter("world"));

2. 装饰器的使用

装饰器(Decorator)在React中的应用
JavaScript 中的装饰器是什么?

3. 函数类型声明的不同方式

1. 最常见的方式

函数声明(Function Declaration)类型的,就是普通的具名函数

  function add(x: number, y: number): number {
    return x + y
  }
2. 函数表达式(Function Expression)类型声明
  • 1.这种就是后面赋值号后面有个匿名或者具名函数,比较好理解的写法,都在函数体内写类型
  handle = (
    baseValue: number,
    increment: number,
  ): number => {
    return baseValue
  }
  • 2.给变量定义类型,同时也在函数内部定义类型
  handle: (baseValue: number, increment: number) => number = (
    baseValue: number,
    increment: number,
  ): number => {
    return baseValue
  }
  • 3.将变量的类型抽取到接口中
interface IHandle {
  (baseValue: number, increment: number): number
}

 handle: IHandle = (baseValue: number, increment: number): number => {
    return baseValue
  }

既然前面的变量声明了接口,那么后面的函数里面的类型就可以去掉了

interface IHandle {
  (baseValue: number, increment: number): number
}

 handle: IHandle = (baseValue,increment)=> {
    return baseValue
  }

但是发现个问题

interface IHandle {
  (baseValue: number, increment: number): number
}

 handle: IHandle = (baseValue)=> {
    return baseValue
  }

这么写居然vscode没有报错,increment明明不是可选参数,不是很理解,有待讨论

查阅了typescript的官方文档的函数章节

interface UIElement {
    addClickListener(onclick: (this: void, e: Event) => void): void;
}

这么写的接口,和函数类型的接口声明惊人的相似,

interface IHandle {
  (baseValue: number, increment: number): number
}

差别在哪呢.函数类型的接口声明是匿名的,而上面对象的类型声明的函数是具名的,做了一下测试

var src:UIElement = function() {}
//报错:
// Type '() => void' is not assignable to type 'UIElement'.
// Property 'addClickListener' is missing in type '() => void'.
// var src: UIElement

interface UIElement {
  addClickListener(name: string): void
}

var src: UIElement = {//不报错
  addClickListener() {},
}

var src: UIElement = { //报错
  addClickListener(name: string, age: number) {},
}
// Type '{ addClickListener(name: string, age: number): void; }' is not assignable to type 'UIElement'.
// Types of property 'addClickListener' are incompatible.
//   Type '(name: string, age: number) => void' is not assignable to type '(name: string) => void'.

看样就是实际的函数的参数可以比接口少定义,但是不能多定义
函数接口的是用来修饰变量的,当然包括函数的形参的修饰,以及返回值的修饰,但是不能修饰具名函数

interface IHandle {
  props?: object
  (baseValue: number, increment: number): number
}

function source:IHandle(baseValue) {//这么修饰具名函数会报错
  return baseValue
}

function source(baseValue): IHandle {//这么写是可以的,表明返回了一个IHandle 的函数
  return baseValue
}

3. 怎样修饰类(类类型)

class Greeter {
    static standardGreeting = "Hello, there";
    greeting: string;
    greet() {
        if (this.greeting) {
            return "Hello, " + this.greeting;
        }
        else {
            return Greeter.standardGreeting;
        }
    }
}

let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());

let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";

let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());
  1. 我们一般都是修饰一个类的实例的,怎么简单的修饰类,typeof是个很好的办法,
    这个例子里,greeter1与之前看到的一样。 我们实例化 Greeter类,并使用这个对象。 与我们之前看到的一样。
    再之后,我们直接使用类。 我们创建了一个叫做 greeterMaker的变量。 这个变量保存了这个类或者说保存了类构造函数。 然后我们使用 typeof Greeter,意思是取Greeter类的类型,而不是实例的类型。 或者更确切的说,"告诉我 Greeter标识符的类型",也就是构造函数的类型。 这个类型包含了类的所有静态成员和构造函数。 之后,就和前面一样,我们在 greeterMaker上使用new,创建Greeter的实例。

    也就是使用typeof ClassName
interface IPerson {
  age: number;
}
class Person {
  age: 99;
}

let p: typeof IPerson = Person;//这么写会报错的
  1. 使用构造函数接口修饰类
interface IPerson {
  age: number;
}

interface IPersonConstructor {
  new (): IPerson;
}

class Person {
  age: 99;
}

let p: IPersonConstructor = Person;

之前对new (): IPerson;这句话后面的返回值不是很理解,直到看到了将基类构造函数的返回值作为'this',也就是说new Person()的时候执行的是构造函数,那么构造函数就返回了Person的实例,自然new (): IPerson;构造函数返回IPerson就很好理解了

4. keyof

比如有个interface a{
a1: 'a1';
a2: 'a2';
.....
.....
a100: 'a100';
}
然后又个类型要继承这个interface的某一个value的值
比如 type anum = 'a1' | 'a2' | 'a3' | ....| 'a100',
应该怎么写?

type anum = typeof a.a1 | typeof a.a2 | typeof a.a3 | ....| typeof a.a100;

有没有简单的写法 那个interface 有可能随时在变

a1到a100全要?

对全要

而且有可能 到a100以上 一直在添加

我想在添加的时候只添加interface type不用在修改了 有没有办法

key还是value

value

keyof
keyof.png

5. 函数名后面的感叹号(非空断言操作符)

    onOk = () => {
      this.props.onOk!(this.picker && this.picker.getValue());
      this.fireVisibleChange(false);
    }

react-component/m-picker
群里问了是非空操作符

再去搜索,在typescript 2.0的文档里找到了,叫 非空断言操作符

// 使用--strictNullChecks参数进行编译
function validateEntity(e?: Entity) {
    // 如果e是null或者无效的实体,就会抛出异常
}

function processEntity(e?: Entity) {
    validateEntity(e);
    let s = e!.name;  // 断言e是非空并访问name属性
}
vscode检测

需要在tsconfig.json 里面加上"strictNullChecks": true,这样vscode会自动检测null和undefined

6. ts差集运算

Add support for literal type subtraction

具体应用在高阶组件里面的props
TypeScript在React高阶组件中的使用技巧

7. 方法重写

重写

方法重写子类的参数需要跟父类一致,否则会报错

8. 剩余参数

const renderWidget = ({ field, widget, ...restParams }) => {
  const min = restParams.min;
};

这段代码的剩余参数restParams 怎么表示
感觉应该这么写

const renderWidget = (
  {
    field,
    widget,
    ...restParams
  }: { field: string; widget: string;restParams: [key: string]: any },
  idx: number,
  primaryField: string
) => {
  const min = restParams.min;
};

但是还是报错,其实应该省去restParams

const renderWidget = ({
  field,
  widget,
  ...restParams
}: {
  field: string;
  widget: string;
  [key: string]: any;
}) => {
  const min = restParams.min;
};

这样就好了Destructuring a function parameter object and …rest

9. 元组推断

群里看到的疑问,为什么最后推断出是string|number

type TTuple = [string, number];

type Res = TTuple[number]; // string|number

一开始不理解,自己改下写法

type TTuple = [string, number];

type Res = TTuple[string]; 

[图片上传失败...(image-894ad4-1590418731585)]
再尝试写下去

type TTuple = [string, number];

type Res = TTuple[0];//string
type Res1 = TTuple[1];//number
type Res2 = TTuple[2];//报错

[图片上传失败...(image-e6ec46-1590418731585)]
TTuple[2]报错

首先js中没有元组概念,数组中可以存放任何数据类型,ts中把存放不同数据类型的数组叫做元组,这样就很好理解TTuple[number]中的number就是索引index,索引只能是数字,而且不能越界,所以两次报错就好理解了,TTuple[number]为什么返回string|number是因为没有指定具体的索引,只能推断出两种可能,string或number

联想到获取接口的属性的类型

interface IProps {
  name: string;
  age: number;
}

type IAge = IProps["name"];// string

const per: IAge = "geek";

10. type interface 泛型差异

下面实现类似于Record的代码

interface Collection<T extends string, U> {
  [P in T]: U;
}

type Collection33<K extends string, T> = {
  [P in K]: T;
};
interface
interface

type完全没问题,interface 的两种写法都会报错,好奇葩

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342