在没有包装器元素的方法中返回多个React元素

时间:2017-02-08 19:36:33

标签: javascript reactjs

我试图从帮助器方法返回多个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

10 个答案:

答案 0 :(得分:9)

错误消息告诉您如何解决此问题:

  

数组或迭代器中的每个子项都应该有一个唯一的&#34;键&#34;丙

而不是:

flags

这样做:

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

是的,您需要在一个范围中包装文本节点(&#34; by&#34;)以便为其提供密钥。这就是休息时间。正如您所看到的,我只是给每个元素一个静态return [ <span key="by"> by </span>, <a key="author" href={getAuthorUrl(this.props.author)}>{this.props.author}</a>, ]; ,因为它们没有任何动态。如果您愿意,也可以使用keykey="1"

或者,你可以这样做:

key="2"

...不需要return <span> by <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a></span>; s。

以下是工作片段中的前一个解决方案:

&#13;
&#13;
key
&#13;
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'));
&#13;
&#13;
&#13;

答案 1 :(得分:6)

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

所以你现在可以使用:

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

有关详细信息,请访问:https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html

答案 2 :(得分:5)

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

请参阅this active Github issue,其中对未来版本的支持正在考虑中。

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

答案 3 :(得分:1)

还有另一种解决方法。我建议你创建另一个组件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>
    );
  }
}

我没有测试过这段代码。但我认为它看起来会更清洁。希望它有所帮助。

答案 4 :(得分:0)

我喜欢为这些东西设置一个If组件,并且我已将所有东西都包裹在一个范围内,因为它并没有真正破坏任何东西并且需要密钥消失......

&#13;
&#13;
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'));
&#13;
<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>
&#13;
&#13;
&#13;

...完全跳过阵列的事情?

答案 5 :(得分:0)

这有点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

答案 6 :(得分:0)

您可以从子渲染函数返回片段,但不能从主渲染函数返回,至少在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)自动分配基于索引的键,而不是返回数组。请注意,必须将字符串转换为可以使用密钥分配的跨度或其他节点:

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>

答案 7 :(得分:0)

试试这个:

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>
        );
    }
}

答案 8 :(得分:0)

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

你正在触发警告,因为你试图从数组渲染而不是反应元素而是直接使用html。为了解决这个问题,你必须这样做

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

React addons createFragment函数基本上就是这样,它会将你的html元素减少为你可以呈现的反应片段。

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>
  );
}

答案 9 :(得分:-4)

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

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