typescript 難點梳理

1.new關鍵字在類型中的使用

泛型

在泛型裏使用類類型

在TypeScript使用泛型建立工廠函數時,須要引用構造函數的類類型。好比,javascript

function create<T>(c: {new(): T; }): T {//這邊的new()很差理解
    return new c();
}

一個更高級的例子,使用原型屬性推斷並約束構造函數與類實例的關係。html

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類的類型。
帶着疑問寫了測試代碼:java


vscode依然報錯,仔細想下,createInstance函數return new c();這句話是類的實例化,因此傳進來的參數c是個類,而不是類的實例,故要使用(c: new () => A)標明c是個類,而不是(c: Animal)類的實例,從下面的調用也能夠看出傳遞的是類而不是實例。
咱們知道js裏面是沒有類的,ES6裏面的class也只是個語法糖,編譯後依然爲一個function。因此去修飾一個class也就是修飾一個function,可是修飾的是構造函數,因此這邊加以區別,前面有個new。react


接口

類靜態部分與實例部分的區別

這邊一樣用到了關鍵字new()
第一個例子報錯了,
官方解釋:
當你操做類和接口的時候,你要知道類是具備兩個類型的:靜態部分的類型和實例的類型。 你會注意到,當你用構造器簽名去定義一個接口並試圖定義一個類去實現這個接口時會獲得一個錯誤:

這裏由於當一個類實現了一個接口時,只對其實例部分進行類型檢查。 constructor存在於類的靜態部分,因此不在檢查的範圍內。
所以,咱們應該直接操做類的靜態部分。 看下面的例子,咱們定義了兩個接口, ClockConstructor爲構造函數所用和ClockInterface爲實例方法所用。 爲了方便咱們定義一個構造函數 createClock,它用傳入的類型建立實例。git

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是否符合構造函數簽名。github

再結合react官方接口的書寫,typescript

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文件裏面不能這麼寫,同事被相同的問題糾結住,json

interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> { }
    class Component<P, S> {

        static contextType?: Context<any>;

        // TODO (TypeScript 3.0): unknown
        context: any;

        constructor(props: Readonly<P>);
        /**
         * @deprecated
         * @see https://reactjs.org/docs/legacy-context.html
         */
        constructor(props: P, context?: any);

        // We MUST keep setState() as a unified signature because it allows proper checking of the method return type.
        // See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257
        // Also, the ` | S` allows intellisense to not be dumbisense
        setState<K extends keyof S>(
            state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null),
            callback?: () => void
        ): void;

        forceUpdate(callback?: () => void): void;
        render(): ReactNode;

      
        readonly props: Readonly<P> & Readonly<{ children?: ReactNode }>;
        state: Readonly<S>;
        /**
         * @deprecated
         * https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs
         */
        refs: {
            [key: string]: ReactInstance
        };
    }

再次看React index.d.ts 會發現interface Component沒有聲明任何內容,生命週期方法也是繼承過來的,包括setState,forceUpdate,render的聲明都是在class Component中的,經過組件中的this.setState也是定位到class Component中,咱們知道interface Component是用來修飾組件的實例的,可是無法描述組件的靜態屬性,好比contextType,這時候也許只能藉助class來描述static成員,經過上述分析可知interface是能夠用來修飾類的,再結合interface ComponentClassapi

interface ComponentClass<P = {}, S = ComponentState> extends StaticLifecycle<P, S> {
        new (props: P, context?: any): Component<P, S>;
        propTypes?: WeakValidationMap<P>;
        contextType?: Context<any>;
        contextTypes?: ValidationMap<any>;
        childContextTypes?: ValidationMap<any>;
        defaultProps?: Partial<P>;
        displayName?: string;
    }

ComponentClass是用來描述class Component的,這個時候限制Component的static成員,接着上面的例子作個測試:數組

// 用來描述類類型
interface ClockConstructor {
  new (hour: number, minute: number);
  contextType: any; // 新增個成員變量
}

interface ClockInterface {
  tick: () => void;
}

class DigitalClock implements ClockInterface {
  //這邊實現的接口不能是直接的構造器
  constructor(h: number, m: number) {}
  tick() {
    console.log("beep beep");
  }
}

function createClock(
  ctor: ClockConstructor,
  hour: number,
  minute: number
): ClockInterface {
  //這個和泛型中使用類類型相同,
  return new ctor(hour, minute); //須要類型爲ClockInterface的兩個參數的構造器類,只是兩者寫法有點區別
}

用類類型描述class的靜態屬性
vscode給報錯了,說是DigitalClock缺乏contextType,其實就是缺乏靜態屬性 contextType,

class DigitalClock implements ClockInterface {
  static contextType;
  //這邊實現的接口不能是直接的構造器
  constructor(h: number, m: number) {}
  tick() {
    console.log("beep beep");
  }
}
let digital = createClock(DigitalClock, 12, 17);

給DigitalClock 加上static contextType就不會報錯了,ClockInterface是用來描述類的實例的,ClockConstructor是用來描述類的,用ClockConstructor描述類有哪些靜態屬性沒問題,可是怎麼去描述實例的靜態屬性呢?仿造React的方式在.ts文件中定義同名interface跟class

interface ClockInterface {}

class ClockInterface {
  tick(): void;
}

clipboard.png

說函數沒有具體實現,將代碼貼到d.ts文件,就不會報錯,

interface ClockInterface {
  name: string;
}

class ClockInterface {
  tick(): void;
  static jump(): void;
}

使用該接口,

class DigitalClock implements ClockInterface {
  static jump: () => {};
  name: string = "";
  tick() {
    console.log("beep beep");
  }
}

DigitalClock.jump();

在vscode中輸入DigitalClock. 或者static j都有相應的提示,因此能夠在d.ts中用同名interface和class描述類的靜態屬性

到此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];

錯誤寫法
再嘗試寫下去

type TTuple = [string, number];

type Res = TTuple[0];//string
type Res1 = TTuple[1];//number
type Res2 = TTuple[2];//報錯

2報錯
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";
相關文章
相關標籤/搜索