【问题标题】:Return multiple React elements in a method without a wrapper element在没有包装器元素的方法中返回多个 React 元素
【发布时间】:2019-03-08 02:33:37
【问题描述】:

我正在尝试从辅助方法返回多个 React 元素。我可以简单地通过移动一些代码来解决它,但我想知道是否有更清洁的方法来解决它。我有一个返回 render 方法的一部分的方法,并且该函数需要返回一个 React 元素和一些文本。通过一个例子更清楚:

class Foo extends React.Component {
  _renderAuthor() {
    if (!this.props.author) {
      return null;
    }

    return [
      ' by ',
      <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
    ]; // Triggers warning: Each child in an array or iterator should have a unique "key" prop.

  render() {
    return (
      <div>
        {this.props.title}
        {this._renderAuthor()}
      </div>
    );
  }
}

我知道render 方法必须准确返回 1 个 React 元素。使用这样的辅助方法会触发警告,而修复警告(通过添加键)会使代码过于复杂。有没有一种干净的方法可以在不触发警告的情况下执行此操作?

编辑:

另一个用例:

render() {
  return (
    <div>
      {user
        ? <h2>{user.name}</h2>
          <p>{user.info}</p>
        : <p>User not found</p>}
    </div>
  );
}

编辑 2:

事实证明这是不可能的,我在这里写了大约 2 个解决方法:https://www.wptutor.io/web/js/react-multiple-elements-without-wrapper

【问题讨论】:

  • 什么样的警告?
  • "数组或迭代器中的每个子元素都应该有一个唯一的 "key" 属性。"向文本节点添加键是不可能的。即使可以,当我试图返回 2 个固定元素时,添加键似乎也很尴尬。
  • 当你在 React 中动态插入额外的元素时,它会寻找 key prop,所以当你渲染你的链接时这样做:&lt;a key = {'prefix-'+random_string_generator()} href={getAuthorUrl(this.props.author)}&gt;{this.props.author}&lt;/a&gt;
  • 是的,但我正在寻找一种不需要这样做的方法。
  • FWIW,他们正在努力为此添加官方支持:github.com/facebook/react/issues/2127

标签: javascript reactjs


【解决方案1】:

错误信息告诉你如何解决这个问题:

数组或迭代器中的每个子元素都应该有一个唯一的“key”属性。

而不是这个:

return [
  ' by ',
  <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];

这样做:

return [
  <span key="by"> by </span>,
  <a key="author" href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];

是的,您需要将文本节点(“by”)包装在一个跨度中,以便为其提供一个键。这就是休息。正如你所看到的,我只是给每个元素一个静态的key,因为它们没有任何动态。如果您愿意,也可以使用key="1"key="2"

或者,您可以这样做:

return <span> by <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a></span>;

...这样就不需要keys。

这是工作 sn-p 中的前一个解决方案:

const getAuthorUrl = author => `/${author.toLowerCase()}`;

class Foo extends React.Component {
  _renderAuthor() {
    if (!this.props.author) {
      return null;
    }

    return [
      <span key="by"> by </span>,
      <a key="author" href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
    ];
  }

