O Storybook é uma ferramenta para desenvolvimento de interfaces. Ela torna o desenvolvimento mais fácil e rápido ao isolar componentes, o que lhe permite trabalhar em um componente de cada vez. Você pode desenvolver UIs inteiras sem precisar subir uma stack completa e/ou complexa de desenvolvimento, sem ter de inserir dados na sua base ou ficar navegando pelas telas da sua aplicação até chegar no componente que quer testar.
Especificamente quando falamos de ReactJS, desenvolver interfaces não é um passeio no parque, na minha opinião. Ao mesmo tempo que componentização é uma regra nesta biblioteca, muitas vezes é extremamente custoso testar nossos componentes de maneira adequada.
StoryBook é justamente pra isso.
Para este tutorial eu vou pegar como exemplo um projeto recente envolvendo meu último tutorial de ReactJS. Não é obrigatório que você esteja usando o mesmo projeto, mas resolvi usá-lo pois ele tem dois componentes reais, não são apenas exercícios, e serão uma ótima prática pra gente. Você pode baixar o projeto ainda sem StoryBook deixando seu email no formulário ao final do tutorial passado, ou ele completo com Storybook ao final deste tutorial.
Atenção: este não deve ser o seu primeiro tutorial sobre ReactJS. Leia esta série antes.
#1 – Instalando o StoryBook
Você precisa já ter um projeto React na sua máquina. Pode inclusive criar um do zero com create-react-app se quiser.
Navegando até a raiz do seu projeto, rode o comando abaixo para baixar e instalar o StoryBook.
1 2 3 |
npx sb init |
Vai demorar um bocado, mas depois de terminar, você pode rodar o projeto do StoryBook com o comando abaixo.
1 2 3 |
npm run storybook |
Isso vai executar o StoryBook na porta 6006 com alguns componentes de exemplo que você pode testar antes mesmo.
Navegue pelos componentes de exemplo para entender que, para cada componente, existem “histórias” contando os seus diferentes estados. No caso do Button, por exemplo, temos as histórias Primary, Secondary, Large e Small, mostrando o que acontece com o botão com estes diferentes estados.
#2 – Entendendo as Histórias
Se você olhar no seu projeto, vai ver que foi criada uma pasta stories e dentro tem uma série de arquivos.
Para cada componente de exemplo, foi fornecido o seu JS, o seu CSS e um arquivo com o mesmo nome do componente mas terminado em stories.js que é justamente o que guarda as diferentes histórias/estados para aquele componente em questão.
Tomemos o Button.stories.js como exemplo.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
import React from 'react'; import { Button } from './Button'; export default { title: 'Example/Button', component: Button, argTypes: { backgroundColor: { control: 'color' }, }, }; const Template = (args) => <Button {...args} />; export const Primary = Template.bind({}); Primary.args = { primary: true, label: 'Button', }; export const Secondary = Template.bind({}); Secondary.args = { label: 'Button', }; export const Large = Template.bind({}); Large.args = { size: 'large', label: 'Button', }; export const Small = Template.bind({}); Small.args = { size: 'small', label: 'Button', }; |
Repare que, além de importar o componente real, é criado um template para o mesmo e depois os diferentes estados, definindo os args que serão passados para o componente em cada situação.
Experimente, por exemplo, criar mais uma história no final, para um botão com o texto Hello Storybook, como abaixo.
1 2 3 4 5 6 7 |
export const Hello = Template.bind({}); Hello.args = { size: 'small', label: 'Hello Storybook', }; |
Salve e automaticamente o seu Storybook vai se atualizar e você vai ver um novo story com o Hello.
Experimente modificar esta história, alterando outras características do componente Button e até mesmo escreva mais algumas histórias, até se sentir confiante de avançar.
#3 – Criando Histórias
Agora que você entendeu como funciona, vamos criar uma história 100% original, como um bom escritor de histórias deve fazer. 🙂
Tomemos como exemplo o componente abaixo, retirado de meu último tutorial. Se você não está usando o tutorial em questão, saiba que precisará das dependências react-bootstrap, bootstrap e styled-components para que ele funcione adequadamente.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
import React from 'react'; import styled from 'styled-components'; const ItemContainer = styled.div` border-radius: 4px; background-color: #fff; height: 120px; width: 262px; color: #29303b; margin-bottom: 10px; margin-right: 10px; padding: 10px; `; const Thumbnail = styled.img` width: auto; height: 100%; border: 0; vertical-align: middle; float: left; margin-right: 10px; `; const TitlePane = styled.div` font-weight: 700; margin-bottom: 5px; `; const PricePane = styled.div` margin-bottom: 5px; `; const ItemLink = styled.a` text-decoration: none; `; function ListItem(props) { return ( <ItemLink href={props.url} title="Clique para comprar"> <ItemContainer> <Thumbnail src={props.image} /> <TitlePane>{props.title}</TitlePane> <PricePane>R$ {props.price}</PricePane> </ItemContainer> </ItemLink> ); } export default ListItem; |
Este componente é um item que será usado mais tarde para compor uma lista de itens. Uma lista de meus livros à venda na Amazon, para ser mais exato.
Tradicionalmente, para testarmos este componente, teríamos de adicioná-lo a alguma página existente e subir a aplicação React inteira, além de ter dados mockados vindo de uma API. Ou inserindo esses dados previamente no banco de dados. Enfim, uma trabalheira.
Como podemos ver este componente em ação muito mais fácil e rapidamente? Com Storybook, é claro!
Sendo assim, crie um novo arquivo dentro da pasta stories com o nome de ListItem.stories.js e vamos colocar nele apenas uma história inicial chamada DefaultBook.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import 'bootstrap/dist/css/bootstrap.min.css'; import React from 'react'; import ListItem from '../components/ListItem'; export default { title: 'ListItem', component: ListItem, }; const Template = (args) => <ListItem {...args} />; export const DefaultBook = Template.bind({}); DefaultBook.args = { title: 'Programação Web com Node.js', image: 'https://m.media-amazon.com/images/I/4110e7iseFL.jpg', price: 14.99, url: "https://www.luiztools.com.br/livro-nodejs-amazon" }; |
Repare que adicionei o CSS do Bootstrap no topo, para que o estilo do meu componente fique correto. Ao salvar esta história, automaticamente você verá ela atualizada no Storybook.
Vamos criar mais algumas histórias com meus diferentes livros, precisaremos deles na última parte deste tutorial.
#4 – Sequências de Histórias
Boas histórias merecem sequências, não é mesmo?
Algumas histórias do Storybook dependerão de outras histórias, isso porque alguns componentes dependem de outros componentes para existir. Nos próprios exemplos do Storybook temos um componente Page que depende de um componente Header. Vamos replicar este mesmo comportamento com ListItem e ListContainer.
Olhando meu projeto anterior em React, você deve ver que no App.js o ListContainer foi definido lá dentro mesmo, vamos extrai-lo para a pasta components em um arquivo ListContainer.js justamente para podermos testá-lo em separado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import React from 'react'; import styled from 'styled-components'; import ListItem from '../components/ListItem'; const ListContent = styled.div` display: flex; flex-direction: row; flex-wrap: wrap; background-color: #ccc; padding: 10px; `; function ListContainer(props) { return ( <ListContent> {props.books.map(book => { return ( <ListItem key={book.url} title={book.title} image={book.image} price={book.price} url={book.url} /> ) })} </ListContent> ); } export default ListContainer; |
E para não deixar o projeto anterior quebrado, vamos modificar nosso App.js para a nova realidade do ListContainer componentizado.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import { Container } from 'react-bootstrap'; import { getBooks } from '../services/BooksService'; import { useEffect, useState } from 'react'; import SearchBar from '../components/SearchBar'; import ListContainer from '../components/ListContainer'; function App() { const [books, setBooks] = useState([]); useEffect(() => { setBooks(getBooks()); }, []) return ( <Container> <h1>LuizTools' Books</h1> <hr /> <SearchBar setBooks={setBooks} /> <ListContainer books={books} /> </Container> ); } export default App; |
Agora voltando ao que nos interessa, vamos criar um arquivo ListContainer.stories.js para criar as histórias desse novo componente.
Poderíamos simplesmente criar do mesmo jeito que fizemos antes, montando os objetos na mão, mas como o ListContainer é uma lista de ListItems, isso geraria muito código repetido, pois os objetos de livros seriam os mesmos. Ao invés disso, vamos reaproveitar as histórias que criamos antes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import 'bootstrap/dist/css/bootstrap.min.css'; import React from 'react'; import ListContainer from '../components/ListContainer'; import * as ListItemStories from './ListItem.stories'; export default { title: 'ListContainer', component: ListContainer, }; const Template = (args) => <ListContainer {...args} />; export const DefaultList = Template.bind({}); DefaultList.args = { books: [{ ...ListItemStories.NodeMongoBook.args }, { ...ListItemStories.NodeMySQLBook.args }, { ...ListItemStories.MicroservicesBook.args }] }; |
Esse código acima deve refletir no Storybook da seguinte forma.
Repare como importei as histórias pré-existentes de ListItem e usei cada uma delas para compor o array de books que deve ser passado como argumento para ListContainer.
A vantagem disso é muito clara quando precisamos alterar alguma das histórias anteriores e automaticamente a história do ListContainer também será alterada por consequência.
E isso remete também à própria vantagem do Storybook em si, pois uma alteração no componente original pode ser facilmente visualizada apenas navegando pelas histórias criadas, identificando mudanças fora do esperado muito facilmente.
E com isso finalizamos este tutorial. Espero ter lhe ajudado a entender as vantagens e como usar o Storybook em seus projetos de front React, explorando ao máximo um dos principais aspectos dessa tecnologia que é a componentização.
Quer aprender mais sobre testes de front-end cm Storybook, Jest e Chromatic? Dá uma olhada neste tutorial!
Até a próxima!
Curtiu o tutorial? Conheça minha formação completa de fullstack com JavaScript clicando no banner abaixo!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.