React組件邏輯複用的那些事兒(Mixins -> HOC+render props -> Hooks)

基本每一個開發者都須要考慮邏輯複用的問題,不然你的項目中將充斥着大量的重複代碼。那麼 React 是怎麼複用組件邏輯的呢?本文將一一介紹 React 複用組件邏輯的幾種方法,但願你讀完以後可以有所收穫。若是你對這些內容已經很是清楚,那麼略過本文便可。html

我已儘可能對文中的代碼和內容進行了校驗,可是由於自身知識水平限制,不免有錯誤,歡迎在評論區指正。react

1. Mixins

Mixins 事實上是 React.createClass 的產物了。固然,若是你曾經在低版本的 react 中使用過 Mixins,例如 react-timer-mixinreact-addons-pure-render-mixin,那麼你可能知道,在 React 的新版本中咱們其實仍是可使用 mixin,雖然 React.createClass 已經被移除了,可是仍然可使用第三方庫 create-react-class,來繼續使用 mixin。甚至,ES6 寫法的組件,也一樣有方式去使用 mixin。固然啦,這不是本文討論的重點,就很少作介紹了,若是你維護的老項目在升級的過程當中遇到這類問題,能夠與我探討。git

新的項目中基本不會出現 Mixins,可是若是大家公司還有一些老項目要維護,其中可能就應用了 Mixins,所以稍微花點時間,瞭解下 Mixins 的使用方法和原理,仍是有必要的。假若你徹底沒有這方面的需求,那麼跳過本節亦是能夠的。github

Mixins 的使用

React 15.3.0 版本中增長了 PureComponent。而在此以前,或者若是你使用的是 React.createClass 的方式建立組件,那麼想要一樣的功能,就是使用 react-addons-pure-render-mixin,例如:算法

//下面代碼在新版React中可正常運行,由於如今已經沒法使用 `React.createClass`,我就不使用 `React.createClass` 來寫了。

const createReactClass = require('create-react-class');
const PureRenderMixin = require('react-addons-pure-render-mixin');

const MyDialog = createReactClass({
    displayName: 'MyDialog',
    mixins: [PureRenderMixin],
    //other code
    render() {
        return (
            <div> {/* other code */} </div>
        )
    }
});
複製代碼

首先,須要注意,mixins 的值是一個數組,若是有多個 Mixins,那麼只須要依次放在數組中便可,例如: mixins: [PureRenderMixin, TimerMixin]redux

Mixins 的原理

Mixins 的原理能夠簡單理解爲將一個 mixin 對象上的方法增長到組件上。相似於 $.extend 方法,不過 React 還進行了一些其它的處理,例如:除了生命週期函數外,不一樣的 mixins 中是不容許有相同的屬性的,而且也不能和組件中的屬性和方法同名,不然會拋出異常。另外即便是生命週期函數,constructorrendershouldComponentUpdate 也是不容許重複的。react-native

而如 compoentDidMount 的生命週期,會依次調用 Mixins,而後再調用組件中定義的 compoentDidMount設計模式

例如,上面的 PureRenderMixin 提供的對象中,有一個 shouldComponentUpdate 方法,便是將這個方法增長到了 MyDialog 上,此時 MyDialog 中不能再定義 shouldComponentUpdate,不然會拋出異常。數組

//react-addons-pure-render-mixin 源碼
var shallowEqual = require('fbjs/lib/shallowEqual');

module.exports = {
  shouldComponentUpdate: function(nextProps, nextState) {
    return (
      !shallowEqual(this.props, nextProps) ||
      !shallowEqual(this.state, nextState)
    );
  },
};
複製代碼

