Uso con React

Comencemos enfatizando que Redux no tiene relación alguna con React. Puedes escribir aplicaciones Redux con React, Angular, Ember, jQuery o vanilla JavaScript.

Dicho esto, Redux funciona especialmente bien con librerías como React y Deku porque te permiten describir la interfaz de usuario como una función de estado, y Redux emite actualizaciones de estado en respuesta a acciones.

Usaremos React para crear nuestra una aplicación sencilla de asuntos pendites (To-do).

Instalando React Redux

React Redux no está incluido en Redux de manera predeterminada. Debe instalarlo explícitamente:

npm install --save react-redux

Si no usas npm, puedes obtener la distribución UMD (Universal Module Definition) más reciente desde unpkg (ya sea la distribución de desarrollo o la de producción). La distribución UMD exporta una variable global llamada window.ReactRedux por si la añades a tu página a través de la etiqueta <script>.

Componentes de Presentación y Contenedores

Para asociar React con Redux se recurre a la idea de separación de presentación y componentes contenedores. Si no está familiarizado con estos términos, lea sobre ellos primero, y luego vuelva. ¡Son importantes, así que vamos le vamos a esperar!

¿Ha terminado de leer el artículo? Repasemos sus diferencias:

Componentes de Presentación Componentes Contenedores
Propósito Como se ven las cosas (markup, estilos) Como funcionan las cosas (búsqueda de datos, actualizaciones de estado)
Pertinente a Redux No Yes
Para leer datos Lee datos de los props Se suscribe al estado en Redux
Para manipular datos Invoca llamada de retorno (callback) desde los props Envia acciones a Redux
Son escritas Manualmente Usualmente generados por React Redux

La mayoría de los componentes que escribiremos serán de presentación, pero necesitaremos generar algunos componentes contenedores para conectarlos al store que maneja Redux. Con esto y el resumen de diseño que mencionaremos a continuación no implica que los componentes contenedores deban estar cerca o en la parte superior del árbol de componentes. Si un componente contenedor se vuelve demasiado complejo (es decir, tiene componentes de presentación fuertemente anidados con innumerables devoluciones de llamadas que se pasan hacia abajo), introduzca otro contenedor dentro del árbol de componentes como se indica en el FAQ.

Técnicamente usted podría escribir los componentes contenedores manualmente usando store.subscribe(). No le aconsejamos que haga esto porque React Redux hace muchas optimizaciones de rendimiento que son difíciles de hacer a mano. Por esta razón, en lugar de escribir los componentes contenedores, los generaremos utilizando el comando connect(), función proporcionada por React Redux, como verá a continuación.

Diseño de la jerarquía de componentes

Recuerda cómo diseñamos y dimos forma al objecto del estado raíz? Es hora de diseñar la jerarquía de la interfaz de usuario para que coincida con este objeto del estado. Esto no es una tarea específica de Redux. Thinking in React es un excelente tutorial que explica el proceso.

Nuestro breve resumen del diseño es simple. Queremos mostrar una lista de asuntos pendientes. Al hacer clic, un elemento de la lista se tachará como completado. Queremos mostrar un campo en el que el usuario puede agregar una tarea nueva. En el pie de página, queremos mostrar un toggle para mostrar todas las taras, sólo las completadas, o sólo las activas.

Diseño de componentes de presentación

Podemos ver los siguientes componentes de presentación y sus props surgir a través de esta breve descripción:

  • TodoList es una lista que mostrará las tareas pendientes disponibles.
    • todos: Array es un arreglo de tareas pendientes que contiene la siguiente descripción { id, text, completed }.
    • onTodoClick(id: number) es un callback para invocar cuando un asunto pendientes es presionado.
  • Todo es un asunto pendiente.
    • text: string es el texto a mostrar.
    • completed: boolean indica si la tarea debe aparecer tachada.
    • onClick() es un callback para invocar cuando la tarea es presionada.
  • Link es el enlace con su callback.
    • onClick() es un callback para invocar cuando el enlace es presionado.
  • Footer es donde dejamos que el usuario cambie las tareas pendientes visibles actualmente.
  • App es el componente raíz que representa todo lo demás.

