国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

With TypeScript 2.8+ :更好的 React 組件開發(fā)模式

simon_chen / 986人閱讀

摘要:近兩年來一直在關(guān)注開發(fā),最近也開始全面應(yīng)用。首先,我們從無狀態(tài)組件開始。渲染回調(diào)模式有一種重用組件邏輯的設(shè)計方式是把組件的寫成渲染回調(diào)函數(shù)或者暴露一個函數(shù)屬性出來。最后,我們將這個回調(diào)函數(shù)的參數(shù)聲明為一個獨立的類型。

近兩年來一直在關(guān)注 React 開發(fā),最近也開始全面應(yīng)用 TypeScript 。國內(nèi)有很多講解 React 和 TypeScript 的教程,但如何將 TypeScript 更好地應(yīng)用到 React 組件開發(fā)模式的文章卻幾乎沒有(也可能是我沒找到),特別是 TS 的一些新特性,如:條件類型、條件類型中的類型引用等。這些新特性如何應(yīng)用到 React 組件開發(fā)?沒辦法只能去翻一些國外的文章,結(jié)合 TS 的官方文檔慢慢摸索... 于是就有了想法把這個過程整理成文檔。

本文內(nèi)容很長,希望你有個舒服的椅子,我們馬上開始。

所有代碼均使用 React 16.3、TypeScript 2.9 + strict mode 編寫
全部示例代碼都在這里
開始

本文假設(shè)你已經(jīng)對 React、TypeScript 有一定的了解。我不會講到例如:webpack 打包、Babel 轉(zhuǎn)碼、TypeScript 編譯選項這一類的問題,而將一切焦點放在如何將 TS 2.8+ 更好地應(yīng)用到 React 組件設(shè)計模式中。

首先,我們從無狀態(tài)組件開始。

無狀態(tài)組件

無狀態(tài)組件就是沒有 state 的,通常我們也叫做純函數(shù)組件。用原生 JS 我們可以這樣寫一個按鈕組件:

import React from "react";

const Button = ({onClick: handleClick, children}) => (
  
);

如果你把代碼直接放到 .tsx 文件中,tsc 編譯器馬上會提示錯誤:有隱含的 any 類型,因為用了嚴(yán)格模式。我們必須明確的定義組件屬性,修改一下:

import React, { MouseEvent, ReactNode } from "react";

interface Props { 
 onClick(e: MouseEvent): void;
 children?: ReactNode;
};

const Button = ({ onClick: handleClick, children }: Props) => (
  
);

OK,錯誤沒有了!好像已經(jīng)完事了?其實再花點心思可以做的更好。

React 中有個預(yù)定義的類型,SFC

type SFC

= StatelessComponent

;

他是 StatelessComponent 的一個別名,而 StatelessComponent 聲明了純函數(shù)組件的一些預(yù)定義示例屬性和靜態(tài)屬性,如:childrendefaultPropsdisplayName 等,所以我們不需要自己寫所有的東西!

最后我們的代碼是這樣的:

有狀態(tài)的類組件

接著我們來創(chuàng)建一個計數(shù)器按鈕組件。首先我們定義初始狀態(tài):

const initialState = {count: 0};

然后,定義一個別名 State 并用 TS 推斷出類型:

type State = Readonly;
知識點:這樣做不用分開維護接口聲明和實現(xiàn)代碼,比較實用的技巧

同時應(yīng)該注意到,我們將所有的狀態(tài)屬性聲明為 readonly 。然后我們需要明確定義 state 為組件的實例屬性:

readonly state: State = initialState;

為什么要這樣做?我們知道在 React 中我們不能直接改變 State 的屬性值或者 State 本身:

this.state.count = 1; 
this.state = {count: 1};

如果這樣做在運行時將會拋出錯誤,但在編寫代碼時卻不會。所以我們需要明確的聲明 readonly ,這樣 TS 會讓我們知道如果執(zhí)行了這種操作就會出錯了:

