Bucle dentro de React JSX

1571

Estoy tratando de hacer algo como lo siguiente en React JSX (donde ObjectRow es un componente separado):

<tbody>
    for (var i=0; i < numrows; i++) {
        <ObjectRow/>
    } 
</tbody>

Me doy cuenta y entiendo por qué esto no es JSX válido, ya que JSX se asigna a llamadas de función. Sin embargo, viniendo de la tierra de la plantilla y siendo nuevo en JSX, no estoy seguro de cómo lograría lo anterior (agregando un componente varias veces).

9
  • 51
    Es importante tener en cuenta que en JSX necesita las etiquetas {} alrededor de su sintaxis de JavaScript. Esto puede ayudar a facebook.github.io/react/docs/… . Isaiah Grey 24/12/15 a las 18:25
  • 36
    let todos = this.props.todos.map((todo) => {return <h1>{todo.title}</h1>})OverCoder 15 de enero de 2017 a las 19:58
  • Relacionado: ¿Cómo puedo renderizar elementos React repetidos? vsync 3/07/19 a las 12:11
  • @OverCoder ¿Por qué pondrías el retorno completo en la etiqueta {}? Sería => return <h1> {todo.title} </h1> ¿No es así? pravin poudel 17/03/20 a las 7:41
  • 5
    @pravinpoudel en realidad esa respuesta es antigua, más como let todos = this.props.todos.map(t => <h1>{t.title}</h1>):)OverCoder 17/03/20 a las 10:16
1469

Piense en ello como si estuviera llamando a funciones de JavaScript. No puede usar un forbucle donde irían los argumentos de una llamada de función:

return tbody(
    for (var i = 0; i < numrows; i++) {
        ObjectRow()
    } 
)

Vea cómo tbodyse le pasa un forbucle a la función como argumento, lo que genera un error de sintaxis.

Pero puedes hacer una matriz y luego pasarla como argumento:

var rows = [];
for (var i = 0; i < numrows; i++) {
    rows.push(ObjectRow());
}
return tbody(rows);

Básicamente, puede usar la misma estructura cuando trabaja con JSX:

var rows = [];
for (var i = 0; i < numrows; i++) {
    // note: we are adding a key prop here to allow react to uniquely identify each
    // element in this array. see: https://reactjs.org/docs/lists-and-keys.html
    rows.push(<ObjectRow key={i} />);
}
return <tbody>{rows}</tbody>;

Por cierto, mi ejemplo de JavaScript es casi exactamente en lo que se transforma ese ejemplo de JSX. Juega con Babel REPL para tener una idea de cómo funciona JSX.

1
  • 64
    React necesita actualizar dom de manera eficiente, en este caso, el padre debe asignar un keya cada hijo. REF: facebook.github.io/react/docs/…qsun 2 de ene. De 2016 a las 22:36
975

No estoy seguro de si esto funcionará para su situación, pero a menudo el mapa es una buena respuesta.

Si este era tu código con el bucle for :

<tbody>
    for (var i=0; i < objects.length; i++) {
        <ObjectRow obj={objects[i]} key={i}>
    }
</tbody>

Podrías escribirlo así con el mapa :

<tbody>
    {objects.map(function(object, i){
        return <ObjectRow obj={object} key={i} />;
    })}
</tbody>

Sintaxis de ES6:

<tbody>
    {objects.map((object, i) => <ObjectRow obj={object} key={i} />)}
