Storybook i React: Bygg bedre UI-komponenter raskere!

Utforsk Storybook: Et Verktøy for Utvikling av UI-Komponenter

Har du noen gang tenkt på å samle alle dine UI-komponenter på ett sted i React?

Hvis du er fersk i React-verdenen, er det kanskje ikke noe du umiddelbart tenker på.

Hva betyr egentlig det?

Se på react-beautiful-dnd eksempler for å få en idé.

Det du ser i disse eksemplene, kalles «historier». Verktøyet som brukes til å lage disse historiene, heter Storybook.

Nå har du forhåpentligvis en forståelse av hva vi skal diskutere i denne artikkelen. La oss se nærmere på det.

Hva er Storybook?

Storybook er et isolert utviklingsmiljø for brukergrensesnitt som fungerer som en lekeplass for komponentene dine. Her kan du eksperimentere med komponentene på ulike måter uten å måtte kjøre hele hovedapplikasjonen. Storybook kjøres på en egen port.

Verktøyet er ikke begrenset til React. Det kan brukes med de fleste front-end rammeverk som Vue, Angular, Mithril, Marko, Svelte og flere.

Du finner mer informasjon om Storybook her.

Hva er en historie?

En historie definerer den visuelle representasjonen av en komponent i en gitt tilstand. En enkelt komponent kan brukes på flere måter med ulike props. For hver av disse tilstandene kan vi skrive en egen historie.

La oss ta for oss en enkel knapp-komponent.

En knapp kan ha mange ulike tilstander som deaktivert, lastende, primær, sekundær, liten, stor, medium, osv. Hvis vi skulle liste opp alle mulige tilstander, ville det bli en omfattende liste. Poenget er at du skal forstå at en komponent kan ha mange varianter. Dette vil bli klarere etterhvert som du jobber mer med Storybook.

Du kan se historier for en knapp i ulike størrelser (Stor, Medium, Liten).

Slik Setter du Opp Storybook i et Prosjekt

La oss sette opp Storybook i et eksisterende React-prosjekt.

La oss begynne.

  • Lag et nytt React-prosjekt med følgende kommando. Du kan gi prosjektet et navn du ønsker.
npx create-react-app storybook-demo
  • Installer Storybook i prosjektet med følgende kommando.
npx sb init

Nå har du fullført oppsettet av Storybook.

Storybook genererer en egen server.

Hvordan starter vi den?

Storybook legger automatisk til en kommando i `package.json`-filen. Du kan sjekke denne under `scripts`-delen. Du starter Storybook-serveren med følgende kommando:

npm run storybook

Dette vil starte en ny server på den porten som er definert i `package.json`. Storybook vil automatisk åpnes i din standard nettleser, akkurat som React-serveren.

Du vil se noen standardhistorier. Du kan fjerne disse eller beholde dem som referanse. Som vi diskuterte tidligere, kan en knapp ha flere tilstander, og du vil se dette representert i Storybook (dog ikke alle mulige tilstander). Vi skal skrive et utvalg historier for en knapp i den siste delen av denne veiledningen.

Utforsk de ulike delene av Storybook og gjør deg kjent med funksjonene. Vi vil dekke noen av disse underveis i veiledningen.

La oss skrive vår første historie.

Test av Storybook

Vi har nå sett Storybook i gang med noen eksempler.

  • Opprett en mappe ved navn `Button` inne i `src`-mappen.
  • Opprett filene `Button.jsx`, `Button.css` og `constants.js`.
  • Plasser den respektive koden fra eksemplene nedenfor i disse filene.

Button.jsx

import React, { Component } from "react";
import PropTypes from "prop-types";

import "./Button.css";

import { buttonTypes, buttonVariants, buttonSizes } from "./constants";

class Button extends Component {
    static defaultProps = {
        isDisabled: false,
        type: "filled",
        variant: "oval",
        size: "medium",
        backgroundColor: "#1ea7fd",
        textColor: "#ffffff",
    };

    static buttonTypes = buttonTypes;
    static buttonVariants = buttonVariants;
    static buttonSizes = buttonSizes;

    renderButton = () => {
        const {
            text,
            isDisabled,
            type,
            variant,
            size,
            backgroundColor,
            textColor,
            onClick,
        } = this.props;
        return (
            <button
                onClick={onClick}
                className={`default ${variant} ${size} ${
                    isDisabled ? "disabled" : ""
                }`}
                style={
                    type === buttonTypes.outline
                        ? {
                              border: `1px solid ${backgroundColor}`,
                              color: "#000000",
                              backgroundColor: "transparent",
                          }
                        : {
                              backgroundColor: `${backgroundColor}`,
                              border: `1px solid ${backgroundColor}`,
                              color: textColor,
                          }
                }
                disabled={isDisabled}
            >
                {text}
            </button>
        );
    };