下面是完整的代碼:

這個組件不需要外部傳遞任何 Props ,所以泛型的第一個參數(shù)給的是不帶任何屬性的對象

屬性默認(rèn)值

讓我們來擴展一下純函數(shù)按鈕組件,加上一個顏色屬性:

interface Props {
    onClick(e: MouseEvent): void;
    color: string;
}

如果想要定義屬性默認(rèn)值的話,我們知道可以通過 Button.defaultProps = {...} 做到。并且我們需要把這個屬性聲明為可選屬性:(注意屬性名后的 ?

interface Props {
    onClick(e: MouseEvent): void;
    color?: string;
}

那么組件現(xiàn)在看起來是這樣的:

const Button: SFC = ({onClick: handleClick, color, children}) => (
    
);

一切看起來好像都很簡單,但是這里有一個“痛點”。注意我們使用了 TS 的嚴(yán)格模式,color?: string 這個可選屬性的類型現(xiàn)在是聯(lián)合類型 -- string | undefined

這意味著什么?如果你要對這種屬性進行一些操作,比如:substr() ,TS 編譯器會直接報錯,因為類型有可能是 undefined ,TS 并不知道屬性默認(rèn)值會由 Component.defaultProps 來創(chuàng)建。

碰到這種情況我們一般用兩種方式來解決:

使用類型斷言手動去除,添加 ! 后綴,像這樣:color!.substr(...)

使用條件判斷或者三元操作符讓 TS 編譯器知道這個屬性不是 undefined,比如: if (color) ...

以上的方式雖然可以工作但有種多此一舉的感覺,畢竟默認(rèn)值已經(jīng)有了只是 TS 編譯器“不知道”而已。下面來說一種可重用的方案:我們寫一個 withDefaultProps 函數(shù),利用 TS 2.8 的條件類型映射,可以很簡單的完成:

這里涉及到兩個 type 定義,寫在 src/types/global.d.ts 文件里面:

declare type DiffPropertyNames =
    { [P in T]: P extends U ? never: P }[T];

declare type Omit = Pick>;

看一下 TS 2.8 的新特性說明 關(guān)于 Conditional Types 的說明,就知道這兩個 type 的原理了。

注意 TS 2.9 的新變化:keyof T 的類型是 string | number | symbol  的結(jié)構(gòu)子類型。

現(xiàn)在我們可以利用 withDefaultProps 函數(shù)來寫一個有屬性默認(rèn)值的組件了:

現(xiàn)在使用這個組件時默認(rèn)值屬性已經(jīng)發(fā)生作用,是可選的;并且在組件內(nèi)部使用這些默認(rèn)值屬性不用再手動斷言了,這些默認(rèn)值屬性就是必填屬性!感覺還不錯對吧

withDefautProps 函數(shù)同樣可以應(yīng)用在 stateful 有狀態(tài)的類組件上。
渲染回調(diào)模式

有一種重用組件邏輯的設(shè)計方式是:把組件的 children 寫成渲染回調(diào)函數(shù)或者暴露一個 render 函數(shù)屬性出來。我們將用這種思路來做一個折疊面板的場景應(yīng)用。

首先我們先寫一個 Toggleable 組件,完整的代碼如下:

下面我們來逐段解釋下這段代碼,首先先看到組件的屬性聲明相關(guān)部分:

type Props = Partial<{
    children: RenderCallback;
    render: RenderCallback;
}>;

type RenderCallback = (args: ToggleableRenderArgs) => React.ReactNode;

type ToggleableRenderArgs = {
    show: boolean;
    toggle: Toggleable["toggle"];
}

我們需要同時支持 childrenrender 函數(shù)屬性,所以這兩個要聲明為可選的屬性。注意這里用了 Partial 映射類型,這樣就不需要每個手動 ? 操作符來聲明可選了。

為了保持 DRY 原則(Don"t repeat yourself?),我們還聲明了 RenderCallback 類型。

最后,我們將這個回調(diào)函數(shù)的參數(shù)聲明為一個獨立的類型:ToggleableRenderArgs

注意我們使用了 TS 的查找類型lookup types ),這樣 toggle 的類型將和類中定義的同名方法類型保持一致:

private toggle = (event: MouseEvent) => {
    this.setState(prevState => ({show: !prevState.show}));
};
同樣是為了 DRY ,TS 非常給力!

接下來是 State 相關(guān)的:

const initialState = {show: false};
type State = Readonly;

這個沒什么特別的,跟前面的例子一樣。

剩下的部分就是 渲染回調(diào) 設(shè)計模式了,代碼很好理解:

class Toggleable extends Component {

    // ...

    render() {
        const {children, render} = this.props;
        const {show} = this.state;
        const renderArgs = {show, toggle: this.toggle};

        if (render) {
            return render(renderArgs);
        } else if (isFunction(children)) {
            return children(renderArgs);
        } else {
            return null;
        }
    }

    // ...
}

現(xiàn)在我們可以將 children 作為一個渲染函數(shù)傳遞給 Toggleable 組件:

或者將渲染函數(shù)傳遞給 render 屬性:

下面我們來完成折疊面板剩下的工作,先寫一個 Panel 組件來重用 Toggleable 的邏輯:

最后寫一個 Collapse 組件來完成這個應(yīng)用:

這里我們不談樣式的事情,運行起來看看,跟期待的效果是否一致?

這種方式對于需要擴展渲染內(nèi)容時非常有用:Toggleable 組件并不知道也不關(guān)心具體的渲染內(nèi)容,但他控制著顯示狀態(tài)邏輯!
組件注入模式

為了使組件邏輯更具伸縮性,下面我們來說說組件注入模式。

那么什么是組件注入模式呢?如果你用過 React-Router ,你已經(jīng)使用過這種模式來定義路由了:

不同于渲染回調(diào)模式,我們使用 component 屬性“注入”一個組件。為了演示這個模式是如何工作的,我們將重構(gòu)折疊面板這個場景,首先寫一個可重用的 PanelItem 組件:

import { ToggleableComponentProps } from "./Toggleable";

type PanelItemProps = { title: string };

const PanelItem: SFC = props => {
    const {title, children, show, toggle} = props;

    return (
        

{title}

{show ? children : null}
); };

然后重構(gòu) Toggleable 組件:加入新的 component 屬性。對比先頭的代碼,我們需要做出如下變化:

children 屬性類型更改為 function 或者 ReactNode(當(dāng)使用 component 屬性時)

component 屬性將傳遞一個組件注入進去,這個注入組件的屬性定義上需要有 ToggleableComponentProps (其實是原來的 ToggleableRenderArgs ,還記得嗎?)

還需要定義一個 props 屬性,這個屬性將用來傳遞注入組件需要的屬性值。我們會設(shè)置 props 可以擁有任意的屬性,因為我們并不知道注入組件會有哪些屬性,當(dāng)然這樣我們會丟失 TS 的嚴(yán)格類型檢查...

const defaultInjectedProps = {props: {} as { [propName: string]: any }};
type DefaultInjectedProps = typeof defaultInjectedProps;
type Props = Partial<{
    children: RenderCallback | ReactNode;
    render: RenderCallback;
    component: ComponentType>
} & DefaultInjectedProps>;

下一步我們把原來的 ToggleableRenderArgs 修改為 ToggleableComponentProps ,允許將注入組件需要的屬性通過 這樣來傳遞:

type ToggleableComponentProps

= { show: boolean; toggle: Toggleable["toggle"]; } & P;

現(xiàn)在我們還需要重構(gòu)一下 render 方法:

render() {
    const {component: InjectedComponent, children, render, props} = this.props;
    const {show} = this.state;
    const renderProps = {show, toggle: this.toggle};

    if (InjectedComponent) {
        return (
            
                {children}
            
        );
    }

    if (render) {
        return render(renderProps);
    } else if (isFunction(children)) {
        return children(renderProps);
    } else {
        return null;
    }
}

我們已經(jīng)完成了整個 Toggleable 組件的修改,下面是完整的代碼:

最后我們寫一個 PanelViaInjection 組件來應(yīng)用組件注入模式:

import React, { SFC } from "react";
import { Toggleable } from "./Toggleable";
import { PanelItemProps, PanelItem } from "./PanelItem";

const PanelViaInjection: SFC = ({title, children}) => (
    
        {children}
    
);
注意:props 屬性沒有類型安全檢查,因為他被定義為了包含任意屬性的可索引類型:
{ [propName: string]: any }

現(xiàn)在我們可以利用這種方式來重現(xiàn)折疊面板場景了:

class Collapse extends Component {

    render() {
        return (
            

內(nèi)容1

內(nèi)容2

內(nèi)容3

); } }
泛型組件

在組件注入模式的例子中,props 屬性丟失了類型安全檢查,我們?nèi)绾稳バ迯?fù)這個問題呢?估計你已經(jīng)猜出來了,我們可以把 Toggleable 組件重構(gòu)為泛型組件!

