React.js:将一个组件包装到另一个组件中

时间:2013-12-31 04:05:19

标签: javascript template-engine composition reactjs transclusion

许多模板语言都有“slots”或“yield”语句,允许进行某种控制反转,将一个模板包装在另一个模板中。

Angular有"transclude" option

Rails有yield statement。如果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组件以实现相同的输出?

3 个答案:

答案 0 :(得分:214)

尝试:

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

有关详细信息,请参阅文档中的Multiple Components: ChildrenType of the Children props

答案 1 :(得分:125)

使用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,这对于组件是唯一的,如果您愿意,您也可以使用普通道具,或混合使用道具和儿童:

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 propchildren道具通常用于提供回调。

这种模式并不适用于布局。包装器组件通常用于保存和管理某些状态并将其注入其渲染函数中。

反例:

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 ......),因为人们倾向于比HOC更容易找到这个API。 react-powerplug是简单的渲染道具组件的集合。 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);

Higher-Order Component / HOC通常是一个获取组件并返回新组件的函数。

使用高阶组件可能比使用childrenrender props更高效,因为包装器可以使用shouldComponentUpdate提前一步缩短渲染。

我们正在使用PureComponent。重新渲染应用程序时,如果WrappedApp名称道具不随时间变化,则包装器可以说“我不需要渲染,因为道具(实际上,名称)与以前相同”。使用上面基于children的解决方案,即使包装器是PureComponent,也不是这种情况,因为每次父渲染时都会重新创建子元素,这意味着包装器可能总是重新渲染,甚至如果包裹的成分是纯净的。有一个babel plugin可以帮助减轻这种影响,并确保随着时间的推移保持children元素。

结论

高阶组件可以为您提供更好的性能。它不是那么复杂,但一开始看起来肯定不友好。

阅读此内容后,请勿将整个代码库迁移到HOC。请记住,在应用程序的关键路径上,出于性能原因,您可能希望使用HOC而不是运行时包装器,特别是如果使用相同的包装器很多次,则值得考虑将其设置为HOC。

Redux首先使用运行时包装器<Connect>,然后出于性能原因切换到HOC connect(options)(Comp)(默认情况下,包装器是纯的并使用shouldComponentUpdate)。这是我想在这个答案中强调的完美例证。

注意,如果一个组件有一个render-prop API,通常很容易在它上面创建一个HOC,所以如果你是一个lib作者,你应该首先编写一个render prop API,并最终提供一个HOC版本。这就是Apollo对<Query> render-prop组件以及使用它的graphql HOC所做的事情。

就个人而言,我同时使用两者,但如果有疑问,我更喜欢HOC,因为:

  • 与渲染道具
  • 相比,组合它们(compose(hoc1,hoc2)(Comp))更具惯用性
  • 它可以给我更好的表现
  • 我熟悉这种编程风格

我毫不犹豫地使用/创建我最喜欢的工具的HOC版本:

  • React的Context.Consumer comp
  • 未说明的Subscribe
  • 使用Apollo的graphql HOC代替Query渲染道具

在我看来,有时渲染道具使代码更具可读性,有时更少......我尝试根据我的约束使用最实用的解决方案。有时可读性比表演更重要,有时则不然。明智地选择并且不要紧跟2018年将所有内容转换为渲染道具的趋势。

答案 2 :(得分:30)

除了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} />
    }
});