精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

React中組件邏輯復用的那些事兒

開發 前端
基本每個開發者都需要考慮邏輯復用的問題,否則你的項目中將充斥著大量的重復代碼。那么 React 是怎么復用組件邏輯的呢?本文將一一介紹 React 復用組件邏輯的幾種方法,希望你讀完之后能夠有所收獲。

 [[326839]]

基本每個開發者都需要考慮邏輯復用的問題,否則你的項目中將充斥著大量的重復代碼。那么 React 是怎么復用組件邏輯的呢?本文將一一介紹 React 復用組件邏輯的幾種方法,希望你讀完之后能夠有所收獲。如果你對這些內容已經非常清楚,那么略過本文即可。

我已盡量對文中的代碼和內容進行了校驗,但是因為自身知識水平限制,難免有錯誤,歡迎在評論區指正。

1. Mixins

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

新的項目中基本不會出現 Mixins,但是如果你們公司還有一些老項目要維護,其中可能就應用了 Mixins,因此稍微花點時間,了解下 Mixins 的使用方法和原理,還是有必要的。倘若你完全沒有這方面的需求,那么跳過本節亦是可以的。

Mixins 的使用

React 15.3.0 版本中增加了 PureComponent。而在此之前,或者如果你使用的是 React.createClass 的方式創建組件,那么想要同樣的功能,就是使用 react-addons-pure-render-mixin,例如: 

  1. //下面代碼在新版React中可正常運行,因為現在已經無法使用 `React.createClass`,我就不使用 `React.createClass` 來寫了。  
  2. const createReactClass = require('create-react-class');  
  3. const PureRenderMixin = require('react-addons-pure-render-mixin');  
  4. const MyDialog = createReactClass({  
  5.     displayName: 'MyDialog',  
  6.     mixins: [PureRenderMixin],  
  7.     //other code  
  8.     render() {  
  9.         return ( 
  10.              <div>  
  11.                 {/* other code */}  
  12.             </div>  
  13.         )  
  14.     }  
  15. }); 

首先,需要注意,mixins 的值是一個數組,如果有多個 Mixins,那么只需要依次放在數組中即可,例如: mixins: [PureRenderMixin, TimerMixin]。

Mixins 的原理

Mixins 的原理可以簡單理解為將一個 mixin 對象上的方法增加到組件上。類似于 $.extend 方法,不過 React 還進行了一些其它的處理,例如:除了生命周期函數外,不同的 mixins 中是不允許有相同的屬性的,并且也不能和組件中的屬性和方法同名,否則會拋出異常。另外即使是生命周期函數,constructor 、render 和 shouldComponentUpdate 也是不允許重復的。

而如 compoentDidMount 的生命周期,會依次調用 Mixins,然后再調用組件中定義的 compoentDidMount。

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

  1. //react-addons-pure-render-mixin 源碼  
  2. var shallowEqual = require('fbjs/lib/shallowEqual');  
  3. module.exports = {  
  4.   shouldComponentUpdate: function(nextProps, nextState) {  
  5.     return (  
  6.       !shallowEqual(this.props, nextProps) ||  
  7.       !shallowEqual(this.state, nextState)  
  8.     );  
  9.   },  
  10. }; 

Mixins 的缺點

  1.  Mixins 引入了隱式的依賴關系。

    例如,每個 mixin 依賴于其他的 mixin,那么修改其中一個就可能破壞另一個。

      2.  Mixins 會導致名稱沖突

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

      3.  Mixins 會導致越來越復雜

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

然后其他人可能需要實現一個提示框,他們不想復制 HoverMixin() 的邏輯,于是他們創建了一個使用 HoverMixin 的 TooltipMixin,TooltipMixin 在它的 componentDidUpdate 中讀取 HoverMixin() 提供的 isHovering() 來決定顯示或隱藏提示框。

幾個月之后,有人想將提示框的方向設置為可配置的。為了避免代碼重復,他們將 getTooltipOptions() 方法增加到了 TooltipMixin 中。結果過了段時間,你需要再同一個組件中顯示多個提示框,提示框不再是懸停時顯示了,或者一些其他的功能,你需要解耦 HoverMixin() 和 TooltipMixin 。另外,如果很多組件使用了某個 mixin,mixin 中新增的功能都會被添加到所有組件中,事實上很多組件完全不需要這些新功能。

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

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