下來我們開始重構(gòu) Toggleable 組件。首先我們需要讓 props 支持泛型:

type DefaultInjectedProps

= { props: P }; const defaultInjectedProps: DefaultInjectedProps = {props: {}}; type Props

= Partial<{ children: RenderCallback | ReactNode; render: RenderCallback; component: ComponentType> } & DefaultInjectedProps

>;

然后讓 Toggleable 的 class 也支持泛型:

class Toggleable extends Component, State> {}

看起來好像已經(jīng)搞定了!如果你是用的 TS 2.9,可以直接這樣用:

const PanelViaInjection: SFC = ({title, children}) => (
      component={PanelItem} props={{title}}>
         {children}
     
);

但是如果 <= TS 2.8 ... JSX 里面不能直接應(yīng)用泛型參數(shù) 那么我們還有一步工作要做,加入一個靜態(tài)方法 ofType ,用來進行構(gòu)造函數(shù)的類型轉(zhuǎn)換:

static ofType() {
    return Toggleable as Constructor>;
}

這里用到一個 type:Constructor,依然定義在 src/types/global.d.ts 里面:

declare type Constructor = { new(...args: any[]): T };

好了,我們完成了所有的工作,下面是 Toggleable 重構(gòu)后的完整代碼:

現(xiàn)在我們來看看怎么使用這個泛型組件,重構(gòu)下原來的 PanelViaInjection 組件:

