Redux, es un framework para desarrollo web, que nos provee de herramientas para controlar el estado de nuestra aplicación en un estandarizado patrón de arquitectura hecho por Flux. Se puede ver todo su potencial en una SPA (Single Page Application), es decir una aplicación web de una sola página. Es mucho más ligero que el patrón Flux, y además te permite mantener todos tus estados juntos, en uno solo.
Redux nace de la necesidad de controlar el estado de nuestra aplicación y además poder dar vuelta atrás a alguna acción de usuario, o por algún error en respuesta del servidor. Controlar una aplicación robusta, en donde tengamos que actualizar vistas y controladores , se vuelve difícil cuando cada uno tiene una estado propio, porque además de repetir muchas veces el código, en ocasiones no guardamos el estado antes de alguna acción o evento del usuario. Entonces nuestra aplicación se vuelve menos intuitiva porque tenemos que darle al usuario de nuevo un formulario en blanco, o mostrarle bienvenida a la interfaz de usuario.
Redux almacena todos esos estados, con una función especial (combineReducers), que te permite crear estados individuales para diversas vistas, y agregarlos a un árbol de estado global, al que se puede acceder fácilmente.
Empecemos explicando la ideología y filosofía de Redux, en este patrón existen 3 partes esenciales: Reductores o funciones de estados (Reducers), Acciones (Actions), Funciones modificadoras de estados (Dispatcher).
Hablemos de los 3 aspectos:
- Actions
- Reducers
También son llamados funciones de estado, controlan todo el estado de la vista, inicialmente deben retornar el estado inicial de aplicación y por cada acción recibida, retorna un nuevo estado, NUNCA modificar el estado actual.
- Dispacher
Ahora, hagamos un ejemplo de cómo utilizar correctamente Redux. Con una aplicación que crea una tabla, te permite, elimina, filtrar e incluso modificar los datos. Redux, puede usar con o sin un framework o librería, (jQuery, Material), en este ejemplo lo usaremos sin ninguno de ellos sólo utilizaremos los estilos CSS de Bootstrap.
Configuramos nuestro entorno en un archivo html así:
Estás funciones nos ayudarán a la hora de modificar el estado de nuestra aplicación.
Ahora crearemos nuestro Reducer para la tabla que controlará el estado global:
Así se vería nuestro código html:
Y así se vería nuestra aplicación real.
Antes de hacer algo con nuestra aplicación, debes crear el estado de la aplicación, eso debe hacer con la función createStore de Redux, es importante que creemos el estado antes de hacer algo con él, y asignarlo a una variable global. Para que todas las funciones activadoras, puedan acceder al estado, el objeto obtiene varios métodos para acceder al estado, entre ellos:
Ahora agreguemos los activadores de acciones, es decir desde dónde se puede cambiar el estado de nuestra aplicación:
Que es, cuando agregamos una nueva persona, eliminarla o editarla.
Si quieres probarlo, puedes encontrar los archivos en el siguiente enlace.
O en la siguiente dirección https://github.com/Soluciones-SORE/example-redux
Configuramos nuestro entorno en un archivo html así:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Redux Example</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"/>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"/>
<link rel="stylesheet" href="index.css"/>
</head>
<body>
<!--Librería para usar, funciones de Array (map, filter, find, concat) y Object (assign)-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/6.26.0/polyfill.min.js"></script>
<!--Redux-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.min.js"></script>
</body>
</html>
Primero crearemos funciones de acción en un archivo index.js:
var Redux = window.Redux,
createStore = Redux.createStore;
var ADD = 'ADD', UPDATE = 'UPDATE', DELETE = 'DELETE', FILTER = 'FILTER';
//Actions REDUX
/*
* ADD PERSONA
* Agregar una persona
*/
function addPersona(persona){
return {
type: ADD,
persona: persona
}
}
/*
* FILTER TABLE
* Filtrar la tabla
*/
function filterTableByArea(area){
return {
type: FILTER,
area: area
}
}
/*
* UPDATE PERSONA
* Actualizar una persona
*/
function updatePersona(persona){
return {
type: UPDATE,
persona: persona
}
}
/*
* DELETE PERSONA
* Eliminar una persona
*/
function deletePersona(personaId){
return {
type: DELETE,
id: personaId
}
}
createStore = Redux.createStore;
var ADD = 'ADD', UPDATE = 'UPDATE', DELETE = 'DELETE', FILTER = 'FILTER';
//Actions REDUX
/*
* ADD PERSONA
* Agregar una persona
*/
function addPersona(persona){
return {
type: ADD,
persona: persona
}
}
/*
* FILTER TABLE
* Filtrar la tabla
*/
function filterTableByArea(area){
return {
type: FILTER,
area: area
}
}
/*
* UPDATE PERSONA
* Actualizar una persona
*/
function updatePersona(persona){
return {
type: UPDATE,
persona: persona
}
}
/*
* DELETE PERSONA
* Eliminar una persona
*/
function deletePersona(personaId){
return {
type: DELETE,
id: personaId
}
}
Estás funciones nos ayudarán a la hora de modificar el estado de nuestra aplicación.
Ahora crearemos nuestro Reducer para la tabla que controlará el estado global:
/*
* Estado global de la aplicación
*/
function todoList(state, action){
//Estado inicial de la aplicación
state = state || {
todos: []
}
switch(action.type){
//Caso para agregar una persona
case ADD:
if(!action.persona.id){
action.persona.id = state.todos.length + 1
}
return Object.assign({}, state, {
todos: state.todos.concat(action.persona)
})
//Filtrar la tabla
case FILTER:
applyFilterTable([].concat(state.todos), action.area)
return state
//Actualizar un dato de la tabla
case UPDATE:
return Object.assign({}, state, {
todos: updateTablePersona(state.todos, action.persona)
})
//Eliminar
case DELETE:
return Object.assign({}, state, {
todos: deleteTablePersona(state.todos, action.id)
})
default:
return state
}
return state
}
Ahora crearemos funciones auxiliares para modificar los estados
//Aplicar un filtro
function applyFilterTable(todos, area){
var tableBody = document.getElementById('todolist')
while(tableBody.firstChild) {
tableBody.removeChild(tableBody.firstChild)
}
if(area === 'todo'){
todos.forEach(function(persona){
return addTablePersona(persona)
})
}else{
todos.filter(function(persona){
return persona.area === area
}).forEach(function(persona){
return addTablePersona(persona)
})
}
}
//Actualizar tabla
function updateTablePersona(todos, personaUpdate){
return todos.map(function(persona){
if(persona.id === personaUpdate.id){
var newPersona = {}
if(personaUpdate.name){
newPersona.name = personaUpdate.name
var cellName = persona.tableRow.firstChild
while(cellName.firstChild){
cellName.removeChild(cellName.firstChild)
}
cellName.appendChild( document.createTextNode(personaUpdate.name) )
}
if(personaUpdate.area){
newPersona.area = personaUpdate.area
var cellArea = persona.tableRow.childNodes[1]
while(cellArea.firstChild){
cellArea.removeChild(cellArea.firstChild)
}
cellArea.appendChild( document.createTextNode(personaUpdate.area) )
}
return Object.assign({}, persona, newPersona)
}
return persona
})
}
Después algo de vista para aplicación, una tabla, un formulario para agregar, y unas herramientas para filtrar los datos.* Estado global de la aplicación
*/
function todoList(state, action){
//Estado inicial de la aplicación
state = state || {
todos: []
}
switch(action.type){
//Caso para agregar una persona
case ADD:
if(!action.persona.id){
action.persona.id = state.todos.length + 1
}
return Object.assign({}, state, {
todos: state.todos.concat(action.persona)
})
//Filtrar la tabla
case FILTER:
applyFilterTable([].concat(state.todos), action.area)
return state
//Actualizar un dato de la tabla
case UPDATE:
return Object.assign({}, state, {
todos: updateTablePersona(state.todos, action.persona)
})
//Eliminar
case DELETE:
return Object.assign({}, state, {
todos: deleteTablePersona(state.todos, action.id)
})
default:
return state
}
return state
}
Ahora crearemos funciones auxiliares para modificar los estados
//Aplicar un filtro
function applyFilterTable(todos, area){
var tableBody = document.getElementById('todolist')
while(tableBody.firstChild) {
tableBody.removeChild(tableBody.firstChild)
}
if(area === 'todo'){
todos.forEach(function(persona){
return addTablePersona(persona)
})
}else{
todos.filter(function(persona){
return persona.area === area
}).forEach(function(persona){
return addTablePersona(persona)
})
}
}
//Actualizar tabla
function updateTablePersona(todos, personaUpdate){
return todos.map(function(persona){
if(persona.id === personaUpdate.id){
var newPersona = {}
if(personaUpdate.name){
newPersona.name = personaUpdate.name
var cellName = persona.tableRow.firstChild
while(cellName.firstChild){
cellName.removeChild(cellName.firstChild)
}
cellName.appendChild( document.createTextNode(personaUpdate.name) )
}
if(personaUpdate.area){
newPersona.area = personaUpdate.area
var cellArea = persona.tableRow.childNodes[1]
while(cellArea.firstChild){
cellArea.removeChild(cellArea.firstChild)
}
cellArea.appendChild( document.createTextNode(personaUpdate.area) )
}
return Object.assign({}, persona, newPersona)
}
return persona
})
}
Así se vería nuestro código html:
<div id="todo" class="table-responsive">
<div class="input-group">
<label for="filter" class="input-group-addon">Filtrar</label>
<select name="filter" id="filter-todo" class="form-control" onchange="filterTodo(event)">
<option value="todo" selected>Todos</option>
<option value="contabilidad">Contabilidad</option>
<option value="sistemas">Sistemas</option>
</select>
</div>
<table class="table">
<thead>
<tr>
<th>Nombre</th>
<th>Area</th>
<th>Herramientas</th>
</tr>
</thead>
<tbody id="todolist"></tbody>
</table>
</div>
<form action="/add" onsubmit="addTodo(event)" id="form-add-persona">
<legend>Agregar</legend>
<fieldset class="form-group">
<label for="nombre">Nombre</label>
<input type="text" placeholder="Nombre" name="nombre" id="inputName" class="form-control" required/>
</fieldset>
<fieldset class="form-group">
<label for="area">Area</label>
<select name="area" id="selectArea" class="form-control">
<option value="contabilidad">Contabilidad</option>
<option value="sistemas" selected>Sistemas</option>
</select>
</fieldset>
<button type="submit" class="btn btn-default" id="btn-add">Agregar</button>
</form>
<div class="input-group">
<label for="filter" class="input-group-addon">Filtrar</label>
<select name="filter" id="filter-todo" class="form-control" onchange="filterTodo(event)">
<option value="todo" selected>Todos</option>
<option value="contabilidad">Contabilidad</option>
<option value="sistemas">Sistemas</option>
</select>
</div>
<table class="table">
<thead>
<tr>
<th>Nombre</th>
<th>Area</th>
<th>Herramientas</th>
</tr>
</thead>
<tbody id="todolist"></tbody>
</table>
</div>
<form action="/add" onsubmit="addTodo(event)" id="form-add-persona">
<legend>Agregar</legend>
<fieldset class="form-group">
<label for="nombre">Nombre</label>
<input type="text" placeholder="Nombre" name="nombre" id="inputName" class="form-control" required/>
</fieldset>
<fieldset class="form-group">
<label for="area">Area</label>
<select name="area" id="selectArea" class="form-control">
<option value="contabilidad">Contabilidad</option>
<option value="sistemas" selected>Sistemas</option>
</select>
</fieldset>
<button type="submit" class="btn btn-default" id="btn-add">Agregar</button>
</form>
Y así se vería nuestra aplicación real.
Antes de hacer algo con nuestra aplicación, debes crear el estado de la aplicación, eso debe hacer con la función createStore de Redux, es importante que creemos el estado antes de hacer algo con él, y asignarlo a una variable global. Para que todas las funciones activadoras, puedan acceder al estado, el objeto obtiene varios métodos para acceder al estado, entre ellos:
- getState
- susbcribe
- dispatch
Ahora agreguemos los activadores de acciones, es decir desde dónde se puede cambiar el estado de nuestra aplicación:
Que es, cuando agregamos una nueva persona, eliminarla o editarla.
function deleteTablePersona(todos, idPersona){
var prevTodos = todos,
buttonBack = document.getElementById('button-back');
return todos.filter(function(persona){
if(persona.id === idPersona){
persona.tableRow.parentNode.removeChild(persona.tableRow)
}
return persona.id !== idPersona
})
}
function filterTodo(e){
var select = e.target || this
var area = select.value
store.dispatch( filterTableByArea(area) )
}
function addTodo(e){
if(e && ( e.preventDefault || e.stopPropagation ) ){
e.preventDefault() && e.stopPropagation()
}
var inputName = document.getElementById('inputName'),
selectArea = document.getElementById('selectArea'),
name = inputName.value,
area = selectArea.value,
persona = {
name: name,
area: area
};
var storePersona = addTablePersona(persona)
store.dispatch(addPersona(storePersona))
inputName.value = ''
}
/*
* Manejadores de los eventos para los botones de las personas
*/
/*
* UPDATE PERSONA
*/
function handleUpdatePersona(persona){
var formUpdate = document.getElementById('form-add-persona'),
inputName = document.getElementById('inputName'),
selectArea = document.getElementById('selectArea'),
buttonCalcel = document.createElement('button'),
btnAdd = document.getElementById('btn-add');
function changeUpdateToAdd(){
formUpdate.onsubmit = addTodo
formUpdate.removeChild(buttonCalcel)
while(btnAdd.firstChild){
btnAdd.removeChild(btnAdd.firstChild)
}
btnAdd.appendChild( document.createTextNode('Agregar') )
inputName.value = ''
}
buttonCalcel.setAttribute('class', 'btn btn-default')
buttonCalcel.appendChild( document.createTextNode('Cancelar') )
buttonCalcel.onclick = changeUpdateToAdd
formUpdate.appendChild(buttonCalcel)
while(btnAdd.firstChild){
btnAdd.removeChild(btnAdd.firstChild)
}
btnAdd.appendChild( document.createTextNode('Modificar') )
inputName.value = persona.name
selectArea.value = persona.area
formUpdate.onsubmit = function(e){
e.preventDefault()
var newPersona = Object.assign({}, persona, {name: inputName.value, area: selectArea.value})
store.dispatch(updatePersona(newPersona))
changeUpdateToAdd()
}
}
/*
* DELETE PERSONA
*/
function handleDeletePersona(idPersona){
return store.dispatch(deletePersona(idPersona))
}
//Agregar una fila a tabla con la nueva persona
function addTablePersona(persona){
var table = document.getElementById('todolist'),
tableRow = document.createElement('tr'),
tableCellName = document.createElement('td'),
tableCellArea = document.createElement('td'),
tableCellTools = document.createElement('td'),
buttonUpdate = document.createElement('button'),
buttonDelete = document.createElement('button');
buttonDelete.appendChild( document.createTextNode('Eliminar') )
buttonUpdate.appendChild( document.createTextNode('Actualizar') )
buttonDelete.setAttribute('class', 'btn btn-default')
buttonUpdate.setAttribute('class', 'btn btn-default')
buttonDelete.onclick = function(){
handleDeletePersona(persona.id)
}
buttonUpdate.onclick = function(){
handleUpdatePersona(persona)
}
tableCellArea.appendChild( document.createTextNode(persona.area) )
tableCellName.appendChild( document.createTextNode(persona.name) )
tableCellTools.appendChild( buttonUpdate )
tableCellTools.appendChild( buttonDelete )
tableRow.appendChild(tableCellName)
tableRow.appendChild(tableCellArea)
tableRow.appendChild(tableCellTools)
table.appendChild(tableRow)
persona.tableRow = tableRow
return persona
}
Es una verdadera aplicación, que crea una tabla con datos, todo lo que un día quisimos, y con Redux, podemos controlar su estado en un sólo sentido.var prevTodos = todos,
buttonBack = document.getElementById('button-back');
return todos.filter(function(persona){
if(persona.id === idPersona){
persona.tableRow.parentNode.removeChild(persona.tableRow)
}
return persona.id !== idPersona
})
}
function filterTodo(e){
var select = e.target || this
var area = select.value
store.dispatch( filterTableByArea(area) )
}
function addTodo(e){
if(e && ( e.preventDefault || e.stopPropagation ) ){
e.preventDefault() && e.stopPropagation()
}
var inputName = document.getElementById('inputName'),
selectArea = document.getElementById('selectArea'),
name = inputName.value,
area = selectArea.value,
persona = {
name: name,
area: area
};
var storePersona = addTablePersona(persona)
store.dispatch(addPersona(storePersona))
inputName.value = ''
}
/*
* Manejadores de los eventos para los botones de las personas
*/
/*
* UPDATE PERSONA
*/
function handleUpdatePersona(persona){
var formUpdate = document.getElementById('form-add-persona'),
inputName = document.getElementById('inputName'),
selectArea = document.getElementById('selectArea'),
buttonCalcel = document.createElement('button'),
btnAdd = document.getElementById('btn-add');
function changeUpdateToAdd(){
formUpdate.onsubmit = addTodo
formUpdate.removeChild(buttonCalcel)
while(btnAdd.firstChild){
btnAdd.removeChild(btnAdd.firstChild)
}
btnAdd.appendChild( document.createTextNode('Agregar') )
inputName.value = ''
}
buttonCalcel.setAttribute('class', 'btn btn-default')
buttonCalcel.appendChild( document.createTextNode('Cancelar') )
buttonCalcel.onclick = changeUpdateToAdd
formUpdate.appendChild(buttonCalcel)
while(btnAdd.firstChild){
btnAdd.removeChild(btnAdd.firstChild)
}
btnAdd.appendChild( document.createTextNode('Modificar') )
inputName.value = persona.name
selectArea.value = persona.area
formUpdate.onsubmit = function(e){
e.preventDefault()
var newPersona = Object.assign({}, persona, {name: inputName.value, area: selectArea.value})
store.dispatch(updatePersona(newPersona))
changeUpdateToAdd()
}
}
/*
* DELETE PERSONA
*/
function handleDeletePersona(idPersona){
return store.dispatch(deletePersona(idPersona))
}
//Agregar una fila a tabla con la nueva persona
function addTablePersona(persona){
var table = document.getElementById('todolist'),
tableRow = document.createElement('tr'),
tableCellName = document.createElement('td'),
tableCellArea = document.createElement('td'),
tableCellTools = document.createElement('td'),
buttonUpdate = document.createElement('button'),
buttonDelete = document.createElement('button');
buttonDelete.appendChild( document.createTextNode('Eliminar') )
buttonUpdate.appendChild( document.createTextNode('Actualizar') )
buttonDelete.setAttribute('class', 'btn btn-default')
buttonUpdate.setAttribute('class', 'btn btn-default')
buttonDelete.onclick = function(){
handleDeletePersona(persona.id)
}
buttonUpdate.onclick = function(){
handleUpdatePersona(persona)
}
tableCellArea.appendChild( document.createTextNode(persona.area) )
tableCellName.appendChild( document.createTextNode(persona.name) )
tableCellTools.appendChild( buttonUpdate )
tableCellTools.appendChild( buttonDelete )
tableRow.appendChild(tableCellName)
tableRow.appendChild(tableCellArea)
tableRow.appendChild(tableCellTools)
table.appendChild(tableRow)
persona.tableRow = tableRow
return persona
}
Si quieres probarlo, puedes encontrar los archivos en el siguiente enlace.
O en la siguiente dirección https://github.com/Soluciones-SORE/example-redux
Comentarios
Publicar un comentario