Intro to Storybook
  • 开始吧
  • 简单组件
  • 合成组件
  • 数据
  • 页面
  • 部署
  • 测试
  • 插件
  • 总结
  • 贡献
Framework:
ReactReact NativeVueAngularSvelteEmber

构建一个页面

用组件构建一个页面
此社区翻译尚未更新为最新的Storybook版本。通过应用此翻译的中文指南中的更改来帮助我们更新它。 Pull requests 欢迎他们.

我们关注了如何自底向上的构建 UI;从小规模起步逐渐增加复杂性。这样我们可以独立的开发每个组件,弄清其所需的数据,以及如何将其应用于 Storybook。所有的这些操作都不需要我们启动一个服务器或者创建一个页面!

在本章节中我们继续增加复杂性,我们将组件组合成一个页面并在 Storybook 中应用它。

嵌套的容器组件

因为我们的应用十分简单,所以我们要构建的页面也十分简单,只需要给TaskList容器组件(使用 Vuex 中的数据)添加一些布局,并使用 store 创建一个顶层的error字段(例如当我们遇到一些服务器连接问题时我们可以设置此字段)。在src/components/文件夹中创建一个表示型组件PureInboxScreen.vue

src/components/PureInboxScreen.vue
<template>
  <div>
    <div v-if="error" class="page lists-show">
      <div class="wrapper-message">
        <span class="icon-face-sad" />
        <div class="title-message">Oh no!</div>
        <div class="subtitle-message">Something went wrong</div>
      </div>
    </div>
    <div v-else class="page lists-show">
      <nav>
        <h1 class="title-page">
          <span class="title-wrapper">Taskbox</span>
        </h1>
      </nav>
      <TaskList />
    </div>
  </div>
</template>

<script>
  import TaskList from './TaskList.vue';
  export default {
    name: 'PureInboxScreen',
    components: { TaskList },
    props: {
      error: { type: Boolean, default: false },
    },
  };
</script>

接着我们创建一个容器,为src/components/InboxScreen.vue中的PureInboxScreen抓取数据:

src/components/InboxScreen.vue
<template>
  <PureInboxScreen :error="error" />
</template>

<script>
  import { computed } from 'vue';

  import { useStore } from 'vuex';

  import PureInboxScreen from './PureInboxScreen';

  export default {
    name: 'InboxScreen',
    components: { PureInboxScreen },
    setup() {
      //👇 Creates a store instance
      const store = useStore();

      //👇 Retrieves the error from the store's state
      const error = computed(() => store.state.error);
      return {
        error,
      };
    },
  };
</script>

同时我们修改App组件让其渲染InboxScreen(最终我们会使用路由来决定渲染哪个页面,现在我们暂时不需要担心这些):

src/App.vue
<template>
  <div id="app">
-   <task-list />
+   <InboxScreen />
  </div>
</template>

<script>
- import TaskList from './components/TaskList.vue';
+ import InboxScreen from './components/InboxScreen.vue';
  export default {
    name: 'app',
    components: {
-     TaskList
+     InboxScreen,
    },
  };
</script>

<style>
  @import './index.css';
</style>

但是,当在 Storybook 中渲染 story 时事情就变得有趣起来了。

正如我们之前所见的,TaskList组件作为一个容器负责渲染PureTaskList这个表示型组件。我们定义了容器组件不能简单的被隔离式地渲染;我们希望传递给它们一些上下文或者让它们和某个服务进行通信。这表示如果我们希望在 Storybook 中渲染一个容器,我们必须模拟(例如提供一个虚拟版本)一个其所需的上下文或者服务。

当在 Storybook 中处理TaskList时,我们可以简单的通过渲染PureTaskList来回避这个问题。我们这里同样通过渲染PureInboxScreen来解决此问题。

但是这里我们遇到的问题是,尽管PureInboxScreen本身是表示型的,但它的子组件TaskList却并不是。某种意义上来说PureIndexScreen被“容器性”所污染了。所以当我们设置src/components/PureInboxScreen.stories.js时:

src/components/PureInboxScreen.stories.js
import PureInboxScreen from './PureInboxScreen.vue';

export default {
  component: PureInboxScreen,
  title: 'PureInboxScreen',
};

const Template = args => ({
  components: { PureInboxScreen },
  setup() {
    return {
      args,
    };
  },
  template: '<PureInboxScreen v-bind="args" />',
});

export const Default = Template.bind({});

export const Error = Template.bind({});
Error.args = { error: true };

我们发现尽管error的 story 运作正常,但因为TaskList没有连接相对应的 Vuex store,所以default的 story 出错了。(类似的问题同样出现在PureIndexSreen的单元测试中)。

Broken inbox

回避此问题的一种方法是永远不要在您应用中渲染容器组件,除非该组件是最高层组件,并且在最高层组件中自顶而下的传递所有需要的数据。

但是,开发人员将会不可避免的在下层结构中渲染容器组件。如果我们想要在 Storybook 中渲染应用中大部分或者全部的组件(我们想!),我们仍需要一个解决方案。

💡 需要说明的是,自顶而下的传递数据是一种合理的解决方案,尤其是使用GraphQL时。这也是我们在Chromatic中构建超过800个story的方式。

在 story 中提供上下文

好消息是对于 story 来说在PureInboxScreen中使用 Vuex store 十分容易!我们可以在 story 文件中创建一个新的 store 作为上下文:

src/components/PureInboxScreen.stories.js
+ import { app } from '@storybook/vue3';

+ import { createStore } from 'vuex';

import PureInboxScreen from './PureInboxScreen.vue';

+ import { action } from '@storybook/addon-actions';
+ import * as TaskListStories from './PureTaskList.stories';

+ const store = createStore({
+   state: {
+     tasks: TaskListStories.Default.args.tasks,
+   },
+   actions: {
+     pinTask(context, id) {
+       action("pin-task")(id);
+     },
+     archiveTask(context, id) {
+       action("archive-task")(id);
+     },
+   },
+ });

+ app.use(store);

export default {
  title: 'PureInboxScreen',
  component: PureInboxScreen,
};

const Template = (args) => ({
  components: { PureInboxScreen },
  setup() {
    return {
      args,
    };
  },
  template: '<PureInboxScreen v-bind="args" />',
});

export const Default = Template.bind({});

export const Error = Template.bind({});
Error.args = { error: true };

使用其他的,例如ApolloRelay 或者别的库时也可以利用类似的方法模拟上下文。

在 Storybook 中遍历状态让我们可以轻松的测试应用的正确性:

组件驱动开发

我们以Task起步,进一步实现TaskList,现在我们创建了整个页面的 UI。我们的InboxScreen包括了一个嵌套容器组件,以及一系列相关联的 story。

组件驱动开发让您可以一步步的在升级组件结构的同时扩展应用的复杂性。同时也使得我们可以更专注于开发本身,并提高对所有可能的 UI 排列组合的覆盖率。简而言之,CDD 帮助您创建了高质量以及更复杂的 UI。

我们还没有完全结束 - 光创建 UI 是不够的。我们仍需要保证应用的耐用性。

💡 别忘了提交您的代码!
Keep your code in sync with this chapter. View 2450b37 on GitHub.
Is this free guide helping you? Tweet to give kudos and help other devs find it.
Next Chapter
部署
学习如何部署Storybook
✍️ Edit on GitHub – PRs welcome!
Docs
Documentation
Add Storybook to your project in less than a minute to build components faster and easier.
reactvueangularweb-components
Tutorial
Tutorials
Learn Storybook with in-depth tutorials that teaches Storybook best practices. Follow along with code samples.
Learn Storybook now
Storybook
The MIT License (MIT). Website design by @domyen and the awesome Storybook community.
StorybookShowcaseDocsTutorialsAddonsBlogReleasesGet involvedUse casesSupportTelemetryTeam
Community GitHub Twitter Discord chat Youtube Component Driven UIs
Subscribe
Get news, free tutorials, and Storybook tips emailed to you.

Maintained by
Chromatic
Continuous integration by
CircleCI
Hosting by
Netlify