  render() {
    return (
      <div>
        {this.props.datePosted}
        {this._renderAuthor()}
      </div>
    );
  }
}

ReactDOM.render(<Foo datePosted="Today" author="Me"/>, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>

【讨论】:

  • 这样做的问题是它添加了不必要的标记和 JSX。也许我的问题不清楚,但我正在寻找一种不必使用key 的方法。我假设这是不可能的,但我问了所以以防万一。
  • 在渲染元素数组时必须使用键,这是 React 识别元素的方式的一部分 facebook.github.io/react/docs/lists-and-keys.html#keys
  • 如果我必须对数组使用键,那么我正在寻找一种不使用数组的方法。
【解决方案2】:

已使用 Fragment 组件添加了支持。这是一流的组件。

所以你现在可以使用:

render() {
  return (   
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}

欲了解更多信息,请访问:https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html

【讨论】:

    【解决方案3】:

    如果没有某种解决方法(例如将所有内容包装在另一个组件中),目前是不可能做到这一点的,因为它最终会导致底层 React 代码尝试返回多个对象。

    请参阅this active Github issue,但正在考虑在未来版本中支持此功能。


    编辑:您现在可以在 React 16 中使用 Fragments 执行此操作,请参阅: https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html

    【讨论】:

    【解决方案4】:

    还有另一种方法可以解决这个问题。我会建议你创建另一个组件 Author.js:

    function Author(props) {
      return (<span>
        <span> by </span>
        <a href={props.getAuthorUrl(props.author)}>{props.author}</a>
      </span>)
    }
    
    
    class Foo extends React.Component {
      render() {
        return (
          <div>
            {this.props.title}
            {this.props.author && <Author author={this.props.author} getAuthorUrl={this.getAuthorUrl} />}
          </div>
        );
      }
    }
    

    虽然我没有测试这段代码。但我认为它看起来会更干净。希望对您有所帮助。

    【讨论】:

      【解决方案5】:

      我喜欢用一个 If 组件来处理这些事情,并且我已经将所有东西都包装到一个跨度中,因为它不会真正破坏任何东西,并且不需要键...

      const getAuthorUrl = author => `/${author.toLowerCase()}`;
      
      function If({condition,children}) {
        return condition ? React.Children.only(children) : null;
      
      }
      
      class Foo extends React.Component {
      
        render() {
          return (
            <div>
              {this.props.datePosted}
              <If condition={this.props.author}>
                <span> by 
                <a key="author" href={getAuthorUrl(this.props.author)}>
                  {this.props.author}
                </a>
                </span>
              </If>
            </div>
          );
        }
      }
      
      ReactDOM.render(<Foo datePosted="Today" author="Me"/>, document.getElementById('container'));
      <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
      <div id="container"></div>

      ...完全跳过数组?

      【讨论】:

      • 您仍在将 &lt;span&gt; 包裹在文本和 &lt;a&gt; 周围。我也可以包装 &lt;span&gt; 而不是使用数组,但我不想要额外的标记。
      【解决方案6】:

      这有点hacky,但它没有你希望的不必要的jsx。

      var author = 'Daniel';
      var title = 'Hello';
      
      var Hello = React.createClass({
        _renderAutho0r: function() {
          if (!author) {
            return null;
          }
      
          return <a href="#">{author}</a>
      },
      
          render: function() {
          var by = author ? ' by ' : null;
      
              return (
            <div>
              {title}
              {by}
              {this._renderAutho0r()}
            </div>
          );
          }
      });
      
      React.render(<Hello name="World" />, document.body);
      

      我的JSFiddle

      【讨论】:

        【解决方案7】:

        你可以从子渲染函数返回片段,但不能从主渲染函数返回,至少在 React 16 之前。为此,返回一个组件数组。您不需要手动设置键,除非您的片段子项会更改(数组默认使用索引键控)。

        你也可以使用createFragment来创建片段。

        对于内联使用,您可以使用数组或利用立即调用的箭头函数。 请参见下面的示例:

        const getAuthorUrl = author => `/${author.toLowerCase()}`;
        
        class Foo extends React.Component {
          constructor() {
             super();
             this._renderAuthor = this._renderAuthor.bind(this);
             this._renderUser = this._renderUser.bind(this);
          }
          
          _renderAuthor() {
            if (!this.props.author) {
              return null;
            }
        
            return [
              ' by ',
              <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
            ];
          }
          
          _renderUser() {
            return [
              <h2>{this.props.user.name}</h2>,
              <p>{this.props.user.info}</p>
            ]
          }
          
          render() {
            return (
              <div>
                {this.props.datePosted}
                {this._renderAuthor()}
                
                <div>
                  {this.props.user
                    ? this._renderUser()
                    : <p>User not found</p>}
                </div>
                
                <div>
                  {this.props.user
                    ? [
                        <h2>{this.props.user.name}</h2>,
                        <p>{this.props.user.info}</p>
                      ]
                    : <p>User not found</p>}
                </div>
                
                <div>
                  {this.props.user
                    ? (() => [
                        <h2>{this.props.user.name}</h2>,
                        <p>{this.props.user.info}</p>
                      ])()
                    : <p>User not found</p>}
                </div>
              </div>
            );
          }
        }
        
        ReactDOM.render(<Foo datePosted="Today" author="Me" user={{name: 'test', info: 'info'}} />, document.getElementById('container'));
        <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
        <div id="container"></div>

        为了不收到警告,必须为每个孩子分配一个键。为此,请使用辅助函数 fragment(...children) 自动分配基于索引的键,而不是返回数组。请注意,字符串必须转换为可以分配键的 span 或其他节点:

        const fragment = (...children) =>
            children.map((child, index) =>
                React.cloneElement(
                    typeof child === 'string'
                    ? <span>{child}</span>
                    : child
                , { key: index }
                )
            )
        

        const getAuthorUrl = author => `/${author.toLowerCase()}`;
        
        const fragment = (...children) =>
            children.map((child, index) =>
                React.cloneElement(
                    typeof child === 'string'
                    ? <span>{child}</span>
                    : child
                , { key: index }
                )
            )
        
        class Foo extends React.Component {
          constructor() {
             super();
             this._renderAuthor = this._renderAuthor.bind(this);
             this._renderUser = this._renderUser.bind(this);
          }
          
          _renderAuthor() {
            if (!this.props.author) {
              return null;
            }
        
            return fragment(
              ' by ',
              <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>
            );
          }
          
          _renderUser() {
            return fragment(
              <h2>{this.props.user.name}</h2>,
              <p>{this.props.user.info}</p>
            )
          }
          
          render() {
            return (
              <div>
                {this.props.datePosted}
                {this._renderAuthor()}
                
                <div>
                  {this.props.user
                    ? this._renderUser()
                    : <p>User not found</p>}
                </div>
                
                <div>
                  {this.props.user
                    ? fragment(
                        <h2>{this.props.user.name}</h2>,
                        <p>{this.props.user.info}</p>
                      )
                    : <p>User not found</p>}
                </div>
                
                <div>
                  {this.props.user
                    ? (() => fragment(
                        <h2>{this.props.user.name}</h2>,
                        <p>{this.props.user.info}</p>
                      ))()
                    : <p>User not found</p>}
                </div>
              </div>
            );
          }
        }
        
        ReactDOM.render(<Foo datePosted="Today" author="Me" user={{name: 'test', info: 'info'}} />, document.getElementById('container'));
        <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
        <div id="container"></div>

        【讨论】:

        • 我很确定您使用的 React 文件的警告已被删除。我想删除警告是一个可行的选择。
        • 我不知道警告,因为它们没有在 sn-ps 中显示。要摆脱这些,请使用fragment 函数查看编辑后的答案。
        【解决方案8】:

        试试这个:

        class Foo extends React.Component {
            _renderAuthor() {
                return <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>
            }
        
            render() {
                return (
                  <div>
                        {this.props.title}
                        {this.props.author && " by "}
                        {this.props.author && this._renderAuthor()}
                  </div>
                );
            }
        }
        

        【讨论】:

          【解决方案9】:

          也许更简单的方法是重新考虑如何构建应用程序。但是,以更简单的方式。

          您正在触发警告,因为您尝试从数组呈现而不是反应元素,而是直接 html。为了解决这个问题,您必须这样做

          {this._renderAuthor().map(
              (k,i) => (React.addons.createFragment({k}))
              )  }
          

          React createFragment documentation

          或者,以更好的方法,您可以像这样创建 AuthorLink stateless 组件..

          function AuthorLink(props) {
            return (
              <div className="author-link">
                <span> by </span>
                <a href={props.authorUrl}> {props.author} </a>
              </div> 
            });
          }
          

          并在主组件的渲染中使用它

          render() {
            const { author } = this.props;
            return (
              <div>
                {this.props.datePosted}
                <AuthorLink url={getAuthorUrl(author)} author={author} />
              </div>
            );
          }
          

          【讨论】:

            【解决方案10】:

            在您的阵列上尝试这种方法:

            return [
                  <span key={'prefix-'+random_string_generator()}>' by '</span>,
                  <a key={'prefix-'+random_string_generator()} href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
                ];
            

            【讨论】:

            • 1.你基本上是从上面@JustinMUcar 的评论中复制的,所以-1 和2。random_string_generator() 几乎违背了key 的目的,即帮助虚拟DOM reconciliation。它使警告消失,但这几乎是最糟糕的解决方案。
            猜你喜欢
            • 2017-04-08
            • 1970-01-01
            • 2018-02-20
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2019-09-06
            • 2013-08-22
            • 1970-01-01
            相关资源
            最近更新 更多