|
| 1 | +# 路由和代码分割 |
| 2 | + |
| 3 | +## 使用 `vue-router` 的路由 |
| 4 | + |
| 5 | +你可能已经注意到,我们的服务器代码使用了一个 `*` 处理程序,它接受任意 URL。这允许我们将访问的 URL 传递到我们的 Vue 应用程序中,然后对客户端和服务器复用相同的路由配置! |
| 6 | + |
| 7 | +为此,建议使用官方提供的 `vue-router`。我们首先创建一个文件,在其中创建 router。注意,类似于 `createApp`,我们也需要给每个请求一个新的 router 实例,所以文件导出一个 `createRouter` 函数: |
| 8 | + |
| 9 | +```js |
| 10 | +// router.js |
| 11 | +import Vue from 'vue' |
| 12 | +import Router from 'vue-router' |
| 13 | +Vue.use(Router) |
| 14 | +export function createRouter () { |
| 15 | + return new Router({ |
| 16 | + mode: 'history', |
| 17 | + routes: [ |
| 18 | + // ... |
| 19 | + ] |
| 20 | + }) |
| 21 | +} |
| 22 | +``` |
| 23 | + |
| 24 | +然后更新 `app.js`: |
| 25 | + |
| 26 | +```js |
| 27 | +// app.js |
| 28 | +import Vue from 'vue' |
| 29 | +import App from './App.vue' |
| 30 | +import { createRouter } from './router' |
| 31 | +export function createApp () { |
| 32 | + // 创建 router 实例 |
| 33 | + const router = createRouter() |
| 34 | + const app = new Vue({ |
| 35 | + // 注入 router 到根 Vue 实例 |
| 36 | + router, |
| 37 | + render: h => h(App) |
| 38 | + }) |
| 39 | + // 注入 router 到根 Vue 实例 |
| 40 | + return { app, router } |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +现在我们需要在 `entry-server.js` 中实现服务器端路由逻辑(server-side routing logic): |
| 45 | + |
| 46 | +```js |
| 47 | +// entry-server.js |
| 48 | +import { createApp } from './app' |
| 49 | +export default context => { |
| 50 | + // 因为有可能会是异步路由钩子函数或组件,所以我们将返回一个 Promise, |
| 51 | + // 以便服务器能够等待所有的内容在渲染前, |
| 52 | + // 就已经准备就绪。 |
| 53 | + return new Promise((resolve, reject) => { |
| 54 | + const { app, router } = createApp() |
| 55 | + // 设置服务器端 router 的位置 |
| 56 | + router.push(context.url) |
| 57 | + // 等到 router 将可能的异步组件和钩子函数解析完 |
| 58 | + router.onReady(() => { |
| 59 | + const matchedComponents = router.getMatchedComponents() |
| 60 | + // 匹配不到的路由,执行 reject 函数,并返回 404 |
| 61 | + if (!matchedComponents.length) { |
| 62 | + return reject({ code: 404 }) |
| 63 | + } |
| 64 | + // Promise 应该 resolve 应用程序实例,以便它可以渲染 |
| 65 | + resolve(app) |
| 66 | + }, reject) |
| 67 | + }) |
| 68 | +} |
| 69 | +``` |
| 70 | + |
| 71 | +假设服务器 bundle 已经完成构建(请再次忽略现在的构建设置),服务器用法看起来如下: |
| 72 | + |
| 73 | +```js |
| 74 | +// server.js |
| 75 | +const createApp = require('/path/to/built-server-bundle.js') |
| 76 | +server.get('*', (req, res) => { |
| 77 | + const context = { url: req.url } |
| 78 | + createApp(context).then(app => { |
| 79 | + renderer.renderToString(app, (err, html) => { |
| 80 | + if (err) { |
| 81 | + if (err.code === 404) { |
| 82 | + res.status(404).end('Page not found') |
| 83 | + } else { |
| 84 | + res.status(500).end('Internal Server Error') |
| 85 | + } |
| 86 | + } else { |
| 87 | + res.end(html) |
| 88 | + } |
| 89 | + }) |
| 90 | + }) |
| 91 | +}) |
| 92 | +``` |
| 93 | + |
| 94 | +## 代码分割 |
| 95 | + |
| 96 | +应用程序的代码分割或惰性加载,有助于减少浏览器在初始渲染中下载的资源体积,可以极大地改善大体积 bundle 的可交互时间(TTI - time-to-interactive)。这里的关键在于,对初始首屏而言,"只加载所需"。 |
| 97 | + |
| 98 | +Vue 提供异步组件作为第一类的概念,将其与 [webpack 2 所支持的使用动态导入作为代码分割点](https://webpack.js.org/guides/code-splitting-async/)相结合,你需要做的是: |
| 99 | + |
| 100 | +```js |
| 101 | +// 这里进行修改…… |
| 102 | +import Foo from './Foo.vue' |
| 103 | +// 改为这样: |
| 104 | +const Foo = () => import('./Foo.vue') |
| 105 | +``` |
| 106 | + |
| 107 | +如果你正在构建纯客户端 Vue 应用程序,这将在任何情况下运行。但是,在 SSR 中使用时有一些限制。首先,你需要在启动渲染之前先在服务器上解析所有的异步组件,否则你将在标记中获得一个空的占位符。在客户端,你还需要在开始混合之前执行此操作,否则客户端将遇到内容不匹配错误。 |
| 108 | + |
| 109 | +这使得在应用程序中的任意位置使用异步组件变得有点棘手(我们将来可能会改进这一点)。但是,**如果你在路由级别完成以上,可以实现无缝运行**(即在路由配置中使用异步组件),因为在解析路由时,`vue-router` 将自动解析匹配的异步组件。你需要做的是确保在服务器和客户端上都使用 `router.onReady`。我们已经在我们的服务器入口(server entry)中实现过了,现在我们只需要更新客户端入口(client entry): |
| 110 | + |
| 111 | +```js |
| 112 | +// entry-client.js |
| 113 | +import { createApp } from './app' |
| 114 | +const { app, router } = createApp() |
| 115 | +router.onReady(() => { |
| 116 | + app.$mount('#app') |
| 117 | +}) |
| 118 | +``` |
| 119 | + |
| 120 | +异步路由组件的路由配置示例: |
| 121 | + |
| 122 | +```js |
| 123 | +// router.js |
| 124 | +import Vue from 'vue' |
| 125 | +import Router from 'vue-router' |
| 126 | +Vue.use(Router) |
| 127 | +export function createRouter () { |
| 128 | + return new Router({ |
| 129 | + mode: 'history', |
| 130 | + routes: [ |
| 131 | + { path: '/', component: () => import('./components/Home.vue') }, |
| 132 | + { path: '/item/:id', component: () => import('./components/Item.vue') } |
| 133 | + ] |
| 134 | + }) |
| 135 | +} |
| 136 | +``` |
0 commit comments