Подтвердить что ты не робот

Возвращать несколько элементов React в методе без элемента оболочки

Я пытаюсь вернуть несколько элементов 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. Использование вспомогательного метода, подобного этому, вызовет предупреждение, и исправление предупреждения (путем добавления ключей) сделает код слишком запутанным. Есть ли чистый способ сделать это, не вызывая предупреждения?

Edit:

Другой вариант использования:

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

Изменить 2:

Оказывается, это пока невозможно, я написал около двух обходных путей здесь: https://www.wptutor.io/web/js/react-multiple-elements-without-wrapper

4b9b3361

Ответ 1

В настоящее время это невозможно сделать без какого-либо обходного пути, такого как обертывание всего в другом компоненте, поскольку оно заканчивается базовым кодом React, пытающимся вернуть несколько объектов.

См. этот активный вопрос Github, где поддержка этого рассматривается для будущей версии.

Ответ 2

Сообщение об ошибке сообщает вам, как это решить:

Каждый дочерний элемент в массиве или итераторе должен иметь уникальный "ключ".

Вместо этого:

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>,
];

Да, вам нужно обернуть текст node ( "by" ) в промежутке, чтобы дать ему ключ. Таковы перерывы. Как вы можете видеть, я только что дал каждому элементу статический key, так как в нем нет ничего динамического. Вы могли бы использовать key="1" и key="2", если хотите.

В качестве альтернативы вы можете сделать это:

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

..., что устраняет необходимость в key s.

Здесь первое решение в рабочем фрагменте:

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>

Ответ 3

Есть другой способ решить эту проблему. Я предлагаю вам создать еще один компонент 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

Мне нравится иметь 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>

Ответ 5

Это немного взломано, но у него нет ненужного 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);

my JSFiddle

Ответ 6

Вы можете возвращать фрагменты из вспомогательных функций рендеринга, но не из основной функции рендеринга, по крайней мере до 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>

Ответ 7

Попробуйте следующее:

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

Возможно, более простым способом было бы переосмыслить, как вы архивируете свое приложение. Однако более простым способом.

Вы запускаете предупреждение, потому что пытаетесь отобразить из массива и не реагировать на элементы, а непосредственно на html. Чтобы приблизиться к этому, вам нужно будет сделать

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

React addons createFragment функция в основном делает это, он уменьшает ваши элементы html в реагирующие фрагменты, которые вы можете отобразить.

Реагировать документацию createFragment

В качестве альтернативы, в гораздо более эффективном подходе вы можете создать компонент 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

Попробуйте этот подход в своем массиве:

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