Una inmersión en frío en React Native (Tutorial para principiantes)
Publicado: 2022-03-11Cuando se anunció React Native, las primeras reacciones fueron abrumadoramente positivas. Tradicionalmente, cuando pensamos en tecnologías web en el espacio móvil, nos vienen a la mente cosas como Apache Cordova, que nos permiten empaquetar sitios web o aplicaciones web como aplicaciones para plataformas móviles. En este tutorial para principiantes, veremos la arquitectura de React Native, la filosofía detrás de React Native y cómo se diferencia de otras soluciones en el mismo espacio. Al final del artículo, habremos transformado una aplicación React "Hello World" en una aplicación React Native.
Comencemos diciendo que React Native es una tecnología relativamente nueva. Ha estado disponible oficialmente desde marzo de 2015, habiendo estado en versión beta privada desde el comienzo de ese año, y utilizado internamente en Facebook durante un tiempo antes de eso. El dicho "Roma no se construyó en un día" generalmente se aplica también a la tecnología. Herramientas como grunt
y plataformas como Node.js tardaron años en madurar. En el mundo web, las cosas se mueven rápidamente, y con una gran cantidad de marcos, paquetes y herramientas que salen todos los días, los desarrolladores tienden a volverse un poco más escépticos y no quieren subirse al carro de todas las exageraciones solo para darse cuenta de que terminaron en una situación de bloqueo de proveedores. Nos adentraremos en lo que hace que React Native sea especial, por qué es una tecnología en la que vale la pena entrar y cubriremos algunos casos en los que no todo son unicornios y arcoíris.
Bajo el capó
Cuando se habla de tecnologías web en dispositivos móviles, las soluciones disponibles suelen caer en una de las siguientes categorías.
Empaquetado de aplicaciones web en un navegador web móvil
La aplicación web vive en un navegador móvil, normalmente llamado WebView. Sin ninguna refactorización importante, un sitio web o aplicación web funciona en el dispositivo móvil. Es posible que debamos considerar los eventos del navegador móvil, como tocar o escuchar los cambios de orientación del dispositivo y la pantalla más pequeña para una experiencia de usuario completa, pero tenemos una versión móvil que funciona con un esfuerzo mínimo. Cordova/PhoneGap es la opción más popular en esta categoría. Desafortunadamente, esta opción tiene una gran desventaja: en algunos casos, las aplicaciones desarrolladas con Cordova son significativamente más lentas que las aplicaciones nativas, especialmente para aplicaciones con muchos gráficos. En otros casos, el sistema operativo móvil en realidad no ofrece todas las funciones de WebView que están disponibles en el navegador móvil. La experiencia del usuario también puede diferir de las aplicaciones nativas; esto puede ocurrir debido a la aplicación o la propia plataforma. Este problema puede ir desde que las barras de desplazamiento no se sienten igual hasta tener un retraso notable al tocar los elementos.
Compilación con tecnologías nativas
Una solución completamente diferente es crear una base de código nativa al final. Esto sucede al transformar el código fuente original en otro lenguaje de programación. Cambiamos el rendimiento nativo por una capa de abstracción con algunas incertidumbres. En los casos de soluciones de código cerrado, ni siquiera estamos seguros de lo que sucede debajo del capó y con qué tipo de caja negra estamos tratando. En otros casos, no estamos seguros de cuánto romperá nuestro código la próxima actualización del sistema operativo móvil y cuándo estarán disponibles las correcciones o actualizaciones. Un ejemplo popular de esta categoría sería Haxe.
Usar una capa de JavaScript
Aquí, usamos el motor de JavaScript del entorno móvil y ejecutamos nuestro JavaScript allí. Los controles nativos se asignan a objetos y funciones de JavaScript, por lo que cuando invocáramos una función llamada fancyButtonRightHere()
, aparecería un botón en la pantalla. NativeScript o Appcelerator Titanium son ejemplos bien conocidos de esta categoría.
React Native podría clasificarse como algo de la tercera categoría. Para las versiones de iOS y Android, React Native usa JavaScriptCore bajo el capó, que es el motor de JavaScript predeterminado en iOS. JavaScriptCore también es el motor de JavaScript en los navegadores Safari de Apple. Los desarrolladores de OS X e iOS pueden interactuar directamente con él si así lo desean.
Una gran diferencia es que React Native ejecuta el código JavaScript en un hilo separado, por lo que la interfaz de usuario no se bloquea y las animaciones deben ser sedosas y fluidas.
Reaccionar es la característica clave
Vale la pena señalar que "React" en React Native no se coloca allí por accidente. Para React Native, necesitamos comprender qué ofrece exactamente React. Los siguientes conceptos funcionan de la misma manera tanto en React como en React Native, aunque estos ejemplos de código están diseñados para ejecutarse en el navegador.
Punto de entrada de renderizado único
Cuando echamos un vistazo a un componente React simple, lo primero que podemos notar es que el componente tiene una función de render
. De hecho, React arroja un error si no hay una función de renderización definida dentro del componente.
var MyComponent = function() { this.render = function() { // Render something here }; };
Lo especial es que no nos metemos con los elementos DOM aquí, pero devolvemos una construcción basada en XML que representa lo que se representará en el DOM. Esta construcción basada en XML se llama JSX.
var MyComponent = function() { this.render = function() { return <div className="my-component">Hello there</div>; }; };
Un transformador JSX especial toma todo ese código de aspecto XML y lo convierte en funciones. Así es como se verá el componente después de la transformación:
var MyComponent = function() { this.render = function() { return React.createElement("div", { className: "my-component" }, "Hello there"); }; };
La mayor ventaja es que al echar un vistazo rápido al componente, siempre sabemos lo que se supone que debe hacer. Por ejemplo, un <FriendList />
podría generar varios componentes <Friend />
. No podemos representar nuestros componentes en ningún otro lugar que no sea dentro de la función de render
, por lo que nunca existe la preocupación de no saber de dónde proviene exactamente nuestro componente representado.
Flujo de datos unidireccional
Para construir el contenido de un componente, React proporciona propiedades o accesorios para abreviar. Similar a los atributos XML, pasamos los accesorios directamente a un componente y luego podemos usar los accesorios dentro del componente construido.
var Hello = function(props) { this.render = function() { return <div className="my-component">Hello {props.name}</div>; }; }; var Greeter = function() { this.render = function() { return <Hello name="there" /> } };
Esto lleva a que nuestros componentes estén en una estructura similar a un árbol, y solo se nos permite pasar datos cuando construimos elementos secundarios.
Volver a renderizar en los cambios
Además de accesorios, los componentes también pueden tener un estado interno. El ejemplo más destacado de ese comportamiento sería un contador de clics que actualiza su valor cuando se presiona un botón. El número de clics en sí se guardaría en el estado.
Cada uno de los cambios de propiedad y estado desencadena una nueva representación completa del componente.
DOM virtuales
Ahora, cuando todo se vuelve a renderizar cuando cambian los accesorios o el estado, ¿cómo es que React se está desempeñando tan bien? El ingrediente mágico es el "DOM virtual". Cada vez que se necesita volver a renderizar algo, se genera una representación virtual del DOM actualizado. El DOM virtual consiste en representaciones ligeras de elementos modelados según el árbol de componentes, lo que hace que el proceso de generación sea mucho más eficiente que la generación de elementos DOM reales. Antes de aplicar los cambios al DOM real, se realizan comprobaciones para determinar exactamente en qué parte del árbol de componentes ocurrieron los cambios, se crea una diferencia y solo se aplican esos cambios específicos.
Primeros pasos con este tutorial de React Native
Hay ciertos requisitos previos que los principiantes deberán configurar para desarrollar React Native. Dado que iOS fue la primera plataforma admitida y la que tratamos en este tutorial, necesitamos macOS y Xcode, al menos la versión 6.3. También se necesita Node.js. Lo que ayuda es instalar Watchman a través del administrador de paquetes Brew con brew install watchman
. Si bien esto no es necesariamente necesario, ayuda cuando se trata de muchos archivos dentro de nuestro proyecto React Native.
Para instalar React Native, simplemente necesitamos instalar la aplicación de línea de comandos de React Native con npm install -g react-native-cli
. Llamar al comando react-native
nos ayuda a crear una nueva aplicación React Native. La ejecución react-native init HelloWorld
crea una carpeta llamada HelloWorld
en la que se puede encontrar el código repetitivo.
Transformar una aplicación React
Dado que React es la característica clave y los principios básicos que provienen de la biblioteca React, echemos un vistazo a lo que necesitamos para transformar una aplicación mínima de React "Hello World" en una React Native.
Usamos algunas características de ES2015 en este ejemplo de código, específicamente clases. Es completamente factible quedarse con React.createClass
o usar una forma de función similar al patrón de módulo popular.
var React = require('react'); class HelloThere extends React.Component { clickMe() { alert('Hi!'); } render() { return ( <div className="box" onClick={this.clickMe.bind(this)}>Hello {this.props.name}. Please click me.</div> ); } } React.render(<HelloThere name="Component" />, document.getElementById('content'));
Paso 1: adopte los módulos CommonJS
En el primer paso, debemos cambiar el requisito de que el módulo React use react-native
en su lugar.
var React = require('react-native'); class HelloThere extends React.Component { clickMe() { alert('Hi!'); } render() { return ( <div className="box" onClick={this.clickMe.bind(this)}>Hello {this.props.name}. Please click me.</div> ); } } React.render(<HelloThere name="Component" />, document.getElementById('content'));
Lo que suele ser parte de la tubería de herramientas cuando se desarrolla una aplicación web React es una parte integral de React Native.
Paso 2: No hay DOM
No es sorprendente que no haya DOM en dispositivos móviles. Donde anteriormente usamos <div />
, necesitamos usar <View />
y donde usamos <span />
, el componente que necesitamos aquí es <Text />
.
import React from 'react'; import {View, Text, Alert} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert('hi!'); } render() { return ( <View className="box" onClick={this.clickMe.bind(this)}>Hello {this.props.name}. Please click me.</View> ); } } React.render(<HelloThere name="Component" />, document.getElementById('content'));
Si bien es muy conveniente colocar texto directamente en elementos <div />
, en el mundo nativo, el texto no se puede colocar directamente en una <View />
. Para eso necesitamos insertar un componente <Text />
.
import React from 'react'; import {View, Text, Alert} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert('hi!'); } render() { return ( <View className="box" onClick={this.clickMe.bind(this)}> <Text>Hello {this.props.name}. Please click me.</Text> </View> ); } } React.render(<HelloThere name="Component" />, document.getElementById('content'));
Paso 3: los estilos en línea son el camino a seguir
React Native nos permite usar el modelado de Flexbox en lugar de perder el tiempo con float
y inline-block
que estamos tan familiarizados en el mundo web. Lo interesante es que React Native no usa CSS.
import React from 'react'; import {View, Text, StyleSheet, Alert} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert('hi!'); } render() { return ( <View style={styles.box} onClick={this.clickMe.bind(this)}> <Text>Hello {this.props.name}. Please click me.</Text> </View> ); } } var styles = StyleSheet.create({ box: { borderColor: 'red', backgroundColor: '#fff', borderWidth: 1, padding: 10, width: 100, height: 100 } }); React.render(<HelloThere name="Component" />, document.getElementById('content'));
El uso de estilos en línea parece desconcertante para los principiantes. Es similar a la transición por la que los desarrolladores de React tuvieron que pasar cuando se enfrentaron a JSX y anteriormente usaban motores de plantillas como Handlebars o Jade.
La idea es que no tenemos hojas de estilo globalmente en la forma en que usamos CSS. Declaramos las hojas de estilo directamente en el nivel del componente, por lo que tenemos toda la información que necesitamos para ver qué hace nuestro componente, el diseño que crea y los estilos que aplica.
import React from 'react'; import {Text} from 'react-native'; var Headline = function(props) { this.render = () => <Text style={headlineStyle.text}>{props.caption}</Text>; }; var headlineStyles = StyleSheet.create({ text: { fontSize: 32, fontWeight: 'bold' } }); module.exports = Headline;
Paso 4: Manejo de eventos
El equivalente a hacer clic en páginas web es tocar un elemento en el dispositivo móvil. Cambiemos nuestro código para que aparezca la "alerta" cuando toquemos el elemento.

