Noțiuni introductive cu Storybook în React

V-ați întrebat vreodată cum ar fi să adunați toate elementele UI într-un singur loc în React?

Dacă sunteți nou în universul React, probabil că nu ați explorat încă această posibilitate.

Ce implică exact acest lucru?

Vedeți exemplele oferite de react-beautiful-dnd.

Ceea ce ați observat în aceste exemple se numește „povestiri” sau „stories”. Instrumentul care permite crearea acestora este cunoscut sub numele de Storybook.

Acum, presupun că ați înțeles subiectul central al acestui articol. Haideți să începem explorarea!

Ce este Storybook?

Storybook este un mediu de dezvoltare izolat, dedicat interfețelor utilizator, oferind un spațiu experimental pentru componentele dumneavoastră. Aici, puteți interacționa cu componentele în diverse moduri, fără a fi nevoie să porniți aplicația principală. Storybook poate rula pe un port specific, în funcție de configurare.

Acest instrument nu este limitat la React; poate fi folosit cu majoritatea framework-urilor frontend, precum Vue, Angular, Mithril, Marko, Svelte și multe altele.

Pentru informații suplimentare despre Storybook, vizitați aici.

Ce reprezintă o „poveste”?

O poveste descrie starea de redare a unei componente. De exemplu, o componentă simplă poate fi utilizată în diverse moduri prin intermediul proprietăților. Pentru fiecare dintre aceste stări, putem defini o poveste separată.

Să luăm ca exemplu o componentă Button.

Un buton poate exista în mai multe stări, cum ar fi dezactivat, în curs de încărcare, primar, secundar, mic, mare, mediu, etc. Dacă am enumera toate aceste stări, ar fi dificil să progresăm cu tutorialul. Cred că ați înțeles ideea. Veți obține o înțelegere mai bună pe măsură ce veți începe să lucrați cu Storybook.

Puteți observa poveștile unui buton în diferite situații (Mare, Mediu, Mic).

Configurarea Storybook într-un proiect

Vom configura Storybook într-un proiect React.

Să începem.

  • Creați un nou proiect React folosind următoarea comandă. Puteți alege orice nume pentru proiect.
npx create-react-app storybook-demo
  • Apoi, instalați Storybook în proiectul dumneavoastră cu ajutorul comenzii de mai jos.
npx sb init

Configurarea Storybook este acum finalizată.

Storybook ne oferă un server separat.

Cum îl pornim?

Storybook adaugă automat o comandă în fișierul de scripturi. Aceasta poate fi verificată în secțiunea de scripturi a fișierului package.json. Pentru moment, rulați comanda de mai jos pentru a porni serverul Storybook.

npm run storybook

Storybook va porni un nou server pe portul specificat în secțiunea de scripturi a fișierului package.json. Acesta va deschide automat Storybook în browser-ul dumneavoastră implicit (la fel ca serverul React).

Veți vedea o serie de povești disponibile implicit. Puteți alege să le eliminați dacă nu aveți nevoie de ele, sau să le păstrați ca referință. Așa cum am menționat anterior, un buton poate avea mai multe stări, iar unele dintre acestea vor fi vizibile în Storybook (deși nu toate cele menționate). Vom scrie un set amplu de povești pentru un buton în ultima parte a acestui tutorial.

Explorați diferitele secțiuni ale Storybook și familiarizați-vă cu ele. Vom discuta unele dintre acestea în continuare.

Acum, să creăm prima noastră poveste.

Testarea Storybook

Am văzut Storybook în funcțiune și câteva exemple în el.

  • În directorul src, creați un folder cu numele Button.
  • În interiorul folderului Button, creați fișierele Button.jsx, Button.css și constants.js.
  • Introduceți codul corespunzător din fragmentele de mai jos în aceste fișiere.

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

Ce reprezintă acest cod?

Am creat o componentă comună Button, care poate fi utilizată în diferite moduri. Acum avem o componentă cu mai multe stări posibile.

Să scriem prima noastră poveste, urmând acești pași:

  • Creați un fișier numit Button.stories.jsx
  • Importați React și componenta noastră Button în acest fișier.
  • Definiți un titlu sau o cale pentru poveștile componentei. Vom folosi următorul cod:
export default {
   title: ‘common/Button’,
}

Codul de mai sus va plasa toate poveștile din fișierul curent în directorul common/Button/.

  • Exportați un buton cu proprietățile necesare, după cum urmează:
export const defaultButton = () => (
    <Button text=”Default Button” onClick={() => {}} />
);

Am finalizat prima noastră poveste. Rulați Storybook cu următoarea comandă și vizualizați rezultatul.

npm run storybook

Vom crea mai multe povești în continuare, deci nu vă faceți griji.

Care este utilitatea în dezvoltarea Frontend?

Care este principalul beneficiu al utilizării Storybook?

Să presupunem că lucrăm într-o echipă de 10 persoane. Avem nevoie să verificăm componentele comune create de fiecare membru pentru proiectul curent.

Cum putem face asta?

Am fi nevoiți să analizăm fiecare componentă comună în parte. Această metodă este ineficientă și consumatoare de timp. Aici intervine Storybook.

Cum putem utiliza Storybook pentru a rezolva această problemă?

Putem crea povești pentru componentele comune (sau orice componente UI) utilizând Storybook. Astfel, oricând un coleg dorește să vadă componentele celorlalți, poate porni simplu serverul Storybook și va vizualiza toate componentele UI, așa cum am văzut mai devreme.

Putem face mult mai mult cu componentele redate în Storybook. Storybook are un concept numit „Addons”, care adaugă funcționalități suplimentare poveștilor noastre.

De exemplu, dacă trebuie să verificăm capacitatea de răspuns a componentelor UI chiar în Storybook, putem folosi un addon numit Viewport. Vom învăța mai multe despre addon-uri în secțiunile următoare.

