New
Code coverage with Storybook test runnerAutomate with Chromatic
Star74,802
Back to Intro to Storybook
Chapters
  • Leg los
  • Einfache Komponente
  • Komposition
  • Daten
  • Screens
  • Deployment
  • Tests
  • Addons
  • Fazit
  • Unterstützen

Baue eine einfache Komponente

Baue eine einfache Komponente in Isolation
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.

Beim Bauen unserer UI werden wir nach der Component-Driven Development (CDD) Methodik vorgehen. Das it ein Vorgehen, in dem UIs "bottom up" entwickelt werden. Man beginnt mit Komponenten und endet mit Screens. CDD hilft dabei, die Komplexität zu begrenzen, mit der man beim Bauen einer UI konfrontiert wird.

Task

Task-Komponente in drei Zuständen

Task ist die zentrale Komponente in unserer App. Jede Aufgabe wird ein wenig anders angezeigt, abhängig davon, in welchem Zustand sie sich befindet. Wir zeigen eine ausgewählte (oder nicht ausgewählte) Checkbox an, einige Informationen über die Aufgabe und einen "Pin"-Button, der uns erlaubt, Aufgaben in der Liste hoch oder runter zu bewegen. Daraus ergeben sich folgende Props:

  • title – ein String, der die Aufgabe beschreibt
  • state - In welcher Liste befindet sich die Aufgabe aktuell und ist sie abgeschlossen?

Beim Entwickeln der Task-Komponente schreiben wir zunächst unsere Test-Zustände, die den oben skizzierten möglichen Aufgaben-Typen entsprechen. Anschließend verwenden wir Storybook, um die Komponente mit gemockten Daten isoliert zu entwickeln. Wärend wir entwickeln, prüfen wir die Komponente in jedem möglichen Zustand auf ihre visuelle Erscheinung.

Dieses Vorgehen ähnelt der testgetriebenen Entwicklung (TDD). Wir nennen es “Visual TDD”.

Los geht's

Lass uns zunächst eine Komponente für die Aufgaben anlegen sowie die zugehörige Story-Datei: src/components/Task.js und src/components/Task.stories.js.

Zunächst starten wir mit dem Grundgerüst von Task, in dem wir einfach die benötigten Attribute und die zwei Aktionen mitnehmen, die auf einer Aufgabe ausgeführt werden können (um sie zwischen Listen hin und her zu bewegen):

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

export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) {
  return (
    <div className="list-item">
      <input type="text" value={title} readOnly={true} />
    </div>
  );
}

Oben rendern wir ein einfaches Markup für Task, basierend auf der bestehenden HTML-Struktur in der Todos-App.

Unten bilden wir die drei Test-Zustände, die Task einnehmen kann, in einer Story-Datei ab:

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

import Task from './Task';
export default {
  component: Task,
  title: 'Task',
  // Our exports that end in "Data" are not stories.
  excludeStories: /.*Data$/,
};

export const taskData = {
  id: '1',
  title: 'Test Task',
  state: 'TASK_INBOX',
  updatedAt: new Date(2018, 0, 1, 9, 0),
};

export const actionsData = {
  onPinTask: action('onPinTask'),
  onArchiveTask: action('onArchiveTask'),
};

export const Default = () => {
  return <Task task={{ ...taskData }} {...actionsData} />;
};

export const Pinned = () => <Task task={{ ...taskData, state: 'TASK_PINNED' }} {...actionsData} />;

export const Archived = () => (
  <Task task={{ ...taskData, state: 'TASK_ARCHIVED' }} {...actionsData} />
);

Es gibt zwei grundlegende Ebenen, in denen Komponenten in Storybook origanisiert werden können: Die Komponente selbst und ihr untergeordnete Stories. Stell dir eine Story als Ausprägung einer Komponente vor. Du kannst so viele Stories für eine Komponente anlegen, wie du brauchst.

  • Komponente
    • Story
    • Story
    • Story

Um Storybook die Komponente, die wir dokumentieren, zugänglich zu machen, erstellen wir einen default Export. Dieser beinhaltet:

  • component -- die Komponente selbst,
  • title -- wie die Komponente in der Sidebar der Storybook App referenziert werden soll,
  • excludeStories -- Exporte in der Story Datei, die von Storybook nicht als Stories gerendert werden sollen.

Unsere Stories definieren wir, indem wir für jeden unserer Test-Zustände eine Funktion exportieren, um eine Story zu generieren. Die Story ist eine Funktion, die ein gerendertes Element in einem definierten Zustand zurückgibt (z.B. eine Komponenten-Klasse mit einer Menge an Props) --- genau wie eine Functional Component in React.

