【问题标题】:React.js: Wrapping one component into anotherReact.js:将一个组件包装到另一个组件中
【发布时间】:2014-01-18 01:07:06
【问题描述】:

许多模板语言都有“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 没有&lt;yield/&gt;。如何定义 Wrapper 组件以实现相同的输出?

【问题讨论】:

标签: javascript template-engine composition reactjs transclusion


【解决方案1】:

试试:

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

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

【讨论】:

【解决方案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 中的一个特殊属性,它将包含组件标签内的内容(这里 &lt;App name={name}/&gt;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 上下文、React-motion、Apollo ......),因为人们倾向于发现这个 API 比 HOC 更容易。 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 名称 prop 不随时间变化,则包装器可以说“我不需要渲染,因为 props(实际上是名称)与以前相同”。使用上面基于children 的解决方案,即使包装器是PureComponent,情况也并非如此,因为每次父渲染时都会重新创建子元素,这意味着包装器可能总是重新渲染,即使包装的组件是纯的。有一个 babel plugin 可以帮助缓解这种情况并确保随着时间的推移保持不变的 children 元素。


结论

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

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

Redux 最初使用运行时包装器 &lt;Connect&gt;,后来出于性能原因切换到 HOC connect(options)(Comp)(默认情况下,包装器是纯的并使用 shouldComponentUpdate)。这完美地说明了我想在这个答案中强调的内容。

注意如果一个组件有 render-prop API,通常很容易在它上面创建一个 HOC,所以如果你是一个 lib 作者,你应该先写一个 render prop API,最终提供一个 HOC 版本.这就是 Apollo 对 &lt;Query&gt;render-prop 组件和使用它的 graphql HOC 所做的。

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

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

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

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

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

【讨论】:

  • 这种方法还可以很容易地将 props 向下传递给子组件(在本例中为 Hello)。从 React 0.14.* 开始,将 props 传递给子组件的唯一方法是使用 React.createClone,这可能很昂贵。
  • 问题:答案提到“更好的性能” - 我不明白:与其他解决方案相比更好?
  • 与运行时包装器相比,HOC 可以具有更好的性能,因为它们可以更早地缩短渲染。
  • 谢谢!就像你从我的月中吸取了一些话,但你用更大的天赋表达了它们?
  • 这是一个更好的答案:] 谢谢!
【解决方案3】:

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

【讨论】:

  • 我在delegate上没有看到任何文档,你是怎么找到的?
  • 您可以将任何您想要的道具添加到组件并根据需要命名它们,我在第 4 行使用 this.props.delegate 但也可以将其命名为其他名称。
猜你喜欢
  • 2020-07-02
  • 2018-02-19
  • 2018-02-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-17
  • 1970-01-01
相关资源
最近更新 更多