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

characters

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

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

const EnhancedComponent = higherOrderComponent(WrappedComponent);

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

HOC在第三方React庫中很常見,例如Redux connect和Relay createContainer

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

針對交叉問題使用HOC

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

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

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

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>    );  
                                          }}

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

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)不同的輸出。但是他們的大部分實現(xiàn)都是一樣的:

  • 在mount上,添加一個更改監(jiān)聽器DataSource

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

  • 在卸載時,刪除更改偵聽器。

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

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

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

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

CommentListWithSubscriptionBlogPostWithSubscription被渲染,CommentList并且BlogPost將傳遞一個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} />;    }  };}

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

就是這樣!被包裝的組件接收容器的所有道具以及一個新的道具,data它用來渲染其輸出。HOC不關(guān)心如何或為什么使用數(shù)據(jù),并且封裝的組件不關(guān)心數(shù)據(jù)來自何處。

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

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

不要改變原始組件。使用構(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);

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

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

通過將輸入組件包裝在容器組件中,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} />;    }  }}

這個HOC具有與變種版本相同的功能,同時避免了沖突的可能性。它與類和功能組件一樣有效。而且因為它是一個純粹的功能,它可以與其他HOC組合,甚至可以與其自身組合。

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

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

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

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

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看起來都一樣。有時他們只接受一個參數(shù),包裝組件:

const NavbarWithRouter = withRouter(Navbar);

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

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

HOC最常見的簽名如下所示:

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

什么?!如果你把它分開,很容易看到發(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);

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

這種形式可能看起來很混亂或不必要,但它有一個有用的特性。單參數(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)

(這個屬性也允許使用connect其他增強器樣式的HOC作為裝飾器,這是一個實驗性JavaScript提案。)

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

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

由HOC創(chuàng)建的容器組件像任何其他組件一樣出現(xiàn)在React Developer Tools中。為了便于調(diào)試,選擇一個顯示名稱來傳達它是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';
          }

注意事項

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

不要在render方法中使用HOC

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

通常情況下,你不需要考慮這一點。但是它對于HOC很重要,因為它意味著你無法將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 />;
    }

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

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

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

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

有時在React組件上定義靜態(tài)方法很有用。例如,中繼容器公開了一個靜態(tài)方法getFragment來促進GraphQL片段的組合。

但是,如果將HOC應(yīng)用于組件,則原始組件將使用容器組件進行包裝。這意味著新組件沒有任何原始組件的靜態(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

為了解決這個問題,你可以在返回之前將這些方法復(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來自動復(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)方法與組件本身分開導(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沒有通過

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

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

也就是說,有些時候refs是必要的逃生艙口,否則React不會支持它們。聚焦輸入字段是一個例子,您可能需要對組件進行必要的控制。在這種情況下,一種解決方案是通過給它一個不同的名稱來傳遞一個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();

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

Previous article: Next article: