Pierwsze kroki ze Storybookiem w React

Photo of author

By maciekx

Czy kiedykolwiek zastanawiałeś się, jak efektywnie zarządzać wszystkimi elementami interfejsu użytkownika w jednym miejscu podczas pracy z React?

Jeśli dopiero zaczynasz swoją przygodę z React, odpowiedź brzmi prawdopodobnie „nie”.

Co to dokładnie oznacza?

Spójrz na przykłady dostępne w react-beautiful-dnd.

To, co widzisz w tych przykładach, to właśnie „historie” (stories). Narzędziem służącym do tworzenia takich historii jest Storybook.

Teraz już wiesz, o czym będzie ten artykuł. Przejdźmy zatem od razu do sedna.

Czym jest Storybook?

Storybook to odizolowane środowisko deweloperskie interfejsu użytkownika, które służy jako plac zabaw dla Twoich komponentów. Dzięki niemu możesz testować i modyfikować komponenty w różny sposób bez konieczności uruchamiania głównej aplikacji. Storybook działa na swoim własnym serwerze, z dedykowaną konfiguracją portu.

Co istotne, Storybook nie jest ograniczony tylko do React. Można go z powodzeniem używać z większością frameworków frontendowych, takich jak Vue, Angular, Mithril, Marko, Svelte i wiele innych.

Jeśli chcesz dowiedzieć się więcej o Storybook, odwiedź oficjalną stronę projektu.

Czym jest „historia”?

„Historia” to definicja stanu renderowania danego komponentu. Weźmy na przykład prosty komponent, który może być używany w różny sposób, z różnymi atrybutami. Dla każdego takiego stanu możemy stworzyć osobną „historię”.

Załóżmy, że mamy komponent przycisku (Button).

Przycisk może występować w różnych wariantach: wyłączony, w trakcie ładowania, podstawowy, drugorzędny, mały, duży, średni itd. Wypisanie wszystkich tych stanów znacznie wydłużyłoby samouczek. Myślę, że rozumiesz. Zobaczysz to jeszcze lepiej, gdy zaczniesz pracować ze Storybookiem.

Możesz zobaczyć, jak przycisk zachowuje się w różnych przypadkach (duży, średni, mały).

Konfiguracja Storybook w projekcie

Skonfigurujemy Storybook w projekcie React.

Zaczynajmy!

  • Utwórz nowy projekt React za pomocą poniższego polecenia. Możesz nadać mu dowolną nazwę.
npx create-react-app storybook-demo
  • Teraz zainstaluj Storybook w swoim projekcie za pomocą następującego polecenia.
npx sb init

Konfiguracja Storybook jest już zakończona.

Storybook zapewnia nam oddzielny serwer.

Jak go uruchomić?

Storybook automatycznie dodaje odpowiednie polecenie do pliku ze skryptami. Możesz to sprawdzić w sekcji „scripts” pliku `package.json`. Aby uruchomić serwer Storybook, wykonaj teraz następujące polecenie:

npm run storybook

Storybook uruchomi nowy serwer na porcie określonym w pliku `package.json`. Automatycznie otworzy się on w domyślnej przeglądarce (podobnie jak serwer React).

Domyślnie zobaczysz różne predefiniowane historie. Możesz je usunąć, jeśli ich nie potrzebujesz, lub zostawić jako przykład. Tak jak wspominaliśmy wcześniej, przycisk może mieć wiele stanów, które są przedstawione w Storybooku (choć nie wszystkie). W dalszej części samouczka stworzymy kilka dodatkowych historii dla przycisku.

Przejrzyj różne sekcje Storybooka, aby lepiej się z nim zapoznać. W tym samouczku omówimy kilka z nich.

Napiszmy naszą pierwszą historię.

Testowanie Storybooka

Widzieliśmy już działanie Storybooka i kilka przykładów w nim zawartych.

  • Utwórz folder o nazwie „Button” w folderze „src”.
  • Utwórz w tym folderze pliki: „Button.jsx”, „Button.css” i „constants.js”.
  • Wklej do nich odpowiedni kod z poniższych fragmentów.

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",
};

Czym jest ten kod?

Stworzyliśmy prosty komponent przycisku, który może być używany na różne sposoby. Teraz mamy komponent, który może przyjmować różne stany.

Napiszmy naszą pierwszą historię, wykonując poniższe kroki.

  • Utwórz plik o nazwie „Button.stories.jsx”
  • Zaimportuj React i nasz komponent „Button” do tego pliku.
  • Zdefiniujmy teraz tytuł lub ścieżkę do naszych historii. Zrobimy to przy pomocy poniższego kodu.
export default {
   title: ‘common/Button’,
}

Powyższy kod umieści wszystkie historie z tego pliku w katalogu „common/Button/”.

  • Wyeksportujmy nasz przycisk z wymaganymi atrybutami w następujący sposób.
export const defaultButton = () => (
    <Button text=”Default Button” onClick={() => {}} />
);

Właśnie stworzyliśmy naszą pierwszą historię. Uruchom Storybook przy pomocy poniższego polecenia i zobacz wynik.

npm run storybook

Spokojnie, wkrótce napiszemy ich więcej.

Jak Storybook przydaje się w rozwoju frontendu?

Jaka jest największa zaleta korzystania ze Storybooka?

Wyobraźmy sobie, że pracujemy w 10-osobowym zespole. Musimy przetestować współdzielone komponenty, które wszyscy napisali dla bieżącego projektu.

Jak możemy to zrobić?

Musielibyśmy uruchamiać każdy komponent osobno, aby go sprawdzić. To jednak zabiera dużo czasu i nie jest najefektywniejsze. W tym miejscu na scenę wkracza Storybook.

Jak możemy go wykorzystać, aby rozwiązać nasz problem?

Dzięki Storybookowi możemy pisać „historie” dla współdzielonych komponentów (a tak naprawdę dla dowolnych komponentów UI). Kiedy któryś z członków zespołu będzie chciał przejrzeć komponenty napisane przez innych, wystarczy, że uruchomi serwer Storybooka i zobaczy tam wszystkie elementy interfejsu użytkownika, tak jak to widzieliśmy wcześniej.

Możemy zrobić o wiele więcej ze składowymi wyrenderowanymi w Storybooku. Storybook oferuje funkcję „Addons”, która znacznie rozszerza możliwości naszych historii.

Na przykład, jeśli chcemy sprawdzić responsywność naszych komponentów UI w Storybooku, możemy użyć dodatku „Viewport”. O dodatkach dowiemy się więcej w kolejnych sekcjach.

Praca ze Storybookiem

W tej sekcji napiszemy różne historie, które będą definiować różne stany naszego współdzielonego komponentu „Button”.

Pisanie historii nie jest trudne. Historia definiuje stan komponentu. Jeśli dobrze znasz atrybuty komponentu, łatwo zrozumiesz różne przypadki jego użycia.

Napiszmy kilka historii, podając opcjonalne atrybuty.

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"
    />
);

Powyższe historie definiują różne przypadki użycia naszego komponentu „Button”. Teraz twoja kolej, aby dodać kolejne historie do naszego współdzielonego komponentu. Spróbuj dodać historie dla „disabledSmallRectangularButton”, „dangerButton”, „succesDisabledButton” itp.

Nie będę podawał kodu dla tych przypadków. Spróbuj napisać je sam, aby lepiej zrozumieć. Poniżej znajdziesz pełen kod historii, które stworzyliśmy do tej pory.

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"
    />
);

Teraz masz pełną kontrolę nad pisaniem historii dla komponentu.

Przejdźmy do następnej sekcji, w której dowiemy się, jak dodatki wzbogacają nasze historie.

Dodatki do Storybooka

Domyślnie dostępnych jest wiele dodatków. W tej sekcji przyjrzymy się najbardziej przydatnym z nich w kontekście rozwoju aplikacji.

Wzbogaćmy nasze historie dla przycisków.

Kontrolki (Controls)

Dodatek „Controls” pozwala na interaktywne modyfikowanie atrybutów komponentu w Storybooku. Możemy dodawać kontrolki do naszego komponentu „Button”, aby zmieniać jego różne atrybuty na żywo.

Załóżmy, że chcemy znaleźć najlepszy kolor tła dla naszego przycisku. Testowanie różnych kolorów poprzez ręczne wpisywanie ich w kodzie byłoby czasochłonne. Zamiast tego możemy użyć kontrolki, która umożliwi wybór koloru bezpośrednio w Storybooku. W ten sposób możemy przetestować kolory tła znacznie szybciej.

Zobaczmy, jak dodać kontrolki do naszych historii dla przycisków.

