国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

文字

高階組件(HOC)是React中用于重用組件邏輯的高級(jí)技術(shù)。HOC本身不是React API的一部分。它們是從React的構(gòu)圖本質(zhì)中浮現(xiàn)出來(lái)的一種模式。

具體而言,高階組件是一個(gè)接收組件并返回新組件的函數(shù)。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

盡管組件將道具轉(zhuǎn)換為UI,但高階組件會(huì)將組件轉(zhuǎn)換為另一個(gè)組件。

HOC在第三方React庫(kù)中很常見(jiàn),例如Redux connect和Relay createContainer。

在本文中,我們將討論為什么高階組件有用,以及如何編寫自己的。

針對(duì)交叉問(wèn)題使用HOC

注意   我們以前推薦mixin作為處理交叉問(wèn)題的一種方式。之后我們意識(shí)到mixin會(huì)造成比他們的價(jià)值更大的麻煩。了解更多關(guān)于我們?yōu)槭裁措x開(kāi)mixin以及如何轉(zhuǎn)換現(xiàn)有組件的更多信息。

組件是React中代碼重用的主要單元。但是,您會(huì)發(fā)現(xiàn)某些模式不適合傳統(tǒng)組件。

例如,假設(shè)您有一個(gè)CommentList組件訂閱外部數(shù)據(jù)源來(lái)呈現(xiàn)評(píng)論列表:

class CommentList extends React.Component {  constructor(props) {    super(props);    
        this.handleChange = this.handleChange.bind(this);    
        this.state = {      // "DataSource" is some global data source
      comments: DataSource.getComments()    };  
      }  componentDidMount() {    // Subscribe to changes
                DataSource.addChangeListener(this.handleChange);  }  componentWillUnmount() {    // Clean up listener
                            DataSource.removeChangeListener(this.handleChange);  }  handleChange() {    // Update component state whenever the data source changes    
                                this.setState({
                                  comments: DataSource.getComments()    });  }  render() {    
                                      return (      <div>        {this.state.comments.map((comment) => (          
                                          <Comment comment={comment} key={comment.id} />        ))}      </div>    );  
                                          }}

之后,您將編寫一個(gè)組件訂閱單個(gè)博客帖子,該帖子遵循類似的模式:

class BlogPost extends React.Component {  constructor(props) {    super(props);    
this.handleChange = this.handleChange.bind(this);    this.state = {
      blogPost: DataSource.getBlogPost(props.id)    };  }  componentDidMount() {
    DataSource.addChangeListener(this.handleChange);  }  componentWillUnmount() {
    DataSource.removeChangeListener(this.handleChange);  }  handleChange() {    this.setState({
      blogPost: DataSource.getBlogPost(this.props.id)    });  }  render() {    return <TextBlock text={this.state.blogPost} />;  }}

CommentList并且BlogPost不完全相同 - 它們調(diào)用不同的方法DataSource,并且它們呈現(xiàn)不同的輸出。但是他們的大部分實(shí)現(xiàn)都是一樣的:

  • 在mount上,添加一個(gè)更改監(jiān)聽(tīng)器DataSource。

  • 在監(jiān)聽(tīng)器內(nèi)部,setState每當(dāng)數(shù)據(jù)源發(fā)生變化時(shí)都會(huì)調(diào)用。

  • 在卸載時(shí),刪除更改偵聽(tīng)器。

你可以想象,在一個(gè)大型應(yīng)用程序中,訂閱DataSource和調(diào)用的相同模式setState將會(huì)一遍又一遍地發(fā)生。我們需要一種抽象,使我們能夠在單個(gè)地方定義這種邏輯,并在多個(gè)組件之間共享這些邏輯。這是高階元件擅長(zhǎng)的地方。

我們可以編寫一個(gè)創(chuàng)建組件的函數(shù),比如CommentListBlogPost訂閱DataSource。該函數(shù)將接受作為其參數(shù)之一的接收訂閱數(shù)據(jù)作為道具的子組件。我們來(lái)調(diào)用這個(gè)函數(shù)withSubscription

const CommentListWithSubscription = withSubscription(
  CommentList,  (DataSource) => DataSource.getComments());const BlogPostWithSubscription = withSubscription(
  BlogPost,  (DataSource, props) => DataSource.getBlogPost(props.id));

第一個(gè)參數(shù)是包裝組件。第二個(gè)參數(shù)檢索我們感興趣的數(shù)據(jù),給出一個(gè)DataSource和當(dāng)前的道具。