Mixins 的缺點

  1. Mixins 引入了隱式的依賴關係。性能優化

    例如,每一個 mixin 依賴於其餘的 mixin,那麼修改其中一個就可能破壞另外一個。

  2. Mixins 會致使名稱衝突

    若是兩個 mixin 中存在同名方法,就會拋出異常。另外,假設你引入了一個第三方的 mixin,該 mixin 上的方法和你組件的方法名發生衝突,你就不得不對方法進行重命名。

  3. Mixins 會致使愈來愈複雜

    mixin 開始的時候是簡單的,可是隨着時間的推移,容易變得愈來愈複雜。例如,一個組件須要一些狀態來跟蹤鼠標懸停,爲了保持邏輯的可重用性,將 handleMouseEnter()handleMouseLeave()isHovering() 提取到 HoverMixin() 中。

    而後其餘人可能須要實現一個提示框,他們不想複製 HoverMixin() 的邏輯,因而他們建立了一個使用 HoverMixinTooltipMixinTooltipMixin 在它的 componentDidUpdate 中讀取 HoverMixin() 提供的 isHovering() 來決定顯示或隱藏提示框。

    幾個月以後,有人想將提示框的方向設置爲可配置的。爲了不代碼重複,他們將 getTooltipOptions() 方法增長到了 TooltipMixin 中。結果過了段時間,你須要再同一個組件中顯示多個提示框,提示框再也不是懸停時顯示了,或者一些其餘的功能,你須要解耦 HoverMixin()TooltipMixin 。另外,若是不少組件使用了某個 mixinmixin 中新增的功能都會被添加到全部組件中,事實上不少組件徹底不須要這些新功能。

    漸漸地,封裝的邊界被侵蝕了,因爲很難更改或移除現有的mixin,它們變得愈來愈抽象,直到沒有人理解它們是如何工做的。

React 官方認爲在 React 代碼庫中,Mixin 是沒必要要的,也是有問題的。推薦開發者使用高階組件來進行組件邏輯的複用。

2. HOC

React 官方文檔對 HOC 進行了以下的定義:高階組件(HOC)是 React 中用於複用組件邏輯的一種高級技巧。HOC 自身不是 React API 的一部分,它是一種基於 React 的組合特性而造成的設計模式。

簡而言之,高階組件就是一個函數,它接受一個組件爲參數,返回一個新組件。

高階組件的定義形以下面這樣:

//接受一個組件 WrappedComponent 做爲參數,返回一個新組件 Proxy
function withXXX(WrappedComponent) {
    return class Proxy extends React.Component {
        render() {
            return <WrappedComponent {...this.props}> } } } 複製代碼

開發項目時,當你發現不一樣的組件有類似的邏輯,或者發現本身在寫重複代碼的時候,這時候就須要考慮組件複用的問題了。

這裏我以一個實際開發的例子來講明,近期各大APP都在適配暗黑模式,而暗黑模式下的背景色、字體顏色等等和正常模式確定是不同的。那麼就須要監聽暗黑模式開啓關閉事件,每一個UI組件都須要根據當前的模式來設置樣式。

每一個組件都去監聽事件變化來 setState 確定是不可能的,由於會形成屢次渲染。

這裏咱們須要藉助 context API 來作,我以新的 Context API 爲例。若是使用老的 context API 實現該功能,須要使用發佈訂閱模式來作,最後利用 react-native / react-dom 提供的 unstable_batchedUpdates 來統一更新,避免屢次渲染的問題(老的 context API 在值發生變化時,若是組件中 shouldComponentUpdate 返回了 false,那麼它的子孫組件就不會從新渲染了)。

順便多說一句,不少新的API出來的時候,不要急着在項目中使用,好比新的 Context API,若是你的 react 版本是 16.3.1, react-dom 版本是16.3.3,你會發現,當你的子組件是函數組件時,便是用 Context.Consumer 的形式時,你是能獲取到 context 上的值,而你的組件是個類組件時,你根本拿不到 context 上的值。

一樣的 React.forwardRef 在該版本食用時,某種狀況下也有屢次渲染的bug。都是血和淚的教訓,很少說了,繼續暗黑模式這個需求。

個人想法是將當前的模式(假設值爲 light / dark)掛載到 context 上。其它組件直接從 context 上獲取便可。不過咱們知道的是,新版的 ContextAPI 函數組件和類組件,獲取 context 的方法是不一致的。並且一個項目中有很是多的組件,每一個組件都進行一次這樣的操做,也是重複的工做量。因而,高階組件就派上用場啦(PS:React16.8 版本中提供了 useContextHook,用起來很方便)

固然,這裏我使用高階組件還有一個緣由,就是咱們的項目中還包含老的 context API (不要問我爲何不直接重構下,牽扯的人員太多了,無法隨便改),新老 context API 在一個項目中是能夠共存的,不過咱們不能在同一個組件中同時使用。因此若是一個組件中已經使用的舊的 context API,要想重新的 context API 上獲取值,也須要使用高階組件來處理它。

因而,我編寫了一個 withColorTheme 的高階組件的雛形(這裏也能夠認爲 withColorTheme 是一個返回高階組件的高階函數):

