¿Cómo puedo renderizar elementos React repetidos?

81

He escrito un código para renderizar elementos repetidos en ReactJS, pero odio lo feo que es.

render: function(){
  var titles = this.props.titles.map(function(title) {
    return <th>{title}</th>;
  });
  var rows = this.props.rows.map(function(row) {
    var cells = [];
    for (var i in row) {
      cells.push(<td>{row[i]}</td>);
    }
    return <tr>{cells}</tr>;
  });
  return (
    <table className="MyClassName">
      <thead>
        <tr>{titles}</tr>
      </thead>
      <tbody>{rows}</tbody>
    </table>
  );
} 

¿Existe una mejor manera de lograrlo?

(Me gustaría incrustar forbucles dentro del código de la plantilla o algún enfoque similar).

fadedbee
fuente
3
Sí, eso es lo que quiero, pero la respuesta aceptada es muy similar al código feo que ya estoy escribiendo. Debe haber una mejor manera ...
fadedbee
Espero que esto ayude a stackoverflow.com/a/37600679/1785635
abhirathore2006

Respuestas:

130

Puede poner expresiones entre llaves. Observe en el JavaScript compilado por qué un forbucle nunca sería posible dentro de la sintaxis JSX; JSX equivale a llamadas a funciones y argumentos de función azucarados. Solo se permiten expresiones.

(Además: recuerde agregar keyatributos a los componentes renderizados dentro de los bucles).

JSX + ES2015 :

render() {
  return (
    <table className="MyClassName">
      <thead>
        <tr>
          {this.props.titles.map(title =>
            <th key={title}>{title}</th>
          )}
        </tr>
      </thead>
      <tbody>
        {this.props.rows.map((row, i) =>
          <tr key={i}>
            {row.map((col, j) =>
              <td key={j}>{col}</td>
            )}
          </tr>
        )}
      </tbody>
    </table>
  );
} 

JavaScript :

render: function() {
  return (
    React.DOM.table({className: "MyClassName"}, 
      React.DOM.thead(null, 
        React.DOM.tr(null, 
          this.props.titles.map(function(title) {
            return React.DOM.th({key: title}, title);
          })
        )
      ), 
      React.DOM.tbody(null, 
        this.props.rows.map(function(row, i) {
          return (
            React.DOM.tr({key: i}, 
              row.map(function(col, j) {
                return React.DOM.td({key: j}, col);
              })
            )
          );
        })
      )
    )
  );
} 
Ross Allen
fuente
Buenas capturas. Agregué el paréntesis que faltaba en la parte superior y cambié de fora map. Como no sé cuáles son los datos en cada una de las estructuras de datos, asumí que las filas son matrices y usé el índice de la iteración para key. Cuando se agregan / eliminan datos de la matriz, eso provocará una nueva representación innecesaria. Debe cambiar keya un valor que identifique de manera única los datos y que no dependa del orden y / o tamaño de la matriz.
Ross Allen
@ssorallen Gracias por tu respuesta. Dejaré esta pregunta abierta durante un par de días, en caso de que haya una forma mejor de hacerlo, antes de aceptar su respuesta. Creo que mi problema es que JSX no parece tener un escape sin expresión, para forbucles y similares, ni tiene una sintaxis de plantilla para bucles.
fadedbee
1
@chrisdew JSX no es un lenguaje de plantilla, es un azúcar sintáctico para JavaScript antiguo. No obtendrá operadores como espera de los lenguajes de plantilla. Intente pegar el código de esta respuesta en el compilador JSX en vivo para comprender lo que está haciendo y por qué un forbucle nunca sería posible.
Ross Allen
¿Podría agregar el evento onClick para cada fila y pasar el elemento de la fila a la función? Estoy intentando algo como esto, pero obtengo TypeError no detectado: No se puede leer la propiedad 'clickHandler' de clickHandler () {console.log ('en la fila de clics'); } <tbody> {this.props.rows.map (function (row, i) {return (<tr key = {i} onClick = this.clickHandler ()> {row.map (function (col, j) {return <td key = {j}> {col} </td>;})} </tr>);})} </tbody>
Kiran
No importa, he agregado .bind (esto) al método del mapa, entonces el método se involucra para cada fila. Gracias.
Kiran
13