action() erlaubt uns, ein Callback zu erstellen, das im Actions-Panel der Storybook-UI erscheint, wenn man auf dieses klickt. Wenn wir also einen Pin-Button bauen, können wir so in der Test-UI sehen, ob ein Button-Klick erfolgreich war.

Da wir allen Ausprägungen unserer Komponente immer das selbe Menge an Actions übergeben müssen, ist es naheliegend, sie in einer einzigen actionsData-Variable zusammenzufassen und die {...actionsData} Syntax von JSX ("spread attributes") zu verwenden, um alle Props auf einmal zu übergeben. <Task {...actionsData}> ist äquivalent zu <Task onPinTask={actionsData.onPinTask} onArchiveTask={actionsData.onArchiveTask}>.

Ein weiterer Vorteil, die Actions in actionsData zusammenzufassen ist, wie wir später sehen werden, dass man diese Variable dann export-ieren und die Actions in Stories anderer Komponenten zur Verfügung stellen kann, die diese Komponente wiederverwenden.

Beim Erstellen einer Story nutzen wir eine Basis-Aufgabe (taskData), um die Struktur der Aufgabe zu definieren, die unsere Komponente erwartet. Diese basiert üblicherweise auf realitätsnahen Beispiel-Daten. Noch einmal: Diese Struktur zu export-ieren erlaubt uns, sie später in weiteren Stories zu verwenden, wie wir noch sehen werden.

Actions helfen dir, Interaktionen zu verifizieren, wenn du UI-Komponenten isoliert entwickelst. Oftmals hast du bei der Entwicklung keinen Zugriff auf die Funktionen und den Zustand, die im Kontext deiner App existieren. Nutze action(), um sie als Stub zur Verfügung zu haben.

Konfiguration

Wir müssen auch noch eine kleine Anpassung an der Storybook-Konfiguration (.storybook/config.js) vornehmen, so dass unsere .stories.js-Dateien und unsere CSS-Datei berücksichtigt werden. Standardmäßig sucht Storybook im Verzeichnis /stories nach Stories; dieses Tutorial verwendet ein Namens-Schema äquivalent zum .test.js-Namens-Schema, das von CRA für automatisierte Tests bevorzugt wird.

Copy
.storybook/config.js
import { configure } from '@storybook/react';
import '../src/index.css';

configure(require.context('../src/components', true, /\.stories\.js$/), module);

Sobald wir das erledigt und den Storybook Server neu gestartet haben, sollten die Testfälle für die drei Zustände von Task angezeigt werden:

Die Zustände implementieren

Da wir Storybook jetzt eingerichtet, die Styles importiert und die Testfälle angelegt haben, können wir einfach damit loslegen, das HTML der Komponente zu implementieren, so dass es dem Design entspricht.

Die Komponente ist noch immer sehr einfach gehalten. Schreib zunächst den Code, um das Design zu erhalten, ohne dass wir zu sehr ins Detail gehen:

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

export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) {
  return (
    <div className={`list-item ${state}`}>
      <label className="checkbox">
        <input
          type="checkbox"
          defaultChecked={state === 'TASK_ARCHIVED'}
          disabled={true}
          name="checked"
        />
        <span className="checkbox-custom" onClick={() => onArchiveTask(id)} />
      </label>
      <div className="title">
        <input type="text" value={title} readOnly={true} placeholder="Input title" />
      </div>

      <div className="actions" onClick={(event) => event.stopPropagation()}>
        {state !== 'TASK_ARCHIVED' && (
          <a onClick={() => onPinTask(id)}>
            <span className={`icon-star`} />
          </a>
        )}
      </div>
    </div>
  );
}

Das obige zusätzliche Markup, zusammen mit dem CSS, das wir zuvor imporiert haben, resultiert in folgender Darstellung:

Datenstruktur spezifizieren

Es ist üblich, propTypes in React zu verwenden, um die Struktur der Daten zu spezifizieren, die eine Komponente erwartet. Das dient nicht nur als Dokumentation, sondern hilft auch dabei, Probleme früh abzufangen.

Copy
src/components/Task.js
import React from 'react';
import PropTypes from 'prop-types';

export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) {
  // ...
}

