SlideShare uma empresa Scribd logo
1 de 155
Baixar para ler offline
deSymfony 16-17 septiembre 2016
Madrid
INTEGRANDO REACT.JS
EN APLICACIONES
SYMFONY
Nacho Martín
deSymfony
¡Muchas gracias a nuestros
patrocinadores!
Programo en Limenius
Casi todos los proyectos
necesitan un frontend rico, por
una razón o por otra
Hacemos aplicaciones a medida
Así que le hemos dado unas cuantas vueltas
Objetivo:
Mostrar cosas que nos
encontramos al usar React desde
Symfony, en tierra de nadie, y que
ninguno de los dos va a
documentar.
¿A mí qué me importa el
frontend?
¿Qué es React.js?
Echar huevos en sartén
La premisa fundamental
Cómo hacer una tortilla
Comprar huevos
Romper huevos
Echar huevos en sartén
Batir huevos
La premisa fundamental
Cómo hacer una tortilla
Comprar huevos
Romper huevos
Opciones:
La premisa fundamental
Opciones:
La premisa fundamental
1: Repintamos todo.
Opciones:
La premisa fundamental
1: Repintamos todo. Simple
Opciones:
La premisa fundamental
1: Repintamos todo. Simple Poco eficiente
Opciones:
2: Buscamos en el DOM
dónde insertar, qué
mover, qué eliminar.
La premisa fundamental
1: Repintamos todo. Simple Poco eficiente
Opciones:
2: Buscamos en el DOM
dónde insertar, qué
mover, qué eliminar.
La premisa fundamental
1: Repintamos todo. Simple
Complejo
Poco eficiente
Opciones:
2: Buscamos en el DOM
dónde insertar, qué
mover, qué eliminar.
La premisa fundamental
1: Repintamos todo. Simple
Muy eficienteComplejo
Poco eficiente
Opciones:
2: Buscamos en el DOM
dónde insertar, qué
mover, qué eliminar.
La premisa fundamental
1: Repintamos todo. Simple
Muy eficienteComplejo
Poco eficiente
React nos permite hacer 1, aunque en la sombra hace 2
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.*
La premisa fundamental
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.*
La premisa fundamental
* A menos que quieras tener control absoluto.
¡Clícame! Clicks: 0
Nuestro primer componente
¡Clícame! Clicks: 1
Nuestro primer componente
¡Clícame!
Nuestro primer componente
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
}
export default Counter;
Nuestro primer componente
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
}
export default Counter;
Sintaxis ES6 (opcional)
Nuestro primer componente
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
}
export default Counter;
Sintaxis ES6 (opcional)
Estado inicial
Nuestro primer componente
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
}
export default Counter;
Sintaxis ES6 (opcional)
Modificar estado
Estado inicial
Nuestro primer componente
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
}
export default Counter;
Sintaxis ES6 (opcional)
Modificar estado
render(), lo llama React
Estado inicial
Trabajar con el estado
Trabajar con el estado
constructor(props) {
super(props);
this.state = {count: 1};
}
Estado inicial
Trabajar con el estado
constructor(props) {
super(props);
this.state = {count: 1};
}
Estado inicial
this.setState({count: this.state.count + 1});
Asignar estado
Trabajar con el estado
constructor(props) {
super(props);
this.state = {count: 1};
}
Estado inicial
this.setState({count: this.state.count + 1});
Asignar estado
this.state.count = this.state.count + 1;
Simplemente recordar evitar
render() y JSX
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
No es HTML, es JSX.
React lo transforma internamente a elementos.
Buena práctica: Dejar render() lo más limpio posible,
solo un return.
render() y JSX
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
No es HTML, es JSX.
React lo transforma internamente a elementos.
Algunas cosas cambian
Buena práctica: Dejar render() lo más limpio posible,
solo un return.
render() y JSX
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
No es HTML, es JSX.
React lo transforma internamente a elementos.
Algunas cosas cambian
Entre {} podemos insertar expresiones JS
Buena práctica: Dejar render() lo más limpio posible,
solo un return.
Thinking in React
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
Thinking in React
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
Aquí no modificar el estado
Thinking in React
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
Aquí no Ajax
Thinking in React
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
Aquí no calcular decimales de pi y enviar un email
Importante: pensar la jerarquía
Importante: pensar la jerarquía
Jerarquía de componentes: props
class CounterGroup extends Component {
render() {
return (
<div>
<Counter name="amigo"/>
<Counter name="señor"/>
</div>
);
}
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>
Clícame {this.props.name}
</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
y en Counter…
Jerarquía de componentes: props
class CounterGroup extends Component {
render() {
return (
<div>
<Counter name="amigo"/>
<Counter name="señor"/>
</div>
);
}
}
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>
Clícame {this.props.name}
</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
y en Counter…
Jerarquía de componentes: props
class CounterGroup extends Component {
render() {
return (
<div>
<Counter name="amigo"/>
<Counter name="señor"/>
</div>
);
}
}
Pro tip: componentes sin estado
const Saludador = (props) => {
<div>
<div>Hola {props.name}</div>
</div>
}
Todo depende del estado, por tanto:
Todo depende del estado, por tanto:
•Podemos reproducir estados,
Todo depende del estado, por tanto:
•Podemos reproducir estados,
•rebobinar,
Todo depende del estado, por tanto:
•Podemos reproducir estados,
•rebobinar,
•loguear cambios de estado,
Todo depende del estado, por tanto:
•Podemos reproducir estados,
•rebobinar,
•loguear cambios de estado,
•hacer álbum de estilo
Todo depende del estado, por tanto:
•Podemos reproducir estados,
•rebobinar,
•loguear cambios de estado,
•hacer álbum de estilo
•…
Learn once,
write everywhere
¿Y si en lugar de algo así…
render() {
return (
<div className="App">
<button onClick={this.tick.bind(this)}>Clícame!</button>
<span>Clicks: {this.state.count}</span>
</div>
);
}
…tenemos algo así?
render () {
return (
<View>
<ListView
dataSource={dataSource}
renderRow={(rowData) =>
<TouchableOpacity >
<View>
<Text>{rowData.name}</Text>
<View>
<SwitchIOS
onValueChange={(value) =>
this.setMissing(item, value)}
value={item.missing} />
</View>
</View>
</TouchableOpacity>
}
/>
</View>
);
}
…tenemos algo así?
render () {
return (
<View>
<ListView
dataSource={dataSource}
renderRow={(rowData) =>
<TouchableOpacity >
<View>
<Text>{rowData.name}</Text>
<View>
<SwitchIOS
onValueChange={(value) =>
this.setMissing(item, value)}
value={item.missing} />
</View>
</View>
</TouchableOpacity>
}
/>
</View>
);
}
React Native
React Targets
•Web - react-dom
•Mobile - React Native
•Gl shaders - gl-react
•Canvas - react-canvas
•Terminal - react-blessed
react-blessed (terminal)
Setup
¿Assetic?
Setup recomendado
Setup recomendado
Webpack
Pros
Webpack
• Gestiona dependencias por nosotros.
Pros
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
Pros
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
Pros
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
• Uso de preprocesadores/“transpiladores”, como Babel.
Pros
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
• Uso de preprocesadores/“transpiladores”, como Babel.
• Los programadores de frontend no nos arquearán la ceja.
Pros
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
• Uso de preprocesadores/“transpiladores”, como Babel.
• Los programadores de frontend no nos arquearán la ceja.
Pros
Contras
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
• Uso de preprocesadores/“transpiladores”, como Babel.
• Los programadores de frontend no nos arquearán la ceja.
Pros
Contras
• Tiene su curva de aprendizaje.
Webpack
• Gestiona dependencias por nosotros.
• Permite varios entornos: producción, desarrollo, ….
• Recarga automática de página (e incluso hot reload).
• Uso de preprocesadores/“transpiladores”, como Babel.
• Los programadores de frontend no nos arquearán la ceja.
Pros
Contras
• Tiene su curva de aprendizaje.
Ejemplo completo: https://github.com/Limenius/symfony-react-sandbox
Inserción
<div id="react-placeholder"></div>
import ReactDOM from 'react-dom';
ReactDOM.render(
<Counter name="amigo">,
document.getElementById('react-placeholder')
);
HTML
JavaScript
Integración con Symfony
https://github.com/Limenius/ReactBundle
https://github.com/shakacode/react_on_rails
Basado en
ReactBundle
{{ react_component('RecipesApp', {'props': props}) }}
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppServer';
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
ReactBundle
{{ react_component('RecipesApp', {'props': props}) }}
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppServer';
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
ReactBundle
{{ react_component('RecipesApp', {'props': props}) }}
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppServer';
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
ReactBundle
{{ react_component('RecipesApp', {'props': props}) }}
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppServer';
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
<div class="js-react-on-rails-component" style="display:none" data-
component-name=“RecipesApp” data-props=“[mi Array en JSON]" data-
trace=“false" data-dom-id=“sfreact-57d05640f2f1a”></div>
HTML generado:
Aplicaciones universales
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.
La premisa fundamental
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.
La premisa fundamental
Podemos renderizar componentes desde el servidor.
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.
La premisa fundamental
Podemos renderizar componentes desde el servidor.
• SEO friendly.
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.
La premisa fundamental
Podemos renderizar componentes desde el servidor.
• SEO friendly.
• Carga de página más rápida.
Dame un estado y una función render() que
dependa de él, y olvídate de cómo y cuándo pinto.
La premisa fundamental
Podemos renderizar componentes desde el servidor.
• SEO friendly.
• Carga de página más rápida.
• Podemos cachear.
Client-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'client-side'}}) }}
TWIG
Client-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'client-side'}}) }}
TWIG
Client-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'client-side'}}) }}
TWIG
<div class="js-react-on-rails-component" style="display:none" data-
component-name=“RecipesApp” data-props=“…” data-dom-
id=“sfreact-57d05640f2f1a”></div>
HTML que devuelve el servidor
Client-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'client-side'}}) }}
TWIG
<div class="js-react-on-rails-component" style="display:none" data-
component-name=“RecipesApp” data-props=“…” data-dom-
id=“sfreact-57d05640f2f1a”></div>
HTML que devuelve el servidor
Generado en el navegador
<div id="sfreact-57d05640f2f1a"><div data-reactroot="" data-
reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb"
data-reactid="2"><li class="active" data-reactid=“3">Recipes</li>
…
…
</div>
Client-side y server-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'both'}}) }}
TWIG
Client-side y server-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'both'}}) }}
TWIG
Client-side y server-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'both'}}) }}
TWIG
HTML que devuelve el servidor
<div id="sfreact-57d05640f2f1a"><div data-reactroot="" data-
reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb"
data-reactid="2"><li class="active" data-reactid=“3">Recipes</li>
…
…
</div>
Client-side y server-side
{{ react_component('RecipesApp', {'props': props,
'rendering': 'both'}}) }}
TWIG
HTML que devuelve el servidor
<div id="sfreact-57d05640f2f1a"><div data-reactroot="" data-
reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb"
data-reactid="2"><li class="active" data-reactid=“3">Recipes</li>
…
…
</div>
Y React en el navegador toma el control
al evaluar el código
Aplicaciones universales: Opciones
Opción 1: llamar a subproceso node.js
Llamamos a node.js con el componente Process de
Symfony
* Cómodo (si tenemos node.js instalado).
* Lento.
Librería: https://github.com/nacmartin/phpexecjs
Opción 2: v8js
Usamos la extensión de PHP v8js
* Cómodo (aunque puede que haya que compilar la
extensión y v8).
* Lento por ahora, potencialmente podríamos tener
v8 precargada usando php-pm.
Librería: https://github.com/nacmartin/phpexecjs
Configuración Opciones 1 y 2
limenius_react:
serverside_rendering:
mode: "phpexecjs"
phpexecjs detecta si tenemos la extensión v8js,
y si no llama a node.js
config.yml:
Opción 3: Servidor externo
Tenemos un servidor node.js “tonto” que nos
renderiza React.
Es un servidor de <100 líneas, que es independiente
de nuestra lógica.
* “Incómodo” (hay que mantener el servidor node.js
corriendo, que tampoco es para morirse).
* Rápido.
Ver ejemplo en
https://github.com/Limenius/symfony-react-sandbox
Configuración Opción 3
limenius_react:
serverside_rendering:
mode: “external”
server-socket-path: “../tal_y_tal/node.sock”
config.yml:
Lo mejor de los dos mundos
En desarrollo usar llamada a node.js o v8js con
phpexecjs.
En producción tener un servidor externo.
Si podemos cachear, menos problema.
Es decir:
limenius_react:
serverside_rendering:
mode: “external”
server-socket-path: “../tal_y_tal/node.sock”
config.yml:
limenius_react:
serverside_rendering:
mode: "phpexecjs"
config_dev.yml:
¿Vale la pena una app universal?
¿Vale la pena una app universal?
En ocasiones sí, pero introduce
complejidad.
Soporte para Redux
(+brevísima introducción a Redux)
Redux: una cuestión de estado
guardar
Tu nombre: Juan
Hola, Juan
Cosas de Juan:
Redux: una cuestión de estado
guardar
Tu nombre: Juan
Hola, Juan
Cosas de Juan:
Redux: una cuestión de estado
guardar
Tu nombre: Juan
Hola, Juan
Cosas de Juan:
state.name
callback para cambiarlo
dispatch(changeName(‘Juan'));
Componente
dispatch(changeName(‘Juan'));
Componente
changeName = (name) => {
return {
type: ‘CHANGE_NAME',
name
}
}
Action
dispatch(changeName(‘Juan'));
Componente
changeName = (name) => {
return {
type: ‘CHANGE_NAME',
name
}
}
Action
const todo = (state = {name: null}, action) => {
switch (action.type) {
case 'CHANGE_USER':
return {
name: action.name
}
}
}
Reducer
dispatch(changeName(‘Juan'));
Componente
changeName = (name) => {
return {
type: ‘CHANGE_NAME',
name
}
}
Action
const todo = (state = {name: null}, action) => {
switch (action.type) {
case 'CHANGE_USER':
return {
name: action.name
}
}
}
Reducer
Store
this.props.name == ‘Juan';dispatch(changeName(‘Juan'));
Componente
changeName = (name) => {
return {
type: ‘CHANGE_NAME',
name
}
}
Action
const todo = (state = {name: null}, action) => {
switch (action.type) {
case 'CHANGE_USER':
return {
name: action.name
}
}
}
Reducer
Store
Redux en ReactBundle
Ver ejemplo en
https://github.com/Limenius/symfony-react-sandbox
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppClient';
import recipesStore from '../store/recipesStore';
ReactOnRails.registerStore({recipesStore})
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
{{ redux_store('recipesStore', props) }}
{{ react_component('RecipesApp') }}
Redux en ReactBundle
Ver ejemplo en
https://github.com/Limenius/symfony-react-sandbox
import ReactOnRails from 'react-on-rails';
import RecipesApp from './RecipesAppClient';
import recipesStore from '../store/recipesStore';
ReactOnRails.registerStore({recipesStore})
ReactOnRails.register({ RecipesApp });
Twig:
JavaScript:
{{ redux_store('recipesStore', props) }}
{{ react_component('RecipesApp') }}
{{ react_component('OtroComponente') }}
Compartir store entre componentes
Compartir store entre componentes
React
React
React
Twig
Twig
React
Al compartir store comparten estado
Twig
Formularios, un caso especial
Formularios muy dinámicos
•Dentro de aplicaciones React.
•Formularios importantísimos en los que un detalle
de usabilidad vale mucho dinero.
•Formularios muy específicos.
•Formularios muy dinámicos que no cansen (ver
typeform por ejemplo).
El problema
Supongamos un form así
public function buildForm(FormBuilderInterface $builder, array
$options)
{
$builder
->add('country', ChoiceType::class, [
'choices' => [
'España' => 'es',
'Portugal' => 'pt',
]
])
->add('addresses', CollectionType::class, ...);
};
Forms en HTML
$form->createView();
state.usuario
Forms en HTML
$form->createView();
state.usuario
Forms en HTML
$form->createView();
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
Forms en HTML
$form->createView();
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
Forms en HTML
$form->createView();
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST bien formadito
con country:’es’
y no ‘España’, ‘espana', ‘spain', ‘0’…
Forms en HTML
$form->createView();
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST bien formadito
con country:’es’
y no ‘España’, ‘espana', ‘spain', ‘0’…
Forms en API
$form;
state.usuario
Forms en API
$form;
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
✘
Forms en API
$form;
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
Forms en API
$form;
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST “voy a tener suerte”
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
Forms en API
$form;
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST “voy a tener suerte”
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
This form should not contain extra fields!!1
Forms en API
$form;
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST “voy a tener suerte”
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
This form should not contain extra fields!!1
The value you selected is not a valid choice!!
Forms en API
$form;
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST “voy a tener suerte”
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
This form should not contain extra fields!!1
The value you selected is not a valid choice!!One or more of the given values is invalid!! :D
Forms en API
$form;
$form->submit($request);
submit
país: España
España
Portugal
direcciones:
C tal-
+state.usuario
POST “voy a tener suerte”
✘
¿Cómo sabemos los campos,
o los choices?
¡A documentar! :(
This form should not contain extra fields!!1
The value you selected is not a valid choice!!One or more of the given values is invalid!! :DMUHAHAHAHAHA!!!!!
Definir (y mantener) por triplicado
Form SF API docs Form Cliente
:(
¿Cuántos programadores hacen falta para hacer un
formulario?
Caso: Wizard complejo
Caso: Wizard complejo
Caso: Wizard complejo
Lo que necesitamos
$form->createView();
HTML
API
$miTransformador->transform($form);
Lo que necesitamos
$form->createView();
HTML
¡Serializar! Vale, ¿A qué formato?
API
$miTransformador->transform($form);
JSON Schema
Qué pinta tiene
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object",
"properties": {
"name": {
"description": "Name of the product",
"type": "string"
},
"price": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"minItems": 1,
"uniqueItems": true
}
},
"required": ["id", "name", "price"]
}
definiciones,
tipos,
reglas de validación
:)
Nuevo recurso: mi-api/products/form
A partir del schema generamos form
Generadores client-side
• jdorn/json-editor: no React, es un veterano.
• mozilla/react-jsonschema-form: React. creado
por un antiguo Symfonero (Niko Perriault).
• limenius/liform-react: React + redux; integrado
con redux-form (I ♥ redux-form)
• …
• Crear el nuestro puede ser conveniente.
Generadores client-side: diferencias
Cada uno amplía json-schema a su manera para
especificar detalles UI: Orden de campos, qué widget
específico usar, etc.
Si queremos usarlos al máximo hay que generar
un json-schema específico para cada uno
(no son totalmente intercambiables).
Ejemplo: usar editor Wysiwyg en un campo texto
Ejemplo: LiformBundle y liform-react
limenius/LiformBundle: Genera json-schema a partir
de formularios Symfony.
limenius/liform-react: Generador de formularios
React (con redux-form) a partir de json-schema.
Son Work in progress
Cómo serializar: resolvers + transformers
$transformer = $resolver->resolve($form);
$jsonSchema = $transformer->transform($form);
Ejemplo de esta técnica: https://github.com/Limenius/LiformBundle
Resolver
public function resolve(FormInterface $form)
{
$types = FormUtil::typeAncestry($form);
foreach ($types as $type) {
if (isset($this->transformers[$type])) {
return $this->transformers[$type];
}
}
}
Misión: Encuentra el transformer apropiado para el form
Transformer
Misión: Inspecciona el form y crea un array.
Si es compuesto resuelve+transforma los hijos.
class IntegerTransformer extends AbstractTransformer
{
public function transform(FormInterface $form)
{
$schema = [
'type' => 'integer',
];
if ($liform = $form->getConfig()->getOption('liform')) {
if ($format = $liform['format']) {
$schema['format'] = $format;
}
}
$this->addCommonSpecs($form, $schema);
return $schema;
}
}
protected function addLabel($form, &$schema)
{
if ($label = $form->getConfig()->getOption('label')) {
$schema['title'] = $label;
}
}
Transformer
Recopila información de cada Form Field.
Podemos sacar mucha información:
•Valores por defecto & placeholders.
•Atributos del formulario.
•Validadores.
Ejemplo: validación ‘pattern’
protected function addPattern($form, &$schema)
{
if ($attr = $form->getConfig()->getOption('attr')) {
if (isset($attr['pattern'])) {
$schema['pattern'] = $attr['pattern'];
}
}
}
Esta técnica vale también
para Angular, Backbone,
mobile…
Repaso:
Repaso:
• Qué es React
• Setup
• Apps universales (ReactBundle)
• Para qué sirve Redux
• El problema de los formularios
• Cómo aliviarlo con JSON Schema
MADRID · NOV 27-28 · 2015
¡Gracias!
@nacmartin
nacho@limenius.com
http://limenius.com
Formación, consultoría
y desarrollo de proyectos

Mais conteúdo relacionado

Mais procurados

How to configure SSH on Cisco switch
How to configure SSH on Cisco switchHow to configure SSH on Cisco switch
How to configure SSH on Cisco switch
tcpipguru
 

Mais procurados (20)

Introduction to react_js
Introduction to react_jsIntroduction to react_js
Introduction to react_js
 
Introduction To PHP
Introduction To PHPIntroduction To PHP
Introduction To PHP
 
Capítulo 8 asignación de direcciones ip
Capítulo 8 asignación de direcciones ipCapítulo 8 asignación de direcciones ip
Capítulo 8 asignación de direcciones ip
 
Cascading Style Sheets - Part 01
Cascading Style Sheets - Part 01Cascading Style Sheets - Part 01
Cascading Style Sheets - Part 01
 
Android - ADB
Android - ADBAndroid - ADB
Android - ADB
 
Css3 cheat-sheet
Css3 cheat-sheetCss3 cheat-sheet
Css3 cheat-sheet
 
Lab huawei2
Lab huawei2Lab huawei2
Lab huawei2
 
Manual de php con ejercicios
Manual de php con ejerciciosManual de php con ejercicios
Manual de php con ejercicios
 
Css - Tema 1
Css - Tema 1Css - Tema 1
Css - Tema 1
 
AD & LDAP
AD & LDAPAD & LDAP
AD & LDAP
 
How to configure SSH on Cisco switch
How to configure SSH on Cisco switchHow to configure SSH on Cisco switch
How to configure SSH on Cisco switch
 
Javascript
JavascriptJavascript
Javascript
 
Comandos cisco show
Comandos cisco showComandos cisco show
Comandos cisco show
 
Semana 3 Responsive Design y Media Queries
Semana 3   Responsive Design y Media QueriesSemana 3   Responsive Design y Media Queries
Semana 3 Responsive Design y Media Queries
 
CSS Selectors
CSS SelectorsCSS Selectors
CSS Selectors
 
Retrofit
RetrofitRetrofit
Retrofit
 
Creación de tablas y relaciones en MySQL y SQL yog
Creación de tablas y relaciones en MySQL y SQL yogCreación de tablas y relaciones en MySQL y SQL yog
Creación de tablas y relaciones en MySQL y SQL yog
 
PHP
PHPPHP
PHP
 
React and redux
React and reduxReact and redux
React and redux
 
PHP
PHPPHP
PHP
 

Destaque

Destaque (20)

Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
Finally, Professional Frontend Dev with ReactJS, WebPack & Symfony (Symfony C...
 
Refactorizando Pccomponentes.com con Symfony
Refactorizando Pccomponentes.com con SymfonyRefactorizando Pccomponentes.com con Symfony
Refactorizando Pccomponentes.com con Symfony
 
Integrating React.js Into a PHP Application
Integrating React.js Into a PHP ApplicationIntegrating React.js Into a PHP Application
Integrating React.js Into a PHP Application
 
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and moreSymfony Guard Authentication: Fun with API Token, Social Login, JWT and more
Symfony Guard Authentication: Fun with API Token, Social Login, JWT and more
 
Decoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDDDecoupling the Ulabox.com monolith. From CRUD to DDD
Decoupling the Ulabox.com monolith. From CRUD to DDD
 
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...
Handling 10k requests per second with Symfony and Varnish - SymfonyCon Berlin...
 
When cqrs meets event sourcing
When cqrs meets event sourcingWhen cqrs meets event sourcing
When cqrs meets event sourcing
 
Desarrollo código mantenible en WordPress utilizando Symfony
Desarrollo código mantenible en WordPress utilizando SymfonyDesarrollo código mantenible en WordPress utilizando Symfony
Desarrollo código mantenible en WordPress utilizando Symfony
 
Multi kernelowa aplikacja w oparciu o Symfony 3 i microkernele
Multi kernelowa aplikacja w oparciu o Symfony 3 i microkerneleMulti kernelowa aplikacja w oparciu o Symfony 3 i microkernele
Multi kernelowa aplikacja w oparciu o Symfony 3 i microkernele
 
Introducción al ecosistema de React.js
Introducción al ecosistema de React.jsIntroducción al ecosistema de React.js
Introducción al ecosistema de React.js
 
From * to Symfony2
From * to Symfony2From * to Symfony2
From * to Symfony2
 
SaaS con Symfony2
SaaS con Symfony2SaaS con Symfony2
SaaS con Symfony2
 
Scaling symfony apps
Scaling symfony appsScaling symfony apps
Scaling symfony apps
 
Symfony day 2016
Symfony day 2016Symfony day 2016
Symfony day 2016
 
Getting your open source company to contribution
Getting your open source company to contributionGetting your open source company to contribution
Getting your open source company to contribution
 
Doctrine2 sf2Vigo
Doctrine2 sf2VigoDoctrine2 sf2Vigo
Doctrine2 sf2Vigo
 
WordCamp Cantabria - Código mantenible con WordPress
WordCamp Cantabria  - Código mantenible con WordPressWordCamp Cantabria  - Código mantenible con WordPress
WordCamp Cantabria - Código mantenible con WordPress
 
Nuvola: a tale of migration to AWS
Nuvola: a tale of migration to AWSNuvola: a tale of migration to AWS
Nuvola: a tale of migration to AWS
 
How Symfony Changed My Life
How Symfony Changed My LifeHow Symfony Changed My Life
How Symfony Changed My Life
 
You Got React.js in My PHP
You Got React.js in My PHPYou Got React.js in My PHP
You Got React.js in My PHP
 

Semelhante a Integrando React.js en aplicaciones Symfony (deSymfony 2016)

Tutorial Nro 1 de Desarrollo de Aplicaciones Móviles con Android
Tutorial Nro 1 de Desarrollo de Aplicaciones Móviles con AndroidTutorial Nro 1 de Desarrollo de Aplicaciones Móviles con Android
Tutorial Nro 1 de Desarrollo de Aplicaciones Móviles con Android
Luis Ernesto Castillo Alfaro
 
Buenas Prácticas de desarrollo en Ruby on Rails
Buenas Prácticas de desarrollo en Ruby on RailsBuenas Prácticas de desarrollo en Ruby on Rails
Buenas Prácticas de desarrollo en Ruby on Rails
Sergio Gil
 
Como crear un proyecto en visual Studio 2010
Como crear un proyecto en visual Studio 2010Como crear un proyecto en visual Studio 2010
Como crear un proyecto en visual Studio 2010
a9788398
 

Semelhante a Integrando React.js en aplicaciones Symfony (deSymfony 2016) (20)

Introducción a react + redux
Introducción a react + reduxIntroducción a react + redux
Introducción a react + redux
 
React
ReactReact
React
 
Android Bootcamp - GTUG Uruguay
Android Bootcamp - GTUG UruguayAndroid Bootcamp - GTUG Uruguay
Android Bootcamp - GTUG Uruguay
 
Manual
ManualManual
Manual
 
Tutorial JPA Parte 1 : CRUD BASICO CON JPA Y SWING en NETBEANS
Tutorial  JPA Parte 1  : CRUD BASICO CON JPA Y SWING en NETBEANSTutorial  JPA Parte 1  : CRUD BASICO CON JPA Y SWING en NETBEANS
Tutorial JPA Parte 1 : CRUD BASICO CON JPA Y SWING en NETBEANS
 
Tutorial Nro 1 de Desarrollo de Aplicaciones Móviles con Android
Tutorial Nro 1 de Desarrollo de Aplicaciones Móviles con AndroidTutorial Nro 1 de Desarrollo de Aplicaciones Móviles con Android
Tutorial Nro 1 de Desarrollo de Aplicaciones Móviles con Android
 
Modulo 1 - Proteus
Modulo 1 - ProteusModulo 1 - Proteus
Modulo 1 - Proteus
 
Report
ReportReport
Report
 
Hi Fiber!
Hi Fiber!Hi Fiber!
Hi Fiber!
 
React redux
React redux React redux
React redux
 
Introducción al desarrollo Web: Frontend con Angular 6
Introducción al desarrollo Web: Frontend con Angular 6Introducción al desarrollo Web: Frontend con Angular 6
Introducción al desarrollo Web: Frontend con Angular 6
 
Android bootcamp 101 v2.0
Android bootcamp 101 v2.0Android bootcamp 101 v2.0
Android bootcamp 101 v2.0
 
Reactvolution
ReactvolutionReactvolution
Reactvolution
 
sERVIDORES
sERVIDORESsERVIDORES
sERVIDORES
 
CrossDvlpu - REACT para desarrolladores de ASP.NET
CrossDvlpu - REACT para desarrolladores de ASP.NETCrossDvlpu - REACT para desarrolladores de ASP.NET
CrossDvlpu - REACT para desarrolladores de ASP.NET
 
Cross development - React para desarrolladores de asp.net
Cross development - React para desarrolladores de asp.netCross development - React para desarrolladores de asp.net
Cross development - React para desarrolladores de asp.net
 
Reportes
ReportesReportes
Reportes
 
Programación i
Programación iProgramación i
Programación i
 
Buenas Prácticas de desarrollo en Ruby on Rails
Buenas Prácticas de desarrollo en Ruby on RailsBuenas Prácticas de desarrollo en Ruby on Rails
Buenas Prácticas de desarrollo en Ruby on Rails
 
Como crear un proyecto en visual Studio 2010
Como crear un proyecto en visual Studio 2010Como crear un proyecto en visual Studio 2010
Como crear un proyecto en visual Studio 2010
 

Mais de Ignacio Martín

Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worlds
Ignacio Martín
 

Mais de Ignacio Martín (17)

Elixir/OTP for PHP developers
Elixir/OTP for PHP developersElixir/OTP for PHP developers
Elixir/OTP for PHP developers
 
Introduction to React Native Workshop
Introduction to React Native WorkshopIntroduction to React Native Workshop
Introduction to React Native Workshop
 
Server side rendering with React and Symfony
Server side rendering with React and SymfonyServer side rendering with React and Symfony
Server side rendering with React and Symfony
 
Symfony 4 Workshop - Limenius
Symfony 4 Workshop - LimeniusSymfony 4 Workshop - Limenius
Symfony 4 Workshop - Limenius
 
Server Side Rendering of JavaScript in PHP
Server Side Rendering of JavaScript in PHPServer Side Rendering of JavaScript in PHP
Server Side Rendering of JavaScript in PHP
 
Extending Redux in the Server Side
Extending Redux in the Server SideExtending Redux in the Server Side
Extending Redux in the Server Side
 
Redux Sagas - React Alicante
Redux Sagas - React AlicanteRedux Sagas - React Alicante
Redux Sagas - React Alicante
 
React Native Workshop - React Alicante
React Native Workshop - React AlicanteReact Native Workshop - React Alicante
React Native Workshop - React Alicante
 
Asegurando APIs en Symfony con JWT
Asegurando APIs en Symfony con JWTAsegurando APIs en Symfony con JWT
Asegurando APIs en Symfony con JWT
 
Redux saga: managing your side effects. Also: generators in es6
Redux saga: managing your side effects. Also: generators in es6Redux saga: managing your side effects. Also: generators in es6
Redux saga: managing your side effects. Also: generators in es6
 
Integrating React.js with PHP projects
Integrating React.js with PHP projectsIntegrating React.js with PHP projects
Integrating React.js with PHP projects
 
Introduction to Redux
Introduction to ReduxIntroduction to Redux
Introduction to Redux
 
Keeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and WebpackKeeping the frontend under control with Symfony and Webpack
Keeping the frontend under control with Symfony and Webpack
 
Adding Realtime to your Projects
Adding Realtime to your ProjectsAdding Realtime to your Projects
Adding Realtime to your Projects
 
Symfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worldsSymfony & Javascript. Combining the best of two worlds
Symfony & Javascript. Combining the best of two worlds
 
Symfony 2 CMF
Symfony 2 CMFSymfony 2 CMF
Symfony 2 CMF
 
Presentacion git
Presentacion gitPresentacion git
Presentacion git
 

Integrando React.js en aplicaciones Symfony (deSymfony 2016)

  • 1. deSymfony 16-17 septiembre 2016 Madrid INTEGRANDO REACT.JS EN APLICACIONES SYMFONY Nacho Martín
  • 2. deSymfony ¡Muchas gracias a nuestros patrocinadores!
  • 3. Programo en Limenius Casi todos los proyectos necesitan un frontend rico, por una razón o por otra Hacemos aplicaciones a medida Así que le hemos dado unas cuantas vueltas
  • 4. Objetivo: Mostrar cosas que nos encontramos al usar React desde Symfony, en tierra de nadie, y que ninguno de los dos va a documentar.
  • 5. ¿A mí qué me importa el frontend?
  • 6.
  • 8. Echar huevos en sartén La premisa fundamental Cómo hacer una tortilla Comprar huevos Romper huevos
  • 9. Echar huevos en sartén Batir huevos La premisa fundamental Cómo hacer una tortilla Comprar huevos Romper huevos
  • 12. Opciones: La premisa fundamental 1: Repintamos todo. Simple
  • 13. Opciones: La premisa fundamental 1: Repintamos todo. Simple Poco eficiente
  • 14. Opciones: 2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar. La premisa fundamental 1: Repintamos todo. Simple Poco eficiente
  • 15. Opciones: 2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar. La premisa fundamental 1: Repintamos todo. Simple Complejo Poco eficiente
  • 16. Opciones: 2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar. La premisa fundamental 1: Repintamos todo. Simple Muy eficienteComplejo Poco eficiente
  • 17. Opciones: 2: Buscamos en el DOM dónde insertar, qué mover, qué eliminar. La premisa fundamental 1: Repintamos todo. Simple Muy eficienteComplejo Poco eficiente React nos permite hacer 1, aunque en la sombra hace 2
  • 18. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto.* La premisa fundamental
  • 19. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto.* La premisa fundamental * A menos que quieras tener control absoluto.
  • 20. ¡Clícame! Clicks: 0 Nuestro primer componente
  • 21. ¡Clícame! Clicks: 1 Nuestro primer componente ¡Clícame!
  • 22. Nuestro primer componente import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter;
  • 23. Nuestro primer componente import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Sintaxis ES6 (opcional)
  • 24. Nuestro primer componente import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Sintaxis ES6 (opcional) Estado inicial
  • 25. Nuestro primer componente import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Sintaxis ES6 (opcional) Modificar estado Estado inicial
  • 26. Nuestro primer componente import React, { Component } from 'react'; class Counter extends Component { constructor(props) { super(props); this.state = {count: 1}; } tick() { this.setState({count: this.state.count + 1}); } render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } } export default Counter; Sintaxis ES6 (opcional) Modificar estado render(), lo llama React Estado inicial
  • 27. Trabajar con el estado
  • 28. Trabajar con el estado constructor(props) { super(props); this.state = {count: 1}; } Estado inicial
  • 29. Trabajar con el estado constructor(props) { super(props); this.state = {count: 1}; } Estado inicial this.setState({count: this.state.count + 1}); Asignar estado
  • 30. Trabajar con el estado constructor(props) { super(props); this.state = {count: 1}; } Estado inicial this.setState({count: this.state.count + 1}); Asignar estado this.state.count = this.state.count + 1; Simplemente recordar evitar
  • 31. render() y JSX render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } No es HTML, es JSX. React lo transforma internamente a elementos. Buena práctica: Dejar render() lo más limpio posible, solo un return.
  • 32. render() y JSX render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } No es HTML, es JSX. React lo transforma internamente a elementos. Algunas cosas cambian Buena práctica: Dejar render() lo más limpio posible, solo un return.
  • 33. render() y JSX render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } No es HTML, es JSX. React lo transforma internamente a elementos. Algunas cosas cambian Entre {} podemos insertar expresiones JS Buena práctica: Dejar render() lo más limpio posible, solo un return.
  • 34. Thinking in React render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }
  • 35. Thinking in React render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } Aquí no modificar el estado
  • 36. Thinking in React render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } Aquí no Ajax
  • 37. Thinking in React render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); } Aquí no calcular decimales de pi y enviar un email
  • 38. Importante: pensar la jerarquía
  • 39. Importante: pensar la jerarquía
  • 40. Jerarquía de componentes: props class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  • 41. render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}> Clícame {this.props.name} </button> <span>Clicks: {this.state.count}</span> </div> ); } y en Counter… Jerarquía de componentes: props class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  • 42. render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}> Clícame {this.props.name} </button> <span>Clicks: {this.state.count}</span> </div> ); } y en Counter… Jerarquía de componentes: props class CounterGroup extends Component { render() { return ( <div> <Counter name="amigo"/> <Counter name="señor"/> </div> ); } }
  • 43. Pro tip: componentes sin estado const Saludador = (props) => { <div> <div>Hola {props.name}</div> </div> }
  • 44. Todo depende del estado, por tanto:
  • 45. Todo depende del estado, por tanto: •Podemos reproducir estados,
  • 46. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar,
  • 47. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar, •loguear cambios de estado,
  • 48. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar, •loguear cambios de estado, •hacer álbum de estilo
  • 49. Todo depende del estado, por tanto: •Podemos reproducir estados, •rebobinar, •loguear cambios de estado, •hacer álbum de estilo •…
  • 51. ¿Y si en lugar de algo así… render() { return ( <div className="App"> <button onClick={this.tick.bind(this)}>Clícame!</button> <span>Clicks: {this.state.count}</span> </div> ); }
  • 52. …tenemos algo así? render () { return ( <View> <ListView dataSource={dataSource} renderRow={(rowData) => <TouchableOpacity > <View> <Text>{rowData.name}</Text> <View> <SwitchIOS onValueChange={(value) => this.setMissing(item, value)} value={item.missing} /> </View> </View> </TouchableOpacity> } /> </View> ); }
  • 53. …tenemos algo así? render () { return ( <View> <ListView dataSource={dataSource} renderRow={(rowData) => <TouchableOpacity > <View> <Text>{rowData.name}</Text> <View> <SwitchIOS onValueChange={(value) => this.setMissing(item, value)} value={item.missing} /> </View> </View> </TouchableOpacity> } /> </View> ); } React Native
  • 54. React Targets •Web - react-dom •Mobile - React Native •Gl shaders - gl-react •Canvas - react-canvas •Terminal - react-blessed
  • 56. Setup
  • 60. Webpack • Gestiona dependencias por nosotros. Pros
  • 61. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. Pros
  • 62. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). Pros
  • 63. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). • Uso de preprocesadores/“transpiladores”, como Babel. Pros
  • 64. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). • Uso de preprocesadores/“transpiladores”, como Babel. • Los programadores de frontend no nos arquearán la ceja. Pros
  • 65. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). • Uso de preprocesadores/“transpiladores”, como Babel. • Los programadores de frontend no nos arquearán la ceja. Pros Contras
  • 66. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). • Uso de preprocesadores/“transpiladores”, como Babel. • Los programadores de frontend no nos arquearán la ceja. Pros Contras • Tiene su curva de aprendizaje.
  • 67. Webpack • Gestiona dependencias por nosotros. • Permite varios entornos: producción, desarrollo, …. • Recarga automática de página (e incluso hot reload). • Uso de preprocesadores/“transpiladores”, como Babel. • Los programadores de frontend no nos arquearán la ceja. Pros Contras • Tiene su curva de aprendizaje. Ejemplo completo: https://github.com/Limenius/symfony-react-sandbox
  • 68. Inserción <div id="react-placeholder"></div> import ReactDOM from 'react-dom'; ReactDOM.render( <Counter name="amigo">, document.getElementById('react-placeholder') ); HTML JavaScript
  • 71. ReactBundle {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript:
  • 72. ReactBundle {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript:
  • 73. ReactBundle {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript:
  • 74. ReactBundle {{ react_component('RecipesApp', {'props': props}) }} import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppServer'; ReactOnRails.register({ RecipesApp }); Twig: JavaScript: <div class="js-react-on-rails-component" style="display:none" data- component-name=“RecipesApp” data-props=“[mi Array en JSON]" data- trace=“false" data-dom-id=“sfreact-57d05640f2f1a”></div> HTML generado:
  • 76.
  • 77. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto. La premisa fundamental
  • 78. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto. La premisa fundamental Podemos renderizar componentes desde el servidor.
  • 79. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto. La premisa fundamental Podemos renderizar componentes desde el servidor. • SEO friendly.
  • 80. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto. La premisa fundamental Podemos renderizar componentes desde el servidor. • SEO friendly. • Carga de página más rápida.
  • 81. Dame un estado y una función render() que dependa de él, y olvídate de cómo y cuándo pinto. La premisa fundamental Podemos renderizar componentes desde el servidor. • SEO friendly. • Carga de página más rápida. • Podemos cachear.
  • 82. Client-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }} TWIG
  • 83. Client-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }} TWIG
  • 84. Client-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }} TWIG <div class="js-react-on-rails-component" style="display:none" data- component-name=“RecipesApp” data-props=“…” data-dom- id=“sfreact-57d05640f2f1a”></div> HTML que devuelve el servidor
  • 85. Client-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'client-side'}}) }} TWIG <div class="js-react-on-rails-component" style="display:none" data- component-name=“RecipesApp” data-props=“…” data-dom- id=“sfreact-57d05640f2f1a”></div> HTML que devuelve el servidor Generado en el navegador <div id="sfreact-57d05640f2f1a"><div data-reactroot="" data- reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb" data-reactid="2"><li class="active" data-reactid=“3">Recipes</li> … … </div>
  • 86. Client-side y server-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }} TWIG
  • 87. Client-side y server-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }} TWIG
  • 88. Client-side y server-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }} TWIG HTML que devuelve el servidor <div id="sfreact-57d05640f2f1a"><div data-reactroot="" data- reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb" data-reactid="2"><li class="active" data-reactid=“3">Recipes</li> … … </div>
  • 89. Client-side y server-side {{ react_component('RecipesApp', {'props': props, 'rendering': 'both'}}) }} TWIG HTML que devuelve el servidor <div id="sfreact-57d05640f2f1a"><div data-reactroot="" data- reactid="1" data-react-checksum="2107256409"><ol class="breadcrumb" data-reactid="2"><li class="active" data-reactid=“3">Recipes</li> … … </div> Y React en el navegador toma el control al evaluar el código
  • 91. Opción 1: llamar a subproceso node.js Llamamos a node.js con el componente Process de Symfony * Cómodo (si tenemos node.js instalado). * Lento. Librería: https://github.com/nacmartin/phpexecjs
  • 92. Opción 2: v8js Usamos la extensión de PHP v8js * Cómodo (aunque puede que haya que compilar la extensión y v8). * Lento por ahora, potencialmente podríamos tener v8 precargada usando php-pm. Librería: https://github.com/nacmartin/phpexecjs
  • 93. Configuración Opciones 1 y 2 limenius_react: serverside_rendering: mode: "phpexecjs" phpexecjs detecta si tenemos la extensión v8js, y si no llama a node.js config.yml:
  • 94. Opción 3: Servidor externo Tenemos un servidor node.js “tonto” que nos renderiza React. Es un servidor de <100 líneas, que es independiente de nuestra lógica. * “Incómodo” (hay que mantener el servidor node.js corriendo, que tampoco es para morirse). * Rápido. Ver ejemplo en https://github.com/Limenius/symfony-react-sandbox
  • 95. Configuración Opción 3 limenius_react: serverside_rendering: mode: “external” server-socket-path: “../tal_y_tal/node.sock” config.yml:
  • 96. Lo mejor de los dos mundos En desarrollo usar llamada a node.js o v8js con phpexecjs. En producción tener un servidor externo. Si podemos cachear, menos problema.
  • 97. Es decir: limenius_react: serverside_rendering: mode: “external” server-socket-path: “../tal_y_tal/node.sock” config.yml: limenius_react: serverside_rendering: mode: "phpexecjs" config_dev.yml:
  • 98. ¿Vale la pena una app universal?
  • 99. ¿Vale la pena una app universal? En ocasiones sí, pero introduce complejidad.
  • 100. Soporte para Redux (+brevísima introducción a Redux)
  • 101. Redux: una cuestión de estado guardar Tu nombre: Juan Hola, Juan Cosas de Juan:
  • 102. Redux: una cuestión de estado guardar Tu nombre: Juan Hola, Juan Cosas de Juan:
  • 103. Redux: una cuestión de estado guardar Tu nombre: Juan Hola, Juan Cosas de Juan: state.name callback para cambiarlo
  • 105. dispatch(changeName(‘Juan')); Componente changeName = (name) => { return { type: ‘CHANGE_NAME', name } } Action
  • 106. dispatch(changeName(‘Juan')); Componente changeName = (name) => { return { type: ‘CHANGE_NAME', name } } Action const todo = (state = {name: null}, action) => { switch (action.type) { case 'CHANGE_USER': return { name: action.name } } } Reducer
  • 107. dispatch(changeName(‘Juan')); Componente changeName = (name) => { return { type: ‘CHANGE_NAME', name } } Action const todo = (state = {name: null}, action) => { switch (action.type) { case 'CHANGE_USER': return { name: action.name } } } Reducer Store
  • 108. this.props.name == ‘Juan';dispatch(changeName(‘Juan')); Componente changeName = (name) => { return { type: ‘CHANGE_NAME', name } } Action const todo = (state = {name: null}, action) => { switch (action.type) { case 'CHANGE_USER': return { name: action.name } } } Reducer Store
  • 109. Redux en ReactBundle Ver ejemplo en https://github.com/Limenius/symfony-react-sandbox import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppClient'; import recipesStore from '../store/recipesStore'; ReactOnRails.registerStore({recipesStore}) ReactOnRails.register({ RecipesApp }); Twig: JavaScript: {{ redux_store('recipesStore', props) }} {{ react_component('RecipesApp') }}
  • 110. Redux en ReactBundle Ver ejemplo en https://github.com/Limenius/symfony-react-sandbox import ReactOnRails from 'react-on-rails'; import RecipesApp from './RecipesAppClient'; import recipesStore from '../store/recipesStore'; ReactOnRails.registerStore({recipesStore}) ReactOnRails.register({ RecipesApp }); Twig: JavaScript: {{ redux_store('recipesStore', props) }} {{ react_component('RecipesApp') }} {{ react_component('OtroComponente') }}
  • 111. Compartir store entre componentes
  • 112. Compartir store entre componentes React React React Twig Twig React Al compartir store comparten estado Twig
  • 113. Formularios, un caso especial
  • 114. Formularios muy dinámicos •Dentro de aplicaciones React. •Formularios importantísimos en los que un detalle de usabilidad vale mucho dinero. •Formularios muy específicos. •Formularios muy dinámicos que no cansen (ver typeform por ejemplo).
  • 115.
  • 117. Supongamos un form así public function buildForm(FormBuilderInterface $builder, array $options) { $builder ->add('country', ChoiceType::class, [ 'choices' => [ 'España' => 'es', 'Portugal' => 'pt', ] ]) ->add('addresses', CollectionType::class, ...); };
  • 120. Forms en HTML $form->createView(); submit país: España España Portugal direcciones: C tal- +state.usuario
  • 121. Forms en HTML $form->createView(); submit país: España España Portugal direcciones: C tal- +state.usuario
  • 122. Forms en HTML $form->createView(); submit país: España España Portugal direcciones: C tal- +state.usuario POST bien formadito con country:’es’ y no ‘España’, ‘espana', ‘spain', ‘0’…
  • 123. Forms en HTML $form->createView(); $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST bien formadito con country:’es’ y no ‘España’, ‘espana', ‘spain', ‘0’…
  • 125. Forms en API $form; submit país: España España Portugal direcciones: C tal- +state.usuario ✘
  • 126. Forms en API $form; submit país: España España Portugal direcciones: C tal- +state.usuario ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :(
  • 127. Forms en API $form; $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST “voy a tener suerte” ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :(
  • 128. Forms en API $form; $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST “voy a tener suerte” ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :( This form should not contain extra fields!!1
  • 129. Forms en API $form; $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST “voy a tener suerte” ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :( This form should not contain extra fields!!1 The value you selected is not a valid choice!!
  • 130. Forms en API $form; $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST “voy a tener suerte” ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :( This form should not contain extra fields!!1 The value you selected is not a valid choice!!One or more of the given values is invalid!! :D
  • 131. Forms en API $form; $form->submit($request); submit país: España España Portugal direcciones: C tal- +state.usuario POST “voy a tener suerte” ✘ ¿Cómo sabemos los campos, o los choices? ¡A documentar! :( This form should not contain extra fields!!1 The value you selected is not a valid choice!!One or more of the given values is invalid!! :DMUHAHAHAHAHA!!!!!
  • 132.
  • 133.
  • 134. Definir (y mantener) por triplicado Form SF API docs Form Cliente :( ¿Cuántos programadores hacen falta para hacer un formulario?
  • 139. Lo que necesitamos $form->createView(); HTML ¡Serializar! Vale, ¿A qué formato? API $miTransformador->transform($form);
  • 141.
  • 142. Qué pinta tiene { "$schema": "http://json-schema.org/draft-04/schema#", "title": "Product", "description": "A product from Acme's catalog", "type": "object", "properties": { "name": { "description": "Name of the product", "type": "string" }, "price": { "type": "number", "minimum": 0, "exclusiveMinimum": true }, "tags": { "type": "array", "items": { "type": "string" }, "minItems": 1, "uniqueItems": true } }, "required": ["id", "name", "price"] } definiciones, tipos, reglas de validación :) Nuevo recurso: mi-api/products/form
  • 143. A partir del schema generamos form
  • 144. Generadores client-side • jdorn/json-editor: no React, es un veterano. • mozilla/react-jsonschema-form: React. creado por un antiguo Symfonero (Niko Perriault). • limenius/liform-react: React + redux; integrado con redux-form (I ♥ redux-form) • … • Crear el nuestro puede ser conveniente.
  • 145. Generadores client-side: diferencias Cada uno amplía json-schema a su manera para especificar detalles UI: Orden de campos, qué widget específico usar, etc. Si queremos usarlos al máximo hay que generar un json-schema específico para cada uno (no son totalmente intercambiables). Ejemplo: usar editor Wysiwyg en un campo texto
  • 146. Ejemplo: LiformBundle y liform-react limenius/LiformBundle: Genera json-schema a partir de formularios Symfony. limenius/liform-react: Generador de formularios React (con redux-form) a partir de json-schema. Son Work in progress
  • 147. Cómo serializar: resolvers + transformers $transformer = $resolver->resolve($form); $jsonSchema = $transformer->transform($form); Ejemplo de esta técnica: https://github.com/Limenius/LiformBundle
  • 148. Resolver public function resolve(FormInterface $form) { $types = FormUtil::typeAncestry($form); foreach ($types as $type) { if (isset($this->transformers[$type])) { return $this->transformers[$type]; } } } Misión: Encuentra el transformer apropiado para el form
  • 149. Transformer Misión: Inspecciona el form y crea un array. Si es compuesto resuelve+transforma los hijos. class IntegerTransformer extends AbstractTransformer { public function transform(FormInterface $form) { $schema = [ 'type' => 'integer', ]; if ($liform = $form->getConfig()->getOption('liform')) { if ($format = $liform['format']) { $schema['format'] = $format; } } $this->addCommonSpecs($form, $schema); return $schema; } } protected function addLabel($form, &$schema) { if ($label = $form->getConfig()->getOption('label')) { $schema['title'] = $label; } }
  • 150. Transformer Recopila información de cada Form Field. Podemos sacar mucha información: •Valores por defecto & placeholders. •Atributos del formulario. •Validadores.
  • 151. Ejemplo: validación ‘pattern’ protected function addPattern($form, &$schema) { if ($attr = $form->getConfig()->getOption('attr')) { if (isset($attr['pattern'])) { $schema['pattern'] = $attr['pattern']; } } }
  • 152. Esta técnica vale también para Angular, Backbone, mobile…
  • 154. Repaso: • Qué es React • Setup • Apps universales (ReactBundle) • Para qué sirve Redux • El problema de los formularios • Cómo aliviarlo con JSON Schema
  • 155. MADRID · NOV 27-28 · 2015 ¡Gracias! @nacmartin nacho@limenius.com http://limenius.com Formación, consultoría y desarrollo de proyectos