    render() {
        return this.renderButton();
    }
}

Button.propTypes = {
    text: PropTypes.string,
    isDisabled: PropTypes.bool,
    type: PropTypes.oneOf([buttonTypes.outline, buttonTypes.filled]),
    variant: PropTypes.oneOf([buttonVariants.oval, buttonVariants.rectangular]),
    size: PropTypes.oneOf([
        buttonSizes.small,
        buttonSizes.medium,
        buttonSizes.large,
    ]),
    backgroundColor: PropTypes.string,
    textColor: PropTypes.string,
    onClick: PropTypes.func,
};

export { Button };

Button.css

.default {
    border: none;
    cursor: pointer;
    background-color: transparent;
}

.default:focus {
    outline: none;
}

.disabled {
    opacity: 0.75;
    cursor: not-allowed;
}
.small {
    font-size: 12px;
    padding: 4px 8px;
}

.medium {
    font-size: 14px;
    padding: 8px 12px;
}

.large {
    font-size: 16px;
    padding: 12px 16px;
}

.oval {
    border-radius: 4px;
}

.rectangular {
    border-radius: 0;
}

constants.js

export const buttonTypes = {
    outline: "outline",
    filled: "filled",
};

export const buttonVariants = {
    oval: "oval",
    rectangular: "rectangular",
};

export const buttonSizes = {
    small: "small",
    medium: "medium",
    large: "large",
};

Hva gjør denne koden?

Vi har skrevet en vanlig `Button`-komponent som kan brukes på flere måter. Komponent kan ha forskjellige tilstander.

La oss skrive vår første historie ved å følge trinnene nedenfor.

  • Opprett en fil med navnet `Button.stories.jsx`.
  • Importer React og `Button`-komponenten til denne filen.
  • Definer en tittel eller sti for komponentens historier. Vi gjør dette med følgende kode.
export default {
   title: 'common/Button',
}

Koden over vil plassere alle historier i den nåværende filen i `common/Button`-katalogen.

  • Eksporter en knapp med obligatoriske props, som følger.
export const defaultButton = () => (
    <Button text="Default Button" onClick={() => {}} />
);

Vi har skrevet vår første historie. Start Storybook med følgende kommando og se resultatet.

npm run storybook

Vi skal skrive flere historier etterhvert, ikke bekymre deg.

Hvordan er dette nyttig i Front-end utvikling?

Hva er den største fordelen med å bruke Storybook?

La oss si at du jobber i et team på 10 personer. Dere må sjekke ut de vanlige komponentene som alle har skrevet for prosjektet.

Hvordan løser man det?

Man må i utgangspunktet gå gjennom hver enkelt komponent. Dette er tidkrevende og ikke en optimal løsning. Her kommer Storybook inn i bildet.

Hvordan kan Storybook løse dette problemet?

Vi kan skrive historier for de vanlige komponentene (alle UI-komponenter) ved hjelp av Storybook. Når en lagkamerat vil sjekke ut de ulike komponentene, starter de bare Storybook-serveren og ser alle UI-komponentene der, slik vi har sett over.

Vi kan gjøre mye mer med komponentene i Storybook. Storybook har et konsept som heter «Addons» som gir ekstra funksjonalitet til historiene.

La oss si at vi må sjekke responsiviteten til UI-komponentene direkte i Storybook. Da kan vi bruke et tillegg som heter «Viewport». Vi skal lære mer om tillegg senere.

Arbeid med Storybook

I denne delen skal vi skrive ulike historier som definerer ulike tilstander for den vanlige knappe-komponenten.

Det er ikke vanskelig å skrive historier. En historie definerer tilstanden til en komponent. Hvis du ser på props for en komponent, vil du raskt forstå de ulike brukstilfellene for komponenten.

La oss skrive noen historier ved å bruke ulike props.

export const largeButton = () => (
    <Button text="Large Button" onClick={() => {}} size="large" />
);
export const outlineSmallButton = () => (
    <Button
        text="Outline Small Button"
        onClick={() => {}}
        size="small"
        type="outline"
    />
);
export const rectangularLargeButton = () => (
    <Button
        text="Rectangular Large Button"
        onClick={() => {}}
        size="large"
        variant="rectangular"
    />
);


