diff --git a/content/intro-to-storybook/vue/zh-CN/composite-component.md b/content/intro-to-storybook/vue/zh-CN/composite-component.md index eb328e052..203b16cbf 100644 --- a/content/intro-to-storybook/vue/zh-CN/composite-component.md +++ b/content/intro-to-storybook/vue/zh-CN/composite-component.md @@ -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 ``` -下一步我们在 story 文件中创建`Tasklist`的测试状态。 +下一步,在 story 文件中创建 `Tasklist` 的测试状态。 ```js:title=src/components/TaskList.stories.js import TaskList from './TaskList.vue'; @@ -144,12 +144,12 @@ Empty.args = { ```
-装饰器 提供了一种任意包装story的方法。上述例子中我们在default export中使用decorator关键字来添加样式。装饰器也可以给组件添加其他上下文,详见下文。 +Decorators - 装饰器 提供了一种任意包装 story 的方法。上述例子中我们使用默认导出的 decorator 关键字给渲染的组件周围添加一些 margin。但是装饰器也可以给组件添加其他上下文,详见下文。
-通过导入`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 吧。 -注意固定项目出现在列表中的位置。我们希望固定项目可以出现在列表的顶端以提示用户其优先度。 - -## 自动化测试 - -在上一章中我们学习了如何使用 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/))。 +注意固定项目出现在列表中的位置。我们希望固定项目可以渲染在列表的顶端,从而提示用户其优先级。·
-别忘记提交您的代码到git! +💡 别忘记提交您的代码到 git!
diff --git a/content/intro-to-storybook/vue/zh-CN/conclusion.md b/content/intro-to-storybook/vue/zh-CN/conclusion.md index 6d4f3d121..eaee5f651 100644 --- a/content/intro-to-storybook/vue/zh-CN/conclusion.md +++ b/content/intro-to-storybook/vue/zh-CN/conclusion.md @@ -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) +
-从零起步其实很简单。[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 的邮件列表。 diff --git a/content/intro-to-storybook/vue/zh-CN/contribute.md b/content/intro-to-storybook/vue/zh-CN/contribute.md index ec705495f..ee899de4c 100644 --- a/content/intro-to-storybook/vue/zh-CN/contribute.md +++ b/content/intro-to-storybook/vue/zh-CN/contribute.md @@ -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) 处留言。 diff --git a/content/intro-to-storybook/vue/zh-CN/data.md b/content/intro-to-storybook/vue/zh-CN/data.md index 013b7706b..db6d0439c 100644 --- a/content/intro-to-storybook/vue/zh-CN/data.md +++ b/content/intro-to-storybook/vue/zh-CN/data.md @@ -1,120 +1,159 @@ --- title: '绑定数据' tocTitle: '数据' -description: '学习如何在您的UI组件中绑定数据' +description: '学习如何在您的 UI 组件中绑定数据' commit: '4aee860' --- -我们创建了隔离的无状态组件 -这对于 Storybook 来说没问题,但是在真实 app 中只有绑定了数据后这样的组件才有意义。 +到目前为止,我们创建了隔离的无状态组件 - 这对于 Storybook 来说没问题,但是在真实 app 中只有绑定了数据才有意义。 -这份教程不会关注如何构建一个特定的 app,所以我们不会讨论一些构建 app 的细节。但是我们将会花点时间来研究一下通常是如何给一个容器组件绑定数据。 +这份教程不会关注如何构建一个特定的 app,所以我们不会深入这些细节。但是,我们将花点时间来看看容器组件绑定数据的常见模式。 ## 容器组件(Container components) -我们目前编写的`TaskList`组件属于一个“表示型(presentational)”的组件(参照[这篇博客](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)),意味着该组件不会告诉外部它自己的实现。为了将数据放进该组件,我们需要一个“容器”。 +我们目前编写的 `TaskList` 组件是一个“展示型(presentational)”的组件,不会与其实现之外的任何东西进行交流。为了将数据放进该组件,我们需要一个“容器”。 -此示例使用[Vuex](https://vuex.vuejs.org),一个 Vue 默认的数据管理库,来为我们的 app 创建一个直观的数据模型。不过此处的示例同样也适用于其他的数据管理库,例如[Apollo](https://www.apollographql.com/client/)和[MobX](https://mobx.js.org/)。 +此示例使用 [Pinia](https://pinia.vuejs.org/),Vue 默认的数据管理库,来为我们的 app 创建一个直观的数据模型。但是,此处使用的模式同样也适用于其他的数据管理库,例如 [Apollo](https://www.apollographql.com/client/) 和 [MobX](https://mobx.js.org/)。 -首先通过下面的命令安装 vuex: +为项目添加必要的依赖: ```shell -yarn add vuex@next --save +yarn add pinia ``` -在`src/store.js`中我们构建了一个标准的 Vuex store 来处理一些可能的改变状态的操作。 +首先,我们在 `src/store.js` 中我们构建了一个简单(故意保持简单)的 Pinia store 来处理一些可能改变状态的操作。 ```js:title=src/store.js -import { createStore } from 'vuex'; - -export default createStore({ - state: { - tasks: [ - { id: '1', title: 'Something', state: 'TASK_INBOX' }, - { id: '2', title: 'Something more', state: 'TASK_INBOX' }, - { id: '3', title: 'Something else', state: 'TASK_INBOX' }, - { id: '4', title: 'Something again', state: 'TASK_INBOX' }, - ], - }, - mutations: { - ARCHIVE_TASK(state, id) { - state.tasks.find(task => task.id === id).state = 'TASK_ARCHIVED'; +/* A simple Pinia store/actions implementation. + * A true app would be more complex and separated into different files. + */ +import { defineStore } from 'pinia'; + +/* + * The initial state of our store when the app loads. + * Usually, you would fetch this from a server. Let's not worry about that now + */ +const defaultTasks = [ + { id: '1', title: 'Something', state: 'TASK_INBOX' }, + { id: '2', title: 'Something more', state: 'TASK_INBOX' }, + { id: '3', title: 'Something else', state: 'TASK_INBOX' }, + { id: '4', title: 'Something again', state: 'TASK_INBOX' }, +]; + +/* + * The store is created here. + * You can read more about Pinia defineStore in the docs: + * https://pinia.vuejs.org/core-concepts/ + */ +export const useTaskStore = defineStore({ + id: 'taskbox', + state: () => ({ + tasks: defaultTasks, + status: 'idle', + error: null, + }), + actions: { + archiveTask(id) { + const task = this.tasks.find((task) => task.id === id); + if (task) { + task.state = 'TASK_ARCHIVED'; + } }, - PIN_TASK(state, id) { - state.tasks.find(task => task.id === id).state = 'TASK_PINNED'; + pinTask(id) { + const task = this.tasks.find((task) => task.id === id); + if (task) { + task.state = 'TASK_PINNED'; + } }, }, - actions: { - archiveTask({ commit }, id) { - commit('ARCHIVE_TASK', id); - }, - pinTask({ commit }, id) { - commit('PIN_TASK', id); + getters: { + getFilteredTasks: (state) => { + const filteredTasks = state.tasks.filter( + (t) => t.state === 'TASK_INBOX' || t.state === 'TASK_PINNED' + ); + return filteredTasks; }, }, }); ``` -下一步, 我们需要更新应用的入口文件 (`src/main.js`)以帮助我们更轻松的将 store 集成到组件结构中: - -```diff:title=src/main.js -import { createApp } from 'vue'; - -import App from './App.vue'; +然后我们将通过读取 store 中的数据来更新 `TaskList`。首先,将我们现有的演示版本移入文件 `src/components/PureTaskList.vue` 中(将组件重命名为 `PureTaskList`),并用容器包裹它。 -+ import store from './store'; +在 `src/components/PureTaskList.vue` 文件中: -- createApp(App).mount('#app') -+ createApp(App).use(store).mount('#app') -``` - -当我们在 app 中使用了 store 后,我们需要更新顶层的组件(`src/App.vue`)来显示`TaskList`组件。 - -```diff:title=src/App.vue +```html:title=src/components/PureTaskList.vue - - - -``` - -接下来我们更新`TaskList`让其读取 store 中的数据。首先让我们将目前的表示型版本移动到文件`src/components/PureTaskList.vue`中(重命名组件为`PureTaskList`),并用容器包裹起来。 - -在`src/components/PureTaskList.vue`中: - -```html:title=src/components/PureTaskList.vue - - - ``` -In `src/components/TaskList.vue`: +在 `src/components/TaskList.vue` 文件中: ```html:title=src/components/TaskList.vue ``` -将`TaskList`的表示型版本分离开的原因是,这使得我们的测试和隔离更加容易。同时因为它不依赖 store,所以从测试的角度来说将变的更加容易。重命名`src/components/TaskList.stories.js`为`src/components/PureTaskList.stories.js`,并在我们的 story 中使用表示型版本: +将 `TaskList` 的展示版本分离的原因是测试和隔离更加方便。因为它不依赖于 store 存在,所以从测试角度来说更易处理。让我们将 `src/components/TaskList.stories.js` 重命名为 `src/components/PureTaskList.stories.js` 并确保在 story 中使用展示版本: ```diff:title=src/components/PureTaskList.stories.js + import PureTaskList from './PureTaskList.vue'; @@ -224,35 +264,8 @@ Empty.args = { /> -同样的,我们也需要在 Jest 测试中使用`PureTaskList`: - -```diff:title=tests/unit/PureTaskList.spec.js -import { mount } from '@vue/test-utils'; - -- import TaskList from '../../src/components/TaskList.vue'; - -+ import PureTaskList from '../../src/components/PureTaskList.vue'; - -//👇 Our story imported here -- import { WithPinnedTasks } from '../src/components/TaskList.stories.js'; - -+ import { WithPinnedTasks } from '../../src/components/PureTaskList.stories'; - -it('renders pinned tasks at the start of the list', () => { - // render PureTaskList -- const wrapper = mount(TaskList, { -- //👇 Story's args used with our test -- propsData: WithPinnedTasks.args, -- }); -+ const wrapper = mount(PureTaskList, { -+ propsData: WithPinnedTasks.args, -+ }); - - const firstPinnedTask = wrapper.find('.list-item:nth-child(1).TASK_PINNED'); - expect(firstPinnedTask).not.toBe(null); -}); -``` -
-💡 您需要更新快照来应对上述的修改。加上-u重新运行测试命令来更新快照。同时别忘记提交您的代码! +💡 别忘记提交您的代码到 git!
+ +现在我们从 Pinia store 中获取了一些实际数据来填充组件,我们可以在 `src/App.vue` 中绑定并渲染它。不用担心,我们将在下一个章节讨论这个问题。 diff --git a/content/intro-to-storybook/vue/zh-CN/deploy.md b/content/intro-to-storybook/vue/zh-CN/deploy.md index 20057f132..76d2c7955 100644 --- a/content/intro-to-storybook/vue/zh-CN/deploy.md +++ b/content/intro-to-storybook/vue/zh-CN/deploy.md @@ -1,31 +1,31 @@ --- title: '部署Storybook' tocTitle: '部署' -description: '学习如何部署Storybook' +description: '学习如何线上部署Storybook' commit: 'ae564af' --- -在此教程中,我们在自己的电脑上构建了组件。某些时候我们还需要和组员分享我们的工作以获得其他人的反馈。让我们部署 Storybook 让其他成员审查实现好的 UI 吧。 +在此教程中,我们在我们本地开发环境构建了组件。某些时候,我们还需要和组员分享我们的工作以获得其他人的反馈。让我们部署 Storybook 到线上,帮助同事对 UI 实现进行 review。 ## 导出为一个静态应用 -为了部署 Storybook 我们首先需要将其导出为一个静态 web 应用。Storybook 已经集成了此功能并已预配置完成,我们只需要将[开始吧章节](/intro-to-storybook/vue/zh-CN/get-started)中的脚本更新即可。 +为了部署 Storybook 我们首先需要将其导出为一个静态 web 应用。Storybook 已经集成了此功能并进行了预配置。 -运行`yarn build-storybook`会在`storybook-static`目录下输出一个静态 Storybook,用于部署在任何可以托管静态网站的服务中。 +运行 `yarn build-storybook` 将会在 `storybook-static` 目录下输出一个静态 Storybook,可以被部署在任何静态网站托管服务。 ## 发布 Storybook -此教程使用Chromatic,一个 Storybook 团队开发的免费发布服务。它使得我们可以安全的将我们的 Storybook 部署到云端。 +此教程使用 Chromatic,一个 Storybook 维护者提供的免费发布服务。它使得我们可以安全的将我们的 Storybook 部署到云端。 ### 在 GitHub 中创建一个仓库 -在我们开始之前,我们首先需要将本地代码和一个远程版本控制服务关联起来。在[开始吧章节](/intro-to-storybook/vue/zh-CN/get-started)中,Vue CLI 初始化时已经为我们创建了一个本地仓库。也就是说我们已经拥有了一系列可以提交到远程仓库的 commits。 +在我们开始之前,我们首先需要将本地代码和一个远程版本控制服务关联起来。当我们在[开始吧章节](/intro-to-storybook/vue/zh-CN/get-started)中构建我们的项目时,已经初始化了了一个本地仓库。在此阶段,我们已经拥有了一系列可以推送到远程仓库的 commits。 -进入 GitHub 然后创建一个新的仓库,[这里](https://github.com/new)。和我们的本地项目一样,命名为“taskbox”。 +进入 GitHub 然后在[这里](https://github.com/new)创建一个新的仓库。和我们的本地项目一样,命名为“taskbox”。 ![设置GitHub](/intro-to-storybook/github-create-taskbox.png) -在新的仓库中,获得仓库的 URL 并执行下述命令将其应用于您的 git 项目中: +在新的仓库中,获得仓库的 URL 并执行下述命令将其添加到您的 git 项目中: ```shell git remote add origin https://github.com//taskbox.git @@ -39,15 +39,15 @@ git push -u origin main ### 使用 Chromatic -追加下述包到 development dependency 中: +将包作为 development dependency 添加: ```shell yarn add -D chromatic ``` -安装完成之后,使用 GitHub 账号[登录 Chromatic](https://www.chromatic.com/start/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook)(Chromatic 只会请求很轻量级的权限)。然后我们创建一个项目名叫“taskbox”,并和我们已经设置好的 GitHub 仓库同步。 +安装完成之后,使用 GitHub 账号[登录 Chromatic](https://www.chromatic.com/start/?utm_source=storybook_website&utm_medium=link&utm_campaign=storybook)(Chromatic 只会请求很轻量级的权限)。然后我们创建一个项目名叫“taskbox”,并将我们已经设置好的 GitHub 仓库同步。 -在 collaborators 下点击`Choose GitHub repo`并选择您的仓库。 +在 collaborators 下点击 `Choose GitHub repo` 并选择您的仓库。 -复制为您的项目所创建的唯一`project-token`。使用下述的命令来构建并部署我们的 Storybook。请确保替换`project-token`为您项目的 token。 +复制为您的项目所创建的唯一 `project-token`。然后在命令行执行以下命令来构建并部署我们的 Storybook。请确保将 `project-token` 替换为您项目的 token。 ```shell yarn chromatic --project-token= @@ -64,21 +64,21 @@ yarn chromatic --project-token= ![Chromatic running](/intro-to-storybook/chromatic-manual-storybook-console-log.png) -执行完成后,您会收到一个已经发布的 Storybook 对应的链接`https://random-uuid.chromatic.com`。请与您的团队分享链接并获得反馈。 +执行完成后,您会收到一个已经发布的 Storybook 对应的链接 `https://random-uuid.chromatic.com`。请与您的团队分享链接并获得反馈。 ![Storybook deployed with chromatic package](/intro-to-storybook/chromatic-manual-storybook-deploy-6-0.png) -太好了!现在我们只需要一条命令就可以发布我们的 Storybook,但是每次我们需要获取团队关于 UI 的反馈时,我们都要重复的手动执行一次命令。理想情况下,我们希望每次我们提交代码时都可以同步发布最新版本的组件。也就是持续交付 Storybook。 +太好了!现在我们只需要一条命令就可以发布我们的 Storybook,但是每次我们需要获取团队关于 UI 的反馈时,我们都要重复的手动执行一次命令。理想情况下,我们希望每次我们提交代码时都可以同步发布最新版本的组件。我们需要持续部署 Storybook。 ## 使用 Chromatic 持续部署 -现在我们的项目存储在 GitHub 仓库中,所以我们可以使用一个持续集成(CI)服务来帮助我们自动部署 Storybook。[GitHub Actions](https://github.com/features/actions)是一个免费的集成在 GitHub 中的 CI 服务,可以帮我们轻松实现自动发布。 +现在我们的项目存储在 GitHub 仓库中,所以我们可以使用一个持续集成(CI)服务来帮助我们自动部署 Storybook。[GitHub Actions](https://github.com/features/actions) 是一个免费的集成在 GitHub 中的 CI 服务,可以帮我们轻松实现自动发布。 ### 添加一个 GitHub Action 来部署 Storybook -在我们项目的根目录下,创建一个叫做`.github`的文件夹并在其中创建另一个叫做`workflows`的文件夹。 +在我们项目的根目录下,创建一个叫做 `.github` 的文件夹,并在其中创建另一个叫做 `workflows` 的文件夹。 -创建一个叫`chromatic.yml`的文件,内容如下。替换`CHROMATIC_PROJECT_TOKEN`为您项目所持有的 token。 +如下所示创建一个叫 `chromatic.yml` 的文件: ```yaml:title=.github/workflows/chromatic.yml # Workflow name @@ -100,12 +100,12 @@ jobs: - uses: chromaui/action@v1 # Options required for Chromatic's GitHub Action with: - #👇 Chromatic projectToken, see https://storybook.js.org/tutorials/intro-to-storybook/vue/zh-cn/deploy/ to obtain it + #👇 Chromatic projectToken, see https://storybook.js.org/tutorials/intro-to-storybook/vue/zh-CN/deploy/ to obtain it projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }} ``` -

