首頁  >  文章  >  web前端  >  分解React組件的幾種進階方法

分解React組件的幾種進階方法

小云云
小云云原創
2018-02-09 16:40:362224瀏覽

React 元件魔力無窮,同時彈性超強。我們可以在組件的設計上,玩出很多花樣。但是保證組件的Single responsibility principle: 單一原則非常重要,它可以使得我們的組件更簡單、更方便維護,更重要的是使得組件更加具有復用性。本文主要和大家分享分解React 元件的幾種進階方法,希望能幫助大家。

但是,如何對一個功能複雜且臃腫的 React 元件進行分解,也許並不是一件簡單的事情。本文由淺入深,介紹三個分解 React 元件的方法。

方法一:切割 render() 方法

這是一個最容易想到的方法:當一個元件渲染了許多元素時,就需要嘗試分離這些元素的渲染邏輯。最迅速的方式就是切割 render() 方法為多個 sub-render 方法。

看下面的例子會更直觀:

class Panel extends React.Component {
    renderHeading() {        // ...
    }

    renderBody() {        // ...
    }

    render() {        return (
            <div>
                {this.renderHeading()}
                {this.renderBody()}
            </div>
        );
    }
}

細心的讀者很快就能發現,其實這並沒有分解組件本身,該Panel 組件仍然保持有原先的state, props,以及class 方法。

如何真正做到減少元件複雜度呢?我們需要創建一些子組件。此時,採用最新版 React 支援並推薦的函數式元件/無狀態元件一定會是一個很好的嘗試:

const PanelHeader = (props) => (    // ...);const PanelBody = (props) => (    // ...);class Panel extends React.Component {
    render() {        return (
            <div>                // Nice and explicit about which props are used
                <PanelHeader title={this.props.title}/>
                <PanelBody content={this.props.content}/>
            </div>
        );
    }
}

同之前的方式相比,這個微妙的改進是革命性的。

我們新建了兩個單元組件:PanelHeader 和 PanelBody。這樣帶來了測試的便利,我們可以直接分開測試不同的組件。同時,借助於 React 新的演算法引擎 React Fiber,兩個單元元件在渲染的效率上,樂觀地預期會有較大幅度的提升。

方法二:模版化元件

回到問題的起點,為什麼一個元件會變的臃腫而複雜呢?其一是渲染元素較多且嵌套,另外就是元件內部變化較多,或存在多種 configurations 的情況。

此時,我們便可以將元件改造為模版:父元件類似一個模版,只專注於各種 configurations。

還是要舉例來說,這樣理解起來更加清晰。

例如我們有一個 Comment 元件,這個元件存在多種行為或事件。

同時元件所展現的資訊會根據使用者的身分有所變化:

  • 使用者是否是此comment 的作者;

  • #此comment 是否正確保存;

  • 各種權限不同

  • 等等......

都會造成這個元件的不同展示行為。

這時候,與其把所有的邏輯混淆在一起,也許更好的做法是利用React 可以傳遞React element 的特性,我們將React element 進行組件間傳遞,這樣就更加像一個強大的模版:

class CommentTemplate extends React.Component {
    static propTypes = {        // Declare slots as type node
        metadata: PropTypes.node,
        actions: PropTypes.node,
    };

    render() {        return (            <div>                <CommentHeading>                    <Avatar user={...}/>

                    // Slot for metadata                    <span>{this.props.metadata}</span>                </CommentHeading>                <CommentBody/>                <CommentFooter>                    <Timestamp time={...}/>

                    // Slot for actions                    <span>{this.props.actions}</span>                </CommentFooter>            </div>
            ...
        )
    }
}

此時,我們真正的Comment 元件組織為:

class Comment extends React.Component {
    render() {        const metadata = this.props.publishTime ?        <PublishTime time={this.props.publishTime} /> :        <span>Saving...</span>;        const actions = [];        if (this.props.isSignedIn) {
            actions.push(<LikeAction />);
            actions.push(<ReplyAction />);
        }
        if (this.props.isAuthor) {
            actions.push(<DeleteAction />);
        }

        return <CommentTemplate metadata={metadata} actions={actions} />;
    }
}

