jak renderować komponenty reagujące za pomocą mapowania i łączenia

102

Mam jeden składnik, który będzie wyświetlać tablicę ciągów. Kod wygląda następująco.

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

Działa doskonale. tj. jeśli props.data = ['tom', 'jason', 'chris'] Wyrenderowany wynik na stronie to tomjasonchris

Następnie chcę połączyć wszystkie nazwy przecinkiem, więc zmieniam kod na

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

Jednak wyrenderowany wynik to [Object], [Object], [Object].

Nie wiem, jak interpretować obiekt, aby stał się komponentem reagującym do renderowania. Jakieś sugestie ?

Xiaohe Dong
źródło

Odpowiedzi:

147

Prostym rozwiązaniem jest użycie reduce()bez drugiego argumentu i bez rozprzestrzeniania poprzedniego wyniku:

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

Bez drugiego argumentu, reduce()będzie rozpocząć w indeksie 1 zamiast 0, a React jest całkowicie zadowolony z zagnieżdżonych tablic.

Jak napisano w komentarzach, chcesz tego używać tylko dla tablic z co najmniej jednym elementem, ponieważ reduce()bez drugiego argumentu wyrzuci pustą tablicę. Zwykle nie powinno to stanowić problemu, ponieważ i tak chcesz wyświetlić niestandardowy komunikat z napisem „to jest puste” dla pustych tablic.

Aktualizacja dla maszynopisu

Możesz tego użyć w Typescript (bez typu niebezpiecznego any) z React.ReactNodeparametrem typu na .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
źródło
3
If the array is empty and no initialValue was provided, TypeError would be thrown.. więc nie będzie działać dla pustej tablicy.
Eugene Krevenets
6
Zauważ również, że to rekursywnie zagnieżdża prevtablicę. np . [1, 2, 3, 4]staje się [[[1,",",2],",",3],",",4].
Tamlyn
2
@Tamlyn: Czy uważasz, że moja pierwotna wzmianka o twoim punkcie w odpowiedzi („React jest doskonale zadowolony z zagnieżdżonych tablic”) jest wystarczająco jasna, czy powinienem to rozwinąć?
Maarten ter Horst
1
Czy ktoś sprawił, że to rozwiązanie działało z React + TypeScript?
Matt Dell
1
@Jstuff Zakończyłem używanie dowolnego. .reduce((prev: JSX.Element, current: JSX.Element): any => [prev, (<span>&nbsp;</span>), current])
Matt Dell
26

Możesz użyć reducedo połączenia wielu elementów tablicy:

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

To inicjalizuje akumulator wartością null, więc możemy zawinąć pierwszy element w tablicy. Dla każdego kolejnego elementu w tablicy tworzymy nową tablicę, która zawiera wszystkie poprzednie elementy za pomocą ...-operator, dodajemy separator, a następnie następny element.

Array.prototype.reduce ()

devsnd
źródło
2
dzięki, dokładnie to, czego potrzebowałem. Możesz usunąć zerową kontrolę i trójskładnik, inicjując akumulator jako pustą tablicę
Larry
7
@Larry, jeśli usuniesz znak zerowy i trójskładnik i przekażesz [] jako inicjator, jak uniknąć początkowego przecinka? ['a'].reduce((a, e) => [...a, ',', e],[])plony [",", "a"]. Przekazanie żadnego inicjatora nie działa, chyba że tablica jest pusta, w takim przypadku zwraca TypeError.
Guy,
@ Guy, masz całkowitą rację - mój błąd polegał na tym, że łączyłem wartości spacjami i brakowało mi pustego zakresu w renderowanym wyjściu
Larry
12

Zaktualizuj za pomocą React 16: Teraz możliwe jest bezpośrednie renderowanie ciągów, więc możesz uprościć swój kod, usuwając wszystkie bezużyteczne <span>tagi.

const list = ({ data }) => data.reduce((prev, curr) => [ prev, ', ', curr ]);
Miękisz
źródło
8

Zaakceptowana odpowiedź w rzeczywistości zwraca tablicę tablic, ponieważ prevza każdym razem będzie tablicą. React jest wystarczająco inteligentny, aby to zadziałało, ale jest podatny na problemy w przyszłości, takie jak złamanie algorytmu różnicującego Reacts podczas podawania kluczy do każdego wyniku mapy.

Nowa React.Fragmentfunkcja pozwala nam to zrobić w łatwy do zrozumienia sposób, bez podstawowych problemów.

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

Dzięki temu React.Fragmentmożemy po prostu umieścić separator ,poza zwracanym kodem HTML, a React nie będzie narzekać.

