在使用 React
開發組件時常常會有一些苦惱,好比當一個組件的複雜度逐步上升時,它所擁有的狀態不容易追溯;當須要查看某種狀態的組件時,可能須要手動更改組件的屬性或是更改接口返回的數據(數據驅動的組件)等等。因而我就去了解並學習 Storybook
,而後組織了一次分享會,這也是咱們團隊的第一次技術分享。javascript
關於 Storybook
,我在一兩年前有接觸並嘗試使用,當時對組件化開發的理解可能有限,只是爲了用而用,並未感覺到它的實用之處;加上通過屢次的迭代,Storybook
已經到了 6.0 版本,能夠說是更易用、更優雅了。java
上圖是分享會 ppt 的封面,感興趣的同窗能夠私信我,接下來進入正題。react
這篇文章主要給你們分享一下幾點:webpack
Next.js
中使用 Storybook由於包含了實踐,可能有如下幾點要求,不過不用擔憂,只要你能看懂就行:git
Next.js
的,這個我在上一篇文章中有講到如何搭建 Next.js
項目,能夠點擊這裏把我搭建的腳手架克隆到本地,以即可以跟着動手。Typescript
、styled-component
。storybook是一個開源工具,爲React、Vue、Angular等框架提供一個沙箱環境,可獨立地開發UI組件;它更有組織和高效地構建出使人驚歎的 UIs。github
獨立構建組件web
建立組件時不須要豎起屏幕,不須要處理數據,也不須要構建業務邏輯。 shell
模擬難以達到的用例json
在一個應用中渲染關鍵狀態是不容易的 bash
用例做爲一個故事
將用例保存爲 Javascript 中的故事,以便在開發、測試和QA期間從新訪問。
使用插件減小工做流程
使用插件能夠更快地構建UI,組件文檔化,並簡化工做流程。
確保一致的用戶體驗
每當寫一個故事,就獲得一種狀態的視覺效果。快速地瀏覽故事,檢查最賤 UI 的正確性。
自動迴歸測試代碼
使用官方插件 Storyshots 啓動代碼快照。
單元測試組件
對組件進行單元測試確保組件能正常工做。
基於每次提交像素級地捕獲UI變化
用視覺測試工具查明UI的變化。
在項目中查找任何組件
Storybook 可搜索編寫的任何組件,爲你的UI組件提供真實信息的單一來源。
開發過程當中得到及時反饋
經過 Storybook 部署到雲端,與團隊協做實現UI。
跨端跨應用共享組件
每一個故事都是一個用例,團隊成員能夠找到它並決定是否重用。
生成文檔
編寫 markdown/MDX,爲組件庫和設計系統生成可定製化的文檔。
下面我會經過一個示例想你們展現 Storybook 是如何工做的,期間也能看到我是如何使用結合 Typescript、styled-components以及個人編碼習慣。
假設你已經克隆了這個倉庫,首先在項目中安裝 storybook
:
# 安裝 storybook
yarn add storybook
# 初始化 storybook 項目,會根據項目類型自動地進行配置
npx sb init
# 啓動 storybook 服務
yarn storybook
複製代碼
以上幾步沒問題以後,如今就能夠在 http://localhost:6006/ 訪問 Storybook 提供的 UIs 了:
它默認提供了幾個例子,如 Button
、Header
等,例子代碼在 src/pages/stories
中:
後綴名爲 stories.tsx
的文件就是一個故事,它定義了咱們想要定義的組件的表現狀態;你們可能不是很理解一個故事是什麼,後面你們看了示例以後就會理解了,我先打個比方,一我的就比如一個故事,當他有不一樣的心情時,就會表現出不一樣的表情,同一時間只能看到它的一種表情,但我如今用照片記錄他所表現的一個個不一樣的表情,這有利於我去分析這我的的性格;Storybook 就像是照相機,能夠記錄組件的不一樣狀態,便於咱們去追溯。
ProductOptimCard
組件接下來設計並實現 ProductOptimCard
組件,這個組件是數據驅動的,也就是內容是根據數據的變化而變化的,爲了方便,我只定義了標題、是否必作、是否完成這三個屬性,它們的變化會展現不一樣狀態下的視圖,默認的效果以下:
如下是組件實現代碼:
// 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/ 查看:
圖中紅框中的組件就是 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/:
紅框是咱們爲 ProductOptimCard
編寫的故事,點擊不一樣狀態以查看 UI 效果:
能夠看到,咱們很容易就知道並查看這個組件的不一樣狀態,是否是有點躍躍欲試了呢,點擊 Docs
可查看文檔,其它操做就你們課後本身嘗試:
項目中若有使用 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 組件驅動開發。
一般一個組件引入的三方庫在最頂部,其次是自定義組件,因此我這裏的順序值得是組件中變量定義的位置,如下是我所習慣的定義順序(從上往下),每一個區域隔一行:
三方庫
自定義組件
圖片常量
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 測試,對於後面兩個測試庫,確實進一步地提高了組件的健壯性,可是會增長不少的工做量,通常的小公司確實用不着,感興趣的能夠課下自行研究。