metadata 和actions 其實就是在特定情況下需要渲染的React element。

例如:

  • 如果this.props.publishTime 存在,metadata 就是;

  • 反之則為Saving...

  • 如果使用者已經登陸,則需要渲染(即actions值為) ;

  • #如果是作者本身,需要渲染的內容就要加入

方法三:高階元件

在實際開發當中,元件常會被其他需求污染。

想想像這樣一個場景:我們想統計頁面中所有連結的點擊資訊。在連結點擊時,發送統計請求,同時這條請求需要包含此頁面 document 的 id 值。

常見的做法是在Document 元件的生命週期函數componentDidMount 和componentWillUnmount 增加程式碼邏輯:

class Document extends React.Component {
    componentDidMount() {
        ReactDOM.findDOMNode(this).addEventListener('click', this.onClick);
    }

    componentWillUnmount() {
        ReactDOM.findDOMNode(this).removeEventListener('click', this.onClick);
    }

    onClick = (e) => {        // Naive check for <a> elements        if (e.target.tagName === 'A') { 
            sendAnalytics('link clicked', {                // Specific information to be sent
                documentId: this.props.documentId 
            });
        }
    };

    render() {        // ...
    }
}

這麼做的幾個問題在於:

  • #相關元件Document 除了本身的主要邏輯:顯示主頁面之外,多了其他統計邏輯;

  • #如果Document 元件的生命週期函數中,還有其他邏輯,那麼這個元件就會變的更含糊不合理;

  • 統計邏輯程式碼無法重複使用;

  • 元件重構、維護都會變的更困難。

為了解決這個問題,我們提出了高階元件這個概念: higher-order components (HOCs)。不去晦澀地解釋這個名詞,我們來直接看看使用高階元件如何來重構上面的程式碼:

function withLinkAnalytics(mapPropsToData, WrappedComponent) {    class LinkAnalyticsWrapper extends React.Component {
        componentDidMount() {
            ReactDOM.findDOMNode(this).addEventListener('click', this.onClick);
        }

        componentWillUnmount() {
            ReactDOM.findDOMNode(this).removeEventListener('click', this.onClick);
        }

        onClick = (e) => {            // Naive check for <a> elements            if (e.target.tagName === 'A') { 
                const data = mapPropsToData ? mapPropsToData(this.props) : {};
                sendAnalytics('link clicked', data);
            }
        };

        render() {            // Simply render the WrappedComponent with all props            return <WrappedComponent {...this.props} />;
        }
    }
    ...
}

要注意的是,withLinkAnalytics 函數並不會去改變WrappedComponent 元件本身,更不會去改變WrappedComponent 元件的行為。而是返回了一個被包裹的新組件。實際用法為:

class Document extends React.Component {
    render() {        // ...
    }
}

export default withLinkAnalytics((props) => ({
    documentId: props.documentId
}), Document);

這樣一來,Document 元件仍只需關心自己該關心的部分,而 withLinkAnalytics 則賦予了復用統計邏輯的能力。

高階元件的存在,完美地展示了 React 天生的複合(compositional)能力,在 React 社群當中,react-redux,styled-components,react-intl 等都普遍採用了這個方式。值得一提的是,recompose 類別庫又利用高階元件,並發揚光大,做到了「腦洞大開」的事。

React 及其周邊社區的崛起,讓函數式程式設計風靡一時,受到追捧。其中關於 decomposing 和 composing 的思想,我認為非常值得學習。同時,對開發設計的一個建議是,一般情況下,不要猶豫將你的組件拆分的更小、更單一,因為這樣能換來強健和復用。

相關推薦:

React元件生命週期實例分析

#建構React元件最全方法

store優化React元件的方法詳解

以上是分解React組件的幾種進階方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn