【问题标题】:Can a react component have multiple areas for child content?一个反应组件可以有多个子内容区域吗?
【发布时间】:2016-06-26 13:15:10
【问题描述】:

假设我有一个简单的反应组件:

import React from 'react';
import styles from "./index.css";

export default React.createClass({
  render: function() {
    return (
      <header className={styles.root}>
        // area for child content
        {this.props.children}
      </header>
    );
  }
});

现在让我们说,我想要两个,而不是任何子组件的一个区域,就像这样:

import React from 'react';
import styles from "./index.css";

export default React.createClass({
  render: function() {
    return (
      <header className={styles.root}>
        <div className={styles.logo}>
          // logo children here
        </div>
        <div>
          // navigation children here
        </div>
      </header>
    );
  }
});

我知道我可以使用属性,但是对于大量的 html 内容来说这不是很优雅。如何以类似于 swig 中的named blocks 的方式做出反应?


命名块示例:

{% extends 'layout.html' %}

{% block title %}My Page{% endblock %}

{% block head %}
  {% parent %}
  <link rel="stylesheet" href="custom.css">
{% endblock %}

{% block content %}
<p>This is just an awesome page.</p>
{% endblock %}

您会看到这些块可以通过引用它们的名称来使用,从而允许“产生”多个内容区域。我希望有一种类似优雅的方式来做出反应,因为它使组件非常可组合。

【问题讨论】:

  • 当你说“我知道我可以使用属性”时——你的意思是道具吗?为什么不优雅呢?
  • 是的,标签&lt;ReactComponent property="a large amount of html here wouldn't be very maintainable" /&gt;中定义的属性。
  • 您的命名块示例看起来像 Django,而不是 swig?

标签: javascript reactjs


【解决方案1】:
import React, { Component } from 'react';
import styles from "./index.css";
import Logo from "./components/Logo";
import Navbar from "./components/Logo";

class Comp extends Component {
    render(){
        return (
            <Header className={styles.root}
                top={Logo}
                right={Navbar}
                />
        );
    }
}

class  Header extends Component {
    render(){
        let {top,right} =this.props;
        return (
            <header className={styles.root}>
                <div className={styles.logo}>
                    {top && <top />}
                </div>
                <div>
                    {right && <right />}
                </div>
            </header>
        );
    }
}

【讨论】:

  • 这是正确的解决方案,即使 react 文档建议采用这种方法 facebook.github.io/react/docs/composition-vs-inheritance.html 是错误的 关于 Do not use props to pass components as children 的声明“道具和组合为您提供了自定义组件外观和以明确和安全的方式行为。请记住,组件可以接受任意道具,包括原始值、React 元素或函数。”
  • 这是正确的解决方案。感谢@ncubica 提供链接。
  • 如果您使用的是 Typescript,那么这种策略(或使用渲染道具)比公认的答案更安全。
【解决方案2】:

制作单独的组件 不要使用 props 将组件作为子组件传递。像这样的。

header.js

import React from 'react';
import styles from "./index.css";