Lucrul cu Storybook

În această secțiune, vom crea diferite povești care descriu diverse stări ale componentei noastre comune Button.

Scrierea poveștilor nu este complicată. O poveste definește o stare specifică a unei componente. Dacă analizați proprietățile componentei, veți înțelege cu ușurință diferitele cazuri de utilizare.

Să scriem câteva povești oferind proprietăți opționale.

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

Poveștile de mai sus ilustrează diferite cazuri de utilizare pentru componenta Button. Acum este rândul dumneavoastră să adăugați alte cazuri pentru componenta comună. Încercați să creați povești pentru disabledSamllRectangularButton, dangerButton, successDisabledButton, etc.

Nu voi furniza codul pentru cazurile menționate. Trebuie să le creați singuri pentru a le înțelege. Puteți vizualiza codul complet al poveștilor scrise până acum:

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

Acum, aveți o înțelegere completă a modului de a crea povești pentru o componentă.

În secțiunea următoare, vom discuta despre addon-uri și cum acestea pot îmbunătăți poveștile noastre.

Addon-uri pentru Storybook

Mai multe addon-uri sunt disponibile implicit. În această secțiune, vom explora cele mai utile addon-uri pentru dezvoltarea noastră.

Să îmbunătățim poveștile componentei Button.

Controls

Controls adaugă o funcționalitate care permite modificarea dinamică a proprietăților unei componente direct din Storybook. Pentru componenta Button, putem adăuga controale pentru a schimba diverse proprietăți direct în Storybook.

Să presupunem că vrem să găsim cea mai potrivită culoare pentru fundalul butonului. Ar fi ineficient să testăm individual fiecare culoare prin modificarea directă a componentei. În schimb, putem adăuga un control care permite alegerea culorii direct din Storybook, oferind o modalitate eficientă de a testa culoarea de fundal.

Să vedem cum putem adăuga controale poveștilor noastre Button.

Mai întâi, trebuie să definim toate proprietățile sub titlu, după cum urmează:

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

Apoi, separați proprietățile de componentă și alocați-le ca argumente, după cum urmează:

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

Puteți observa controalele în partea de jos a ferestrei de previzualizare a componentelor.

Puteți vedea tabul de controls în partea de jos a ferestrei de previzualizare a componentelor. Interacționați cu acestea.

Actualizați toate poveștile în mod similar. Aici învățăm sintaxa pentru addon-urile Storybook. În argTypes, am folosit diferite tipuri de controls. Lista completă a controls disponibile în Storybook o puteți găsi aici.

Poveștile actualizate ale butonului vor arăta după cum urmează:

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

Actions

Acțiunile sunt evenimente în JavaScript. Putem face clic pe un buton, care este un eveniment în JavaScript. Putem efectua unele acțiuni la clic pe buton folosind addon-ul Actions.

Cu Actions, putem testa dacă evenimentele funcționează corect. De exemplu, un buton dezactivat nu ar trebui să fie clickabil, iar un buton activat trebuie să fie clickabil. Putem verifica acest lucru folosind Actions.

Să vedem cum să adăugăm o acțiune la un clic pe buton.

Am folosit o funcție anonimă pentru proprietatea onClick. Acum trebuie să actualizăm acest lucru.

  • Importați acțiunea din addon-ul Storybook folosind următoarea declarație:
import { action } from "@storybook/addon-actions";
  • Înlocuiți toate () => {} cu următoarea declarație:
action("Button is clicked!")

Acum, accesați Storybook și faceți clic pe un buton. Veți vedea mesajul tipărit în tab-ul Actions, care se află lângă tab-ul Controls. Mesajul nu va fi afișat dacă faceți clic pe butonul dezactivat, deoarece acesta nu este clickabil.

Putem utiliza acțiunile pentru diferite evenimente precum onChange, onMouseOver, onMouseOut etc., pentru a ne asigura că funcționează corect. Încercați să implementați același lucru pentru evenimentul onChange pe un element de input.

Consultați documentația Actions aici.

Backgrounds

Putem schimba fundalul ferestrei de previzualizare folosind addon-ul Backgrounds. Nu este necesar să scriem cod. Schimbarea se face direct din Storybook. Puteți vedea un GIF mai jos:

Viewport

Putem testa capacitatea de răspuns a componentelor noastre direct în Storybook. Consultați GIF-ul de mai jos pentru mai multe detalii despre opțiunile Viewport.

Docs

Putem documenta componentele direct în Storybook folosind addon-ul Docs. Acesta este foarte util când lucrăm într-o echipă. Colegii pot citi documentația direct pentru a înțelege o componentă. Această abordare economisește timp prețios pentru dezvoltatori.

În fereastra de previzualizare a componentelor din Storybook, puteți vedea tab-ul Docs în partea dreaptă sus, lângă tab-ul Canvas. Aici veți găsi documentația completă pentru toate poveștile unei componente. Trebuie să folosim Button.stories.mdx dacă vrem să documentăm componenta incluzând atât Markdown cât și redarea componentei. Vom introduce codul Markdown, alături de poveștile componentei.

Să creăm o documentație pentru poveștile noastre. Codul include atât Markdown cât și redarea componentei. Sintaxa nu este complicată.

Iată codul documentului 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>

Aflați mai multe despre documentarea componentelor aici.

Pentru mai multe informații despre addon-uri, vizitați aici.

Concluzie

Sper că v-a plăcut acest tutorial și că ați învățat despre Storybook. Utilizați-l eficient în echipa dumneavoastră pentru a crește productivitatea.

Sunteți nou în React? Consultați aceste resurse pentru a învăța mai multe.

Spor la codare! 🙂