JSX
- Una expresión JSX genera un elemento (Ver siguiente sección)
- Cuando se escriban componentes multilineas, encerrarlos entre paréntesis para evitar "automatic semicolon insertion".
- Los atributos de los componentes pueden ser:
- String: van encerrados entre comillas dobles
- Expresiones JS: van encerrados entre llaves
- Instalar sublime-babel-vscode en Visual Studio Code para tener un correcto resaltado de JSX
Elementos
- Los elementos son objetos JSON simples que representan los objetos DOM.
- No es el elemento DOM, sino que es una representación simplificada del mismo.
- A modo general, solo tiene dos campos:
type: (string | ReactClass) and props: Object
y los demás elementos anidados (dentro de las props).
- No tienen métodos.
- Los elementos son inmutables. Una vez que se crean, no pueden modificarse.
- Son como los frames de una película, representan cómo debe verse la aplicación en un momento determinado
- Para actualizar la UI, es necesario crear un nuevo elemento.
- React DOM se encarga de actualizar solo los elementos de DOM que se han actualizado.
Componentes
- Los componentes son piezas reutilizables de la UI
- Sus nombres siempre deben empezar con mayúscula, sino React considera que son elementos HTML
- Un componente NUNCA debe modificar sus propiedades
- Los componentes reciben
props
como inputs y devuelven un elemento como output.
Propiedades
- Deben ser inmutables
- Se envían a traves de JSX como si fueran atributos HTML
- Se reciben en el componente a través del objeto props
Estado
- Es similar a las propiedades, pero son internas al componente
- Solo está disponible para los "Componentes de clases"
Componentes funcionales
- Son componentes definidos mediante una función
- Reciben un único argumento props
- Devuelven un componente
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
Componentes de clases
- Son componentes definidos como una clase ES6 que extiende la clase React.Component
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
- Tienen mas funcionalidades como estado y hooks para manejar el ciclo de vida del componente.
Estado
- Se puede inicializar en el constructor de la siguiente manera:
constructor(props) {
super(props);
this.state = {
someInitialValue: true
}
}
* Usar `this.setState({state: value})` para modificar el estado. Este método permite notificar que el componente debe re-renderizarse
* React puede juntar varias operaciones de actualización del estado y realizarlas en conjunto para mejorar la performance. Por lo tanto, no se debería calcular el próximo estado a partir del estado actual. Para ello, utilizar:
```Javascript
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
- Solo se actualizan las propiedades indicadas a través de
setState()
. Las demás propiedades del estado se mergean con el nuevo estado.
Constructor
- Siempre debería llamar al constructor base
- De ser necesario, inicializar el estado
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount()
- Hook que se invoca cuando el componente se renderiza por primera vez
- Es a donde se debe, por ejemplo, inicializar un timer, realizar una llamada a una API, ejecutar un action dispatcher de Redux.
componentWillUnmount()
- Hook que se invoca cuando el componente se remueve del DOM
- Es un buen lugar por ejemplo, para eliminar un timer o liberar recursos
Flujo unidireccional
- Los componentes pueden enviarles informacion a los componentes anidados a través de sus propiedades
- A los componentes anidados no les importa si el dato proviene de una propiedad o de su estado
Eventos
- Usan formato
camelCase
- Para prevenir el comportamiento por defecto, los handlers deben llamar explícitamente a preventDefault:
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
- Para establecer un listener, indicar el mismo al momento del renderizado en lugar de usar addEventListener:
render() {
return (
<button onClick={this.handleClick}>Click me</button>
);
Binding this
Por defecto, en JS los métodos declarados en una clase no vinculan la variable this
a las instancias de la clase. Por lo tanto, si dentro de un método llamamos a this, el resultado será undefined
.
Esto se puede resolver de 3 maneras:
- Invocando a la función bind en cada método en el constructor:
class Button extends React.Component {
constructor(props) {
super(props);
this.state = {...};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {...}
}
class Button extends React.Component {
handleClick = () => {
console.log('this is:', this);
}
}
- Usar una arrow function en el callback. No se lo recomienda porque crea un handler cada vez que se renderiza el componente:
render() {
return (
<button onClick={(e) => this.handleClick(e)}>Click me</button>
);
}
Renderizado condicional
- Se pueden utilizar
if-else
, el operador condicional ternario y el if
en línea con el operador &&:
{bool_condition && <a>Link</a>}
- Si se desea que el mismo componente decida en función de su estado si no debe renderizarse, devolver
null
como resultado de la función o del método render.
Listas
- Las listas de elementos/componentes pueden realizarse usando la función
map()
de JS.
- Cada elemento/componente dentro de la función
map()
debe declarar un atributo key, de tipo string:
const todoItems = todos.map((todo) =>
<li key={todo.id.toString()}>
{todo.text}
</li>
);
- Si no se declara una
key
para cada elemento, React asignará como key
el índice de la función map()
y generará un WARNING
- No se recomienda usar el
index
como key
por cuestiones de performance
- El atributo
key
NO es accesible en los componentes a través de las props
Formularios
- Los elementos HTML
<input>
, <textarea>
y <select>
son diferentes al resto porque mantienen un estado interno relacionado con las interacciones con el usuario.
- Los componentes React manejan su estado a través de la propiedad interna
state
.
- Ambos estados se combinan con una técnica llamada "Controlled components"
Componentes controlados
- Consiste en forzar a que los elementos HTML tomen su estado a partir del estado del componente React
- La mayoría de los elementos HTML toman su valor a partir del estado del componente contenedor. Así mismo, el contenedor define el handler que controla las interacciones del usuario:
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return (
<input type="text" value={this.state.value} onChange={this.handleChange} />
);
}
}
- Los
<textarea>
también tienen una propiedad value
para definir su contenido, en lugar de definirlo entre la etiqueta de apertura y cierre.
- Los
<select>
también tienen una propiedad value
para definir el elemento seleccionado, en lugar de definir el atributo selected
en la <option>
correspondiente.
- Un
<select>
también puede recibir múltiples values
:
<select multiple={true} value={['B', 'C']}>
- Los
<input type="file">
no se pueden manejar como elementos controlados, puesto que la propiedad value es read-only.
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
Lifting up state
Cuando se necesita que más de un componente compartan un estado común, se debe elevar el manejo de ese estado al primer ancestro común que estos tengan.
Composición vs Herencia
- No se recomienda utilizar jerarquias de componentes. En su lugar se recomienda utilizar composición de componentes.
- Se puede utilizar la propiedad especial
props.childen
para graficar elementos entre las etiquetas de apertura y cierre de un componente:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">Welcome</h1>
</FancyBorder>
);
}
- Si se necesitan propiedades adicionales, también se pueden utilizar:
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">{props.left}</div>
<div className="SplitPane-right">{props.right}</div>
</div>
);
}
function App() {
return (
<SplitPane
left={<Contacts />}
right={<Chat />}
/>
);
}
- Si hay funcionalidad común entre componentes, que no esté relacionada con la UI, se recomienda utilizar un módulo de JS e importarlo cuando sea necesario.
Pensando en React
- Crear un mock de la aplicación/componente a implementar y descomponer el mock en una jerarquía de componentes
- Construir una versión estática en React.
- Los datos se cargar a partir de una colección de datos mock
- Los datos se pasan a través de propiedades. Los componentes no tienen estado ya que el estado tiene que ver con la interacción. En esta etapa, los componentes TIENEN que ser estáticos.
- Para aplicaciones pequeñas se recomienda un enfoque top-down.
- Para aplicaciones grandes se recomienda un enfoque bottom-up, e ir realizando tests a medida que se desarrollan los componentes menores.
- Identificar el conjunto mínimo de estados requeridos. Para ello, identificar todas las piezas de información que participan en la aplicación. Luego, sobre cada pieza de información, preguntarse:
- Si la información es recibida desde el padre, probablemente no es estado
- Si la información no cambia en el tiempo, probablemente no es estado
- Si la información puede ser calculada (en base a otras props/state), NO ES UN ESTADO
- Identificar a quien pertence cada estado:
- Identificar que componentes requieren cada pieza de información
- Posiblemente, el estado podría vivir en el primer ancestro común a esos componentes
- Si no es coherente que ningún componente contenga ese estado, crear un componente solo para alojar el estado e insertarlo en algún lugar de la jerarquía (siempre que sea común a quienes requieren de ese estado)
- Agregar event handlers. Como el elemento que contiene el estado es quien puede actualizarlo, allí se deben definir los event handlers. Luego, pasar esos event handlers como callbacks a los componentes hijos.