我是如何在 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👏

相关文章
相关标签/搜索