</tbody>
2
  • 51
    El mapa tiene más sentido si iteratee es una matriz; Un bucle for es apropiado si es un número. Code Whisperer 26/04/15 a las 18:40
  • 27
    <tbody>{objects.map((o, i) => <ObjectRow obj={o} key={i}/>}</tbody>utilizando el soporte ES6 de Reactify o Babel. Distortum 27/07/2015 a las 10:24
532

Si aún no tiene una matriz para map()que le guste la respuesta de @ FakeRainBrigand, y desea alinear esto para que el diseño de la fuente corresponda a la salida más cercana que la respuesta de @ SophieAlpert:

Con sintaxis ES2015 (ES6) (funciones de extensión y flecha)

http://plnkr.co/edit/mfqFWODVy8dKQQOkIEGV?p=preview

<tbody>
  {[...Array(10)].map((x, i) =>
    <ObjectRow key={i} />
  )}
</tbody>

Re: transpiling con Babel, su página de advertencias dice que Array.fromes necesario para la difusión, pero en la actualidad ( v5.8.23) ese no parece ser el caso cuando se difunde un archivo Array. Tengo un problema de documentación abierto para aclarar eso. Pero utilícelo bajo su propio riesgo o polyfill.

Vainilla ES5

Array.apply

<tbody>
  {Array.apply(0, Array(10)).map(function (x, i) {
    return <ObjectRow key={i} />;
  })}
</tbody>

IIFE en línea

http://plnkr.co/edit/4kQjdTzd4w69g8Suu2hT?p=preview

<tbody>
  {(function (rows, i, len) {
    while (++i <= len) {
      rows.push(<ObjectRow key={i} />)
    }
    return rows;
  })([], 0, 10)}
</tbody>

Combinación de técnicas de otras respuestas.

Mantenga el diseño de la fuente correspondiente a la salida, pero haga que la parte en línea sea más compacta:

render: function () {
  var rows = [], i = 0, len = 10;
  while (++i <= len) rows.push(i);

  return (
    <tbody>
      {rows.map(function (i) {
        return <ObjectRow key={i} index={i} />;
      })}
    </tbody>
  );
}

Con sintaxis y Arraymétodos de ES2015

Con Array.prototype.fillpodría hacer esto como una alternativa al uso de spread como se ilustra arriba:

<tbody>
  {Array(10).fill(1).map((el, i) =>
    <ObjectRow key={i} />
  )}
</tbody>

(Creo que en realidad podrías omitir cualquier argumento fill(), pero no estoy al 100% en eso). Gracias a @FakeRainBrigand por corregir mi error en una versión anterior de la fill()solución (ver revisiones).

key

En todos los casos, el keyatributo alivia una advertencia con la compilación de desarrollo, pero no es accesible en el niño. Puede pasar un atributo adicional si desea que el índice esté disponible en el niño. Consulte Listas y claves para obtener más información.

0
107

Simplemente usando el método map Array con sintaxis ES6:

<tbody>
  {items.map(item => <ObjectRow key={item.id} name={item.name} />)} 
</tbody>

No olvide la keypropiedad.

1
  • Agrego una clave aleatoria 'Math.random ()' en lugar de id, no funciona bien cuando setState dentro de los niños, ¿tiene alguna idea de por qué? Oliver D 19 dic 19 a las 18:04
98

El uso de la función de mapa de matriz es una forma muy común de recorrer una matriz de elementos y crear componentes de acuerdo con ellos en React . Esta es una excelente manera de hacer un bucle que es bastante eficiente y es una forma ordenada de hacer sus bucles en JSX . Es no la única manera de hacerlo, pero la forma preferida.

Además, no olvide tener una clave única para cada iteración según sea necesario. La función de mapa crea un índice único a partir de 0, pero no se recomienda usar el índice producido, pero si su valor es único o si hay una clave única, puede usarlos:

<tbody>
  {numrows.map(x=> <ObjectRow key={x.id} />)}
</tbody>

Además, algunas líneas de MDN si no está familiarizado con la función de mapa en Array:

map calls a provided callback function once for each element in an array, in order, and constructs a new array from the results. callback is invoked only for indexes of the array which have assigned values, including undefined. It is not called for missing elements of the array (that is, indexes that have never been set, which have been deleted or which have never been assigned a value).

callback is invoked with three arguments: the value of the element, the index of the element, and the Array object being traversed.

If a thisArg parameter is provided to the map, it will be used as callback's this value. Otherwise, the value undefined will be used as its this value. This value ultimately observable by the callback is determined according to the usual rules for determining the this seen by a function.

map does not mutate the array on which it is called (although callback, if invoked, may do so).

1
  • Array#mapno es asincrónico! philraj 17 de agosto de 2017 a las 1:51
72

Si ya está usando lodash, la _.timesfunción es útil.

import React, { Component } from "react";
import Select from "./Select";
import _ from "lodash";

export default class App extends Component {
  render() {
    return (
      <div className="container">
        <ol>
          {_.times(3, (i) => (
            <li key={i}>repeated 3 times</li>
          ))}
        </ol>
      </div>
    );
  }
}
0
50

También puede extraer fuera del bloque de retorno:

render: function() {
    var rows = [];
    for (var i = 0; i < numrows; i++) {
        rows.push(<ObjectRow key={i}/>);
    } 

    return (<tbody>{rows}</tbody>);
}
0
40

Es posible que desee verificar React Templates , que le permite usar plantillas de estilo JSX en React, con algunas directivas (como rt-repeat).

Su ejemplo, si usó react-templates, sería:

<tbody>
     <ObjectRow rt-repeat="obj in objects"/>
</tbody>
0
38

Hay varias formas de hacerlo. JSX finalmente se compila en JavaScript, por lo que siempre que esté escribiendo JavaScript válido, estará bien.

Mi respuesta tiene como objetivo consolidar todas las maravillosas formas ya presentadas aquí:

Si no tiene una matriz de objeto, simplemente el número de filas:

Dentro del returnbloque, creando Arrayy usando Array.prototype.map:

render() {
  return (
    <tbody>
      {Array(numrows).fill(null).map((value, index) => (
        <ObjectRow key={index}>
      ))}
    </tbody>
  );
}

Fuera del returnbloque, simplemente use un JavaScript for loop normal :

render() {
  let rows = [];
  for (let i = 0; i < numrows; i++) {
    rows.push(<ObjectRow key={i}/>);
  }
  return (
    <tbody>{rows}</tbody>
  );
}

Expresión de función invocada inmediatamente:

render() {
  return (
    <tbody>
      {() => {
        let rows = [];
        for (let i = 0; i < numrows; i++) {
          rows.push(<ObjectRow key={i}/>);
        }
        return rows;
      }}
    </tbody>
  );
}

Si tienes una matriz de objetos

Dentro del returnbloque, .map()cada objeto de un <ObjectRow>componente:

render() {
  return (
    <tbody>
      {objectRows.map((row, index) => (
        <ObjectRow key={index} data={row} />
      ))}
    </tbody>
  );
}

Fuera del returnbloque, simplemente use un JavaScript for loop normal :

render() {
  let rows = [];
  for (let i = 0; i < objectRows.length; i++) {
    rows.push(<ObjectRow key={i} data={objectRows[i]} />);
  }
  return (
    <tbody>{rows}</tbody>
  );
}

Expresión de función invocada inmediatamente:

render() {
  return (
    <tbody>
      {(() => {
        const rows = [];
        for (let i = 0; i < objectRows.length; i++) {
          rows.push(<ObjectRow key={i} data={objectRows[i]} />);
        }
        return rows;
      })()}
    </tbody>
  );
}
1
  • 2
    Excelentes respuestas: varias técnicas, todas usan solo javascript simple que muestra el poder de javascript, gracias a sus diversas formas de realizar tareas similares. Lex Soft 15/03/19 a las 14:59
29

Si numrows es una matriz, es muy simple:

<tbody>
   {numrows.map(item => <ObjectRow />)}
</tbody>

El tipo de datos de matriz en React es mucho mejor. Una matriz puede respaldar una nueva matriz y admitir filtrar, reducir, etc.

1
  • 1
    Esta no es una respuesta decente ya que no hace uso de una clave. kojow7 20 de septiembre de 2017 a las 21:55
26

Hay varias respuestas que apuntan al uso de la mapdeclaración. Aquí está un ejemplo completo utilizando un repetidor dentro del listaCaracterísticas componente a la lista de funciones componentes basado en una estructura de datos llamada JSON características .

const FeatureList = ({ features, onClickFeature, onClickLikes }) => (
  <div className="feature-list">
    {features.map(feature =>
      <Feature
        key={feature.id}
        {...feature}
        onClickFeature={() => onClickFeature(feature.id)}
        onClickLikes={() => onClickLikes(feature.id)}
      />
    )}
  </div>
); 

Puede ver el código FeatureList completo en GitHub. El accesorio de características se enumera aquí .

1
  • Cuando se trata de datos JSON, como cuando se recuperan datos de la base de datos mediante la API de recuperación, confío en el método Array.prototype.map. Es una forma conveniente y probada para ese propósito. Lex Soft 15/03/19 a las 14:32
25

Para realizar un bucle varias veces y regresar, puede lograrlo con la ayuda de fromy map:

<tbody>
  {
    Array.from(Array(i)).map(() => <ObjectRow />)
  }
</tbody>

dónde i = number of times


Si desea asignar ID de clave únicos a los componentes renderizados, puede usarlos React.Children.toArraycomo se propone en la documentación de React

React.Children.toArray

Devuelve la estructura de datos opaca de los niños como una matriz plana con claves asignadas a cada niño. Útil si desea manipular colecciones de niños en sus métodos de renderizado, especialmente si desea reordenar o dividir this.props.children antes de pasarlo.

Note:

React.Children.toArray() changes keys to preserve the semantics of nested arrays when flattening lists of children. That is, toArray prefixes each key in the returned array so that each element’s key is scoped to the input array containing it.

<tbody>
  {
    React.Children.toArray(
      Array.from(Array(i)).map(() => <ObjectRow />)
    )
  }
</tbody>
23

Digamos que tenemos una variedad de elementos en su estado:

[{name: "item1", id: 1}, {name: "item2", id: 2}, {name: "item3", id: 3}]

<tbody>
    {this.state.items.map((item) => {
        <ObjectRow key={item.id} name={item.name} />
    })}
</tbody>
1
  • Creo que es posible que deba deshacerse del {} después del mapa (elemento), que pareció funcionar para mí. Ian 10/12/2016 a las 17:22
23

Si opta por convertir este retorno interno () del método de representación , la opción más sencilla sería utilizar el método map () . Asigne su matriz a la sintaxis JSX usando la función map (), como se muestra a continuación ( se usa la sintaxis ES6 ).


Dentro del componente principal :

<tbody>
   { objectArray.map(object => <ObjectRow key={object.id} object={object.value}>) }
</tbody>

Tenga en cuenta que el keyatributo se agrega a su componente secundario. Si no proporcionó un atributo clave, puede ver la siguiente advertencia en su consola.

Warning: Each child in an array or iterator should have a unique "key" prop.

Nota: Un error común que la gente comete es usarlo indexcomo clave al iterar. El uso indexdel elemento como clave es un antipatrón, y puede leer más sobre él aquí . En resumen, si es que no una lista estática, nunca utilice indexcomo clave.


Ahora, en el componente ObjectRow , puede acceder al objeto desde sus propiedades.

Dentro del componente ObjectRow

const { object } = this.props

O

const object = this.props.object

Esto debería traerle el objeto que pasó del componente principal a la variable objecten el componente ObjectRow . Ahora puede escupir los valores en ese objeto de acuerdo con su propósito.


Referencias:

método map () en JavaScript

ECMAScript 6 o ES6

19

Una posibilidad de ECMAScript 2015 / Babel es usar una función generadora para crear una matriz de JSX:

function* jsxLoop(times, callback)
{
    for(var i = 0; i < times; ++i)
        yield callback(i);
}

...

<tbody>
    {[...jsxLoop(numrows, i =>
        <ObjectRow key={i}/>
    )]}
</tbody>
18

ES2015 Array. De con la función de mapa + tecla

Si no tiene nada .map()que pueda usar Array.from()con la mapfunción para repetir elementos:

<tbody>
  {Array.from({ length: 5 }, (value, key) => <ObjectRow key={key} />)}
</tbody>
18

Yo uso esto:

gridItems = this.state.applications.map(app =>
          <ApplicationItem key={app.Id} app={app } />
);

PD: ¡nunca olvides la clave o tendrás muchas advertencias!

1
  • O use el índice de matriz si sus elementos no tienen una .Idpropiedad, comoitems.map( (item, index) => <Foo key={index}>{item}</Foo>kontur 30 de marzo de 2017 a las 9:26
17

... O también puede preparar una matriz de objetos y asignarla a una función para obtener la salida deseada. Prefiero esto, porque me ayuda a mantener la buena práctica de codificar sin lógica dentro del retorno de render.

render() {
const mapItem = [];
for(let i =0;i<item.length;i++) 
  mapItem.push(i);
const singleItem => (item, index) {
 // item the single item in the array 
 // the index of the item in the array
 // can implement any logic here
 return (
  <ObjectRow/>
)

}
  return(
   <tbody>{mapItem.map(singleItem)}</tbody>
  )
}
16

Simplemente use .map()para recorrer su colección y devolver <ObjectRow>elementos con accesorios de cada iteración.

Suponiendo que objectshay una matriz en alguna parte ...

<tbody>
  { objects.map((obj, index) => <ObjectRow obj={ obj } key={ index }/> ) }
</tbody>
14

Esto se puede hacer de varias formas.

  1. Como se sugirió anteriormente, antes de returnalmacenar todos los elementos en la matriz

  2. Bucle en el interior return

    Método 1

     let container =[];
        let arr = [1,2,3] //can be anything array, object 
    
        arr.forEach((val,index)=>{
          container.push(<div key={index}>
                         val
                         </div>)
            /** 
            * 1. All loop generated elements require a key 
            * 2. only one parent element can be placed in Array
            * e.g. container.push(<div key={index}>
                                        val
                                  </div>
                                  <div>
                                  this will throw error
                                  </div>  
                                )
            **/   
        });
        return (
          <div>
             <div>any things goes here</div>
             <div>{container}</div>
          </div>
        )
    

    Método 2

       return(
         <div>
         <div>any things goes here</div>
         <div>
            {(()=>{
              let container =[];
              let arr = [1,2,3] //can be anything array, object 
              arr.forEach((val,index)=>{
                container.push(<div key={index}>
                               val
                               </div>)
                             });
                        return container;     
            })()}
    
         </div>
      </div>
    )
    
1
  • Sí. En mi proyecto actual, utilizo el método 1, es decir, usando el método Array.prototype, forEach () para crear un elemento de selección HTML que se completa con datos de la base de datos. Sin embargo, es más probable que los reemplace con el método Array.prototype.map (), ya que el método del mapa parece más compacto (menos código). Lex Soft 15/03/19 a las 15:08
14

Tiendo a favorecer un enfoque en el que la lógica de programación ocurre fuera del valor de retorno de render. Esto ayuda a mantener lo que realmente se hace fácil de asimilar.

Entonces probablemente haría algo como:

import _ from 'lodash';

...

const TableBody = ({ objects }) => {
  const objectRows = objects.map(obj => <ObjectRow object={obj} />);      

  return <tbody>{objectRows}</tbody>;
} 

Es cierto que esta es una cantidad tan pequeña de código que insertarlo podría funcionar bien.

4
  • Gran fan del mapa lodash para iterar a través de objetos. Me inclinaría a import { map } from 'lodash'hacerlo, por lo que no está importando todas las funciones lodash si no las necesita. Stretch0 8 de marzo de 2018 a las 11:11
  • En estos días, ni siquiera necesitamos un mapa lodash, solo haga un mapa sobre la matriz directamente. Adam Donahue 9/03/18 a las 22:19
  • Sí, pero no puede recorrer un objeto con es6 map(), solo matrices. Eso es lo que estaba diciendo que lodash es bueno para recorrer un objeto. Stretch0 12 mar. 18 a las 9:26
  • Pensé que te referías objectscomo en una lista de cosas, mi error. Adam Donahue 13/03/18 a las 13:24
13

Su código JSX se compilará en código JavaScript puro, cualquier etiqueta será reemplazada por ReactElementobjetos. En JavaScript, no puede llamar a una función varias veces para recopilar sus variables devueltas.

Es ilegal, la única forma es usar una matriz para almacenar las variables devueltas por la función.

O puede usar el Array.prototype.mapque está disponible desde JavaScript ES5 para manejar esta situación.

Tal vez podamos escribir otro compilador para recrear una nueva sintaxis JSX para implementar una función de repetición como la de Angularng-repeat .

13

Por supuesto, puede resolver con un .map como lo sugiere la otra respuesta. Si ya usa Babel , podría pensar en usar declaraciones de control jsx .

Requieren un poco de configuración, pero creo que vale la pena en términos de legibilidad (especialmente para desarrolladores que no reaccionan). Si usa un linter, también hay instrucciones eslint-plugin-jsx-control- .

13

Aquí tienes una solución sencilla.

var Object_rows = [];
for (var i = 0; i < numrows; i++) {
  Object_rows.push(<ObjectRow />);
}
<tbody>{Object_rows}</tbody>;

No se requiere mapeo ni código complejo. Solo necesita empujar las filas a la matriz y devolver los valores para representarla.

1
  • Al crear el elemento de selección html, esta técnica similar fue la que me vino a la mente en primer lugar, así que la usé, aunque uso el método forEach (). Pero cuando releí el documento de React sobre temas de Listas y Claves, descubrí que usan el método map () como también se muestra en varias respuestas aquí. Esto me hace pensar que es la forma preferida. Estoy de acuerdo, ya que parece más compacto (menos código). Lex Soft 15/03/19 a las 15:20
10

También puede utilizar una función autoinvocada:

return <tbody>
           {(() => {
              let row = []
              for (var i = 0; i < numrows; i++) {
                  row.push(<ObjectRow key={i} />)
              }
              return row

           })()}
        </tbody>
3
  • 1
    El uso de funciones anónimas dentro del renderizado no se considera una buena práctica en react, ya que estas funciones deben volver a crearse o descartarse en cada renderizado. Vlatko Vlahek 29 de septiembre de 2017 a las 7:25
  • @VlatkoVlahek lo está confundiendo con accesorios de función que se vuelven a crear en cada ciclo de renderizado, lo que podría conducir a un rendimiento deficiente a gran escala. La creación de una función anónima en cualquier otro lugar no afectará el rendimiento de manera significativa. Emile Bergeron 20 feb.20 a las 16:54
  • 1
    @EmileBergeron Esto fue hace un tiempo jaja. Estoy de acuerdo si esto no se usa como accesorio, no hay diferencia. Vlatko Vlahek 20 feb.20 a las 18:36
10

Lo uso como

<tbody>
  { numrows ? (
     numrows.map(obj => { return <ObjectRow /> }) 
    ) : null
  }
</tbody>
10

Dado que está escribiendo sintaxis JavaScript dentro del código JSX, debe envolver su código JavaScript entre llaves.

row = () => {
   var rows = [];
   for (let i = 0; i<numrows; i++) {
       rows.push(<ObjectRow/>);
   }
   return rows;
}
<tbody>
{this.row()}
</tbody>
0
10

Aquí hay una muestra de la documentación de React, JavaScript Expressions as Children :

function Item(props) {
  return <li>{props.message}</li>;
}

function TodoList() {
  const todos = ['finish doc', 'submit pr', 'nag dan to review'];
  return (
    <ul>
      {todos.map((message) => <Item key={message} message={message} />)}
    </ul>
  );
}

En cuanto a tu caso, te sugiero que escribas así:

function render() {
  return (
    <tbody>
      {numrows.map((roe, index) => <ObjectRow key={index} />)}
    </tbody>
  );
}

Tenga en cuenta que la clave es muy importante, porque React usa la clave para diferenciar los datos en la matriz.

9

Puedes hacer algo como:

let foo = [1,undefined,3]
{ foo.map(e => !!e ? <Object /> : null )}
9

Con el tiempo, el idioma se vuelve más maduro y, a menudo, nos topamos con problemas comunes como este. El problema es hacer un bucle de un componente 'n' veces.

{[...new Array(n)].map((item, index) => <MyComponent key={index} />)}

donde, n -es el número de veces que desea realizar un bucle. itemserá indefinido y indexserá como de costumbre. Además, ESLint desaconseja el uso de un índice de matriz como clave.

Pero tiene la ventaja de no tener que inicializar la matriz antes y, lo que es más importante, evitar el forbucle ...

Para evitar el inconveniente de que el elemento no está definido, puede usar un _, de modo que se ignore al aplicar pelusa y no arroje ningún error de formación de pelusa, como

{[...new Array(n)].map((_, index) => <MyComponent key={index} />)}