TypeScript 使用指南

TypeScript 是 JS 的超集,強(qiáng)調(diào)變量類(lèi)型片部。讓JS更加接近強(qiáng)類(lèi)型語(yǔ)言,下圖說(shuō)明了接入 TS 的好處:


TS作用

1、接入流程

  • 安裝 tslint 和 typescript
npm i typescript -D
npm install typescript-eslint-parser --save-dev
npm install eslint-plugin-typescript --save-dev // typescript-eslint-parser 對(duì)一部分 ESLint 規(guī)則支持性不好财骨,故我們需要安裝 eslint-plugin-typescript,彌補(bǔ)一些支持性不好的規(guī)則
  • 項(xiàng)目根目錄添加tsconfig.json
// tsconfig.json
{
  "compilerOptions": {
    "target": "es5", // 編譯后的代碼轉(zhuǎn)為 es5 以兼容低版本
    "module": "esnext", // 讀取的文件采用的都是 ES6 的模塊系統(tǒng)
    "allowSyntheticDefaultImports": true, // 允許從沒(méi)有設(shè)置默認(rèn)導(dǎo)出的模塊中默認(rèn)導(dǎo)入
    "experimentalDecorators": true, // 允許使用 裝飾器
    "moduleResolution": "node", // 決定如何處理模塊
    "strict": true, // 啟用所有嚴(yán)格檢查選項(xiàng)藏姐。 包含--noImplicitAny, --noImplicitThis, --alwaysStrict, --strictBindCallApply, --strictNullChecks, --strictFunctionTypes和--strictPropertyInitialization
    "allowJs": true, // 接受JavaScript做為輸入
    "skipLibCheck": true, // 忽略所有的聲明文件(*.d.ts)的類(lèi)型檢查
    "sourceMap": true,
    "importHelpers": true,// 從tslib導(dǎo)入輔助工具函數(shù)
    "downlevelIteration": true,
    "lib": ["es2015", "dom"], // 編譯過(guò)程中需要引入的庫(kù)文件的列表
    "jsx": "react" // 在.tsx文件里支持JSX
  },
  "include": [ //讀取所有可識(shí)別的src目錄下的文件
    "./src/**/*"
  ],
  "exclude": ["*.js", "dist", "node_modules", "ios", "android"]  // 不讀取這些文件
}

  • 調(diào)整 eslintrc 文件
    parser: 'typescript-eslint-parser',
    plugins: [
        'typescript'
    ],
  • 調(diào)整 vscode 設(shè)置
"eslint.validate": [
    "javascript",
    "javascriptreact",
    "typescript",
    "typescriptreact" // jsx 支持
  ],
  • 遇到的一些問(wèn)題:
    1隆箩、webpack 中定義了alias后,在文件中使用時(shí)報(bào)"模塊找不到的錯(cuò)誤"羔杨,這時(shí)需要在 tsconfig.json 中定義 baseUrl捌臊,如
// alias
containers: resolve('src/containers'),
// 某tsx文件中
import Com from 'containers/Com' // 這時(shí)會(huì)報(bào)模塊找不到錯(cuò)誤
// 配置tsconfig.json 的 baseUrl 可以解決
baseUrl: './src'

2、在 tsx 文件中引入的 scss兜材、css理澎、png等非 js 文件報(bào)模塊找不到錯(cuò)誤逞力,可以在 src 下新建一個(gè) global.d.ts,內(nèi)容如下:

declare module '*.(scss|png|jpg)' {
  const content: any;
  export default content;
}

3糠爬、webpack 的 module.hot 報(bào)錯(cuò)掏击,需安裝 @types/webpack-env @types/node

npm i -D @types/webpack-env  @types/node

2、基本語(yǔ)法

基礎(chǔ)類(lèi)型

相比于原始JS秩铆,多了 any砚亭,void,never殴玛,元組捅膘,枚舉