import React from 'react'; import {View, Text, StyleSheet, TouchableOpacity, Alert} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert("Hi!") } render() { return ( <TouchableOpacity onPress={this.clickMe()}> <View style={styles.box}> <Text>Hello {this.props.name}. Please click me.</Text> </View> </TouchableOpacity> ); } } var styles = StyleSheet.create({ box: { borderColor: 'red', backgroundColor: '#fff', borderWidth: 1, padding: 10, width: 100, height: 100 } }); React.render(<HelloThere name="Component" />, document.getElementById('content'));
En lugar de que los eventos estén disponibles directamente en los componentes <View />
, necesitamos usar explícitamente elementos que activen eventos, en nuestro caso, un evento táctil al presionar la vista. Hay diferentes tipos de componentes táctiles disponibles, cada uno de ellos proporciona una respuesta visual diferente.
Paso 5: personalizar el comportamiento en todas las plataformas
Es posible detectar en qué plataforma se está ejecutando la aplicación React Native, accediendo al valor de Platform.OS
. Digamos que, en el ejemplo anterior, queríamos mostrar un mensaje de alerta diferente según la plataforma en la que estamos ejecutando. Podemos hacerlo así:
... clickMe() { var message = ''; if(Platform.OS == 'ios') { message = 'Welcome to iOS!'; } else if(Platform.OS == 'android') { message = 'Welcome to Android!'; } Alert.alert(message); } ...
Alternativamente, también está disponible el método de select
, que proporciona una sintaxis similar a la de un interruptor:
… clickMe() { Alert.alert(Platform.select({ ios: 'Welcome to iOS!', android: 'Welcome to Android!' }) ); } ...
Paso 6: fuentes personalizadas y react-native link
Para agregar una fuente personalizada, debemos pasar por algunos obstáculos. En primer lugar, asegúrese de que el nombre completo de la fuente y el nombre del archivo de la fuente sean iguales: iOS usará el nombre completo de la fuente para elegir la fuente, mientras que Android usará el nombre del archivo.
Entonces, si el nombre completo de su fuente es myCustomFont
, asegúrese de que el nombre del archivo de la fuente sea myCustomFont.ttf
.
Después de eso, necesitamos crear una carpeta de activos y apuntar npm a ella. Podemos hacerlo creando primero la carpeta, en assets/fonts
en el directorio raíz de la aplicación. Cualquier otro directorio servirá, pero este es el nombre convencional que se usa para el directorio de fuentes.
Podemos decirle a npm dónde tenemos nuestros activos agregando una propiedad de Assets
en la sección de integración de npm de React, rnpm:
"rnpm": { "Assets": [ "./assets/fonts/" ] }
Después de haber hecho todo eso, finalmente podemos ejecutar react-native link
. Eso copiará las fuentes en los directorios correctos y agregará el xml necesario a info.plist en iOS.
Una vez hecho esto, podemos usar nuestra fuente simplemente haciendo referencia a ella en cualquier hoja de estilo por su nombre completo. Usémoslo en nuestro elemento Text
:
import React from 'react'; import {View, Text, StyleSheet, TouchableOpacity, Alert} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert("Hi!") } render() { return ( <TouchableOpacity onPress={this.clickMe()}> <View style={styles.box}> <Text style={styles.message}>Hello {this.props.name}. Please click me.</Text> </View> </TouchableOpacity> ); } } var styles = StyleSheet.create({ box: { borderColor: 'red', backgroundColor: '#fff', borderWidth: 1, padding: 10, width: 100, height: 100 }, message: { fontFamily: 'myCustomFont' } }); React.render(<HelloThere name="Component" />, document.getElementById('content'));
Paso 7: Mover cosas alrededor
React Native usa las mismas reglas que Flexbox para diseñar componentes. Digamos que queríamos colocar nuestro botón en la parte inferior de la pantalla: envolvamos nuestra TouchableOpacity
con un contenedor View
:
<View style={styles.container}> <TouchableOpacity onPress={this.clickMe.bind(this)}> <View style={styles.box}> <Text style={styles.message}>Hello {this.props.name}. Please click me.</Text> </View> </TouchableOpacity> </View>
Y ahora definamos el estilo del container
, junto con los otros estilos ya definidos:
container: { flex: 1, justifyContent: 'center', alignItems: 'center' }
Centrémonos en justifyContent
el contenido y en los elementos alignItems
. Esas dos propiedades controlan cómo se alinea el componente respectivamente a lo largo de su eje principal y su eje secundario. De forma predeterminada, el eje principal es el vertical y el eje secundario es el eje horizontal (puede cambiar eso configurando la propiedad flexDirection
en row
).
justifyContent
tiene seis valores posibles en los que se puede establecer:
-
flex-start
colocará todos los elementos juntos, al comienzo del cuadro delimitador del componente. -
flex-end
colocará todos los elementos al final. -
center
colocará todos los elementos en el centro del cuadro delimitador. -
space-around
distribuirá los componentes de manera uniforme y los centrará en sus cuadros delimitadores creados. -
space-evenly
también distribuirá los componentes de manera uniforme, pero intentará dejar la misma cantidad de espacio entre los componentes y los otros límites. -
space-between
distribuirá los componentes manteniendo igual el espaciado entre los componentes adyacentes.
alignItems
se puede establecer en cuatro valores posibles: flex-start
, flex-end
, center
y stretch
. Los tres primeros se comportan como lo hacen para justifyContent
el contenido, mientras que stretch
hará que el componente ocupe todo el espacio disponible a lo largo del eje, de modo que el eje se llene por completo.
Entonces, dado que queremos que nuestra TouchableOpacity
se muestre en la parte inferior y centrada a lo largo del eje horizontal, podemos cambiar el estilo de esta manera:
container: { flex: 1, justifyContent: 'flex-end', alignItems: 'center' }
Se puede encontrar más información sobre los valores que deben tener los elementos de justifyContent
y alignItems
aquí y aquí.
Paso 8: Registro de la aplicación
Al desarrollar con React para el navegador, solo necesitamos definir un punto de montaje, llamar a React.render
y dejar que React haga su magia. En React Native, esto es un poco diferente.
import React from 'react'; import {View, Text, StyleSheet, TouchableOpacity, Alert, Platform} from 'react-native'; class HelloThere extends React.Component { clickMe() { Alert.alert(Platform.select({ ios: 'Welcome to iOS!', android: 'Welcome to Android!' })); } render() { return ( <View style={styles.container}> <TouchableOpacity onPress={this.clickMe.bind(this)}> <View style={styles.box}> <Text style={styles.message}>Hello {this.props.name}. Please click me.</Text> </View> </TouchableOpacity> </View> ); } } var styles = StyleSheet.create({ container: { flex: 1, justifyContent: 'flex-start', alignItems: 'center' }, box: { borderColor: 'red', backgroundColor: '#fff', borderWidth: 1, padding: 10, width: 100, height: 100 }, message: { fontFamily: 'myCustomFont' } }); var MainComponent = function() { this.render = function() { return <HelloThere name="Component" />; } }; AppRegistry.registerComponent('MainComponent', function() { return MainComponent; });
Tenemos que registrar el componente para el lado Objective-C de las cosas, lo cual se hace usando el objeto AppRegistry
. El nombre que damos tiene que coincidir con el nombre dentro del proyecto Xcode.
Nuestra aplicación Hello World React Native tiene significativamente más líneas de código que su contraparte web, pero por otro lado, React Native lleva la separación de preocupaciones un poco más allá, especialmente porque los estilos se definen con el componente.
Como nota al margen, no debemos volver a vincular el método clickMe
a this
contexto en el método de render
, especialmente si nuestra aplicación React (Native) se vuelve un poco más compleja. Vuelve a vincular el método en cada llamada de procesamiento, lo que puede convertirse en mucho. La alternativa es vincular el método dentro del constructor.
Ejecutar la aplicación
Para ejecutar la aplicación, debemos reemplazar el contenido del archivo index.ios.js
con el fragmento de código de nuestra aplicación transformada del último paso. Luego, solo tenemos que abrir el proyecto Xcode y presionar el botón grande Ejecutar. Primero, se abrirá una terminal con el servidor React Native y luego aparecerá la ventana del simulador. El servidor React Native crea un paquete, que luego buscará la aplicación nativa. Esto permite un ciclo de desarrollo rápido similar al desarrollo web, donde los cambios se reflejarán casi instantáneamente en el simulador.
Para Android, basta con agregar lo siguiente a su archivo package.json
, en scripts
:
"android-linux": "react-native bundle --platform android --dev false --entry-file index.ios.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/ main/res && react-native run-android"
Y luego ejecute npm run android-linux
. Asegúrese de que el directorio android/app/src/main/assets
exista de antemano.
Después de que aparezca la terminal, nuestra aplicación aparecerá en el simulador. Al presionar CMD+D se mostrará un menú de desarrollo. Al hacer clic en el cuadro, se mostrará una alerta. La versión de iOS:
Y Android hace algo como esto:
Para la distribución, tener una aplicación que apunte a un servidor de desarrollo local no estaría funcionando para nosotros. Por esta razón, podemos crear el paquete para usarlo cuando el servidor React Native no se está ejecutando con el comando react-native bundle
. En ese caso, debemos actualizar el método didFinishLaunchingWithOptions
de AppDelegate
para usar el paquete sin conexión.
Esta aplicación de ejemplo también está disponible en Github.
Trabajando con React Native
Otra cosa que vale la pena mencionar es que no solo usamos conceptos de React y JavaScript para nuestras aplicaciones móviles, sino que algunos de los flujos de trabajo a los que están acostumbrados los desarrolladores web también están disponibles con React Native. Cuando venimos del desarrollo web, estamos acostumbrados a las herramientas de desarrollo, la inspección de elementos y la recarga en vivo.
La forma en que funciona React Native es que pone todos nuestros archivos JavaScript en un paquete. Este paquete se sirve desde un servidor o se incluye junto con la aplicación. El primero es increíblemente útil para el desarrollo en el Simulador, ya que podemos habilitar la recarga en vivo. El menú de desarrollador que proporciona React no es tan poderoso como las herramientas de desarrollo de Chrome, pero brinda una experiencia de desarrollador muy similar a la web con recarga y depuración en vivo con las herramientas de desarrollo/depuración de Chrome (o Safari).
Los desarrolladores web están familiarizados con JSFiddle o JSBin, un área de juegos en línea para pruebas web rápidas. Existe un entorno similar que nos permite probar React Native en un navegador web.
React Native: una opción sólida y moderna
Originalmente había sugerido un enfoque más cauteloso para React Native. Hoy, es una opción madura y sólida.
Una de las grandes ventajas de React es que no se impone en su flujo de trabajo, ya que solo representa la capa de visualización. ¿Quieres definir tu propia canalización de Grunt? ¿O preferirías usar Webpack? ¿Y utilizará Backbone.js para las necesidades de su modelo? ¿O quieres ir con objetos simples de JavaScript? Las respuestas a todas estas preguntas dependen totalmente de usted, porque React no impone ninguna restricción a estas opciones. Como lo expresó el sitio oficial: "Dado que React no hace suposiciones sobre el resto de su pila de tecnología, es fácil probarlo en una pequeña función en un proyecto existente".
Hasta cierto punto, esto también es cierto para React Native. Los desarrolladores móviles pueden integrar React Native como parte de su aplicación, aprovechar el flujo de trabajo de desarrollo inspirado en la web y optar por integrar la biblioteca a mayor escala si es necesario.
En cualquier caso, una cosa es segura: React Native no va a desaparecer. Facebook tiene una gran participación en tener múltiples aplicaciones impulsadas por React Native en las tiendas de aplicaciones. La comunidad alrededor de React Native es enorme y continúa creciendo.