React
Jesús García
Objetivos del curso
Aprender a usar React
Entender por qué es interesante
- Dan Abramov
Consistency
Responsiveness
Latency
Navigation
Staleness
Entropy
Priority
Accesibility
Internationalization
Delivery
Resilience
Abstraction
React
The V in MVC
A JavaScript library for building user interfaces
Declarative
Component-based
Learn Once, Write Anywhere
Contenido
-
Elementos, DOM Virtual y JSX
-
Componentes y props
-
Estado y eventos (hooks y clases)
-
Estilos
-
Tipos y testing
Elementos, DOM Virtual y JSX
HTML
const myElement = document.createElement("div");
myElement.textContent = "Hola Kairós reacters";
document.getElementById("app").appendChild(myElement);
const element = React.createElement(
"div",
{ className: "" },
"Hola Kairós reacters"
);
ReactDOM.render(element, document.getElementById("app"));
const myElement = <div>Hola Kairós reacters</div>;
ReactDOM.render(element, document.getElementById("app"));
React (sin JSX)
React (con JSX)
Podemos interpolar cualquier expresión de JavaScript en JSX
/* Conditional render */
const element =(
<div>
{
userIsLoggedIn && <div>Welcome, {user.name}<div/>
}
</div>
);
/* Conditional render with ternary operator */
const element = (
<div>
{
userIsLoggedIn ? <div>Welcome, {user.name}<div/> : <div>Hello, please log in<div/>
}
</div>
);
Booleans
Podemos interpolar cualquier expresión de JavaScript en JSX
/* Conditional render */
const element =(
<div>
{
someArray.length > 0 && <div>Welcome<div/>
}
</div>
);
/* MAL - esto puede renderizar '0' */
const element = (
<div>
{
someArray.length && <div>Welcome<div/>
}
</div>
);
Numbers
Podemos interpolar cualquier expresión de JavaScript en JSX
/* Un string se puede pasar como hijo.
React se encarga de escaparlo para evitar ataques de cross-site scripting */
const element =(
<div>
Hola
</div>
);
/* Un string también puede representar un caso de un 'enum' */
const size = 'small' //'medium', 'big'
const style = {
small: {width: '10px', height: '10px'},
medium: {width: '20px', height: '20px'},
big: {width: '30px', height: '30px'}
};
const element = (
<div style={style[size]}/>
);
Strings
Podemos interpolar cualquier expresión de JavaScript en JSX
/* Un array de datos puede convertirse en un array de elementos de React
mediante la función map */
const people = [
{id: '1', name: Jesús, age: 27},
{id: '2', name: Marcos, age: 1},
{id:'3', name: Vega, age: 0}
]
const element = (
<div>
{people.map(({id, name, age}) => (
<div key={id}>
Name: {name}
Age: {age}
</div>
))};
</div>
);
Arrays
Podemos interpolar cualquier expresión de JavaScript en JSX
/*JSX*/
const someJsx = <div>Hello!</div>
const element = (
<div>
{someJsx};
</div>
);
/*Componente*/
const MyGreetingComponent = () => <div>Hello!</div>
const element = (
<div>
<MyGreetingComponent/>;
</div>
);
/*Función*/
JSX, componentes, funciones
Más recursos con JSX
/*elegir elemento dinámicamente*/
const Text = ({tag, children}) => {
const Tag = tag
return <Tag>{children}</Tag>
}
/*Varios componentes en un objeto*/
const Text = {
small(){}
medium(){}
big(){}
}
const jsx = <Text.small>Hola</Text.small>
/*Props spread*/
const MyComponent = ({color, ...rest}) => <div style={{color}} {...rest}/>
Extra
La propiedad $$typeoff de los elementos de React sirve para prevenir ataques XSS
¿Por qué llama React a nuestros componentes en lugar de nosotros mismos?
Hay ventajas en esta inversión de control
Componentes
const MyComponent = (props) => (
<div className={props.className}>
{props.children}
</div>
);
Expresar UIs con JavaScript en vez de templates
- Potente, se puede usar lógica arbitraria y podemos reutilizar y componer igual que hacemos normalmente en JS
- No tenemos que aprender otro lenguaje de marcado
- Difícil de generar
- Difícil comprobar si es correcto en tiempo de compilación
- Más fácil introducir bugs
Principle of least power
The choice of language is a common design choice. The low power end of the scale is typically simpler to design, implement and use...
...The reason for this is that the less powerful the language, the more you can do with the data stored in that language. The Semantic Web is an attempt, largely, to map large quantities of existing data onto a common language so that the data can be analyzed in ways never dreamed of by its creators.
I chose HTML not to be a programming language because I wanted different programs to do different things with it: present it differently, extract tables of contents, index it, and so on.
I wish we could retire the term “virtual DOM”. It made sense in 2013 because otherwise people assumed React creates DOM nodes on every render. But people rarely assume this today. “Virtual DOM” sounds like a workaround for some DOM issue. But that’s not what React is.
React is “value UI”. Its core principle is that UI is a value, just like a string or an array. You can keep it in a variable, pass it around, use JavaScript control flow with it, and so on. That expressiveness is the point — not some diffing to avoid applying changes to the DOM.
For example, if it was fast enough to always assign properties on every node update, we would just do that. We wouldn’t throw away our React apps because “DOM is fast now”. That’s nonsense. UI is a value. Function is the building block. Lazy calls (elements) are the glue.
Estado y eventos
Para añadir estado a un componente usamos clases
class MyComponent extends React.Component{
constructor(props){
super(props)
//state siempre es un objeto
this.state = {}
}
//lo mismo que el cuerpo de la función en un componente función
//tiene acceso a this.state y this.props
render(){
/*...*/
}
}
class MyComponent extends React.Component{
state = {}
render(){
/*...*/
}
}
Para actualizar el estado en respuesta a eventos de la interfaz pasamos handlers como props a nuestros elementos
class MyComponent extends React.Component {
constructor(){
super()
this.state = {/*...*/}
this.myMethod = this.myMethod.bind(this)
/*...*/
}
myMethod(){/*...*/}
render(){
return (
<button onClick={this.myMethod}>hola</button>
)
}
}
React tiene un sistema de eventos sintético con pooling. Los eventos se reutilizan, por lo que no se debe acceder a ellos de forma asíncrona.
Binding para los métodos de clase
- Binding en el constructor
class MyComponent extends React.Component {
constructor(){
super()
this.state = {/*...*/}
this.myMethod = this.myMethod.bind(this)
/*...*/
}
myMethod(){/*...*/}
render(){
return (
<button onClick={this.myMethod}>hola</button>
)
}
}
- Binding inline
class MyComponent extends React.Component {
constructor(){
super()
this.state = {/*...*/}
/*...*/
}
myMethod(){/*...*/}
render(){
return (
<button onClick={this.myMethod.bind(this)}>hola</button>
)
}
}
Binding para los métodos de clase
- arrow function
class MyComponent extends React.Component {
constructor(){
super()
this.state = {/*...*/}
}
myMethod(){/*...*/}
render(){
return (
<button onClick={e => this.myMethod(e)}>hola</button>
)
}
}
- class properties FTW
class MyComponent extends React.Component {
constructor(){
super()
this.state = {/*...*/}
}
myMethod = () => {/*...*/}
render(){
return (
<button onClick={this.myMethod}>hola</button>
)
}
}
- El estado es inmutable
setState :: (Object | Function, [Function]) -> void
- Lo actualizamos mediante el método asíncrono setState
setState
class MyComponent extends React.Component {
state = {thing: 1, anotherThing: 2, yetAnotherThing: 3}
updateThing = () => {
//shallow merge of object with previous state
this.setState({
thing: 4
})
}
updateAnotherThing = () => {
this.setState(prevState => {
//return a new object to shallow merge
return {
anotherThing: 5
}
})
}
updateYetAnotherThingBasedOnSomeEvent = (e) => {
const value = e.target.value
if (someCondition(value)){
this.setState(prevState => {
//WARNING: do not access the event (e) in here
//The event may already be null - we save the value above
return {
...prevState,
yetAnotherThing: 5
}
}, () => {
/*Here this.state has been updated*/
})
}
}
}
function CounterWithHook({ initialValue }) {
const [count, setCount] = React.useState(0)
function increment() {
setCount(oldCount => oldCount + 1)
}
const double = count * 2
return (
<div>
<div>count: {count}</div>
<div>double: {double}</div>
<button onClick={increment}>increment</button>
</div>
)
}
useState hook
Formularios
Se puede tener un formulario "normal" en React
const UncontrolledForm = () => (
<form>
<label>
Email
<input type="text" name="email" />
</label>
<label>
Password
<input type="text" name="password" />
</label>
<input type="submit" value="Submit" />
</form>
);
Pero lo recomendable es usar "controlled components" para tener el estado en JS
class ControlledForm extends React.Component {
constructor(props) {
super(props);
this.state = { email: '', password: '' };
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(e) {
const { name, value } = e.target;
this.setState({ [name]: value });
}
handleSubmit(e) {
e.preventDefault(); //evitar el refresco de la página
//Hacer algo con this.state
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Email
<input type="text" name="email" value={this.state.email} onChange={this.handleChange}/>
</label>
<label>
Password
<input type="text" name="password" value={this.state.password} onChange={this.handleChange}/>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
De esta forma, React se convierte en la fuente de la verdad
Tener el estado en JS nos permite aplicar validaciones, formateos, etc
//textarea HTML
<textarea>
Contenido de la textarea
</textarea>
//textarea React
<textarea value={this.state.value} onChange={this.handleChange} />
//select HTML
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
//select React
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
Refs
React nos abstrae de manipular el DOM, pero a veces necesitamos acceder a un elemento para, por ejemplo, darle el foco.
Todos los elementos aceptan la prop reservada "ref"
//1. Forma - React.createRef()
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.inputRef = React.createRef();
}
render() {
return <input type="text" ref={this.inputRef} />;
}
componentDidMount() {
this.inputRef.current.focus();
}
}
//2. forma (antigua) - callback ref
class MyComponent extends React.Component {
constructor(props) {
super(props);
}
render() {
return <input type="text" ref={input => {
input && this.inputRef = input
}} />;
}
componentDidMount() {
this.inputRef.focus();
}
}
Estilos
Una breve historia de CSS
- 1991 - Tim Berners-Lee propone HTML
- 1991-1996 Surgen diversas propuestas de lenguajes de estilos
- 1996 - Se publica la primera versión del spec de CSS
- 1998-99 se publican los specs de CSS 2 y los primeros drafts de CSS3
- 2000 Los navegadores soportan CSS "completamente"
- 2009 se publica la primera version del spec de FlexBox
Problemas con CSS
CSS fue ideado para estilar documentos, y da problemas cuando se usa para estilar aplicaciones a gran escala
- Estilos globales
- Cascada, especificidad y herencia lo hacen impredecible
- Difícil reusar
- Se recurre a selectores complejos e !important
Sharing does not imply endorsement :P
Otros: Atomic CSS, OOCSS, SMACSS
CSS-in-JS
Typechecking y Testing
Higiene básica
Linter - ESLint
Formatter - Prettier
Git Hooks para automatizar
Convención commits
Higiene básica
$ npm install --save-dev eslint prettier eslint-config-prettier lint-staged husky
//.eslintrc
{
"parserOptions": {
"ecmaVersion": "2018"
},
"extends": [
"eslint:recommended", "eslint-config-prettier"
],
"rules": {
},
"env": {
"browser": true
}
}
//.prettierrc
{
"arrowParens": "avoid",
"bracketSpacing": false,
"jsxBracketSameLine": false,
"printWidth": 80,
"proseWrap": "always",
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"useTabs": false
}
*
* no instalar eslint con Create React App - viene incluida
//.lintstagedrc
{
"linters": {
"*.js": [
"eslint"
],
"*.+(js|jsx|json|yml|yaml|css|less|scss|ts|tsx|md|graphql|mdx)": [
"prettier --write",
"git add"
]
}
}
//package.json
{
"scripts": {
"lint": "eslint src",
"format": "prettier --write \"**/*.+(js|jsx|json|yml|yaml|css|less|scss|ts|tsx|md|mdx|graphql|vue)\"",
}
},
{
"husky": {
"hooks": {
"pre-commit": "npm test && lint-staged",
"pre-push": "npm test"
}
}
}
Contratos en runtime - lanzan warnings por consola
//Componente función
import PropTypes from "prop-types"
function MyComponent(props){
return (
<div>
Hi, my name is {name}
and I am {age} years old
</div>
)
}
MyComponent.defaultProps = {
name: "Hinclimer",
age: 3
}
MyComponent.propTypes = {
name: PropTypes.string,
age: PropTypes.number
}
//Componente clase
import PropTypes from "prop-types"
class MyComponent extends React.Component {
static defaultProps = {
name: "Hinclimer",
age: 3
}
static propTypes = {
name: PropTypes.string,
age: PropTypes.number
}
render(){
return (
<div>
Hi, my name is {name}
and I am {age} years old
</div>
)
}
}
//Ejemplos de reutilización de propTypes
import PropTypes from 'prop-types';
import { Image } from 'react-native';
export const childrenType = PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
PropTypes.func
]);
export const styleType = PropTypes.oneOfType([
PropTypes.object,
PropTypes.array,
PropTypes.number
]);
export const apiDefaultProp = {
loading : false,
retry : () => {},
error : null
};
export const imageSourceType = Image.propTypes.source;
//Para componentes que acepten un único hijo
MyComponent.propTypes = {
children: PropTypes.element.isRequired
};
export interface HelloProps { compiler: string; framework: string; }
export const Hello = (props: HelloProps) =>
<h1>Hello from {props.compiler} and {props.framework}!</h1>;
TypeScript
export interface HelloProps { compiler: string; framework: string; }
// 'HelloProps' describes the shape of props.
// State is never set so we use the '{}' type.
export class Hello extends React.Component<HelloProps, {}> {
render() {
return <h1>Hello from {this.props.compiler} and {this.props.framework}!</h1>;
}
}
Testing
¿Cómo debemos testear una interfaz?
¿Cómo debemos testear ?
TDD, ATDD, Unit Tests, Integration Tests, End-to-End tests, Visual Regression Tests, Snapshots, ...
- Librería recomendada oficialmente
- Sencilla, provee assertions para buscar y hacer comprobaciones en el DOM
- Enfocada a tests que simulen la interacción de un usuario con la interfaz
it('calls "onClick" prop on button click', () => {
//usamos un mock de jest para onClick
const onClick = jest.fn();
// rtl nos provee la función render, que nos devuelve utilidades
// para interactuar con el "DOM" del componente renderizado
const { getByText } = render(<button onClick={onClick}>click me</button>);
fireEvent.click(getByText(/click me/i));
expect(onClick).toHaveBeenCalled();
});
- Un tipo de assertion para los tests
- Se generan archivos .snap que se deben commitear
- El test únicamente comprueba que no ha cambiado el archivo: debemos validar cada cambio
- Típicamente usados para testear el output de los componentes de React, pero tienen más posibilidades
import React from 'react';
import Link from '../Link.react';
import renderer from 'react-test-renderer';
it('renders correctly', () => {
const tree = renderer
.create(<Link page="http://www.facebook.com">Facebook</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});
exports[`renders correctly 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
Formación React Generación K
By Jesús García Martínez
Formación React Generación K
- 50