let a:any = 'tom'; // 為編程階段還不清楚類(lèi)型的變量指定一個(gè)類(lèi)型
let b:function():void = function(){} // 當(dāng)一個(gè)函數(shù)沒(méi)有返回值時(shí)
let c:function():never = function(){ throw Error('error')}  // 返回never的函數(shù)必須存在無(wú)法達(dá)到的終點(diǎn)
let d:[string,number] =['tom',123]  // 元組類(lèi)型允許表示一個(gè)已知元素?cái)?shù)量和類(lèi)型的數(shù)組,各元素的類(lèi)型不必相同
enum Color = {Green,Red,Blue}  // 枚舉類(lèi)型可以為一組數(shù)值賦予友好的名字

// 其他:類(lèi)型斷言(即類(lèi)型轉(zhuǎn)換)
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
// 或者
let strLength: number = (<string>someValue).length;

高級(jí)類(lèi)型

  • 交叉類(lèi)型 &
    交叉類(lèi)型是將多個(gè)類(lèi)型合并為一個(gè)類(lèi)型
function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}
  • 聯(lián)合類(lèi)型 |
    聯(lián)合類(lèi)型表示一個(gè)值可以是幾種類(lèi)型之一,如果一個(gè)值是聯(lián)合類(lèi)型滚粟,我們只能訪問(wèn)此聯(lián)合類(lèi)型的所有類(lèi)型里共有的成員
function padLeft(value: string, padding: string | number) {
    // ...
}
  • typeof 和 instanceof 類(lèi)型保護(hù)
    TypeScript可以將二者識(shí)別為一個(gè)類(lèi)型保護(hù)
function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

// 類(lèi)型為SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {
    padder; // 類(lèi)型細(xì)化為'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
    padder; // 類(lèi)型細(xì)化為'StringPadder'
}

// 去除聯(lián)合類(lèi)型中的null,語(yǔ)法是添加!后綴:identifier!從identifier的類(lèi)型里去除了null和undefined
function fixed(name: string | null): string {
  function postfix(epithet: string) {
    return name!.charAt(0) + '.  the ' + epithet; // ok
  }
  name = name || "Bob";
  return postfix("great");
}
  • 類(lèi)型別名 type
    類(lèi)型別名會(huì)給一個(gè)類(lèi)型起個(gè)新名字寻仗。 類(lèi)型別名有時(shí)和接口很像,但是可以作用于原始值凡壤,聯(lián)合類(lèi)型署尤,元組以及其它任何你需要手寫(xiě)的類(lèi)型。通常用類(lèi)型別名定義聯(lián)合類(lèi)型亚侠,與接口的區(qū)別在于沒(méi)有產(chǎn)生新的類(lèi)型曹体,且不能被繼承和實(shí)現(xiàn)
type a = 'string' | 'number' | null

接口 interface

TypeScript的核心原則之一是對(duì)值所具有的結(jié)構(gòu)進(jìn)行類(lèi)型檢查,接口的作用就是為這些類(lèi)型命名和為你的代碼或第三方代碼定義契約硝烂。接口的2個(gè)作用:1箕别、類(lèi)型檢查;2滞谢、被類(lèi)繼承串稀,強(qiáng)制類(lèi)實(shí)現(xiàn)某種契約

// 基本語(yǔ)法(前 readonly 只讀,后 狮杨? 可選屬性)
interface 類(lèi)型名稱(chēng) {
   ...
}
  • 定義對(duì)象接口
interface SquareConfig {
    color?: string;
    readonly width?: number;
    [index: string]: number;
    [propName: string]: any;
}
  • 定義函數(shù)接口