當(dāng)CommentListWithSubscriptionBlogPostWithSubscription被渲染,CommentList并且BlogPost將傳遞一個(gè)data與從檢索到的最新的數(shù)據(jù)道具DataSource

// This function takes a component...
    function withSubscription(WrappedComponent, selectData) {  // ...and returns another component...  
        return class extends React.Component {    constructor(props) {      super(props);      
        this.handleChange = this.handleChange.bind(this);      this.state = {
        data: selectData(DataSource, props)      };    }    componentDidMount() {      // ... that takes care of the subscription...
      DataSource.addChangeListener(this.handleChange);    }    componentWillUnmount() {
      DataSource.removeChangeListener(this.handleChange);    }    handleChange() {      this.setState({
        data: selectData(DataSource, this.props)      });    }    render() {      // ... and renders the wrapped component with the fresh data!      
        // Notice that we pass through any additional props      return <WrappedComponent data={this.state.data} {...this.props} />;    }  };}

請(qǐng)注意,HOC不會(huì)修改輸入組件,也不會(huì)使用繼承來(lái)復(fù)制其行為。相反,HOC 通過(guò)包裝在容器組件中來(lái)組成原始組件。HOC是一種純粹的功能,具有零副作用。

就是這樣!被包裝的組件接收容器的所有道具以及一個(gè)新的道具,data它用來(lái)渲染其輸出。HOC不關(guān)心如何或?yàn)槭裁词褂脭?shù)據(jù),并且封裝的組件不關(guān)心數(shù)據(jù)來(lái)自何處。

因?yàn)?code>withSubscription是一個(gè)正常的函數(shù),所以你可以添加盡可能多或者很少的參數(shù)。例如,您可能希望使dataprop 的名稱可配置,以進(jìn)一步將HOC與封裝組件隔離?;蛘吣梢越邮芘渲玫膮?shù)shouldComponentUpdate,或者配置數(shù)據(jù)源的參數(shù)。這些都是可能的,因?yàn)镠OC完全控制組件的定義。

與組件一樣,合約withSubscription與包裝組件之間的合約完全基于道具。這可以很容易地將一個(gè)HOC換成另一個(gè)HOC,只要它們?yōu)榘b組件提供相同的道具。例如,如果您更改數(shù)據(jù)提取庫(kù),這可能很有用。

不要改變?cè)冀M件。使用構(gòu)圖。

抵制HOC內(nèi)部修改組件原型(或者改變它)的誘惑。

function logProps(InputComponent) {
  InputComponent.prototype.componentWillReceiveProps = function(nextProps) {
    console.log('Current props: ', this.props);
    console.log('Next props: ', nextProps);  };  // The fact that we're returning the original input is a hint that it has  
    // been mutated.  return InputComponent;}
    // EnhancedComponent will log whenever props are receivedconst EnhancedComponent = logProps(InputComponent);

這有幾個(gè)問(wèn)題。一個(gè)是輸入組件不能與增強(qiáng)組件分開(kāi)重復(fù)使用。更關(guān)鍵的是,如果你申請(qǐng)的另一個(gè)HOC到EnhancedComponent那個(gè)發(fā)生變異componentWillReceiveProps,第一HOC的功能將被改寫!這個(gè)HOC也不能用于沒(méi)有生命周期方法的函數(shù)組件。

突變HOC是一個(gè)漏洞抽象 - 消費(fèi)者必須知道它們是如何實(shí)施的,以避免與其他HOC發(fā)生沖突。

通過(guò)將輸入組件包裝在容器組件中,HOC不應(yīng)該使用變異,而應(yīng)該使用組合:

function logProps(WrappedComponent) {  return class extends React.Component {    componentWillReceiveProps(nextProps) {
      console.log('Current props: ', this.props);
      console.log('Next props: ', nextProps);    }    render() {      
          // Wraps the input component in a container, without mutating it. Good!     
           return <WrappedComponent {...this.props} />;    }  }}

這個(gè)HOC具有與變種版本相同的功能,同時(shí)避免了沖突的可能性。它與類和功能組件一樣有效。而且因?yàn)樗且粋€(gè)純粹的功能,它可以與其他HOC組合,甚至可以與其自身組合。

您可能已經(jīng)注意到HOC和稱為容器組件的模式之間的相似之處。集裝箱組件是在高層和低層關(guān)注點(diǎn)之間分離責(zé)任戰(zhàn)略的一部分。容器管理諸如訂閱和狀態(tài)之類的東西,并將道具傳遞給處理諸如呈現(xiàn)UI之類的事物的組件。HOC使用容器作為其實(shí)施的一部分。您可以將HOC視為參數(shù)化容器組件定義。

