Storybook Vue3 Router
一个 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.net.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()
和/或你的组件在 created 生命周期钩子中需要特定的路由/路由数据,你可能需要使用 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()
。你可以修改 preview 如下
const preview = {
decorators: [
(story) => ({
components: { story },
template: '<Suspense><story /></Suspense>',
}),
],
};
export default preview;
请参阅examples 文件夹以了解更多高级用法。
装饰器参数
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 Router 组合式 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 > With Router Guards > Global Guard - 当点击“Global Guard”链接时,你会注意到页面会刷新以应用全局守卫(由于之前存在的故事)。
如果你只为单个故事使用此装饰器,则不会出现此问题。
在解决了这个问题后,为了能够使用不同的路由设置创建多个故事,注意到这导致全局 beforeEach
函数在每个路由上都被添加。例如,每次点击不同的故事时,新的 beforeEach
钩子都会被添加 - 但之前的不会被移除,这导致与“当前”故事无关的故事上触发多个守卫。