// 函數(shù)類(lèi)型檢查器
interface SearchFunc {
  (source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(src, sub) {
    let result = src.search(sub);
    return result > -1;
}
  • 定義數(shù)組接口
// 數(shù)組類(lèi)型 索引檢查器
interface StringArray {
  [index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];

let myStr: string = myArray[0]
  • 定義類(lèi)接口
// 類(lèi)類(lèi)型檢查器(當(dāng)一個(gè)類(lèi)實(shí)現(xiàn)了一個(gè)接口時(shí)母截,只對(duì)其實(shí)例部分進(jìn)行類(lèi)型檢查。 constructor存在于類(lèi)的靜態(tài)部分橄教,所以不在檢查的范圍內(nèi)清寇。)
interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}
// 等價(jià)于
declare class ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}
  • 接口繼承接口
interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

let square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;
  • 接口繼承類(lèi)(會(huì)繼承到類(lèi)的private和protected成員)
class Control {
    private state: any;
}

interface SelectableControl extends Control {
    select(): void;
}

class Button extends Control implements SelectableControl {
    select() { }
}

class TextBox extends Control {
    select() { }
}

// Error: Property 'state' is missing in type 'Image'.
class Image implements SelectableControl {
    select() { }
}
  • 泛型接口
// 函數(shù)泛型接口,比普通函數(shù)接口更為嚴(yán)格
interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;
  • 混合接口
    一個(gè)對(duì)象可以同時(shí)做為函數(shù)和對(duì)象使用颤陶,并帶有額外的屬性
// 這個(gè)接口既可以當(dāng)類(lèi)接口也可當(dāng)對(duì)象接口
interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

類(lèi)

  • 成員修飾符 public , protected, private
    所以成員默認(rèn)都是public.
    private成員不能在類(lèi)外使用颗管,即不能在實(shí)例上以及子類(lèi)上使用
    protected成員可以在子類(lèi)中使用陷遮,不能在自身及其子類(lèi)的實(shí)例上使用滓走。不過(guò)可以通過(guò)子類(lèi)的實(shí)例方法訪問(wèn)
    若把構(gòu)造函數(shù)聲明為protected,則該類(lèi)只能被子類(lèi)實(shí)例化帽馋,本身不能實(shí)例化
  • 只讀屬性 readonly
    只讀屬性必須在聲明時(shí)或構(gòu)造函數(shù)里被初始化
  • 參數(shù)屬性
    參數(shù)屬性可以方便地讓我們?cè)谝粋€(gè)地方定義并初始化一個(gè)成員搅方,通過(guò)用在構(gòu)造函數(shù)上比吭。參數(shù)屬性通過(guò)給構(gòu)造函數(shù)參數(shù)添加一個(gè)訪問(wèn)限定符來(lái)聲明
class Animal {
    constructor(private name: string) { }
    move(distanceInMeters: number) {
        console.log(`${this.name} moved ${distanceInMeters}m.`);
    }
}
  • 抽象類(lèi)
    abstract 類(lèi)中的抽象方法必須被子類(lèi)所實(shí)現(xiàn)
abstract class Animal {
    abstract makeSound(): void;
    move(): void {
        console.log('roaming the earch...');
    }
}

函數(shù)

傳遞給一個(gè)函數(shù)的參數(shù)個(gè)數(shù)必須與函數(shù)期望的參數(shù)個(gè)數(shù)一致

  • 可選參數(shù)和默認(rèn)參數(shù)
    可選參數(shù)必須跟在必須參數(shù)后面
    帶默認(rèn)值的參數(shù)不需要放在必須參數(shù)的后面。 如果帶默認(rèn)值的參數(shù)出現(xiàn)在必須參數(shù)前面姨涡,用戶必須明確的傳入undefined值來(lái)獲得默認(rèn)值
function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}
  • 剩余參數(shù)
function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;
  • 函數(shù)重載
    為同一個(gè)函數(shù)提供多個(gè)函數(shù)類(lèi)型定義來(lái)進(jìn)行函數(shù)重載
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

泛型

泛型即類(lèi)型變量衩藤,它是一種特殊的變量,只用于表示類(lèi)型而不是值

function identity<T>(arg: T): T {
    return arg;
}

我們定義了泛型函數(shù)后涛漂,可以用兩種方法使用赏表。
第一種是,傳入所有的參數(shù)匈仗,包含類(lèi)型參數(shù):

let output = identity<string>("myString");  // type of output will be 'string'

第二種方法更普遍瓢剿。利用了類(lèi)型推論 -- 即編譯器會(huì)根據(jù)傳入的參數(shù)自動(dòng)地幫助我們確定T的類(lèi)型:

let output = identity("myString");  // type of output will be 'string'

如果編譯器不能夠自動(dòng)地推斷出類(lèi)型的話,只能像上面那樣明確的傳入T的類(lèi)型悠轩,在一些復(fù)雜的情況下间狂,這是可能出現(xiàn)的。

  • 泛型接口
interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;
  • 泛型類(lèi)
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
  • 泛型約束
    在泛型中使用 extends 關(guān)鍵字
interface Lengthwise {
    length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);  // Now we know it has a .length property, so no more error
    return arg;
}

類(lèi)型兼容性

  • 普通類(lèi)型和對(duì)象
    要將y復(fù)制給x火架,必須保證x的每個(gè)屬性都在y中鉴象,且類(lèi)型匹配。對(duì)于y多余的屬性則不管何鸡。
interface Named {
    name: string;
}

let x: Named;
let y = { name: 'Alice', location: 'Seattle' };
x = y;
  • 函數(shù)兼容
    與上面剛好相反
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK
x = y; // Error

模塊

模塊的導(dǎo)入導(dǎo)出和ES6一致纺弊,import export,但也有新增部分

  • 整體導(dǎo)出
    使用export =導(dǎo)出一個(gè)模塊骡男,則必須使用TypeScript的特定語(yǔ)法import module = require("module")來(lái)導(dǎo)入此模塊俭尖。
let numberRegexp = /^[0-9]+$/;
class ZipCodeValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
export = ZipCodeValidator;

// 導(dǎo)入
import zip = require("./ZipCodeValidator");
  • 使用外部模塊
    node里大部分模塊都不是TS寫(xiě)的,如果要用就需要為這些模塊做個(gè)TS聲明洞翩,一般放在.d.ts文件里:
// node.d.ts
declare module "url" {
    export interface Url {
        protocol?: string;
        hostname?: string;
        pathname?: string;
    }

    export function parse(urlStr: string, parseQueryString?, slashesDenoteHost?): Url;
}

declare module "path" {
    export function normalize(p: string): string;
    export function join(...paths: any[]): string;
    export let sep: string;
}

現(xiàn)在我們可以/// <reference> node.d.ts并且使用import url = require("url");或import * as URL from "url"加載模塊稽犁。

/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");

假如你不想在使用一個(gè)新模塊之前花時(shí)間去編寫(xiě)聲明,你可以采用聲明的簡(jiǎn)寫(xiě)形式以便能夠快速使用它骚亿。

declare module "hot-new-module";
import x, {y} from "hot-new-module"; // 該模塊的導(dǎo)出所有類(lèi)型都將會(huì)是any
x(y);
  • 導(dǎo)入其他類(lèi)型文件已亥,如txt,json等
declare module "*!text" {
    const content: string;
    export default content;
}
// Some do it the other way around.
declare module "json!*" {
    const value: any;
    export default value;
}

// 
import fileContent from "./xyz.txt!text";
import data from "json!http://example.com/data.json";
console.log(data, fileContent);
  • 擴(kuò)展模塊
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
    interface Observable<T> {
        map<U>(f: (x: T) => U): Observable<U>;
    }
}
Observable.prototype.map = function (f) {
    // ... another exercise for the reader
}

// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map(x => x.toFixed());


// observable.ts
export class Observable<T> {
    // ... still no implementation ...
}

declare global {  // 全局?jǐn)U展
    interface Array<T> {
        toObservable(): Observable<T>;
    }
}