出于文章简洁性的考虑,GitHub secrets并没有被提及。Secrets实际上是GitHub提供的安全相关环境变量,这样我们就不需要硬编码project-token了。

+

出于文章的简洁起见,GitHub secrets 并没有被提及。Secrets 实际上是 GitHub 提供的安全环境变量,这样我们就不需要硬编码 project-token 了。

### 提交 action @@ -131,9 +131,9 @@ git push origin main ![Chromatic user dashboard](/intro-to-storybook/chromatic-user-dashboard.png) -点击最新的 build,一般是在最上方。 +点击最新的构建版本,一般是最上面的那个。 -然后点击`View Storybook`按钮查看您最新版本的 Storybook。 +然后点击 `View Storybook` 按钮查看您最新版本的 Storybook。 ![Storybook link on Chromatic](/intro-to-storybook/chromatic-build-storybook-link.png) diff --git a/content/intro-to-storybook/vue/zh-CN/get-started.md b/content/intro-to-storybook/vue/zh-CN/get-started.md index 15e57ff65..f954b768c 100644 --- a/content/intro-to-storybook/vue/zh-CN/get-started.md +++ b/content/intro-to-storybook/vue/zh-CN/get-started.md @@ -5,7 +5,7 @@ description: '在你的开发环境下配置Storybook' commit: '68c6ac9' --- -Storybook 是在开发模式下与您的应用程序一同运行的。它可以帮助您构建 UI 组件,并且将其与您应用程序中的业务逻辑和上下文分离开来。这份教程适用于 Vue;其他的则适用于[React](/intro-to-storybook/react/en/get-started),[React Native](/intro-to-storybook/react-native/en/get-started/),[Angular](/intro-to-storybook/angular/en/get-started),[Svelte](/intro-to-storybook/svelte/en/get-started)和[Ember](/intro-to-storybook/ember/en/get-started)。 +Storybook 是在开发模式下与您的应用程序一同运行的。它可以帮助您构建 UI 组件,并且将其与您应用程序中的业务逻辑和上下文分离开来。这份教程适用于 Vue;其他的则适用于[React](/intro-to-storybook/react/zh-CN/get-started),[React Native](/intro-to-storybook/react-native/en/get-started/),[Angular](/intro-to-storybook/angular/en/get-started),[Svelte](/intro-to-storybook/svelte/en/get-started)和[Ember](/intro-to-storybook/ember/en/get-started)。 ![Storybook and your app](/intro-to-storybook/storybook-relationship.jpg) @@ -58,16 +58,22 @@ yarn serve git init ``` -下一行: +接着是: ```shell git add . ``` -最后: +然后: ```shell git commit -m "first commit" ``` +最后: + +```shell +git branch -M main +``` + 让我们创建第一个组件吧! diff --git a/content/intro-to-storybook/vue/zh-CN/screen.md b/content/intro-to-storybook/vue/zh-CN/screen.md index 980d63ffa..85060473a 100644 --- a/content/intro-to-storybook/vue/zh-CN/screen.md +++ b/content/intro-to-storybook/vue/zh-CN/screen.md @@ -5,13 +5,13 @@ description: '用组件构建一个页面' commit: '97f73c6' --- -我们关注了如何自底向上的构建 UI;从小规模起步逐渐增加复杂性。这样我们可以独立的开发每个组件,弄清其所需的数据,以及如何将其应用于 Storybook。所有的这些操作都不需要我们启动一个服务器或者创建一个页面! +我们关注于自底向上构建 UI,从小规模起步逐渐增加复杂性。这样我们可以独立的开发每个组件,弄清其所需的数据,以及如何将其应用于 Storybook。所有的这些操作都不需要我们启动一个服务器或者创建一个页面! 在本章节中我们继续增加复杂性,我们将组件组合成一个页面并在 Storybook 中应用它。 ## 嵌套的容器组件 -因为我们的应用十分简单,所以我们要构建的页面也十分简单,只需要给`TaskList`容器组件(使用 Vuex 中的数据)添加一些布局,并使用 store 创建一个顶层的`error`字段(例如当我们遇到一些服务器连接问题时我们可以设置此字段)。在`src/components/`文件夹中创建一个表示型组件`PureInboxScreen.vue`。 +因为我们的应用十分简单,所以我们要构建的页面也十分简单,只需要给 `TaskList` 容器组件(使用 Pinia 中的数据)添加一些布局,并使用 store 创建一个顶层的 `error` 字段(例如当我们遇到一些服务器连接问题时我们可以设置此字段)。在 `src/components/` 文件夹中创建一个展示型组件 `PureInboxScreen.vue`。 ```html:title=src/components/PureInboxScreen.vue ``` -接着我们创建一个容器,为`src/components/InboxScreen.vue`中的`PureInboxScreen`抓取数据: +接着,我们可以创建一个容器,为 `src/components/InboxScreen.vue` 中的 `PureInboxScreen` 抓取数据: ```html:title=src/components/InboxScreen.vue ``` -同时我们修改`App`组件让其渲染`InboxScreen`(最终我们会使用路由来决定渲染哪个页面,现在我们暂时不需要担心这些): +下一步,我们需要修改应用的入口文件(`src/main.js`),这样我们就可以快速的将 store 绑定在组件的层次结构中: + +```diff:title=src/main.js +import { createApp } from 'vue'; ++ import { createPinia } from 'pinia'; + +import App from './App.vue'; + +- createApp(App).mount('#app') ++ createApp(App).use(createPinia()).mount('#app'); +``` + +我们同样需要修改 `App` 组件让其渲染 `InboxScreen`(最终,我们将会使用路由来决定渲染哪个页面,现在我们暂时不需要关注这些): ```diff:title=src/App.vue ``` -但是,当在 Storybook 中渲染 story 时事情就变得有趣起来了。 +然而,当在 Storybook 中渲染 story 时事情就变得有趣起来了。 -正如我们之前所见的,`TaskList`组件作为一个**容器**负责渲染`PureTaskList`这个表示型组件。我们定义了容器组件不能简单的被隔离式地渲染;我们希望传递给它们一些上下文或者让它们和某个服务进行通信。这表示如果我们希望在 Storybook 中渲染一个容器,我们必须模拟(例如提供一个虚拟版本)一个其所需的上下文或者服务。 +正如我们之前所见的,`TaskList` 组件作为一个**容器**负责渲染 `PureTaskList` 这个展示型组件。根据定义,容器组件不能简单的被隔离式地渲染;它们被期望传递一些上下文或者与某个服务进行通信。这意味着如果我们希望在 Storybook 中渲染一个容器,我们必须 mock(例如提供一个虚拟版本)一个其所需的上下文或者服务。 -当在 Storybook 中处理`TaskList`时,我们可以简单的通过渲染`PureTaskList`来回避这个问题。我们这里同样通过渲染`PureInboxScreen`来解决此问题。 +当在 Storybook 中处理 `TaskList` 时,我们可以通过简单的渲染 `PureTaskList` 并避开容器来回避这个问题。我们将做类似的处理并在 Storybook 中渲染 `PureInboxScreen`。 -但是这里我们遇到的问题是,尽管`PureInboxScreen`本身是表示型的,但它的子组件`TaskList`却并不是。某种意义上来说`PureIndexScreen`被“容器性”所污染了。所以当我们设置`src/components/PureInboxScreen.stories.js`时: +但是,对于 `PureInboxScreen` 有一个问题。尽管 `PureInboxScreen` 自身是展示型的,但是其子组件 `TaskList` 不是。某种意义上来说,`PureInboxScreen` 已经被“容器性”污染了。所以当我们在 stroy 中设置 `src/components/PureInboxScreen.stories.js` 时: ```js:title=src/components/PureInboxScreen.stories.js import PureInboxScreen from './PureInboxScreen.vue'; @@ -136,47 +148,30 @@ export const Error = Template.bind({}); Error.args = { error: true }; ``` -我们发现尽管`error`的 story 运作正常,但因为`TaskList`没有连接相对应的 Vuex store,所以`default`的 story 出错了。(类似的问题同样出现在`PureIndexSreen`的单元测试中)。 +我们可以发现尽管 `error` story 运作正常,但因为 `TaskList` 没有连接相对应的 Pinia store,所以 `default` 的 story 出错了。(你同样会遇到类似的问题当试图对 `PureInboxScreen` 进行单元测试时)。 -![Broken inbox](/intro-to-storybook/broken-inboxscreen-vue.png) +![Broken inbox](/intro-to-storybook/broken-inboxscreen-vue-pinia.png) 回避此问题的一种方法是永远不要在您应用中渲染容器组件,除非该组件是最高层组件,并且在最高层组件中自顶而下的传递所有需要的数据。 但是,开发人员**将会**不可避免的在下层结构中渲染容器组件。如果我们想要在 Storybook 中渲染应用中大部分或者全部的组件(我们想!),我们仍需要一个解决方案。
-💡 需要说明的是,自顶而下的传递数据是一种合理的解决方案,尤其是使用GraphQL时。这也是我们在Chromatic中构建超过800个story的方式。 +💡 需要说明的是,自顶而下的传递数据是一种合理的解决方案,尤其是使用 GraphQL 时。这也是我们在 Chromatic 中构建超过 800 个 story 的方式。
## 在 story 中提供上下文 -好消息是对于 story 来说在`PureInboxScreen`中使用 Vuex store 十分容易!我们可以在 story 文件中创建一个新的 store 作为上下文: +好消息是在 story 中的 `PureInboxScreen` 中使用 Pinia store 十分容易!我们可以更新 story 并直接导入在上一章中创建的 Pinia store。 ```diff:title=src/components/PureInboxScreen.stories.js + import { app } from '@storybook/vue3'; -+ import { createStore } from 'vuex'; ++ import { createPinia } from 'pinia'; -import PureInboxScreen from './PureInboxScreen.vue'; ++ app.use(createPinia()); -+ 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); +import PureInboxScreen from './PureInboxScreen.vue'; export default { title: 'PureInboxScreen', @@ -199,7 +194,7 @@ export const Error = Template.bind({}); Error.args = { error: true }; ``` -使用其他的,例如[Apollo](https://www.npmjs.com/package/apollo-storybook-decorator),[Relay](https://github.com/orta/react-storybooks-relay-container) 或者别的库时也可以利用类似的方法模拟上下文。 +在其它库中也存在类似的方法提供模拟上下文,例如 [Apollo](https://www.npmjs.com/package/apollo-storybook-decorator),[Relay](https://github.com/orta/react-storybooks-relay-container) 或者其他库。 在 Storybook 中遍历状态让我们可以轻松的测试应用的正确性: @@ -211,9 +206,116 @@ Error.args = { error: true }; /> +## 交互测试 + +到目前为止,我们已经从头创建了一个功能齐全的应用,从简单组件到页面,并不断通过 story 来测试每个变动。但是每个新建的 story 需要手动检查其他所有 story 来确保 UI 没有崩溃。造成很多额外的工作。 + +难道我们就不能将该流程自动化并自动对组件进行交互测试吗? + +### 通过 play 函数编写一个交互测试 + +Storybook 的 [`play`](https://storybook.js.org/docs/vue/writing-stories/play-function) 和 [`@storybook/addon-interactions`](https://storybook.js.org/docs/vue/writing-tests/interaction-testing) 帮助我们解决上述问题。一个 play 函数包含 story 渲染之后的一小段代码。 + +play 函数帮助我们验证当 task 更新后 UI 的变化。它使用与框架无关的 DOM API,这意味着不管什么框架,我们都可以通过编写 story 的 play 函数来与 UI 进行交互并模拟人类行为。 + +`@storybook/addon-interactions` 帮助我们在 Storybok 中可视化我们的测试,提供一个循序渐进的流程。它还提供了一些方便的 UI 控件,可以暂停、恢复、倒带并逐步完成每个交互。 + +让我们来看看它的实际应用,更新你新创建的 `PureInboxScreen` 文件,并通过添加以下内容来创建组件交互: + +```diff:title=src/components/PureInboxScreen.stories.js +import { app } from '@storybook/vue3'; + ++ import { fireEvent, within } from '@storybook/testing-library'; + +import { createPinia } from 'pinia'; + +app.use(createPinia()); + +import PureInboxScreen from './PureInboxScreen.vue'; + +export default { + title: 'PureInboxScreen', + component: PureInboxScreen, +}; + +const Template = (args) => ({ + components: { PureInboxScreen }, + setup() { + return { + args, + }; + }, + template: '', +}); + +export const Default = Template.bind({}); + +export const Error = Template.bind({}); +Error.args = { error: true }; + ++ export const WithInteractions = Template.bind({}); ++ WithInteractions.play = async ({ canvasElement }) => { ++ const canvas = within(canvasElement); ++ // Simulates pinning the first task ++ await fireEvent.click(canvas.getByLabelText('pinTask-1')); ++ // Simulates pinning the third task ++ await fireEvent.click(canvas.getByLabelText('pinTask-3')); ++ }; +``` + +检查你最新创建的 story。点击 `Interactions` 面板来查看在 story play 函数中的交互列表。 + + + +### 使用 test runner 进行自动化测试 + +通过 Storybook 的 play 函数,我们可以避开之前的问题,使我们能与 UI 进行交互,并在我们更新 task 时快速检测其变化 - 无需额外的手动工作确保 UI 的一致性。 + +但是,当我们仔细查看 Storybook,我们可以看到只有在查看 story 时才会运行交互测试。因此,我们在进行变更的时候仍然需要查看每个 story 从而运行所有的检查。我们就不能让它更加自动化么? + +好消息是可以的!Storybook 的 [test runner](https://storybook.js.org/docs/vue/writing-tests/test-runner) 可以完成那样的操作。这是基于 [Playwright](https://playwright.dev/) 的一个独立的库 - 运行所有的交互测试并捕获 story 的错误。 + +让我们看看它时如何工作的!运行以下命令进行安装: + +```shell +yarn add --dev @storybook/test-runner +``` + +下一步,更新 `package.json` 的 `scripts` 并添加一个新的测试任务: + +```json:clipboard=false +{ + "scripts": { + "test-storybook": "test-storybook" + } +} +``` + +最后,在 Storybook 运行的情况下,打开新的控制台界面并运行以下命令: + +```shell +yarn test-storybook --watch +``` + +
+💡 使用 play 函数的交互测试时测试 UI 组件的绝佳方式。它能做的远比目前看到的多;我们推荐您阅读官方文档进行深入了解。 +
+为了深入了解测试,请查看测试手册。它涵盖了缩放前端(scaled-front-end)团队所使用的测试策略,以增强您的开发工作流程。 +
+ +![Storybook test runner successfully runs all tests](/intro-to-storybook/storybook-test-runner-execution.png) + +成功了!现在我们拥有一个工具,可以帮助我们检查所有的 story 渲染是否出错,并且所有的断言是否自动通过。更重要的是,如果一个测试失败了,它将会提供一个链接,该链接可以在浏览器打开失败的 story。 + ## 组件驱动开发 -我们以`Task`起步,进一步实现`TaskList`,现在我们创建了整个页面的 UI。我们的`InboxScreen`包括了一个嵌套容器组件,以及一系列相关联的 story。 +我们以 `Task` 起步,进一步实现了 `TaskList`,现在我们创建了整个页面的 UI。我们的 `InboxScreen` 包括了一个嵌套容器组件,以及一系列相关联的 story。 -[**组件驱动开发**](https://www.componentdriven.org/)让您可以一步步的在升级组件结构的同时扩展应用的复杂性。同时也使得我们可以更专注于开发本身,并提高对所有可能的 UI 排列组合的覆盖率。简而言之,CDD 帮助您创建了高质量以及更复杂的 UI。 +[**组件驱动开发**](https://www.componentdriven.org/)让您可以一步步的在升级组件结构的同时扩展应用的复杂性。同时也使得我们可以更专注于开发本身,并提高对所有可能的 UI 排列组合的覆盖率。简而言之,CDD 帮助您创建了高质量以及更复杂的交互界面。 我们还没有完全结束 - 光创建 UI 是不够的。我们仍需要保证应用的耐用性。 diff --git a/content/intro-to-storybook/vue/zh-CN/simple-component.md b/content/intro-to-storybook/vue/zh-CN/simple-component.md index 21884274d..53592bf3b 100644 --- a/content/intro-to-storybook/vue/zh-CN/simple-component.md +++ b/content/intro-to-storybook/vue/zh-CN/simple-component.md @@ -16,42 +16,42 @@ commit: '6cb2cb5' - `title` – 描述任务的字符串 - `state` - 当前任务所在列表,以及其是否被选中? -在我们构建`Task`之前,首先我们根据上述的草图编写测试所需的状态(state)。然后我们使用 Storybook 模拟数据并独立的构建组件。我们可以“视觉测试”自己设定好状态的组件外观。 - -这个过程有点像[驱动测试开发](https://en.wikipedia.org/wiki/Test-driven_development) (TDD) 所以我们可以称之为“[Visual TDD](https://www.chromatic.com/blog/visual-test-driven-development)” +在我们构建`Task`之前,首先我们根据上述的草图编写测试所需的状态(state)。然后我们使用 Storybook 模拟数据并独立的构建组件。我们可以“视觉测试”组件在每个状态下的外观。 ## 开始设置 首先,让我们创建 task 组件以及它相关的 story 文件:`src/components/Task.vue` 和 `src/components/Task.stories.js`。 -首先我们使用已知将会用到的属性为基础实现一个最基本的`Task`: +我们将从 `Task` 的基础实现开始,简单传入我们所需要的属性和需要对任务执行的两个操作(在列表之间移动它): ```html:title=src/components/Task.vue ``` -如上所示,我们直接基于现有的 Todos 应用的 HTML 结构创建一个`Task`。 +如上所示,我们基于 Todos 应用程序现有的 HTML 结构为 `Task` 直接渲染 markup。 -如下,我们在 story 文件中创建 Task 的三个不同测试状态: +如下所示,我们在 story 文件中创建 Task 的三个不同测试状态: ```js:title=src/components/Task.stories.js import Task from './Task.vue'; @@ -88,7 +88,6 @@ Default.args = { id: '1', title: 'Test Task', state: 'TASK_INBOX', - updatedAt: new Date(2018, 0, 1, 9, 0), }, }; @@ -112,43 +111,47 @@ Archived.args = { Storybook 有两个基本的组织级别:组件和他的 story。可以将每个 story 视作其组件的排列组合。您可以根据需要给每一个组件创建任意个 story。 - **组件** + - Story + - Story + - Story -我们创建了一个`default`来提示 Storybook 我们正在文档化的组件: +为了告诉 Storybook 我们正在文档化的组件,我们创建了一个 `default` 的默认导出,其中包含: -- `component` -- 组件本身, -- `title` -- 在 Storybook 应用侧边栏的显示, -- `excludeStories` -- story 本身需要但是不用在 Storybook 应用中渲染的信息。 +- `component` -- 组件本身 +- `title` -- 如何在 Storybook 应用侧边栏中引用组件 +- `excludeStories` -- story 本身需要但是不用在 Storybook 应用中渲染的信息 +- `argTypes` -- 在每个 story 中具体说明 [args](https://storybook.js.org/docs/vue/api/argtypes) 的行为 -我们为每一个我们需要测试的状态导出一个函数,以此来定义我们的 story。Story 实际上就是一个根据给定的状态返回已渲染元素的函数---就像是[函数式组件](https://vuejs.org/v2/guide/render-function.html#Functional-Components)那样。 +为了定义我们的 stories,我们为每个测试状态导出一个函数用于生成一个 story。Story 实际上就是一个根据给定的状态返回已渲染元素(例如:一个具有一组 props 的类组件)的函数---就像是[函数式组件](https://vuejs.org/v2/guide/render-function.html#Functional-Components)。 -因为我们的组件存在多种排列组合,所以设置一个`Template`变量不失为一种便捷的做法。使用这样的模式来创建您的 Story 可以大量减少代码量和维护成本。 +因为我们的组件存在多种排列组合,所以设置一个 `Template` 变量不失为一种便捷的做法。使用这样的模式来创建您的 Story 可以大量减少代码量和维护成本。
💡 Template.bind({})标准JavaScript 中用来复制函数的技术。我们使用这项技术保证了在使用同一份实现的同时,让每一个导出的story可以配置自己的属性。
-Arguments 或者简写[`args`](https://storybook.js.org/docs/vue/writing-stories/args) , 让我们可以在不重启 Storybook 的前提下实时编辑我们的组件。只要 [`args`](https://storybook.js.org/docs/vue/writing-stories/args) 的值被修改我们的组件也会相应的更新。 +Arguments 或者简写 [`args`](https://storybook.js.org/docs/vue/writing-stories/args) ,让我们可以在不重启 Storybook 的前提下实时编辑我们的组件。一旦 [`args`](https://storybook.js.org/docs/vue/writing-stories/args) 的值被修改我们的组件也会进行相应的更新。 -我们创建了一个最基本的`task`变量来描述 task 组件应该呈现的样子。这通常是根据真实数据来进行建模的。同时,`导出`此 task 也让我们在以后编写其他 story 时可以复用它,详见下文。 +当创建一个 story,我们使用一个基本的 `task` 变量来构建 task 组件所期望的形状。通常是根据真实数据来进行建模的。 -`action()`使我们可以创建一个回调函数,当点击事件触发时 Storybook UI 的**actions**面板会显示结果。所以如果我们创建了一个 pin 按钮,我们就可以通过面板清楚的知道按钮是否被成功点击了。 +`actions` 允许我们创建 Storybook UI 的 **actions** 面板被点击时显示的回调。因此当我们构建一个 pin button 时,我们能够在 UI 上验证 button 点击是否成功。 -考虑到我们需要为组件的每一个排列组合都传入同样的 actions,通常的便捷做法是将他们合并到一个`actionsData`变量中,并传入给每一个定义好的 story 中。 +由于我们需要将相同的一组 actions 传入到组件的所有排列组合中,将它们合并到一个 `actionsData` 变量中,并在我们每次定义 story 的时候传入将会变得非常方便。 -值得一提的是当我们将组件所需的操作都合并到`actionsData`之后,我们可以在其他组件复用此组件时,让其他组件的 story 也可以复用`导出`的`actionsData`,详见下文。 +值得一提的是当我们将组件所需的操作都合并到 `actionsData` 之后,我们可以在其他组件复用此组件时,让其他组件的 story 也可以复用 `export` 的 `actionsData`,详见下文。
-💡 Actions帮助您在隔离构建UI组件时验证交互。 通常情况下您不会持有应用程序上下文中函数和状态的访问权限。请使用action()保存它们。 +💡 Actions 帮助您在独立构建UI组件时验证交互。通常情况下您无法访问应用程序上下文中的函数和状态。请使用 action() 将他们插入。
## 配置 -我们需要对 Storybook 的配置做几处修改,这样其不仅可以识别到近期创建的 story,同时还允许我们可以使用应用的 CSS 文件(在`sec/index.css`)。 +我们需要对 Storybook 的配置做几处修改,这样不仅可以识别到近期创建的 story,并且允许我们使用应用程序的 CSS 文件(位于 `src/index.css`)。 -如下修改您的 Storybook 配置文件(`.storybook/main.js`): +首先,修改您的 Storybook 配置文件(`.storybook/main.js`) 为以下内容: ```diff:title=.storybook/main.js module.exports = { @@ -157,11 +160,23 @@ module.exports = { - '../src/**/*.stories.@(js|jsx|ts|tsx)' - ], + stories: ['../src/components/**/*.stories.js'], - addons: ['@storybook/addon-links', '@storybook/addon-essentials'], + staticDirs: ['../public'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + ], + framework: '@storybook/vue3', + core: { + builder: '@storybook/builder-webpack5', + }, + features: { + interactionsDebugger: true, + }, }; ``` -完成上述的修改后,如下所示修改您`.storybook`文件夹中的`preview.js` : +完成上述的修改后,修改位于 `.storybook`文件夹中的 `preview.js` 为以下内容: ```diff:title=.storybook/preview.js + import '../src/index.css'; @@ -169,14 +184,20 @@ module.exports = { //👇 Configures Storybook to log the actions( onArchiveTask and onPinTask ) in the UI. export const parameters = { actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, }; ``` -[`parameters`](https://storybook.js.org/docs/vue/writing-stories/parameters) 通常用来控制 Storybook 中特性和插件的行为。在我们的例子中我们使用它们来配置`actions`(模拟的回掉)该如何被处理。 +[`parameters`](https://storybook.js.org/docs/vue/writing-stories/parameters) 通常用来控制 Storybook 功能和插件的行为。在我们的例子中,我们使用它们来配置 `actions` (模拟回调)如何被处理。 -`actions`允许我们创建回调来决定当点击事件发生时 Storybook UI 的**actions**面板将如何显示。这样当我们创建了一个 pin 按钮后,我们就可以在测试 UI 中查看该按钮是否被点击成功了。 +`actions` 允许我们创建 Storybook UI 的 **actions** 面板被点击时显示的回调。因此当我们构建一个 pin button 时,我们能够在 UI 上验证 button 点击是否成功。 -完成了上述配置后重启 Storybook 服务器,Task 的三种状态的测试用例应该就生成完毕了: +当我们完成这些,重启 Storybook 服务将会生成三种 Task 状态的测试用例: