簡體   English   中英

React.js:將一個組件包裝到另一個組件中

[英]React.js: Wrapping one component into another

許多模板語言都有“slots”或“yield”語句,允許進行某種控制反轉以將一個模板包裝在另一個模板中。

Angular 有“transclude”選項

Rails 有yield 聲明 如果 React.js 有 yield 語句,它看起來像這樣:

var Wrapper = React.createClass({
  render: function() {
    return (
      <div className="wrapper">
        before
          <yield/>
        after
      </div>
    );
  }
});

var Main = React.createClass({
  render: function() {
    return (
      <Wrapper><h1>content</h1></Wrapper>
    );
  }
});

期望的輸出:

<div class="wrapper">
  before
    <h1>content</h1>
  after
</div>

唉,React.js 沒有<yield/> 如何定義 Wrapper 組件以實現相同的輸出?

嘗試:

var Wrapper = React.createClass({
  render: function() {
    return (
      <div className="wrapper">
        before
          {this.props.children}
        after
      </div>
    );
  }
});

有關更多信息,請參閱文檔中的多個組件:子項子項道具的類型

使用children

const Wrapper = ({children}) => (
  <div>
    <div>header</div>
    <div>{children}</div>
    <div>footer</div>
  </div>
);

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = ({name}) => (
  <Wrapper>
    <App name={name}/>
  </Wrapper>
);

render(<WrappedApp name="toto"/>,node);

這在 Angular 中也稱為transclusion

children是 React 中的一個特殊道具,它將包含組件標簽內的內容(這里<App name={name}/>Wrapper ,所以它是children

請注意,您不一定需要使用children ,這對於組件來說是唯一的,如果需要,您也可以使用普通的 props,或者混合 props 和 children:

const AppLayout = ({header,footer,children}) => (
  <div className="app">
    <div className="header">{header}</div>
    <div className="body">{children}</div>
    <div className="footer">{footer}</div>
  </div>
);

const appElement = (
  <AppLayout 
    header={<div>header</div>}
    footer={<div>footer</div>}
  >
    <div>body</div>
  </AppLayout>
);

render(appElement,node);

這對於許多用例來說既簡單又好,我建議大多數消費者應用程序使用它。


渲染道具

可以將渲染函數傳遞給組件,這種模式通常稱為render prop ,並且children prop 通常用於提供該回調。

這種模式並不真正用於布局。 包裝器組件通常用於保存和管理某些狀態並將其注入到其渲染函數中。

反例:

const Counter = () => (
  <State initial={0}>
    {(val, set) => (
      <div onClick={() => set(val + 1)}>  
        clicked {val} times
      </div>
    )}
  </State>
); 

你可以變得更花哨,甚至可以提供一個對象

<Promise promise={somePromise}>
  {{
    loading: () => <div>...</div>,
    success: (data) => <div>{data.something}</div>,
    error: (e) => <div>{e.message}</div>,
  }}
</Promise>

請注意,您不一定需要使用children ,這是品味/API 的問題。

<Promise 
  promise={somePromise}
  renderLoading={() => <div>...</div>}
  renderSuccess={(data) => <div>{data.something}</div>}
  renderError={(e) => <div>{e.message}</div>}
/>

截至今天,許多庫都在使用渲染道具(React context、React-motion、Apollo...),因為人們傾向於發現這個 API 比 HOC 更容易。 react-powerplug是一個簡單的 render-prop 組件的集合。 react-adopt可幫助您進行組合。


高階組件 (HOC)。

const wrapHOC = (WrappedComponent) => {
  class Wrapper extends React.PureComponent {
    render() {
      return (
        <div>
          <div>header</div>
          <div><WrappedComponent {...this.props}/></div>
          <div>footer</div>
        </div>
      );
    }  
  }
  return Wrapper;
}

const App = ({name}) => <div>Hello {name}</div>;

const WrappedApp = wrapHOC(App);

render(<WrappedApp name="toto"/>,node);

高階組件 / HOC通常是一個函數,它接受一個組件並返回一個新組件。

使用高階組件比使用children組件或render props性能更高,因為包裝器可以使用shouldComponentUpdate提前一步縮短渲染。

這里我們使用PureComponent 重新渲染應用程序時,如果WrappedApp名稱 prop 不隨時間變化,則包裝器可以說“我不需要渲染,因為 props(實際上,名稱)與以前相同”。 使用上述基於children的解決方案,即使包裝器是PureComponent ,也不是這種情況,因為每次父級渲染時都會重新創建子元素,這意味着即使包裝的組件是純的,包裝器也可能總是重新渲染。 有一個babel 插件可以幫助緩解這種情況並確保隨着時間的推移保持恆定的children元素。


結論

高階組件可以為您提供更好的性能。 它並沒有那么復雜,但一開始看起來肯定不友好。

閱讀本文后,不要將整個代碼庫遷移到 HOC。 請記住,在您的應用程序的關鍵路徑上,出於性能原因,您可能希望使用 HOC 而不是運行時包裝器,特別是如果多次使用相同的包裝器,則值得考慮將其設為 HOC。

Redux 最初使用運行時包裝器<Connect> ,后來出於性能原因切換到 HOC connect(options)(Comp) (默認情況下,包裝器是純的並使用shouldComponentUpdate )。 這是我想在這個答案中強調的內容的完美例證。

注意如果一個組件有render-prop API,通常很容易在它之上創建一個HOC,所以如果你是一個lib作者,你應該先寫一個render prop API,最后提供一個HOC版本。 這就是 Apollo 對<Query>渲染道具組件以及使用它的graphql HOC graphql

就我個人而言,我兩者都使用,但有疑問時我更喜歡 HOC,因為:

  • 與渲染道具相比,組合它們( compose(hoc1,hoc2)(Comp) )更慣用
  • 它可以給我更好的表現
  • 我熟悉這種編程風格

我會毫不猶豫地使用/創建我最喜歡的工具的 HOC 版本:

  • React 的Context.Consumer comp
  • 未說明的Subscribe
  • 使用 Apollo 的graphql HOC 而不是Query渲染道具

在我看來,渲染道具有時會使代碼更具可讀性,有時則更少……我嘗試根據我的約束使用最實用的解決方案。 有時可讀性比性能更重要,有時則不然。 明智地選擇,不要拘泥於 2018 年將所有內容都轉換為渲染道具的趨勢。

除了 Sophie 的回答之外,我還發現了發送子組件類型的用途,執行如下操作:

var ListView = React.createClass({
    render: function() {
        var items = this.props.data.map(function(item) {
            return this.props.delegate({data:item});
        }.bind(this));
        return <ul>{items}</ul>;
    }
});

var ItemDelegate = React.createClass({
    render: function() {
        return <li>{this.props.data}</li>
    }
});

var Wrapper = React.createClass({    
    render: function() {
        return <ListView delegate={ItemDelegate} data={someListOfData} />
    }
});

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM