Skip to content

组件开发规范

Jack edited this page Aug 31, 2021 · 5 revisions

目录结构

$ tree -L 2 date-picker
date-picker
├── DatePicker.tsx
├── __test__
│   └── DatePicker.test.tsx
├── demos
│   ├── DatePicker.stories.tsx
│   └── DatePickerPage.tsx
├── index.ts
├── interfaces.ts
├── locales
│   ├── en-US.ts
│   └── zh-CN.ts
└── style
    ├── index.less
    └── index.ts

4 directories, 10 files
  • 组件实现:包含组件实现、组件接口定义和组件导出等文件;
  • Demo:demos 目录下,组件各种样式的 Demo 和组件文档;
  • 单元测试:__test__ 目录下,使用 Jest 和 React Testing Library 实现的测试用例;
  • 国际化:locales 目录下,每种语言对应一个文件;
  • 样式:style 目录下,Less 实现的组件样式。

组件实现

  • index.ts 导出组件和组件的 Props
  • interfaces.ts 定义组件的 Props
  • 组件实现文件的文件名要和组件名一致

Demo

主要包含 DatePicker.stories.tsxDatePickerPage.tsx 两种文件。前者是组件的故事集,包含各种组件的样式;后者是组件文档。

故事集

每个组件都有一个默认的 Story,使用 Props 的默认值,然后可通过 Storybook 的 Controls 来修改 Props 值,来查看各种样式。

默认故事

示例:

const Template: Story = (args) => <DatePicker {...args} />;

export const Basic = Template.bind({});
Basic.args = {};

自定义故事

只要返回 Story 类型值即可,如果为 Story 设置了 Props 类型,在 Controls 面板即可控制这些 Props

示例:

export const DisabledDate = Template.bind({});
DisabledDate.args = {
  disabledDate: (date: Date) => isBefore(startOfToday(), date),
};

组件设计稿

为了方便开发效果和设计对比,集成了 storybook-addon-designs 插件,可以在 Storybook 网站上直接预览设计稿。

集成方式如下:

export default {
  parameters: {
    design: {
      type: 'figma',
      url: 'https://www.figma.com/file/kP3A6S2fLUGVVMBgDuUx0f/GrowingIO-Design-Components?node-id=2672%3A30128',
      allowFullscreen: true,
    },
  },
} as Meta;

如果某个 Story 有单独的样式,它的设计稿也可以单独设置:

Indicator.story = {
  parameters: {
    design: {
      url: 'https://www.figma.com/file/kP3A6S2fLUGVVMBgDuUx0f/GrowingIO-Design-Components?node-id=889%3A1159',
    },
  },
};

组件文档

文档主要包含以下几部分:

  • 标题
  • 代码演示
  • 参数说明

标题

标题直接是组件的名称,使用 Storybook 的 Title 组件。关于组件的定义,直接使用 p 标签即可。

示例:

<Title>{formatMessage({ defaultMessage: 'DatePicker 日期选择器' })}</Title>
<p>{formatMessage({ defaultMessage: '当用户需要一个日期,可以在面板中进行选择。' })}</p>

代码演示

“代码演示”用 Heading 标签,各种样式名称用 Subheading。用 Story 来展示组件的各种使用场景,每种场景对应一个 Story。 主要提供场景的描述和 Story 链接。另外,Story 外面包一层 Canvas,这样可以在文档中直接显示组件。

示例:

<Heading>{formatMessage({ defaultMessage: '代码演示' })}</Heading>
<Subheading>{formatMessage({ defaultMessage: '基本样式' })}</Subheading>
<Canvas>
  <Story id="pickers-datepicker--basic" />
</Canvas>

参数说明

“参数说明”使用 Heading 标签,使用 Storybook 的 ArgsTable 展示组件的 Props 类型。

示例:

<Heading>{formatMessage({ defaultMessage: '参数说明' })}</Heading>
<ArgsTable of={DatePicker} />

文档国际化

组件文档的国际化使用 react-intl 工具来实现。所以上文中所有文案都用 formatMessage 函数。

import React from 'react';
import { Canvas, Title, Heading, Story, Subheading, ArgsTable } from '@storybook/addon-docs';
import { useIntl } from 'react-intl';
import DatePicker from '../DatePicker';

export default function DatePickerPage() {
  const { formatMessage } = useIntl();

  return (
    <>
      <Title>{formatMessage({ defaultMessage: 'DatePicker 日期选择器' })}</Title>
      ...
    </>
  );
}

单元测试

测试工具使用 Jest + React Testing Library。 在使用 Storybook 时,我们创建了组件的各种使用场景,当我们编写测试用例时,为了提高测试覆盖率,我们需要做同样的工作!所以,在单元测试中要复用 Storybook 的故事集。示例如下:

import { Basic, DisabledDate } from '../demos/DatePicker.stories';

describe('DatePicker', () => {
  it('render with default', () => {
    const handleOnSelect = jest.fn();
    render(<Basic {...Basic.args} onSelect={handleOnSelect} />);
    expect(screen.getAllByText(20)).toHaveLength(1);
    fireEvent.click(screen.getByText(20));
    expect(handleOnSelect).toHaveBeenCalledWith(new Date());
  });

  it('render with disabled date', () => {
    const handleOnSelect = jest.fn();
    render(<DisabledDate {...DisabledDate.args} onSelect={handleOnSelect} />);
    expect(screen.getAllByText(21)).toHaveLength(1);
    fireEvent.click(screen.getByText(21));
    expect(handleOnSelect).not.toHaveBeenCalled();
  });
});

国际化

  • 每个组件有自己的 Locale 文件
  • src/locales 目录下是汇总所有组件的 Locale 文件
  • 每种语言对应一个文件,语言代码参考 LCID

DatePicker 组件的示例:

import rcPickerLocale from 'rc-picker/lib/locale/zh_CN';

const locale = {
  ...rcPickerLocale,
};

export default locale;

汇总的 Locale 文件:

import type { Locale } from '@gio-design/utils';
import datePickerLocale from '../date-picker/locales/zh-CN';

export const locale: Locale = {
  code: 'zh-CN',
  DatePicker: {
    ...datePickerLocale,
  },
};

export default locale;

样式

  • index.less 中实现组件的样式,实现时遵循 CSS BEM 书写规范
  • index.tsimport './index.less'。如果使用了其他组件,也要 import 对应组件的 Less 文件。