Как визуализировать компоненты реакции с помощью карты и объединения
У меня есть один компонент, который будет отображать Array of String. Код выглядит следующим образом.
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(', ')
Однако рендеринг результата - [Object], [Object], [Object].
Я не знаю, как интерпретировать объект, чтобы стать реагирующим компонентом. Любое предложение?
Ответы
Ответ 1
Простым решением является использование 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()
без второго аргумента будет выбрасывать с пустым массивом. Обычно это не должно быть проблемой, так как вы хотите отобразить пользовательское сообщение, говорящее что-то вроде 'this is empty' для пустых массивов в любом случае.
Обновление для Typescript
Вы можете использовать это в Typescript (без небезопасного типа 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>
}
}
Ответ 2
Вы можете использовать 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()
Ответ 3
Обновление с помощью React 16: Теперь ему разрешено визуализировать строки напрямую, поэтому вы можете упростить свой код, удалив все бесполезные теги <span>
.
const List = ({data}) => data.reduce((prev, curr) => [prev, ', ', curr])
Ответ 4
возвращаемый <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
Ответ 5
Принятый ответ на самом деле возвращает массив массивов, потому что prev
будет массивом каждый раз. Реакт достаточно умен, чтобы сделать эту работу, но он подвержен возникновению проблем в будущем, таких как нарушение алгоритма Reacts diffing при предоставлении ключей к каждому результату карты.
Новая функция 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 не будет жаловаться.
Ответ 6
Мой вариант:
{this.props.data
.map(item => <span>{item}</span>)
.map((item, index) => [index > 0 && ', ', item ])}
Ответ 7
Используйте вложенный массив, чтобы сохранить "," снаружи.
<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>
Ответ 8
Если я просто хочу сделать через запятую массив компонентов, я обычно 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 '}
Ответ 9
Это сработало для меня:
{data.map( ( item, i ) => {
return (
<span key={i}>{item.propery}</span>
)
} ).reduce( ( prev, curr ) => [ prev, ', ', curr ] )}
Ответ 10
Как упомянул Pith, 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>
}
}
Ответ 11
Это должно вернуть плоский массив. Обработайте случай с не повторяемым первым объектом, предоставив исходный пустой массив и отфильтровав ненужную первую запятую из результата
[{}, 'b', 'c'].reduce((prev, curr) => [...prev, ', ', curr], []).splice(1) // => [{}, 'b', 'c']
Ответ 12
Используя es6, вы можете сделать что-то вроде:
const joinComponents = (accumulator, current) => [
...accumulator,
accumulator.length ? ', ' : '',
current
]
А затем запустите:
listComponents
.map(item => <span key={item.id}> {item.t} </span>)
.reduce(joinComponents, [])
Ответ 13
Я единственный, кто считает, что в ответах здесь много ненужного распространения и вложения? Конечно, это значит быть лаконичным, но это оставляет дверь открытой для вопросов масштаба или Реакции, изменяющих то, как они справляются с вложенными массивами/фрагментами.
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>
Один массив, без вложенности. Никакого сексуального метода также нет, но если вы обнаружите, что делаете это часто, вы всегда можете добавить его в прототип массива или обернуть его в функцию, которая также выполняет обратный вызов отображения, чтобы сделать все это за один раз.
Ответ 14
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> );
}