Array.prototype.toObservable = function () {
    // ...
}

一些操作符和關(guān)鍵字

  • keyof T 索引類(lèi)型查詢操作符
    keyof T的結(jié)果為T(mén)上已知的公共屬性名的聯(lián)合
  • T[K] 索引訪問(wèn)操作符
    T[K] 返回屬性的類(lèi)型
  • extend 繼承
    接口繼承接口来屠,接口繼承類(lèi)虑椎,類(lèi)繼承類(lèi)
  • implement 實(shí)現(xiàn)
    類(lèi)實(shí)現(xiàn)接口
  • extends 泛型約束關(guān)鍵字
  • new
function createInstance<A>(c: new () => A): A {
    return new c();
}

function createInstance<A>(c: {new(): T; }): A {
    return new c();
}

常見(jiàn)問(wèn)題

  • 枚舉類(lèi)型key-value互轉(zhuǎn)
enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green; // 2

let colorName: string = Color[Color.Green];  // 'Green'
  • 給回調(diào)函數(shù)聲明類(lèi)型
type Cb = () => void;
on(type: string, cb: Cb);
  • 自己寫(xiě) 類(lèi)型聲明文件
// 首先聲明一下模塊:
declare module 'progressbar.js' {
  // 模塊中暴露了 Circle 類(lèi)
  export class Circle {
    constructor(container: HTMLElement, options: Options);
  }
  // 構(gòu)造函數(shù)的 Options 需要單獨(dú)聲明 
  interface Options {
    easing?: string;
    strokeWidth?: number;
    trailColor?: string;
    trailWidth?: number;
  }
}
  • 接口屬性定義順序
// 只讀、必選俱笛、可選捆姜、未知屬性
interface iProps {
  readonly x: number;
  readonly y: number;
  name: string;
  age: number;
  height?: number;
  [propName: string]: any;
}
  • 小技巧
// 定義一個(gè)javascript的對(duì)象,key是字符串迎膜,value是任意類(lèi)型
const people:Record<string,any> = {
    name: 'chengfeng',
    age: 10
}

// 將傳入的屬性變?yōu)榭蛇x項(xiàng)
interface IPeople {
    title: string;
    name: string;
}

const people: Partial<IPeople> = {
    title: 'Delete inactive users',
};

// 傳入的屬性變?yōu)樽兂芍蛔x
const people: Readonly<IPeople> = {
    title: 'todo list',
    name: chenfeng;
};

const people1: Partial<IPeople>  = { title: 'ts' }; // OK

const people22: Required<IPeople> = { title: 'ts' }; // Error: property 'name' missing

type T = keyof IPeople // -> "name" | "age"

// 結(jié)合 react-router-dom 使用
import { withRouter, RouteComponentProps } from 'react-router-dom';

class App extends React.Component<IProps & RouteComponentProps<{}>, AppStates> {}
export default withRouter(App);
  • any 與 object 類(lèi)型的區(qū)別
    Object類(lèi)型的變量只是允許你給它賦任意值泥技,但是卻不能夠在它上面調(diào)用任意的方法,即便它真的有這些方法磕仅;any 可以
  • readonly 與 const
    做為變量使用的話用const珊豹,若做為屬性則使用readonly
  • 對(duì)象字面量的TS檢測(cè)
    對(duì)象字面量會(huì)被特殊對(duì)待而且會(huì)經(jīng)過(guò)額外屬性檢查簸呈,當(dāng)將它們賦值給變量或作為參數(shù)傳遞的時(shí)候。 如果一個(gè)對(duì)象字面量存在任何“目標(biāo)類(lèi)型”不包含的屬性時(shí)店茶,你會(huì)得到一個(gè)錯(cuò)誤蜕便。可以用索引簽名解決這個(gè)問(wèn)題:
interface SquareConfig {
    color?: string;
    width?: number;
    [propName: string]: any;  // 可包含任何屬性
}
  • 類(lèi)作為接口
    類(lèi)可以產(chǎn)生接口贩幻,類(lèi)分為靜態(tài)部分和實(shí)例部分
