前言:关于react只做了几个小demo和一个真前后端分离的项目,调取的配置在nginx上是死的,模仿了一些简书上的简单功能,觉得很没意思,整个项目让人感觉非常僵硬,react生态圈太大了,目前学习react-native的时机也不成熟,所以毅然决然的先学习vue,就门槛来说简直不要比react友好太多,这篇文章主要总结vue-router

vue-cli脚手架配置文件解析

虽然说脚手架目的是为了高效开发,但本着生命不息折腾不止的原则,还是得尽量弄懂,说不定以后会有写个性化的配置需求

脚手架的安装及启动

1
2
3
4
npm install -g vue
nom install -g vue-cli
cd projectdir
vue init webpack(-simple) projectname //这里我们只用webpack模板

目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|-- build                            // 项目构建(webpack)相关代码
| |-- build.js // 生产环境构建代码
| |-- check-version.js // 检查node、npm等版本
| |-- dev-client.js // 热更新配置相关
| |-- dev-server.js // 构建本地服务器
| |-- utils.js // 构建工具函数
| |-- webpack.base.conf.js // webpack基础配置
| |-- webpack.dev.conf.js // webpack开发环境配置
| |-- webpack.prod.conf.js // webpack生产环境配置
|-- config // 项目开发环境配置
| |-- dev.env.js // 开发环境变量
| |-- index.js // 项目一些配置变量
| |-- prod.env.js // 生产环境变量
| |-- test.env.js // 测试环境变量
|-- src // 工作区源码目录
| |-- components // vue公共组件
| |-- store // vuex的状态管理
| |-- App.vue // 项目的总组件,是$router的总载体
| |-- main.js // 程序入口文件,加载各种公共组件
|-- static // 静态文件,比如一些图片,json数据等
| |-- gitkeep
|-- .babelrc // ES6语法编译配置(插件及预设)
|-- .editorconfig // 定义代码格式
|-- .gitignore // git忽略的文件,想想以前还把核心模块一起上传了,很僵硬
|-- README.md
|-- favicon.ico
|-- index.html // js打包后的载体模板
|-- package.json // 项目基本信息及相关依赖

命令字段

1
2
3
4
5
"scripts": {
"dev": "node build/dev-server.js",
"start": "node build/dev-server.js",
"build": "node build/build.js"
}
  • npm run dev 或 npm start : 启动本地服务器,热更新就绪

  • npm run build :将工作区源码打包可上线文件,默认目录放在根目录下dist文件夹,不过打包好了我们也不能直接双击index.html打开,移驾 build/build.js 文件末尾:

1
2
3
4
5
console.log(chalk.cyan('  Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))

他告诉我们这是要部署的上线文件,需要在服务器环境下运行,可是我把它放到Apache服务器环境下仍然不能运行,后来查了资料是它本身打包的径路问题,因为webpack默认打包的路径都是从根目录去访问的,所以把静态资源和index.html放www根目录下就好能正常打开了,但是发现还是不行,因为Apache的初始配置不同于脚手架的路由配置风格,Apache的原则是;一个路径对应一个页面,但我们打包出来就一个页面啊,所以需要对Apache进行相关配置,让所有路径都指向该页面,不过我觉得本地打开这些没什么意义,就好比博客的部署,deploy也是不能用文件系统打开的

dev-server.js

build文件下js文件都是分模块配置的,对应功能划分非常明显,具有良好的拓展性及维护性,先从这开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 检查 Node 和 npm 版本
require('./check-versions')()

// 获取 config/index.js 的默认配置
var config = require('../config')

// 如果 Node 的环境无法判断当前是 dev / product 环境
// 使用 config.dev.env.NODE_ENV 作为当前的环境

if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)

// 使用 NodeJS 自带的文件路径工具
var path = require('path')

// 使用 express
var express = require('express')

// 使用 webpack
var webpack = require('webpack')

// 一个可以强制打开浏览器并跳转到指定 url 的插件
var opn = require('opn')

// 使用 proxyTable
var proxyMiddleware = require('http-proxy-middleware')

// 使用 dev 环境的 webpack 配置
var webpackConfig = require('./webpack.dev.conf')

// default port where dev server listens for incoming traffic

// 如果没有指定运行端口,使用 config.dev.port 作为运行端口
var port = process.env.PORT || config.dev.port

// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware

// 使用 config.dev.proxyTable 的配置作为 proxyTable 的代理配置
var proxyTable = config.dev.proxyTable

// 使用 express 启动一个服务
var app = express()

// 启动 webpack 进行编译
var compiler = webpack(webpackConfig)

// 启动 webpack-dev-middleware,将 编译后的文件暂存到内存中
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
stats: {
colors: true,
chunks: false
}
})

// 启动 webpack-hot-middleware,也就是我们常说的 Hot-reload
var hotMiddleware = require('webpack-hot-middleware')(compiler)
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function (compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
hotMiddleware.publish({ action: 'reload' })
cb()
})
})

// proxy api requests
// 将 proxyTable 中的请求配置挂在到启动的 express 服务上
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
}
app.use(proxyMiddleware(context, options))
})

// handle fallback for HTML5 history API
// 使用 connect-history-api-fallback 匹配资源,如果不匹配就可以重定向到指定地址
app.use(require('connect-history-api-fallback')())

// serve webpack bundle output
// 将暂存到内存中的 webpack 编译后的文件挂在到 express 服务上
app.use(devMiddleware)

// enable hot-reload and state-preserving
// compilation error display
// 将 Hot-reload 挂在到 express 服务上
app.use(hotMiddleware)

// serve pure static assets
// 拼接 static 文件夹的静态资源路径
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
// 为静态资源提供响应服务
app.use(staticPath, express.static('./static'))

// 让我们这个 express 服务监听 port 的请求,并且将此服务作为 dev-server.js 的接口暴露
module.exports = app.listen(port, function (err) {
if (err) {
console.log(err)
return
}
var uri = 'http://localhost:' + port
console.log('Listening at ' + uri + '\n')

// when env is testing, don't need open it
// 如果不是测试环境,自动打开浏览器并跳到我们的开发地址
if (process.env.NODE_ENV !== 'testing') {
opn(uri)
}
})

webpack.dev.conf.js

dev-server.js中引入了webpack.dev.conf.js 和 index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 同样的使用了 config/index.js
var config = require('../config')

// 使用 webpack
var webpack = require('webpack')

// 使用 webpack 配置合并插件
var merge = require('webpack-merge')

// 使用一些小工具
var utils = require('./utils')

// 加载 webpack.base.conf
var baseWebpackConfig = require('./webpack.base.conf')

// 使用 html-webpack-plugin 插件,这个插件可以帮我们自动生成 html 并且注入到 .html 文件中
var HtmlWebpackPlugin = require('html-webpack-plugin')

// add hot-reload related code to entry chunks
// 将 Hol-reload 相对路径添加到 webpack.base.conf 的 对应 entry 前
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})

// 将我们 webpack.dev.conf.js 的配置和 webpack.base.conf.js 的配置合并
module.exports = merge(baseWebpackConfig, {
module: {
// 使用 styleLoaders
loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// eval-source-map is faster for development
// 使用 #eval-source-map 模式作为开发工具,此配置可参考 DDFE 往期文章详细了解
devtool: '#eval-source-map',
plugins: [

// definePlugin 接收字符串插入到代码当中, 所以你需要的话可以写上 JS 的字符串
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.optimize.OccurenceOrderPlugin(),

// HotModule 插件在页面进行变更的时候只会重回对应的页面模块,不会重绘整个 html 文件
new webpack.HotModuleReplacementPlugin(),

// 使用了 NoErrorsPlugin 后页面中的报错不会阻塞,但是会在编译结束后报错
new webpack.NoErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin

// 将 index.html 作为入口,注入 html 代码后生成 index.html文件
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
})
]
})

webpack.base.conf.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// 使用 NodeJS 自带的文件路径插件
var path = require('path')

// 引入 config/index.js
var config = require('../config')

// 引入一些小工具
var utils = require('./utils')

// 拼接我们的工作区路径为一个绝对路径
var projectRoot = path.resolve(__dirname, '../')

// 将 NodeJS 环境作为我们的编译环境
var env = process.env.NODE_ENV

// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
// various preprocessor loaders added to vue-loader at the end of this file

// 是否在 dev 环境下开启 cssSourceMap ,在 config/index.js 中可配置
var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)

// 是否在 production 环境下开启 cssSourceMap ,在 config/index.js 中可配置
var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)

// 最终是否使用 cssSourceMap
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd

module.exports = {
entry: {
// 编译文件入口
app: './src/main.js'
},
output: {
// 编译输出的根路径
path: config.build.assetsRoot,
// 正式发布环境下编译输出的发布路径
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
// 编译输出的文件名
filename: '[name].js'
},
resolve: {
// 自动补全的扩展名
extensions: ['', '.js', '.vue'],
// 不进行自动补全或处理的文件或者文件夹
fallback: [path.join(__dirname, '../node_modules')],
alias: {
// 默认路径代理,例如 import Vue from 'vue',会自动到 'vue/dist/vue.common.js'中寻找
'vue': 'vue/dist/vue.common.js',
'src': path.resolve(__dirname, '../src'),
'assets': path.resolve(__dirname, '../src/assets'),
'components': path.resolve(__dirname, '../src/components')
}
},
resolveLoader: {
fallback: [path.join(__dirname, '../node_modules')]
},
module: {
preLoaders: [
// 预处理的文件及使用的 loader
{
test: /\.vue$/,
loader: 'eslint',
include: projectRoot,
exclude: /node_modules/
},
{
test: /\.js$/,
loader: 'eslint',
include: projectRoot,
exclude: /node_modules/
}
],
loaders: [
// 需要处理的文件及使用的 loader
{
test: /\.vue$/,
loader: 'vue'
},
{
test: /\.js$/,
loader: 'babel',
include: projectRoot,
exclude: /node_modules/
},
{
test: /\.json$/,
loader: 'json'
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url',
query: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url',
query: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
eslint: {
// eslint 代码检查配置工具
formatter: require('eslint-friendly-formatter')
},
vue: {
// .vue 文件配置 loader 及工具 (autoprefixer)
loaders: utils.cssLoaders({ sourceMap: useCssSourceMap }),
postcss: [
require('autoprefixer')({
browsers: ['last 2 versions']
})
]
}
}

config/index.js

index.js 中有 dev 和 production 两种环境的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
var path = require('path')

module.exports = {
// production 环境
build: {
// 使用 config/prod.env.js 中定义的编译环境
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'), // 编译输入的 index.html 文件
// 编译输出的静态资源根路径
assetsRoot: path.resolve(__dirname, '../dist'),
// 编译输出的二级目录
assetsSubDirectory: 'static',
// 编译发布上线路径的根目录,可配置为资源服务器域名或 CDN 域名
assetsPublicPath: '/',
// 是否开启 cssSourceMap
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
// 是否开启 gzip
productionGzip: false,
// 需要使用 gzip 压缩的文件扩展名
productionGzipExtensions: ['js', 'css']
},
// dev 环境
dev: {
// 使用 config/dev.env.js 中定义的编译环境
env: require('./dev.env'),
// 运行测试页面的端口
port: 8080,
// 编译输出的二级目录
assetsSubDirectory: 'static',
// 编译发布上线路径的根目录,可配置为资源服务器域名或 CDN 域名
assetsPublicPath: '/',
// 需要 proxyTable 代理的接口(可跨域)
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
// 是否开启 cssSourceMap
cssSourceMap: false
}
}

build/build.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
require('./check-versions')() 

// 使用了 shelljs 插件,可以让我们在 node 环境的 js 中使用 shell
require('shelljs/global')
env.NODE_ENV = 'production'

var path = require('path')

// 加载 config.js
var config = require('../config')

// 一个很好看的 loading 插件
var ora = require('ora')

// 加载 webpack
var webpack = require('webpack')

// 加载 webpack.prod.conf
var webpackConfig = require('./webpack.prod.conf')

// 输出提示信息 ~ 提示用户请在 http 服务下查看本页面,否则为空白页
console.log(
' Tip:\n' +
' Built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
)

// 使用 ora 打印出 loading + log
var spinner = ora('building for production...')
// 开始 loading 动画
spinner.start()

// 拼接编译输出文件路径
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
// 删除这个文件夹 (递归删除)
rm('-rf', assetsPath)
// 创建此文件夹
mkdir('-p', assetsPath)
// 复制 static 文件夹到我们的编译输出目录
cp('-R', 'static/*', assetsPath)

// 开始 webpack 的编译
webpack(webpackConfig, function (err, stats) {
// 编译成功的回调函数
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n')
})

webpack.prod.conf.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
var path = require('path')

// 加载 confi.index.js
var config = require('../config')

// 使用一些小工具
var utils = require('./utils')

var webpack = require('webpack')

// 加载 webpack 配置合并工具
var merge = require('webpack-merge')

// 加载 webpack.base.conf.js
var baseWebpackConfig = require('./webpack.base.conf')

// 一个 webpack 扩展,可以提取一些代码并且将它们和文件分离开
// 如果我们想将 webpack 打包成一个文件 css js 分离开,那我们需要这个插件
var ExtractTextPlugin = require('extract-text-webpack-plugin')

// 一个可以插入 html 并且创建新的 .html 文件的插件
var HtmlWebpackPlugin = require('html-webpack-plugin')
var env = config.build.env

// 合并 webpack.base.conf.js
var webpackConfig = merge(baseWebpackConfig, {
module: {
// 使用的 loader
loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
},
// 是否使用 #source-map 开发工具,更多信息可以查看 DDFE 往期文章
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
// 编译输出目录
path: config.build.assetsRoot,
// 编译输出文件名
// 我们可以在 hash 后加 :6 决定使用几位 hash 值
filename: utils.assetsPath('js/[name].[chunkhash].js'),
// 没有指定输出名的文件输出的文件名
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
vue: {
// 编译 .vue 文件时使用的 loader
loaders: utils.cssLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
plugins: [
// 使用的插件
// http://vuejs.github.io/vue-loader/en/workflow/production.html
// definePlugin 接收字符串插入到代码当中, 所以你需要的话可以写上 JS 的字符串
new webpack.DefinePlugin({
'process.env': env
}),
// 压缩 js (同样可以压缩 css)
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new webpack.optimize.OccurrenceOrderPlugin(),
// extract css into its own file
// 将 css 文件分离出来
new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
// 输入输出的 .html 文件
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
// 是否注入 html
inject: true,
// 压缩的方式
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
// 没有指定输出文件名的文件输出的静态文件名
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
// 没有指定输出文件名的文件输出的静态文件名
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})
]
})

// 开启 gzip 的情况下使用下方的配置
if (config.build.productionGzip) {
// 加载 compression-webpack-plugin 插件
var CompressionWebpackPlugin = require('compression-webpack-plugin')
// 向webpackconfig.plugins中加入下方的插件
var reProductionGzipExtensions = '\\.(' + config.build.productionGzipExtensions.join('|') + '$)'
webpackConfig.plugins.push(
// 使用 compression-webpack-plugin 插件进行压缩
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(reProductionGzipExtensions), // 注:此处因有代码格式化的bug,与源码有差异
threshold: 10240,
minRatio: 0.8
})
)
}