Cada artículo describe la apariencia pero no conoce de donde vienen los datos, o cómo cambiarlos. Sólo muestran lo que se les da. Si migras de Redux a otra cosa, podrás mantener todos estos componentes exactamente iguales. No dependen de Redux en absoluto.

Diseño de componentes contenedores

También necesitaremos algunos componentes contenedores para conectar los componentes de presentación a Redux. Por ejemplo, el componente de presentación TodoList necesita un contenedor comoVisibleTodoList que se suscribe al store de Redux y debe saber cómo aplicar el filtro de visibilidad. Para cambiar el filtro de visibilidad, proporcionaremos un componente contenedor FilterLink que renderiza un Link que distribuye la debida acción al hacer clic:

  • VisibleTodoList filtra los asuntos de acuerdo a la visibilidad actual y renderiza el TodoList.
  • FilterLink obtiene el filtro de visibilidad actual y renderiza un Link.
    • filter: string es el tipo del filtro de visibilidad.

Diseño de otros componentes

A veces es difícil saber si un componente debe ser componente de presentación o contenedor. Por ejemplo, a veces la forma y la función están realmente entrelazadas, como en el caso de este pequeño componente:

  • AddTodo es un campo de entrada con un botón "Añadir tarea"

Técnicamente podríamos dividirlo en dos componentes, pero podría ser demasiado pronto en esta etapa. Está bien mezclar presentación y lógica en un componente que sea muy pequeño. A medida que crece, será más obvio cómo dividirlo, así que lo dejaremos en uno solo.

Implementación de componentes

Vamos a escribir los componentes! Comenzaremos con los componentes de presentación por lo que no es necesario pensar en la relación con Redux todavía.

Implementación de componentes de presentación

Todos estos son componentes normales de React, por lo que no los examinaremos en detalle. Escribiremos componentes funcionales sin-estado a menos que necesitemos usar el estado local o los métodos del ciclo de duración. Esto no significa que los componentes de presentación tengan que ser funciones - es solo que es más fácil definirlos de esta manera. Si, y cuando necesites agregar un estado local, métodos de ciclo de duración u optimizaciones de rendimiento, puede convertirlos a clases.

components/Todo.js

import React, { PropTypes } from 'react'

const Todo = ({ onClick, completed, text }) => (
  <li
    onClick={onClick}
    style={{
      textDecoration: completed ? 'line-through' : 'none'
    }}
  >
    {text}
  </li>
)

Todo.propTypes = {
  onClick: PropTypes.func.isRequired,
  completed: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired
}

export default Todo

components/TodoList.js

import React, { PropTypes } from 'react'
import Todo from './Todo'

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

TodoList.propTypes = {
  todos: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.number.isRequired,
    completed: PropTypes.bool.isRequired,
    text: PropTypes.string.isRequired
  }).isRequired).isRequired,
  onTodoClick: PropTypes.func.isRequired
}

export default TodoList

components/Link.js

import React, { PropTypes } from 'react'

const Link = ({ active, children, onClick }) => {
  if (active) {
    return <span>{children}</span>
  }

  return (
    <a href="#"
       onClick={e => {
         e.preventDefault()
         onClick()
       }}
    >
      {children}
    </a>
  )
}

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired
}

export default Link

components/Footer.js

import React from 'react'
import FilterLink from '../containers/FilterLink'

const Footer = () => (
  <p>
    Show:
    {" "}
    <FilterLink filter="SHOW_ALL">
      Todos
    </FilterLink>
    {", "}
    <FilterLink filter="SHOW_ACTIVE">
      Activo
    </FilterLink>
    {", "}
    <FilterLink filter="SHOW_COMPLETED">
      Completado
    </FilterLink>
  </p>
)

export default Footer

components/App.js

import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'

const App = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
    <Footer />
  </div>
)

export default App

Ahora es el momento de conectar los componentes de presentación a Redux mediante la creación de algunos contenedores. Técnicamente, un componente contenedor es sólo un componente de React que utiliza store.subscribe () para leer una parte del árbol de estado en Redux y suministrar los props a un componente de presentación que renderiza. Puedes escribir un componente contenedor manualmente, pero sugerimos generar los componentes contenedores con la función connect() de la librería React Redux, ya que proporciona muchas optimizaciones útiles para evitar re-renders innecesarios. (Un beneficio de utilizar esta librería es que usted no tiene que preocuparse por la implementación del método shouldComponentUpdate recomendado por React para mejor rendimiento.)

Para usar connect(), es necesario definir una función especial llamada mapStateToProps que indiqua cómo transformar el estado actual del store Redux en los props que desea pasar a un componente de presentación. Por ejemplo, VisibleTodoList necesita calcular todos para pasar a TodoList, así que definimos una función que filtra el state.todos de acuerdo con el state.visibilityFilter, y lo usamos en su mapStateToProps:

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

Además de leer el estado, los componentes contenedores pueden enviar acciones. De manera similar, puede definir una función llamada mapDispatchToProps() que recibe el método dispatch() y devuelve los callback props que deseas inyectar en el componente de presentación. Por ejemplo, queremos que VisibleTodoList inyecte un prop llamado onTodoClick en el componente TodoList, y queremos que onTodoClick envíe una acción TOGGLE_TODO:

const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
    }
  }
}

Finalmente, creamos VisibleTodoList llamando connect() y le pasamos estas dos funciones:

import { connect } from 'react-redux'

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList

Estos son los conceptos básicos de la API de React Redux, pero hay algunos atajos y opciones avanzadas por lo que le animamos a revisar su documentación en detalle. En caso de que que te preocupe el hecho que mapStateToProps esté creando objetos nuevos con demasiada frecuencia, quizás desees aprender acerca de computar datos derivados con reselect.

El resto de los componentes contenedores están definidos a continuación:

containers/FilterLink.js

import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch(setVisibilityFilter(ownProps.filter))
    }
  }
}

const FilterLink = connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)

export default FilterLink

containers/VisibleTodoList.js

import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
    }
  }
}

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList

Implementación de otros componentes

containers/AddTodo.js

import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

let AddTodo = ({ dispatch }) => {
  let input

  return (
    <div>
      <form onSubmit={e => {
        e.preventDefault()
        if (!input.value.trim()) {
          return
        }
        dispatch(addTodo(input.value))
        input.value = ''
      }}>
        <input ref={node => {
          input = node
        }} />
        <button type="submit">
          Añadir tarea
        </button>
      </form>
    </div>
  )
}
AddTodo = connect()(AddTodo)

export default AddTodo

Transferir al store

Todos los componentes contenedores necesitan acceso al store Redux para que puedan suscribirse a ella. Una opción sería pasarlo como un prop a cada componente contenedor. Sin embargo, se vuelve tedioso, ya que hay que enlzar store incluso a través del componentes de presentación ya que puede suceder que tenga que renderizar un contenedor allá en lo profundo del árbol de componentes.

La opción que recomendamos es usar un componente React Redux especial llamado <Proveedor> para mágicamente hacer que el store esté disponible para todos los componentes del contenedor en la aplicación sin pasarlo explícitamente. Sólo es necesario utilizarlo una vez al renderizar el componente raíz:

index.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

Siguientes pasos

Lea el código fuente completo de este tutorial para internalizar mejor el conocimiento que ha adquirido. Luego, dirígete directamente al tutorial avanzado para aprender a manejar los network requests y el routing!

results matching ""

    No results matching ""