From: edison Date: Wed, 10 Feb 2021 08:22:23 +0000 (+0800) Subject: docs: translate to zh-CN (#728) X-Git-Tag: v4.0.4~13 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e16a2f8acca725f313393508e46d341fa47d4532;p=thirdparty%2Fvuejs%2Frouter.git docs: translate to zh-CN (#728) Co-authored-by: xiaaa123 <3288432965@qq.com> --- diff --git a/docs/.vitepress/config.js b/docs/.vitepress/config.js index b7bb2293..271e8855 100644 --- a/docs/.vitepress/config.js +++ b/docs/.vitepress/config.js @@ -18,18 +18,6 @@ const config = { lang: 'en-US', title: 'Vue Router', description: 'The official router for Vue.js.', - locales: { - '/': { - lang: 'en-US', - title: 'Vue Router', - description: 'The official router for Vue.js.', - }, - // '/es/': { - // lang: 'es-ES', - // title: 'Vue Router', - // description: 'El router oficial par Vue.js', - // }, - }, head, // serviceWorker: true, themeConfig: { @@ -54,6 +42,11 @@ const config = { locales: { // English '/': { + lang: 'en-US', + label: 'English', + selectText: 'Languages', + title: 'Vue Router', + description: 'The official router for Vue.js.', nav: [ { text: 'Guide', @@ -177,6 +170,136 @@ const config = { }, ], }, + // 简体中文 + '/zh/': { + lang: 'zh-CN', + label: '中文', + selectText: '选择语言', + title: 'Vue Router', + description: 'Vue.js 的官方路由', + nav: [ + { + text: '教程', + link: '/zh/guide/', + }, + { + text: 'API 参考', + link: '/zh/api/', + }, + { + text: '更新日志', + link: + 'https://github.com/vuejs/vue-router-next/blob/master/CHANGELOG.md', + }, + ], + + sidebar: [ + { + text: '介绍', + link: '/zh/introduction.html', + }, + { + text: '安装', + link: '/zh/installation.html', + }, + { + text: '基础', + collapsable: false, + children: [ + { + text: '入门', + link: '/zh/guide/', + }, + { + text: '动态路由匹配', + link: '/zh/guide/essentials/dynamic-matching.html', + }, + { + text: '路由的匹配语法', + link: '/zh/guide/essentials/route-matching-syntax.html', + }, + { + text: '嵌套路由', + link: '/zh/guide/essentials/nested-routes.html', + }, + { + text: '编程式导航', + link: '/zh/guide/essentials/navigation.html', + }, + { + text: '命名路由', + link: '/zh/guide/essentials/named-routes.html', + }, + { + text: '命名视图', + link: '/zh/guide/essentials/named-views.html', + }, + { + text: '重定向和别名', + link: '/zh/guide/essentials/redirect-and-alias.html', + }, + { + text: '路由组件传参', + link: '/zh/guide/essentials/passing-props.html', + }, + { + text: '不同的历史记录模式', + link: '/zh/guide/essentials/history-mode.html', + }, + ], + }, + { + text: '进阶', + collapsable: false, + children: [ + { + text: '导航守卫', + link: '/zh/guide/advanced/navigation-guards.html', + }, + { + text: '路由元信息', + link: '/zh/guide/advanced/meta.html', + }, + { + text: '数据获取', + link: '/zh/guide/advanced/data-fetching.html', + }, + { + text: '组合式 API', + link: '/zh/guide/advanced/composition-api.html', + }, + { + text: '过渡动效', + link: '/zh/guide/advanced/transitions.html', + }, + { + text: '滚动行为', + link: '/zh/guide/advanced/scroll-behavior.html', + }, + { + text: '路由懒加载', + link: '/zh/guide/advanced/lazy-loading.html', + }, + { + text: '扩展 RouterLink', + link: '/zh/guide/advanced/extending-router-link.html', + }, + { + text: '导航故障', + link: '/zh/guide/advanced/navigation-failures.html', + }, + { + text: '动态路由', + link: '/zh/guide/advanced/dynamic-routing.html', + }, + ], + }, + { + text: '从 Vue2 迁移', + link: '/zh/guide/migration/index.html', + }, + ], + }, }, // '/es/': { diff --git a/docs/zh/api/index.md b/docs/zh/api/index.md new file mode 100644 index 00000000..908868ec --- /dev/null +++ b/docs/zh/api/index.md @@ -0,0 +1,1079 @@ +--- +sidebar: auto +--- + +# API 参考 + +## `` Props + +### to + +- **类型**:[`RouteLocationRaw`](#routelocationraw) +- **详细内容**: + + 表示目标路由的链接。当被点击后,内部会立刻把 `to` 的值传到 `router.push()`,所以这个值可以是一个 `string` 或者是[描述目标位置的对象](#routelocationraw)。 + +```html + +Home + +Home + + +Home + + +Home + + +User + + + + Register + +``` + +### replace + +- **类型**:`boolean` +- **默认值**:`false` +- **详细内容**: + + 设置 `replace` 属性的话,当点击时,会调用 `router.replace()`,而不是 `router.push()`,所以导航后不会留下历史记录。 + +```html + +``` + +### active-class + +- **类型**:`string` +- **默认值**:`"router-link-active"` (或者全局 [`linkActiveClass`](#linkactiveclass)) +- **详细内容**: + + 链接激活时,应用于渲染的 `` 的 class。 + +### aria-current-value + +- **类型**:`'page' | 'step' | 'location' | 'date' | 'time' | 'true' | 'false'` (`string`) +- **默认值**:`"page"` +- **详细内容**: + + 当链接激活时,传递给属性 `aria-current` 的值。 + +### custom + +- **类型**:`boolean` +- **默认值**:`false` +- **详细内容**: + + `` 是否应该将其内容包裹在 `` 元素中。在使用 [`v-slot`](#router-link-s-v-slot) 创建自定义 RouterLink 时很有用。默认情况下,`` 会将其内容包裹在 `` 元素中,即使使用 `v-slot` 也是如此。传递`自定义的` prop,可以去除这种行为。 + +- **例如**: + + ```html + + {{ route.fullPath }} + + ``` + + 渲染成 `/home`。 + + ```html + + {{ route.fullPath }} + + ``` + + 渲染成 `/home`。 + +### exact-active-class + +- **类型**:`string` +- **默认值**:`"router-link-exact-active"` (或者全局 [`linkExactActiveClass`](#linkexactactiveclass)) +- **详细内容**: + + 链接精准激活时,应用于渲染的 `` 的 class。 + +## `` 的 `v-slot` + +`` 通过一个[作用域插槽](https://v3.vuejs.org/guide/component-slots.html#scoped-slots)暴露底层的定制能力。这是一个更高阶的 API,主要面向库作者,但也可以为开发者提供便利,大多数情况下用在一个类似 _NavLink_ 这样的组件里。 + +:::tip 注意 +记得把 `custom` 配置传递给 ``,以防止它将内容包裹在 `` 元素内。 +::: + +```html + + + {{ route.fullPath }} + + +``` + +- `href`:解析后的 URL。将会作为一个 `` 元素的 `href` 属性。如果什么都没提供,则它会包含 `base`。 +- `route`:解析后的规范化的地址。 +- `navigate`:触发导航的函数。 **会在必要时自动阻止事件**,和 `router-link` 一样。例如:`ctrl` 或者 `cmd` + 点击仍然会被 `navigate` 忽略。 +- `isActive`:如果需要应用 [active class](#active-class),则为 `true`。允许应用一个任意的 class。 +- `isExactActive`:如果需要应用 [exact active class](#exact-active-class),则为 `true`。允许应用一个任意的 class。 + +### 示例:将激活的 class 应用在外层元素 + +有的时候我们可能想把激活的 class 应用到一个外部元素而不是 `` 标签本身,这时你可以在一个 `router-link` 中包裹该元素并使用 `v-slot` 属性来创建链接: + +```html + +
  • + {{ route.fullPath }} +
  • + +``` + +:::tip 提示 +如果你在 `a` 元素上添加一个 `target="_blank"`,你必须省略 `@click="navigate"` 的处理。 +::: + +## `` Props + +### name + +- **类型**:`string` +- **默认值**:`"default"` +- **详细内容**: + + 如果 `` 设置了 `name`,则会渲染对应的路由配置中 `components` 下的相应组件。 + +- **更多的内容请看**:[命名视图](/zh/guide/essentials/named-views.md) + +### route + +- **类型**:[`RouteLocationNormalized`](#routelocationnormalized) +- **详细内容**: + + 一个路由地址的所有组件都已被解析(如果所有组件都被懒加载),因此可以显示。 + +## `` 的 `v-slot` + +`` 暴露了一个 `v-slot` API,主要使用 `` 和 `` 组件来包裹你的路由组件。 + +```html + + + +``` + +- `Component`: 要传递给 `` 的 VNodes `是` prop。 +- `route`: 解析出的标准化[路由地址](#routelocationnormalized)。 + +## createRouter + +创建一个可以被 Vue 应用程序使用的路由实例。查看 [`RouterOptions`](#routeroptions) 中的所有可以传递的属性列表。 + +**函数签名:** + +```typescript +export declare function createRouter(options: RouterOptions): Router +``` + +### 参数 + +| 参数 | 类型 | 描述 | +| ------- | ------------------------------- | ------------------------- | +| options | [RouterOptions](#routeroptions) | Options 用来初始化 router | + +## createWebHistory + +创建一个 HTML5 历史,即单页面应用程序中最常见的历史记录。应用程序必须通过 http 协议被提供服务。 + +**函数签名:** + +```typescript +export declare function createWebHistory(base?: string): RouterHistory +``` + +### 参数 + +| 参数 | 类型 | 描述 | +| ---- | -------- | -------------------------------------------------------------------------------------------------- | +| base | `string` | 提供的可选 base。当应用程序被托管在诸如 `https://example.com/folder/` 之类的文件夹中时非常有用。 | + +### 示例 + +```js +createWebHistory() // 没有 base,应用托管在域名 `https://example.com` 的根目录下。 +createWebHistory('/folder/') // 给出的网址为 `https://example.com/folder/` +``` + +## createWebHashHistory + +创建一个 hash 历史记录。对于没有主机的 web 应用程序 (例如 `file://`),或当配置服务器不能处理任意URL时这非常有用。**注意:如果 SEO 对你很重要,你应该使用 [`createWebHistory`](#createwebhistory)**。 + +**函数签名:** + +```typescript +export declare function createWebHashHistory(base?: string): RouterHistory +``` + +### 参数 + +| 参数 | 类型 | 描述 | +| ---- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| base | `string` | 提供可选的 base。默认是 `location.pathname + location.search`。如果 `head` 中有一个 ``,它的值将被忽略,而采用这个参数。**但请注意它会影响所有的 history.pushState() 调用**,这意味着如果你使用一个 `` 标签,它的 `href` 值**必须与这个参数相匹配** (请忽略 `#` 后面的所有内容) | + +### 示例 + +```js +// at https://example.com/folder +createWebHashHistory() // 给出的网址为 `https://example.com/folder#` +createWebHashHistory('/folder/') // 给出的网址为 `https://example.com/folder/#` +// 如果在 base 中提供了 `#`,则它不会被 `createWebHashHistory` 添加 +createWebHashHistory('/folder/#/app/') // 给出的网址为 `https://example.com/folder/#/app/` +// 你应该避免这样做,因为它会更改原始 url 并打断正在复制的 url +createWebHashHistory('/other-folder/') // 给出的网址为 `https://example.com/other-folder/#` + +// at file:///usr/etc/folder/index.html +// 对于没有 `host` 的位置,base被忽略 +createWebHashHistory('/iAmIgnored') // 给出的网址为 `file:///usr/etc/folder/index.html#` +``` + +## createMemoryHistory + +创建一个基于内存的历史记录。这个历史记录的主要目的是处理 SSR。它在一个特殊的位置开始,这个位置无处不在。如果用户不在浏览器上下文中,它们可以通过调用 `router.push()` 或 `router.replace()` 将该位置替换为启动位置。 + +**函数签名:** + +```typescript +export declare function createMemoryHistory(base?: string): RouterHistory +``` + +### 参数 + +| 参数 | 类型 | 描述 | +| ---- | -------- | ------------------------------ | +| base | `string` | Base 适用于所有 URL,默认为'/' | + +### Returns + +一个可以传递给路由构造函数的历史对象。 + +## NavigationFailureType + +包含所有可能导航失败类型的枚举,可以传递给 [isNavigationFailure](#isnavigationfailure) 来检查某些特定类型的失败。**不要使用任何数值**,总是使用诸如 `NavigationFailureType.aborted` 这样的变量。 + +**函数签名:** + +```typescript +export declare enum NavigationFailureType +``` + +### 成员 + +| 成员 | 值 | 描述 | +| ---------- | --- | ------------------------------------------------------------------------ | +| aborted | 4 | 终止导航是指由于导航守卫返回 `false` 或调用 `next(false)` 而失败的导航。 | +| cancelled | 8 | 取消导航是指由于最近的导航完成启动(不一定是完成)而失败的导航。 | +| duplicated | 16 | 重复导航是指在启动时已经在同一位置失败的导航。 | + +## START_LOCATION + +- **类型**:[`RouteLocationNormalized`](#routelocationnormalized) +- **详细内容**: + + 路由所在的初始路由地址。可用于导航守卫中,以区分初始导航。 + + ```js + import { START_LOCATION } from 'vue-router' + + router.beforeEach((to, from) => { + if (from === START_LOCATION) { + // 初始导航 + } + }) + ``` + +## Composition API + +### onBeforeRouteLeave + +添加一个导航守卫,在当前位置的组件将要离开时触发。类似于 `beforeRouteLeave`,但它可以在任何组件中使用。当组件被卸载时,导航守卫将被移除。 + +**函数签名:** + +```typescript +export declare function onBeforeRouteLeave(leaveGuard: NavigationGuard): void +``` + +#### 参数 + +| 参数 | 类型 | 描述 | +| ---------- | ------------------------------------- | ---------------- | +| leaveGuard | [`NavigationGuard`](#navigationguard) | 要添加的导航守卫 | + +### onBeforeRouteUpdate + +添加一个导航守卫,在当前位置即将更新时触发。类似于 `beforeRouteUpdate`,但它可以在任何组件中使用。当组件被卸载时,导航守卫将被移除。 + +**函数签名:** + +```typescript +export declare function onBeforeRouteUpdate(updateGuard: NavigationGuard): void +``` + +#### 参数 + +| 参数 | 类型 | 描述 | +| ----------- | ------------------------------------- | ---------------- | +| updateGuard | [`NavigationGuard`](#navigationguard) | 要添加的导航守卫 | + +### useLink + +返回 [`v-slot` API](#router-link-s-v-slot) 暴露的所有内容。 + +**函数签名:** + +```typescript +export declare function useLink(props: RouterLinkOptions): { + route: ComputedRef, + href: ComputedRef, + isActive: ComputedRef, + isExactActive: ComputedRef, + navigate: (event?: MouseEvent) => Promise(NavigationFailure | void), +} +``` + +#### 参数 + +| 参数 | 类型 | 描述 | +| ----- | ----------------- | ---------------------------------------------------------------- | +| props | RouterLinkOptions | props 对象可以传递给``。接收 `Ref` 和 `ComputedRef` | + +### useRoute + +返回当前路由地址。相当于在模板中使用 `$route`。必须在 `setup()` 中调用。 + +**函数签名:** + +```typescript +export declare function useRoute(): RouteLocationNormalized +``` + +### useRouter + +返回 [router](#router-properties) 实例。相当于在模板中使用 `$router`。必须在 `setup()` 中调用。 + +**函数签名:** + +```typescript +export declare function useRouter(): Router +``` + +## TypeScript + +下面是 Vue Router 使用的一些接口和类型。文档引用它们是为了让你了解对象中现有的属性。 + +## Router 属性 + +### currentRoute + +- **类型**:[`Ref`](#routelocationnormalized) +- **详细内容**: + + 当前路由地址。只读的。 + +### options + +- **类型**:[`RouterOptions`](#routeroptions) +- **详细内容**: + + 创建 Router 时传递的原始配置对象。只读的。 + +## Router 方法 + +### addRoute + +添加一条新的[路由记录](#routerecordraw)作为现有路由的子路由。如果路由有一个 `name`,并且已经有一个与之名字相同的路由,它会先删除之前的路由。 + +**函数签名:** + +```typescript +addRoute(parentName: string | symbol, route: RouteRecordRaw): () => void +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ---------- | ----------------------------------- | ---------------- | -------------------------------------- | +| parentName | `string| symbol` | 父路由记录,`route` 应该被添加到的位置 | +| route | [`RouteRecordRaw`](#routerecordraw) | 要添加的路由记录 | + +### addRoute + +添加一条新的[路由记录](#routerecordraw)到路由。如果路由有一个 `name`,并且已经有一个与之名字相同的路由,它会先删除之前的路由。 + +**函数签名:** + +```typescript +addRoute(route: RouteRecordRaw): () => void +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ----- | ----------------------------------- | ---------------- | +| route | [`RouteRecordRaw`](#routerecordraw) | 要添加的路由记录 | + +:::tip 提示 +请注意,添加路由并不会触发新的导航。也就是说,除非触发新的导航,否则不会显示所添加的路由。 +::: + +### afterEach + +添加一个导航钩子,在每次导航后执行。返回一个删除注册钩子的函数。 + +**函数签名:** + +```typescript +afterEach(guard: NavigationHookAfter): () => void +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ----- | ------------------- | ---------------- | +| guard | NavigationHookAfter | 要添加的导航钩子 | + +#### 示例 + +```js +router.afterEach((to, from, failure) => { + if (isNavigationFailure(failure)) { + console.log('failed navigation', failure) + } +}) +``` + +### back + +如果可能的话,通过调用 `history.back()` 回溯历史。相当于 `router.go(-1)`。 + +**函数签名:** + +```typescript +back(): void +``` + +### beforeEach + +添加一个导航守卫,在任何导航前执行。返回一个删除已注册守卫的函数。 + +**函数签名:** + +```typescript +beforeEach(guard: NavigationGuard): () => void +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ----- | ------------------------------------- | ---------------- | +| guard | [`NavigationGuard`](#navigationguard) | 要添加的导航守卫 | + +### beforeResolve + +添加一个导航守卫,在导航即将解析之前执行。在这个状态下,所有的组件都已经被获取,并且其他导航守卫也已经成功。返回一个删除已注册守卫的函数。 + +**函数签名:** + +```typescript +beforeResolve(guard: NavigationGuard): () => void +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ----- | ------------------------------------- | ---------------- | +| guard | [`NavigationGuard`](#navigationguard) | 要添加的导航守卫 | + +#### 示例 + +```js +router.beforeEach(to => { + if (to.meta.requiresAuth && !isAuthenticated) return false +}) +``` + +### forward + +如果可能的话,通过调用 `history.forward()` 在历史中前进。相当于 `router.go(1)`。 + +**函数签名:** + +```typescript +forward(): void +``` + +### getRoutes + +获取所有 [路由记录](#routerecord)的完整列表。 + +**函数签名:** + +```typescript +getRoutes(): RouteRecord[] +``` + +### go + +允许你在历史中前进或后退。 + +**函数签名:** + +```typescript +go(delta: number): void +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ----- | -------- | ------------------------------------ | +| delta | `number` | 相对于当前页面,你要移动到的历史位置 | + +### hasRoute + +确认是否存在指定名称的路由。 + +**函数签名:** + +```typescript +hasRoute(name: string | symbol): boolean +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ---- | ------- | ------- | ---------------- | +| name | `string | symbol` | 要确认的路由名称 | + +### isReady + +当路由器完成初始化导航时,返回一个 Promise,这意味着它已经解析了所有与初始路由相关的异步输入钩子和异步组件。如果初始导航已经发生了,那么 promise 就会立即解析。这在服务器端渲染中很有用,可以确保服务器和客户端的输出一致。需要注意的是,在服务器端,你需要手动推送初始位置,而在客户端,路由器会自动从 URL 中获取初始位置。 + +**函数签名:** + +```typescript +isReady(): Promise +``` + +### onError + +添加一个错误处理程序,在导航期间每次发生未捕获的错误时都会调用该处理程序。这包括同步和异步抛出的错误、在任何导航守卫中返回或传递给 `next` 的错误,以及在试图解析渲染路由所需的异步组件时发生的错误。 + +**函数签名:** + +```typescript +onError(handler: (error: any) => any): () => void +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ------- | --------------------- | ------------------ | +| handler | `(error: any) => any` | 要注册的错误处理器 | + +### push + +通过在历史堆栈中推送一个 entry,以编程方式导航到一个新的 URL。 + +**函数签名:** + +```typescript +push(to: RouteLocationRaw): Promise +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ---- | --------------------------------------- | ------------------ | +| to | [`RouteLocationRaw`](#routelocationraw) | 要导航到的路由地址 | + +### removeRoute + +通过名称删除现有路由。 + +**函数签名:** + +```typescript +removeRoute(name: string | symbol): void +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ---- | ------- | ------- | ---------------- | +| name | `string | symbol` | 要删除的路由名称 | + +### replace + +通过替换历史堆栈中的当前 entry,以编程方式导航到一个新的 URL。 + +**函数签名:** + +```typescript +replace(to: RouteLocationRaw): Promise +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ---- | --------------------------------------- | ------------------ | +| to | [`RouteLocationRaw`](#routelocationraw) | 要导航到的路由地址 | + +### resolve + +返回[路由地址](#routelocationraw)的[标准化版本](#routelocation)。还包括一个包含任何现有 `base` 的 `href` 属性。 + +**函数签名:** + +```typescript +resolve(to: RouteLocationRaw): RouteLocation & { + href: string +} +``` + +_参数_ + +| 参数 | 类型 | 描述 | +| ---- | --------------------------------------- | -------------------- | +| to | [`RouteLocationRaw`](#routelocationraw) | 要解析的原始路由地址 | + +## RouterOptions + +### history + +用于路由实现历史记录。大多数 web 应用程序都应该使用 `createWebHistory`,但它要求正确配置服务器。你还可以使用 `createWebHashHistory` 的基于 _hash_ 的历史记录,它不需要在服务器上进行任何配置,但是搜索引擎根本不会处理它,在 SEO 上表现很差。 + +**函数签名:** + +```typescript +history: RouterHistory +``` + +#### 示例 + +```js +createRouter({ + history: createWebHistory(), + // 其他配置... +}) +``` + +### linkActiveClass + +用于激活的 [RouterLink](#router-link-props) 的默认类。如果什么都没提供,则会使用 `router-link-active`。 + +**函数签名:** + +```typescript +linkActiveClass?: string +``` + +### linkExactActiveClass + +用于精准激活的 [RouterLink](#router-link-props) 的默认类。如果什么都没提供,则会使用 `router-link-exact-active`。 + +**函数签名:** + +```typescript +linkExactActiveClass?: string +``` + +### parseQuery + +用于解析查询的自定义实现。必须解码查询键和值。参见对应的 [stringifyQuery](#stringifyquery)。 + +**函数签名:** + +```typescript +parseQuery?: (searchQuery: string) => Record +``` + +#### 示例 + +比方说,你想使用 [qs](https://github.com/ljharb/qs) 包来解析查询,你可以同时提供 `parseQuery` 和 `stringifyQuery`: + +```js +import qs from 'qs' + +createRouter({ + // 其他配置... + parseQuery: qs.parse, + stringifyQuery: qs.stringify, +}) +``` + +### routes + +应该添加到路由的初始路由列表。 + +**函数签名:** + +```typescript +routes: RouteRecordRaw[] +``` + +### scrollBehavior + +在页面之间导航时控制滚动的函数。可以返回一个 Promise 来延迟滚动。 + +**函数签名:** + +```typescript +scrollBehavior?: ScrollBehavior +``` + +#### 示例 + +```js +function scrollBehavior(to, from, savedPosition) { + // `to` 和 `from` 都是路由地址 + // `savedPosition` 可以为空,如果没有的话。 +} +``` + +### stringifyQuery + +对查询对象进行字符串化的自定义实现。不应该在前面加上 `?`。应该正确编码查询键和值。 [parseQuery](#parsequery) 对应于处理查询解析。 + +**函数签名:** + +```typescript +stringifyQuery?: ( + query: Record< + string | number, + string | number | null | undefined | (string | number | null | undefined)[] + > +) => string +``` + +## RouteRecordRaw + +当用户通过 [`routes` option](#routeroptions) 或者 [`router.addRoutes()`](#addroutes) 来添加路由时,可以得到路由记录。 有三种不同的路由记录: + +- 单一视图记录:有一个 `component` 配置 +- 多视图记录 ([命名视图](/zh/guide/essentials/named-views.md)) :有一个 `components` 配置 +- 重定向记录:没有 `component` 或 `components` 配置,因为重定向记录永远不会到达。 + +### path + +- **类型**:`string` +- **详细内容**: + + 记录的路径。应该以 `/` 开头,除非该记录是另一条记录的子记录。可以定义参数:`/users/:id` 匹配 `/users/1` 以及 `/users/posva`。 + +- **更多的内容请看**:[动态路由匹配](/zh/guide/essentials/dynamic-matching.md) + +### redirect + +- **类型**:`RouteLocationRaw | (to: RouteLocationNormalized) => RouteLocationRaw` (可选) +- **详细内容**: + + 如果路由是直接匹配的,那么重定向到哪里呢。重定向发生在所有导航守卫之前,并以新的目标位置触发一个新的导航。也可以是一个接收目标路由地址并返回我们应该重定向到的位置的函数。 + +### children + +- **类型**:[`RouteRecordRaw`](#routerecordraw) 数组 (可选) +- **详细内容**: + + 当前记录的嵌套路由。 + +- **更多的内容请看**:[Nested Routes](/zh/guide/essentials/nested-routes.md) + +### alias + +- **类型**:`string | string[]` (可选) +- **详细内容**: + + 路由的别名。允许定义类似记录副本的额外路由。这使得路由可以简写为像这种 `/users/:id` 和 + `/u/:id`。 **所有的 `alias` 和 `path` 值必须共享相同的参数**。 + +### name + +- **类型**:`string | symbol` (可选) +- **详细内容**: + + 路由记录独一无二的名称。 + +### beforeEnter + +- **类型**:[`NavigationGuard | NavigationGuard[]`](#navigationguard) (可选) +- **详细内容**: + + 在进入特定于此记录的守卫之前。注意如果记录有`重定向`属性,则 `beforeEnter` 无效。 + +### props + +- **类型**:`boolean | Record | (to: RouteLocationNormalized) => Record` (可选) +- **详细内容**: + + 允许将参数作为 props 传递给由 `router-view` 渲染的组件。当传递给一个*多视图记录*时,它应该是一个与`组件`具有相同键的对象,或者是一个应用于每个组件的`布尔值`。 + +- **更多的内容请看**:[给路由组件传 props](/zh/guide/essentials/passing-props.md) + +### meta + +- **类型**:[`RouteMeta`](#routemeta) (可选) +- **详细内容**: + + 在记录上附加自定义数据。 + +- **更多的内容请看**:[Meta 字段](/zh/guide/advanced/meta.md) + +## RouteRecordNormalized + +[路由记录](#routerecordraw)的标准化版本 + +### aliasOf + +- **类型**:`RouteRecordNormalized | undefined` +- **详细内容**: + + 定义此记录是否是另一个记录的别名。如果该记录是原始记录,则此属性为 `undefined`。 + +### beforeEnter + +- **类型**:[`NavigationGuard`](#navigationguard) +- **详细内容**: + + 当从其他地方进入此记录时,导航守卫会被应用。 + +- **更多的内容请看**:[导航守卫](/zh/guide/advanced/navigation-guards.md) + +### children + +- **类型**:标准化[路由记录](#routerecordnormalized)数组 +- **详细内容**: + + 当前路由的子路由记录。如果没有则为空数组。 + +### components + +- **类型**:`Record` +- **详细内容**: + + 命名视图的字典,如果没有,包含一个键为 `default` 的对象。 + +### meta + +- **类型**:`RouteMeta` +- **详细内容**: + + 附在记录上的任意数据。 + +- **更多的内容请看**:[Meta 字段](/zh/guide/advanced/meta.md) + +### name + +- **类型**:`string | symbol | undefined` +- **详细内容**: + + 路由记录的名称。如果什么都没提供,则为 `undefined`。 + +### path + +- **类型**:`string` +- **详细内容**: + + 路由记录的标准化路径。包括所有父级的 `path`。 + +### props + +- **类型**:`Record>` +- **详细内容**: + + 每个命名视图的 [`props` 配置](#props)字典。如果没有,它将只包含一个名为 `default` 的属性。 + +### redirect + +- **类型**:[`RouteLocationRaw`](#routelocationraw) +- **详细内容**: + + 如果路由是直接匹配的,那么重定向到哪里呢。重定向发生在所有导航守卫之前,并触发一个带有新目标位置的新导航。 + +## RouteLocationRaw + +用户级的路由地址,可以传递给 `router.push()`,`redirect`,并在[导航守卫](/zh/guide/advanced/navigation-guards.md)中返回。 + +原始位置可以是一个 `字符串`,比如 `/users/posva#bio`,也可以是一个对象: + +```js +// 这三种形式是等价的 +router.push('/users/posva#bio') +router.push({ path: '/users/posva', hash: '#bio' }) +router.push({ name: 'users', params: { username: 'posva' }, hash: '#bio' }) +// 只改变 hash +router.push({ hash: '#bio' }) +// 只改变 query +router.push({ query: { page: '2' } }) +// 只改变 param +router.push({ params: { username: 'jolyne' } }) +``` + +注意 `path` 必须以编码方式提供(例如,`phantom blood` 变为 `phantom%20blood`)。而 `params`、`query` 和 `hash` 一定不要这样,因为它们会被路由编码。 + +原始路由地址还支持一个额外的配置 `replace` 来调用导航守卫中的 `router.replace()`,而不是 `router.push()`。请注意,即使在调用 `router.push()`时,它也会在内部调用 `router.replace()` : + +```js +router.push({ hash: '#bio', replace: true }) +// 相当于 +router.replace({ hash: '#bio' }) +``` + +## RouteLocation + +可以包含[重定向记录](#routerecordraw)的解析的 [RouteLocationRaw](#routelocationraw)。除此之外,它还具有与 [RouteLocationNormalized](#routelocationnormalized) 相同的属性。 + +## RouteLocationNormalized + +标准化的路由地址。没有任何[重定向记录](#routerecordraw)。在导航守卫中,`to` 和 `from` 总是属于这种类型。 + +### fullPath + +- **类型**:`string` +- **详细内容**: + + URL 编码与路由地址有关。包括 `path`、 `query` 和 `hash`。 + +### hash + +- **类型**:`string` +- **详细内容**: + + 已解码 URL 的 `hash` 部分。总是以 `#`开头。如果 URL 中没有 `hash`,则为空字符串。 + +### query + +- **类型**:`Record` +- **详细内容**: + + 从 URL 的 `search` 部分提取的已解码查询参数的字典。 + +### matched + +- **类型**:[`RouteRecordNormalized[]`](#routerecordnormalized) +- **详细内容**: + + 与给定路由地址匹配的[标准化的路由记录](#routerecord)数组。 + +### meta + +- **类型**:`RouteMeta` +- **详细内容**: + + 附加到从父级到子级合并(非递归)的所有匹配记录的任意数据。 + +- **更多的内容请看**:[Meta 字段](/zh/guide/advanced/meta.md) + +### name + +- **类型**:`string | symbol | undefined | null` +- **详细内容**: + + 路由记录的名称。如果什么都没提供,则为 `undefined`。 + +### params + +- **类型**:`Record` +- **详细内容**: + + 从 `path` 中提取的已解码参数字典。 + +### path + +- **类型**:`string` +- **详细内容**: + + 编码 URL 的 `pathname` 部分,与路由地址有关。 + +### redirectedFrom + +- **类型**:[`RouteLocation`](#routelocation) +- **详细内容**: + + 在找到 `redirect` 配置或带有路由地址的名为 `next()` 的导航守卫时,我们最初尝试访问的路由地址,最后到达当前位置。如果没有重定向,则为 `undefined`。 + +## NavigationFailure + +### from + +- **类型**:[`RouteLocationNormalized`](#routelocationnormalized) +- **详细内容**: + + 导航来的路由地址 + +### to + +- **类型**:[`RouteLocationNormalized`](#routelocationnormalized) +- **详细内容**: + + 导航去的路由地址 + +### type + +- **类型**:[`NavigationFailureType`](#navigationfailuretype) +- **详细内容**: + + 导航失败的类型 + +- **更多的内容请看**:[Navigation Failures](/zh/guide/advanced/navigation-failures.md) + +## NavigationGuard + +- **Arguments**: + + - [`RouteLocationNormalized`](#routelocationnormalized) to - 我们要导航到的路由地址 + - [`RouteLocationNormalized`](#routelocationnormalized) from - 我们从哪里来的路由地址 + - `Function` next (可选) - 回调以验证导航 + +- **详细内容**: + + 可以通过函数来控制路由导航。如果你返回一个值(或一个 Promise ),则可以省略 `next` 回调,并且我们鼓励这样做。可能的返回值 (和 `next`的参数) 有: + + - `undefined | void | true`: 验证导航 + - `false`: 取消导航 + - [`RouteLocationRaw`](#routelocationraw): 重定向到一个不同的位置 + - `(vm: ComponentPublicInstance) => any` **仅适用于 `beforeRouteEnter`**:导航完成后执行的回调。接收路由组件实例作为参数。 + +- **更多的内容请看**:[导航守卫](/zh/guide/advanced/navigation-guards.md) + +## Component Injections + +### Component Injected 属性 + +这些属性通过调用 `app.use(router)` 注入到每个子组件中。 + +- **this.\$router** + + router 实例 + +- **this.\$route** + + 当前激活的[路由地址](#routelocationnormalized)。这个属性是只读的,并且它的属性是不可改变的,但是它可以被观察。 + +### Component Enabled Options + +- **beforeRouteEnter** +- **beforeRouteUpdate** +- **beforeRouteLeave** + +请看[组件内的守卫](/zh/guide/advanced/navigation-guards.md#组件内的守卫)。 diff --git a/docs/zh/guide/advanced/composition-api.md b/docs/zh/guide/advanced/composition-api.md new file mode 100644 index 00000000..3ba2109a --- /dev/null +++ b/docs/zh/guide/advanced/composition-api.md @@ -0,0 +1,111 @@ +# Vue Router 和 组合式 API + +引入 `setup` 和 Vue 的[组合式 API](https://v3.vuejs.org/guide/composition-api-introduction.html),开辟了新的可能性,但要想充分发挥 Vue Router 的潜力,我们需要使用一些新的函数来代替访问 `this` 和组件内导航守卫。 + +## 在 `setup` 中访问路由和当前路由 + +因为我们在 `setup` 里面没有访问 `this`,所以我们不能再直接访问 `this.$router` 或 `this.$route`。作为替代,我们使用 `useRouter` 函数: + +```js +import { useRouter, useRoute } from 'vue-router' + +export default { + setup() { + const router = useRouter() + const route = useRoute() + + function pushWithQuery(query) { + router.push({ + name: 'search', + query: { + ...route.query, + }, + }) + } + }, +} +``` + +`route` 对象是一个响应式对象,所以它的任何属性都可以被监听,但你应该**避免监听整个 `route`** 对象: + +```js +import { useRoute } from 'vue-router' + +export default { + setup() { + const route = useRoute() + const userData = ref() + + // 当参数更改时获取用户信息 + watch( + () => route.params, + async newParams => { + userData.value = await fetchUser(newParams.id) + } + ) + }, +} +``` + +请注意,在模板中我们仍然可以访问 `$router` 和 `$route`,所以不需要在 `setup` 中返回 `router` 或 `route`。 + +## 导航守卫 + +虽然你仍然可以通过 `setup` 函数来使用组件内的导航守卫,但 Vue Router 将更新和离开守卫作为 组合式 API 函数公开: + +```js +import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router' + +export default { + setup() { + // 与 beforeRouteLeave 相同,无法访问 `this` + onBeforeRouteLeave((to, from) => { + const answer = window.confirm( + 'Do you really want to leave? you have unsaved changes!' + ) + // 取消导航并停留在同一页面上 + if (!answer) return false + }) + + const userData = ref() + + // 与 beforeRouteLeave 相同,无法访问 `this` + onBeforeRouteUpdate(async (to, from) => { + //仅当 id 更改时才获取用户,例如仅 query 或 hash 值已更改 + if (to.params.id !== from.params.id) { + userData.value = await fetchUser(to.params.id) + } + }) + }, +} +``` + +组合式 API 守卫也可以用在任何由 `` 渲染的组件中,它们不必像组件内守卫那样直接用在路由组件上。 + +## `useLink` + +Vue Router 将 RouterLink 的内部行为作为一个组合式 API 函数公开。它提供了与 [`v-slot` API](/zh/api/#router-link-s-v-slot) 相同的访问属性: + +```js +import { RouterLink, useLink } from 'vue-router' + +export default { + name: 'AppLink', + + props: { + // 如果使用 TypeScript,请添加 @ts-ignore + ...RouterLink.props, + inactiveClass: String, + }, + + setup(props) { + const { route, href, isActive, isExactActive, navigate } = useLink(props) + + const isExternalLink = computed( + () => typeof props.to === 'string' && props.to.startsWith('http') + ) + + return { isExternalLink, href, navigate, isActive } + }, +} +``` diff --git a/docs/zh/guide/advanced/data-fetching.md b/docs/zh/guide/advanced/data-fetching.md new file mode 100644 index 00000000..3b999f20 --- /dev/null +++ b/docs/zh/guide/advanced/data-fetching.md @@ -0,0 +1,106 @@ +# 数据获取 + +有时候,进入某个路由后,需要从服务器获取数据。例如,在渲染用户信息时,你需要从服务器获取用户的数据。我们可以通过两种方式来实现: + +- **导航完成之后获取**:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。 + +- **导航完成之前获取**:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航。 + +从技术角度讲,两种方式都不错 —— 就看你想要的用户体验是哪种。 + +## 导航完成后获取数据 + +当你使用这种方式时,我们会马上导航和渲染组件,然后在组件的 created 钩子中获取数据。这让我们有机会在数据获取期间展示一个 loading 状态,还可以在不同视图间展示不同的 loading 状态。 + +假设我们有一个 `Post` 组件,需要基于 `$route.params.id` 获取文章数据: + +```html + +``` + +```js +export default { + data() { + return { + loading: false, + post: null, + error: null, + } + }, + created() { + // watch 路由的参数,以便再次获取数据 + this.$watch( + () => this.$route.params, + () => { + this.fetchData() + }, + // 组件创建完后获取数据, + // 此时 data 已经被 observed 了 + { immediate: true } + ) + }, + methods: { + fetchData() { + this.error = this.post = null + this.loading = true + // replace `getPost` with your data fetching util / API wrapper + // 用你的数据获取 util 或 API 替换 `getPost` + getPost(this.$route.params.id, (err, post) => { + this.loading = false + if (err) { + this.error = err.toString() + } else { + this.post = post + } + }) + }, + }, +} +``` + +## 在导航完成前获取数据 + +通过这种方式,我们在导航转入新的路由前获取数据。我们可以在接下来的组件的 `beforeRouteEnter` 守卫中获取数据,当数据获取成功后只调用 `next` 方法: + +```js +export default { + data() { + return { + post: null, + error: null, + } + }, + beforeRouteEnter(to, from, next) { + getPost(to.params.id, (err, post) => { + next(vm => vm.setData(err, post)) + }) + }, + // 路由改变前,组件就已经渲染完了 + // 逻辑稍稍不同 + async beforeRouteUpdate(to, from) { + this.post = null + try { + this.post = await getPost(to.params.id) + } catch (error) { + this.error = error.toString() + } + }, +} +``` + +在为后面的视图获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。 + + + + diff --git a/docs/zh/guide/advanced/dynamic-routing.md b/docs/zh/guide/advanced/dynamic-routing.md new file mode 100644 index 00000000..90109158 --- /dev/null +++ b/docs/zh/guide/advanced/dynamic-routing.md @@ -0,0 +1,104 @@ +# 动态路由 + +添加路由到你的路由上通常是通过 [`routes` 配置](/zh/api/#routes)来完成的,但是在某些情况下,你可能想在应用程序已经运行的时候添加或删除路由。具有可扩展接口(如 [Vue CLI UI](https://cli.vuejs.org/dev-guide/ui-api.html) )这样的应用程序可以使用它来扩展应用程序。 + +## 添加路由 + +动态路由主要通过两个函数实现。`router.addRoute()` 和 `router.removeRoute()`。它们**只**注册一个新的路由,也就是说,如果新增加的路由与当前位置相匹配,就需要你用 `router.push()` 或 `router.replace()` 来**手动导航**,才能显示该新路由。我们来看一个例子: + +想象一下,只有一个路由的以下路由: + +```js +const router = createRouter({ + history: createWebHistory(), + routes: [{ path: '/:articleName', component: Article }], +}) +``` + +进入任何页面,`/about`,`/store`,或者 `/3-tricks-to-improve-your-routing-code` 最终都会呈现 `Article` 组件。如果我们在 `/about` 上添加一个新的路由: + +```js +router.addRoute({ path: '/about', component: About }) +``` + +页面仍然会显示 `Article` 组件,我们需要手动调用 `router.replace()` 来改变当前的位置,并覆盖我们原来的位置(而不是添加一个新的路由,最后在我们的历史中两次出现在同一个位置): + +```js +router.addRoute({ path: '/about', component: About }) +// 我们也可以使用 this.$route 或 route = useRoute() (在 setup 中) +router.replace(router.currentRoute.value.fullPath) +``` + +记住,如果你需要等待新的路由显示,可以使用 `await router.replace()`。 + + +## 在导航守卫中添加路由 + +如果你决定在导航守卫内部添加或删除路由,你不应该调用 `router.replace()`,而是通过返回新的位置来触发重定向: + +```js +router.beforeEach(to => { + if (!hasNecessaryRoute(to)) { + router.addRoute(generateRoute(to)) + // 触发重定向 + return to.fullPath + } +}) +``` + +上面的例子有两个假设:第一,新添加的路由记录将与 `to` 位置相匹配,实际上导致与我们试图访问的位置不同。第二,`hasNecessaryRoute()` 在添加新的路由后返回 `false`,以避免无限重定向。 + +因为是在重定向中,所以我们是在替换将要跳转的导航,实际上行为就像之前的例子一样。而在实际场景中,添加路由的行为更有可能发生在导航守卫之外,例如,当一个视图组件挂载时,它会注册新的路由。 + +## 删除路由 + +有几个不同的方法来删除现有的路由: + +- 通过添加一个名称冲突的路由。如果添加与现有途径名称相同的途径,会先删除路由,再添加路由: + ```js + router.addRoute({ path: '/about', name: 'about', component: About }) + // 这将会删除之前已经添加的路由,因为他们具有相同的名字且名字必须是唯一的 + router.addRoute({ path: '/other', name: 'about', component: Other }) + ``` +- 通过调用 `router.addRoute()` 返回的回调: + ```js + const removeRoute = router.addRoute(routeRecord) + removeRoute() // 删除路由如果存在的话 + ``` + 当路由没有名称时,这很有用。 +- 通过使用 `router.removeRoute()` 按名称删除路由: + ```js + router.addRoute({ path: '/about', name: 'about', component: About }) + // 删除路由 + router.removeRoute('about') + ``` + 需要注意的是,如果你想使用这个功能,但又想避免名字的冲突,可以在路由中使用 `Symbol` 作为名字。 + +当路由被删除时,**所有的别名和子路由也会被同时删除** + +## 添加嵌套路由 + +要将嵌套路由添加到现有的路由中,可以将路由的 _name_ 作为第一个参数传递给 `router.addRoute()`,这将有效地添加路由,就像通过 `children` 添加的一样: + +```js +router.addRoute({ name: 'admin', path: '/admin', component: Admin }) +router.addRoute('admin', { path: 'settings', component: AdminSettings }) +``` + +这等效于: + +```js +router.addRoute({ + name: 'admin', + path: '/admin', + component: Admin, + children: [{ path: 'settings', component: AdminSettings }], +}) +``` + +## 查看现有路由 + +Vue Router提供了两个功能来查看现有的路由: + +- [`router.hasRoute()`](/zh/api/#hasroute):检查路由是否存在。 +- [`router.getRoutes()`](/zh/api/#getroutes):获取一个包含所有路由记录的数组。 diff --git a/docs/zh/guide/advanced/extending-router-link.md b/docs/zh/guide/advanced/extending-router-link.md new file mode 100644 index 00000000..4fd95bc6 --- /dev/null +++ b/docs/zh/guide/advanced/extending-router-link.md @@ -0,0 +1,92 @@ +# 扩展 RouterLink + +RouterLink 组件提供了足够的 `props` 来满足大多数基本应用程序的需求,但它并未尝试涵盖所有可能的用例,在某些高级情况下,你可能会发现自己使用了 `v-slot`。在大多数中型到大型应用程序中,值得创建一个(如果不是多个)自定义 RouterLink 组件,以在整个应用程序中重用它们。例如导航菜单中的链接,处理外部链接,添加 `inactive-class` 等。 + +让我们扩展 RouterLink 来处理外部链接,并在 `AppLink.vue` 文件中添加一个自定义的 `inactive-class`: + +```vue + + + +``` + +如果你喜欢使用渲染函数或创建 `computed` 属性,你可以使用 [Composition API](./composition-api.md) 中的 `useLink` : + +```js +import { RouterLink, useLink } from 'vue-router' + +export default { + name: 'AppLink', + + props: { + // 如果使用 TypeScript,请添加 @ts-ignore + ...RouterLink.props, + inactiveClass: String, + }, + + setup(props) { + // toRef 允许我们提取一个 prop 并保持它的响应 + // https://v3.vuejs.org/api/refs-api.html#toref + const { navigate, href, route, isActive, isExactActive } = useLink( + toRef(props, 'to') + ) + + // profit! + + return { isExternalLink } + }, +} +``` + +在实践中,你可能希望将你的 `AppLink` 组件用于应用程序的不同部分。例如,使用 [Tailwind CSS](https://tailwindcss.com),你可以用所有的类创建一个 `NavLink.vue` 组件: + +```vue + +``` + diff --git a/docs/zh/guide/advanced/lazy-loading.md b/docs/zh/guide/advanced/lazy-loading.md new file mode 100644 index 00000000..5f0f5c48 --- /dev/null +++ b/docs/zh/guide/advanced/lazy-loading.md @@ -0,0 +1,51 @@ +# 路由懒加载 + +当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就会更加高效。 + +Vue Router 支持开箱即用的[动态导入](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_Imports),这意味着你可以用动态导入代替静态导入: + +```js +// 将 +// import UserDetails from './views/UserDetails' +// 替换成 +const UserDetails = () => import('./views/UserDetails') + +const router = createRouter({ + // ... + routes: [{ path: '/users/:id', component: UserDetails }], +}) +``` + +`component` (和 `components`) 配置接收一个返回 Promise 组件的函数,Vue Router **只会在第一次进入页面时才会获取这个函数**,然后使用缓存数据。这意味着你也可以使有更复杂的函数,只要它们返回一个 Promise : + +```js +const UserDetails = () => + Promise.resolve({ + /* 组件定义 */ + }) +``` + +一般来说,对所有的路由**都使用动态导入**是个好主意。 + +::: tip 注意 +**不要**在路由中使用[异步组件](https://v3.vuejs.org/guide/component-dynamic-async.html#async-components)。异步组件仍然可以在路由组件中使用,但路由组件本身就是动态导入的。 +::: + +如果你使用的是 webpack 之类的打包器,它将自动从[代码分割](https://webpack.js.org/guides/code-splitting/)中受益。 + +如果你使用的是 Babel,你将需要添加 [syntax-dynamic-import](https://babeljs.io/docs/plugins/syntax-dynamic-import/) 插件,才能使 Babel 正确地解析语法。 + +## 把组件按组分块 + +有时候我们想把某个路由下的所有组件都打包在同个异步块 (chunk) 中。只需要使用[命名 chunk](https://webpack.js.org/guides/code-splitting/#dynamic-imports),一个特殊的注释语法来提供 chunk name (需要 Webpack > 2.4): + +```js +const UserDetails = () => + import(/* webpackChunkName: "group-user" */ './UserDetails.vue') +const UserDashboard = () => + import(/* webpackChunkName: "group-user" */ './UserDashboard.vue') +const UserProfileEdit = () => + import(/* webpackChunkName: "group-user" */ './UserProfileEdit.vue') +``` + +webpack 会将任何一个异步模块与相同的块名称组合到相同的异步块中。 diff --git a/docs/zh/guide/advanced/meta.md b/docs/zh/guide/advanced/meta.md new file mode 100644 index 00000000..6a0bdd4b --- /dev/null +++ b/docs/zh/guide/advanced/meta.md @@ -0,0 +1,67 @@ +# 路由元信息 + +有时,你可能希望将任意信息附加到路由上,如过渡名称、谁可以访问路由等。这些事情可以通过接收属性对象的`meta`属性来实现,并且它可以在路由地址和导航守卫上都被访问到。定义路由的时候你可以这样配置 `meta` 字段: + +```js +const routes = [ + { + path: '/posts', + component: PostsLayout, + children: [ + { + path: 'new', + component: PostsNew, + // 只有经过身份验证的用户才能创建帖子 + meta: { requiresAuth: true } + }, + { + path: ':id', + component: PostsDetail + // 任何人都可以阅读文章 + meta: { requiresAuth: false } + } + ] + } +] +``` + +那么如何访问这个 `meta` 字段呢? + + + +首先,我们称呼 `routes` 配置中的每个路由对象为 **路由记录**。路由记录可以是嵌套的,因此,当一个路由匹配成功后,它可能匹配多个路由记录。 + +例如,根据上面的路由配置,`/posts/new` 这个 URL 将会匹配父路由记录 (`path: '/posts'`) 以及子路由记录 (`path: 'new'`)。 + +一个路由匹配到的所有路由记录会暴露为 `$route` 对象(还有在导航守卫中的路由对象)的`$route.matched` 数组。我们需要遍历这个数组来检查路由记录中的 `meta` 字段,但是 Vue Router 还为你提供了一个 `$route.meta` 方法,它是一个非递归合并**所有 `meta`** 字段的(从父字段到子字段)的方法。这意味着你可以简单地写 + +```js +router.beforeEach((to, from) => { + // 而不是去检查每条路由记录 + // to.matched.some(record => record.meta.requiresAuth) + if (to.meta.requiresAuth && !auth.isLoggedIn()) { + // 此路由需要授权,请检查是否已登录 + // 如果没有,则重定向到登录页面 + return { + path: '/login', + // 保存我们所在的位置,以便以后再来 + query: { redirect: to.fullPath }, + } + } +}) +``` + +## TypeScript + +可以通过扩展 `RouteMeta` 接口来输入 meta 字段: + +```ts +declare module 'vue-router' { + interface RouteMeta { + // 是可选的 + isAdmin?: boolean + // 每个路由都必须声明 + requiresAuth: boolean + } +} +``` diff --git a/docs/zh/guide/advanced/navigation-failures.md b/docs/zh/guide/advanced/navigation-failures.md new file mode 100644 index 00000000..d688bfd2 --- /dev/null +++ b/docs/zh/guide/advanced/navigation-failures.md @@ -0,0 +1,93 @@ +# 等待导航结果 + +当使用 `router-link` 组件时,Vue Router 会自动调用 `router.push` 来触发一次导航。虽然大多数链接的预期行为是将用户导航到一个新页面,但也有少数情况下用户将留在同一页面上: + +- 用户已经位于他们正在尝试导航到的页面 +- 一个[导航守卫](./navigation-guards.md)通过调用 `return false` 中断了这次导航 +- 当前的导航守卫还没有完成时,一个新的导航守卫会出现了 +- 一个[导航守卫](./navigation-guards.md)通过返回一个新的位置,重定向到其他地方 (例如,`return '/login'`) +- 一个[导航守卫](./navigation-guards.md)抛出了一个 `Error` + +如果我们想在一个导航完成后做一些事情,我们需要一个在调用 `router.push` 后进行等待的方法。想象一下,我们有一个移动手机菜单,它允许我们进入不同的页面,而我们只想在导航到新页面后隐藏菜单,我们可能想这样做: + +```js +router.push('/my-profile') +this.isMenuOpen = false +``` + +但是这样做会马上关闭菜单,因为 **导航是异步的**,我们需要 `await` `router.push` 返回的 promise : + +```js +await router.push('/my-profile') +this.isMenuOpen = false +``` + +现在,一旦导航完成,菜单就会关闭,但如果导航被阻止,它也会关闭。我们需要一种方法来检测我们是否真的改变了页面。 + +## 检测导航故障 + +如果导航被阻止,导致用户停留在同一个页面上,由 `router.push` 返回的 `Promise` 的解析值将是 _Navigation Failure_。否则,它将是一个 _falsy_ 值(通常是 `undefined`)。这样我们就可以区分我们导航是否离开了当前位置: + +```js +const navigationResult = await router.push('/my-profile') + +if (navigationResult) { + // 导航被阻止 +} else { + // 导航成功 (包括重新导航的情况) + this.isMenuOpen = false +} +``` + +_Navigation Failure_ 是带有一些额外属性的 `Error` 实例,这些属性为我们提供了足够的信息,让我们知道哪些导航被阻止了以及为什么被阻止了。要检查导航结果的性质,请使用 `isNavigationFailure` 函数: + +```js +import { NavigationFailureType, isNavigationFailure } from 'vue-router' + +// 试图离开未保存的编辑文本界面 +const failure = await router.push('/articles/2') + +if (isNavigationFailure(failure, NavigationFailureType.aborted)) { + // 给用户显示一个小通知 + showToast('You have unsaved changes, discard and leave anyway?') +} +``` + +::: tip +如果你忽略第二个参数: `isNavigationFailure(failure)`,那么就只会检查这个 `failure` 是不是一个 _Navigation Failure_。 +::: + +## 鉴别导航故障 + +正如我们在一开始所说的,有不同的情况会导致导航的中止,所有这些情况都会导致不同的 _Navigation Failure_。它们可以用 `isNavigationFailure` 和 `NavigationFailureType` 来区分。总共有三种不同的类型: + +- `aborted`:在导航守卫中返回 `false` 中断了本次导航。 +- `cancelled`: 在当前导航还没有完成之前又有了一个新的导航。比如,在等待导航守卫的过程中又调用了 `router.push`。 +- `duplicated`:导航被阻止,因为我们已经在目标位置了。 + +## *导航故障*的属性 + +所有的导航失败都会暴露 `to` 和 `from` 属性,以反映失败导航的当前位置和目标位置: + +```js +// 正在尝试访问 admin 页面 +router.push('/admin').then(failure => { + if (isNavigationFailure(failure, NavigationFailureType.redirected)) { + failure.to.path // '/admin' + failure.from.path // '/' + } +}) +``` + +在所有情况下,`to` 和 `from` 都是规范化的路由地址。 + +## 检测重定向 + +当在导航守卫中返回一个新的位置时,我们会触发一个新的导航,覆盖正在进行的导航。与其他返回值不同的是,重定向不会阻止导航,**而是创建一个新的导航**。因此,通过读取路由地址中的 `redirectedFrom` 属性,对其进行不同的检查: + +```js +await router.push('/my-profile') +if (router.currentRoute.value.redirectedFrom) { + // redirectedFrom 是解析出的路由地址,就像导航守卫中的 to和 from +} +``` diff --git a/docs/zh/guide/advanced/navigation-guards.md b/docs/zh/guide/advanced/navigation-guards.md new file mode 100644 index 00000000..bbef3667 --- /dev/null +++ b/docs/zh/guide/advanced/navigation-guards.md @@ -0,0 +1,244 @@ +# 导航守卫 + +正如其名,vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。这里有很多方式植入路由导航中:全局的,单个路由独享的,或者组件级的。 + +## 全局前置守卫 + +你可以使用 `router.beforeEach` 注册一个全局前置守卫: + +```js +const router = createRouter({ ... }) + +router.beforeEach((to, from) => { + // ... + // 返回 false 以取消导航 + return false +}) +``` + +当一个导航触发时,全局前置守卫按照创建顺序调用。守卫是异步解析执行,此时导航在所有守卫 resolve 完之前一直处于**等待中**。 + +每个守卫方法接收两个参数: + +- **`to`**: 即将要进入的目标 [用一种标准化的方式](/zh/api/#routelocationnormalized) +- **`from`**: 当前导航正要离开的路由 [用一种标准化的方式](/zh/api/#routelocationnormalized) + +可以返回的值如下: + +- `false`: 取消当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 `from` 路由对应的地址。 +- 一个[路由地址](/zh/api/#routelocationraw): 通过一个路由地址跳转到一个不同的地址,就像你调用 [`router.push()`](/zh/api/#push) 一样,你可以设置诸如 `replace: true` 或 `name: 'home'` 之类的配置。当前的导航被中断,然后进行一个新的导航,就和 `from` 一样。 + +如果遇到了意料之外的情况,可能会抛出一个 `Error`。这会取消导航并且调用 [`router.onError()`](/zh/api/#onerror) 注册过的回调。 + +如果什么都没有,`undefined` 或返回 `true`,**则导航是有效的**,并调用下一个导航守卫 + +以上所有都同 **`async` 函数** 和 Promise 工作方式一样: + +```js +router.beforeEach(async (to, from) => { + // canUserAccess() 返回 `true` 或 `false` + return await canUserAccess(to) +}) +``` + +### 可选的第三个参数 `next` + +在之前的 Vue Router 版本中,也是可以使用 _第三个参数_ `next` 的。这是一个常见的错误来源,可以通过 [RFC](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0037-router-return-guards.md#motivation) 来消除错误。然而,它仍然是被支持的,这意味着你可以向任何导航守卫传递第三个参数。在这种情况下,**确保 `next`** 在任何给定的导航守卫中都被**严格调用一次**。它可以出现多于一次,但是只能在所有的逻辑路径都不重叠的情况下,否则钩子永远都不会被解析或报错。这里有一个在用户未能验证身份时重定向到`/login`的**错误用例**: + +```js +// BAD +router.beforeEach((to, from, next) => { + if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) + // 如果用户未能验证身份,则 `next` 会被调用两次 + next() +}) +``` + +下面是正确的版本: + +```js +// GOOD +router.beforeEach((to, from, next) => { + if (to.name !== 'Login' && !isAuthenticated) next({ name: 'Login' }) + else next() +}) +``` + +## 全局解析守卫 + +你可以用 `router.beforeResolve` 注册一个全局守卫。这和 `router.beforeEach` 类似,因为它在 **每次导航**时都会触发,但是确保在导航被确认之前,**同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被正确调用**。这里有一个例子,确保用户可以访问[自定义 meta](./meta.md) 属性 `requiresCamera` 的路由: + +```js +router.beforeResolve(async to => { + if (to.meta.requiresCamera) { + try { + await askForCameraPermission() + } catch (error) { + if (error instanceof NotAllowedError) { + // ... 处理错误,然后取消导航 + return false + } else { + // 意料之外的错误,取消导航并把错误传给全局处理器 + throw error + } + } + } +}) +``` + +`router.beforeResolve` 是一个理想的位置,可以在用户无法进入页面的情况下,获取数据或进行任何其他你想避免的操作。 + + + +## 全局后置钩子 + +你也可以注册全局后置钩子,然而和守卫不同的是,这些钩子不会接受 `next` 函数也不会改变导航本身: + +```js +router.afterEach((to, from) => { + sendToAnalytics(to.fullPath) +}) +``` + + + +它们对于分析、更改页面标题、声明页面等辅助功能以及许多其他事情都很有用。 + +它们也反映了 [navigation failures](./navigation-failures.md) 作为第三个参数: + +```js +router.afterEach((to, from, failure) => { + if (!failure) sendToAnalytics(to.fullPath) +}) +``` + +了解更多关于 navigation failures 的信息在[它的指南](./navigation-failures.md)中。 + +## 路由独享的守卫 + +你可以直接在路由配置上定义 `beforeEnter` 守卫: + +```js +const routes = [ + { + path: '/users/:id', + component: UserDetails, + beforeEnter: (to, from) => { + // reject the navigation + return false + }, + }, +] +``` + +`beforeEnter` 守卫 **只在进入路由时触发**,不会在 `params`、`query` 或 `hash` 改变时触发。例如,从 `/users/2` 进入到 `/users/3` 或者从 `/users/2#info` 进入到 `/users/2#projects`。它们只有在 **从一个不同的** 路由导航时,才会被触发。 + +你也可以将一个函数数组传递给 `beforeEnter`,这在为不同的路由重用守卫时很有用: + +```js +function removeQueryParams(to) { + if (Object.keys(to.query).length) + return { path: to.path, query: {}, hash: to.hash } +} + +function removeHash(to) { + if (to.hash) return { path: to.path, query: to.query, hash: '' } +} + +const routes = [ + { + path: '/users/:id', + component: UserDetails, + beforeEnter: [removeQueryParams, removeHash], + }, + { + path: '/about', + component: UserDetails, + beforeEnter: [removeQueryParams], + }, +] +``` + +请注意,你也可以通过使用[路径 meta 字段](./meta.md)和[全局导航守卫](#global-before-guards)来实现类似的行为。 + +## 组件内的守卫 + +最后,你可以在路由组件内直接定义路由导航守卫(传递给路由配置的) + +### 可用的配置 API + +你可以为路由组件添加以下配置: + +- `beforeRouteEnter` +- `beforeRouteUpdate` +- `beforeRouteLeave` + +```js +const UserDetails = { + template: `...`, + beforeRouteEnter(to, from) { + // 在渲染该组件的对应路由被验证前调用 + // 不能获取组件实例 `this` ! + // 因为当守卫执行时,组件实例还没被创建! + }, + beforeRouteUpdate(to, from) { + // 在当前路由改变,但是该组件被复用时调用 + // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候, + // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。 + // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this` + }, + beforeRouteLeave(to, from) { + // 在导航离开渲染该组件的对应路由时调用 + // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this` + }, +} +``` + +`beforeRouteEnter` 守卫 **不能** 访问 `this`,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。 + +不过,你可以通过传一个回调给 `next` 来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数: + +```js +beforeRouteEnter (to, from, next) { + next(vm => { + // 通过 `vm` 访问组件实例 + }) +} +``` + +注意 `beforeRouteEnter` 是支持给 `next` 传递回调的唯一守卫。对于 `beforeRouteUpdate` 和 `beforeRouteLeave` 来说,`this` 已经可用了,所以*不支持* 传递回调,因为没有必要了: + +```js +beforeRouteUpdate (to, from) { + // just use `this` + this.name = to.params.name +} +``` + +这个 **离开守卫** 通常用来预防用户在还未保存修改前突然离开。该导航可以通过返回 `false` 来取消。 + +```js +beforeRouteLeave (to, from) { + const answer = window.confirm('Do you really want to leave? you have unsaved changes!') + if (!answer) return false +} +``` + +### 使用组合 API + +如果你正在使用[组合 API 和 `setup` 函数](https://v3.vuejs.org/guide/composition-api-setup.html#setup)来编写组件,你可以通过 `onBeforeRouteUpdate` 和 `onBeforeRouteLeave` 分别添加 update 和 leave 守卫。 请参考[组合 API 部分](./composition-api.md#导航守卫)以获得更多细节。 + +## 完整的导航解析流程 + +1. 导航被触发。 +2. 在失活的组件里调用 `beforeRouteLeave` 守卫。 +3. 调用全局的 `beforeEach` 守卫。 +4. 在重用的组件里调用 `beforeRouteUpdate` 守卫(2.2+)。 +5. 在路由配置里调用 `beforeEnter`。 +6. 解析异步路由组件。 +7. 在被激活的组件里调用 `beforeRouteEnter`。 +8. 调用全局的 `beforeResolve` 守卫(2.5+)。 +9. 导航被确认。 +10. 调用全局的 `afterEach` 钩子。 +11. 触发 DOM 更新。 +12. 调用 `beforeRouteEnter` 守卫中传给 `next` 的回调函数,创建好的组件实例会作为回调函数的参数传入。 diff --git a/docs/zh/guide/advanced/scroll-behavior.md b/docs/zh/guide/advanced/scroll-behavior.md new file mode 100644 index 00000000..e370cbf8 --- /dev/null +++ b/docs/zh/guide/advanced/scroll-behavior.md @@ -0,0 +1,109 @@ +# 滚动行为 + +使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。 + +**注意: 这个功能只在支持 history.pushState 的浏览器中可用。** + +当创建一个 Router 实例,你可以提供一个 `scrollBehavior` 方法: + +```js +const router = createRouter({ + history: createWebHashHistory(), + routes: [...], + scrollBehavior (to, from, savedPosition) { + // return 期望滚动到哪个的位置 + } +}) +``` + +`scrollBehavior` 函数接收 `to`和` from` 路由对象,如 [Navigation Guards](./navigation-guards.md)。第三个参数 `savedPosition`,只有当这是一个 `popstate` 导航时才可用(由浏览器的后退/前进按钮触发)。 + +该函数可以返回一个 [`ScrollToOptions`](https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions) 位置对象: + +```js +const router = createRouter({ + scrollBehavior(to, from, savedPosition) { + // 始终滚动到顶部 + return { top: 0 } + }, +}) +``` + +你也可以通过 `el` 传递一个 CSS 选择器或一个 DOM 元素。在这种情况下,`top` 和 `left` 将被视为该元素的相对偏移量。 + +```js +const router = createRouter({ + scrollBehavior(to, from, savedPosition) { + // 始终在元素 #main 上方滚动 10px + return { + // 也可以这么写 + // el: document.getElementById('main'), + el: '#main', + top: -10, + } + }, +}) +``` + +如果返回一个 falsy 的值,或者是一个空对象,那么不会发生滚动。 + +返回 `savedPosition`,在按下 后退/前进 按钮时,就会像浏览器的原生表现那样: + +```js +const router = createRouter({ + scrollBehavior(to, from, savedPosition) { + if (savedPosition) { + return savedPosition + } else { + return { top: 0 } + } + }, +}) +``` + +如果你要模拟 “滚动到锚点” 的行为: + +```js +const router = createRouter({ + scrollBehavior(to, from, savedPosition) { + if (to.hash) { + return { + el: to.hash, + } + } + }, +}) +``` + +如果你的浏览器支持[滚动行为](https://developer.mozilla.org/en-US/docs/Web/API/ScrollToOptions/behavior),你可以让它变得更流畅: + +```js +const router = createRouter({ + scrollBehavior(to, from, savedPosition) { + if (to.hash) { + return { + el: to.hash + behavior: 'smooth', + } + } + } +}) +``` + +## 延迟滚动 + +有时候,我们需要在页面中滚动之前稍作等待。例如,当处理过渡时,我们希望等待过渡结束后再滚动。要做到这一点,你可以返回一个 Promise,它可以返回所需的位置描述符。下面是一个例子,我们在滚动前等待 500ms: + +```js +const router = createRouter({ + scrollBehavior(to, from, savedPosition) { + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve({ left: 0, top: 0 }) + }, 500) + }) + }, +}) +``` + +我们可以将其与页面级过渡组件的事件挂钩,以使滚动行为与你的页面过渡很好地结合起来,但由于使用场景可能存在的差异和复杂性,我们只是提供了这个基础来实现特定的用户场景。 diff --git a/docs/zh/guide/advanced/transitions.md b/docs/zh/guide/advanced/transitions.md new file mode 100644 index 00000000..4fd02e86 --- /dev/null +++ b/docs/zh/guide/advanced/transitions.md @@ -0,0 +1,67 @@ +# 过渡动效 + +想要在你的路径组件上使用转场,并对导航进行动画处理,你需要使用 [v-slot API](/zh/api/#router-view-s-v-slot): + +```html + + + + + +``` + +[Transition 的所有功能](https://v3.vuejs.org/guide/transitions-enterleave.html) 在这里同样适用。 + +## 单个路由的过渡 + +上面的用法会对所有的路由使用相同的过渡。如果你想让每个路由的组件有不同的过渡,你可以将[元信息](./meta.md)和动态的 `name` 结合在一起,放在`` 上: + +```js +const routes = [ + { + path: '/custom-transition', + component: PanelLeft, + meta: { transition: 'slide-left' }, + }, + { + path: '/other-transition', + component: PanelRight, + meta: { transition: 'slide-right' }, + }, +] +``` + +```html + + + + + + +``` + +## 基于路由的动态过渡 + +也可以根据目标路由和当前路由之间的关系,动态地确定使用的过渡。使用和刚才非常相似的片段: + +```html + + + + + + +``` + +我们可以添加一个 [after navigation hook](./navigation-guards.md#全局后置钩子),根据路径的深度动态添加信息到 `meta` 字段。 + +```js +router.afterEach((to, from) => { + const toDepth = to.path.split('/').length + const fromDepth = from.path.split('/').length + to.meta.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left' +}) +``` + + + diff --git a/docs/zh/guide/essentials/dynamic-matching.md b/docs/zh/guide/essentials/dynamic-matching.md new file mode 100644 index 00000000..496659fd --- /dev/null +++ b/docs/zh/guide/essentials/dynamic-matching.md @@ -0,0 +1,106 @@ +# 带参数的动态路由匹配 + +很多时候,我们需要将给定匹配模式的路由映射到同一个组件。例如,我们可能有一个 `User` 组件,它应该对所有用户进行渲染,但用户 ID 不同。在 Vue Router 中,我们可以在路径中使用一个动态段来实现,我们称之为 _路径参数_ : + +```js +const User = { + template: '
    User
    ', +} + +// 这些都会传递给 `createRouter` +const routes = [ + // 动态段以冒号开始 + { path: '/users/:id', component: User }, +] +``` + +现在像 `/users/johnny` 和 `/users/jolyne` 这样的 URL 都会映射到同一个路由。 + +_路径参数_ 用冒号 `:` 表示。当一个路由被匹配时,它的 _params_ 的值将在每个组件中以 `this.$route.params` 的形式暴露出来。因此,我们可以通过更新 `User` 的模板来呈现当前的用户 ID: + +```js +const User = { + template: '
    User {{ $route.params.id }}
    ', +} +``` + +你可以在同一个路由中设置有多个 _路径参数_,它们会映射到 `$route.params` 上的相应字段。例如: + +| 匹配模式 | 匹配路径 | \$route.params | +| ------------------------------ | ------------------------ | ---------------------------------------- | +| /users/:username | /users/eduardo | `{ username: 'eduardo' }` | +| /users/:username/posts/:postId | /users/eduardo/posts/123 | `{ username: 'eduardo', postId: '123' }` | + +除了 `$route.params` 之外,`$route` 对象还公开了其他有用的信息,如 `$route.query`(如果 URL 中存在参数)、`$route.hash` 等。你可以在 [API 参考](/zh/api/#routelocationnormalized)中查看完整的细节。 + +这个例子的 demo 可以在[这里](https://codesandbox.io/s/route-params-vue-router-examples-mlb14?from-embed&initialpath=%2Fusers%2Feduardo%2Fposts%2F1)找到。 + + + +## 响应路由参数的变化 + +使用带有参数的路由时需要注意的是,当用户从 `/users/johnny` 导航到 `/users/jolyne` 时,**相同的组件实例将被重复使用**。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。**不过,这也意味着组件的生命周期钩子不会被调用**。 + +要对同一个组件中参数的变化做出响应的话,你可以简单地 watch `$route` 对象上的任意属性,在这个场景中,就是 `$route.params` : + +```js +const User = { + template: '...', + created() { + this.$watch( + () => this.$route.params, + (toParams, previousParams) => { + // 对路由变化做出响应... + } + ) + }, +} +``` + +或者,使用 `beforeRouteUpdate` [导航守卫](../advanced/navigation-guards.md),它也可以取消导航: + +```js +const User = { + template: '...', + async beforeRouteUpdate(to, from) { + // 对路由变化做出响应... + this.userData = await fetchUser(to.params.id) + }, +} +``` + +## 捕获所有路由或 404 Not found 路由 + +常规参数只匹配 url 片段之间的字符,用 `/` 分隔。如果我们想匹配**任意路径**,我们可以使用自定义的 _路径参数_ 正则表达式,在 _路径参数_ 后面的括号中加入 正则表达式 : + +```js +const routes = [ + // 将匹配所有内容并将其放在 `$route.params.pathMatch` 下 + { path: '/:pathMatch(.*)*', name: 'NotFound', component: NotFound }, + // 将匹配以 `/user-` 开头的所有内容,并将其放在 `$route.params.afterUser` 下 + { path: '/user-:afterUser(.*)', component: UserGeneric }, +] +``` + +在这个特定的场景中,我们在括号之间使用了[自定义正则表达式](/zh/guide/essentials/route-matching-syntax.md#在参数中自定义正则),并将`pathMatch` 参数标记为[可选可重复](/zh/guide/essentials/route-matching-syntax.md#可选参数)。这样做是为了让我们在需要的时候,可以通过将 `path` 拆分成一个数组,直接导航到路由: + +```js +this.$router.push({ + name: 'NotFound', + params: { pathMatch: this.$route.path.split('/') }, +}) +``` + +更多内容请参见[重复参数](/zh/guide/essentials/route-matching-syntax.md#可重复的参数)部分。 + +如果你正在使用[历史模式](./history-mode.md),请务必按照说明正确配置你的服务器。 + +## 高级匹配模式 + +Vue Router 使用自己的路径匹配语法,其灵感来自于 `express`,因此它支持许多高级匹配模式,如可选的参数,零或多个 / 一个或多个,甚至自定义的正则匹配规则。请查看[高级匹配](./route-matching-syntax.md)文档来探索它们。 diff --git a/docs/zh/guide/essentials/history-mode.md b/docs/zh/guide/essentials/history-mode.md new file mode 100644 index 00000000..69cf4165 --- /dev/null +++ b/docs/zh/guide/essentials/history-mode.md @@ -0,0 +1,175 @@ +# 不同的历史模式 + +在创建路由器实例时,`history` 配置允许我们在不同的历史模式中进行选择。 + +## Hash 模式 + +hash 模式是用 `createWebHashHistory()` 创建的: + +```js +import { createRouter, createWebHashHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHashHistory(), + routes: [ + //... + ], +}) +``` + +它在内部传递的实际 URL 之前使用了一个哈希字符(`#`)。由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理。不过,**它在SEO中确实有不好的影响**。如果你担心这个问题,可以使用 HTML5 模式。 + +## HTML5 模式 + +用 `createWebHistory()` 创建 HTML5 模式,推荐使用这个模式: + +```js +import { createRouter, createWebHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + //... + ], +}) +``` + +当使用这种历史模式时,URL 会看起来很 "正常",例如 `https://example.com/user/id`。漂亮! + +不过,问题来了。由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置,用户在浏览器中直接访问 `https://example.com/user/id`,就会得到一个 404 错误。这就丑了。 + +Not to worry: To fix the issue, all you need to do is add a simple catch-all fallback route to your server. If the URL doesn't match any static assets, it should serve the same `index.html` page that your app lives in. Beautiful, again! +不用担心:要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 `index.html` 相同的页面。漂亮依旧! + +## 服务器配置示例 + +**注意**:以下示例假定你正在从根目录提供服务。如果你部署到子目录,你应该使用[Vue CLI的 `publicPath` 配置](https://cli.vuejs.org/config/#publicpath)和相关的[路由器的 `base` 属性](/zh/api/#createwebhistory)。你还需要调整下面的例子,以使用子目录而不是根目录(例如,将`RewriteBase/` 替换为 `RewriteBase/name-of-your-subfolder/`)。 + +### Apache + +```apacheconf + + RewriteEngine On + RewriteBase / + RewriteRule ^index\.html$ - [L] + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_FILENAME} !-d + RewriteRule . /index.html [L] + +``` + +也可以使用 [`FallbackResource`](https://httpd.apache.org/docs/2.2/mod/mod_dir.html#fallbackresource) 代替 `mod_rewrite`。 +### nginx + +```nginx +location / { + try_files $uri $uri/ /index.html; +} +``` + +### 原生 Node.js + +```js +const http = require('http') +const fs = require('fs') +const httpPort = 80 + +http + .createServer((req, res) => { + fs.readFile('index.htm', 'utf-8', (err, content) => { + if (err) { + console.log('We cannot open "index.htm" file.') + } + + res.writeHead(200, { + 'Content-Type': 'text/html; charset=utf-8', + }) + + res.end(content) + }) + }) + .listen(httpPort, () => { + console.log('Server listening on: http://localhost:%s', httpPort) + }) +``` + +### Express + Node.js + +对于 Node.js/Express,可以考虑使用 [connect-history-api-fallback 中间件](https://github.com/bripkens/connect-history-api-fallback)。 + +### Internet Information Services (IIS) + +1. 安装 [IIS UrlRewrite](https://www.iis.net/downloads/microsoft/url-rewrite) +2. 在网站的根目录下创建一个 `web.config` 文件,内容如下: + +```xml + + + + + + + + + + + + + + + + + +``` + +### Caddy + +``` +rewrite { + regexp .* + to {path} / +} +``` + +### Firebase hosting + +将此添加到你的 `firebase.json` 中: + +```json +{ + "hosting": { + "public": "dist", + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + } +} +``` + +### Netlify + +创建一个 `_redirects` 文件,包含在你的部署文件中: + +``` +/* /index.html 200 +``` + +在 vue-cli、nuxt 和 vite 项目中,这个文件通常放在名为 `static` 或 `public` 的目录下。 + +你可以在 [Netlify 文档](https://docs.netlify.com/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps)中找到更多关于语法的信息。你也可以[创建一个 `netlify.toml`](https://docs.netlify.com/configure-builds/file-based-configuration/) 来结合其他 Netlify 功能的重定向。 + +## Caveat + +这有一个注意事项。你的服务器将不再报告 404 错误,因为现在所有未找到的路径都会显示你的 `index.html` 文件。为了解决这个问题,你应该在你的 Vue 应用程序中实现一个万能的路由来显示 404 页面。 + +```js +const router = createRouter({ + history: createWebHistory(), + routes: [{ path: '/:pathMatch(.*)', component: NotFoundComponent }], +}) +``` + +另外,如果你使用的是 Node.js 服务器,你可以通过在服务器端使用路由器来匹配传入的 URL,如果没有匹配到路由,则用 404 来响应,从而实现回退。查看 [Vue 服务器端渲染文档](https://ssr.vuejs.org/en/)了解更多信息。 diff --git a/docs/zh/guide/essentials/named-routes.md b/docs/zh/guide/essentials/named-routes.md new file mode 100644 index 00000000..1f34933d --- /dev/null +++ b/docs/zh/guide/essentials/named-routes.md @@ -0,0 +1,36 @@ +# 命名路由 + +除了 `path` 之外,你还可以为任何路由提供 `name`。这有以下优点: + +- 没有硬编码的 URL +- `params` 的自动编码/解码。 +- 防止你在 url 中出现打字错误。 +- 绕过路径排序(如显示一个) + +```js +const routes = [ + { + path: '/user/:username', + name: 'user', + component: User + } +] +``` + +要链接到一个命名的路由,可以向 `router-link` 组件的 `to` 属性传递一个对象: + +```html + + User + +``` + +这跟代码调用 `router.push()` 是一回事: + +```js +router.push({ name: 'user', params: { username: 'erina' } }) +``` + +在这两种情况下,路由将导航到路径 `/user/erina`。 + +完整的例子[这里](https://github.com/vuejs/vue-router/blob/dev/examples/named-routes/app.js). diff --git a/docs/zh/guide/essentials/named-views.md b/docs/zh/guide/essentials/named-views.md new file mode 100644 index 00000000..4c450449 --- /dev/null +++ b/docs/zh/guide/essentials/named-views.md @@ -0,0 +1,87 @@ +# 命名视图 + +有时候想同时 (同级) 展示多个视图,而不是嵌套展示,例如创建一个布局,有 `sidebar` (侧导航) 和 `main` (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 `router-view` 没有设置名字,那么默认为 `default`。 + +```html + + + +``` + +一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 `components` 配置 (带上 **s**): + +```js +const router = createRouter({ + history: createWebHashHistory(), + routes: [ + { + path: '/', + components: { + default: Home, + // LeftSidebar: LeftSidebar 的缩写 + LeftSidebar, + // 它们与 `` 上的 `name` 属性匹配 + RightSidebar, + }, + }, + ], +}) +``` + +以上案例相关的可运行代码请[移步这里](https://codesandbox.io/s/named-views-vue-router-4-examples-rd20l). + +## 嵌套命名视图 + +我们也有可能使用命名视图创建嵌套视图的复杂布局。这时你也需要命名用到的嵌套 `router-view` 组件。我们以一个设置面板为例: + +``` +/settings/emails /settings/profile ++-----------------------------------+ +------------------------------+ +| UserSettings | | UserSettings | +| +-----+-------------------------+ | | +-----+--------------------+ | +| | Nav | UserEmailsSubscriptions | | +------------> | | Nav | UserProfile | | +| | +-------------------------+ | | | +--------------------+ | +| | | | | | | | UserProfilePreview | | +| +-----+-------------------------+ | | +-----+--------------------+ | ++-----------------------------------+ +------------------------------+ +``` + +- `Nav` 只是一个常规组件。 +- `UserSettings` 是一个视图组件。 +- `UserEmailsSubscriptions`、`UserProfile`、`UserProfilePreview` 是嵌套的视图组件。 + +**注意**:_我们先忘记 HTML/CSS 具体的布局的样子,只专注在用到的组件上。_ + +`UserSettings` 组件的 `