Back to Intro to Storybook
Chapters
  • Leg los
  • Einfache Komponente
  • Komposition
  • Daten
  • Screens
  • Deployment
  • Tests
  • Addons
  • Fazit
  • Unterstützen

Einen Screen erstellen

Stelle einen Screen aus Komponenten zusammen
Diese Community-Übersetzung wurde noch nicht auf die neueste Storybook-Version aktualisiert. Helfen Sie uns, es zu aktualisieren, indem Sie die Änderungen im deutschen Leitfaden für diese Übersetzung übernehmen. Pull requests sind willkommen.

Bisher haben wir uns darauf konzentriert, UIs bottom-up zu bauen; klein starten und Komplexität hinzufügen. Das erlaubte uns, jede Komponente in Isolation zu entwickeln, ihre Anforderungen an Daten zu ermitteln und damit in Storybook herumzuspielen. All das, ohne einen Server aufzusetzen oder Screens zu erstellen.

In diesem Kapitel werden wir den Schwierigkeitsgrad weiter erhöhen, indem wir Komponenten in einem Screen kombinieren, den wir in Storybook entwickeln.

Verschachtelte Container-Komponenten

Da unsere App sehr einfach ist, wird auch unser Screen ziemlich trivial sein. Wir fügen die TaskList-Komponente (die ihre Daten via Redux zur Verfügung stellt) in ein Layout ein und holen uns ein top-level error-Feld aus Redux (lass uns annehmen, wir setzten dieses Feld, wenn wir Probleme mit der Verbindung zu unserem Server haben). Lege InboxScreen.js in deinem components-Verzeichnis an:

Copy
src/components/InboxScreen.js
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">Something went wrong</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);

Außerdem ändern wir die App-Komponente so, dass sie den InboxScreen rendert (vermutlich würden wir einen Router nutzen, um den richtigen Screen zu wählen, aber das soll jetzt nicht unsere Sorge sein):

Copy
src/App.js
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;

Es wird jedoch erst interessant, wenn wir die Story in Storybook rendern.

Wie zuvor gesehen, ist die TaskList-Komponente ein Container, der die darstellende PureTaskList-Komponente rendert. Laut Definition können Container-Komponenten nicht einfach in Isolation gerendert werden; sie erwarten, dass ihnen ein Kontext übergeben wird, oder dass sie sich zu einem Service verbinden. Das bedeutet, dass wir den Kontext oder den Service, den ein Container erwartet, mocken (sprich eine vorgetäusche Version bereitstellen) müssen, um einen Container in Storybook zu rendern.

Als wir die TaskList in Storybook eingefügt haben, konnten wir dieses Problem einfach umgehen, indem wir die PureTaskList gerendert und so die Einbindung des Containers vermieden haben. Jetzt machen wir es genauso und rendern auch den PureInboxScreen in Storybook.

Trotzdem haben wir dabei ein Problem, denn auch wenn der PureInboxScreen rein darstellend ist, ist es sein Kind, die TaskList, nicht. In gewisser Weise wurde der PureInboxScreen also mit "Container-haftigkeit" verschmutzt. Wenn wir also unsere Stories in InboxScreen.stories.js aufsetzen, ...

Copy
src/components/InboxScreen.stories.js
import React from 'react';

import { PureInboxScreen } from './InboxScreen';

export default {
  component: PureInboxScreen,
  title: 'InboxScreen',
};

export const Default = () => <PureInboxScreen />;

export const Error = () => <PureInboxScreen error="Something" />;

... merken wir, dass auch wenn unsere error-Story richtig funktioniert, wir ein Problem mit der default-Story haben. Das liegt daran, dass die TaskList keinen Redux Store hat, zu dem sie sich verbinden kann. (Ähnliche Probleme würden auch auftreten, wenn du versuchen würdest, den PureInboxScreen mit einem Unit-Test zu testen.)

Fehlerhafte Inbox

Ein Weg, dieses Problem zu umgehen, ist niemals eine Container-Komponente irgendwo in deiner App zu rendern, außer auf oberster Ebene, und stattdessen alle benötigten Daten durch die Komponenten-Hierarchie nach unten durchzureichen.

Entwickler müssen Container aber auch zwangsläufig weiter unten in der Komponenten-Hierarchie rendern. Wenn wir also den Großteil oder alles von unserer App in Storybook rendern wollen (und das wollen wir!), brauchen wir hierfür eine Lösung.

Im Übrigen ist es durchaus ein legitimer Ansatz, Daten die Hierarchie hinunter zu reichen, insbesondere beim Einsatz von GraphQL. So haben wir auch Chromatic entwickelt, neben 800+ weiteren Stories.

Kontext über Decorators zur Verfügung stellen

Die gute Nachricht ist, dass es einfach ist, dem InboxScreen einen Redux Store in einer Story zur Verfügung zu stellen. Wir können einfach eine gemockte Version des Redux Stores nutzen, die über einen Decorator zur Verfügung gestellt wird:

Copy
src/components/InboxScreen.stories.js
import React from 'react';
import { action } from '@storybook/addon-actions';
import { Provider } from 'react-redux';

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

export default {
  component: PureInboxScreen,
  title: 'InboxScreen',
  decorators: [(story) => <Provider store={store}>{story()}</Provider>],
};

// A super-simple mock of a redux store
const store = {
  getState: () => {
    return {
      tasks: defaultTasksData,
    };
  },
  subscribe: () => 0,
  dispatch: action('dispatch'),
};

export const Default = () => <PureInboxScreen />;

export const Error = () => <PureInboxScreen error="Something" />;

Auch für andere Bibliotheken gibt es ähnliche Ansätze, um einen gemockten Kontext zur Verfügung zu stellen, z.B. für Apollo, Relay und weitere.

Indem wir uns durch die Zustände in Storybook klicken, können wir leicht prüfen, ob wir alles richtig gemacht haben:

Komponent-getriebene Entwicklung

Wir haben ganz unten mit Task angefangen, schritten dann vorwärts zur TaskList und sind nun hier angelangt, bei einer vollwertigen Screen-UI. Unser InboxScreen beherbergt eine verschachtelte Container-Komponente und kommt inklusive der zugehörigen Stories.

Komponent-getriebene Entwicklung (CDD) erlaubt uns, schrittweise die Komplexität zu erhöhen, während wir uns in der Komponenten-Hierarchie nach oben bewegen. Ein besserer Fokus im Entwicklungs-Prozess und eine erhöhte Abdeckung aller möglichen Permutationen in der UI zählen zu den Vorzügen. Kurz, CDD hilft dir dabei, komplexere Benutzeroberflächen mit höherer Qualität zu entwickeln.

Wir sind noch nicht fertig - der Job ist nicht erledigt, wenn die UI gebaut ist. Wir müssen auch sicherstellen, dass sie über die Zeit stabil bleibt.

Keep your code in sync with this chapter. View 2275632 on GitHub.
Is this free guide helping you? Tweet to give kudos and help other devs find it.
Next Chapter
Deployment
Deploye Storybook online mit GitHub und Netlify
✍️ Edit on GitHub – PRs welcome!
Join the community
6,536 developers and counting
WhyWhy StorybookComponent-driven UI
Open source software
Storybook

Maintained by
Chromatic
Special thanks to Netlify and CircleCI