Construir una pantalla

Nos hemos concentrado en crear interfaces de usuario de abajo hacia arriba; comenzando por lo pequeño y añadiendo complejidad. Esto nos ha permitido desarrollar cada componente de forma aislada, determinar los datos que necesita y jugar con ellos en Storybook. Todo sin necesidad de levantar un servidor o construir pantallas!

En este capítulo continuaremos aumentando la sofisticación combinando componentes en una pantalla y desarrollando esa pantalla en Storybook.

Componentes de contenedor anidados

Como nuestra aplicación es muy simple, la pantalla que construiremos es bastante trivial, simplemente envolviendo el componente TaskList (que proporciona sus propios datos a través de Redux) en alguna maqueta y sacando un campo error de primer nivel de redux (asumamos que pondremos ese campo si tenemos algún problema para conectarnos a nuestro servidor):

import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';

import TaskList from './TaskList';

export function PureInboxScreen({ error }) {
  if (error) {
    return (
      <div className="page lists-show">
        <div className="wrapper-message">
          <span className="icon-face-sad" />
          <div className="title-message">Oh no!</div>
          <div className="subtitle-message">Algo va mal</div>
        </div>
      </div>
    );
  }

  return (
    <div className="page lists-show">
      <nav>
        <h1 className="title-page">
          <span className="title-wrapper">Taskbox</span>
        </h1>
      </nav>
      <TaskList />
    </div>
  );
}

PureInboxScreen.propTypes = {
  error: PropTypes.string,
};

PureInboxScreen.defaultProps = {
  error: null,
};

export default connect(({ error }) => ({ error }))(PureInboxScreen);

También cambiamos el componente App para renderizar la pantalla de la bandeja de entrada InboxScreen (normalmente usaríamos un router para elegir la pantalla correcta, pero no nos preocupemos por ello aquí):

import React, { Component } from 'react';
import { Provider } from 'react-redux';
import store from './lib/redux';

import InboxScreen from './components/InboxScreen';

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <InboxScreen />
      </Provider>
    );
  }
}

export default App;

Sin embargo, donde las cosas se ponen interesantes es en la representación de la historia en Storybook.

Como vimos anteriormente, el componente TaskList es un contenedor que renderiza el componente de presentación PureTaskList. Por definición, los componentes de un contenedor no pueden simplemente hacer render de forma aislada; esperan que se les pase algún contexto o que se conecten a un servicio. Lo que esto significa es que para hacer render de un contenedor en Storybook, debemos mockearlo (es decir, proporcionar una versión ficticia) del contexto o servicio que requiere.

Al colocar la "Lista de tareas" TaskList en Storybook, pudimos esquivar este problema simplemente renderizando la PureTaskList y evadiendo el contenedor. Haremos algo similar y renderizaremos la PureInboxScreen en Storybook también.

Sin embargo, para la PureInboxScreen tenemos un problema porque aunque la PureInboxScreen en si misma es presentacional, su hijo, la TaskList, no lo es. En cierto sentido la PureInboxScreen ha sido contaminada por la "contenedorización". Así que cuando preparamos nuestras historias:

import React from 'react';
import { storiesOf } from '@storybook/react';

import { PureInboxScreen } from './InboxScreen';

storiesOf('InboxScreen', module)
  .add('default', () => <PureInboxScreen />)
  .add('error', () => <PureInboxScreen error="Something" />);

Vemos que aunque la historia de error funciona bien, tenemos un problema en la historia default, porque la TaskList no tiene una store de Redux a la que conectarse. (También encontrarás problemas similares cuando intentes probar la PureInboxScreen con un test unitario).

Broken inbox

Una forma de evitar este problema es nunca renderizar componentes contenedores en ninguna parte de tu aplicación excepto en el nivel más alto y en su lugar pasar todos los datos requeridos bajo la jerarquía de componentes.

Sin embargo, los desarrolladores necesitarán inevitablemente renderizar los contenedores más abajo en la jerarquía de componentes. Si queremos renderizar la mayor parte o la totalidad de la aplicación en Storybook (¡lo hacemos!), necesitamos una solución a este problema.

Por otro lado, la transmisión de datos a nivel jerárquico es un enfoque legítimo, especialmente cuando utilizas GraphQL. Así es como hemos construido Chromatic junto a más de 670+ historias.

Suministrando contexto con decoradores

La buena noticia es que es fácil suministrar una store de Redux a la InboxScreen en una historia! Podemos usar una versión mockeada de la store de Redux provista en un decorador:

import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { Provider } from 'react-redux';

import { PureInboxScreen } from './InboxScreen';
import { defaultTasks } from './TaskList.stories';

// Un mock super simple de un store de redux
const store = {
  getState: () => {
    return {
      tasks: defaultTasks,
    };
  },
  subscribe: () => 0,
  dispatch: action('dispatch'),
};

storiesOf('InboxScreen', module)
  .addDecorator(story => <Provider store={store}>{story()}</Provider>)
  .add('default', () => <PureInboxScreen />)
  .add('error', () => <PureInboxScreen error="Something" />);

Existen enfoques similares para proporcionar un contexto simulado para otras bibliotecas de datos, tales como Apollo, Relay y algunas otras.

Recorrer los estados en Storybook hace que sea fácil comprobar que lo hemos hecho correctamente:

Desarrollo basado en componentes

Empezamos desde abajo con Task, luego progresamos a TaskList, ahora estamos aquí con una interfaz de usuario de pantalla completa. Nuestra InboxScreen contiene un componente de contenedor anidado e incluye historias de acompañamiento.

El desarrollo basado en componentes te permite expandir gradualmente la complejidad a medida que asciendes en la jerarquía de componentes. Entre los beneficios están un proceso de desarrollo más enfocado y una mayor cobertura de todas las posibles mutaciones de la interfaz de usuario. En resumen, la CDD te ayuda a construir interfaces de usuario de mayor calidad y complejidad.

Aún no hemos terminado, el trabajo no termina cuando se construye la interfaz de usuario. También tenemos que asegurarnos de que siga siendo duradero a lo largo del tiempo.

Keep your code in sync with this chapter. View 22a1898 on GitHub.
Tweet "I’m learning Storybook! It’s a great dev tool for UI components."
Testing
Aprende las formas de hacer test a los componentes de la UI