前几天忙于他务,所以没怎么更新过,不过今天周末了,所以补上了前一章节的样例,再更新一章节;接下来可能就没办法这么频繁的更新了,说不定我的新工作都不是前端开发而是转行了,如果是这样的话,这个就变成了纯粹的爱好,哈哈也不错。
今天这一章节,主要是来看看路由的重定向、传参、别名以及 HTML 5 History 模式。
1 重定向
当访问 /a 时,需要实际访问到 /b 这种路由方式,就是重定向,其实也是通过 routes 来完成的,比如:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: '/b' }
]
});
重定向的目标也可以是一个命名的路由:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: { name: 'foo' }}
]
});
当然也可以是一个方法,动态返回重定向目标:
const router = new VueRouter({
routes: [
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
return '/b';
}}
]
});
还有不少有趣的用法,可以参考下面的样例:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const Home = { template: '<router-view></router-view>' };
const Default = { template: '<div>default</div>' };
const Foo = { template: '<div>foo</div>' };
const Bar = { template: '<div>bar</div>' };
const Baz = { template: '<div>baz</div>' };
const WithParams = { template: '<div>{{ $route.params.id }}</div>' };
const Foobar = { template: '<div>foobar</div>' };
const FooBar = { template: '<div>FooBar</div>' };
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
{ path: '/', component: Home,
children: [
{ path: '', component: Default },
{ path: 'foo', component: Foo },
{ path: 'bar', component: Bar },
{ path: 'baz', name: 'baz', component: Baz },
{ path: 'with-params/:id', component: WithParams },
// relative redirect to a sibling route
{ path: 'relative-redirect', redirect: 'foo' }
]
},
// absolute redirect
{ path: '/absolute-redirect', redirect: '/bar' },
// dynamic redirect, note that the target route to
// is available for the redirect function
{ path: '/dynamic-redirect/:id?',
redirect: to => {
const { hash, params, query } = to;
if (query.to === 'foo') {
return { path: '/foo', query: null };
}
if (hash === '#baz') {
return { name: 'baz', hash: '' };
}
if (params.id) {
return '/with-params/:id';
} else {
return '/bar';
}
}
},
// named redirect
{ path: '/named-redirect', redirect: { name: 'baz' }},
// redirect with params
{ path: '/redirect-with-params/:id', redirect: '/with-params/:id' },
// redirect with caseSensitive
{ path: '/foobar', component: Foobar, caseSensitive: true },
// redirect with pathToRegexpOptions
{ path: '/FooBar', component: FooBar, pathToRegexpOptions: { sensitive: true }},
// catch all redirect
{ path: '*', redirect: '/' }
]
});
new Vue({
router,
template: `
<div id="app">
<h1>Redirect</h1>
<ul>
<li><router-link to="/relative-redirect">
/relative-redirect (redirects to /foo)
</router-link></li>
<li><router-link to="/relative-redirect?foo=bar">
/relative-redirect?foo=bar (redirects to /foo?foo=bar)
</router-link></li>
<li><router-link to="/absolute-redirect">
/absolute-redirect (redirects to /bar)
</router-link></li>
<li><router-link to="/dynamic-redirect">
/dynamic-redirect (redirects to /bar)
</router-link></li>
<li><router-link to="/dynamic-redirect/123">
/dynamic-redirect/123 (redirects to /with-params/123)
</router-link></li>
<li><router-link to="/dynamic-redirect?to=foo">
/dynamic-redirect?to=foo (redirects to /foo)
</router-link></li>
<li><router-link to="/dynamic-redirect#baz">
/dynamic-redirect#baz (redirects to /baz)
</router-link></li>
<li><router-link to="/named-redirect">
/named-redirect (redirects to /baz)
</router-link></li>
<li><router-link to="/redirect-with-params/123">
/redirect-with-params/123 (redirects to /with-params/123)
</router-link></li>
<li><router-link to="/foobar">
/foobar
</router-link></li>
<li><router-link to="/FooBar">
/FooBar
</router-link></li>
<li><router-link to="/not-found">
/not-found (redirects to /)
</router-link></li>
</ul>
<router-view class="view"></router-view>
</div>
`
}).$mount('#app');
2 别名
重定向的意思是,当用户访问 /a 时,URL 将会被替换成 /b,然后匹配路由为 /b,别名则是:当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。
其对应的路由配置为:
const router = new VueRouter({
routes: [
{ path: '/a', component: A, alias: '/b' }
]
});
这样对用户(尤其是对 URL 敏感的用户)来说,就不会产生疑问和担心了,更多的样例如下:
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const Root = { template: '<div>root</div>' };
const Home = { template: '<div><h1>Home</h1><router-view></router-view></div>' };
const Foo = { template: '<div>foo</div>' };
const Bar = { template: '<div>bar</div>' };
const Baz = { template: '<div>baz</div>' };
const Default = { template: '<div>default</div>' };
const Nested = { template: '<router-view/>' };
const NestedFoo = { template: '<div>nested foo</div>' };
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
{ path: '/root', component: Root, alias: '/root-alias' },
{ path: '/home', component: Home,
children: [
// absolute alias
{ path: 'foo', component: Foo, alias: '/foo' },
// relative alias (alias to /home/bar-alias)
{ path: 'bar', component: Bar, alias: 'bar-alias' },
// multiple aliases
{ path: 'baz', component: Baz, alias: ['/baz', 'baz-alias'] },
// default child route with empty string as alias.
{ path: 'default', component: Default, alias: '' },
// nested alias
{ path: 'nested', component: Nested, alias: 'nested-alias',
children: [
{ path: 'foo', component: NestedFoo }
]
}
]
}
]
});
new Vue({
router,
template: `
<div id="app">
<h1>Route Alias</h1>
<ul>
<li><router-link to="/root-alias">
/root-alias (renders /root)
</router-link></li>
<li><router-link to="/foo">
/foo (renders /home/foo)
</router-link></li>
<li><router-link to="/home/bar-alias">
/home/bar-alias (renders /home/bar)
</router-link></li>
<li><router-link to="/baz">
/baz (renders /home/baz)
</router-link></li>
<li><router-link to="/home/baz-alias">
/home/baz-alias (renders /home/baz)
</router-link></li>
<li><router-link to="/home">
/home (renders /home/default)
</router-link></li>
<li><router-link to="/home/nested-alias/foo">
/home/nested-alias/foo (renders /home/nested/foo)
</router-link></li>
</ul>
<router-view class="view"></router-view>
</div>
`
}).$mount('#app');
3 路由组件传参
在组件中使用 $route 会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了灵活性。
我们可以使用 props 将组件和路由解耦:
3.1 取代与 $route 的耦合
const User = {
template: '<div>User {{ $route.params.id }}</div>'
};
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
});
3.2 通过 props 解耦
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
};
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 props 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
});
这样便可以在任何地方使用该组件,使得该组件更易于重用和测试。
3.3 布尔模式
如果 props 被设置为 true,route.params 将会被设置为组件属性。
3.4 对象模式
如果 props 是一个对象,它会被按原样设置为组件属性,当 props 是静态的时候生效。
const router = new VueRouter({
routes: [
{ path: '/promotion/from-newsletter',
component: Promotion,
props: { newsletterPopup: false } }
]
});
3.5 函数模式
我们可以创建一个函数返回 props:这样可以将参数转换成另一种类型,将静态值与基于路由的值结合等等。
const router = new VueRouter({
routes: [
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
]
});
URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件。
尽可能保持 props 函数为无状态的,因为它只会在路由发生变化时起作用;如果我们需要状态来定义 props,那么就使用包装组件,这样 Vue 才可以对状态变化做出反应。
更多的例子如下:
import Vue from 'vue';
import VueRouter from 'vue-router';
import Hello from './Hello.vue';
Vue.use(VueRouter);
function dynamicPropsFn (route) {
const now = new Date();
return {
name: (now.getFullYear() + parseInt(route.params.years)) + '!'
};
}
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
// No props, no nothing
{ path: '/', component: Hello },
// Pass route.params to props
{ path: '/hello/:name', component: Hello, props: true },
// static values
{ path: '/static', component: Hello, props: { name: 'world' }},
// custom logic for mapping between route and props
{ path: '/dynamic/:years', component: Hello, props: dynamicPropsFn },
{ path: '/attrs', component: Hello, props: { name: 'attrs' }}
]
});
new Vue({
router,
template: `
<div id="app">
<h1>Route props</h1>
<ul>
<li><router-link to="/">/</router-link></li>
<li><router-link to="/hello/you">/hello/you</router-link></li>
<li><router-link to="/static">/static</router-link></li>
<li><router-link to="/dynamic/1">/dynamic/1</router-link></li>
<li><router-link to="/attrs">/attrs</router-link></li>
</ul>
<router-view class="view" foo="123"></router-view>
</div>
`
}).$mount('#app');
4 HTML5 History 模式
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
const router = new VueRouter({
mode: 'history',
routes: [...]
});
当使用 history 模式时,URL 就像正常的 url,例如 http://mysite.com/user/id,并没有那个有点扎眼的 # 存在。
不过这种模式要玩好,还需要后台配置支持;因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id 就会返回 404,这就不好了。
所以呢,我们需要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是 app 依赖的页面。
4.1 后端配置例子
注意:下列示例假设在根目录服务这个应用。
如果想部署到一个子目录,我们需要使用 ="https://cli.http://vuejs.org/zh/config/#publicpath">Vue CLI 的 publicPath 选项 和相关的 router base property;还需要把下列示例中的根目录调整成为子目录(例如用 RewriteBase /name-of-your-subfolder/ 替换掉 RewriteBase /)。
(1) Apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
除了 mod_rewrite,我们也可以使用 FallbackResource。
(2)nginx
location / {
try_files $uri $uri/ /index.html;
}
(3)原生 Node.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);
});
(4)基于 Node.js 的 Express
对于 Node.js/Express,请考虑使用 <a href="https://github.com/bripkens/connect-history-api-fallback">connect-history-api-fallback 中间件。
(5)Internet Information Services (IIS)
- 安装 IIS UrlRewrite
- 在网站根目录中创建一个 web.config 文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<rewrite>
<rules>
<rule name="Handle History Mode and custom 404/500" stopProcessing="true">
<match url="(.*)" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/" />
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
(6)Caddy
在 Caddyfile 当中加入:
rewrite {
regexp .*
to {path} /
}
(7)Firebase 主机
在 firebase.json 中加入:
{
"hosting": {
"public": "dist",
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
]
}
}
4.2 警告
这里要特别留意,在这么做以后,服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件;为了避免这种情况,我们需要在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '*', component: NotFoundComponent }
]
});
或者,如果我们使用 Node.js 服务器,就可以用服务端路由匹配到来的 URL,并在没有匹配到路由的时候返回 404,以实现回退。
更多详情请查阅 Vue 服务端渲染文档。