Task.propTypes = {
  task: PropTypes.shape({
    id: PropTypes.string.isRequired,
    title: PropTypes.string.isRequired,
    state: PropTypes.string.isRequired,
  }),
  onArchiveTask: PropTypes.func,
  onPinTask: PropTypes.func,
};

Nun wird beim Entwickeln eine Warnung angezeigt, wenn die Task-Komponente falsch verwendet wird.

Alternativ kann man hierfür auch JavaScript-Typisierung verwenden, wie z.B. TypeScript, um den Props der Komponente Typen zuzuweisen.

Komponente erstellt!

Jetzt haben wir erfolgreich eine Komponente gebaut, ohne dass wir einen Server oder unsere gesamte Frontend-App dazu benötigt hätten. Als nächstes müssen wir die verbleibenden Taskbox-Komponenten auf die gleiche Weise bauen, eine nach der anderen.

Wie du siehst, ist es recht schnell und einfach möglich, eine Komponente in Isolation zu bauen. Dadurch können wir UIs bauen, die schicker, qualitativ hochwertiger und weniger fehleranfällig sind, weil es möglich ist, in die Tiefe zu gehen und jeden möglichen Zustand abzutesten.

Automatisiertes Testen

Storybook hat uns eine tolle Möglichkeit gegeben, unsere Anwendung visuell zu testen während wir sie entwickeln. Die 'Stories' werden uns dabei helfen, sicherzustellen, dass die Darstellung unserer Task-Komponente nicht zerschossen wird, während wir unsere App weiter entwickeln. Allerdings ist das im Moment noch ein vollständig manueller Vorgang und irgendjemand muss sich die Mühe machen, alle Testzustände durchzuklicken, um sicherzustellen, dass alles korrekt gerendert wird und keine Fehler oder Warnungen auftreten. Können wir das nicht automatisieren?

Snapshot-Tests

Snapshot-Tests beziehen sich darauf, den "wohlbekannten" Output einer Komponente für eine definierten Input festzuhalten und dann die Komponente hervorzuheben, wann immer sich der Output in Zukunft verändert. Das ergänzt Storybook, denn es ist eine schnelle Möglichkeit, die neue Version einer Komponente zu begutachten und ihre Änderungen zu überprüfen.

Stelle sicher, dass deine Kompoenten ein Output rendern, das sich nicht verändert, damit deine Snapshot-Tests nicht jedes mal fehlschlagen. Achte auf Dinge wie Datumsausgaben oder zufällig generierte Werte.

Mit dem Storyshots-Addon wird ein Snapshot-Test für jede deiner Stories generiert. Um es zu verwenden, füge eine devDependency in deiner package.json hinzu:

Copy
yarn add --dev @storybook/addon-storyshots react-test-renderer require-context.macro

Erstelle dann die Datei src/storybook.test.js mit folgendem Inhalt:

Copy
src/storybook.test.js
import initStoryshots from '@storybook/addon-storyshots';
initStoryshots();

Du wirst auch ein babel-Macro verwenden müssen, um sicherzustellen, dass require.context (etwas Webpack-Magie) in Jest (unser Test-Kontext) ausgeführt wird. Installiere es mit:

Copy
yarn add --dev babel-plugin-macros

Und aktiviere es, indem du eine .babelrc Datei im Root-Ordner deiner App (dieselbe Ebene wie die package.json) erstellst:

Copy
// .babelrc

{
  "plugins": ["macros"]
}

Danach aktualisiere .storybook/config.js wie folgt:

Copy
.storybook/config.js
import { configure } from '@storybook/react';
import requireContext from 'require-context.macro';

import '../src/index.css';

configure(requireContext('../src/components', true, /\.stories\.js$/), module);

(Beachte, dass wir require.context ersetzt haben mit einem Aufruf von requireContext, das vom Makro importiert wird.)

Wenn das erledigt ist, können wir yarn test ausführen und sehen die folgende Ausgabe:

Task-Test-Runner

Wir haben jetzt einen Snapshot-Test für jede unserer Task-Stories. Ändern wir die Implementierung von Task, werden wir aufgefordert, die Änderungen zu verifizieren.

Keep your code in sync with this chapter. View efa06ff on GitHub.
Is this free guide helping you? Tweet to give kudos and help other devs find it.
Next Chapter
Komposition
Setze eine Komposition aus einfachen Komponenten zusammen
✍️ Edit on GitHub – PRs welcome!
Join the community
5,870 developers and counting
WhyWhy StorybookComponent-driven UI
Open source software
Storybook

Maintained by
Chromatic
Special thanks to Netlify and CircleCI