export const disabledButton = () => (
    <Button text="Disabled Button" onClick={() => {}} isDisabled={true} />
);


export const warningButton = () => (
    <Button
        text="Warning Button"
        onClick={() => {}}
        backgroundColor="orange"
    />
);

Disse historiene definerer ulike brukstilfeller for knappe-komponenten. Nå er det din tur til å legge til noen flere tilfeller. Prøv deg frem med å legge til `disabledSamllRectangularButton`, `dangerButton`, `successDisabledButton`, osv.

Jeg vil ikke oppgi koden for disse tilfellene. Du må skrive dette selv for å forstå det. Her er all koden vi har skrevet for historier til nå.

import React from "react";

import { Button } from "./Button";

export default {
    title: "src/common/Button",
};

export const defaultButton = () => (
    <Button text="Default Button" onClick={() => {}} />
);

export const largeButton = () => (
    <Button text="Large Button" onClick={() => {}} size="large" />
);

export const outlineSmallButton = () => (
    <Button
        text="Outline Small Button"
        onClick={() => {}}
        size="small"
        type="outline"
    />
);

export const rectangularLargeButton = () => (
    <Button
        text="Rectangular Large Button"
        onClick={() => {}}
        size="large"
        variant="rectangular"
    />
);

export const disabledButton = () => (
    <Button text="Disabled Button" onClick={() => {}} isDisabled={true} />
);

export const warningButton = () => (
    <Button
        text="Disabled Button"
        onClick={() => {}}
        backgroundColor="orange"
    />
);

Nå har du fått en god forståelse av hvordan du skriver historier for en komponent.

La oss hoppe videre til neste del hvor vi lærer om tillegg og hvordan de forbedrer historiene våre.

Storybook Tillegg

Vi har flere tillegg tilgjengelige som standard. Her skal vi utforske de mest nyttige tilleggene for utviklingen vår.

La oss forbedre `Button`-historiene våre.

Kontroller

Kontroller gir en funksjon for å legge til egendefinerte props til komponenten direkte i Storybook. For vår `Button`-komponent kan vi legge til kontroller for å endre de ulike props i Storybook.

La oss si at vi må finne den beste fargen for bakgrunnsfargen til knappen. Det ville være tidkrevende å teste dette ved å endre fargen i koden for hver test. I stedet kan vi legge til en kontroll som lar oss velge ulike farger i Storybook. Vi kan da teste bakgrunnsfargen direkte.

La oss se hvordan du legger til kontroller til `Button`-historiene våre.

Først må vi definere alle props under tittelen slik.

export default {
    title: "src/common/Button",
    argTypes: {
        text: { control: "text" },
        backgroundColor: { control: "color" },
        isDisabled: { control: "boolean" },
        size: {
            control: { type: "select", options: ["small", "medium", "large"] },
        },
        type: {
            control: { type: "select", options: ["filled", "outline"] },
        },
        variant: {
            control: { type: "select", options: ["oval", "rectangular"] },
        },
    },
};

Deretter skiller vi ut props fra komponenten og gir dem som argumenter slik.

export const outlineSmallButton = (args) => (
    <Button {...args} onClick={() => {}} />
);
outlineSmallButton.args = {
    text: "Outline Small Button",
    size: "small",
    type: "outline",
};

Du kan se kontrollene nederst i visningsvinduet for komponenten.

Du kan se «Controls»-fanen nederst i visningsvinduet. Prøv deg frem.

Oppdater alle historiene som vist over. Dette er mer for å forstå syntaksen for Storybook-tillegg. I `argTypes` har vi brukt ulike typer kontroller. Du finner en oversikt over alle kontrollene som er tilgjengelige i Storybook her.

Oppdaterte knappe-historier vil se slik ut.

import React from "react";

import { Button } from "./Button";

export default {
    title: "src/common/Button",
    argTypes: {
        text: { control: "text" },
        backgroundColor: { control: "color" },
        isDisabled: { control: "boolean" },
        size: {
            control: { type: "select", options: ["small", "medium", "large"] },
        },
        type: {
            control: { type: "select", options: ["filled", "outline"] },
        },
        variant: {
            control: { type: "select", options: ["oval", "rectangular"] },
        },
    },
};

export const defaultButton = (args) => <Button {...args} onClick={() => {}} />;
defaultButton.args = {
    text: "Default Button",
};

export const largeButton = (args) => (
    <Button {...args} onClick={() => {}} size="large" />
);
largeButton.args = {
    text: "Large Button",
};

