как визуализировать реагирующие компоненты с помощью карты и соединения

102

У меня есть один компонент, который будет отображать массив строк. Код выглядит так.

React.createClass({
  render() {
     <div>
        this.props.data.map(t => <span>t</span>)
     </div>
  }
})

Он работает отлично. то есть, если props.data = ['tom', 'jason', 'chris'] Отображаемый результат на странице будет tomjasonchris

Затем я хочу объединить все имена с помощью запятой, поэтому я меняю код на

this.props.data.map(t => <span>t</span>).join(', ')

Однако результат рендеринга - [Объект], [Объект], [Объект].

Я не знаю, как интерпретировать объект, чтобы он стал реагирующим компонентом для визуализации. Любое предложение ?

Сяохэ Донг
источник

Ответы:

150

Простое решение - использовать reduce()без второго аргумента и без распространения предыдущего результата:

class List extends React.Component {
  render() {
     <div>
        {this.props.data
          .map(t => <span>{t}</span>)
          .reduce((prev, curr) => [prev, ', ', curr])}
     </div>
  }
}

Без второго аргумента, reduce()будет начинаться с индексом 1 , а не 0, а React совершенно счастлив с вложенными массивами.

Как сказано в комментариях, вы хотите использовать это только для массивов, по крайней мере, с одним элементом, потому что reduce()без второго аргумента будет генерироваться пустой массив. Обычно это не должно быть проблемой, поскольку вы все равно хотите отображать настраиваемое сообщение, говорящее что-то вроде «это пусто» для пустых массивов.

Обновление для машинописного текста

Вы можете использовать это в Typescript (без type-unsafe any) с React.ReactNodeпараметром типа на .map():

class List extends React.Component {
  render() {
     <div>
        {this.props.data
          .map<React.ReactNode>(t => <span>{t}</span>)
          .reduce((prev, curr) => [prev, ', ', curr])}
     </div>
  }
}
Maarten ter Horst
источник
3
If the array is empty and no initialValue was provided, TypeError would be thrown.. поэтому это не сработает для пустого массива.
Евгений Кревенец
6
Также обратите внимание, что это рекурсивно вкладывает prevмассив. например [1, 2, 3, 4]становится [[[1,",",2],",",3],",",4].
Tamlyn
2
@Tamlyn: Считаете ли вы, что мое первоначальное упоминание вашей точки в ответе («React вполне устраивает вложенные массивы») достаточно ясно, или мне следует подробнее остановиться на этом?
Maarten ter Horst
1
Кто-нибудь заставил это решение работать с React + TypeScript?
Мэтт Делл,
1
@Jstuff Мне пришлось использовать любой. .reduce((prev: JSX.Element, current: JSX.Element): any => [prev, (<span>&nbsp;</span>), current])
Мэтт Делл
26

Вы можете использовать reduceдля объединения нескольких элементов массива:

React.createClass({
  render() {
     <div>
        this.props.data
        .map(t => <span>t</span>)
        .reduce((accu, elem) => {
            return accu === null ? [elem] : [...accu, ',', elem]
        }, null)
     </div>
  }
})

Это инициализирует аккумулятор нулевым значением, поэтому мы можем обернуть первый элемент в массиве. Для каждого следующего элемента в массиве мы создаем новый массив, содержащий все предыдущие элементы, используя ...-operator, добавляем разделитель, а затем следующий элемент.

Array.prototype.reduce ()

devsnd
источник
2
спасибо, именно то, что мне нужно. Вы можете удалить нулевую проверку и тернарность, инициализировав аккумулятор как пустой массив
Ларри
7
@Larry, если вы удалите нулевую проверку и тернар и передадите [] в качестве инициализатора, как избежать запятой в начале? ['a'].reduce((a, e) => [...a, ',', e],[])урожайность [",", "a"]. Отсутствие инициализатора работает, если массив не пуст, и в этом случае возвращается ошибка TypeError.
Guy
@Guy вы абсолютно правы - моя ошибка, что я присоединялся значения с пробелами, и отсутствующий пустой промежуток в воспроизведенной выходной
Ларри
12

Обновление с помощью React 16: теперь можно напрямую отображать строки, поэтому вы можете упростить свой код, удалив все бесполезные <span>теги.

const list = ({ data }) => data.reduce((prev, curr) => [ prev, ', ', curr ]);
Пробка
источник
8

Принятый ответ фактически возвращает массив массивов , так как prevбудет массив каждый раз. React достаточно умен, чтобы заставить эту работу работать, но может ли он вызвать проблемы в будущем, такие как нарушение алгоритма различения Reacts при предоставлении ключей для каждого результата карты.

Новая React.Fragmentфункция позволяет нам делать это простым для понимания способом, без основных проблем.

class List extends React.Component {
  render() {
     <div>
        {this.props.data
          .map((t, index) => 
            <React.Fragment key={index}>
              <span> {t}</span> ,
            </React.Fragment>
          )
      </div>
  }
}

С помощью React.Fragmentмы можем просто поместить разделитель ,вне возвращаемого HTML, и React не будет жаловаться.

Jstuff
источник
2
Отличное решение, но вам нужно удалить запятую для последнего элемента массива
indapublic
Вы всегда можете использовать токарный станок и индекс, чтобы решить эту проблему.
Jstuff
2
Вы можете удалить последнюю запятую с помощью этой модификации: <React.Fragment key = {index}> <span> {t} </span> {index === this.props.data.length - 1? '': ','} </React.Fragment>
JohnP
7

<span>{t}</span>вы возвращаете является объектом, а не строка. Проверьте ответные документы об этом https://facebook.github.io/react/docs/jsx-in-depth.html#the-transform

Используя .join()в возвращаемом массиве из map, вы присоединяетесь к массиву объектов.[object Object], ...

Вы можете поместить запятую внутри, <span></span>чтобы он отображался так, как вы хотите.