約定:將不相關(guān)道具傳遞給包裝組件

HOC向組件添加功能。他們不應(yīng)該大幅改變合同。預(yù)計(jì)從HOC返回的組件具有與被包裝組件類似的接口。

HOC應(yīng)該通過(guò)與其特定關(guān)注無(wú)關(guān)的道具。大多數(shù)HOC包含一個(gè)類似于下面的渲染方法:

render() {  // Filter out extra props that are specific to this HOC and shouldn't be  
    // passed through  const { extraProp, ...passThroughProps } = this.props;  
    // Inject props into the wrapped component. These are usually state values or  
    // instance methods.  const injectedProp = someStateOrInstanceMethod;  
    // Pass props to wrapped component  return (    <WrappedComponent
      injectedProp={injectedProp}      {...passThroughProps}    />  );
      }

此慣例有助于確保HOC盡可能靈活且可重用。

約定:最大化組合性

并非所有HOC看起來(lái)都一樣。有時(shí)他們只接受一個(gè)參數(shù),包裝組件:

const NavbarWithRouter = withRouter(Navbar);

HOC通常會(huì)接受其他參數(shù)。在Relay的這個(gè)例子中,一個(gè)配置對(duì)象被用來(lái)指定一個(gè)組件的數(shù)據(jù)依賴關(guān)系:

const CommentWithRelay = Relay.createContainer(Comment, config);

HOC最常見(jiàn)的簽名如下所示:

// React Redux's `connect`const ConnectedComment = connect(commentSelector, commentActions)(CommentList);

什么?!如果你把它分開(kāi),很容易看到發(fā)生了什么。

// connect is a function that returns another functionconst enhance = connect(commentListSelector, commentListActions);
// The returned function is an HOC, which returns a component that is connected
// to the Redux storeconst ConnectedComment = enhance(CommentList);

換句話說(shuō),connect是一個(gè)返回高階組件的高階函數(shù)!

這種形式可能看起來(lái)很混亂或不必要,但它有一個(gè)有用的特性。單參數(shù)HOC(如connect函數(shù)返回的HOC)具有簽名Component => Component。輸出類型與輸入類型相同的函數(shù)非常容易組合在一起。

// Instead of doing this...const EnhancedComponent = withRouter(connect(commentSelector)(WrappedComponent))
// ... you can use a function composition utility
// compose(f, g, h) is the same as (...args) => f(g(h(...args)))const enhance = compose(  
    // These are both single-argument HOCs
  withRouter,  connect(commentSelector))const EnhancedComponent = enhance(WrappedComponent)

(這個(gè)屬性也允許使用connect其他增強(qiáng)器樣式的HOC作為裝飾器,這是一個(gè)實(shí)驗(yàn)性JavaScript提案。)

所述compose效用函數(shù)是由許多第三方庫(kù)包括lodash(如提供lodash.flowRight),終極版,和Ramda。

約定:包裝顯示名稱以便于調(diào)試

由HOC創(chuàng)建的容器組件像任何其他組件一樣出現(xiàn)在React Developer Tools中。為了便于調(diào)試,選擇一個(gè)顯示名稱來(lái)傳達(dá)它是HOC的結(jié)果。

最常用的技術(shù)是封裝包裝組件的顯示名稱。因此,如果您的高階組件被命名withSubscription,并且包裝組件的顯示名稱是CommentList,則使用顯示名稱WithSubscription(CommentList)

function withSubscription(WrappedComponent) {  class WithSubscription extends React.Component {/* ... */}
  WithSubscription.displayName = `WithSubscription(${getDisplayName(WrappedComponent)})`;  
      return WithSubscription;}function getDisplayName(WrappedComponent) {  
          return WrappedComponent.displayName || WrappedComponent.name || 'Component';
          }

注意事項(xiàng)

如果您是React的新手,那么高階組件會(huì)附帶一些注意事項(xiàng),這些注意事項(xiàng)不會(huì)立即顯現(xiàn)出來(lái)。

不要在render方法中使用HOC

React的差異算法(稱為reconciliation)使用組件標(biāo)識(shí)來(lái)確定它是應(yīng)該更新現(xiàn)有的子樹(shù)還是將其丟棄并掛載新的子樹(shù)。如果返回的組件與來(lái)自先前渲染的組件render相同(===),則React通過(guò)用新組件區(qū)分它來(lái)遞歸更新子樹(shù)。如果不相等,則前一個(gè)子樹(shù)完全卸載。