class Clock implements ClockInterface {
  currentTime = new Date()
  setTime(d: Date) {
    this.currentTime = d
  }
  reset() {}
  a = 1
  constructor(h: number, m: number) {}
}

let clock: typeof Clock = Clock   // typeof Clock 取得是 構(gòu)造函數(shù) 的類(lèi)型

3轿腺、React 中使用的最佳實(shí)踐

  • vscode 安裝 Typescript React code snippets 插件
  • 所有用到 jsx 語(yǔ)法的文件都需要以 tsx 后綴命名
  • 使用組件聲明時(shí)的Component<P, S>泛型參數(shù)聲明,來(lái)代替PropTypes丛楚!
  • 全局變量或者自定義的 window 對(duì)象屬性吃溅,統(tǒng)一在項(xiàng)目根下的 global.d.ts 中進(jìn)行聲明定義
  • 對(duì)于項(xiàng)目中常用到的接口數(shù)據(jù)對(duì)象,在schemas/目錄下定義好其結(jié)構(gòu)化類(lèi)型聲明
  • 類(lèi)組件的聲明
class App extends Component<IProps, IState> {
    static defaultProps = {
        // ...
    }
    
    readonly state = { // 使用 class properties 語(yǔ)法對(duì)state做初始化時(shí)鸯檬,會(huì)覆蓋掉Component<P, S>中對(duì)state的readonly標(biāo)識(shí)决侈,所以需要顯示寫(xiě)上 readonly
        // ...
    }; 
    // 小技巧:如果state很復(fù)雜不想一個(gè)個(gè)都初始化,可以結(jié)合類(lèi)型斷言初始化state為空對(duì)象或者只包含少數(shù)必須的值的對(duì)象:  readonly state = {} as IState;
}
  • 函數(shù)式組件的聲明
// SFC: stateless function components
// v16.7起喧务,由于hooks的加入赖歌,函數(shù)式組件也可以使用state,所以這個(gè)命名不準(zhǔn)確功茴。新的react聲明文件里庐冯,也定義了React.FC類(lèi)型^_^
const List: React.SFC<IProps> = props => null
  • 正確的聲明高階組件
import { RouteComponentProps } from 'react-router-dom';
 
// 方法一
@withRouter
class App extends Component<Partial<RouteComponentProps>> {
    public componentDidMount() {
        // 這里就需要使用非空類(lèi)型斷言了
        this.props.history!.push('/');
    }
    // ...
});
 
// 方法二
@withRouter
class App extends Component<{}> {
    get injected() {
        return this.props as RouteComponentProps
    }
 
    public componentDidMount() {
        this.injected.history.push('/');
    }
    // ...

interface IVisible {
    visible: boolean;
}
 
