天天看點

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

在使用 React開發元件時經常會有一些苦惱,比如當一個元件的複雜度逐漸上升時,它所擁有的狀态不容易追溯;當需要檢視某種狀态的元件時,可能需要手動更改元件的屬性或是更改接口傳回的資料(資料驅動的元件)等等。于是我就去了解并學習 Storybook,然後組織了一次分享會,這也是我們團隊的第一次技術分享。

關于 Storybook,我在一兩年前有接觸并嘗試使用,當時對元件化開發的了解可能有限,隻是為了用而用,并未感受到它的實用之處;加上經過多次的疊代,Storybook已經到了 6.0 版本,可以說是更易用、更優雅了。

我是如何在 Nextjs 項目中使用Storybook驅動元件開發的?
上圖是分享會 ppt 的封面,感興趣的同學可以私信我,接下來進入正題。

動機

  • 新項目的 UI 系統需要重新設計
  • 項目疊代,元件複雜度逐漸變高,元件狀态不容易追溯
  • 追求更優雅、更具維護性的編碼方式

目标

這篇文章主要給大家分享一下幾點:

  • 介紹 Storybook
  • 通過一個小例子展示如何在 Next.js 中使用 Storybook
  • 我的代碼編寫習慣

要求

因為包含了實踐,可能有以下幾點要求,不過不用擔心,隻要你能看懂就行:

  • 示例是基于 Next.js 的,這個我在上一篇文章中有講到如何搭建 Next.js 項目,可以點選這裡把我搭建的腳手架克隆到本地,以便可以跟着動手。
  • 因為是基于上一篇文章所搭建的腳手架,是以它所擁有的特性也需要了解,比如 Typescript、styled-component。

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

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

提供強大的 UIs

  • 獨立建構元件

    建立元件時不需要豎起螢幕,不需要處理資料,也不需要建構業務邏輯。

    我是如何在 Nextjs 項目中使用Storybook驅動元件開發的?
  • 模拟難以達到的用例

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

    我是如何在 Nextjs 項目中使用Storybook驅動元件開發的?
  • 用例作為一個故事

    将用例儲存為 Javascript 中的故事,以便在開發、測試和QA期間重新通路。

    我是如何在 Nextjs 項目中使用Storybook驅動元件開發的?
  • 使用插件減少工作流程

    使用插件可以更快地建構UI,元件文檔化,并簡化工作流程。

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

元件更具可靠性

  • 確定一緻的使用者體驗

    每當寫一個故事,就得到一種狀态的視覺效果。快速地浏覽故事,檢查最賤 UI 的正确性。

    我是如何在 Nextjs 項目中使用Storybook驅動元件開發的?
  • 自動回歸測試代碼

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

    我是如何在 Nextjs 項目中使用Storybook驅動元件開發的?
  • 單元測試元件

    對元件進行單元測試確定元件能正常工作。

    我是如何在 Nextjs 項目中使用Storybook驅動元件開發的?
  • 基于每次送出像素級地捕獲UI變化

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

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

分享和重用所有東西

  • 在項目中查找任何元件

    Storybook 可搜尋編寫的任何元件,為你的UI元件提供真實資訊的單一來源。

    我是如何在 Nextjs 項目中使用Storybook驅動元件開發的?
  • 開發過程中獲得及時回報

    通過 Storybook 部署到雲端,與團隊協作實作UI。

    我是如何在 Nextjs 項目中使用Storybook驅動元件開發的?
  • 跨端跨應用共享元件

    每個故事都是一個用例,團隊成員可以找到它并決定是否重用。

    我是如何在 Nextjs 項目中使用Storybook驅動元件開發的?
  • 生成文檔

    編寫 markdown/MDX,為元件庫和設計系統生成可定制化的文檔。

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

使用 Storybook

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

安裝

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

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

以上幾部沒問題之後,現在就可以在 http://localhost:6006/ 通路 Storybook 提供的 UIs 了:

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

它預設提供了幾個例子,如 Button、Header等,例子代碼在 src/pages/stories 中:

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

字尾名為 stories.tsx 的檔案就是一個故事,它定義了我們想要定義的元件的表現狀态;大家可能不是很了解一個故事是什麼,後面大家看了示例之後就會了解了,我先打個比方,一個人就好比一個故事,當他有不同的心情時,就會表現出不同的表情,同一時間隻能看到它的一種表情,但我現在用照片記錄他所表現的一個個不同的表情,這有利于我去分析這個人的性格;Storybook 就像是照相機,可以記錄元件的不同狀态,便于我們去追溯。

設計 ProductOptimCard 元件

接下來設計并實作 ProductOptimCard 元件,這個元件是資料驅動的,也就是内容是根據資料的變化而變化的,為了友善,我隻定義了标題、是否必做、是否完成這三個屬性,它們的變化會展示不同狀态下的視圖,預設的效果如下:

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

以下是元件實作代碼:

// 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>1、尺寸:800 x 800px</Text>
        <Text>2、賣點提煉文字展示(針對同款多、标品類目)</Text>
        <Text>3、産品占圖檔三分之二</Text>
        <Text>4、參考五家淘寶以及阿裡優秀類似款主圖(按成交金額排序)</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/ 檢視:

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

圖中紅框中的元件就是 ProductOptimCard 的預設樣式,元件本身已經實作了不同狀态:如必做、不必做、已完成、未完成;但我想檢視某個狀态,将不得不更改 src/pages/index.tsx 中傳給 ProductOptimCard 的 data 屬性,而這個通常是根據接口傳回的資料,要去該代碼就顯得麻煩不優雅了,不過不用擔心,我們現在有 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,并為其編寫了四種狀态,分别是 DefaultCard、MustDoCard、FinishCard、UnFinishCard,傳入不同的data,自然會表現出不同的狀态。然後打開 http://localhost:6006/:

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

紅框是我們為 ProductOptimCard 編寫的故事,點選不同狀态以檢視 UI 效果:

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

可以看到,我們很容易就知道并檢視這個元件的不同狀态,是不是有點躍躍欲試了呢,點選 Docs 可檢視文檔,其它操作就大家課後自己嘗試:

我是如何在 Nextjs 項目中使用Storybook驅動元件開發的?
項目中如有使用 alias 為檔案夾設定别名,導入形式是這樣 import { Box } from '@/styles/common';,這通常是在我們的 tsconfig.json 中已經配置了,但是 storybook 不認識,也需要配置一下,它支援我們自定義 webpack 配置,打開 .storybook/main.js,添加如下代碼:
// .storybook/main.jsconst 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;複制代碼      

總結