返回至Storybook 简介
章节
  • 开始上手
  • 简单组件
  • 复合组件
  • 数据
  • 屏幕
  • 部署
  • 测试
  • 插件
  • 结论
  • 贡献

构建一个简单组件

在隔离环境中构建一个简单组件
此社区翻译尚未更新至最新版本的 Storybook。请帮助我们将英文指南中的更改应用到此翻译中,以更新此翻译。 欢迎提交 Pull Request.

我们将遵循组件驱动开发 (CDD) 方法构建我们的 UI。这是一个从“自下而上”构建 UI 的过程,从组件开始,到屏幕结束。CDD 帮助您扩展在构建 UI 时面临的复杂性。

任务

Task component in three states

Task 是我们应用中的核心组件。每个任务的显示方式略有不同,具体取决于它所处的状态。我们显示一个选中(或未选中)的复选框、一些关于任务的信息和一个“置顶”按钮,允许我们将任务在列表中上下移动。 综合来看,我们将需要以下 props:

  • title – 描述任务的字符串
  • state - 任务当前所在的列表以及是否已选中?

当我们开始构建 Task 时,我们首先编写与上面草图中的不同任务类型相对应的测试状态。然后我们使用 Storybook 在隔离环境中,使用模拟数据构建组件。我们将随着进度的推移“可视化测试”组件在每种状态下的外观。

此过程类似于测试驱动开发 (TDD),我们可以称之为“可视化 TDD”。

准备设置

首先,让我们为任务组件及其配套的故事文件创建必要的文件:app/components/task.hbsapp/components/task.stories.js

我们将从 Task 的基本实现开始,只需接受我们知道需要的属性以及您可以对任务执行的两个操作(在列表之间移动它)

复制
app/components/task.hbs
<div class="list-item">
  <input type="text" value={{@task.title}} readonly={{true}}/>
</div>

在上面,我们基于 Todos 应用的现有 HTML 结构,为 Task 渲染了简单的标记。

下面我们在故事文件中构建 Task 的三个测试状态

复制
app/components/task.stories.js
import { hbs } from 'ember-cli-htmlbars';

import { action } from '@storybook/addon-actions';

export default {
  title: 'Task',
  component: 'task',
  excludeStories: /.*Data$/,
};

export const actionsData = {
  onPinTask: action('onPinTask'),
  onArchiveTask: action('onArchiveTask'),
};

const Template = args => ({
  template: hbs`<Task @task={{this.task}} @pin={{fn this.onPinTask}} @archive={{fn this.onArchiveTask}}/>`,
  context: args,
});

export const Default = Template.bind({});
Default.args = {
  task: {
    id: '1',
    title: 'Test Task',
    state: 'TASK_INBOX',
    updatedAt: new Date(2018, 0, 1, 9, 0),
  },
  ...actionsData,
};

export const Pinned = Template.bind({});
Pinned.args = {
  ...Default.args,
  task: {
    ...Default.args.task,
    state: 'TASK_PINNED',
  },
};

export const Archived = Template.bind({});
Archived.args = {
  ...Default.args,
  task: {
    ...Default.args.task,
    state: 'TASK_ARCHIVED',
  },
};

Storybook 中有两个基本的组织级别:组件及其子故事。 将每个故事视为组件的一种排列。 您可以根据需要为每个组件设置任意数量的故事。

  • 组件
    • 故事
    • 故事
    • 故事

为了告诉 Storybook 我们正在记录的组件,我们创建一个包含以下内容的 default export:

  • component -- 组件本身,
  • title -- 如何在 Storybook 应用的侧边栏中引用组件,
  • excludeStories -- 故事文件中不应由 Storybook 渲染为故事的 exports。

为了定义我们的故事,我们为每个测试状态导出一个函数,以生成一个故事。故事是一个函数,它返回给定状态下的渲染元素(即,一组 props 的组件)--- 完全类似于函数式组件

由于我们有组件的多种排列,因此将其分配给 Template 变量很方便。在您的故事中引入此模式将减少您需要编写和维护的代码量。

💡 Template.bind({}) 是一种标准 JavaScript 技术,用于制作函数的副本。我们使用此技术允许每个导出的故事设置其自己的属性,但使用相同的实现。

参数或简写 args,允许我们使用 controls 插件实时编辑组件,而无需重启 Storybook。一旦 args 值发生更改,组件也会随之更改。

在创建故事时,我们使用基本 task arg 来构建组件期望的任务形状。这通常根据真实数据的外观建模。同样,export 这个形状将使我们能够在以后的故事中重用它,我们稍后会看到。

💡 Actions 帮助您在隔离构建 UI 组件时验证交互。通常,您无法访问您在应用上下文中拥有的函数和状态。使用 action() 来 stub 它们。

配置

我们还需要对 Storybook 配置进行一个小小的更改,以便它可以注意到我们最近创建的故事。将您的配置文件 (.storybook/main.js) 更改为以下内容

复制
.storybook/main.js
module.exports = {
- stories: [
-   '../src/**/*.stories.mdx',
-   '../src/**/*.stories.@(js|jsx|ts|tsx)'
- ],
+ stories: ['../app/components/**/*.stories.js'],
  addons: ['@storybook/addon-actions', '@storybook/addon-links'],
};

完成此操作后,重启 Storybook 服务器应该会为三个 Task 状态生成测试用例

构建状态

现在我们已经设置了 Storybook,导入了样式,并构建了测试用例,我们可以快速开始实现组件的 HTML 以匹配设计的工作。

该组件目前仍然很简单。首先编写实现设计效果的代码,而无需深入太多细节

复制
app/components/task.hbs
<div class="list-item {{@task.state}}" data-test-task>
  <label class="checkbox">
    <input
      type="checkbox"
      disabled
      name="checked"
      checked={{this.isArchived}}
    />
    <span
      class="checkbox-custom"
      data-test-task-archive
      {{on "click" this.archive}}
    ></span>
  </label>
  <div class="title">
    <input
      type="text"
      readonly
      value={{@task.title}}
      placeholder="Input title"
    />
  </div>
  <div class="actions">
    {{#unless this.isArchived}}
      <span data-test-task-pin {{on "click" this.pin}}>
        <span class="icon-star"></span>
      </span>
    {{/unless}}
  </div>
</div>

然后我们需要创建一个名为 app/components/task.js 的新文件,内容如下

复制
app/components/task.js
import Component from '@glimmer/component';
import { action } from '@ember/object';

export default class Task extends Component {
  // Computed property for the component (to assign a value to the task state checkbox)
  get isArchived() {
    return this.args.task.state === 'TASK_ARCHIVED';
  }

  @action
  pin() {
    this.args.pin?.(this.args.task.id);
  }

  @action
  archive() {
    this.args.archive?.(this.args.task.id);
  }
}

上面额外的标记与我们之前导入的 CSS 相结合,产生了以下 UI

组件已构建!

我们现在已经成功构建了一个组件,而无需服务器或运行整个前端应用程序。下一步是以类似的方式逐个构建其余的 Taskbox 组件。

如您所见,开始在隔离环境中构建组件既简单又快速。我们可以期望生产出更高质量的 UI,错误更少,并且更加完善,因为可以深入挖掘并测试每种可能的状态。

💡 不要忘记使用 git 提交您的更改!
这份免费指南对您有帮助吗?发推文表示感谢,并帮助其他开发者找到它。
下一章
复合组件
从更简单的组件组装一个复合组件
✍️ 在 GitHub 上编辑 – 欢迎 PR!
加入社区
6,721位开发者以及更多
为什么为什么选择 Storybook组件驱动的 UI
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI