Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update: the intro to storybook vue/chinese documentation #680

Merged
merged 12 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 55 additions & 93 deletions content/intro-to-storybook/vue/zh-CN/composite-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@ description: '使用更简单的组件组装复合组件'
commit: 'd9d6e31'
---

我们在上一章构建了我们的第一个组件;这一章我们继续扩展所学并构建一个 TaskList,即一组 Task。让我们组合组件并看看当引入更多的复杂性时会发生什么
我们在上一章构建了我们的第一个组件;这一章我们将扩展学习构建一个 Tasks 列表,即 TaskList 组件。让我们组合组件,并看看当引入更多的复杂性时会发生什么

## Tasklist

Taskbox 通过将固定 task(pinned tasks)置于其他默认 task 之上来强调固定 task。这就需要您针对两种类型的`TaskList`创建对应的 story:默认的以及默认并固定的
Taskbox 通过将固定 task(pinned tasks)置于其他默认 task 之上来强调固定 task。这就需要您针对两种类型的`TaskList`创建对应的 story:默认的以及固定的 task

![默认并固定的](/intro-to-storybook/tasklist-states-1.png)

因为`Task`的数据可以是非同步的,我们**还**需要一个当连接不存在时需要提供的 loading 状态。此外我们还需要一个空状态来对应没有 task 的情况。
因为 `Task` 的数据可以是异步的,我们**还**需要在连接不存的情况下渲染 loading 状态。此外,我们还需要一个空状态来对应没有 task 的情况。

![空的和loading状态的Task](/intro-to-storybook/tasklist-states-2.png)

## 配置

合成组件相比基本组件并没有太大区别。创建一个`TaskList`组件和其伴随 story 文件:`src/components/TaskList.vue` 和 `src/components/TaskList.stories.js`.
合成组件与其所包含基本组件并没有太大区别。创建一个 `TaskList` 组件和其对应的 story 文件:`src/components/TaskList.vue` 和 `src/components/TaskList.stories.js`

先简单实现以下`TaskList`。您需要先导入`Task`组件并将属性作为输入传入。
`TaskList` 的粗略实现开始。您需要先导入先前的 `Task` 组件并将属性作为输入传入。

```html:title=src/components/TaskList.vue
<template>
Expand All @@ -45,41 +45,41 @@ Taskbox 通过将固定 task(pinned tasks)置于其他默认 task 之上来
</template>

<script>
import Task from './Task';
import { reactive, computed } from 'vue';

export default {
name: 'TaskList',
components: { Task },
props: {
tasks: { type: Array, required: true, default: () => [] },
loading: { type: Boolean, default: false },
},
emits: ['archive-task', 'pin-task'],

setup(props, { emit }) {
props = reactive(props);
return {
isEmpty: computed(() => props.tasks.length === 0),
/**
* Event handler for archiving tasks
*/
onArchiveTask(taskId) {
emit('archive-task', taskId);
},
/**
* Event handler for pinning tasks
*/
onPinTask(taskId) {
emit('pin-task', taskId);
},
};
},
};
import Task from './Task';
import { reactive, computed } from 'vue';

export default {
name: 'TaskList',
components: { Task },
props: {
tasks: { type: Array, required: true, default: () => [] },
loading: { type: Boolean, default: false },
},
emits: ['archive-task', 'pin-task'],

setup(props, { emit }) {
props = reactive(props);
return {
isEmpty: computed(() => props.tasks.length === 0),
/**
* Event handler for archiving tasks
*/
onArchiveTask(taskId) {
emit('archive-task', taskId);
},
/**
* Event handler for pinning tasks
*/
onPinTask(taskId) {
emit('pin-task', taskId);
},
};
},
};
</script>
```

下一步我们在 story 文件中创建`Tasklist`的测试状态。
下一步,在 story 文件中创建 `Tasklist` 的测试状态。

```js:title=src/components/TaskList.stories.js
import TaskList from './TaskList.vue';
Expand Down Expand Up @@ -144,12 +144,12 @@ Empty.args = {
```

<div class="aside">
<a href="https://storybook.js.org/docs/vue/writing-stories/decorators"><b>装饰器</b></a> 提供了一种任意包装story的方法。上述例子中我们在default export中使用decorator关键字来添加样式。装饰器也可以给组件添加其他上下文,详见下文。
<a href="https://storybook.js.org/docs/vue/writing-stories/decorators"><b>Decorators - 装饰器</b></a> 提供了一种任意包装 story 的方法。上述例子中我们使用默认导出的 decorator 关键字给渲染的组件周围添加一些 <code>margin</code>。但是装饰器也可以给组件添加其他上下文,详见下文。
</div>

通过导入`TaskStories`,我们能够以最小的代价[合成](https://storybook.js.org/docs/vue/writing-stories/args#args-composition)story 中的参数(argument)。这样就为每个组件保留了其所需的数据和 action(模拟回调)。
通过导入 `TaskStories`,我们能够以最小的代价[组合](https://storybook.js.org/docs/vue/writing-stories/args#args-composition) story 中的参数(argument)。这样,就为每个组件保留了其所需的数据和 action(模拟回调)。

现在在 Storybook 中查看新的`TaskList` story 吧。
现在在 Storybook 中查看新的 `TaskList` story 吧。

<video autoPlay muted playsInline loop>
<source
Expand All @@ -160,33 +160,37 @@ Empty.args = {

## 创建状态

我们的组件仍然很粗糙但我们已经有了该如何构建 story 的方向。您可能觉得`.list-items`太过简单了。您是对的 - 大多数情况下我们不会仅仅为了增加一层包装就创建一个新组件。但是`WithPinnedTasks`,`loading`和 `empty`这些边界情况却揭示了`TaskList`**真正的复杂性**。
我们的组件仍然很粗糙,但我们已经有了构建 story 的方向。您可能觉得 `.list-items` 太过简单了。您是对的 - 大多数情况下我们不会仅仅为了增加一层包装就创建一个新组件。但是 `WithPinnedTasks`,`loading` 和 `empty` 这些边界情况揭示了 `TaskList` **真正的复杂性**。

```diff:title=src/components/TaskList.vue
<template>
<div class="list-items">
<template v-if="loading">
+ <div v-for="n in 6" :key="n" class="loading-item">
+ <span class="glow-checkbox" />
+ <span class="glow-text"> <span>Loading</span> <span>cool</span> <span>state</span> </span>
+ <span class="glow-text">
+ <span>Loading</span> <span>cool</span> <span>state</span>
+ </span>
+ </div>
</template>

<div v-else-if="isEmpty" class="list-items">
+ <div class="wrapper-message">
+ <span class="icon-check" />
+ <div class="title-message">You have no tasks</div>
+ <div class="subtitle-message">Sit back and relax</div>
+ <p class="title-message">You have no tasks</p>
+ <p class="subtitle-message">Sit back and relax</p>
+ </div>
</div>

<template v-else>
+ <Task v-for="task in tasksInOrder"
+ <Task
+ v-for="task in tasksInOrder"
+ :key="task.id"
+ :task="task"
+ @archive-task="onArchiveTask
+ @pin-task="onPinTask"/>
</template>
+ @archive-task="onArchiveTask"
+ @pin-task="onPinTask"
+ />
</template>
</div>
</template>

Expand All @@ -201,7 +205,7 @@ export default {
tasks: { type: Array, required: true, default: () => [] },
loading: { type: Boolean, default: false },
},
emits: ["archive-task", "pin-task"],
emits: ['archive-task', 'pin-task'],

setup(props, { emit }) {
props = reactive(props);
Expand Down Expand Up @@ -240,50 +244,8 @@ export default {
/>
</video>

注意固定项目出现在列表中的位置。我们希望固定项目可以出现在列表的顶端以提示用户其优先度。

## 自动化测试

在上一章中我们学习了如何使用 Storyshots 来进行快照测试。`Task`没有太多的复杂性,所以已经够用了。对于`TaskList`来说其增加了另一层复杂性,这就需要我们寻找一个合适的自动化测试方法来验证特定的输入可以产生特定的输出。我们使用[Jest](https://facebook.github.io/jest/)加测试渲染器来创建单元测试。

![Jest logo](/intro-to-storybook/logo-jest.png)

### 使用 Jest 进行单元测试

Storybook 使用手动检查和快照测试的方式来防止 UI 的 bug。看起来好像只要我们覆盖了足够多的场景,并且使用一些工具保证可以人为检查 story 的变化后,错误将会大大减少。

但是,魔鬼存在于细节中。我们还需要一个测试框架来显示的实现上述需求。也就是单元测试。

在我们的例子中,我们希望`TastList`可以将`tasks`属性中的固定 task 渲染在非固定 task 的**上面**。尽管我们已经有一个 story (`WithPinnedTasks`)来对应此场景;但对于任何人为的检查来说仍然过于含糊了,因此当组件**停止**按上述那样排序时 bug 就产生了。很显然这样的测试并不会大声告诉您**你错了!**。

所以为了防止这样的问题发生,我们可以使用 Jest 将 story 渲染成 DOM,然后跑一些 DOM 查询代码来验证输出中重要的部分。story 的 format 非常棒的一点在于,我们只需要简单的导入 story 到我们的测试中就可以渲染它了!

创建一个测试文件`tests/unit/TaskList.spec.js`。我们创建测试来判断输出结果。

```js:title=tests/unit/TaskList.spec.js
import { mount } from '@vue/test-utils';

import TaskList from '../../src/components/TaskList.vue';

//👇 Our story imported here
import { WithPinnedTasks } from '../../src/components/TaskList.stories';

test('renders pinned tasks at the start of the list', () => {
const wrapper = mount(TaskList, {
//👇 Story's args used with our test
propsData: WithPinnedTasks.args,
});
const firstPinnedTask = wrapper.find('.list-item:nth-child(1).TASK_PINNED');
expect(firstPinnedTask).not.toBe(null);
});
```

![TaskList test runner](/intro-to-storybook/tasklist-testrunner.png)

注意我们可以在 story 和单元测试中重用`withPinnedTasksData`;这样我们就可以继续以越来越多的方式运用现有资源(代表组件各种有趣配置的示例)。

请注意这个测试仍然十分脆弱。随着项目逐渐成熟,以及`Task`的实现改变时 -- 也许使用了另一个类名 -- 那测试很可能失败,这需要我们去更新它。这不一定是个问题,但是在 UI 中使用单元测试仍需十分小心。它们的维护工作并不容易。除了依靠肉眼,快照和视觉回归(visual regression)(参见[测试章节](/intro-to-storybook/vue/zh-CN/test/))。
注意固定项目出现在列表中的位置。我们希望固定项目可以渲染在列表的顶端,从而提示用户其优先级。·

<div class="aside">
别忘记提交您的代码到git
💡 别忘记提交您的代码到 git
</div>
25 changes: 17 additions & 8 deletions content/intro-to-storybook/vue/zh-CN/conclusion.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
---
title: '总结'
description: '汇总所有的知识并学习更多Storybook技巧'
description: '汇总所有的知识并学习更多 Storybook 技巧'
---

恭喜!您可以成功创建和发布了您的第一个 Storybook 插件。相信您通过学习 Storybook 的内部运作原理已经了解到了其自定义化的强大之处。[42%](https://2020.stateofjs.com/en-us/technologies/testing/testing_experience_ranking/)的 JavaScript 开发人员已经在使用 Storybook。丰富多样的插件生态系统是其保持人气的重要原因
恭喜!您在 Storybook 创建了第一个 UI。在此过程中,您学习了如何构建,组合,测试和部署 UI 组件。如果您一直跟随教程,那么您的仓库和部署的 Storybook 应该像下面这样

通过创建一个插件来帮助社区定制和自动化 Storybook 吧!
[📕 **GitHub repo: chromaui/learnstorybook-code**](https://github.com/chromaui/learnstorybook-code/tree/vue)
<br/>

从零起步其实很简单。[Addon Kit](https://github.com/storybookjs/addon-kit)提供了您创建插件所需的全部信息。通过[addon API](https://storybook.js.org/docs/react/addons/addons-api),您可以添加新特性,自动化工作流程,集成您喜爱的库。此外,您还可以通过[addon catalog](https://storybook.js.org/addons)与成千名开发者分享这些插件。
[🌎 **Deployed Storybook**](https://master--5ccbe484c994280020b6d128.chromatic.com)

Storybook 对于 React,React Native,Vue,Angular,Svelte 及其他许多框架是一个强大的工具。它有一个蓬勃发展的社区人员和大量的插件。这篇介绍只是触及了其可能性的表面。我们相信,一旦您采用了 Storybook,你将会对其如何构建可靠 UI 的工作效率产生深刻的印象。

## 了解更多

[**Storybook 官方文档**](https://storybook.js.org/docs/vue/get-started/introduction) 提供了更多指南,样例代码和一个知识库。
想要更加深入?这些是一些有用的资源。

- [**Storybook 官方文档**](https://storybook.js.org/docs/vue/get-started/introduction)拥有 API 文档,社区链接和插件游廊。
- [**UI 测试手册**](https://storybook.js.org/blog/ui-testing-playbook/)重点介绍了 Twilio,Adobe,Peloton,和 Shopify 高速团队使用的最佳实践。
- [**回归测试手册**](https://storybook.js.org/tutorials/visual-testing-handbook/)深入描述使用 Storybook 对组件进行视觉测试。免费的 31 页电子书。
- [**Storybook Discord 聊天室**](https://discord.gg/UUt2PJb)使您与 Storybook 社区交流。从其他 Storybook 用户获取或提供帮助。
- [**Storybook 博客**](https://storybook.js.org/blog/)展示最新版本和功能,简化您 UI 组件开发工作流程。

如果您和我们一起编写代码的话,您的仓库应该如此所示: [Sample addon repository](http://github.com/chromaui/learnstorybook-addon-code). 此样例基于 [Storybook Addon Outline](https://github.com/chromaui/storybook-addon-outline)。
## 谁制作了 Storybook 介绍教程

您也可以参考其他的插件,例如 [Pseudo States](https://github.com/chromaui/storybook-addon-pseudo-states), [Dark Mode](https://github.com/hipstersmoothie/storybook-dark-mode) 和所有 [其他的官方插件](https://github.com/storybookjs/storybook/tree/next/code/addons) 并结合 使用它们
文本、代码和制作都由 [Chromatic](https://www.chromatic.com/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook) 提供。教程的灵感来源于 Chromatic 流行的 [GraphQL + React 系列教程](https://www.chromatic.com/blog/graphql-react-tutorial-part-1-6)

感谢与我们共同学习。 通过订阅 Storybook 的邮件列表来获取其他有用的文章或者像此指南一样的信息
想要类似更多的教程和文章?注册 Storybook 的邮件列表

<iframe style="height:400px;width:100%;max-width:800px;margin:0px auto;" src="https://upscri.be/d42fc0?as_embed"></iframe>
4 changes: 2 additions & 2 deletions content/intro-to-storybook/vue/zh-CN/contribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ title: '贡献'
description: '帮助向全世界分享Storybook'
---

我们鼓励给 Storybook 的教程做贡献!如果有一些小的类似语法或标点符号的修改,欢迎开启一个 pull request。如果是一个大的修改,欢迎[添加 issue](https://github.com/chromaui/learnstorybook.com/issues)来进一步讨论。
我们鼓励给 Storybook 的教程做贡献!如果有一些小的类似语法或标点符号的修改,欢迎开启一个 pull request。如果是一个大的修改,欢迎[添加 issue](https://github.com/chromaui/learnstorybook.com/issues) 来进一步讨论。

Storybook 教程主要是由社区来创建和维护的,所以我们需要每一个人的帮助来保持其更新并随着时间的推进逐步完善。我们感谢任何形式的帮助!

## 翻译

我们的目标是希望让所有人都可以学习 Storybook。请帮助将此教程翻译成其他语言。我们尤其欢迎中文和西班牙语的翻译。在[此 issue](https://github.com/chromaui/learnstorybook.com/issues/3)处留言。
我们的目标是希望让所有人都可以学习 Storybook。请帮助将此教程翻译成其他语言。我们尤其欢迎中文和西班牙语的翻译。在[此 issue](https://github.com/chromaui/learnstorybook.com/issues/3) 处留言。
Loading