我是如何在 Nextjs 項目中使用Storybook驅動組件開發的?

在使用 React開發組件時常常會有一些苦惱,好比當一個組件的複雜度逐步上升時,它所擁有的狀態不容易追溯;當須要查看某種狀態的組件時,可能須要手動更改組件的屬性或是更改接口返回的數據(數據驅動的組件)等等。因而我就去了解並學習 Storybook,而後組織了一次分享會,這也是咱們團隊的第一次技術分享。javascript

關於 Storybook,我在一兩年前有接觸並嘗試使用,當時對組件化開發的理解可能有限,只是爲了用而用,並未感覺到它的實用之處;加上通過屢次的迭代,Storybook已經到了 6.0 版本,能夠說是更易用、更優雅了。java

image-20210112094407173

上圖是分享會 ppt 的封面,感興趣的同窗能夠私信我,接下來進入正題。react

動機

  • 新項目的 UI 系統須要從新設計
  • 項目迭代,組件複雜度逐步變高,組件狀態不容易追溯
  • 追求更優雅、更具維護性的編碼方式

目標

這篇文章主要給你們分享一下幾點:webpack

  • 介紹 Storybook
  • 經過一個小例子展現如何在 Next.js 中使用 Storybook
  • 個人代碼編寫習慣

要求

由於包含了實踐,可能有如下幾點要求,不過不用擔憂,只要你能看懂就行:git

  • 示例是基於 Next.js 的,這個我在上一篇文章中有講到如何搭建 Next.js 項目,能夠點擊這裏把我搭建的腳手架克隆到本地,以即可以跟着動手。
  • 由於是基於上一篇文章所搭建的腳手架,因此它所擁有的特性也須要了解,好比 Typescriptstyled-component

介紹 Storybook

storybook是一個開源工具,爲React、Vue、Angular等框架提供一個沙箱環境,可獨立地開發UI組件;它更有組織和高效地構建出使人驚歎的 UIs。github

提供強大的 UIs

  • 獨立構建組件web

    建立組件時不須要豎起屏幕,不須要處理數據,也不須要構建業務邏輯。 shell

  • 模擬難以達到的用例json

    在一個應用中渲染關鍵狀態是不容易的 bash

  • 用例做爲一個故事

    將用例保存爲 Javascript 中的故事,以便在開發、測試和QA期間從新訪問。

  • 使用插件減小工做流程

    使用插件能夠更快地構建UI,組件文檔化,並簡化工做流程。

組件更具可靠性

  • 確保一致的用戶體驗

    每當寫一個故事,就獲得一種狀態的視覺效果。快速地瀏覽故事,檢查最賤 UI 的正確性。

  • 自動迴歸測試代碼

    使用官方插件 Storyshots 啓動代碼快照。

  • 單元測試組件

    對組件進行單元測試確保組件能正常工做。

  • 基於每次提交像素級地捕獲UI變化

    用視覺測試工具查明UI的變化。

分享和重用全部東西

  • 在項目中查找任何組件

    Storybook 可搜索編寫的任何組件,爲你的UI組件提供真實信息的單一來源。

  • 開發過程當中得到及時反饋

    經過 Storybook 部署到雲端,與團隊協做實現UI。

  • 跨端跨應用共享組件

    每一個故事都是一個用例,團隊成員能夠找到它並決定是否重用。

  • 生成文檔

    編寫 markdown/MDX,爲組件庫和設計系統生成可定製化的文檔。

使用 Storybook

下面我會經過一個示例想你們展現 Storybook 是如何工做的,期間也能看到我是如何使用結合 Typescript、styled-components以及個人編碼習慣。

安裝

假設你已經克隆了這個倉庫,首先在項目中安裝 storybook

# 安裝 storybook
yarn add storybook
# 初始化 storybook 項目,會根據項目類型自動地進行配置
npx sb init
# 啓動 storybook 服務
yarn storybook
複製代碼

以上幾步沒問題以後,如今就能夠在 http://localhost:6006/ 訪問 Storybook 提供的 UIs 了:

image-20210112104704363

它默認提供了幾個例子,如 ButtonHeader等,例子代碼在 src/pages/stories 中:

image-20210112104917297

