come rendere i componenti di reazione usando map e join


102

Ho un componente che visualizzerà Array of String. Il codice ha questo aspetto.

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

Funziona perfettamente bene. cioè se props.data = ['tom', 'jason', 'chris'] Il risultato visualizzato nella pagina sarebbe tomjasonchris

Quindi, voglio unire tutti i nomi usando la virgola, quindi cambio il codice in

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

Tuttavia, il risultato di rendering è [Object], [Object], [Object].

Non so come interpretare l'oggetto per diventare componenti di reazione da renderizzare. Qualche suggerimento ?

Risposte:


147

Una soluzione semplice è usare reduce()senza secondo argomento e senza diffondere il risultato precedente:

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

Senza secondo argomento, reduce()si inizia all'indice 1 invece di 0, e React è perfettamente soddisfatti matrici nidificate.

Come detto nei commenti, vuoi usarlo solo per array con almeno un elemento, perché reduce()senza un secondo argomento verrà lanciato con un array vuoto. Normalmente questo non dovrebbe essere un problema, dal momento che si desidera visualizzare un messaggio personalizzato che dice qualcosa come "questo è vuoto" per gli array vuoti comunque.

Aggiornamento per dattiloscritto

Puoi usarlo in Typescript (senza type-unsafe any) con un React.ReactNodeparametro di tipo su .map():

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

3
If the array is empty and no initialValue was provided, TypeError would be thrown.. quindi non funzionerà per array vuoto.
Eugene Krevenets

6
Nota anche che questo annida ricorsivamente l' prevarray. ad esempio [1, 2, 3, 4]diventa [[[1,",",2],",",3],",",4].
Tamlyn,

2
@ Tamlyn: Pensi che la mia citazione originale del tuo punto nella risposta ('React è perfettamente soddisfatto degli array annidati') è abbastanza chiara o dovrei approfondire questo?
Maarten ter Horst

1
Qualcuno ha fatto funzionare questa soluzione con React + TypeScript?
Matt Dell

1
@ Jstuff ho evitato di dover usare qualsiasi. .reduce((prev: JSX.Element, current: JSX.Element): any => [prev, (<span>&nbsp;</span>), current])
Matt Dell

26

È possibile utilizzare reduceper combinare più elementi di un array:

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

Questo inizializza l'accumulatore con null, quindi possiamo racchiudere il primo elemento in un array. Per ogni elemento successivo nell'array, costruiamo un nuovo array che contiene tutti gli elementi precedenti usando ...-operator, aggiungiamo il separatore e quindi l'elemento successivo.

Array.prototype.reduce ()


2
grazie, esattamente quello di cui avevo bisogno. Puoi rimuovere il controllo nullo e il ternario inizializzando l'accumulatore come un array vuoto
Larry

7
@ Larry se rimuovi il segno di spunta nullo e il ternario e passi [] come inizializzatore, come eviti una virgola iniziale? ['a'].reduce((a, e) => [...a, ',', e],[])rendimenti [",", "a"]. Non passare alcun inizializzatore funziona a meno che l'array non sia vuoto, nel qual caso restituisce un'eccezione TypeError.
Guy

@ Guy hai assolutamente ragione - il mio errore era che stavo unendo valori con spazi e mancava l'intervallo vuoto nell'output renderizzato
Larry

12

Aggiornamento con React 16: ora è possibile eseguire il rendering delle stringhe direttamente, così puoi semplificare il tuo codice rimuovendo tutti i <span>tag inutili .

const list = ({ data }) => data.reduce((prev, curr) => [ prev, ', ', curr ]);

8

La risposta accettata restituisce effettivamente un array di array perché prevsarà ogni volta un array. React è abbastanza intelligente da far funzionare questo, ma è incline a causare problemi in futuro come rompere l'algoritmo di diffing di Reacts quando si danno le chiavi a ciascun risultato della mappa.

La nuova React.Fragmentfunzionalità ci consente di farlo in modo facile da capire senza i problemi sottostanti.

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

Con React.Fragmentpossiamo semplicemente posizionare il separatore ,al di fuori dell'HTML restituito e React non si lamenterà.


2
Soluzione fantastica, ma è necessario rimuovere la virgola per l'ultimo elemento dell'array
indapublic

Puoi sempre usare un tornio e l'indice per risolverlo.
Jstuff

2
Puoi rimuovere l'ultima virgola con questa modifica: <React.Fragment key = {index}> <span> {t} </span> {index === this.props.data.length - 1? '': ','} </React.Fragment>
JohnP

7

quello <span>{t}</span>che stai restituendo è un oggetto, non una stringa. Controlla la documentazione sulla reazione https://facebook.github.io/react/docs/jsx-in-depth.html#the-transform

Usando .join()sull'array restituito da map, stai unendo l'array di oggetti.[object Object], ...

Puoi inserire la virgola all'interno di in <span></span>modo che venga renderizzata nel modo desiderato.

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

esempio: https://jsbin.com/xomopahalo/edit?html,js,output


3

La mia variante:

{this.props.data
    .map(item => <span>{item}</span>)
    .map((item, index) => [index > 0 && ', ', item ])}

2

Se voglio solo eseguire il rendering di un array di componenti separati da virgole, di solito trovo reducetroppo prolisso. Una soluzione più breve in questi casi è

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

{index > 0 && ', '} renderà una virgola seguita da uno spazio davanti a tutti gli elementi dell'array tranne il primo.

Se vuoi separare il penultimo elemento e l'ultimo con qualcos'altro, pronuncia la stringa ' and ', puoi sostituire {index > 0 && ', '}con

{index > 0 && index !== arr.length - 1 && ', '}
{index === arr.length - 1 && ' and '}

2

Usando es6 potresti fare qualcosa come:

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

E poi esegui:

listComponents
  .map(item => <span key={item.id}> {item.t} </span>)
  .reduce(joinComponents, [])

1

Usa un array annidato per mantenere "," all'esterno.

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

Ottimizzalo salvando i dati nell'array e modificando l'ultimo elemento invece di controllare sempre il suo ultimo elemento.

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

0

Questo ha funzionato per me:

    {data.map( ( item, i ) => {
                  return (
                      <span key={i}>{item.propery}</span>
                    )
                  } ).reduce( ( prev, curr ) => [ prev, ', ', curr ] )}

0

Come accennato da Pith , React 16 ti consente di utilizzare le stringhe direttamente, quindi non è più necessario avvolgere le stringhe nei tag span. Basandosi sulla risposta di Maarten , se vuoi anche gestire subito un messaggio personalizzato (ed evitare di generare un errore su un array vuoto), potresti condurre l'operazione con un'istruzione if ternaria sulla proprietà length dell'array. Potrebbe assomigliare a questo:

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

Questo dovrebbe restituire una matrice piatta. Gestisci il caso di s con il primo oggetto non iterabile fornendo un array vuoto iniziale e filtra la prima virgola non necessaria dal risultato

[{}, 'b', 'c'].reduce((prev, curr) => [...prev, ', ', curr], []).splice(1) // => [{}, 'b', 'c']

0

Sono l'unico che pensa che ci sia un sacco di inutili diffusione e nidificazione in corso nelle risposte qui? Punti per essere concisi, sicuri, ma lascia la porta aperta a problemi di scala o React che cambiano il modo in cui gestiscono array / frammenti annidati.

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>

Un array, nessuna nidificazione. Neanche il concatenamento di metodi sexy, ma se ti ritrovi a farlo spesso puoi sempre aggiungerlo al prototipo dell'array o racchiuderlo in una funzione che richiede anche un callback di mappatura per fare tutto in una volta.


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

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.