Najpierw musimy zdefiniować wszystkie atrybuty pod „title” w następujący sposób.

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"] },
        },
    },
};

Następnie oddzielmy atrybuty od komponentu i przekażmy je jako argumenty w następujący sposób.

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

Możesz zobaczyć elementy sterujące na dole okna podglądu komponentu.

Zobacz zakładkę „Controls” na dole okna podglądu komponentu. Wypróbuj ją!

Zaktualizuj w ten sposób wszystkie historie. Chodzi o to, abyś zapoznał się ze składnią dodatków do Storybooka. W `argTypes` użyliśmy różnych typów kontrolek. Wszystkie dostępne kontrolki znajdziesz tutaj.

Zaktualizowane historie przycisków powinny wyglądać następująco.

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",
};

Akcje (Actions)

Akcje to zdarzenia JavaScript. Kliknięcie przycisku jest zdarzeniem. Dodatek „Actions” pozwala nam śledzić te zdarzenia i wykonywać określone akcje, gdy one wystąpią.

Dzięki Akcjom możemy sprawdzić, czy zdarzenia działają poprawnie. Przycisk wyłączony nie powinien reagować na kliknięcie, a przycisk włączony już tak. Akcje pozwalają nam to sprawdzić.

Zobaczmy, jak dodać akcję do kliknięcia przycisku.

Wcześniej ustawiliśmy anonimową funkcję dla atrybutu onClick. Teraz musimy to zmienić.

  • Zaimportujmy akcję z dodatku Storybook przy pomocy poniższej instrukcji.
import { action } from "@storybook/addon-actions";
  • Teraz zastąpmy wszystkie `() => {}` następującą instrukcją.
action("Button is clicked!")

Teraz przejdź do Storybooka i kliknij przycisk. Komunikat wyświetli się pod zakładką „Actions”, która znajduje się obok zakładki „Controls”. Komunikat nie wyświetli się, jeśli klikniesz wyłączony przycisk, ponieważ jest on nieaktywny.

Możemy użyć Akcji dla różnych zdarzeń, takich jak onChange, onMouseOver, onMouseOut itp., aby upewnić się, że działają prawidłowo. Spróbuj zaimplementować to samo dla zdarzenia onChange w elemencie input.

Więcej informacji o Akcjach znajdziesz w dokumentacji tutaj.

Tła (Backgrounds)

Dodatek „Backgrounds” pozwala nam zmieniać tło okna podglądu komponentów w Storybooku. Nie musimy pisać żadnego kodu, aby to zrobić. Wystarczy zmienić to w ustawieniach Storybooka. Zobacz poniższy GIF.

Rzutnie (Viewports)

W Storybooku możemy również testować responsywność naszych komponentów. Zobacz poniższy GIF, aby dowiedzieć się więcej o opcjach rzutni.

Dokumentacja (Docs)

Dodatek „Docs” pozwala nam udokumentować nasze komponenty w Storybooku. Jest to bardzo przydatne, zwłaszcza gdy pracujemy w zespole. Inni programiści mogą łatwo przeczytać i zrozumieć, jak działa dany komponent. To oszczędza mnóstwo czasu.

W oknie podglądu komponentów Storybooka znajdziesz zakładkę „Docs” w prawym górnym rogu zakładki „Canvas”. Znajdują się tam wszystkie dokumenty dla wszystkich historii danego komponentu. Jeśli chcemy udokumentować komponent w sposób, który obejmuje zarówno opis markdown, jak i renderowanie komponentu, musimy użyć pliku „Button.stories.mdx”. W tym pliku piszemy dodatkowy kod markdown wraz z historiami komponentu.

Piszemy dokumentację dla naszych historii. Kod obejmuje zarówno markdown, jak i renderowanie komponentów. To po prostu nauka składni, szybko ją opanujesz.

Zobaczmy kod dokumentu „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>

Więcej o komponentach dokumentacji dowiesz się tutaj.

Możesz dowiedzieć się więcej o dodatkach tutaj.

Podsumowanie

Mam nadzieję, że ten samouczek był dla Ciebie pomocny i że dużo nauczyłeś się o Storybooku. Używaj go efektywnie w swoim zespole, aby zwiększyć produktywność.

Chcesz dowiedzieć się więcej o React? Sprawdź te materiały edukacyjne.

Miłego kodowania! 🙂


newsblog.pl