 //排除 IVisible
function withVisible<Self>(WrappedComponent: React.ComponentType<Self & IVisible>): React.ComponentType<Omit<Self, 'visible'>> {
    return class extends Component<Self> {
        render() {
            return <WrappedComponent {...this.props}  visible={true} />
        }
    }
}
  • react-router-dom 中常用 RouteComponentProps,react中常用的 ReactNode
// IProps 常用寫(xiě)法
interface IProps extends RouteComponentProps {
  title: string | ReactNode
  style: any
}
// 或者
type IProps = {
  style: any
  config: {}[]
}

// IState 狀態(tài)聲明常用寫(xiě)法
const defaultState = {$1}
type IState = Readonly<typeof defaultState>
  • 在組件定義的地方定義接口坎穿,而不是使用的時(shí)候展父,否則需要定義兩遍
// 定義處
type IProps = {
  style?: any
  config: {}[]
}

export default class Paragraph extends PureComponent<IProps, {}> {
  render() {
    const { style, config } = this.props
    return (
      <div className={styles.layout} style={style}>
        {config.map((item, index) => (
          <p className={styles.paragraph} key={index} style={style}>
            {item}
          </p>
        ))}
      </div>
    )
  }
}

// 使用處
<Paragraph config={this.config.subTitle} />
  • 泛型IProps
    上述IProps中,如果config是多類(lèi)型的對(duì)象數(shù)組玲昧,則可以這樣
type IProps<T> = {
  style?: any
  config: T[]
}

export default class Paragraph<T> extends PureComponent<IProps<T>, {}> {
  render() {
    const { style, config } = this.props
    return (
      <div className={styles.layout} style={style}>
        {(config as T[]).map((item, index) => (
          <p className={styles.paragraph} key={index} style={style}>
            {item}
          </p>
        ))}
      </div>
    )
  }
}

參考

https://github.com/plantain-00/blogs/issues/28
https://tasaid.com/blog/20171102225101.html
https://juejin.im/entry/5a156adaf265da43231aa032
typescript 高級(jí)技巧
https://mp.weixin.qq.com/s/_lO3cd0FcF0Dg3TRnHPdwg)
TypeScript 模塊解析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末栖茉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子孵延,更是在濱河造成了極大的恐慌吕漂,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尘应,死亡現(xiàn)場(chǎng)離奇詭異惶凝,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)犬钢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)苍鲜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人玷犹,你說(shuō)我怎么就攤上這事混滔。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵遍坟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我晴股,道長(zhǎng)愿伴,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任电湘,我火速辦了婚禮隔节,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘寂呛。我一直安慰自己怎诫,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布贷痪。 她就那樣靜靜地躺著幻妓,像睡著了一般。 火紅的嫁衣襯著肌膚如雪劫拢。 梳的紋絲不亂的頭發(fā)上肉津,一...
    開(kāi)封第一講書(shū)人閱讀 51,737評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音舱沧,去河邊找鬼妹沙。 笑死,一個(gè)胖子當(dāng)著我的面吹牛熟吏,可吹牛的內(nèi)容都是我干的距糖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼牵寺,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼悍引!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起帽氓,我...
    開(kāi)封第一講書(shū)人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吗铐,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后杏节,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體唬渗,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年奋渔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镊逝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡嫉鲸,死狀恐怖撑蒜,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤座菠,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布狸眼,位于F島的核電站,受9級(jí)特大地震影響浴滴,放射性物質(zhì)發(fā)生泄漏拓萌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一升略、第九天 我趴在偏房一處隱蔽的房頂上張望微王。 院中可真熱鬧,春花似錦品嚣、人聲如沸炕倘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)罩旋。三九已至,卻和暖如春眶诈,著一層夾襖步出監(jiān)牢的瞬間瘸恼,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工册养, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留东帅,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓球拦,卻偏偏與公主長(zhǎng)得像靠闭,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坎炼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,105評(píng)論 1 32
  • 周末的早晨10:00左右愧膀,餓到肚子扁,終于等到懶懶的小主起床了…… 跟著小主pp后走來(lái)走去谣光,心里想:“快給我準(zhǔn)備早...
    小布瓜啊哈閱讀 200評(píng)論 0 0
  • 2018年6月12 星期二 晴 午飯時(shí)檩淋。娘倆邊吃邊聊,說(shuō)她們組今中午升格了萄金。學(xué)了數(shù)學(xué)廣角---推理蟀悦,老師講了...
    朱嘉怡媽媽閱讀 250評(píng)論 0 0
  • 謝娜越來(lái)越漂亮了孙乖,基于這點(diǎn)我開(kāi)始回頭去找謝娜變漂亮的原因浙炼。 事業(yè)順?biāo)旆菅酰蠌V電一姐,有為自己量身定做的節(jié)目弯屈。感情如...
    晚來(lái)Yvonne閱讀 4,201評(píng)論 53 81
  • 乘黃閱讀 146評(píng)論 0 0