module.exports = webpackConfig

vue-router2.0 router、routes、组件配置项

routes

数组,最基本的,里面配置着一坨坨的路由

vue-router 的两种模式:hash和history

hash模式:路径由hash值来搭载,比如一个 URL:http://www.abc.com/#/hello,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面

history模式:利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持),这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求

hash 模式和 history 模式都属于浏览器自身的特性,vue-Router 只是利用了这两个特性(通过调用浏览器提供的接口)来实现前端路由。本次的路由设计用的自然是history模式了,毕竟中间加个#确实很丑

router-link配置项

router-link 比起写死的 a 标签会好很多,理由如下:

  • 无论是 HTML5 history 模式还是 hash 模式,它的表现行为一致,所以,当你要切换路由模式,或者在 IE9 降级使用 hash 模式,无须作任何变动

  • 在 HTML5 history 模式下,router-link 会守卫点击事件,让浏览器不再重新加载页面

  • 当你在 HTML5 history 模式下使用 base 选项之后,所有的 to 属性都不需要写(基路径)了

to:必须,字符串或对象,指明路径的配置

replace:布尔值,true:点击时,会调用 router.replace( ) 而不是 router.push( ),导航后不会留下 history 记录

exact:布尔值,默认false,表明是否精确匹配,一般用在导向默认页