import ThemeContext from './context';
function withColorTheme(options={}) {
    return function(WrappedComponent) {
        return class ProxyComponent extends React.Component {
            static contextType = ThemeContext;
            render() {
                return (<WrappedComponent {...this.props} colortheme={this.context}/>) } } } } 複製代碼

包裝顯示名稱

上面這個雛形存在幾個問題,首先,咱們沒有爲 ProxyComponent 包裝顯示名稱,所以,爲其加上:

import ThemeContext from './context';

function withColorTheme(options={}) {
    return function(WrappedComponent) {
        class ProxyComponent extends React.Component {
            static contextType = ThemeContext;
            render() {
                return (<WrappedComponent {...this.props} colortheme={this.context}/>) } } } function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; } const displayName = `WithColorTheme(${getDisplayName(WrappedComponent)})`; ProxyComponent.displayName = displayName; return ProxyComponent; } 複製代碼

咱們來看一下,不包裝顯示名稱和包裝顯示名稱的區別:

React Developer Tools 中調試

ReactNative的紅屏報錯

複製靜態方法

衆所周知,使用 HOC 包裝組件,須要複製靜態方法,若是你的 HOC 僅僅是某幾個組件使用,沒有靜態方法須要拷貝,或者須要拷貝的靜態方法是肯定的,那麼你手動處理一下也能夠。

由於 withColorTheme 這個高階組件,最終是要提供給不少業務使用的,沒法限制別人的組件寫法,所以這裏咱們必須將其寫得通用一些。

hoist-non-react-statics 這個依賴能夠幫助咱們自動拷貝非 React 的靜態方法,這裏有一點須要注意,它只會幫助你拷貝非 React 的靜態方法,而非被包裝組件的全部靜態方法。我第一次使用這個依賴的時候,沒有仔細看,覺得是將 WrappedComponent 上全部的靜態方法都拷貝到 ProxyComponent。而後就遇到了 XXX.propsTypes.style undefined is not an object 的紅屏報錯(ReactNative調試)。由於我沒有手動拷貝 propTypes,錯誤的覺得 hoist-non-react-statics 會幫我處理了。

hoist-non-react-statics 的源碼很是短,有興趣的話,能夠看一下,我當前使用的 3.3.2 版本。

WechatIMG1440.png

所以,諸如 childContextTypescontextTypecontextTypesdefaultPropsdisplayNamegetDefaultPropsgetDerivedStateFromErrorgetDerivedStateFromProps mixinspropTypestype 等不會被拷貝,其實也比較容易理解,由於 ProxyComponent 中可能也須要設置這些,不能簡單去覆蓋。