import React, { SFC } from "react";
import { Toggleable } from "./Toggleable";
import { PanelItemProps, PanelItem } from "./PanelItem";

const ToggleableOfPanelItem = Toggleable.ofType();

const PanelViaInjection: SFC = ({title, children}) => (
    
        {children}
    
);

所有的功能都能像原來的代碼一樣工作,并且現(xiàn)在 props 屬性也支持 TS 類型檢查了,很棒有木有!

高階組件

最后我們來看下 HOC 。前面我們已經(jīng)實現(xiàn)了 Toggleable 的渲染回調(diào)模式,那么很自然的我們可以衍生出一個 HOC 組件。

如果對 HOC 不熟悉的話,可以先看下 React 官方文檔對于 HOC 的說明。

先來看看定義 HOC 我們需要做哪些工作:

displayName (方便在 devtools 里面進行調(diào)試)

WrappedComponent (可以訪問原始的組件 -- 有利于調(diào)試)

引入 hoist-non-react-statics 包,將原始組件的靜態(tài)方法全部“復(fù)制”到 HOC 組件上

下面直接上代碼 -- withToggleable 高階組件:

現(xiàn)在我們來用 HOC 重寫一個 Panel :

import { PanelItem } from "./PanelItem";
import withToggleable from "./withToggleable";