tag:字符串,表明router-link要被渲染成的元素,默认a链接

active-class:指明router被激活时的类名,默认router-link-active,如果嫌长可以通过new Router({ })配置项linkActiveClass进行自定义默认设置,暂时只用到这么多

嵌套路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import layout from 'path'
import project from 'path'
import coding from 'path'
{
path: '/admin',
name: 'admin',
component: layout,
children:[
{
path: '/project',
name: 'project',
component: project
},
{
path: '/coding',
name: 'coding',
component: coding
}
]
}

路由重定向和别名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
path: '*',
name: 'name1',
alias: 'name2',
redirect: '/',
redirect: (to) => { //也可动态设置重定向的目标
// 目标路由对象,就是访问的路径的路由信息
if( to.path === '/123' ) {
return '/home'
}else(to.path === '/456'){
return {path: '/document'}
}
}
}

命名视图

1
2
3
4
5
6
7
8
9
10
{
path: '/document',
name: 'Dcoument',
components: {
default: document,
slider: slider
}
}
<router-view class="center"></router-view> //默认挂载的视图
<router-view name="slider"></router-view> //指定挂载的视图

命名视图让我们得以在一个路由下面挂载多个相互独立的组件

滚动行为:scrollBehavior

1
2
3
4
5
6
7
8
9
10
scrollBehavior(to,from,savePosition){ // 点击浏览器的前进后退或切换导航触发
console.log(to); // 要进入的目标路由对象
console.log(from) // 离开的路由对象
console.log(savePosition) // 记录滚动条的坐标 点击前进后退的时候记录值
if( to.hash){
return {
selector: to.hash
}
}
}

通过以上配合可以实现在路由切换中精确定位到某个元素

组件配置项:监控$route路由信息对象

1
2
3
4
5
6
watch:{
$route(){
this.navname = navnames[this.$route.path.slice(1)];
//this指向组件实例
}
}

用在父路由或者是带有hash值得路由,子路由和hash值得改变都能触发该函数,通过相关配合可以配合组件渲染页面,本次设计的面包屑导航正是运用了这一点

route配置项:query传参

1
2
3
4
5
6
{
path:'/login',
query:{
key : val
}
}

配合路由钩子函数实现目标定向,本次设计从首页在没有登录的情况下跳转到项目页,会通过全局路由的钩子函数先导向登录页,通过给跳向登录页的路由传参,最后自动跳转到项目页

导航切换过渡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//template
<transition mode='out-in'>
<router-view></router-view>
</transition>

//css
.v-enter{ //将要进来的组件初始样式状态量
opacity: 0;
}
.v-enter-to{ //将要进来的组件挂载完毕样式状态量
opacity: 1;
}
.v-enter-active{ //将要进来的组件过渡形式
-webkit-transition: 0.3s ease-out;
}
.v-leave{ //将要离开的组件初始样式状态量
opacity: 1;
}
.v-leave-to{ //将要离开的组件挂载完毕样式状态量
opacity: 0;
}
.v-leave-active{ //将要离开的组件过渡样式
transition: 0.3s ease-out;
}

mode:out-in和in-out,先进后出还是先出后进,我觉得是必须加上的,因为如果不设router-view定位不好的话会引起文档流的脱节,在共同的过渡时间先后两个组件是共存的。v-只是vue-router预留的名字,可以自定义类名,如left-enter,相应的添加类名即可

编程式导航

所谓的编程式导航只不过是在浏览器的历史记录栈里面串来串去,个人觉得好鸡肋啊

动态路径参数及query传参

在router-link中动态绑定to配置项,在路由配置中做相应改变,用正则兼容匹配个数,实现在某个页面根据用户或分类信息访问多个并列路由,然后我们可以在组件的$route下的params属性下去访问动态绑定的那些参数,如:this.route.pamas.userId,区分于嵌套路由,两者有着明显功能划分

1
2
3
4
5
6
7
8
9
10
11
12
13
{
path: '/user/:tip?/:userId?',
//正则中?为匹配或不匹配,显然该路由要兼容匹配到/user
/user/vip/id1 /user/common/id2
component: user
}
<router-link
:to="{path:'/user/'+item.tip+'/'+item.id,query:{info:'follow'}}"
key="index"
v-for="item,index in userList"
>
{{item.userName}}
</router-link>

路由元信息

这个概念非常简单,就是在路由配置里有个属性叫 meta,它的数据结构是一个对象。你可以放一些自定义key-value进去,方便在钩子函数执行的时候用。举个粒子,你要配置哪几个页面需要登录的时候,你可以在meta中加入一个 requireslogin标志位,然后设置为true或false

全局钩子函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
router.beforeEach( (to,from,next) =>{
if(to.matched.some( (item) => item.meta.requirelogin ) ){
let logininfo = router.app.$localsave.fetch('logininfo');
if( logininfo.islogin ){
next();
}else{
router.push({
path:'/login',
query:{
redirect : to.path.slice(1)
}
});
alert('查看项目和工作台需要先登录!');
}
}else{
next();
}
} )
router.afterEach((to, from, next) => {
......
});

三个参数

  • to: Route: 即将要进入的目标路由对象

  • from: Route: 当前导航正要离开的路由对象

  • next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数

next( ): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的),一般就把他仍在逻辑运算的底层就好了

next(false): 中断当前的导航。如果浏览器的 URL 改变了(可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址

next(‘/‘) 或者 next({ path: ‘/‘ }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航

全局路由的钩子函数在该页面所有路由的跳转都会执行的函数,该函数通过请求到后端返回的数据或者是从本地(如localStorage、cookie等等)获取到数据来干涉每次路由的跳转,本次设计用localStorage来模拟登录跳转,之后在钩子函数中请求本地数据来干涉是否要跳转到登录页面,特别注意的是,在他的参数函数中,this拿不到总组件实例了,很友好的是vue-router已经为我们预留了接口:通过router.app就能访问到我们的总组件实例了

组件生命周期函数

beforeCreate:在实例初始化之后,数据观测(Data Observer)和event/watcher事件配置之前被调用

create:实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据(Data Observer)、属性和方法的运算,watch/event事件回调。然而,挂载阶段还没开始,$el属性目前不可见

beforeMount:在挂载开始之前被调用:相关的render函数首次被调用

mounted:el被新创建的vm.el替换,并挂载到实例上去之后调用该钩子。如果root实例挂载了一个文档内元素,当mounted被调用时vm.$el也在文档内

beforeUpdate:数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前。你可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程

updated:由于数据更改导致虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。当这个钩子被调用时,组件DOM已经更新,所以你现在可以执行依赖于DOM的操作。然而在大多数情况下,你应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用

beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用

destroyed:Vue实例销毁后调用。调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用

组件级路由钩子函数

组件内的路由钩子函数,他作用于所有导向该组件的路由,其作用是进行更细粒度的路由干涉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当钩子执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}

vue-router设计实现

情景分析

整个应用分为首页和内容页和登录页,首页对应三个入口对应内容页的三个模块,三个模块为嵌套在内容页layout组件内的三个子路由,但路由地址仍然表现为一级导航,其中我的项目和工作台两个模块需要进行登录,在没有登录的请况下从首页跳转至我的项目会首先导向登录页面,经验简单的验证,用localstorage模拟ajax进行存储用户名和登录状态,之后跳转回我的项目页,工作台同理,实现面包屑导航,路由地址错误重定向,用户名面板、登录面板及退出面板相互耦合绑定、所有导航转场和谐过渡,我的文档模块利用hash值精确定位文档内容,引入第三方动画库,在组件级钩子函数路由更新时,组件新创建时均实现切换过渡、退出后定向到首页

首页

首页

内容页(文档)

内容页(文档)

登录之后项目页

登录之后项目页

路由文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import Vue from 'vue';
import Router from 'vue-router';
import home from '@/components/home';
import layout from '@/components/layout';
import login from '@/components/login';
import project from '@/components/project';
import coding from '@/components/coding';
import document from '@/components/document';

Vue.use(Router);

var router = new Router({
mode: 'history',
linkActiveClass: 'is-active',
routes:[
{
path: '/',
name: 'home',
component: home
},
{
path: '/admin',
name: 'admin',
component: layout,
children:[
{
path: '/project',
name: 'project',
component: project,
meta:{
requirelogin:true
}
},
{
path: '/coding',
name: 'coding',
component: coding,
meta:{
requirelogin:true
}
},
{
path: '/document',
name: 'document',
component: document
}
]
},
{
path:'/login',
name: 'login',
component: login
},
{
path: '*',
redirect: '/'
}

]
})

router.beforeEach( (to,from,next) =>{
if(to.matched.some( (item) => item.meta.requirelogin ) ){
let logininfo = router.app.$localsave.fetch('logininfo');
if( logininfo.islogin ){
next();
}else{
router.push({
path:'/login',
query:{
redirect : to.path.slice(1)
}
});
alert('查看项目和工作台需要先登录!');
}
}else{
next();
}
} )

export default router;

进阶设计

将工具函数挂载到Vue根实例下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var obj={
install:function(vm,utils){
vm.ptototype.custompropname = utils
}
}
Vue.use(obj)
//示例
//--------------------------插件模块
var localsave={
save(key,val){
localStorage.setItem(key,JSON.stringify(val));
},
fetch(key){
return JSON.parse(localStorage.getItem(key))||{};
}
}

export default {
install:function(vm){
vm.prototype.$localsave=localsave;
}
}
//--------------------------入口文件
Vue.use(localsave)

vue-异步组件和webpack代码分割实现按需加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//异步组件
{
components{
custom: (resolve,reject)=>{

}
}
}
//webpack代码分割
require.ensure([],function,string)
//三个参数分别为:依赖、回调、代码块名
//示例:
普通模式引入组件:
import layout from '@/components/layout'
按需加载模式引入组件:
let layout = (resolve)=>{
return require.ensure([],()=>{
resolve(require('@/components/layout'))
},'codebliock1')
}
//相同名字的组件会被分割在同一块代码,这样只有当激活导向该组件的路由是才会加载该组件

案例源码传送门

在线演示demo