React.js:将一个组件包装到另一个组件中用children 渲染道具高阶元件 (HOC)。

许多模板语言都有“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/>


答案 1

尝试:

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

有关详细信息,请参阅文档中的多个组件:儿童和儿童道具的类型


答案 2

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}/>Wrapperchildren

请注意,你不一定需要使用 ,这对于一个组件来说是独一无二的,如果你愿意,你也可以使用普通的道具,或者混合道具和子道具: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);

对于许多用例来说,这既简单又好,我建议大多数消费者应用程序使用此功能。


渲染道具

可以将渲染函数传递给组件,这种模式通常称为渲染 prop,并且 prop 通常用于提供该回调。children

此模式实际上并不用于布局。包装器组件通常用于保存和管理某些状态,并将其注入其呈现函数中。

反例:

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>

注意你不一定需要使用,这是一个味道/API的问题。children

<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 组件的集合。反应采用帮助你做组合。


高阶元件 (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通常是获取组件并返回新组件的函数。

使用高阶组件可能比使用 或 更高性能,因为包装器可以使 渲染短路一步。childrenrender propsshouldComponentUpdate

在这里,我们使用.重新渲染应用时,如果名称 prop 不随时间而变化,则包装器能够说“我不需要渲染,因为 props(实际上是名称)与以前相同”。使用上面的基于解决方案,即使包装器是 ,情况并非如此,因为每次父元素呈现时都会重新创建子元素,这意味着包装器可能始终重新呈现,即使包装的组件是纯的。有一个babel插件可以帮助缓解这种情况,并确保随着时间的推移保持恒定的元素。PureComponentWrappedAppchildrenPureComponentchildren


结论

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

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

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

请注意,如果一个组件具有 render-prop API,则通常很容易在其上创建一个 HOC,因此,如果您是 lib 作者,则应首先编写一个 render-prop API,并最终提供 HOC 版本。这就是Apollo对渲染道具组件所做的,以及HOC使用它。<Query>graphql

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

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

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

  • React 的 compContext.Consumer
  • 未说明的Subscribe
  • 使用阿波罗的 HOC 而不是渲染道具graphqlQuery

在我看来,有时渲染道具会使代码更具可读性,有时则更少......我尝试根据我所拥有的限制使用最实用的解决方案。有时可读性比性能更重要,有时则不然。明智地选择,不要拘泥于2018年将所有内容转换为渲染道具的趋势。