const PanelViaHOC = withToggleable(PanelItem);

然后,又可以實現(xiàn)折疊面板了

class Collapse extends Component {

    render() {
        return (
            

內(nèi)容1

內(nèi)容2

); } }
尾聲

感謝能堅持看完的朋友,你們真的很棒!
所有的示例代碼都在 這里 ,如果覺得還不錯幫忙給個 star

最后,感謝 Anders?Hejlsberg 和所有的 TS 貢獻者

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/95362.html

相關(guān)文章

  • FCC 成都社區(qū)·前端周刊 第 6 期

    摘要:詳情發(fā)布新版本中可以自動修復(fù)和合并沖突的文件,還新增了命令。詳情是一個用構(gòu)建設(shè)計系統(tǒng)的開源工具,提供了一套基礎(chǔ)應(yīng)用程序開發(fā)的工具,模式和實踐。目前,只有和的最新版本支持該屬性。詳情每周一同步更新到歡迎 01. JS 引擎 V8 v6.6 的更新 最新 v6.6 版本的 V8 JavaScript 引擎更新了方法 Function.prototype.toString(),改進了代碼緩存...

    Airy 評論0 收藏0
  • FCC 成都社區(qū)·前端周刊 第 6 期

    摘要:詳情發(fā)布新版本中可以自動修復(fù)和合并沖突的文件,還新增了命令。詳情是一個用構(gòu)建設(shè)計系統(tǒng)的開源工具,提供了一套基礎(chǔ)應(yīng)用程序開發(fā)的工具,模式和實踐。目前,只有和的最新版本支持該屬性。詳情每周一同步更新到歡迎 01. JS 引擎 V8 v6.6 的更新 最新 v6.6 版本的 V8 JavaScript 引擎更新了方法 Function.prototype.toString(),改進了代碼緩存...

    Jiavan 評論0 收藏0
  • FCC 成都社區(qū)·前端周刊 第 6 期

    摘要:詳情發(fā)布新版本中可以自動修復(fù)和合并沖突的文件,還新增了命令。詳情是一個用構(gòu)建設(shè)計系統(tǒng)的開源工具,提供了一套基礎(chǔ)應(yīng)用程序開發(fā)的工具,模式和實踐。目前,只有和的最新版本支持該屬性。詳情每周一同步更新到歡迎 01. JS 引擎 V8 v6.6 的更新 最新 v6.6 版本的 V8 JavaScript 引擎更新了方法 Function.prototype.toString(),改進了代碼緩存...

    honhon 評論0 收藏0
  • typescript - 一種思維方式

    摘要:怎么影響了我的思考方式對前端開發(fā)者來說,能強化了面向接口編程這一理念。使用的過程就是在加深理解的過程,確實面向接口編程天然和靜態(tài)類型更為親密。 電影《降臨》中有一個觀點,語言會影響人的思維方式,對于前端工程師來說,使用 typescript 開發(fā)無疑就是在嘗試換一種思維方式做事情。 其實直到最近,我才開始系統(tǒng)的學(xué)習(xí) typescript ,前后大概花了一個月左右的時間。在這之前,我也在...

    CKJOKER 評論0 收藏0
  • TypeScript - 一種思維方式

    摘要:怎么影響了我的思考方式對前端開發(fā)者來說,能強化了面向接口編程這一理念。使用的過程就是在加深理解的過程,確實面向接口編程天然和靜態(tài)類型更為親密。摘要: 學(xué)會TS思考方式。 原文:TypeScript - 一種思維方式 作者:zhangwang Fundebug經(jīng)授權(quán)轉(zhuǎn)載,版權(quán)歸原作者所有。 電影《降臨》中有一個觀點,語言會影響人的思維方式,對于前端工程師來說,使用 typescript 開...

    noONE 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<