  render() {
    return (
      <div>
        { this.props.data.map(
            (t,i) => <span>{t}{ this.props.data.length - 1 === i ? '' : ','} </span>
          )
        }
      </div>
    )
  }

образец: https://jsbin.com/xomopahalo/edit?html,js,output

Oobgam
источник
3

Мой вариант:

{this.props.data
    .map(item => <span>{item}</span>)
    .map((item, index) => [index > 0 && ', ', item ])}
Fen1kz
источник
2

Если я просто хочу отобразить массив компонентов, разделенных запятыми, я обычно нахожу reduceслишком подробным. Более короткое решение в таких случаях:

{arr.map((item, index) => (
  <Fragment key={item.id}>
    {index > 0 && ', '}
    <Item {...item} />
  </Fragment>
))}

{index > 0 && ', '} отобразит запятую с последующим пробелом перед всеми элементами массива, кроме первого.

Если вы хотите разделить предпоследний элемент и последний чем-то другим, скажем, строкой ' and ', вы можете заменить {index > 0 && ', '}на

{index > 0 && index !== arr.length - 1 && ', '}
{index === arr.length - 1 && ' and '}
Казимир
источник
2

Используя es6, вы можете сделать что-то вроде:

const joinComponents = (accumulator, current) => [
  ...accumulator, 
  accumulator.length ? ', ' : '', 
  current
]

А затем запустите:

listComponents
  .map(item => <span key={item.id}> {item.t} </span>)
  .reduce(joinComponents, [])
Марко Антонио
источник
1

Используйте вложенный массив, чтобы сохранить "," снаружи.

  <div>
      {this.props.data.map((element, index) => index == this.props.data.length - 1 ? <span key={index}>{element}</span> : [<span key={index}>{element}</span>, ", "])}
  </div>

Оптимизируйте его, сохранив данные в массиве и изменив последний элемент, вместо того, чтобы постоянно проверять, был ли его последний элемент.

  let processedData = this.props.data.map((element, index) => [<span key={index}>{element}</span>, ", "])
  processedData [this.props.data.length - 1].pop()
  <div>
      {processedData}
  </div>
inc
источник
0

Это сработало для меня:

    {data.map( ( item, i ) => {
                  return (
                      <span key={i}>{item.propery}</span>
                    )
                  } ).reduce( ( prev, curr ) => [ prev, ', ', curr ] )}
qin.ju
источник
0

Как упоминал Пит , React 16 позволяет вам использовать строки напрямую, поэтому упаковка строк в теги span больше не нужна. Основываясь на ответе Маартена , если вы также хотите сразу же обработать настраиваемое сообщение (и не выдавать ошибку в пустом массиве), вы можете выполнить операцию с помощью тернарного оператора if для свойства length в массиве. Это может выглядеть примерно так:

class List extends React.Component {
  const { data } = this.props;

  render() {
     <div>
        {data.length
          ? data.reduce((prev, curr) => [prev, ', ', curr])
          : 'No data in the array'
        }
     </div>
  }
}
Фредрик Ваадленд
источник
0

Это должно вернуть плоский массив. Обработка случая с не повторяемым первым объектом путем предоставления начального пустого массива и фильтрации ненужной первой запятой из результата

[{}, 'b', 'c'].reduce((prev, curr) => [...prev, ', ', curr], []).splice(1) // => [{}, 'b', 'c']
Калин Ортан
источник
0

Неужели я единственный, кто думает, что здесь в ответах происходит много ненужного распространения и вложения? Конечно, баллы за краткость, но это оставляет дверь открытой для проблем масштабирования или изменения React, как они работают с вложенными массивами / фрагментами.

const joinJsxArray = (arr, joinWith) => {
  if (!arr || arr.length < 2) { return arr; }

  const out = [arr[0]];
  for (let i = 1; i < arr.length; i += 1) {
    out.push(joinWith, arr[i]);
  }
  return out;
};

// render()
<div>
  {joinJsxArray(this.props.data.map(t => <span>t</span>), ', ')}
</div>

Один массив, без вложенности. Никакого соблазнительного связывания методов тоже нет, но если вы обнаружите, что делаете это часто, вы всегда можете добавить его в прототип массива или обернуть его функцией, которая также принимает обратный вызов сопоставления, чтобы сделать все это за один раз.

Lokasenna
источник
0
function YourComponent(props) {

  const criteria = [];

  if (something) {
    criteria.push(<strong>{ something }</strong>);
  }

  // join the jsx elements with `, `
  const elements = criteria.reduce((accu, elem) => {
    return accu === null ? [elem] : [...accu, ', ', elem]
  }, null);

  // render in a jsx friendly way
  return elements.map((el, index) => <React.Fragment key={ index }>{ el }</React.Fragment> );

}
Ilovett
источник