JavaScript

Knowledge Base

JavaScript > Tools

Storybook

Storybook  (https://storybook.js.org/) ist eine UI Komponenten Spielplatz für die eigenen - in der Regel - meist kleineren Komponenten (allerdings gibt es hier keine Begrenzung ;-)

Die Idee dahinter ist, das Aussehen und (UI-)Verhalten der UI-Komponenten ohne Starten der App (und evtl. mühsamen Herbeiführen des gewünschten Zustands) prüfen und ausprobieren zu können.

Zieht man das konsequent für die eigenen UI-Komponenten durch, entsteht ein visueller Werkzeugkasten und man bekommt eine gute Übersicht über die eigenen Komponenten.

Dadurch, dass man sich hier auch primär auf das Rendern der UI-Komponenten auf Basis verschiedener (UI-)Zustände konzentriert, lassen sich einzelne UI-Komponenten auch deutlich schneller entwickeln und testen - die Verbindungen zu Redux, API, Datenbank stehen hier nicht im Mittelpunkt.

Storybook für React (https://storybook.js.org/docs/guides/guide-react/) lässt sich vgl. einfach installieren:

npx -p @storybook/cli sb init --type react

Damit passiert eine ganze Menge:

  • Installation von @storybook/react, @storybook/addons, @storybook/addon-links, @storybook/addon-actions
  • Erweiterung von package.json um ""storybook": "start-storybook -p 6006", "build-storybook": "build-storybook"
  • Neuer Folder ".storybook" mit der initialen Konfiguration von Storybook (addons.js / config.js)
  • Neuer Folder "stories" mit Beispielen

---

Diese Standard-Konfiguration ändern wir leicht ab:

1. Da wir die stories in der Regel "in der Nähe" (bei uns gleiches Verzeichnis) (= colocated) und als *.stories.jsx anlegen - muss dies noch in der ./storybook/config.js berücksichtigt werden:

import React from "react";
import { configure, addDecorator } from "@storybook/react";
 
import { withInfo } from "@storybook/addon-info";
import { withKnobs } from "@storybook/addon-knobs";
 
 
// Add wrapper around each story (sets the width of the container to 600px)
addDecorator(storyFn => {
    return (
    <div style={{ width: 600 }}>
        { storyFn() }
    </div>
    );
});
 
// Adds the withKnobs decorator to all stories
addDecorator(withKnobs);
 
//Loads all stories (ending with story.js, story.jsx, stories.js, stories.jsx) from "/src/components":
configure(require.context("../src/components/", true, /\.stor(y|ies)\.jsx?$/), module);

2. Und registrieren das "addon-knobs" in der .storybook/addons.js Datei

import "@storybook/addon-actions/register";
import "@storybook/addon-knobs/register";
// import "@storybook/addon-links/register";

---

Für Storybook gibt es zahlreich Addons, die sich vgl. einfach installieren (und konfigurieren) lassen:

https://storybook.js.org/addons/

---

Eine Story kann seit Storybook >= 5.2 als Component Story Format erstellt werden (https://storybook.js.org/docs/formats/component-story-format/). Dies ist aus meiner Sicht vgl. einfach zu verstehen, als die bisherigen storiesOf Funktionen - Beispiel für ein Button-Stories (1. "Sizes": Buttons in verschiedenen Größen / 2. "Disabled" Button im "Disabled"-Status mit "Knobs", mit Hilfe dessen einzelne Props direkt innerhalb von Storybook verändert werden können):

import React from "react";
import Button from "./Button";
import { action } from "@storybook/addon-actions";
import { boolean, text } from "@storybook/addon-knobs";
import { actionHandler } from "../../../../.storybook/storybook.utils";
 
export default {
    title: "Base|Button",
    component: Button,
};
 
export const Sizes = () => (
    <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
        <Button label={"Small"} onClick={action("Small button clicked")} />
        <Button label={"Medium"} onClick={action("Medium button clicked")} size={"medium"} />
        <Button label={"Large"} onClick={action("Large button clicked")} size={"large"} />
    </div>
);
 
export const Disabled = () => {
    const label = text("label", "Disabled Button");
 
    return (
        <Button
            label={label}
            onClick={actionHandler("Button Click", `${label} clicked`)}
            disabled={boolean("disabled", true)}
        />
    );
};