Hace unos días que impartimos el curso de React en vivo donde se cubrieron desde conceptos básicos de la propia librería a cómo adentrarnos en el desarrollo de una aplicación web real con esta tecnología.
Durante el curso de esta formación los alumnos realizaron preguntas muy interesantes tanto durante el curso en vivo como por el canal de slack que abrimos para el mismo. Hemos decidido seleccionar un grupo de preguntas y material en este post, espero que te sea interesante.
Por cierto si tienes ganas de aprender React, puedes adquirir la grabación de este curso por sólo 29 € pinchando en este enlace.
¿Cómo arrancar un proyecto con React desde cero? ¿Qué necesito?
Prerrequisitos
Necesitarás tener instalado Node. Al instalar Node, podemos abrir una terminal y ejecutar el comando npm
para listar los paquetes necesarios.
Boilerplate
Tienes en el repositorio react-training-ts (o react-training-es6 para la versión en ES6) todos los ejemplos estructurados con su código fuente junto con un README.md explicando paso a paso para ejecutarlo. En el ejemplo 02 Sample App / 00 Boilerplate se encuentra una plantilla que puedes utilizar como punto de inicio o configuración inicial.
IDE / editor
Como editor podemos usar, por ejemplo, Atom o VisualStudio Code.
Plugins necesarios para trabajar con Atom:
- En el caso de usar TypeScript: atom-typescript
- En el caso de ES6, language-babel para el highlight del código, auto-complete JSX, etc.
Otros plugins de Atom que te pueden ayudar:
- file-icons: para cambiar los iconos del tree-view
- En este post te recomendamos algunos plugins muy utilizados.
Para VS Code no hay necesidad de instalar plugins para poder arrancar, ya que viene con todo lo necesario para trabajar con TypeScript, incluso para trabajar con JSX. Aun así hay plugins como:
- vscode-icons: para cambiar los iconos del tree-view.
Librerías
En el README.md
están los pasos necesarios para instalar todos los paquetes:
Las librerías y herramientas utilizadas son:
- Webpack 2 y webpack-dev-server: en el
README.md
también incluimos los loaders y plugins que usamos, además de cómo configurarlos. - Core-js: para habilitar los polyfills de ES6, para la versión TypeScript.
- Babel: para habiliar los polyfills de ES6, para la versión ES6.
- Bootstrap: para maquetación.
- React y ReactDOM: para la estructuración de la UI.
- React-router: para la navegación entre páginas.
- toastr: para las notificaciones.
¿Qué funciones aporta React? ¿cómo las distingo de funciones que nombramos nosotros mismos?
React es una librería que tiene como finalidad estructurar la presentación y para ello la principal herramienta que te da es la de poder crear componentes mediante funciones puras o como clases que hereden de Component
o PureComponent
(es ES5 utilizando el método React.createClass
). React dentro de un componente te ofrece entre otros métodos:
setState
para poder actualizar el estado del componente y provocar una actualización mediante los métodosshouldComponentUpdate
,componentWillUpdate
,render
ycomponentDidUpdate
, estos tres últimos sishouldComponentUpdate
devuelvetrue
.forceUpdate
para provocar un render sin pasar porshouldComponentUpdate
(rara vez utilizado por lo general).
Además de los métodos del componente, React te ofrece otros métodos útiles dentro de React.Children
como forEach
, map
, etc para poder iterar sobre los hijos del componente (generalmente utilizado a la hora de lanzar un render por componentes cuyo fin es servir de contenedor a otros componentes), React.cloneElement
para poder crear una copia de un componente, etc. Para más información sobre los métodos de la API de React los puedes ver en el siguiente enlace.
ReactDOM, por otro lado, además del método render
tiene otros métodos como unmountComponentAtNode
para desmontar un componente o findDOMNode
para obtener el elemento del DOM donde ha sido montado el componente de React. Todos estos métodos son utilizados para realizar acciones en el DOM sobre componentes de React, para más información.
Si trabajas con TypeScript desde el editor puedes ir a la definición de una función para saber si procede de React o si, por el contrario, las hemos creado nosotros. También puedes añadir el atributo private
para métodos que no necesiten ser expuestos de forma pública (aunque tras la transpilación se hagan públicos).
¿Cómo funciona la relación entre componentes padre-hijo en React?
El componente padre (que puede ser un componente contenedor o presentacional) cambia la presentación de sus componentes hijos mediante un paso de propiedades props
que por lo general están basadas en el estado del padre, o basadas en algún cálculo computado de propiedades o simplemente con las mismas propiedades que provengan de algún componente de más arriba.
Los componentes hijos pueden cambiar el estado del padre mediante funciones obtenidas de props
que pueden desencadenar (o no) cambios en los hijos.
¿Es lo mismo utilizar una arrow function que .bind(this)
cuando pasas una función a un componente hijo?
Aunque la finalidad es la misma, hay una ligera diferencia: con .bind(this)
creas una nueva función y con las arrow functions no. A la hora de añadir la función a un componente hijo utilizando arrow functions te quitas un paso adicional, que es, crear la función enlazada, con el coste de, quizás, romper con la forma en la que se definen funciones en una clase:
class App { doSomething() { /* ... */ } doOtherThing = () => { /* ... */ }; }
Sin embargo en un bucle creado por map
para asignar una función mediante high order functions el efecto de crear una nueva función permanece, independientemente de como esté definida. Te dejo dos enlaces de interés:
¿Qué hay que tener en cuenta a la hora de generar una build a producción con React?
De acuerdo a la documentación de React en el apartado Developmnet and Production Versions de descarga e instalación citan que para usar React en entorno de producción hay que especificarle a Webpack que utilice DefinePlugin
para establecer el entorno a production y UglifyJsPlugin
para minificar. Dichas configuraciones se deben añadir en el apartado plugins de Webpack, por ejemplo:
plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }), new webpack.optimize.UglifyJsPlugin({ comments: false, sourceMap: true, compress: { warnings: false }, }) ],
Webpack tiene un parámetro citado en su documentación que es -p que es equivalente a ejecutar los parámetros --optimize-minimize --define process.env.NODE_ENV="'production'" además de añadir UglifyJsPlugin, LoaderOptionsPlugin y establecer la variable de entorno en Node. Con el parámetro -p puedes descartar la configuración anterior de DefinePlugin y UglifyJsPlugin a no ser que quieras añadirle alguna opción que no utilice por defecto (como keep_fnames, screw_ie8, etc).
Por útimo recordad a la hora de desplegar el habilitar la compresión gzip en el servidor para reducir aún más el tamaño de los ficheros.
¿Cómo se trabaja con componentes Stateless en TypeScript?
Para crear componentes basados en funciones puras se puede añadir el tipado React.StatelessComponent<Props> al componente para añadirle ciertas propiedades sin que el compilador de TypeScript dé algún error o tengamos que definir el tipo de datos a mano:
- propTypes para definir el tipo de datos de las props del componente para validaciones en tiempo de ejecución.
MyComponent.propTypes = { myProp: React.PropTypes.string };
- contextTypes para definir el tipo de datos de las propiedades inyectadas por context (no recomendable el uso de context).
MyComponent.contextTypes = { myProp: React.PropTypes.string };
- defaultProps para establecer un valor por defecto a las propiedades si no son recibidas.
MyComponent.defaultProps = { myProp: 'awesome' };
- displayName para que el nombre del componente sea usado para mostrar información de debug en caso de haber errores.
MyComponent.displayName = 'MyComponent';
shallowCompare, spread operator, promises, destructuring, ¿qué es todo esto?
Aquí te dejo una lista de términos que han ido saliendo durante el curso con una breve explicación:
SPA: SPA viene de las siglas Single Page Application o aplicación basada en una página, es aquel tipo de aplicación web donde la navegación no es gestionada por el servidor sino por el cliente, por lo que no se recarga la página al navegar por diferentes rutas y se obtiene una experiencia más fluida. Los ficheros estáticos se cargan en la página principal o a demanda cuando ocurra dicha navegación.
Destructuring: Es una característica de ES6 que permite realizar declaraciones de variables (también en los argumentos de las funciones) de propiedades de objetos, esto incluye a los arrays, que también son objetos para JavaScript. Os dejo un par de ejemplos:
Destructuring de objetos:
function getCoordinates() { return { latitude: 43.33302, longitude: 8.33021 }; } // Con destructuring: const { latitude, longitude } = getCoordinates(); // latitude === 43.33302 // longitude === 8.33021 // Sin destructuring: const coordinates = getCoordinates(); const latitude = coordinates.latitude; // 43.33302 const longitude = coordinates.longitude; // 8.33021
Destructuring de arrays:
const competitorResults = ['James', 'John', 'Alice', 'Mary', 'Adolf', 'Paula', 'Wallace']; // Con destructuring: const [first, second, third] = competitorResults; // first === 'James' // second === 'John' // third === 'Alice' // Sin destructuring: const first = competitorResults[0]; // 'James' const second = competitorResults[1]; // 'John' const third = competitorResults[2]; // 'Alice'
En este enlace de la MDN te dejo más información sobre el destructuring.
Promises: Las promesas son otra característica de ES6 que permite encadenar un conjunto de llamadas, generalmente asíncronas, mediante los métodos then y/o catch. Dichos métodos reciben como parámetro una función cuyo parámetro a su vez tiene el resultado de la promesa. Esto se creó como alternativa al clásico manejo de funciones asíncronas que se hacían pasando como parámetro un callback que seria llamado una vez se terminara de realizar las operaciones. Cuando tienes un buen número de funciones encadenadas es más legible el uso de promesas... si no puedes meterte en lo que se conoce como callback hell donde el código se te va escalonando y es más difícil de mantener.
- Un ejemplo de callback hell.
- Aquí un enlace con más información de las promesas.
Callback: Un callback no es más que una función que es pasada como argumento a otra función con el fin de ser invocada en algún momento de su ejecución. Unos de los ejemplos más básicos de uso de callbacks son en composición y las asignaciones de eventos de elementos en el DOM:
function myClickHandler(event) { // do some stuff... } document.getElementById('myButton').addEventListener('click', myClickHandler, false);
Yarn: Yarn es un gestor de paquetes que puede ser utilizado en sustitución de npm. Por lo general es más rápido instalando dependencias, ya que cachea los paquetes descargados para que no vuelvan a ser obtenidos del servidor, además que realiza descargas en paralelo. Aunque salió hace relativamente poco, cumple bastante bien su función aunque para algunos paquetes que necesiten realizar operaciones complejas (scripts de post instalación, etc) puede que haya alguno que de problemas. Tiene una API con comandos muy parecidos (algunos idénticos) a los de npm. En este enlace os dejo una pequeña chuletilla de su uso.
Spread/Rest operator: Los operadores Rest/Spread (representados como puntos suspensivos ...) son parte de una característica muy potente de JavaScript que se ha aportado en ES6/7 que se utilizan principalmente para agrupar (rest) o separar (spread) propiedades de un objeto o piezas de un array permitiendo, por ejemplo, crear variables. En el curso lo hemos usado para crear nuevos objetos en base a uno existente, un ejemplo:
// Spread operator con objetos const task = { title: 'tidy room', completed: false }; const completedTask = {task, completed: true}; console.log(completedTask); // { title: 'tidy room', completed: true } // Spread operator con arrays const firstGroup = ['a', 'b']; const secondGroup = ['c', 'd']; const thirdGroup = ['e', 'f']; const all = [firstGroup, secondGroup, thirdGroup]; console.log(all); // ['a', 'b', 'c', 'd', 'e, 'f']
High Order Component: Un High Order Component o componente de alto nivel es un componente (función o clase que extiende de Component o PureComponent) que envuelve un componente recibido como parámetro y devuelve otro componente dotado de funcionalidad añadida o compuesto por otros componentes reutilizables. Esta práctica se conoce como composición:
// Componentes de alto nivel utilizando herencia inversa: function LoginRequired(WrappedComponent) { // return class SessionRequiredComponent extends WrappedComponent { render() { if(this.props.loggedIn) { return super.render(); } return <h1>Not logged in</h1>; } } }
Un ejemplo de High Order Components es usado por `react-redux` para enlazar componentes al store mediante `connect`. Os dejo un par de enlaces:
ShallowCompare: ShallowCompare es un tipo de comprobación a nivel de referencia utilizado por los componentes que heredan de PureComponent en el método shouldComponentUpdate (si no lo sobreescribimos) para evitar hacer llamadas al método render cuando las propiedades y estado del componente no varían.
Supongamos que tenemos un componente <Hello />
que recibe la propiedad name
del estado de un componente <App />
dando lugar a <Hello name={this.state.name} />
, al cambiar el estado de <App />
si el valor de name
no cambia y es de nuevo pasada al componente <Hello />
éste no llamará al método render
ya que name
tendrá el mismo valor y shouldComponentUpdate
devolverá false
.
Context: La propiedad context es utilizada para pasar datos entre componentes con gran nivel de profundidad sin pasarlos manualmente mediante props. Usar context es una práctica rara vez utilizada, hasta el punto que la propia documentación de React la desaconseja. Context puede ser utilizado en algunos de los métodos de ciclo de vida de un componente ya que es inyectado automáticamente como parámetro.