2. HOC

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

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

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

  1. //接受一個組件 WrappedComponent 作為參數,返回一個新組件 Proxy  
  2. function withXXX(WrappedComponent) { 
  3.      return class Proxy extends React.Component {  
  4.         render() {  
  5.             return <WrappedComponent {...this.props}>  
  6.         }  
  7.     }  

開發項目時,當你發現不同的組件有相似的邏輯,或者發現自己在寫重復代碼的時候,這時候就需要考慮組件復用的問題了。

這里我以一個實際開發的例子來說明,近期各大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 版本中提供了 useContext 的 Hook,用起來很方便)

當然,這里我使用高階組件還有一個原因,就是我們的項目中還包含老的 context API (不要問我為什么不直接重構下,牽扯的人員太多了,沒法隨便改),新老 context API 在一個項目中是可以共存的,不過我們不能在同一個組件中同時使用。所以如果一個組件中已經使用的舊的 context API,要想從新的 context API 上獲取值,也需要使用高階組件來處理它。

于是,我編寫了一個 withColorTheme 的高階組件的雛形(這里也可以認為 withColorTheme 是一個返回高階組件的高階函數): 

  1. import ThemeContext from './context';  
  2. function withColorTheme(options={}) {  
  3.     return function(WrappedComponent) {  
  4.         return class ProxyComponent extends React.Component {  
  5.             static contextType = ThemeContext 
  6.             render() {  
  7.                 return (<WrappedComponent {...this.props} colortheme={this.context}/> 
  8.             }  
  9.         }  
  10.     }  

包裝顯示名稱

上面這個雛形存在幾個問題,首先,我們沒有為 ProxyComponent 包裝顯示名稱,因此,為其加上: 

  1. import ThemeContext from './context';  
  2. function withColorTheme(options={}) {  
  3.     return function(WrappedComponent) {  
  4.         class ProxyComponent extends React.Component {  
  5.             static contextType = ThemeContext 
  6.             render() {  
  7.                 return (<WrappedComponent {...this.props} colortheme={this.context}/> 
  8.             }  
  9.         }  
  10.     }  
  11.     function getDisplayName(WrappedComponent) {  
  12.         return WrappedComponent.displayName || WrappedComponent.name || 'Component';  
  13.     }  
  14.     const displayName = `WithColorTheme(${getDisplayName(WrappedComponent)})`;  
  15.     ProxyComponent.displayName = displayName;  
  16.     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 版本。

因此,諸如 childContextTypes、contextType、contextTypes、defaultProps、displayName、getDefaultProps、getDerivedStateFromError、getDerivedStateFromProps

mixins、propTypes、type 等不會被拷貝,其實也比較容易理解,因為 ProxyComponent 中可能也需要設置這些,不能簡單去覆蓋。 

  1. import ThemeContext from './context';  
  2. import hoistNonReactStatics from 'hoist-non-react-statics';  
  3. function withColorTheme(options={}) {  
  4.     return function(WrappedComponent) {  
  5.         class ProxyComponent extends React.Component {  
  6.             static contextType = ThemeContext 
  7.             render() {  
  8.                 return (<WrappedComponent {...this.props} colortheme={this.context}/> 
  9.             }  
  10.         }  
  11.     }  
  12.     function getDisplayName(WrappedComponent) {  
  13.         return WrappedComponent.displayName || WrappedComponent.name || 'Component';  
  14.     }  
  15.    const displayName = `WithColorTheme(${getDisplayName(WrappedComponent)})`;  
  16.     ProxyComponent.displayName = displayName;  
  17.     ProxyComponent.WrappedComponent = WrappedComponent;  
  18.     ProxyComponent.propTypes = WrappedComponent.propTypes;  
  19.     //contextType contextTypes 和 childContextTypes 因為我這里不需要,就不拷貝了  
  20.     return ProxyComponent;  

現在似乎差不多了,不過呢,HOC 還有一個問題,就是 ref 傳遞的問題。如果不經過任何處理,我們通過 ref 拿到的是 ProxyComponent 的實例,而不是原本想要獲取的 WrappedComponent 的實例。

ref 傳遞

雖然我們已經用無關的 props 進行了透傳,但是 key 和 ref 不是普通的 prop,React 會對它進行特別處理。

所以這里我們需要對 ref 特別處理一下。如果你的 reac-dom 是 16.4.2 或者你的 react-native 版本是 0.59.9 以上,那么可以放心的使用 React.forwardRef 進行 ref 轉發,這樣使用起來也是最方便的。

使用 React.forwardRef 轉發 

  1. import ThemeContext from './context';  
  2. import hoistNonReactStatics from 'hoist-non-react-statics';  
  3. function withColorTheme(options={}) {  
  4.     return function(WrappedComponent) {  
  5.         class ProxyComponent extends React.Component {  
  6.             static contextType = ThemeContext 
  7.             render() {  
  8.                 const { forwardRef, ...wrapperProps } = this.props;  
  9.                 return <WrappedComponent {...wrapperProps} ref={forwardRef} colorTheme={ this.context } />  
  10.             }  
  11.         }  
  12.     }  
  13.     function getDisplayName(WrappedComponent) {  
  14.         return WrappedComponent.displayName || WrappedComponent.name || 'Component';  
  15.     }  
  16.     const displayName = `WithColorTheme(${getDisplayName(WrappedComponent)})`;  
  17.     ProxyComponent.displayName = displayName;  
  18.     ProxyComponent.WrappedComponent = WrappedComponent;  
  19.     ProxyComponent.propTypes = WrappedComponent.propTypes;  
  20.     //contextType contextTypes 和 childContextTypes 因為我這里不需要,就不拷貝了  
  21.     if (options.forwardRef) {  
  22.         let forwarded = React.forwardRef((props, ref) => (  
  23.             <ProxyComponent {...props} forwardRef={ref} />  
  24.         ));  
  25.         forwarded.displayName = displayName;  
  26.         forwarded.WrappedComponent = WrappedComponent;  
  27.         forwarded.propTypes = WrappedComponent.propTypes;  
  28.         return hoistNonReactStatics(forwarded, WrappedComponent);  
  29.     } else {  
  30.         return hoistNonReactStatics(ProxyComponent, WrappedComponent);  
  31.     }  

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

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

如果要獲取 WrappedComponent 的實例,直接通過 this.textInput 即可,和未使用 withColorTheme 裝飾前一樣獲取。

通過方法調用 getWrappedInstance  

  1. import ThemeContext from './context';  
  2. import hoistNonReactStatics from 'hoist-non-react-statics';  
  3. function withColorTheme(options={}) {  
  4.     return function(WrappedComponent) {  
  5.         class ProxyComponent extends React.Component {  
  6.             static contextType = ThemeContext
  7.             getWrappedInstance = () => {  
  8.                 if (options.forwardRef) {  
  9.                     return this.wrappedInstance;  
  10.                 }  
  11.             }  
  12.             setWrappedInstance = (ref) => {  
  13.                 this.wrappedInstance = ref 
  14.             }  
  15.             render() {  
  16.                 const { forwardRef, ...wrapperProps } = this.props;  
  17.                 let props = {  
  18.                     ...this.props  
  19.                 };  
  20.                 if (options.forwardRef) {  
  21.                     props.ref = this.setWrappedInstance;  
  22.                 }  
  23.                 return <WrappedComponent {...props} colorTheme={ this.context } />  
  24.             }  
  25.         }  
  26.     }   
  27.     function getDisplayName(WrappedComponent) { 
  28.          return WrappedComponent.displayName || WrappedComponent.name || 'Component';  
  29.     }  
  30.     const displayName = `WithColorTheme(${getDisplayName(WrappedComponent)})`;  
  31.     ProxyComponent.displayName = displayName;  
  32.     ProxyComponent.WrappedComponent = WrappedComponent;  
  33.     ProxyComponent.propTypes = WrappedComponent.propTypes;  
  34.     //contextType contextTypes 和 childContextTypes 因為我這里不需要,就不拷貝了  
  35.     if (options.forwardRef) {  
  36.         let forwarded = React.forwardRef((props, ref) => (  
  37.             <ProxyComponent {...props} forwardRef={ref} />  
  38.         ));  
  39.         forwarded.displayName = displayName;  
  40.         forwarded.WrappedComponent = WrappedComponent;  
  41.         forwarded.propTypes = WrappedComponent.propTypes;  
  42.         return hoistNonReactStatics(forwarded, WrappedComponent);  
  43.     } else {  
  44.         return hoistNonReactStatics(ProxyComponent, WrappedComponent);  
  45.     } 
  46.  

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

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

如果要獲取 WrappedComponent 的實例,那么需要通過 this.textInput.getWrappedInstance() 獲取被包裝組件 TextInput 的實例。

最大化可組合

我先說一下,為什么我將它設計為下面這樣: 

  1. function withColorTheme(options={}) {  
  2.     function(WrappedComponent) {  
  3.     }  

而不是像這樣: 

  1. function withColorTheme(WrappedComponent, options={}) {  

主要是使用裝飾器語法比較方便,而且很多業務中也使用了 react-redux: 

  1. @connect(mapStateToProps, mapDispatchToProps)  
  2. @withColorTheme()  
  3. export default class TextInput extends Component {  
  4.     render() {}  

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

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

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

  1. // ... 你可以編寫組合工具函數  
  2. // compose(f, g, h) 等同于 (...args) => f(g(h(...args)))  
  3. const enhance = compose 
  4.   // 這些都是單參數的 HOC  
  5.   withRouter,  
  6.   connect(commentSelector)  
  7.  
  8. const EnhancedComponent = enhance(WrappedComponent) 

compose 的源碼可以看下 redux 的實現,代碼很短。

再復雜化一下就是: 

  1. withRouter(connect(commentSelector)(withColorTheme(options)(WrappedComponent))); 

我們的 enhance 可以編寫為: 

  1. const enhance = compose 
  2.   withRouter,  
  3.   connect(commentSelector),  
  4.   withColorTheme(options)  
  5.  
  6. const EnhancedComponent = enhance(WrappedComponent) 

如果我們是寫成 XXX(WrappedComponent, options) 的形式的話,那么上面的代碼將變成: 

  1. const EnhancedComponent = withRouter(connect(withColorTheme(WrappedComponent, options), commentSelector))

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

HOC的約定和注意事項

 約定

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

 注意事項

  •  不要在 render 方法中使用 HOC 

React 的 diff 算法(稱為協調)使用組件標識來確定它是應該更新現有子樹還是將其丟棄并掛載新子樹。 如果從 render 返回的組件與前一個渲染中的組件相同(===),則 React 通過將子樹與新子樹進行區分來遞歸更新子樹。 如果它們不相等,則完全卸載前一個子樹。

這不僅僅是性能問題 —— 重新掛載組件會導致該組件及其所有子組件的狀態丟失。

如果在組件之外創建 HOC,這樣一來組件只會創建一次。因此,每次 render 時都會是同一個組件。

  •  務必復制靜態方法
  •  Refs 不會被傳遞(需要額外處理)

3. 反向繼承

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

因此呢,我覺得反向繼承不是 React 推崇的方式,這里我們可以做一下了解,某些場景下也有可能會用到。

  • 反向繼承 
  1. function withColor(WrappedComponent) {  
  2.     class ProxyComponent extends WrappedComponent {  
  3.         //注意 ProxyComponent 會覆蓋 WrappedComponent 的同名函數,包括 state 和 props  
  4.         render() {  
  5.             //React.cloneElement(super.render(), { style: { color:'red' }})  
  6.             return super.render();  
  7.         }  
  8.     }  
  9.     return ProxyComponent;  

和上一節不同,反向繼承不會增加組件的層級,并且也不會有靜態屬性拷貝和 refs 丟失的問題。可以利用它來做渲染劫持,不過我目前沒有什么必須要使用反向繼承的場景。

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

4. render props

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

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

  1. <Route  
  2.     {...rest}  
  3.     render={routeProps => (  
  4.         <FadeIn>  
  5.             <Component {...routeProps} />  
  6.         </FadeIn>  
  7.     )}  
  8. /> 

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

  1. import React, {Component} from 'react';  
  2. import {  
  3.     FlatList,  
  4.     View,  
  5.     Text,  
  6.     TouchableHighlight  
  7. } from 'react-native'; 
  8. class MyList extends Component {  
  9.     data = [{ key: 1, title: 'Hello' }, { key: 2, title: 'World' }]  
  10.     render() {  
  11.         return (  
  12.             <FlatList  
  13.                 style={{marginTop: 60}}  
  14.                 data={this.data}  
  15.                 renderItem={({ item, index }) => {  
  16.                     return (  
  17.                         <TouchableHighlight  
  18.                             onPress={() => { alert(item.title) }}  
  19.                         >  
  20.                             <Text>{item.title}</Text>  
  21.                         </TouchableHighlight>  
  22.                     )  
  23.                 }}  
  24.                 ListHeaderComponent={() => {  
  25.                     return (<Text>以下是一個List</Text> 
  26.                 }}  
  27.                 ListFooterComponent={() => {  
  28.                    return <Text>沒有更多數據</Text>  
  29.                 }}  
  30.             />  
  31.         )  
  32.     }  

例如: FlatList 的 renderItem、ListHeaderComponent 就是render prop。

注意,render prop 是因為模式才被稱為 render prop ,你不一定要用名為 render 的 prop 來使用這種模式。render prop 是一個用于告知組件需要渲染什么內容的函數 prop。

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

  1. //提供一個 renderPage 的 prop  
  2. class Swiper extends React.PureComponent {  
  3.     getPages() {  
  4.         if(typeof renderPage === 'function') {  
  5.             return this.props.renderPage(XX,XXX)  
  6.         }  
  7.     }  
  8.     render() {  
  9.         const pages = typeof renderPage === 'function' ? this.props.renderPage(XX,XXX) : XXXX;  
  10.         return (  
  11.             <View>  
  12.                 <Animated.View>  
  13.                     {pages}  
  14.                 </Animated.View>  
  15.             </View>  
  16.         )  
  17.     }  

注意事項

Render Props 和 React.PureComponent 一起使用時要小心

如果在 render 方法里創建函數,那么 render props,會抵消使用 React.PureComponent 帶來的優勢。因為淺比較 props 的時候總會得到 false,并且在這種情況下每一個 render 對于 render prop 將會生成一個新的值。 

  1. import React from 'react';  
  2. import { View } from 'react-native';  
  3. import Swiper from 'XXX';  
  4. class MySwiper extends React.Component { 
  5.     render() {  
  6.         return (  
  7.             <Swiper   
  8.                 renderPage={(pageDate, pageIndex) => {  
  9.                     return (  
  10.                         <View></View>  
  11.                     )  
  12.                 }}  
  13.             />  
  14.         )         
  15.     }  

這里應該比較好理解,這樣寫,renderPage 每次都會生成一個新的值,很多 React 性能優化上也會提及到這一點。我們可以將 renderPage 的函數定義為實例方法,如下: 

  1. import React from 'react';  
  2. import { View } from 'react-native';  
  3. import Swiper from 'XXX';  
  4. class MySwiper extends React.Component {  
  5.     renderPage(pageDate, pageIndex) {  
  6.         return (  
  7.             <View></View>  
  8.         )  
  9.     }  
  10.     render() {  
  11.         return (  
  12.             <Swiper   
  13.                 renderPage={this.renderPage}  
  14.             />  
  15.         )         
  16.     }  

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

5. Hooks

Hook 是 React 16.8 的新增特性,它可以讓你在不編寫 class 的情況下使用 state 以及其他的 React 特性。HOC 和 render props 雖然都可以

React 已經內置了一些 Hooks,如: useState、useEffect、useContext、useReducer、useCallback、useMemo、useRef 等 Hook,如果你還不清楚這些 Hook,那么可以優先閱讀一下官方文檔。

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

  1. import React, {useEffect} from 'react';  
  2. export default function useLogger(componentName,...params) {  
  3.     useEffect(() => {  
  4.         if(process.env.NODE_ENV === 'development') {  
  5.             console.log(componentName, ...params);  
  6.         }  
  7.     });  

使用時: 

  1. import React, { useState } from 'react';  
  2. import useLogger from './useLogger';  
  3. export default function Counter(props) {  
  4.     let [count, setCount] = useState(0);  
  5.     useLogger('Counter', props);  
  6.     return (  
  7.         <div>  
  8.             <button onClick={() => setCount(count + 1)}>+</button>  
  9.             <p>{`${props.title}, ${count}`}</p>  
  10.         </div>  
  11.     )  

另外,官方文檔自定義 Hook 章節也一步一步演示了如何利用 Hook 來進行邏輯復用。我因為版本限制,還沒有在項目中應用 Hook ,雖然文檔已經看過多次。讀到這里,一般都會有一個疑問,那就是 Hook 是否會替代 render props 和 HOC,關于這一點,官方也給出了答案:

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

HOC 最最最討厭的一點就是層級嵌套了,如果項目是基于新版本進行開發,那么需要邏輯復用時,優先考慮 Hook,如果無法實現需求,那么再使用 render props 和 HOC 來解決。 

 

責任編輯:龐桂玉 來源: segmentfault
相關推薦

2022-05-25 08:27:30

tmux軟件

2022-04-14 11:50:39

函數組件hook

2012-07-19 15:30:00

Linux

2011-02-16 09:57:41

2011-06-08 09:19:26

Android JNI

2023-04-11 07:34:40

分布式系統算法

2021-03-18 09:01:53

軟件開發軟件選型

2022-07-10 07:48:26

緩存軟件設計

2021-06-09 13:28:40

密碼安全身份認證數據安全

2013-12-26 14:23:03

定位系統GPS監測

2022-02-08 17:39:04

MySQL服務器存儲

2011-02-25 14:35:00

2018-09-26 06:50:19

2021-06-02 08:33:31

TPCTPC-H系統

2021-03-09 23:12:51

Python集合項目

2021-02-01 14:17:53

裝飾器外層函數里層函數

2021-10-13 08:53:53

Zookeeper存儲系統

2016-06-07 10:47:42

2010-10-15 10:31:00

2021-09-04 16:12:33

壓縮算法數據
點贊
收藏

51CTO技術棧公眾號

欧美高清视频| 中国a一片一级一片| 91麻豆精品激情在线观看最新| 一区二区三区四区国产精品| 国产一级特黄a大片99| av片免费观看| 欧美国产激情| 亚洲欧美日韩国产成人| 日韩成人精品视频在线观看| av资源中文在线天堂| 欧美精彩视频一区二区三区| 91九色在线免费视频| 天天综合网久久综合网| 久久久久久久久久久久久久| 日韩精品久久久久久福利| 在线免费av播放| 极品视频在线| 亚洲伦在线观看| 日韩中文字幕一区| 人妻无码中文字幕| 国内精品伊人久久久久av一坑| 97久久久久久| 91杏吧porn蝌蚪| 夜夜躁狠狠躁日日躁2021日韩| 欧美一区二区视频在线观看| www.国产区| 国产社区精品视频| 亚洲三级免费电影| 五月天久久综合网| 日韩精品福利| 波多野结衣精品在线| 成人午夜高潮视频| 亚洲午夜无码久久久久| 国产日韩一区二区三区在线播放| 久久亚洲影音av资源网| 亚洲国产日韩一区无码精品久久久| 视频一区中文字幕精品| 欧美日韩精品是欧美日韩精品| 欧美日韩在线视频一区二区三区| 亚洲wwwww| 亚洲欧美日韩在线不卡| 翔田千里亚洲一二三区| 玖玖综合伊人| www激情久久| 精品欧美日韩在线| 亚洲成人777777| 国产乱子伦一区二区三区国色天香| 国产精品免费视频xxxx| 青娱乐在线免费视频| 免费精品视频| 日本成人黄色片| 国产精品777777| 亚洲在线视频| 人九九综合九九宗合| 日韩欧美亚洲视频| 日韩视频在线一区二区三区 | 亚洲午夜一区| 欧美另类老女人| 青草影院在线观看| 国产精品porn| 久久久欧美一区二区| 精品视频一区二区在线观看| 国产精品www.| 久久人人爽国产| 国产午夜激情视频| 日韩一区二区久久| 91豆花精品一区| 无码人妻精品一区二| 日本三级亚洲精品| 国产精品一区二区电影| 国产又粗又猛又黄又爽| 国产高清成人在线| 国产一区精品视频| 国产精品国产高清国产| 国产亚洲午夜高清国产拍精品 | 国产sm在线观看| a级日韩大片| 亚洲男人天堂视频| 青青草华人在线视频| 亚洲情侣在线| 97人人爽人人喊人人模波多| 国产无套丰满白嫩对白| 日本 国产 欧美色综合| 96国产粉嫩美女| 国精产品一品二品国精品69xx| 99久久综合精品| 任我爽在线视频精品一| 免费av网站在线看| 亚洲图片一区二区| 四虎永久在线精品无码视频| 欧美视频在线视频精品| 精品欧美乱码久久久久久| 国产精品无码一区二区三区免费 | 一级黄色大片视频| 久久综合综合久久综合| 国产成人成网站在线播放青青| 亚洲人在线观看视频| 中文字幕不卡在线观看| 国产一区二区三区小说| 亚洲成人av观看| 日韩美女一区二区三区四区| 精品无人区无码乱码毛片国产 | 中文字幕人妻一区二区三区视频| 国产麻豆午夜三级精品| 麻豆久久久av免费| 超碰个人在线| 91电影在线观看| 亚洲 自拍 另类 欧美 丝袜| 美女久久久久| 欧美激情区在线播放| 久久这里只有精品9| caoporen国产精品视频| 在线观看欧美一区| 深夜成人影院| 精品国产麻豆免费人成网站| 国产在线免费av| 久久精品人人| 国产精品美女黄网| 日本三级视频在线观看| 色综合色综合色综合| 欧美图片自拍偷拍| 欧美独立站高清久久| 欧美伊久线香蕉线新在线| 精品久久久无码中文字幕| 欧美激情一区不卡| 日本免费黄视频| 99亚洲乱人伦aⅴ精品| 日韩在线欧美在线| 日韩精品一区不卡| 99久久精品国产一区二区三区| 国产免费xxx| 欧美综合影院| 一区二区福利视频| 潘金莲一级淫片aaaaaa播放| 99久久国产综合精品女不卡| www.国产在线视频| 亚洲午夜免费| 欧美高清无遮挡| 国产精品一区二区黑人巨大| 欧美激情综合在线| 美女福利视频在线| 亚洲+变态+欧美+另类+精品| 国内精品400部情侣激情| 精品黑人一区二区三区在线观看 | 国产91在线看| 成人在线免费高清视频| 视频一区中文字幕精品| 欧美大尺度在线观看| 国产精品高潮呻吟AV无码| 国产精品久久久久久亚洲毛片| 国产成人手机视频| 欧洲激情综合| 国产精品 欧美在线| 欧美日韩视频精品二区| 色综合夜色一区| 国产ts在线播放| 日韩精品免费专区| 偷拍视频一区二区| 亚洲视频资源| 欧美成人久久久| www.国产麻豆| 亚洲一卡二卡三卡四卡| 国产一级黄色录像| 国产精品一二| 天天爽天天狠久久久| 国产欧美自拍| 久久五月天色综合| 高h放荡受浪受bl| 激情懂色av一区av二区av| 亚洲第一页av| 美国三级日本三级久久99| 性欧美18一19内谢| 成人性生交大片免费看中文视频| 91精品国产沙发| www在线免费观看| 7777精品伊人久久久大香线蕉经典版下载| 亚洲xxxx3d动漫| 成人亚洲一区二区一| 男人操女人逼免费视频| 欧美码中文字幕在线| 国产日本欧美视频| av免费在线免费观看| 亚洲第一精品福利| 亚洲第一网站在线观看| 日韩理论片一区二区| 国产国语老龄妇女a片| 麻豆精品91| 国产高清精品软男同| 免费观看成人www动漫视频| 日韩女优在线播放| 国产理论在线观看| 日韩电影网在线| 国产精品久久久久精| 亚洲成av人片| 国产又粗又长又硬| av电影天堂一区二区在线观看| 成人在线观看a| 欧美日韩1区| 亚洲春色在线| 精品资源在线| 91亚洲国产成人精品性色| free性m.freesex欧美| 色偷偷亚洲男人天堂| 乱精品一区字幕二区| 欧美日韩精品一区二区天天拍小说 | 国产特级淫片高清视频| 国产韩国精品一区二区三区| 久久精彩视频| 日韩成人在线看| 国产精品永久免费观看| 男人av在线播放| 久久亚洲精品一区| 91成人高清| 日韩www在线| www.超碰在线.com| 欧美人妖巨大在线| 五月天婷婷导航| 亚洲午夜电影在线| 自拍偷拍第9页| 久久久久久免费网| 五月天丁香社区| 国产专区欧美精品| 久久久久久三级| 亚洲在线观看| 好吊妞无缓冲视频观看| 国产精品豆花视频| 天天综合中文字幕| 欧美性感美女一区二区| 久久久久欧美| 国产亚洲成av人片在线观黄桃| 91亚洲精品在线| 欧美风情在线视频| 国产精品一区二区三区成人| 成人片免费看| 日韩av片免费在线观看| 麻豆mv在线看| 国内伊人久久久久久网站视频| 在线网址91| 久久国产精彩视频| 成人高清免费在线| 精品国内自产拍在线观看| av影片在线看| 最近2019中文字幕在线高清| 东热在线免费视频| 国产亚洲精品日韩| 国产精品一区二区婷婷| 国产午夜精品免费一区二区三区 | 成人性生活视频免费看| 亚洲国产精品久久久天堂| 亚洲精品一品区二品区三品区 | 波多野结衣日韩| 色就色 综合激情| 无码人妻精品一区二区| 色婷婷一区二区三区四区| 久久久久久久久久影院| 欧美日韩免费看| 日本中文字幕第一页| 91久久国产最好的精华液| 亚洲 小说区 图片区| 欧美日韩色综合| 99草在线视频| 精品日韩在线一区| 天天操天天干天天操| 亚洲男人天堂网站| 一区二区三区视频在线观看视频| 综合av色偷偷网| 69成人在线| 国产做受69高潮| 女生影院久久| 成人在线观看视频网站| 亚洲91网站| 国内精品国语自产拍在线观看| 亚洲小说图片| 亚洲一区免费看| 欧美激情无毛| 97在线免费公开视频| 另类中文字幕网| 亚洲视频天天射| 久久综合狠狠综合久久激情| 国产精品理论在线| 一区二区成人在线| jizz国产在线观看| 欧美精品久久99| 日本高清视频网站| 亚洲午夜国产成人av电影男同| 免费网站免费进入在线| 91国产精品91| 全球中文成人在线| 国产精品中出一区二区三区| 欧美美女视频| 日韩中文字幕在线免费| 日韩**一区毛片| 91精品人妻一区二区三区四区| 91麻豆国产香蕉久久精品| 999福利视频| 黑人巨大精品欧美一区免费视频| 一级片视频播放| 亚洲日本成人| 91久久精品国产91性色tv| av手机天堂网| 精品国产麻豆免费人成网站| 东凛在线观看| 97在线免费视频| 成人免费观看49www在线观看| 国产日韩在线一区二区三区| 日韩欧美不卡| 凹凸国产熟女精品视频| 国产乱子伦视频一区二区三区| 亚洲激情视频小说| 亚洲一区二区三区不卡国产欧美| 波多野结衣视频在线看| 日韩av一区二区在线| av免费在线观看网站| 国产精品吹潮在线观看| 色综合久久中文| 国产在线视频综合| 久久国产综合精品| 久操视频免费看| 亚洲一二三级电影| 国产精品一二三四五区| 一区二区三区黄色| 三级中文字幕在线观看| 高清视频在线观看一区| 91精品国产麻豆国产在线观看| 黄色高清无遮挡| 91首页免费视频| 亚洲黄色一区二区| 精品少妇一区二区三区免费观看| 国产原创精品视频| 国产主播喷水一区二区| 欧洲乱码伦视频免费| 国产免费人做人爱午夜视频| www.成人在线| 精品无码人妻一区二区三| 日韩午夜小视频| 在线视频观看国产| 成人黄色生活片| 五月天综合网站| 天天看片天天操| 中文字幕亚洲精品在线观看| 中文字幕资源网| 中文字幕精品av| 国产精品伊人| 国产卡一卡二在线| 狠狠网亚洲精品| 玖玖爱这里只有精品| 欧美一区国产二区| 黄色成人在线观看| 91九色蝌蚪成人| 亚洲图片在线| 免费a v网站| 欧美午夜激情在线| 欧美精品a∨在线观看不卡| 国产www精品| 欧美中文字幕一区二区| 色婷婷成人在线| 亚洲欧洲日产国产综合网| 国产精品视频无码| 欧美日本中文字幕| 久久99国产精品久久99大师| 国产老熟妇精品观看| www日韩大片| 在线观看毛片视频| 久久婷婷国产麻豆91天堂| 91精品国产自产精品男人的天堂| 黄页免费在线观看视频| 久久久久一区二区三区四区| 无码人妻丰满熟妇区五十路| www.亚洲一区| 无码国模国产在线观看| 国产免费黄色一级片| 久久久天堂av| 91福利在线观看视频| 欧美精品在线视频观看| 全国精品免费看| 久久99爱视频| 艳妇臀荡乳欲伦亚洲一区| 天堂网av在线播放| 国产精品麻豆va在线播放| 亚洲深深色噜噜狠狠爱网站| 成人区人妻精品一区二| 色婷婷亚洲婷婷| 91精品国产91久久久久久青草| 狠狠久久综合婷婷不卡| 青青草国产成人av片免费| 国产va在线播放| 亚洲欧美中文另类| 国产95亚洲| 国产精品第12页| 亚洲黄色免费电影| 黄色片在线看| 亚洲999一在线观看www| 香蕉国产精品偷在线观看不卡| 国产三级精品三级观看| 亚洲激情视频网| 国产精品99久久免费| 免费大片在线观看| 亚洲国产婷婷综合在线精品| 永久av在线| 六十路精品视频|