通常情況下,你不需要考慮這一點(diǎn)。但是它對(duì)于HOC很重要,因?yàn)樗馕吨銦o(wú)法將HOC應(yīng)用到組件的渲染方法中的組件:

render() {  // A new version of EnhancedComponent is created on every render  
    // EnhancedComponent1 !== EnhancedComponent2  const EnhancedComponent = enhance(MyComponent);  
    // That causes the entire subtree to unmount/remount each time!  return <EnhancedComponent />;
    }

這里的問(wèn)題不僅僅是性能 - 重新安裝組件會(huì)導(dǎo)致組件及其所有子組件的狀態(tài)丟失。

相反,在組件定義之外應(yīng)用HOC,以便只生成一次結(jié)果組件。那么,它的身份將在整個(gè)渲染過(guò)程中保持一致。無(wú)論如何,這通常是你想要的。

在您需要?jiǎng)討B(tài)應(yīng)用HOC的罕見(jiàn)情況下,您也可以在組件的生命周期方法或其構(gòu)造函數(shù)中執(zhí)行此操作。

必須復(fù)制靜態(tài)方法

有時(shí)在React組件上定義靜態(tài)方法很有用。例如,中繼容器公開(kāi)了一個(gè)靜態(tài)方法getFragment來(lái)促進(jìn)GraphQL片段的組合。

但是,如果將HOC應(yīng)用于組件,則原始組件將使用容器組件進(jìn)行包裝。這意味著新組件沒(méi)有任何原始組件的靜態(tài)方法。

// Define a static methodWrappedComponent.staticMethod = function() {/*...*/}
// Now apply an HOCconst EnhancedComponent = enhance(WrappedComponent);
// The enhanced component has no static methodtypeof EnhancedComponent.staticMethod === 'undefined' 
// true

為了解決這個(gè)問(wèn)題,你可以在返回之前將這些方法復(fù)制到容器中:

function enhance(WrappedComponent) {  class Enhance extends React.Component {/*...*/}  
// Must know exactly which method(s) to copy :(
  Enhance.staticMethod = WrappedComponent.staticMethod;  return Enhance;}

但是,這需要您確切地知道需要復(fù)制哪些方法。您可以使用hoist-non-react-statics來(lái)自動(dòng)復(fù)制所有非React靜態(tài)方法:

import hoistNonReactStatic from 'hoist-non-react-statics';function enhance(WrappedComponent) {  class Enhance extends React.Component {/*...*/}  
hoistNonReactStatic(Enhance, WrappedComponent);  return Enhance;}

另一種可能的解決方案是將靜態(tài)方法與組件本身分開(kāi)導(dǎo)出。

// Instead of...MyComponent.someFunction = someFunction;export default MyComponent;
// ...export the method separately...export { someFunction };
// ...and in the consuming module, import bothimport MyComponent, { someFunction } from './MyComponent.js';

Refs沒(méi)有通過(guò)

雖然高階組件的慣例是將所有道具傳遞給包裝組件,但不可能通過(guò)參考。這是因?yàn)?code>ref它不是一個(gè)真正的道具key,它是由React專門處理的。如果將ref添加到其組件是HOC結(jié)果的元素,則ref引用最外層容器組件的實(shí)例,而不是包裝組件。

如果你發(fā)現(xiàn)自己面臨這個(gè)問(wèn)題,理想的解決方案是找出如何避免使用ref。偶爾,剛剛接觸React范例的用戶依賴于在支撐物更好地工作的情況下的參考。

也就是說(shuō),有些時(shí)候refs是必要的逃生艙口,否則React不會(huì)支持它們。聚焦輸入字段是一個(gè)例子,您可能需要對(duì)組件進(jìn)行必要的控制。在這種情況下,一種解決方案是通過(guò)給它一個(gè)不同的名稱來(lái)傳遞一個(gè)ref回調(diào)作為普通道具:

function Field({ inputRef, ...rest }) {  return <input ref={inputRef} {...rest} />;}
// Wrap Field in a higher-order componentconst EnhancedField = enhance(Field);
// Inside a class component's render method...<EnhancedField
  inputRef={(inputEl) => {    // This callback gets passed through as a regular prop    
      this.inputEl = inputEl  }}/>// Now you can call imperative methodsthis.inputEl.focus();

這不是一個(gè)完美的解決方案。我們更喜歡參考資料仍然是圖書館關(guān)注的問(wèn)題,而不是要求您手動(dòng)處理它們。我們正在探索解決這個(gè)問(wèn)題的方法,以便使用HOC是不可觀測(cè)的。

上一篇: 下一篇: