Jesús García
Aprender a usar React
Entender por qué es interesante
Poder tomar decisiones fundamentadas
- Dan Abramov
Consistency
Responsiveness
Latency
Navigation
Staleness
Entropy
Priority
Accesibility
Internationalization
Delivery
Resilience
Abstraction
The V in MVC
A JavaScript library for building user interfaces
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"));
/* 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
/* 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
/* 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
/* 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
/*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
/*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}/>
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
const MyComponent = (props) => (
<div className={props.className}>
{props.children}
</div>
);
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.
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.
class MyComponent extends React.Component {
constructor(){
super()
this.state = {/*...*/}
this.myMethod = this.myMethod.bind(this)
/*...*/
}
myMethod(){/*...*/}
render(){
return (
<button onClick={this.myMethod}>hola</button>
)
}
}
class MyComponent extends React.Component {
constructor(){
super()
this.state = {/*...*/}
this.myMethod = this.myMethod.bind(this)
/*...*/
}
myMethod(){/*...*/}
render(){
return (
<button onClick={this.myMethod.bind(this)}>hola</button>
)
}
}
class MyComponent extends React.Component {
constructor(){
super()
this.state = {/*...*/}
}
myMethod(){/*...*/}
render(){
return (
<button onClick={e => this.myMethod(e)}>hola</button>
)
}
}
class MyComponent extends React.Component {
constructor(){
super()
this.state = {/*...*/}
}
myMethod = () => {/*...*/}
render(){
return (
<button onClick={this.myMethod}>hola</button>
)
}
}
setState :: (Object | Function, [Function]) -> void
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*/
})
}
}
}
//Inmutabilidad
array.reverse() /* >>> */ [...array].reverse()
array.splice() /* >>> */ const removeAtIndex = (arr, i) =>
arr.slice(0, i).concat(arr.slice(i + 1));
array.push(4) /* >>> */ array.concat(4)
array.pop() /* >>> */ array.slice(-1).pop()
export const changeAtIndex = (arr, i, newValue) =>
arr.slice(0, i).concat(newValue, arr.slice(i + 1));
export const removeElement = (arr, predicate) =>
removeAtIndex(arr, arr.findIndex(predicate));
export const toggleArrayElement = (arr, element, predicate) => {
const defaultPredicate = el => el === element;
const i = arr.findIndex(predicate || defaultPredicate);
return i === -1 ? arr.concat(element) : removeAtIndex(arr, i);
};
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>
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();
}
}
CSS fue ideado para estilar documentos, y da problemas cuando se usa para estilar aplicaciones a gran escala
Sharing does not imply endorsement :P
Otros: Atomic CSS, OOCSS, SMACSS
$ 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>;
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>;
}
}
TDD, ATDD, Unit Tests, Integration Tests, End-to-End tests, Visual Regression Tests, Snapshots, ...
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();
});
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>
`;