import ThemeContext from './context';
import hoistNonReactStatics from 'hoist-non-react-statics';
function withColorTheme(options={}) {
    return function(WrappedComponent) {
        class ProxyComponent extends React.Component {
            static contextType = ThemeContext;
            render() {
                return (<WrappedComponent {...this.props} colortheme={this.context}/>) } } } function getDisplayName(WrappedComponent) { return WrappedComponent.displayName || WrappedComponent.name || 'Component'; } const displayName = `WithColorTheme(${getDisplayName(WrappedComponent)})`; ProxyComponent.displayName = displayName; ProxyComponent.WrappedComponent = WrappedComponent; ProxyComponent.propTypes = WrappedComponent.propTypes; //contextType contextTypes 和 childContextTypes 由於我這裏不須要,就不拷貝了 return ProxyComponent; } 複製代碼

如今彷佛差很少了,不過呢,HOC 還有一個問題,就是 ref 傳遞的問題。若是不通過任何處理,咱們經過 ref 拿到的是 ProxyComponent 的實例,而不是本來想要獲取的 WrappedComponent 的實例。

ref 傳遞

雖然咱們已經用無關的 props 進行了透傳,可是 keyref 不是普通的 propReact 會對它進行特別處理。

因此這裏咱們須要對 ref 特別處理一下。若是你的 reac-dom16.4.2 或者你的 react-native 版本是 0.59.9 以上,那麼能夠放心的使用 React.forwardRef 進行 ref 轉發,這樣使用起來也是最方便的。

使用 React.forwardRef 轉發

import ThemeContext from './context';
import hoistNonReactStatics from 'hoist-non-react-statics';
function withColorTheme(options={}) {
    return function(WrappedComponent) {
        class ProxyComponent extends React.Component {
            static contextType = ThemeContext;
            render() {
                const { forwardRef, ...wrapperProps } = this.props;
                return <WrappedComponent {...wrapperProps} ref={forwardRef} colorTheme={ this.context } />
            }
        }
    }
    function getDisplayName(WrappedComponent) {
        return WrappedComponent.displayName || WrappedComponent.name || 'Component';
    }
    const displayName = `WithColorTheme(${getDisplayName(WrappedComponent)})`;
    ProxyComponent.displayName = displayName;
    ProxyComponent.WrappedComponent = WrappedComponent;
    ProxyComponent.propTypes = WrappedComponent.propTypes;
    //contextType contextTypes 和 childContextTypes 由於我這裏不須要,就不拷貝了
    if (options.forwardRef) {
        let forwarded = React.forwardRef((props, ref) => (
            <ProxyComponent {...props} forwardRef={ref} />
        ));
        forwarded.displayName = displayName;
        forwarded.WrappedComponent = WrappedComponent;
        forwarded.propTypes = WrappedComponent.propTypes;
        return hoistNonReactStatics(forwarded, WrappedComponent);
    } else {
        return hoistNonReactStatics(ProxyComponent, WrappedComponent);
    }
}
複製代碼

假設,咱們對 TextInput 進行了裝飾,如 export default withColorTheme({forwardRef: true})(TextInput)

使用: <TextInput ref={v => this.textInput = v}>

若是要獲取 WrappedComponent 的實例,直接經過 this.textInput 便可,和未使用 withColorTheme 裝飾前同樣獲取。

經過方法調用 getWrappedInstance

import ThemeContext from './context';
import hoistNonReactStatics from 'hoist-non-react-statics';
function withColorTheme(options={}) {
    return function(WrappedComponent) {
        class ProxyComponent extends React.Component {
            static contextType = ThemeContext;

            getWrappedInstance = () => {
                if (options.forwardRef) {
                    return this.wrappedInstance;
                }
            }

            setWrappedInstance = (ref) => {
                this.wrappedInstance = ref;
            }

            render() {
                const { forwardRef, ...wrapperProps } = this.props;
                let props = {
                    ...this.props
                };

                if (options.forwardRef) {
                    props.ref = this.setWrappedInstance;
                }
                return <WrappedComponent {...props} colorTheme={ this.context } />
            }
        }
    }
    
    function getDisplayName(WrappedComponent) {
        return WrappedComponent.displayName || WrappedComponent.name || 'Component';
    }

    const displayName = `WithColorTheme(${getDisplayName(WrappedComponent)})`;
    ProxyComponent.displayName = displayName;
    ProxyComponent.WrappedComponent = WrappedComponent;
    ProxyComponent.propTypes = WrappedComponent.propTypes;
    //contextType contextTypes 和 childContextTypes 由於我這裏不須要,就不拷貝了
    if (options.forwardRef) {
        let forwarded = React.forwardRef((props, ref) => (
            <ProxyComponent {...props} forwardRef={ref} />
        ));
        forwarded.displayName = displayName;
        forwarded.WrappedComponent = WrappedComponent;
        forwarded.propTypes = WrappedComponent.propTypes;
        return hoistNonReactStatics(forwarded, WrappedComponent);
    } else {
        return hoistNonReactStatics(ProxyComponent, WrappedComponent);
    }
}
複製代碼

一樣的,咱們對 TextInput 進行了裝飾,如 export default withColorTheme({forwardRef: true})(TextInput)

使用: <TextInput ref={v => this.textInput = v}>

若是要獲取 WrappedComponent 的實例,那麼須要經過 this.textInput.getWrappedInstance() 獲取被包裝組件 TextInput 的實例。

最大化可組合

我先說一下,爲何我將它設計爲下面這樣:

function withColorTheme(options={}) {
    function(WrappedComponent) {

    }
}
複製代碼

而不是像這樣:

function withColorTheme(WrappedComponent, options={}) {
}
複製代碼

主要是使用裝飾器語法比較方便,並且不少業務中也使用了 react-redux

@connect(mapStateToProps, mapDispatchToProps)
@withColorTheme()
export default class TextInput extends Component {
    render() {}
}
複製代碼

這樣設計,能夠不破壞本來的代碼結構。不然的話,本來使用裝飾器語法的業務改起來就有點麻煩。

迴歸到最大化可組合,看看官方文檔怎麼說:

connect(react-redux 提供) 函數返回的單參數 HOC 具備簽名 Component => Component。輸出類型與輸入類型相同的函數很容易組合在一塊兒。

// ... 你能夠編寫組合工具函數
// compose(f, g, h) 等同於 (...args) => f(g(h(...args)))
const enhance = compose(
  // 這些都是單參數的 HOC
  withRouter,
  connect(commentSelector)
)
const EnhancedComponent = enhance(WrappedComponent)
複製代碼

compose 的源碼能夠看下 redux 的實現,代碼很短。

再複雜化一下就是:

withRouter(connect(commentSelector)(withColorTheme(options)(WrappedComponent)));
複製代碼

咱們的 enhance 能夠編寫爲:

const enhance = compose(
  withRouter,
  connect(commentSelector),
  withColorTheme(options)
)
const EnhancedComponent = enhance(WrappedComponent)
複製代碼

若是咱們是寫成 XXX(WrappedComponent, options) 的形式的話,那麼上面的代碼將變成:

const EnhancedComponent = withRouter(connect(withColorTheme(WrappedComponent, options), commentSelector))
複製代碼

試想一下,若是還有更多的 HOC 要使用,這個代碼會變成什麼樣子?

HOC的約定和注意事項

約定

  • 將不相關的 props 傳遞給被包裹的組件(HOC應透傳與自身無關的 props)
  • 最大化可組合性
  • 包裝顯示名稱以便輕鬆調試

注意事項

  • 不要在 render 方法中使用 HOC

Reactdiff 算法(稱爲協調)使用組件標識來肯定它是應該更新現有子樹仍是將其丟棄並掛載新子樹。 若是從 render 返回的組件與前一個渲染中的組件相同(===),則 React 經過將子樹與新子樹進行區分來遞歸更新子樹。 若是它們不相等,則徹底卸載前一個子樹。

這不只僅是性能問題 —— 從新掛載組件會致使該組件及其全部子組件的狀態丟失。

若是在組件以外建立 HOC,這樣一來組件只會建立一次。所以,每次 render 時都會是同一個組件。

  • 務必複製靜態方法
  • Refs 不會被傳遞(須要額外處理)

3. 反向繼承

React 官方文檔上有這樣一段描述: HOC 不會修改傳入的組件,也不會使用繼承來複制其行爲。相反,HOC 經過將組件包裝在容器組件中來組成新組件。HOC 是純函數,沒有反作用。

所以呢,我以爲反向繼承不是 React 推崇的方式,這裏咱們能夠作一下了解,某些場景下也有可能會用到。

反向繼承
function withColor(WrappedComponent) {
    class ProxyComponent extends WrappedComponent {
        //注意 ProxyComponent 會覆蓋 WrappedComponent 的同名函數,包括 state 和 props
        render() {
            //React.cloneElement(super.render(), { style: { color:'red' }})
            return super.render();
        }
    }
    return ProxyComponent;
}
複製代碼

和上一節不一樣,反向繼承不會增長組件的層級,而且也不會有靜態屬性拷貝和 refs 丟失的問題。能夠利用它來作渲染劫持,不過我目前沒有什麼必需要使用反向繼承的場景。

雖然它沒有靜態屬性和 refs的問題,也不會增長層級,可是它也不是那麼好用,會覆蓋同名屬性和方法這點就讓人很無奈。另外雖然能夠修改渲染結果,可是很差注入 props

4. render props

首先, render props 是指一種在 React 組件之間使用一個值爲函數的 prop 共享代碼的簡單技術。

具備 render prop 的組件接受一個函數,該函數返回一個 React 元素並調用它而不是實現本身的渲染邏輯。

<Route
    {...rest}
    render={routeProps => (
        <FadeIn> <Component {...routeProps} /> </FadeIn> )} /> 複製代碼

ReactNative 的開發者,其實 render props 的技術使用的不少,例如,FlatList 組件:

import React, {Component} from 'react';
import {
    FlatList,
    View,
    Text,
    TouchableHighlight
} from 'react-native';

class MyList extends Component {
    data = [{ key: 1, title: 'Hello' }, { key: 2, title: 'World' }]
    render() {
        return (
            <FlatList style={{marginTop: 60}} data={this.data} renderItem={({ item, index }) => { return ( <TouchableHighlight onPress={() => { alert(item.title) }} > <Text>{item.title}</Text> </TouchableHighlight> ) }} ListHeaderComponent={() => { return (<Text>如下是一個List</Text>) }} ListFooterComponent={() => { return <Text>沒有更多數據</Text> }} /> ) } } 複製代碼

例如: FlatListrenderItemListHeaderComponent 就是render prop

注意,render prop 是由於模式才被稱爲 render prop ,你不必定要用名爲 renderprop 來使用這種模式。render prop 是一個用於告知組件須要渲染什麼內容的函數 prop

其實,咱們在封裝組件的時候,也常常會應用到這個技術,例如咱們封裝一個輪播圖組件,可是每一個頁面的樣式是不一致的,咱們能夠提供一個基礎樣式,可是也要容許自定義,不然就沒有通用價值了:

//提供一個 renderPage 的 prop
class Swiper extends React.PureComponent {
    getPages() {
        if(typeof renderPage === 'function') {
            return this.props.renderPage(XX,XXX)
        }
    }
    render() {
        const pages = typeof renderPage === 'function' ? this.props.renderPage(XX,XXX) : XXXX;
        return (
            <View> <Animated.View> {pages} </Animated.View> </View> ) } } 複製代碼

注意事項

Render PropsReact.PureComponent 一塊兒使用時要當心

若是在 render 方法裏建立函數,那麼 render props,會抵消使用 React.PureComponent 帶來的優點。由於淺比較 props 的時候總會獲得 false,而且在這種狀況下每個 render 對於 render prop 將會生成一個新的值。

import React from 'react';
import { View } from 'react-native';
import Swiper from 'XXX';
class MySwiper extends React.Component {
    render() {
        return (
            <Swiper renderPage={(pageDate, pageIndex) => { return ( <View></View> ) }} /> ) } } 複製代碼

這裏應該比較好理解,這樣寫,renderPage 每次都會生成一個新的值,不少 React 性能優化上也會說起到這一點。咱們能夠將 renderPage 的函數定義爲實例方法,以下:

import React from 'react';
import { View } from 'react-native';
import Swiper from 'XXX';
class MySwiper extends React.Component {
    renderPage(pageDate, pageIndex) {
        return (
            <View></View>
        )
    }
    render() {
        return (
            <Swiper renderPage={this.renderPage} /> ) } } 複製代碼

若是你沒法靜態定義 prop,則 <Swiper> 應該擴展 React.Component,由於也沒有淺比較的必要了,就不要浪費時間去比較了。

5. Hooks

HookReact 16.8 的新增特性,它可讓你在不編寫 class 的狀況下使用 state 以及其餘的 React 特性。HOCrender props 雖然均可以

React 已經內置了一些 Hooks,如: useStateuseEffectuseContextuseReduceruseCallbackuseMemouseRefHook,若是你還不清楚這些 Hook,那麼能夠優先閱讀一下官方文檔。

咱們主要是將如何利用 Hooks 來進行組件邏輯複用。假設,咱們有這樣一個需求,在開發環境下,每次渲染時,打印出組件的 props

import React, {useEffect} from 'react';

export default function useLogger(componentName,...params) {
    useEffect(() => {
        if(process.env.NODE_ENV === 'development') {
            console.log(componentName, ...params);
        }
    });
}
複製代碼

使用時:

import React, { useState } from 'react';
import useLogger from './useLogger';

export default function Counter(props) {
    let [count, setCount] = useState(0);
    useLogger('Counter', props);
    return (
        <div> <button onClick={() => setCount(count + 1)}>+</button> <p>{`${props.title}, ${count}`}</p> </div>
    )
}
複製代碼

另外,官方文檔自定義 Hook 章節也一步一步演示瞭如何利用 Hook 來進行邏輯複用。我由於版本限制,尚未在項目中應用 Hook ,雖然文檔已經看過屢次。讀到這裏,通常都會有一個疑問,那就是 Hook 是否會替代 render propsHOC,關於這一點,官方也給出了答案:

一般,render props 和高階組件只渲染一個子節點。咱們認爲讓 Hook 來服務這個使用場景更加簡單。這兩種模式仍有用武之地,例如,FlatList 組件的 renderItem 等屬性,或者是 一個可見的容器組件或許會有它本身的 DOM 結構。但在大部分場景下,Hook 足夠了,而且可以幫助減小嵌套。

HOC 最最最討厭的一點就是層級嵌套了,若是項目是基於新版本進行開發,那麼須要邏輯複用時,優先考慮 Hook,若是沒法實現需求,那麼再使用 render propsHOC 來解決。

參考連接

最後,若是方便的話,點個Star鼓勵: github.com/YvetteLau/B…

相關文章
相關標籤/搜索