後綴名爲 stories.tsx 的文件就是一個故事,它定義了咱們想要定義的組件的表現狀態;你們可能不是很理解一個故事是什麼,後面你們看了示例以後就會理解了,我先打個比方,一我的就比如一個故事,當他有不一樣的心情時,就會表現出不一樣的表情,同一時間只能看到它的一種表情,但我如今用照片記錄他所表現的一個個不一樣的表情,這有利於我去分析這我的的性格;Storybook 就像是照相機,能夠記錄組件的不一樣狀態,便於咱們去追溯。

設計 ProductOptimCard 組件

接下來設計並實現 ProductOptimCard 組件,這個組件是數據驅動的,也就是內容是根據數據的變化而變化的,爲了方便,我只定義了標題、是否必作、是否完成這三個屬性,它們的變化會展現不一樣狀態下的視圖,默認的效果以下:

image-20210112110100542

如下是組件實現代碼:

// src/components/towone/ProductOptim/ProductOptimCard/index.tsx
import React from 'react';
import styled from 'styled-components';

interface IProductOptimCardProps {
  data: {
    isMustDo: boolean;
    isFinish: boolean;
    title: string;
  };
}

const Container = styled.div` width: 452px; height: 276px; background: #fefeff; border: 1px solid #edf0fa; box-shadow: 0px 4px 14px 0px rgba(0, 10, 71, 0.07); `;
const Content = styled.div` height: 225px; background: #fff; padding-top: 21px; padding-left: 20px; position: relative; `;
const Footer = styled.div` height: 50px; background: #f7f8fa; display: flex; align-items: center; justify-content: space-between; padding-right: 10px; padding-left: 20px; `;
const Title = styled.div` font-size: 16px; font-weight: bold; color: #333; margin-bottom: 14px; `;
const Badge = styled.div<{ isMustDo: boolean }>` width: 37px; height: 21px; background: ${({ isMustDo }) => (isMustDo ? '#0af' : '#999999')}; font-weight: bold; color: #fefeff; font-size: 12px; border-radius: 11px 2px 11px 11px; position: absolute; top: 10px; right: 10px; display: flex; align-items: center; justify-content: center; `;
const Text = styled.div` font-size: 14px; color: #666666; margin-bottom: 14px; `;
const MoreText = styled.a` font-size: 14px; color: #333333; `;
const FinishButton = styled.div<{ isFinish: boolean }>` width: 60px; height: 28px; background: ${({ isFinish }) => (isFinish ? '#999' : '#046eff')}; color: #fefeff; font-size: 12px; display: flex; justify-content: center; align-items: center; cursor: pointer; `;

const ProductOptimCard: React.FC<IProductOptimCardProps> = ({ data }) => {
  const { isMustDo, isFinish, title } = data;

  return (
    <Container> <Content> <Title>{title}</Title> <Text>一、尺寸:800 x 800px</Text> <Text>二、賣點提煉文字展現(針對同款多、標品類目)</Text> <Text>三、產品佔圖片三分之二</Text> <Text>四、參考五家淘寶以及阿里優秀相似款主圖(按成交金額排序)</Text> <Badge isMustDo={isMustDo}>必作</Badge> </Content> <Footer> <MoreText>更多教程</MoreText> <FinishButton isFinish={isFinish}>完成了</FinishButton> </Footer> </Container>
  );
};

export default ProductOptimCard;
複製代碼

而後在首頁引入它:

// src/pages/index.tsx

//...

export default function Home() {
  return (
    <Conotainer> <ProductOptimCard data={{ isMustDo: false, isFinish: false, title: '單品標題優化' }} /> </Conotainer>
  );
}
複製代碼

執行 yarn dev 啓動項目,而後打開 http://localhost:3000/ 查看:

image-20210112110624993

圖中紅框中的組件就是 ProductOptimCard 的默認樣式,組件自己已經實現了不一樣狀態:如必作、沒必要作、已完成、未完成;但我想查看某個狀態,將不得不更改 src/pages/index.tsx 中傳給 ProductOptimCarddata 屬性,而這個一般是根據接口返回的數據,要去該代碼就顯得麻煩不優雅了,不過不用擔憂,咱們如今有 Storybook了,請往下看。

在同級目錄新建一個 ProductOptimCard.stories.tsx 文件,爲 ProductOptimCard 編寫故事,代碼以下:

import React, { ComponentProps } from 'react';
import { Story, Meta } from '@storybook/react/types-6-0';