Jstuff
źródło
2
Świetne rozwiązanie, ale musisz usunąć przecinek z ostatniego elementu tablicy
indapublic
Zawsze możesz użyć tokarki i indeksu, aby to rozwiązać.
Jstuff
2
Możesz usunąć ostatni przecinek za pomocą tej modyfikacji: <React.Fragment key = {index}> <span> {t} </span> {index === this.props.data.length - 1? '': ','} </React.Fragment>
JohnP
7

<span>{t}</span>wracasz jest przedmiotem, a nie ciąg. Sprawdź dokumenty reakcji na ten temat https://facebook.github.io/react/docs/jsx-in-depth.html#the-transform

Używając .join()na zwróconej tablicy z map, dołączasz do tablicy obiektów.[object Object], ...

Możesz umieścić przecinek wewnątrz, <span></span>aby był renderowany tak, jak chcesz.

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

przykład: https://jsbin.com/xomopahalo/edit?html,js,output

oobgam
źródło
3

Mój wariant:

{this.props.data
    .map(item => <span>{item}</span>)
    .map((item, index) => [index > 0 && ', ', item ])}
Fen1kz
źródło
2

Jeśli chcę tylko wyrenderować tablicę komponentów oddzielonych przecinkami, zwykle uważam, że jest reducezbyt rozwlekła. Krótszym rozwiązaniem w takich przypadkach jest

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

{index > 0 && ', '} wyświetli przecinek, po którym nastąpi spacja przed wszystkimi elementami tablicy z wyjątkiem pierwszego.

Jeśli chcesz, aby oddzielić drugiego do ostatniego elementu i ostatni przez coś innego, powiedzmy ciąg ' and ', można zastąpić {index > 0 && ', '}z

{index > 0 && index !== arr.length - 1 && ', '}
{index === arr.length - 1 && ' and '}
Kazimierza
źródło
2

Używając es6, możesz zrobić coś takiego:

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

A potem biegnij:

listComponents
  .map(item => <span key={item.id}> {item.t} </span>)
  .reduce(joinComponents, [])
Marco Antônio
źródło
1

Użyj tablicy zagnieżdżonej, aby zachować „,” na zewnątrz.

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

Zoptymalizuj go, zapisując dane w tablicy i modyfikując ostatni element zamiast sprawdzania, czy jest to ostatni element przez cały czas.

  let processedData = this.props.data.map((element, index) => [<span key={index}>{element}</span>, ", "])
  processedData [this.props.data.length - 1].pop()
  <div>
      {processedData}
  </div>
inc
źródło
0

To zadziałało dla mnie:

    {data.map( ( item, i ) => {
                  return (
                      <span key={i}>{item.propery}</span>
                    )
                  } ).reduce( ( prev, curr ) => [ prev, ', ', curr ] )}
qin.ju
źródło
0

Jak wspomniał Pith , React 16 pozwala na bezpośrednie użycie łańcuchów, więc zawijanie ciągów w znaczniki span nie jest już potrzebne. Opierając się na odpowiedzi Maartena , jeśli chcesz od razu zająć się niestandardową wiadomością (i uniknąć wyrzucania błędu na pustą tablicę), możesz poprowadzić operację za pomocą potrójnej instrukcji if na właściwości length w tablicy. To mogłoby wyglądać mniej więcej tak:

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>
  }
}
Fredric Waadeland
źródło
0

Powinno to zwrócić płaską tablicę. Obsługuje przypadek s z niemożliwym do iteracji pierwszym obiektem, dostarczając początkową pustą tablicę i filtrując niepotrzebny pierwszy przecinek z wyniku

[{}, 'b', 'c'].reduce((prev, curr) => [...prev, ', ', curr], []).splice(1) // => [{}, 'b', 'c']
Calin ortan
źródło
0

Czy tylko ja uważam, że w odpowiedziach ma miejsce dużo niepotrzebnego rozprzestrzeniania się i zagnieżdżania? Punkty za zwięzłość, jasne, ale pozostawia to otwarte drzwi do kwestii skalowania lub zmiany sposobu reagowania na zagnieżdżone tablice / fragmenty.

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>

Jedna tablica, bez zagnieżdżania. Nie ma też seksownego tworzenia łańcuchów metod, ale jeśli często to robisz, zawsze możesz dodać ją do prototypu tablicy lub zawinąć w funkcję, która również pobiera wywołanie zwrotne mapowania, aby zrobić to wszystko za jednym razem.

Lokasenna
źródło
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
źródło