5 webpack详解
webpack是一个JS应用的静态模块打包工具,将各种类型文件进行统一打包成浏览器能识别运行的文件,并处理各种模块的依赖关系。webpack不仅可以打包压缩js,css和json、图片等都可以被当作模块来进行使用。
webpack正常使用需要依赖nodejs,所以需要先安装nodejs
由于脚手架需求,建议nodejs版本高于8.9,此处建议webpack版本3.6.0,因为vue2-cli依赖该版本
5.1 安装
全局:npm install webpack@3.6.0 -g
局部:npm install webpack@3.6.0 --save-dev
(此处表示开发依赖,打包后不依赖,等同于-D)
5.2 使用
打包格式:webpack 入口文件 出口文件
webpack ./src/main.js ./dist/bundle.js
index.js引用打包后生成的js文件即可
5.2.1 webpack配置文件
必备两个:entry入口和output出口
entry:是一个入口文件,可以是相对路径
output:接收一个对象,包括path和filename,path接收绝对路径,filename接收字符串表示打包后的js文件
对于绝对路径,为保证项目移动后还能正确打包,引入path模块动态获取绝对路径
// webpack.config.js
const path = require('path')
module.exports = {
entry: './src/main.js',
output: { // 出口必须包含path和filename
path: path.resolve(__dirname,'dist'), // 此处只能接收绝对路径!
filename: 'bundle.js'
}
}
注意:require如果接收一个非路径字符串
表示从项目的node_modules
包中去寻找,为了形成这种结构,需要执行以下步骤:
- npm init
- npm install path --save
path.resolve()
:拼接目录,前面是父目录,后面是子目录
__dirname
:表示当前文件的所在绝对目录
5.2.2 映射命令到package.json
在scripts属性中配置新的命令:build
"scripts":{
"build": "webpack"
}
对于webpack打包命令现在变成了:npm run build
注意:使用npm run
命令会执行后面配置的命令,运行环境会优先匹配本地命令环境,如果本地没有才会去查找全局环境。如果在终端中使用命令会使用全局的环境。
package.json中的scripts脚本执行时会按照顺序寻找相应的命令:node_modules优先,再全局
5.3 loader
5.3.1 css-loader
webpack可以用来处理js代码和相关依赖,但是对于css和图片还有es转码等webpack本身不支持,需要使用扩展,即loader
使用步骤:npm安装对应loader,然后webpack.config.js
中module
关键字下配置相关loader
对于css-loader使用步骤:(相关使用配置文档可以参考官方里的loader
引入:在js文件中通过require引入相关css文件
安装:npm install css-loader style-loader -D
配置:
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader' ]
}
]
}
}
注意:如果仅仅css-loader只能解析css文件,引入和引出,无法将css添加到dom中,还需要style-loader
注意:use中执行顺序是有要求的,会从右向左进行使用,先加载css-loader解析导入导出,再加载style-loader放入dom
5.3.2 less-loader
引入:引入less文件到入口文件或依赖链中的某个js文件
安装:npm install --save-dev less-loader less
(less用来预解析less到css,less是一个工具)
配置:直接参考官网进行copy
5.3.3 图片处理loader
使用url-loader
引入:在css文件中使用url()加载一个文件,或者:import img from './image.png'
安装:npm install --save-dev url-loader
配置:
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
// 当图片小于limit参数时,会使用base64转码成字符串进行url加载
// 当图片大于limit参数时,会使用file-loader进行加载(如果没安装会报错)
limit: 8192
}
}
]
}
]
}
}
安装:file-loader:npm install --save-dev file-loader
使用:import img from './file.png'
配置:
module.exports = {
module: {
rules: [
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'file-loader',
options: {}
}
]
}
]
}
}
默认情况下,生成的文件的文件名就是文件内容的 MD5 哈希值并会保留所引用资源的原始扩展名。此处生成文件 file.png
,输出到输出目录并返回 public URL:"/dist/0dcbbaa7013869e351f.png"
注意:使用file-loader会把对应加载的文件进行打包到dist中,如果图片无法正常加载,需要在output属性下添加配置publicPath: 'dist/'
(这种情况出现原因时index.js没有一起打包到dist文件夹下去,后面会使用插件进行打包html)
由于上面这种打包后图片名字变成了hash不便理解,可以配置图片名称格式
配置:
{
loader: 'url-loader',
options: {
// 当图片小于limit参数时,会使用base64转码成字符串进行url加载
// 当图片大于limit参数时,会使用file-loader进行加载(如果没安装会报错)
limit: 8192,
name: 'img/[name].[hash:8].[ext]'
},
}
解释:name属性表示对图片名字进行格式化处理:
img/
表示打包后的图片放入输出目录下的img文件夹下
[name]
表示原始图片文件名
[hash:8]
表示图片hash保留8位
[ext]
表示原始图片扩展名
5.3.4 ES6文件转码 babel-loader
babel用于将ES6语法转换成ES5的,在webpack中可以使用babel对应的loader
安装:npm install babel-loader babel-core babel-preset-env webpack
(webpack 3.x babel-loader 7.x | babel 6.x)
其中:env代表当前环境,如:es2015则替换成: babel-preset-es2015
,进行简单的搭建转码。如果不进行替换则需要配置一个:babel.rc
的文件,对环境进行配置
配置:
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'] // 或 presets: ['es2015']
}
}
}
]
}
5.3.5 webpack配置Vue vue-loader
安装:npm install vue --save
使用:
import Vue from 'vue'
new Vue({
el: '#app',
template: ...
data: {
message: '123'
}
})
然后在index.js中挂载一个app的标签即可。
注意:el和template同时存在时,会将template直接替换掉el指定的标签!
但是在运行:npm run build
后打开index.html报错,原因是:
- runtime-only:vue代码中,不可以有template,因为只是运行时的库,相当于jre(默认)
- runtime-compiler:vue代码中包含编译库和运行库,可以解析template,相当于jdk
解决:在webpack.config.js中配置:
module.exports = {
// ...
resolve: {
alias: {
'vue$': 'vue/dist/vue.esm.js' // 用 webpack 1 时需用 'vue/dist/vue.common.js'
}
}
}
重新打包后即可正常运行
真实环境中,不会在Vue实例中写data等元素,只需要引入一个组件即可:
new Vue({
el: '#app',
template: '<App/>'
components: {App}
})
其中App为一个Vue组件:app.vue,此时需要解析vue的loader
安装:npm install vue-loader vue-template-compiler --save-dev
配置:
module: {
rules: [
{
test: /\.vue$/,
use: ['vue-loader']
}
]
}
配置完后编译如果提示缺少:vueloaderplugin插件,有两种方法:1 配置该插件 2 降低vue-loader的版本
这里采用降低版本:"vue-loader":"^13.0.0"
(14以上需要插件)
编译即可
注意:如果想在import时省略后缀名,需要在webpack中配置加上
module.exports = {
// ...
resolve: {
extensions: ['.vue','.js','.css']
}
}
5.4 plugin
对现有的架构进行扩展
与loader区别:loader是加载相关文件,进行加载,plugin是扩展。webpack内置了一些插件,对于其他的插件则需要通过npm进行安装
使用插件需要导包
5.4.1 版权插件
为打包的js文件开头添加版权声明注释,使用的插件:BannerPlugin
,内置插件
const webpack = require('webpack')
module.exports = {
...
plugins: [
new webpack.BaannerPlugin('最终版权归pp所有')
]
}
5.4.2 打包html到dist目录插件
将入口html一起打包进dist目录,使用插件:HtmlWebpackPlugin
,可以自动生成一个index.html,自动引入js到html中
安装:npm install html-webpack-plugin --save-dev
配置:
const htmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
...
plugins: [
new htmlWebpackPlugin({
template: 'index.html'
})
]
}
template指定的是以哪个文件作为模板生成html,注意,如果有这个插件则不需要配置publicPath
5.4.3 对js压缩的插件
由于直接打包的js文件有很多不需要的字符,如空格换行注释等,占用空间比较大,使用插件:uglifyjs-webpack-plugin
进行压缩
安装:npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
配置:
const uglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
...
plugins: [
new uglifyJsPlugin()
]
}
5.4.4 搭建本地服务器
安装:npm install webpack-dev-server@2.9.3 --save-dev
配置:
module.exports = {
...
devServer:{
contentBase: './dist',
inline: true
}
}
contentBase:指定服务器管理的文件夹
inline:指定是否实时更新
port:指定运行端口
historyApiFallback:SPA页面中,依赖HTML5的history模式
运行:在package.json
中配置脚本:"dev":"webpack-dev-server --open"
注意:–open表示运行后直接打开浏览器
5.4.5 webpack生产和开发配置文件分离
安装:npm install webpack-merge --save-dev
新建一个build文件夹,将webpack.config.js文件复制到该文件夹下,然后保留公共部分,将开发相关配置和生产相关配置分别放在当前目录的其他两个文件内
prod.config.js
// 生产
const baseConfig = require('./webpack.config.js')
const webpackMerge = require('webpack-Merge')
const uglifyJsPlugin = require('uglifyjs-webpack-plugin')
module.exports = webpackMerge(baseConfig ,{
plugins: [
new uglifyJsPlugin()
]
})
dev.config.js
const baseConfig = require('./webpack.config.js')
const webpackMerge = require('webpack-Merge')
module.exports = webpackMerge(baseConfig ,{
devServer:{
contentBase: './dist',
inline: true
}
})
配置:
- 在package.json中加上指定webpack的目录的配置
"build": "webpack --config ./build/prod.config.js",
"dev": "webpack-dev-server --open --config ./build/dev.config.js"
- 修改webpack.config.js的输出位置
module.exports = {
entry: './src/main.js',
output: { // 出口必须包含path和filename
path: path.resolve(__dirname,'../dist'), // 此处只能接收绝对路径!
filename: 'bundle.js'
}
}
测试:npm run dev
或者npm run build
5.5 alias别名
webpack可以配置别名,配置后在import
的时候使用别名代替一个特定的路径,简化路径写法
注意:在标签中如果想使用别名,需要在别名前加~
,如:@
为src
的别名,则<img src="~@/assets/img/01.png>
6 vue-cli
基于构建工具webpack的Vue CLI(Command-Line Interface),俗称脚手架
Vue-CLI是官方发布的vue.js项目脚手架,可以快速搭建开发Vue环境以及webpack配置
安装:npm install -g @vue/cli
(当前版本是脚手架3)
新建工程:vue create 工程名
运行工程:npm run serve
如果需要使用脚手架2,可以用3的版本初始化一个2的版本:
安装:npm install -g @vue/cli-init
初始化:vue init webpack 项目名称
安装完成后目录结构:
目录解释:
- build:存放webpack相关配置
- config:存放webpack相关变量
- src:存放源代码
- src.assets:存放相关资源,会根据大小进行loader处理(改名或压缩)
- static:静态资源,打包后原封不动放到dist中
- .babelrc:如果安装了babel-preset-env,则需要这个文件用于决定哪些浏览器或者规范进行转码
- editorconfig:编辑器规范,主要定义使用编码和缩进等
- package.json:配置安装的软件版本,配置脚本,配置一些项目信息
- package-lock.json:由于上面指定的版本只是模糊的,由这个文件来精确指定版本
注意:package.json中版本开头的^4.20.1
这种格式表示最后一个点后数值可变,范围往高走。~4.20.1
这种表示最后两个点后数值都可变,范围往高走。
关闭eslint:修改项目根目录-config目录-index.js:useEslint: false
,如果没有则新建package.json同级目录下的vue.config.js
module.exports = {
lintOnSave:false
}
注意一下public/index.html
中,vue不推荐直接使用相对路径引入文件,而采用了<%= BASE_URL %>
代表public路径
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<!-- webpack的一个插件,会去package.json中找name对应的值 -->
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<!-- 当浏览器不支持js时会显示该标签内容 -->
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
6.1 runtime+compiler or runtime only
在main.js中发现vue实例生成的方式与之前不相同:
之前:
runtime+compiler:
当把组件传给vue实例时:template模板 - ast(抽象语法树) - render函数 - 虚拟dom-dom
new Vue({
el: '#app',
template: '<App/>',
components: { App }
})
现在:
runtime only:
当把组件传递给vue实例时:render函数 - 虚拟dom - dom
new Vue({
el: '#app',
render: h => h(App)
})
注意:第二个性能更高,且源代码更少,原因在于:带编译器的版本需要在运行时编译template(当然你也可以不使用template属性,这样就不存在转换过程,但是会多6kb)存在一个template转ast,ast转render的过程。说到底就是template属性,如果想使用,则需要配一个编译器
其中h作为入参,类型是函数,其实就是createElement
函数,该函数可以用来创建标签。
6.2 createElement
是一个函数,调用格式:createElement(标签, {标签属性}, [标签content])
普通用法:createElement('h2',{class: 'box'}, ['hello man', createElement('button',['按钮'])])
组件用法:createElement(组件)
注意:runtime only模式使用的方式就是这种方式,直接在render里接收createElement
函数然后渲染App
,其实.vue
文件中的template
在引入时就已经被解析成了render
函数(由vue-template-compiler
解析的)
6.3 webpack.base.conf.js中的alias
别名可以在导入组件或者模块时方便定位路径:
module.exports = {
...
resolve: {
...
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},
导入文件时:
import ABC from '@/routes/ABC'
如果想看vue中所有webpack的配置可以运行命令vue inspect > output.json
,但是注意,这是导出配置,是原配置的一份拷贝!!
如果想自定义一些vue脚手架的配置,需要在package.json同级目录下定义一个vue.config.js
该文件是webpack的增量文件,会以该文件为准去替换默认配置。该文件被改动后需要重新启动项目
6.4 Vue CLI3
cli3与cli2区别:
- cli3基于webpack4,cli2基于webpack3
- cli3原则是0配置,移除了build和config目录
- cli3提供vue ui命令,提供了可视化配置
- cli3移除了static文件夹,新增public文件夹,且index.html放在其中
cli3创建的目录结构:
cli3的main.js
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
Vue.config.productionTip
表示构建信息,在build时可以设置为true,方便观察构建进度
.$mount
用于挂载vue对象到哪个节点上,和el
属性的作用一样
vue-cli3的配置:vue-cli3将配置文件放到了node_modules中,如果需要修改,只需要在项目根目录下创建一个:vue.config.js
(注意:该名字不可改变),会自动和默认配置合并!
// 脚手架3中配置别名:vue.config.js
module.exports = {
configureWebpack:{
resolve: {
alias: {
'assets':'@/assets',
'common':'@/common'
}
}
}
}
vue.config.js文件相关配置可以在vue cli配置参考文档中查找
6.4.1 vue ui
从脚手架3开始提供一个命令:vue ui
用于可视化配置,之前的配置放到@vue/cli-service
下了
6.5 dist目录解析
脚手架将文件打包时对文件进行了分割,将js和css分开存放到相应文件目录中,index.html直接放在dist根目录:
dist
|-index.html // 入口html
|-css
| |-app.xxxx.css // 所有css都合并到这里了
|-js
|-app.xxxx.js // 关于业务代码放在这里了
|-manifest.xxxx.js // 关于底层export/import等支撑关系的放在这里了
|-vendor.xxxxx.js // 关于第三方,如vue/axios/vue-router等都放在这里了
7 vue-router
前端路由:由前端维护url和页面的映射关系
SPA(单页面富应用)中,改变url页面不会刷新(前端路由核心),而是由前端路由判断url并选择渲染出的页面或者说组件。
改变url页面不刷新的方法:
1 使用window.location
的href
属性,可以直接赋值location.hash
来改变href
,页面不刷新(但是url
末尾会多一个#
号,后面的是hash
)
2 使用h5的history属性,history.pushState({},'','/foo')
,页面不刷新(但url的域名后的地址会替换成/foo
),执行history.back()
可以依次返回上一个url
注意:history.replaceState({},'','home')
是替换操作,不能返回。
注意:history.go(-1)
只能在pushState
使用后使用,表示一次性弹出几个或压入几个,-1表示前一个url
,即back
功能。
注意:history.forward()
和back
功能正好相反
安装vue-router:npm install vue-router --save
使用:
- 导入路由对象,调用Vue.use(VueRouter)
- 创建路由实例,传入路由配置
- 在Vue实例中挂载路由实例
7.1 配置路由和简单使用
安装好路由后,新建一个router
文件夹,新建index.js
,导入vue-router,由于vue-router属于vue插件,如果想让其生效,必须使用方法Vue.use()
传入该插件:Vue.use(VueRouter)
import VueRouter from 'vue-router'
import Vue from 'vue'
Vue.use(VueRouter)
const routes = [] // 在这里配置url的映射关系即可
export default new VueRouter({
routes
})
在main.js中挂载router
import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router
}).$mount('#app')
映射关系配置
const routes = [
{
path: '/home',
component: Home
},
{
path: '/about',
component: About
}
]
配置跳转链接(router-link是vue-router注册的全局组件)
<router-link to='/home'>首页</router-link>
<router-link to='/about'>关于</router-link>
<router-view></router-view>
注意:router-view
:用于占位将要动态渲染的组件,切换路由时只切换其挂载的组件,其他内容不变
注意:默认路由方式为:hash
7.2 路由默认值设置
配置redirect
即可,该标签在解析时会自动发生路由跳转请求
{
path: '/',
redirect: '/home'
}
7.3 使用history路由方式
只需要在导出路由对象时将mode
属性改为history
:
import VueRouter from 'vue-router'
import Vue from 'vue'
Vue.use(VueRouter)
const routes = [] // 在这里配置url的映射关系即可
export default new VueRouter({
routes,
mode: 'history'
})
7.4 router-link组件常用属性
该组件用于跳转触发路由,默认渲染成a标签,可以通过tag
属性改变渲染成的标签
<router-link to='/home' tag='button'>首页</router-link>
这种方式跳转的路由信息会被浏览器保存下来用于前进和后退,如果不需要保存,在标签中添加replace
属性即可
<router-link to='/home' replace>首页</router-link>
如果想要修改link active时的样式,可以在组建的style中修改:router-link-active
类,如果想统一修改所有的link激活样式可以在导出路由对象时添加一个属性:linkActiveClass:样式名
,新加样式即可
注意:如果是精确路由匹配,还会多一个exact-active-class
,路由嵌套下会出现。
7.5 方法中路由跳转
vue-router在vue中注册使用后会在每个组件中都添加一个属性:$router
带记录的跳转:this.$router.push('/home')
不带记录的跳转:this.$router.replace('/home')
注意这个属性必须在普通函数中使用,避免在箭头函数中
如果控制台出现报错:Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location
可以在Vue.use(VueRouter)
之后加上代码:
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
return originalPush.call(this, location).catch(err => err)
注意:此处不能使用window.history.pushState
去直接改变,因为这个绕过了vue-router的路由监听,无法触发渲染
7.6 动态路由:param
如果需要在url上以该形式动态展示一些参数或者数据,可以使用动态路由:(注意使用bind语法才能解析属性中的参数)
<router-link :to="'/home/'+userId" >首页</router-link>
路由配置:
{
path: '/home/:uid',
component: Home
}
函数中获取动态路由参数:this.$route
this.$route.param.uid
7.7 路由传参
参数传递有3中:(后两种实质相同)
- 动态路由:params
参数传递:路由配置:/home/:id
;link链接:/home/123
;函数:this.$router.push('/home/123')
参数获取:this.$route.param.id
- get传参:?
参数传递:路由配置:/home
;link链接:/home?id=12
;函数:this.$router.push('/home?id=12')
参数获取:$route.query.id
;注意,js作用域中也可以获取:location.search
- query传参:{}
参数传递:路由配置:/home
;link链接:{path: '/home', query: {id:12, age: 22}}
;函数:this.$router.push({path: '/home', query: {id:12, age: 22}})
参数获取:$route.query.id
7.8 路由懒加载
懒加载可以有效防止初次请求时网页空白等待事件过长的问题,因为打包的话所有js会全部打包到几个文件中(目录结构上面已经给过了),懒加载可以让一个路由打包到一个js文件里,只有相应路由被访问到的时候才会加载组件,效率更高,速度更快
懒加载的三种方式:
7.9 路由嵌套
即路由出现层级:/一级/二级/三级,这样的划分格式,访问同级别的路由时,要求能访问到不同的组件
实现:
一级层级中:
<template>
<div id="app">
<router-link to='/home'>首页</router-link>
<router-link to='/about'>关于</router-link>
<router-view></router-view>
</div>
</template>
二级层级中:
<template>
<div>
我是首页
<hr/>
<router-link to='/home/a'>HomeA</router-link>
<router-link to='/home/b'>HomeB</router-link>
<router-view></router-view>
</div>
</template>
配置:
const routes = [
{
path: '',
redirect: '/home'
},
{
path: '/home',
component: Home,
children: [
{
path: '',
redirect: 'a'
},
{
path: 'a',
component: HomeA
},
{
path: 'b',
component: HomeB
}
]
},
{
path: '/about',
component: About
}
]
特别注意:
redirect
跳转的话默认path
为空字符串!children
内部的path
开头不能带/
- 跳转的
router-link
和相应占位的router-view
是一对,否则无法展示
7.10 $router 和 $route 如何挂载到Vue
其实$router
和 $route
两者挂载到的是Vue的原型上,所以所有的Vue实例即组件内都能访问到这两个对象(但是注意,模板中得直接写$route.params
和$route.query
才行,实测)
源码:
Object.defineProperty(Vue.prototype,'$router',{get(){return this._routerRoot._router}}
Object.defineProperty(Vue.prototype,'$route',{get(){return this._routerRoot._route}}
注意:Object.defineProperty对象,属性名,属性值)
:用于给某个对象添加属性,等价于:Vue.prototype.$router=this._routerRoot._router
7.11 导航守卫(路由跳转的监听)
7.11.1 全局守卫
在路由跳转的过程中加入了一个类似拦截器的东西,会监听到from和to的路由信息,(route信息),可以在之间做些判断:
// 路由中:加入一个属性meta去定义别的东西
{
path: '/about',
component: About,
meta:{
title:"关于"
}
}
// 在VueRouter实例化后调用实例方法:
const router = new VueRouter({
routes,
mode: 'history'
})
// 使用前置守卫
router.beforeEach((to,from,next)=>{
console.log(to.matched)
console.log(to.meta)
document.title=to.matched[0].meta.title
next()
})
// 后置钩子
// router.afterEach((to,from)=>{})
export default router
注意:
to.matched:表示当前路径所有匹配route,如果有子路由则可能会有多个,第0个为最短匹配路径
to.meta:表示目标路由的route信息,只有一个
next:必须调用next,否则不会进行跳转,(afterEach()
则无需,因为是以将跳转完毕),next可以接受多种类型参数,用于控制路由跳转规则
7.11.2 路由独享守卫
配置在routes配置数组中:只有跳转到该路由时才会进入的守卫,可以做一些权限控制或者判断的逻辑
{
path: '/about',
component: About,
meta:{
title:"关于"
},
beforeEnter: (to,from,next)=>{next()}
}
7.11.3 组件内的守卫
组件内有三个守卫:
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
7.12 keep-alive
在组件切换的时候,切换掉的组件缓存会被清空,切换回会重新渲染,如果想保留切换的组件状态避免重新渲染,可以使用keep-alive
,它时Vue内置组件,将router-view包裹在keep-alive里就可以缓存视图了。
组件被切换掉后会进入销毁阶段destroyed
两个hook:如果组件有keep-alive
则存在:
activated
:组件被激活时,即显示时,回调deactivated
:组件激活变为不激活时,回调
两个属性:keep-alive
有两个属性:
include
:字符串/正则,匹配的组件会被保持缓存,匹配的是组件的name
,匹配多个用逗号分隔exclude
:同上,匹配的组件不会被缓存
8 vuex
状态管理,集中储存式状态管理,简单来说,就是把需要共享状态的变量放在一个对象里,让不同的组件可以共享的获取其中的变量值,同时具备响应式功能
Vuex也集成到了Vue官方调试工具devtools extension,提供time-travel调试,状态快照写入导出等高级调试功能
单页面状态管理:
- state:状态放到view中展示
- view:视图上会产生行为action
- action:行为产生了会改变状态state
vuex属于vue插件,需要安装:npm install vuex --save
使用:建议新建一个store
目录,在其中的index.js
中:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 共享的状态变量
state: {
},
// 定义同步的方法,这里定义的所有方法,默认接收一个state参数
mutations: {
},
// 定义异步的方法
actions: {
},
modules: {
}
})
初始化完成后,所有的Vue
组件都会多一个$store
属性,可以通过:$store.state.xxx
来获取共享状态变量
基本使用:一个简单的示例:
// App.vue
<template>
<div>
当前结果:{{$store.state.count}}<hr/>
<button @click="add">+</button>
<button @click="sub">-</button>
</div>
</template>
<script lang="js">
import Vue from 'vue';
export default {
name: 'App',
methods: {
add(){
this.$store.commit("add")
},
sub(){
this.$store.commit("sub")
}
},
}
</script>
// store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count:0
},
mutations: {
add(state){
state.count++
},
sub(state){
state.count--
}
},
actions: {
},
modules: {
}
})
8.1 vuex常用属性
state中存放的是共享变量,对其改变会影响所有调用该变量的视图,this.$store.state.xxx
来获取共享状态变量
注意:禁止直接对vuex的state进行修改!
三个重要的阶段:
Actions:用于执行异步操作,如请求等,一般是异步方法
Mutations:用于执行同步操作(Devtools只能跟踪同步操作),一般是普通方法
state:存放需要托管的状态,一般是变量和对象
调用逻辑是:
Devtools浏览器插件可以记录Vuex每次修改插件的值和来源,如果vue直接改state则无法跟踪。
8.1.1 state
单一状态树:单一数据源,即所有需要管理的状态放在一个store
实例中即可,一个vue实例中不会有多个store
8.1.2 getters
计算属性,和组件中的computed
计算属性相同使用方式,注意,方法中第一个参数为state
,第二个为getters
getters: {
a(state){
},
b(state,getters){
}
}
特别注意:如果在getters
里需要传参,最好使用闭包,即返回结果是一个函数!将处理逻辑写在函数中(因为getters
只接受两个固定参数:state
和getters
)
getters: {
c(state){
return function(x){
// 逻辑
}
}
}
8.1.3 mutations
Vuex的store状态更新唯一的方式:提交mutation
mutation包含两部分:事件类型+回调函数,回调函数第一个参数就是state
,第二个参数可选:为载荷payload
,没有第三个参数
设置:
mutations: {
add(state){
state.count++
},
sub(state,num){
state.count--
}
}
上面示例中:事件类型为:add
,回调函数为后面的方法体
更新:
// 无参数
this.$store.commit('add')
// 带参数
this.$store.commit('sub',2)
注意:带参数的commit
,参数称为:载荷Payload
commit还有一种提交方式:直接传入一个对象,该对象会被直接赋值到mutation相应方法的第二个参数上!
this.$store.commit({type:'sub',num:2})
响应规则:
- 将需要响应的数据在
state
中初始化 - 如需在原有
state
某个对象中新添加响应属性必须使用:Vue.set(对象,索引,值)
进行修改:Vue.set(state.xxx,'a',1)
- 如需在原有
state
某个对象中删除某个响应属性必须使用:Vue.delete(对象,属性)
8.1.4 actions
通常情况,Vuex要求mutation必须为同步,否则devtools无法跟踪修改,异步放在actions中
设置:context
为上下文,相当于store
,模块中的context
为当前模块下的store
// store配置
state: {
count:0
},
mutations: {
add(state){
state.count++
},
sub(state,num){
state.count-=num
}
},
actions: {
asyncFunc1(context){
setTimeout(()=>{
context.commit('add')
},1000)
},
asyncFunc2(context,payload){
setTimeout(()=>{
context.commit('sub',2)
},1000)
}
},
更新:this.$store.dispatch
// vue组件中使用
methods: {
add(){
// this.$store.commit("add")
this.$store.dispatch('asyncFunc1')
},
sub(){
// this.$store.commit("sub",5)
this.$store.dispatch('asyncFunc2',8)
}
},
Tips:为action函数增加回调写法【优雅】(其实都一样)
考虑到action中放的都是异步函数,有时候需要在异步函数执行完回调一个函数:
- 一种方式是在action参数的payload里传递一个回调函数,或者一个对象,内部有一个回调函数
- 另一种【优雅】的写法是使用promise进行回调!
// 方法一
// 配置
asyncFunc2(context,payload){
setTimeout(()=>{
context.commit('sub',payload.param)
payload.callback(222)
},1000)
}
// 使用
this.$store.dispatch('asyncFunc2',{
param:123,
callback:(x)=>{console.log('done')}
})
// 方法二
asyncFunc2(context,payload){
return new Promise((resolve,reject)=>{
setTimeout(()=>{
context.commit('sub',payload)
resolve(222)
},1000)
})
}
this.$store.dispatch('asyncFunc2',123).then(x=>console.log(x))
注意:官方建议使用常量来定义方法和提交!这样可以避免写错!
// 常量定义文件:mutation-types.js
export const ADD= 'add'
export const SUB = 'sub'
// store文件:index.js
import {ADD,SUB} from 'mutation-types.js'
mutations: {
[ADD](state){
state.count++
},
[SUB](state,num){
state.count--
}
}
// vue组件中使用:a.vue
import {ADD,SUB} from 'mutation-types.js'
this.$store.commit(ADD)
this.$store.commit(SUB,1)
8.1.5 modules
如果想对store进行模块分类可以放在modules
中,每个模块又包含完整的store各个模块:
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules:{
a: {
state:{xxx:xxx},
mutations:{},
actions:{},
modules:{}
}
}
}
注意:moduels
中的state属性使用时:this.$store.state.a.xxx
注意:modules
合并规则:将其中各模块的除state
外的功能直接合并到外部,然后把剩下对象的直接放在state
中。
特殊的:模块中的getter
有第三个参数:rootState
,指向store
中的state
;
8.2 使用
定义好store
后,在组件中使用步骤:
- 在需要引用
state
的地方:$store.state.xxx
- 在需要改变
state
的地方:$store.commit('方法名')
,其中方法为定义在mutations
中的方法 - 在需要异步操作的地方:
$store.dispatch('方法名')
,其中方法为定义在actions
中的方法,actions
必须再commit
调用mutations
去改变state
一个原则state
必须通过mutations
改变!
8.3 目录组织
一般而言建议将除state的其他属性抽取到当前目录的相应js文件中去,然后通过export default导出来,再import进去!
特殊的,modules建议建一个文件夹,里面根据不同的模块进行js文件划分!
9 axios
网络模块封装,为了防止第三方框架的某些漏洞和突然无法使用的问题,需要对其进行封装,以方便切换其他框架。
JSONP跨域请求封装:通过script标签的src来获取跨域数据,并回调挂载在window上的回调函数
axios优点:
- 支持在浏览器中发送XMLHttpRequest请求
- 在nodejs中发送http请求
- 支持promise api
- 支持拦截器