Para ampliar la respuesta de Ross Allen, aquí hay una variante un poco más limpia que usa la sintaxis de flecha ES6.

{this.props.titles.map(title =>
  <th key={title}>{title}</th>
)}

Tiene la ventaja de que la parte JSX está aislada (no returno ;), lo que facilita la colocación de un bucle a su alrededor.

Jodiug
fuente
13

Dado Array(3)que creará una matriz no iterable , debe rellenarse para permitir el uso del mapmétodo Array. Una forma de "convertir" es destruirlo dentro de Array-brackets, lo que "obliga" a que el Array se llene con undefinedvalores, al igual queArray(N).fill(undefined)

<table>
    { [...Array(3)].map((_, index) => <tr key={index}/>) }
</table>

Otra forma sería a través de Array fill():

<table>
    { Array(3).fill(<tr/>) }
</table>

El problema con el ejemplo anterior es la falta de keyapoyo, que es imprescindible .
( No se recomienda usar un iterador indexcomo key)


Nodos anidados:

const tableSize = [3,4]
const Table = (
    <table>
        <tbody>
        { [...Array(tableSize[0])].map((tr, trIdx) => 
            <tr key={trIdx}> 
              { [...Array(tableSize[1])].map((a, tdIdx, arr) => 
                  <td key={trIdx + tdIdx}>
                  {arr.length * trIdx + tdIdx + 1}
                  </td>
               )}
            </tr>
        )}
        </tbody>
    </table>
);

ReactDOM.render(Table, document.querySelector('main'))
td{ border:1px solid silver; padding:1em; }
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<main></main>

vsync
fuente
2
Pasé al menos 2 horas para poder realizar un bucle dentro del bucle en la función de renderizado. Gracias. Salvaste mi día.
Shwe
2
¿No podrías hacer esto en lugar de usar el mapa? Array.from({length: 5}, (value, index) => <tr />)
creativehandle
2

En el espíritu de la programación funcional, hagamos que nuestros componentes sean un poco más fáciles de trabajar utilizando abstracciones.

// converts components into mappable functions
var mappable = function(component){
  return function(x, i){
    return component({key: i}, x);
  }
}

// maps on 2-dimensional arrays
var map2d = function(m1, m2, xss){
  return xss.map(function(xs, i, arr){
    return m1(xs.map(m2), i, arr);
  });
}

var td = mappable(React.DOM.td);
var tr = mappable(React.DOM.tr);
var th = mappable(React.DOM.th);

Ahora podemos definir nuestro render de esta manera:

render: function(){
  return (
    <table>
      <thead>{this.props.titles.map(th)}</thead>
      <tbody>{map2d(tr, td, this.props.rows)}</tbody>
    </table>
  );
}

jsbin


Una alternativa a nuestro map2d sería una función de mapa con curry, pero la gente tiende a evitar curry.

Bandido
fuente
Esas funciones no le dan al desarrollador la oportunidad de especificar el keyatributo. El uso del índice significa que agregar / eliminar elementos obligará a volver a renderizar los objetos que podrían no haber cambiado.
Ross Allen
Es cierto, pero el 95% de las veces las claves basadas en índices son suficientes. Prefiero apartarme un poco de las raras excepciones.
Brigand
De acuerdo, sería bastante fácil asegurarse de que las claves sean únicas en cualquier nivel dado si fuera necesario, me gusta.
Ryan Ore
2

Esta es, en mi opinión, la forma más elegante de hacerlo (con ES6). Cree una instancia de su matriz vacía con 7 índices y mapee en una línea:

Array.apply(null, Array(7)).map((i)=>
<Somecomponent/>
)

felicitaciones a https://php.quicoto.com/create-loop-inside-react-jsx/

Chris
fuente
1
¡Agradable! Parece que también puedes hacerloArray(...Array(10)).map((v, i) => <SomeComponent />)
magritte