export default React.createClass({
  getComponent(key) {
     return this.props.children.filter( (comp) => {
             return comp.key === key;
     });
  }
  render: function() {
    return (
      <header className={styles.root}>
        <div className={styles.logo}>
          {this.getComponent('logo')}
        </div>
        <div>
         {this.getComponent('navbar'}
    
        </div>
      </header>
    );
  }
});

app.js

export default React.createClass({
      render: function() {
        return (
          <Header>
            <Logo key="logo"/>
            <Navbar key="navbar"/>
          </Header>
        );
      }
    });

【讨论】:

  • 所以一个组件基本上总是只能包装一个实体?没有办法将多条内容传递给它?因为这意味着我将不得不对组件进行大量研究以找出它们由哪些子组件组成,而如果我可以传递任意数量的内容区域,我理论上可以对整个安排有一个概述单个区域中的组件/html。
  • 啊,好吧,这有点接近我想要的。尽管我可以看到这可能会令人困惑,因为没有指示哪个孩子去哪里(除非您对其进行注释或使用声明性组件名称),并且很容易意外切换顺序。 swig 中的命名块非常清楚哪些内容在哪里。我想这没有等效的反应?像命名的孩子而不是通过数组索引注入它们?
  • 检查代码现在我为组件徽标和导航栏添加了一个键,现在我可以循环遍历 props.children 来找到我合适的
  • 我怎么会出现“this.props.children.filter is not a function”错误?
  • @newman 过滤器函数用于数组。如果 this.props.children 是一个字符串或任何不同于数组的东西,那么就没有过滤功能。
【解决方案3】:

您可以将道具视为孩子,因为从技术上讲,children 只是另一个道具。这种方法没有任何问题——React 贡献者自己建议你这样做 (Support multiple child insertion points)。

此外,如果您在一个庞大的工程师团队中工作,我还建议标准化您如何命名这些道具以及它们的标准行为 - 它们只是简单的内容还是您可以传递参数的回调。 react-placeholders 可以帮助你。因此,您的组件的代码可能如下所示:

// header.jsx
import React from 'react';
import withSlots from 'react-placeholders';

@withSlots('logo', 'navigation')
export default class Header extends React.Component {
  render() {
    return (
      <header className={styles.root}>
        <div className={styles.logo}>
          {this.props.header()}
        </div>
        <div>
          {this.props.navigation('home')}
        </div>
      </header>
    );
  }
}

这就是您嵌入该组件的方式

// app.jsx
import Header from 'header.jsx'

export default class Header extends React.Component {
  render() {
    return (
      <Header
        logoSlot={<img src="logo.svg" />}
        navigationSlot={(currentPage) => { 
          return <a href="#" style={{ color: currentPage ? 'blue' : 'red' }}>Home</a>
        }}
      />
    );
  }
}

【讨论】:

    【解决方案4】:

    这个问题启发了我。这是我的方法:

    第一个子组件:TestA.tsx

    import * as React from 'react';
    
    export default class TestA extends React.Component {
        render () {
            return (
                <div className="TestA">{this.props.children}</div>
            )
        }
    }
    

    第二个子组件:TestB.tsx

    import * as React from 'react';
    
    export default class TestB extends React.Component {
        render () {
            return (
                <div className="TestB">{this.props.children}</div>
            )
        }
    }
    

    父组件:TestAB.tsx

    import * as React from 'react';
    
    type TestABProps = {
        children: React.ReactNode;
    }
    
    export default class TestAB extends React.Component<TestABProps> {
    
        //Loop through React nodes in children and get all instances of the specified component
        getComp( comp: string ): null | React.ReactNode | React.ReactNode[] {
            if( this.props.children instanceof Array ) {
                let nodes: React.ReactNode[] = [];
                for( let child of this.props.children ) {
                    if( ((child as React.ReactElement)?.type as Function)?.name == comp ) {
                        nodes.push( child );
                    }
                }
                return nodes.length > 1 ? nodes : nodes[0];
            }
            return null;
        }
        render () {
            return (
                <div className="AB">
                    <div className ="A">
                        {this.getComp("TestA")}
                    </div>
                    <div>This is a DIV between the components</div>
                    <div className ="B">
                        {this.getComp("TestB")}
                    </div>
                </div>
            )
        }
    }
    

    如您所见,您只需将{this.getComp("YourComponentName")} 放置在您希望该组件的所有实例出现在您的父组件中的位置。

    使用示例

    <TestAB>
        <TestA>Foo</TestA>
        <TestB>Bar</TestB>
        <TestA>Foobar</TestA>
    </TestAB>
    

    使用非常简单直观。不需要键或其他任何东西,只需插入您的子组件而无需奇怪的语法。

    生成的 HTML

    <div class="AB">
        <div class="A">
            <div class="TestA">Foo</div>
            <div class="TestA">Foobar</div>
        </div>
        <div>This is a DIV between the components</div>
        <div class="B">
            <div class="TestB">Bar</div>
        </div>
    </div>
    

    请注意,您可以按任何顺序插入 TestAB 的子级 - 它们将被插入到正确的位置。

    这一切当然可以改进,您可以根据自己的需要进行调整。例如,如果您放置了除 TestA 或 TestB 之外的任何子组件,它们将不会显示,这将是我的第一个改进。

    【讨论】:

    • 我不明白 -1。我回答了这个问题,它有效。
    猜你喜欢
    • 2016-06-17
    • 2010-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-05
    • 2012-05-02
    • 1970-01-01
    相关资源
    最近更新 更多