import ProductOptimCard from './';

export default {
  title: 'TWOONE/ProductOptim/ProductOptimCard',
  component: ProductOptimCard,
} as Meta;

const Template: Story<ComponentProps<typeof ProductOptimCard>> = (args) => (
  <ProductOptimCard {...args} />
);

export const DefaultCard = Template.bind({});
DefaultCard.args = {
  data: {
    isMustDo: false,
    isFinish: false,
    title: '單品標題優化',
  },
};

export const MustDoCard = Template.bind({});
MustDoCard.args = {
  data: {
    isMustDo: true,
    isFinish: false,
    title: '單品標題優化',
  },
};

export const FinishCard = Template.bind({});
FinishCard.args = {
  data: {
    isMustDo: false,
    isFinish: true,
    title: '單品標題優化',
  },
};

export const UnFinishCard = Template.bind({});
UnFinishCard.args = {
  data: {
    isMustDo: false,
    isFinish: false,
    title: '單品標題優化',
  },
};
複製代碼

咱們引入了 ProductOptimCard,併爲其編寫了四種狀態,分別是 DefaultCardMustDoCardFinishCardUnFinishCard,傳入不一樣的data,天然會表現出不一樣的狀態。而後打開 http://localhost:6006/:

image-20210112111538746

紅框是咱們爲 ProductOptimCard 編寫的故事,點擊不一樣狀態以查看 UI 效果:

e52aa33d-5cfb-4be7-a703-5aa22e07d80c

能夠看到,咱們很容易就知道並查看這個組件的不一樣狀態,是否是有點躍躍欲試了呢,點擊 Docs 可查看文檔,其它操做就你們課後本身嘗試:

image-20210112112953810

項目中若有使用 alias 爲文件夾設置別名,導入形式是這樣 import { Box } from '@/styles/common';,這一般是在咱們的 tsconfig.json 中已經配置了,可是 storybook 不認識,也須要配置一下,它支持咱們自定義 webpack 配置,打開 .storybook/main.js,添加以下代碼:

// .storybook/main.js
const path = require('path');

module.exports = {
  // ...
  webpackFinal: async (config, { configType }) => {
    config.resolve.alias['@'] = path.resolve(__dirname, '../src');
    
    return config;
  },
};
複製代碼

到這裏咱們已經經過一個示例來了解如何使用 Storybook 了,接下來會簡單聊聊個人一些編碼心得。

個人編碼習慣與心得

分類

從數據獲取的層面看,我將組件分爲容器組件和內容組件:

**容器組件:**從接口獲取數據。

**內容組件:**接收 props 數據、可編寫 story 組件驅動開發。

story組件編寫的大體順序

  • Typescript 定義組件接收的參數
  • 爲可選的類型設置默認值
  • 編寫 story 描述不一樣狀態的組件

組件編寫順序

一般一個組件引入的三方庫在最頂部,其次是自定義組件,因此我這裏的順序值得是組件中變量定義的位置,如下是我所習慣的定義順序(從上往下),每一個區域隔一行:

  • 三方庫

  • 自定義組件

  • 圖片常量

  • Typescript 接口

  • 樣式組件

  • 組件區

一個最小化的示例代碼:

import React from 'react';
import styled from 'styled-components';

import { MySelfComp } from '@/components';

import ICON_LOGO from '@/assets/images/icon.logo.png';

interface IProps {}

const Container = styled.div``;

const DemoComp: React.FC<IProps> = () => {
  return <Container></Container>
}

export default DemoComp;
複製代碼

總結

目前帶你們認識了 Storybook,而且介紹瞭如何使用,固然這只是基礎用法,在項目中你們可能也會遇到不一樣的場景,如遇到問題能夠查看官方文檔,仍是寫的挺詳細的;原本想將測試流程也寫進去,不過感受會有不小的篇幅,之後能夠另起一篇文章,我能夠先簡單介紹一個我以爲比較理想的組件開發流程:組件設計並編寫 -> 編寫 story -> Jest 測試 -> Enzyme 測試,對於後面兩個測試庫,確實進一步地提高了組件的健壯性,可是會增長不少的工做量,通常的小公司確實用不着,感興趣的能夠課下自行研究。

附錄

Storybook 6.0

examples

Marketing and docs

BBC Psammead👏

GitLab UI👏

相關文章
相關標籤/搜索