Storybook Vue3 路由器
一个 Storybook 装饰器,允许您使用 Vue 3 路由感知组件。
如果您想使用 <router-view>
或 <router-link>
为 Vue 3 组件构建故事,那么您需要使用 vue-router
来包装您的故事,这个附加组件将允许您轻松地做到这一点。
还提供了一个 模拟路由器装饰器 选项,供只需要访问 $route
和 $router
属性的用户使用。
如何使用
此装饰器与 Storybook 的 组件故事格式 (CSF) 和 悬挂的 CSF 注释 协同工作,这是从 Storybook 6 开始推荐的编写故事的方式。它还没有在 storiesOf API 中进行测试。
Storybook v6:请使用包版本 2.x
Storybook v7:请使用包版本 3+
请参阅 迁移指南。
安装装饰器
npm install --save-dev storybook-vue3-router
// or
yarn add --dev storybook-vue3-router
在您的故事中使用
默认设置将创建一个 vue-router
实例,包含 2 个路由(/
和 /about
)- 这些可以在 defaultRoutes.ts 文件中进行查看。
/* import storybook-vue3-router */
import { vueRouter } from 'storybook-vue3-router'
/* ...story setup... */
/* your story export */
export const Default = Template.bind({})
/* adding storybook-vue3-router decorator */
Default.decorators = [
/* this is the basic setup with no params passed to the decorator */
vueRouter()
]
演示
高级用法
此装饰器带有可选参数,用于自定义 Storybook 中 vue-router
的实现。
自定义路由
/* define our custom routes */
const customRoutes = [
{
path: '/',
name: 'home',
component: HomeComponent // this would need to be defined/imported into the `.stories` file
},
{
path: '/about',
name: 'about',
component: AboutComponent // this would need to be defined/imported into the `.stories` file
}
]
/* adding storybook-vue3-router decorator */
Default.decorators = [
/* pass custom routes to the decorator */
vueRouter(customRoutes)
]
自定义路由(带守卫)
/* define our custom routes */
const customRoutes = [
// ...
{
path: '/admin',
name: 'admin',
component: AdminComponent,
/* add per-route navigation guard */
beforeEnter: (to, from, next) => {
// ...
}
}
]
/* adding storybook-vue3-router decorator */
Default.decorators = [
/* pass custom routes to the decorator */
vueRouter(customRoutes)
]
自定义路由(带初始路由)
默认情况下,装饰器会将起始路由默认为 /
,如果要更改它,可以将其作为参数传递给装饰器。
/* define our custom routes */
const customRoutes = [
{
path: '/',
name: 'dashboard',
component: Dashboard
},
{
path: '/intro',
name: 'intro',
component: Intro
}
]
### With Router Options
We can pass [Vue Router options](https://router.vuejs.ac.cn/api/index.html#history) into our decorator.
```typescript
/* adding storybook-vue3-router decorator */
Default.decorators = [
/* pass vueRouterOptions to the decorator */
vueRouter(undefined, {
vueRouterOptions: {
linkActiveClass: 'my-active-class',
linkExactActiveClass: 'my-exact-active-class'
...etc
}
})
]
router.isReady()
如果您使用 router.isReady()
设置了路由,或者您有在创建生命周期钩子中需要特定路由/路由数据的组件,您可能需要使用 asyncVueRouter
导出。
此导出提供了路由器,该路由器在路由器准备好之前不会渲染故事。
故事设置
import { asyncVueRouter } from 'storybook-vue3-router'
/* define our custom routes */
const customRoutes = [
{
path: '/',
name: 'dashboard',
component: Dashboard
},
{
path: '/intro',
name: 'intro',
component: Intro
}
]
/* adding storybook-vue3-router decorator */
Default.decorators = [
/* pass initialRoute to the decorator */
asyncVueRouter(customRoutes, {
initialRoute: '/intro'
})
]
Preview.js 异步设置
为了使用 async
路由设置方法,您需要修改您的 .storybook/preview.js 文件,以将故事包装在 Vue 3 的 <Suspense>
组件中。这是因为装饰器需要一个 async setup()
来正确地 await router.isReady()
。您可以将预览修改为
const preview = {
decorators: [
(story) => ({
components: { story },
template: '<Suspense><story /></Suspense>',
}),
],
};
export default preview;
请参阅 示例文件夹,以了解更高级的用法。
装饰器参数
function vueRouter(routes: RouteRecordRaw[], options?: { initialRoute?: string, beforeEach?: NavigationGuard, vueRouterOptions?: RouterOptions })
function asyncVueRouter(routes: RouteRecordRaw[], options?: { initialRoute?: string, beforeEach?: NavigationGuard, vueRouterOptions?: RouterOptions })
模拟路由器
并不总是需要完整的 vue-router
- 例如,如果您没有使用 <router-view>
或 <router-link>
的组件,那么使用 mockRouter
导出可能会满足您的需求(并减少故事中使用的导入)。
注意:mockRouter
仅在使用选项 API this.$route
和/或 this.$router
的情况下有效,它不适用于使用 vue 路由器组合式 API(如 useRoute()
和 useRouter()
)的用例。
在您的故事中使用 mockRouter
默认设置将从 vue-router
创建模拟 $router
和 $route
,这允许您为使用编程导航和基于路由的逻辑的组件创建故事。
我们还可以将自定义选项传递到 mockRouter
装饰器中。
{
meta?: Array<string>,
params?: Array<string>,
query?: Array<string>
}
/* import storybook-vue3-router mockRouter */
import { mockRouter } from 'storybook-vue3-router'
/* ...story setup... */
/* your story export */
export const Default = Template.bind({})
/* adding storybook-vue3-router mockRouter decorator */
Default.decorators = [
mockRouter({
meta: ['some_meta'],
params: ['some_param'],
query: ['some_query']
})
]
您可以在我们的 storybook 演示站点 和我们的 代码示例 中看到 mockRouter
的示例。
v2.x > v3+ 迁移
⚠️ 重大更改 ⚠️
v3.x 版本不再使用 vueRouter
装饰器的默认导出,您需要更新为使用命名导入。
/* DONT */
import vueRouter from 'storybook-vue3-router'
/* DO */
import { vueRouter } from 'storybook-vue3-router'
v1.x > v2.x 迁移
从 v1 迁移带来了一些重大更改。
// v1.x - 2nd param is used to pass `beforeEach` router guard
// in this example the guard is used to fire a storybook action with `to` and `from` router objects
vueRouter(customRoutes, (to, from) => action('ROUTE CHANGED')({ to: to, from: from })) // LEGACY
// v2.1 - 2nd param is used to pass additional options to the decorator
vueRouter(customRoutes, {
/* add global beforeEach guard */
beforeEach: (to, from) => action('ROUTE CHANGED')({ to: to, from: from })
})
如果您之前在第二个参数中使用 v1 与路由守卫,则需要将这些守卫重构为使用 特定于路由的路由守卫(推荐),或者您可以使用 beforeEach
选项传递全局路由守卫。
v2.0 没有此 beforeEach
选项,请升级到 v2.1。
⚠️ 警告
使用全局 beforeEach
选项时,如果存在另一个也使用此装饰器的故事,那么我们必须强制页面重新加载,以便设置特定故事路由守卫,这对用户体验/性能有轻微影响。查看 演示,以了解此示例:README > 带有路由守卫 > 全局守卫 - 当您单击“全局守卫”链接时,您会注意到页面已刷新以应用全局守卫(由于之前存在的故事)。
如果您只为一个故事使用此装饰器,则不会出现此问题。
在解决 此问题 后,为了能够使用不同的路由设置创建多个故事,人们注意到这会导致在每个路由上添加全局 beforeEach
函数。例如,每次单击不同的故事时,都会添加新的 beforeEach
钩子 - 但之前的钩子不会被删除,这会导致在与“活动”故事无关的故事上触发多个守卫。