export const outlineSmallButton = (args) => (
    <Button {...args} onClick={() => {}} />
);
outlineSmallButton.args = {
    text: "Outline Small Button",
    size: "small",
    type: "outline",
};

export const rectangularLargeButton = (args) => (
    <Button {...args} onClick={() => {}} />
);
rectangularLargeButton.args = {
    text: "Rectangular Large Button",
    size: "large",
    variant: "rectangular",
};

export const disabledButton = (args) => <Button {...args} onClick={() => {}} />;
disabledButton.args = {
    text: "Disabled Button",
    isDisabled: true,
};

export const warningButton = (args) => <Button {...args} onClick={() => {}} />;
warningButton.args = {
    text: "Warning Button",
    backgroundColor: "orange",
};

Handlinger

Handlinger er hendelser i JavaScript. Et klikk på en knapp er en hendelse i JavaScript. Vi kan utføre handlinger ved å klikke på knappen ved hjelp av «Actions»-tillegget.

Med «Actions» kan vi teste om hendelsene fungerer som de skal. En deaktivert knapp skal ikke kunne klikkes, mens en aktivert knapp skal kunne klikkes. Vi kan sjekke dette med «Actions».

La oss se hvordan vi legger til handlinger på et knappeklikk.

Tidligere har vi brukt en anonym funksjon for `onClick` prop. Nå må vi oppdatere den.

  • Importer `action` fra Storybook-tillegget med følgende setning.
import { action } from "@storybook/addon-actions";
  • Erstatt alle `() => {}` med følgende setning.
action("Button is clicked!")

Gå til Storybook og klikk på en knapp. Du vil se meldingen under «Actions»-fanen ved siden av «Controls»-fanen. Meldingen vil ikke vises om du klikker på en deaktivert knapp.

Vi kan bruke «Actions» for ulike hendelser som `onChange`, `onMouseOver`, `onMouseOut`, osv., for å forsikre oss om at de fungerer som de skal. Prøv å implementere det samme for `onChange` for et input-element.

Se dokumentasjonen for «Actions» her.

Bakgrunn

Vi kan endre bakgrunnen i visningsvinduet med «Backgrounds»-tillegget. Vi trenger ikke å skrive noe kode for dette. Du endrer det direkte i Storybook. Se GIF’en under.

Viewport

Vi kan også teste responsiviteten til komponentene våre i Storybook. Se GIF’en under for å lære mer om visningsport-alternativene.

Dokumentasjon

Vi kan dokumentere komponentene våre i Storybook med «Docs»-tillegget. Dette er nyttig når man jobber i team. Da kan andre se komponenten og forstå den direkte. Dette sparer utviklerne for mye tid.

I Storybooks visningsvindu for komponenter kan du se «Docs» øverst til høyre ved siden av «Canvas»-fanen. Denne inneholder all dokumentasjon for historiene for en komponent. Vi må bruke `Button.stories.mdx` hvis vi vil dokumentere for en komponent som inkluderer både markdown og komponentgjengivelse. Vi legger da til litt ekstra markdown-kode sammen med komponent-historiene.

La oss dokumentere historiene våre. Koden inkluderer markdown og komponentgjengivelse. Dette handler bare om å lære syntaksen. Du får se det første resultatet her.

Her er dokumentasjonskoden for `Button.stories.mdx`.

<!--- Button.stories.mdx -->

import {
    Meta,
    Story,
    Preview,
    ArgsTable
} from '@storybook/addon-docs/blocks';

import { Button } from './Button';

<Meta title="MDX/Button" component={Button} />

# Button Documentation

With `MDX` we can define a story for `Button` right in the middle of our
Markdown documentation.

<ArgsTable of={Button} />

export const Template = (args) => <Button {...args} />

## Default Button
We can write the documentation related to the Default Button
<Preview>
    <Story name="Default Button" args={{
        text: 'Default Button'
    }}>
    {Template.bind({})}
   </Story>
</Preview>

## Large Button
We are writing sample docs for two stories, you can write rest of them
<Preview>
    <Story name="Large Button" args={{
        text: "Large Button",
        }}>
        {Template.bind({})}
    </Story>
</Preview>

Les mer om dokumentasjonskomponenter her.

Du kan lære mer om tillegg her.

Konklusjon

Håper du likte denne veiledningen og lærte noe om Storybook. Og at du kan bruke det effektivt i ditt team for å gjøre arbeidet mer produktivt.

Er du fersk i React? Sjekk ut disse læringsressursene.

Lykke til med kodingen 🙂