更新日期:2021-2-10 晚 【新年快乐】
先看这里:Vue学习随笔+商城项目【上】
目录
- (十二)组件化高级
- (十三)Vue实例的生命周期
- (十四)前端模块化
- (十五)webpack
- (十六)vue-cli
- (十七)vue-router
- (十八)Promise
- (十九)Vuex
- (二十)Axios
- (二十一)Better Scroll
- 商城项目1.0
- (二十一)Better Scroll
- 商城项目1.0
(十二)组件化高级
12.1 slot-插槽的基本使用
概念:
- 组件的插槽是为了让我们封装的组件更具有扩展性
- 让使用者可以决定组件内部的一些内容到底展示什么
思想:
- 抽取共性,保留不同
- 我们在使用组件的时候有时候希望,在组件内部定制化内容,例如京东这样。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K8jb2Pe0-1612964660653)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.1-1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-izzIOxgP-1612964660654)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.1-2.png)]
<!-- 父组件 -->
<div id="app">
<cpn></cpn>
<cpn>
<span style="color:red;">这是插槽内容222</span>
</cpn>
<cpn>
<i style="color:red;">这是插槽内容333</i>
</cpn>
<cpn></cpn>
</div>
<!-- 插槽的基本使用使用<slot></slot> -->
<!-- 子组件 -->
<template id="cpn">
<div>
<div>
{{message}}
</div>
<!-- 插槽默认值 -->
<slot><button>button</button></slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>
const cpn = {
template: "#cpn",
data() {
return {
message: "我是子组件"
}
},
}
const app = new Vue({
el: "#app",
data() {
return {
message: "我是父组件消息"
}
},
components: {
cpn
},
})
</script>
简单使用插槽,定义template时候使用
slot
<!-- 子组件 -->
<template id="cpn">
<div>
<div>
{{message}}
</div>
<!-- 插槽默认值:<button>button</button> -->
<slot><button>button</button></slot>
</div>
</template>
<cpn></cpn>
<cpn><span style="color:red;">这是插槽内容222</span></cpn>
上述代码结果如图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JMgM66ey-1612964660655)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.1-3.png)]
如果想实现组件分成三部分就可以使用三个
<slot></slot>
来填充插槽了。
12.2 slot-具名插槽的使用
- 具名插槽,就是可以让插槽按指定的顺序填充,而没有具名的插槽是按照你填充的顺序排列的,而具名插槽可以自定义排列。
<!-- 父组件 -->
<div id="app">
<cpn>
<span>没具名</span>
<span slot="left">这是左边具名插槽</span>
<!-- 新语法 -->
<template v-slot:center>这是中间具名插槽</template>
<!-- 新语法缩写 -->
<template #right>这是右边具名插槽</template>
</cpn>
</div>
<!-- 插槽的基本使用使用<slot></slot> -->
<!-- 子组件 -->
<template id="cpn">
<div>
<slot name="left">左边</slot>
<slot name="center">中间</slot>
<slot name="right">右边</slot>
<slot>没有具名的插槽</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>
const cpn = {
template: "#cpn",
data() {
return {
message: "我是子组件"
}
},
}
const app = new Vue({
el: "#app",
data() {
return {
message: "我是父组件消息"
}
},
components: {
cpn
},
})
</script>
如图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Ea5pNbW-1612964660657)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.2-1.png)]
没有具名的插槽排在最后,因为在定义组件的时候,排在了最后,如果有多个按顺序排列。具名插槽按照自定义的顺序排列。
<!-- 插槽的基本使用使用<slot></slot> -->
<!-- 子组件模板 -->
<template id="cpn">
<div>
<slot name="left">左边</slot>
<slot name="center">中间</slot>
<slot name="right">右边</slot>
<slot>没有具名的插槽</slot>
</div>
</template>
<!-- 父组件 -->
<div id="app">
<cpn>
<span>没具名</span>
<span slot="left">这是左边具名插槽</span>
<!-- 新语法 -->
<template v-slot:center>这是中间具名插槽</template>
<!-- 新语法缩写 -->
<template #right>这是右边具名插槽</template>
</cpn>
</div>
注意:此处有是三种写法,获取指定插槽。
12.3 编译的作用域
- 前面说过组件都有自己的作用域,自己组件的作用在自己组件内。
<!DOCTYPE html>
<html lang="en">
<head>
<Meta charset="UTF-8">
<Meta name="viewport" content="width=device-width, initial-scale=1.0">
<Meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>编译的作用域</title>
</head>
<body>
<!-- 父组件 -->
<div id="app">
<!-- 使用的vue实例作用域的isShow -->
<cpn v-show="isShow"></cpn>
</div>
<!-- 插槽的基本使用使用<slot></slot> -->
<!-- 子组件 -->
<template id="cpn">
<div>
<h2>我是子组件</h2>
<p>哈哈哈</p>
<!-- 组件作用域,使用的子组件的作用域 -->
<button v-show="isShow"></button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>
const cpn = {
template: "#cpn",
data() {
return {
isShwo:false
}
},
}
const app = new Vue({
el: "#app",
data() {
return {
message: "我是父组件消息",
isShow:true
}
},
components: {
cpn
},
})
</script>
</body>
</html>
结果如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ol0Mc4rI-1612964660658)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.3-1.png)]
子组件使用的是子组件的isShow,子组件为false,所以button没显示,被隐藏。
12.4 作用域插槽案例
<!DOCTYPE html>
<html lang="en">
<head>
<Meta charset="UTF-8">
<Meta name="viewport" content="width=device-width, initial-scale=1.0">
<Meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>作用域插槽案例</title>
</head>
<body>
<!-- 父组件 -->
<div id="app">
<cpn></cpn>
<!-- 目的是获取子组件数据 -->
<cpn>
<!-- 2.5以下必须使用template -->
<template slot-scope="slot">
<!-- <span v-for="(item, index) in slot.data" :key="index">{{item}}-</span> -->
<span>{{slot.data.join(' - ')}}</span>
</template>
</cpn>
<cpn>
<!-- 2.5以下必须使用template -->
<template slot-scope="slot">
<!-- <span v-for="(item, index) in slot.data" :key="index">{{item}}*</span> -->
<span>{{slot.data.join(' * ')}}</span>
</template>
</cpn>
</div>
<!-- 插槽的基本使用使用<slot></slot> -->
<!-- 子组件 -->
<template id="cpn">
<div>
<slot :data="pLanguage">
<ul>
<li v-for="(item, index) in pLanguage" :key="index">{{item}}</li>
</ul>
</slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>
const cpn = {
template: "#cpn",
data() {
return {
isShwo:false,
pLanguage:['JavaScript','Java','C++','C']
}
},
}
const app = new Vue({
el: "#app",
data() {
return {
isShow:true
}
},
components: {
cpn
},
})
</script>
</body>
</html>
组件中使用
slot-scope="slot"
**(2.6.0已经废弃)**给插槽属性命名,在通过slot
调用绑定在插槽上的属性。也可以使用v-slot="slot"
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wWchwHTW-1612964660659)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\12.4-1.png)]
(十三)Vue实例的生命周期
13.1 生命周期图
Vue实例的生命周期中有多个状态。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TdyfidhJ-1612964660660)(C:\Users\86136\Desktop\repository\DailyCode\source_BASIS\Stage10.0-Vue\VueLearnNotes-master\VueLearnNotes-master\13-Vue实例的生命周期\images\lifecycle1.png)]
测试代码
<!DOCTYPE html>
<html lang="en">
<head>
<Meta charset="UTF-8">
<Meta name="viewport" content="width=device-width, initial-scale=1.0">
<Meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Vue实例的生命周期</title>
<!-- 引入vue.js -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
</head>
<body>
<div id="app">
<h1>测试生命周期</h1>
<div>{{msg}}</div>
<hr>
<h3>测试beforeUpdate和update两个钩子函数</h3>
<button @click="handlerUpdate">更新数据</button>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
msg:"12345"
},
methods: {
handlerUpdate:function(){
this.msg=this.msg.split("").reverse().join("");
},
},//按照示意图依次调用
beforeCreate:function(){
console.log("调用了beforeCreate钩子函数");
},
created:function () {
console.log("调用了created钩子函数");
},
beforeMount: function () {
console.log('调用了beforeMount钩子函数')
},
mounted: function () {
console.log('调用了mounted钩子函数')
},
beforeUpdate: function () {
console.log("调用了beforeUpdate钩子函数")
},
updated: function () {
console.log("调用了updated钩子函数");
},
beforeDestroy: function () {
console.log("调用了beforeDestroy钩子函数")
},
destroyed: function () {
console.log("调用了destroyed钩子函数");
},
});
</script>
</body>
</html>
如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Rtw1k0lD-1612964660661)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\1.png)]
点击更新数据后:
12345
变成了54321
,此时调用了:
打开F12控制台
直接输入app.$destroy()
主动销毁Vue实例调用:
13.2 再探究
13.2.1 beforeCreate之前
初始化钩子函数和生命周期
13.2.2 beforeCreate和created钩子函数间的生命周期
在beforeCreate和created之间,进行数据观测(data observer) ,也就是在这个时候开始监控data中的数据变化了,同时初始化事件。
生命周期展示图
13.2.3 created钩子函数和beforeMount间的生命周期
对于created钩子函数和beforeMount有判断:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pysh2brU-1612964660665)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\2.png)]
13.2.3.1el选项对生命周期影响
- 有el选项
new Vue({
el: '#app',
beforeCreate: function () {
console.log('调用了beforeCreat钩子函数')
},
created: function () {
console.log('调用了created钩子函数')
},
beforeMount: function () {
console.log('调用了beforeMount钩子函数')
},
mounted: function () {
console.log('调用了mounted钩子函数')
}
})
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g88IvsmO-1612964660667)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\3.png)]
- 无el选项
new Vue({
beforeCreate: function () {
console.log('调用了beforeCreat钩子函数')
},
created: function () {
console.log('调用了created钩子函数')
},
beforeMount: function () {
console.log('调用了beforeMount钩子函数')
},
mounted: function () {
console.log('调用了mounted钩子函数')
}
})
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2TEokYau-1612964660669)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\4.png)]
证明没有el选项,则停止编译,也意味着暂时停止了生命周期。生命周期到created钩子函数就结束了。而当我们不加el选项,但是手动执行vm.$mount(el)方法的话,也能够使暂停的生命周期进行下去,例如:
var app = new Vue({
beforeCreate: function () {
console.log('调用了beforeCreat钩子函数')
},
created: function () {
console.log('调用了created钩子函数')
},
beforeMount: function () {
console.log('调用了beforeMount钩子函数')
},
mounted: function () {
console.log('调用了mounted钩子函数')
}
})
app.$mount('#app')
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TrT6qNzn-1612964660671)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\3.png)]
13.2.3.2 template
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zwfebznN-1612964660673)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\5.png)]
同时使用
template
和HTML
,查看优先级:
<h1>测试template和HTML的优先级</h1>
<div id="app">
<p>HTML优先</p>
</div>
<script>
var app = new Vue({
el:"#app",
data:{
msg:"template优先"
},
template:"<p>{{msg}}</p>",
});
</script>
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xGmcwHkY-1612964660674)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\6.png)]
结论
- 如果Vue实例对象中有template参数选项,则将其作为模板编译成render函数
- 如果没有template参数选项,则将外部的HTML作为模板编译(template),也就是说,template参数选项的优先级要比外部的HTML高
- 如果1,2条件都不具备,则报错
注意
- Vue需要通过el去找对应的template,Vue实例通过el的参数,首先找自己有没有template,如果没有再去找外部的html,找到后将其编译成render函数。
- 也可以直接调用render选项,优先级:
render函数选项 > template参数 > 外部HTML
。
new Vue({
el: '#app',
render (createElement) {
return (....)
}
})
13.2.4 beforeMount和mounted钩子函数间的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o8sx37we-1612964660675)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\7.png)]
beforeMount
载入前(完成了data和el数据初始化),但是页面中的内容还是vue中的占位符,data中的message信息没有被挂在到Dom节点中,在这里可以在渲染前最后一次更改数据的机会,不会触发其他的钩子函数,一般可以在这里做初始数据的获取。
Mount
载入后html已经渲染(ajax请求可以放在这个函数中),把vue实例中的data里的message挂载到DOM节点中去
这里两个钩子函数间是载入数据。
13.2.5 beforeUpdate钩子函数和updated钩子函数间的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zqu4DhzU-1612964660676)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\8.png)]
在Vue中,修改数据会导致重新渲染,依次调用beforeUpdate钩子函数和updated钩子函数
var app = new Vue({
el: '#app',
data: {
msg: 1
},
template: '<div id="app"><p></p></div>',
beforeUpdate: function () {
console.log('调用了beforeUpdate钩子函数')
},
updated: function () {
console.log('调用了updated钩子函数')
}
})
app.msg = 2
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xpI4PVU3-1612964660678)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\9.png)]
如果绑定了数据,会调用两个钩子函数:
<h1>测试有数据绑定修改数据,钩子函数调用情况</h1>
<div id="app">
</div>
<script>
var app = new Vue({
el:"#app",
template:"<p>{{msg}}</p>",
data:{
msg:"原数据"
},
beforeUpdate: function () {
console.log("调用了beforeUpdate钩子函数")
},
updated: function () {
console.log("调用了updated钩子函数");
},
});
app.msg = "数据被修改了";
</script>
结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2GK8LgEH-1612964660678)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\10.png)]
注意只有写入模板的数据才会被追踪
13.2.6 beforeDestroy和destroyed钩子函数间的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q4h8rCwr-1612964660679)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\11.png)]
13.2.6.1 beforeDestroy
销毁前执行($destroy方法被调用的时候就会执行),一般在这里善后:清除计时器、清除非指令绑定的事件等等…’)
13.2.6.2 destroyed
销毁后 (Dom元素存在,只是不再受vue控制),卸载watcher,事件监听,子组件
总结
- beforecreate : 可以在这加个loading事件
- created :在这结束loading,还做一些初始数据的获取,实现函数自-执行
- mounted : 在这发起后端请求,拿回数据,配合路由钩子做一些事情
- beforeDestroy: 你确认删除XX吗?
- destroyed :当前组件已被删除,清空相关内容
(十四)前端模块化
14.1 为什么要有模块化
aaa.js
//小明开发
var name = '小明'
var age = 22
function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20));
}
- 此时小明的
sum
是没有问题的。
bbb.js
//小红
var name = "小红"
var flag = false
- 此时小明和小红各自用各自的
flag
你变量没问题。
但是此时小明又创建了一个mmm.js
//小明
if(flag){
console.log("flag是true")
}
<script src="aaa.js" ></script>
<script src="bbb.js" ></script>
<script src="ccc.js" ></script>
- 此时小明知道自己在aaa.js中定义的
flag
是true
,认为打印没有问题,但是不知道小红的bbb.js中也定义了flag
为true
,所以mmm.js文件并没有打印出“flag是true”。
这就是全局变量同名问题。
14.2 使用导出全局变量模块解决全局变量同名问题
aaa.js
//模块对象
var moduleA = (function (param) {
//导出对象
var obj = {}
var name = '小明'
var age = 22
function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20))
}
obj.flag=false
return obj
})()
mmm.js
//小明
//使用全局变量moduleA
if(moduleA.flag){
console.log("flag是true")
}
这样直接使用aaa.js导出的moduleA变量获取小明自己定义的flag
。
14.3 Commonjs的模块化实现
aaa.js
//Commonjs需要nodeJS支持
var name = '小明'
var age = 22
function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20))
}
// module.exports = {
// flag : flag,
// sum : sum
// }
//导出对象
module.exports = {
flag,
sum
}
使用module.exports = {}
导出需要的对象。
mmm.js
//导入对象,nodejs语法,需要node支持,从aaa.js取出对象
var {flag,sum} = require("./aaa")
console.log(sum(10,20));
if(flag){
console.log("flag is true");
}
使用 var {flag,sum} = require("./aaa")
获取已经导出的对象中自己所需要的对象。
ES6的模块化实现
如何实现模块化,在html中需要使用type='module'
属性。
<script src="aaa.js" type="module"></script>
<script src="bbb.js" type="module"></script>
<script src="mmm.js" type="module"></script>
此时表示aaa.js是一个单独的模块,此模块是有作用域的。如果要使用aaa.js内的变量,需要在aaa.js中先导出变量,再在需要使用的地方导出变量。
14.4.1 直接导出
export let name = '小明'
使用
import {name} from './aaa.js'
console.log(name)
./aaa.js
表示aaa.js和mmm.js在同级目录。
如图打印结果。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ALP9vlME-1612964660680)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\14.4-1.png)]
14.4.2 统一导出
var age = 22
function sum(num1, num2) {
return num1 + num2
}
var flag = true
if (flag) {
console.log(sum(10, 20))
}
//2.最后统一导出
export {
flag,sum,age
}
使用
import {name,flag,sum} from './aaa.js'
导入多个变量
import {name,flag,sum} from './aaa.js'
console.log(name)
if(flag){
console.log("小明是天才");
}
console.log(sum(20,30));
使用{}将需要的变量放置进去
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UxKMlUaq-1612964660681)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\14.4-2.png)]
14.4.3 导出函数/类
在aaa.js中添加
//3.导出函数/类
export function say(value) {
console.log(value);
}
export class Person{
run(){
console.log("奔跑");
}
}
在mmm.js中添加
import {name,flag,sum,say,Person} from './aaa.js'
console.log(name)
if(flag){
console.log("小明是天才");
}
console.log(sum(20,30));
say('hello')
const p = new Person();
p.run();
如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MUJtzvqf-1612964660682)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\14.4-3.png)]
14.4.4 默认导入 export default
导出
export default {
flag,sum,age
}
导入
//4.默认导入 export default
import aaa from './aaa.js'
console.log(aaa.sum(10,110));
注意:使用默认导出会将所有需要导出的变量打包成一个对象,此时导出一个对象,此时我在
mmm.js
中导入变量时候命名为aaa,如果要调用变量需要使用aaa.变量。
14.4.5 统一全部导入
使用
import * as aaa from './aaa.js'
统一全部导入
// 5.统一全部导入
import * as aaa from './aaa.js'
console.log(aaa.flag);
console.log(aaa.name);
(十五)webpack
15.1 webpack起步
15.1.1 什么是webpack
webpack是一个JavaScript应用的静态模块打包工具。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FTMlmYuI-1612964660683)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-1.png)]
从这句话中有两个要点,模块和打包需要关注。grunt/gulp都可以打包,那有什么区别。
模块化
webpack可以支持前端模块化的一些方案,例如AMD、CMD、Commonjs、ES6。可以处理模块之间的依赖关系。不仅仅是js文件可以模块化,图片、css、json文件等等都可以模块化。
打包
webpack可以将模块资源打包成一个或者多个包,并且在打包过程中可以处理资源,例如压缩图片,将scss转成css,ES6语法转成ES5语法,将TypeScript转成JavaScript等等操作。grunt/gulp也可以打包。
和grunt/glup的对比
const gulp = require('gulp') const babel = require('gulp-babel') gulp.task('js'()=> gulp.src('src/*.js') .pipe(babel({ presets:['es2015'] })) .pipe(gulp.dest('dist')) );
- 什么时候使用grunt/gulp呢?
- 如果工程依赖简单,甚至没有模块化
- 只需要进行简单的合并/压缩
- 如果模块复杂,相互依赖性强,我们需要使用webpack
- grunt/glup和webpack区别
webpack就是前端模块化打包工具
15.1.2 webpack的安装
- webpack依赖node环境。
- node环境依赖众多包,所以需要npm,npm(node packages manager)node包管理工具
- nvm是node管理工具可以自由切换node环境版本
全局安装webpack
npm install webpack -g
//指定版本安装
npm install [email protected] -g
由于vue-cli2基于webpack3.6.0
如果要用vue-cli2的可以使用npm install [email protected] -g
局部安装
npm install webpack --save-dev
-
在终端执行webpack命令,使用的是全局安装。
-
当在package.json中定义了scripts时,其中包括了webpack命令,那么使用的是局部webpack
15.1.3 起步
目录结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jcq1zAYs-1612964660685)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-2.png)]
如图所示在src文件夹(源码文件夹),dist(要发布的文件,已经处理过的)。
1.新建入口js文件main.js
和mathUtils.js
,main.js
依赖mathUtils.js
。
mathUtils
//1.新建mathUtils.js,用Commonjs规范导出
function add(num1,num2) {
return num1+num2
}
function mul(num1,num2) {
return num1*num2
}
module.exports = {
add,mul
}
main.js
//2.新建入口js文件main.js 导入mathUtil.js文件,并调用
const {add,mul} = require("./mathUtils.js")
console.log(add(10,20))
console.log(mul(10,10))
2.使用webpack命令打包js文件
注意:webpack3使用
webpack ./src/main.js ./dist/bundle.js
webpack4,webpack打包在01-webpack的起步目录下打开终端
webpack ./scr/main.js -o ./dist/bundle.js
我全局安装的是[email protected],所以在根路径执行
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lAe82Pd0-1612964660687)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-3.png)]
如图显示打包成功,查看dist文件夹下自动生成了一个bundle.js
。
bundle.js
//2.新建入口js文件main.js 导入mathUtil.js文件,并调用
const {add,mul} = __webpack_require__(1)
console.log(add(10,20))
console.log(mul(10,10))
/***/ }),
/* 1 */
/***/ (function(module, exports) {
//1.新建mathUtils.js,用Commonjs规范导出
function add(num1,num2) {
return num1+num2
}
function mul(num1,num2) {
return num1*num2
}
module.exports = {
add,mul
}
内容很多,其中包含mathUtils.js和main.js 内容,打包成功。
3.新建一个index.html文件,导入bundle.js
<!DOCTYPE html>
<html lang="en">
<head>
<Meta charset="UTF-8">
<Meta name="viewport" content="width=device-width, initial-scale=1.0">
<Meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webpack入门</title>
</head>
<body>
<!-- 3.新建一个indexhtml文件并使用 webpack ./src/main.js ./dist/bundle.js webpack3使用此命令 -->
<!-- 4.引用webpack打包后的js文件 -->
<script src="./dist/bundle.js"></script>
</body>
</html>
如图测试,打印成功。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6vUOf9n8-1612964660688)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-4.png)]
4.新建一个info.js
使用ES6的语法导出
info.js
//es6语法导出
export default {
name:'zzz',
age:24,
}
main.js导入info.js
//使用es6语法导入
import info from './info.js'
console.log(info.name)
console.log(info.age)
再次使用
webpack ./src/main.js ./dist/bundle.js
,重新打包
5.打开index.html测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CfWJwXmt-1612964660689)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-5.png)]
总结
webpack可以帮我们打包js文件,只要指定入口文件(main.js)和输出的文件(bundle.js),不管是es6的模块化还是Commonjs的模块化,webpack都可以帮我们打包,还可以帮我们处理模块之间的依赖。
15.2 webpack的配置
15.2.1 基本配置
如果每次都用webpack命令自己写入口文件和出口文件会很麻烦,此时我们可以使用webpack的配置。
准备工作:复制01-webpack的起步文件夹并粘贴在同级目录,改名为02-webpack的配置。
webpack.config.js
1.在根目录(02-webpack的配置)下新建一个webpack.config.js
//1.导入node的path包获取绝对路径,需要使用npm init初始化node包
const path = require('path')
//2.配置webpack的入口和出口
module.exports = {
entry: './src/main.js',//入口文件
output:{
path: path.resolve(__dirname, 'dist'),//动态获取打包后的文件路径,path.resolve拼接路径
filename: 'bundle.js'//打包后的文件名
}
}
2.在02-webpack的配置根目录执行npm init
初始化node包,因为配置文件中用到了node的path包
npm init
初始化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jyX8dBmh-1612964660690)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-6.png)]
3.使用webpack打包
webkpack
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JwzaOVW4-1612964660691)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-7.png)]
这样入口和出口的配置已经配置完成了,只需要使用webpack命令就行了。
4.使用自定义脚本(script)启动
一般来是我们使用的是
npm run dev//开发环境
npm run build//生产环境
package.json
- 在package.json中的script中加上映射:
"build": "webpack"
- 使用
npm run build
相当于webpack
命令
npm run build
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gT6yZJh4-1612964660692)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-8.png)]
15.2.2 全局安装和局部安装
-
webpack有全局安装和局部安装。
-
使用
npm run build
执行webpack会先从本地查找是否有webpack,如果没有会使用全局的。 -
此时本地需要安装webapck
npm install [email protected] --save-dev
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EtuUOXQG-1612964660693)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-9.png)]
再次使用npm run build
,使用的是本地webpack版本。
15.3 webpack的loader
什么是loader
-
loader是webpack中一个非常核心的概念。
-
此时就需要webpack的扩展,使用对应的loader就可以。
loader使用
-
步骤
-
步骤一:通过npm安装需要使用的loader
-
步骤二:通过webpack.config.js中的modules关键字下进行配置
-
-
大部分loader可以在webpack的官网找到对应的配置。
CSS文件处理
1.将除了入口文件(main.js)所有js文件放在js文件夹,新建一个css文件夹,新建一个normal.css文件
normal.css
body{
background-color: red;
}
2.main.js导入依赖
//4.依赖css文件
require('./css/normal.css')
此时如果直接进行打包npm run build
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fipMVkId-1612964660694)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-10.png)]
3.安装css-loader
npm install --save-dev css-loader
4.使用css-loader
module.exports = {
module: {
rules: [
{
test: /\.css$/,//正则表达式匹配css文件
//css-loader只负责css文件加载,不负责解析,要解析需要使用style-loader
use: [{
loader: 'css-loader'
}]//使用loader
}
]
}
}
执行
npm run build
,提示打包成功,但是背景色并没有变红色,是因为css-loader只负责加载css文件,不负责解析,如果要将样式解析到dom元素中需要使用style-loader。
5.安装使用style-loader
npm install --save-dev style-loader
module: {
rules: [
{
test: /\.css$/,//正则表达式匹配css文件
//css-loader只负责css文件加载,不负责解析,要解析需要使用style-loader
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}]//使用loader
}
]
}
注意:webpack使用多个loader是从右往左解析的,所以需要将css-loader放在style-loader右边,先加载后解析。
此时样式成加载解析到DOM元素上。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wqRubL8O-1612964660695)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-11.png)]
less文件处理
special.less
@fontSize:50px;//定义变量字体大小
@fontColor:orange;//定义变量字体颜色
body{
font-size: @fontSize;
color: @fontColor;
}
2.main.js中导入less文件模块
//5.依赖less文件
require('./css/special.less')
//6.向页面写入一些内容
document.writeln("hello,zzzz!")
3.安装使用less-loader
npm install --save-dev less-loader less
在webpack.config.js
中使用less-loader
module: {
rules: [
{
test: /\.less$/,//正则表达式匹配css文件
//css-loader只负责css文件加载,不负责解析,要解析需要使用style-loader
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}, {
loader: 'less-loader'//less文件loader
}]//使用loader
}
]
}
4.执行npm run build
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T4z0u9Od-1612964660696)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-12.png)]
less文件生效了,字体是orange,大小为50px。
图片文件的处理
准备工作,准备两张图片,图片大小为一张8KB以下(实际大小为5KB,名称为small.jpg),一张大于8KB(实际大小为10KB,名称为big.jpg),新建一个img文件夹将两张图片放入。
body{
/* background-color: red; */
background: url("../img/small.jpg");
}
2.安装使用url-loader处理图片
npm install --save-dev url-loader
- 配置
{
test: /\.(png|jpg|gif)$/,//匹配png/jpg/gif格式图片
use: [
{
loader: 'url-loader',
options: {
limit: 8192//图片小于8KB时候将图片转成base64字符串,大于8KB需要使用file-loader
}
}
]
}
limit属性
3.打包
- 使用npm run build打包后,打开index.html。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kbfiOPVE-1612964660697)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-13.png)]
小于
limit
大小的图片地址被编译成base64格式的字符串。
body{
/* background-color: red; */
/* background: url("../img/small.jpg"); */
background: url("../img/big.jpg");
}
- 再次打包,报错,提示未找到file-loader模块。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mkhApmlJ-1612964660698)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-14.png)]
因为大于
limit
的图片需要file-loader
来打包。
4.安装使用file-loader处理图片
npm install --save-dev file-loader
- 不需要配置,因为url-loader超过limit的图片会直接使用file-loader。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-edtCmIhv-1612964660699)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-15.png)]
1.当加载的图片大小小于limit,使用base64将图片编译成字符串
2.当加载的图片大小大于limit,使用file-loader模块直接将big.jpg直接打包到dist文件夹,文件名会使用hash值防止重复。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tpq1xt6F-1612964660700)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-16.png)]
5.如何使用file-loader,指定路径
修改output属性 publicPath: ‘dist/’
output:{
path: path.resolve(__dirname, 'dist'),//动态获取打包后的文件路径,path.resolve拼接路径
filename: 'bundle.js',//打包后的文件名
publicPath: 'dist/'
},
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mpjhJuI7-1612964660701)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-17.png)]
注意:一般来说,index.html最终也会打包到dist文件夹下,所以,并不需要配置publicPath,如何打包index.html请看webpack处理.vue文件。
file-loader打包后,使用hash值做文件名太长,此时可以使用options的一些配置。
options: {
limit: 8192,
name: 'img/[name].[hash:8].[ext]'
}
修改options,加上name属性,其中img表示文件父目录,[name]表示文件名(原文件名),[hash:8]表示将hash截取8位,[ext]表示后缀
再次打包
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B9E3vJed-1612964660702)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-18.png)]
ES6语法处理
- webpack打包时候ES6语法没有打包成ES5语法,如果需要将ES6打包成ES5语法,那么就需要使用babel。直接使用babel对应的loader就可以了。
安装
npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
配置
{
test: /\.js$/,
//排除node模块的js和bower的js
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
// presets: ['@babel/preset-env']
//这里直接使用指定
presets: ['es2015']
}
}
}
1.如果要使用@babel/preset-env这里需要在根目录新建一个babel的文件
2.exclude排除不需要打包的文件
15.4 webpack的vue
简单安装使用vue
- 如果需要使用vue,必须使用npm先安装vue。
npm install vue --save
- 使用vue简单开发。
准备工作
- 复制
03-webpack的loader
到同级目录,改名为04-webpack的vue
,并在04-webpack的vue
根目录执行npm install vue --save
,下载安装vue。
1.在入口文件main.js导入已安装的vue,并在index.html声明要挂载的div。在main.js加入以下代码。
//6.使用vue开发
import Vue from 'vue'
const app = new Vue({
el: "#app",
data: {
message: "hello webpack and vue"
}
})
<div id="app">
<h2>{{message}}</h2>
</div>
2.再次打包npm run build
后打开index.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o1hiivl6-1612964660703)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-19.png)]
1.
runtime-only
模式,代码中不可以有任何template,因为无法解析。2.
runtime-complier
模式,代码中可以有template,因为complier可以用于编译template。
- 在webpack中配置,设置指定使用
runtime-complier
模式。
webpack.config.js
resolve: {
// alias:别名
alias: {
//指定vue使用vue.esm.js
'vue$':'vue/dist/vue.esm.js'
}
}
3.重新打包,显示正确
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZMUcdDE0-1612964660704)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-20.png)]
如何分步抽取实现vue模块
创建vue的template和el关系
el表示挂载DOM的挂载点
template里面的html将替换挂载点
<!DOCTYPE html>
<html lang="en">
<head>
<Meta charset="UTF-8">
<Meta name="viewport" content="width=device-width, initial-scale=1.0">
<Meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>webpack入门</title>
</head>
<body>
<div id="app">
</div>
<script src="./dist/bundle.js"></script>
</body>
</html>
1.第一次抽取,使用template替换<div id="app"></div>
。
//6.使用vue开发
import Vue from 'vue'
new Vue({
el: "#app",
template:`
<div>
<h2>{{message}}</h2>
<button @click='btnClick'>这是一个按钮</button>
<h2>{{name}}</h2>
</div>
`,
data: {
message: "hello webpack and vue",
name: 'zzzz'
},
methods: {
btnClick(){
console.log("按钮被点击了")
}
},
})
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G24HSsrt-1612964660705)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-21.png)]
2.第二次抽取,使用组件化思想替换template
- 考虑第一次抽取,写在template中,main.js的vue代码太冗余。
//1.定义一个组件
const App = {
template: `
<div>
<h2>{{message}}</h2>
<button @click='btnClick'>这是一个按钮</button>
<h2>{{name}}</h2>
</div>
`,
data() {
return {
message: "hello webpack and vue",
name: 'zzzz'
}
},
methods: {
btnClick(){
console.log("按钮被点击了")
}
},
}
new Vue({
el: "#app",
//使用组件
template: '<App/>',
components: {
//注册局部组件
App
}
})
再次使用npm run build
打包,打包成功,显示和使用template替换div一样。
3.第三次抽取组件对象,封装到新的js文件,并使用模块化导入main.js
此处我的vue-loader是15.7.2。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SgREDQaZ-1612964660706)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-24.png)]
将其修改为13.0.0
"vue-loader": "^13.0.0"
重新安装版本
npm install
再次打包,打包成功,样式生效了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-01w403rX-1612964660707)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-25.png)]
6.组件化开发
我们使用app.vue分离了模板、行为、样式,但是不可能所有的模板和样式都在一个vue文件内,所以要用组件化。
Cpn.vue组件
<template>
<div>
<h2 class='title'>{{name}}</h2>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: "Cpn",
data() {
return {
name: "组件名字是Cpn"
};
}
};
</script>
<style scoped>
.title {
color: red;
}
</style>
将Cpn.vue组件导入到App.vue
<template>
<div>
<h2 class='title'>{{message}}</h2>
<button @click="btnClick">按钮</button>
<h2>{{name}}</h2>
<!-- 使用Cpn组件 -->
<Cpn/>
</div>
</template>
<script type="text/ecmascript-6">
//导入Cpn组件
import Cpn from './Cpn.vue'
export default {
name: "App",
data() {
return {
message: "hello webpack",
name: "zzz"
};
},
methods: {
btnclick() {}
},
components: {
Cpn//注册Cpn组件
}
};
</script>
<style scoped>
.title {
color: green;
}
</style>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2XZOibJw-1612964660708)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-26.png)]
基于此,一个vue文件可以依赖导入很多vue文件,组成一个单页面富应用。
如果你在使用ES6语法导入模块时候想要简写的时候,例如这样省略
.vue
后缀
import Cpn from './Cpn'
可以在webpack.config.js
中配置:
resolve: {
//导入模块简写省略指定后缀
extensions: ['.js', '.css', '.vue'],
// alias:别名
alias: {
//指定vue使用vue.esm.js
'vue$':'vue/dist/vue.esm.js'
}
}
15.5 webpack的plugin
-
loader和plugin的区别
-
plugin的使用过程
准备工作
复制04-webpack的vue到同级目录,并改名为05-webpack的plugin
添加版权的Plugin
//获取webpack
const webpack = require('webpack')
//2.配置plugins
module.exports = {
...
plugins:[
new webpack.BannerPlugin('最终解释权归zz所有')
]
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yENSCbEL-1612964660708)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-27.png)]
- 打包后,查看bundle.js,结果如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9R2lZCmU-1612964660709)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-28.png)]
- 多了一行我们自定义的版权声明注释。
打包html的plugin
-
之前我们的index.html文件都是存放在根目录下的。
-
在正式发布项目的时候发布的是dist文件夹的内容,但是dist文件夹是没有index.html文件的,那么打包就没有意义了。
-
所以我们需要将index.html也打包到dist文件夹中,这就需要使用**
HtmlWebpackPlugin
**插件了。
HtmlWebpackPlugin
:
- 首先需要安装**
HtmlWebpackPlugin
**插件
npm install html-webpack-plugin --save-dev
//获取htmlWebpackPlugin对象
const htmlWbepackPlugin = require('html-webpack-plugin')
//2.配置plugins
module.exports = {
...
plugins:[
new webpack.BannerPlugin('最终解释权归zz所有'),
new htmlWbepackPlugin({
template: 'index.html'
})
]
}
1.template表示根据哪个模板来生成index.html
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oWXarUgw-1612964660710)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-29.png)]
- 自动加入了script引入了bundle.js。
压缩打包代码插件
-
uglifyjs-webpack-plugin是第三方插件,如果是vuecli2需要指定版本1.1.1。
-
安装:
npm install [email protected] --save-dev
- 配置plugin
//获取uglifyjs-webpack-plugin对象
const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
//2.配置plugins
module.exports = {
...
plugins:[
new webpack.BannerPlugin('最终解释权归zz所有'),
new htmlWbepackPlugin({
template: 'index.html'
}),
new uglifyjsWebpackPlugin()
]
}
- 打包过后,打开bundle.js,发现已经压缩了,此时版权声明被删除了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zblEW31p-1612964660711)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-30.png)]
15.6 webpack搭建本地服务器
- webpack提供了一个可选的本地开发服务器,这个本地服务器基于node.js搭建,内部使用了express框架,可以实现热启动。
准备工作复制05-webpack的plugin文件夹到同级目录,并改名为06-webpack搭建本地服务器。
- 不过这是一个单独的模块,在webpack中使用之前需要先安装:
npm install --save-dev [email protected]
//2.配置webpack的入口和出口
module.exports = {
...
devServer: {
contentBase: './dist',//服务的文件夹
port: 4000,
inline: true//是否实时刷新
}
}
- 配置package.json的script:
- 直接输入指令 “webpack-dev-server --open” 会在全局找
"dev": "webpack-dev-server --open"
–open表示直接打开浏览器
- 启动服务器
npm run dev
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7hs1pth8-1612964660712)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-31.png)]
1.服务器启动在内存中。
15.7 webpack的配置文件分离
base.config.js(公共的配置)
//1.导入node的path包获取绝对路径,需要使用npm init初始化node包
const path = require('path')
//获取webpack
const webpack = require('webpack')
//获取htmlWebpackPlugin对象
const htmlWbepackPlugin = require('html-webpack-plugin')
//2.配置webpack的入口和出口
module.exports = {
entry: './src/main.js',//入口文件
output:{
path: path.resolve(__dirname, 'dist'),//动态获取打包后的文件路径,path.resolve拼接路径
filename: 'bundle.js',//打包后的文件名
// publicPath: 'dist/'
},
module: {
rules: [
{
test: /\.css$/,//正则表达式匹配css文件
//css-loader只负责css文件加载,不负责解析,要解析需要使用style-loader
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}]//使用loader
},
{
test: /\.less$/,//正则表达式匹配css文件
//css-loader只负责css文件加载,不负责解析,要解析需要使用style-loader
use: [{
loader: 'style-loader'
}, {
loader: 'css-loader'
}, {
loader: 'less-loader'//less文件loader
}]//使用loader
},
{
test: /\.(png|jpg|gif)$/,//匹配png/jpg/gif格式图片
use: [
{
loader: 'url-loader',
options: {
limit: 8192,//图片小于8KB时候将图片转成base64字符串,大于8KB需要使用file-loader
name: 'img/[name].[hash:8].[ext]'//img表示文件父目录,[name]表示文件名,[hash:8]表示将hash截取8位[ext]表示后缀
}
}
]
},
{
test: /\.js$/,
//排除node模块的js和bower的js
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
//如果要使用@babel/preset-env这里需要在根目录新建一个babel的文件
// presets: ['@babel/preset-env']
//这里直接使用指定
presets: ['es2015']
}
}
},
{
test: /\.vue$/,//正则匹配.vue文件
use: {
loader: 'vue-loader'
}
}
]
},
resolve: {
// alias:别名
alias: {
//指定vue使用vue.esm.js
'vue$':'vue/dist/vue.esm.js'
}
},
plugins:[
new webpack.BannerPlugin('最终解释权归zz所有'),
new htmlWbepackPlugin({
template: 'index.html'
})
]
}
dev.config.js(开发时候需要的配置)
module.exports = {
devServer: {
contentBase: './dist',//服务的文件夹
port: 4000,
inline: true//是否实时刷新
}
}
prod.config.js(构建发布时候需要的配置)
const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
module.exports = {
plugins:[
new uglifyjsWebpackPlugin()
]
}
npm isntall webpack-merge --save-dev
//导入webpack-merge对象
const webpackMerge = require('webpack-merge')
//导入base.config.js
const baseConfig = require('./base.config')
//使用webpackMerge将baseConfig和dev.config的内容合并
module.exports = webpackMerge(baseConfig, {
devServer: {
contentBase: './dist',//服务的文件夹
port: 4000,
inline: true//是否实时刷新
}
})
const uglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
//导入webpack-merge对象
const webpackMerge = require('webpack-merge')
//导入base.config.js
const baseConfig = require('./base.config')
//使用webpackMerge将baseConfig和prod.config的内容合并
module.exports = webpackMerge(baseConfig, {
plugins:[
new uglifyjsWebpackPlugin()
]
})
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --config ./build/prod.config.js",
"dev": "webpack-dev-server --open --config ./build/dev.config.js"
}
- 此时使用
npm run build
打包文件,dist文件并不在根目录下,因为我们在base.config.js
中配置的出口文件使用的是当前文件的路径,即打包的根路径是配置文件的当前路径,也就是build文件夹。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XLb4WQTk-1612964660715)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\15-32.png)]
entry: './src/main.js',//入口文件
output:{
path: path.resolve(__dirname, 'dist'),//动态获取打包后的文件路径,path.resolve拼接路径
filename: 'bundle.js',//打包后的文件名
// publicPath: 'dist/'
}
output:{
path: path.resolve(__dirname, '../dist'),//动态获取打包后的文件路径,path.resolve拼接路径
filename: 'bundle.js',//打包后的文件名
// publicPath: 'dist/'
}
(十六)vue-cli
16.1 vue-cli起步
什么是vue-cli
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:
- 通过
@vue/cli
搭建交互式的项目脚手架。 - 通过
@vue/cli
+@vue/cli-service-global
快速开始零配置原型开发。 - 一个运行时依赖 (
@vue/cli-service
),该依赖: - 一个丰富的官方插件集合,集成了前端生态中最好的工具。
- 一套完全图形化的创建和管理 Vue.js 项目的用户界面。
Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject。
CLI是什么意思?
- CLI是Command-Line Interface,即命令行界面,也叫脚手架。
- vue cli 是vue.js官方发布的一个vue.js项目的脚手架
- 使用vue-cli可以快速搭建vue开发环境和对应的webpack配置
vue cli使用
vue cli使用前提node
-
vue cli依赖nodejs环境,vue cli就是使用了webpack的模板。
-
安装vue脚手架,现在脚手架版本是vue cli3
npm install -g @vue/cli
- 如果使用yarn
yarn global add @vue/cli
- 安装完成后使用命令查看版本是否正确:
vue --version
注意安装cli失败
npm clean cache -force
拉取2.x模板(旧版本)
Vue CLI >= 3 和旧版使用了相同的 vue
命令,所以 Vue CLI 2 (vue-cli
) 被覆盖了。如果你仍然需要使用旧版本的 vue init
功能,你可以全局安装一个桥接工具:
npm install -g @vue/cli-init
# `vue init` 的运行效果将会跟 `[email protected]` 相同
vue init webpack my-project
1.在根目录新建一个文件夹16-vue-cli
,cd到此目录,新建一个vue-cli2的工程。
cd 16-vue-cli
//全局安装桥接工具
npm install -g @vue/cli-init
//新建一个vue-cli2项目
vue init webpack 01-vuecli2test
注意:如果是创建vue-cli3的项目使用:
vue create 02-vuecli3test
@vue-cli4
npx vue create 02-vuecli4test
2.创建工程选项含义
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Dx1dNNu-1612964660716)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-1.png)]
- project name:项目名字(默认)
- project description:项目描述
- author:作者(会默认拉去git的配置)
- vue build:vue构建时候使用的模式
- runtime+compiler:大多数人使用的,可以编译template模板
- runtime-only:比compiler模式要少6kb,并且效率更高,直接使用render函数
- install vue-router:是否安装vue路由
- user eslint to lint your code:是否使用ES规范
- set up unit tests:是否使用unit测试
- setup e2e tests with nightwatch:是否使用end 2 end,点到点自动化测试
- Should we run
npm install
for you after the project has been created? (recommended):使用npm还是yarn管理工具
注意:如果创建工程时候选择了使用ESLint规范,又不想使用了,需要在config文件夹下的index.js文件中找到useEslint,并改成false。
// Use Eslint Loader?
// If true, your code will be linted during bundling and
// linting errors and warnings will be shown in the console.
useEslint: true,
16.2 vue-cli的目录结构
- 创建完成后,目录如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ePjoCxio-1612964660717)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-2.png)]
build和config
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OUcPAUwn-1612964660718)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-3.png)]
-
如图所示,build中将webpack的配置文件做了分离:
webpack.base.conf.js
(公共配置)webpack.dev.conf.js
(开发环境)webpack.prod.conf.js
(生产环境)
-
我们使用的脚本命令配置在
package.json
中。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3r1TVf90-1612964660719)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-4.png)]
- 打包构建:
npm run build
- 如果搭建了本地服务器
webpack-dev-server
,本地开发环境:
npm run dev
build.js
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zz6pWaTw-1612964660720)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-5.png)]
- 在生产环境,即使用build打包时候,使用的是
webpack.prod.conf.js
配置文件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EGhDjxlI-1612964660721)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-6.png)]
-
源码中,显然使用了
webpack-merge
插件来合并prod配置文件和公共的配置文件,合并成一个配置文件并打包,而webpack.dev.conf.js
也是如此操作,在开发环境使用的是dev的配置文件。 -
config文件夹中是build的配置文件中所需的一些变量、对象,在
webpack.base.conf.js
中引入了index.js
。
const config = require('../config')
src和static
其他相关文件
.babelrc文件
- .babelrc是ES代码相关转化配置。
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}],
"stage-2"
],
"plugins": ["transform-vue-jsx", "transform-runtime"]
}
.editorconfig文件
root = true // 根据root开始查找
[*]
charset = utf-8
indent_style = space
indent_size = 2 // 缩进
end_of_line = lf // 最后换行
insert_final_newline = true // 最后添加一行
trim_trailing_whitespace = true // 清除多余空格
.eslintignore文件
- .eslintignore文件忽略一些不规范的代码。
/build/
/config/
/dist/
/*.js
.eslintrc.js文件
.gitignore文件
.postcssrc.js文件
- css转化是配置的一些。
index.html文件
package.json和package-lock.json
- package.json(包管理,记录大概安装的版本)
- package-lock.json(记录真实安装版本)
16.3 runtime-compiler和runtime-only区别
- 新建两个vuecli2项目:
//新建一个以runtime-compiler模式
vue init webpack 02-runtime-compiler
//新建一个以runtime-only模式
vue init webpack 03-runtime-only
- 两个项目的main.js区别
runtime-compiler
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
components: { App },
template: '<App/>'
})
runtime-only
import Vue from 'vue'
import App from './App'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
render: h => h(App)
})
render: h => h(App)
render:function(h){
return h(App)
}
compiler编译解析template过程
template → ast → render → virtual dom → 真实dom
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EnFlPlAN-1612964660722)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-7.png)]
- 过程
runtime-compiler
- template会被解析 => ast(抽象语法树) => 然后编译成render函数 => 渲染成虚拟DOM(vdom)=> 真实dom(UI)
runtime-only
render函数
render:function(createElement){
//1.createElement('标签',{标签属性},[''])
return createElement('h2',
{class:'Box'},
['Hello World',createElement('button',['按钮'])])
//2.传入组件对象
//return createElement(cpn)
}
new Vue({
el: '#app',
// components: { App },
// template: '<App/>'
//1.createElement('标签',{标签属性},[''])
render(createElement){
return createElement('h2',
{class:'Box'},
['hello vue', createElement('button',['按钮'])])
}
})
- 并把config里面的inedx.js的
useEslint: true
改成false,即关掉eslint规范,打包项目npm run dev
,打开浏览器。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EIqUTzJU-1612964660724)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-8.png)]
在修改main.js
new Vue({
el: '#app',
// components: { App },
// template: '<App/>'
//1.createElement('标签',{标签属性},[''])
render(createElement){
// return createElement('h2',
// {class:'Box'},
// ['hello vue', createElement('button',['按钮'])])
//2.传入组件
return createElement(App)
}
再次打包,发现App组件被渲染了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MfBrQxt7-1612964660725)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-9.png)]
16.4 vue-cli3
vue-cli3起步
vue-cli3与2版本区别
- vue-cli3基于webpack4打造,vue-cli2是基于webpack3
- vue-cli3的设计原则是"0配置",移除了配置文件,build和config等
- vue-cli3提供
vue ui
的命令,提供了可视化配置 - 移除了static文件夹,新增了public文件夹,并将index.html移入了public文件夹
创建vue-cli3项目
vue create 04-vuecli3test
目录结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wbfFsrGP-1612964660726)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-10.png)]
使用npm run serve
运行服务器,打开浏览器输入http://localhost:8080/
打开src下的main.js
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#app')
Vue.config.productionTip = false
构建信息是否显示
如果vue实例有el选项,vue内部会自动给你执行$mount('#app')
,如果没有需要自己执行。
vue-cli3的配置
-
在创建vue-cli3项目的时候可以使用
vue ui
命令进入图形化界面创建项目,可以以可视化的方式创建项目,并配置项。 -
vue-cli3配置被隐藏起来了,可以在
node_modules
文件夹中找到@vue
模块,打开其中的cli-service
文件夹下的webpack.config.js
文件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TzYsr0yt-1612964660728)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\16-11.png)]
在项目根目录下新建一个vue.config.js
配置文件,必须为vue.config.js
,vue-cli3会自动扫描此文件,在此文件中修改配置文件。
//在module.exports中修改配置
module.exports = {
}
(十七)vue-router
17.1 路由简介
什么是路由?
-
路由就是通过互联的网络把信息用源地址传送到目的地的活动
-
路由提供了两种机制:路由和传送
- 路由是决定数据包从来源到目的地的路径
- 转送就是将数据转移
-
路由表
- 路由表本质就是一个映射表,决定了数据包的指向
17.2 前端/后端路由
回顾:https://www.bilibili.com/video/BV15741177Eh?p=101&spm_id_from=pageDriver
-
第一阶段 后端渲染(服务端渲染)
-
第二阶段 前后端分离(前端渲染)(ajax请求数据)
- 后端只负责提供数据
- 静态资源服务器(html+css+js)
- ajax发送网络请求后端服务器,服务器回传数据
js代码渲染dom
17.3 URL的hash和HTML5的history
URL的hash
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mJApYZIH-1612964660729)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6sGbMSML-1612964660730)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-2.gif)]
结论
-
测试发现url的地址栏改变了变成了http://localhost:8080/#/zty ,通过查看network发现只有favicon.ico资源重新请求了,这个是工程的logo图标,其他资源都未请求。可以通过改变hash改变url,此时页面是未刷新的。
-
vue-router其实用的就是这样的机制,改变URL地址,这个URL地址存在一份路由映射表里面,比如
/user
代表要请求用户页面,只要配置了这个路由表(路由关系),就可以前端跳转而不刷新页面,所有的数据请求都走ajax。
HTML5的history模式
pushState
同样的使用HTML5的history模式也是不会刷新页面的,history对象栈结构,先进后出,pushState类似压入栈中,back是回退。
hristory.pushState({}, '', '/foo')
history.back()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-74PAvEUH-1612964660731)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-3.png)]
replaceState
- replaceState模式与pushState模式区别在于replaceState模式浏览器没有返回(浏览器左上角没有返回按钮),只是替换,不是压入栈中。“无痕浏览”
history.replaceState({}, '', 'home')
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iAclIyoa-1612964660732)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-4.png)]
go
- go只能在pushState模式中使用,go是前进后退到哪个历史页面。
history.go(-1) // 回退一个页面
history.go(1) // 前进一个页面
history.forward() // 等价于go(1)
history.back() // 等价于go(-1)
17.4 vue-router的安装配置
-
使用
npm install vue-router --save
来安装vue-router插件模块
router文件夹中的index.js
/**
* 配置路由相关信息
* 1.先导入vue实例和vue-router实例
*/
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
// 2. 通过Vue.use(插件),安装插件
Vue.use(Router)
//3. 创建 router路由对象
const routes = [
//配置路由和组件之间的对应关系
{
path: '/',//url
name: 'HelloWorld',
component: HelloWorld //组件名
}
]
const router = new Router({
//配置路由和组件之间的应用关系
routes
})
//4.导出router实例
export default router
main.js中挂载router对象
/* eslint-disable no-new */
new Vue({
el: '#app',
router,//使用路由对象,简写对象增强写法
render: h => h(App)
})
17.5 vue-router的使用
创建路由组件
- 在components文件夹下创建2个组件。
Home组件
<template>
<div class="page-contianer">
<h2>这是首页</h2>
<p>我是首页的内容,123456.</p>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: 'Home'
}
</script>
<style scoped>
</style>
About组件
<template>
<div class="page-contianer">
<h2>这是关于页面</h2>
<p>我是关于页面的内容,about。</p>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: 'About'
}
</script>
<style scoped>
</style>
配置路由映射:组件和路径映射关系
- 在路由与组件对应关系配置在
routes
中。
修改index.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/components/Home'
// 2. 通过Vue.use(插件),安装插件
Vue.use(Router)
//3. 创建 router路由对象
const routes = [
//配置路由和组件之间的对应关系
{
path: '/home',//home 前端路由地址
name: 'Home',
component: Home //组件名
},
{
path: '/about',//about 前端路由地址
name: 'About',
component: () => import('@/components/About') //懒加载组件
}
]
const router = new Router({
//配置路由和组件之间的应用关系
routes
})
//4.导出router实例
export default router
使用路由:通过<router-link>
和<router-view>
在app.vue中使用<router-link>
和<router-view>
两个全局组件显示路由。
<router-link>
是全局组件,最终被渲染成a标签,但是<router-link>
只是标记路由指向类似一个a标签或者按钮一样,但是我们点击a标签要跳转页面或者要显示页面,所以就要用上<router-view>
。
<router-view>
是用来占位的,就是路由对应的组件展示的地方,该标签会根据当前的路径,动态渲染出不同的组件。路由切换的时候切换的是
<router-view>
挂载的组件,其他不会发生改变。
app.vue修改template
<template>
<div id="app">
<router-link to="/home">首页</router-link> |
<router-link to="/about">关于</router-link>
<router-view/>
</div>
</template>
使用npm run dev
启动项目,此时<router-view>
在<router-link>
下面,那渲染页面就在下面,此时未配置路由的默认值,所以第一次进入网页的时候<router-view>
占位的地方是没有内容的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NclowXsK-1612964660733)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-6.gif)]
路由的默认值和history模式
const routes = [
{
path: '',
redirect: '/home'//缺省时候重定向到/home
},
//配置路由和组件之间的对应关系
{
path: '/home',//home 前端路由地址
name: 'Home',
component: Home //组件名
},
{
path: '/about',//about 前端路由地址
name: 'About',
component: () => import('@/components/About') //懒加载组件
}
]
const router = new Router({
//配置路由和组件之间的应用关系
routes,
mode: 'history'//修改模式为history
})
此时发现浏览器地址栏的URL是没有#
的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p1TXtv1j-1612964660734)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-7.png)]
<router-link>
的其他属性
-
tag
属性:可以指定<router-link>
之后渲染成什么组件使用<router-link to='/home' tag='button'>
会被渲染成一个按钮,而不是a标签。 -
relapce
属性:在history模式下指定<router-link to='/home' tag='button' replace>
使用replaceState
而不是pushState,此时浏览器的返回按钮是不能使用的。 -
active-class
属性:当<router-link>
对应的路由匹配成功的时候,会自动给当前元素设置一个router-link-active
的class,设置active-class可以修改默认的名称。-
<router-link to='/home' tag='button' active-class='active'>
此时被选中的<router-link>
就会有active的class。 -
如果每个
<router-link>
都要加上active-class='active'
,那就在路由里面统一更改。
const router = new Router({ //配置路由和组件之间的应用关系 routes, mode: 'history',//修改模式为history linkActiveClass: 'active' })
<template> <div id="app"> <router-link to="/home" tag='button' replace active-class='active'>首页</router-link> | <router-link to="/about" active-class='active'>关于</router-link> <router-view/> </div> </template> <script> export default { name: 'App' } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } .active { color: red; } </style>
修改app.vue文件此时被选中的
<router-link>
就有了active属性,给active的class加上字体变红的css。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JPQE8rBC-1612964660735)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-8.png)]
通过代码修改路由跳转
$router属性
<template>
<div id="app">
<!-- <router-link to="/home" tag='button' replace active-class='active'>首页</router-link> |
<router-link to="/about" active-class='active'>关于</router-link> -->
<button @click="homeClick">首页</button>|
<button @click="aboutClick">关于</button>
<router-view/>
</div>
</template>
<script>
export default {
name: 'App',
methods: {
homeClick() {//通过代码的路径修改路由
this.$router.push('/home')//push 等价于pushState
// this.$router.replace('/home')//replace 等价于replaceState
console.log("homeClick")
},
aboutClick() {
this.$router.push('/about')
// this.$router.replace('/about')//replace 等价于replaceState
console.log("aboutClick")
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.active {
color: red;
}
</style>
- 修改app.vue,将
<router-link>
换成button
等任何组件,添加上点击事件,并写好点击事件响应方法,此时使用this.$router.push('/home')
,push方法 等价于pushState方法,replace 方法等价于replaceState方法。
17.5 渐入vue-router
vue-router的动态路由
- 一个页面的path路径可能是不确定的,例如可能有
/user/aaaa
或者/user/bbbb
,除了/user
之外,后面还跟上了用户ID/user/123
等。这种path和component的匹配关系,叫动态路由。
新建一个User组件
<template>
<div class="page-contianer">
<h2>这是用户界面</h2>
<p>这里是用户页面的内容。</p>
<p>用户ID是: {{ userId }}</p>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: 'User',
computed:{
userId() {
return this.$route.params.userId
}
}
}
</script>
<style scoped>
</style>
配置路由参数index.js
{
path: '/user/:userId',
name: 'User',
component: () => import('@/components/User') //懒加载组件
}
使用:userId
指定动态路由参数userId
。
<router-link :to="/user/ + userId">用户</router-link>
data (){
return {
userId: 'zty'
}
启动项目,点击用户。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vPX5nVoK-1612964660736)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-9.png)]
总结
$route
是代表处于激活状态的路由,这里指的也就是
{
path: '/user/:userId',
name: 'User',
component: () => import('@/components/User')
}
通过$route.params
获取 $route
所有的参数,$route.params.userId
,获取所有参数中的名字叫userId
的属性,此时可以在User组件中动态获取路由参数,也就可以在app.vue中动态设置路由中的userId
,其他属性请参考 $route
。
懒加载——vue-router的打包文件解析
问题:打包时候js太大,页面响应缓慢
- 懒加载
- 如果组件模块化了,当路由被访问的时候才开始加载被选中的组件
ES6
component: () => import('@/components/User')
AMD写法
const About = resolve => require(['../components/About.vue'], resolve)
使用npm run build
命令将之前创建的项目打包,打开dist文件夹,器目录结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Hb4qvn9-1612964660737)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-10.png)]
- app.xxx.js是我们自己编写的业务代码
- vendor.xxx.js是第三方框架,例如vue/vue-router/axios等
- mainfest.xxx.js是为了打包的代码做底层支持的,一般是webpack帮我们做一些事情
- 除了这三个还多了2个js,这2个js文件(0.5bxxx.js和1.e5xxx.js)分别是About和User组件,因为这2个组件是懒加载的所以被分开打包了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PSomBRUH-1612964660738)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-11.png)]
- 此时因为是懒加载,需要用到这个组件的时候才会加载,所以不会一次性请求所有js。
认识嵌套路由
- 平常在一个home页面中,我们可能需要
/home/news
和/home/message
访问一些内容,一个路由映射一个组件就像后端一个api对应一个controller的一个requestMapping一样,访问两个路由也会分别渲染这两个组件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IO7PjMYx-1612964660739)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-12.png)]
- 要实现嵌套路由:
-
创建对应的子组件,并且在路由映射(
router/index.js
)中配置对应的子路由。 -
在组件内部使用
<router-view>
标签来占位。
-
新建2个组件HomeNews和HomeMessage
<template>
<div class="page-contianer">
<ul>
<li v-for="(item, index) in list" :key="index">{{ item + index + 1 }}</li>
</ul>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: 'HomeNews',
data() {
return {
list: ['新闻', '新闻', '新闻', '新闻']
}
}
}
</script>
<style scoped></style>
<template>
<div class="page-contianer">
<ul>
<li v-for="(item, index) in list" :key="index">{{ item + index + 1 }}</li>
</ul>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: 'HomeMessage',
data() {
return {
list: ['消息', '消息', '消息', '消息']
}
}
}
</script>
<style scoped></style>
配置嵌套路由
{
path: '/home',//home 前端路由地址
name: 'Home',
component: Home, //组件名
children: [
{
path: '',
redirect: '/home/news'//缺省时候重定向到/home/news
},
{
path: 'news',//子嵌套路由 无须加/
name: 'News',
component: () => import('@/components/HomeNews') //懒加载组件
},
{
path: 'message',
name: 'Message',
component: () => import('@/components/HomeMessage') //懒加载组件
}
]
},
<template>
<div class="page-contianer">
<h2>这是首页</h2>
<p>我是首页的内容,123456.</p>
<router-link to="/home/news">新闻</router-link>|
<router-link to="/home/message">消息</router-link>
<router-view/>
</div>
</template>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IoUzr6cz-1612964660740)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-13.png)]
vue-router的参数传递
<template>
<div class="page-contianer">
<h2>这是档案界面</h2>
<p>这里是档案页面的内容。</p>
<p>档案的名字是: {{ profileInfo.name }}</p>
<p>档案的年龄是: {{ profileInfo.age }}</p>
<p>档案的身高是: {{ profileInfo.height }}</p>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: 'Profile',
computed: {
profileInfo() {
return this.$route.query.profileInfo
}
}
}
</script>
<style scoped></style>
{
path: '/profile',
name: 'Profile',
component: () => import('@/components/Profile')
}
<router-link :to="{ path: '/profile', query: { profileInfo } }">档案</router-link>
- 在app.vue中设置初始的对象
profileInfo
data (){
return {
userId: 'zty',
profileInfo: {
name: "zty",
age: 24,
height: 177
}
}
}
传递参数主要有两种类型:params和query
-
params的类型也就是动态路由形式
-
query的类型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SBn8k3JT-1612964660741)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-14.png)]
<button @click="userClick">用户</button>
<button @click="profileClick">档案</button>
userClick() {
this.$router.push('/user/' + this.userId)
console.log("userClick")
},
profileClick() {
let profileInfo = this.profileInfo
this.$router.push({
path: '/profile',
query: {
profileInfo
}
})
console.log("profileClick")
}
router和route的由来
- vue全局对象
this.$router
与main.js导入的router对象是一个对象,也就是我们router/index.js
导出的对象router。
new Vue({
el: '#app',
router, zz// 使用路由对象
render: h => h(App)
})
//4.导出router实例
export default router
-
查看
vue-router
源码,在我们项目中的router/index.js
中,vue 对于插件必须要使用Vue.use(Router)
,来安装插件,也就是执行vue-router的install.js
。
在vue-router的github源码中查看src结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CDHrmiGx-1612964660742)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-15.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IrirsR9s-1612964660744)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-16.png)]
发现
- install.js中有注册2个全局组件
RouterView
和RouterLink
,所以我们能使用<router-view>
和<router-link>
组件。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rOJKc3ZX-1612964660745)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-17.png)]
r o u t e r 和 router和 router和route是继承自vue的原型
-
怎么理解原型?学过Java 的都知道有父类和子类,子类也可以有自己的子类,但是他们都有一个处于最顶层的类Object(所有类的父类)。在Vue中就有那一个
Vue
类似Object,在java中在Object中定义的方法,所有的类都可以使用可以重写,类似的Vue.prototype
(Vue的原型)定义的属性方法,他的原型链上的对象都可以使用,而$router
和$route
都在Vue的原型链上。
import Vue from 'vue'
import App from './App'
import router from './router'
//在vue的原型上添加test方法
Vue.prototype.test = function () {
console.log("test")
}
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,//使用路由对象
render: h => h(App)
})
<template>
<div class="page-contianer">
<h2>这是用户界面</h2>
<p>这里是用户页面的内容。</p>
<p>用户ID是: {{ userId }}</p>
<button @click="btnClick">按钮</button>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: 'User',
computed:{
userId() {
return this.$route.params.userId
}
},
methods: {
btnClick() {
//所有组件都继承自vue的原型
console.log(this.$router)
console.log(this.$route)
//调用vue原型的test
this.test()
}
}
}
</script>
<style scoped>
</style>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ozGCeddj-1612964660746)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-18.png)]
- 继续来读install.js,install.js中一开始就将
Vue
这个类当参数传入了install方法中,并把Vue
赋值给_Vue
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bzusTKPA-1612964660747)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-19.png)]
- 继续读install.js发现以下代码
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
//Object.defineProperty用来定义属性
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
Vue.prototype.$router = {
get () { return this._routerRoot._router }
}
Vue.prototype.$router = {
get () { return this._routerRoot._router }
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iiVwUE99-1612964660748)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-20.png)]
这里的this.$options.router
就是我们main.js入口文件传入的参数router
,也就是router/index.js导出的router
对象。
new Vue({
el: '#app',
router,//使用路由对象
render: h => h(App)
})
17.6 vue-router其他
vue-router的导航守卫
问题:我们经常需要在路由跳转后,例如从用户页面跳转到首页,页面内容虽然可以自己定义,但是只有一个html文件,也只有一个title标签,我们需要改变标题。
created() {
//创建的时候修改title
document.title = '关于'
}
mounted() {
//数据被挂载到dom上的时候修改title
}
update() {
//页面刷新的时候修改
}
前置守卫——前置钩子(回调)
- 修改
router/index.js
/**
* 前置钩子:从from跳转到to
* from 来的路由
* to 要去的路由
*/
router.beforeEach((to, from, next) => {
document.title = to.matched[0].Meta.title //给目标路由的页面的title赋值
next()//必须调用,不调用不会跳转
})
router.beforeEach()称为前置钩子(前置守卫),顾名思义,跳转之前做一些处理。
- 当然每个路由配置上也要加上Meta属性,不然就取不到了,为什么要使用
matched[0]
,因为如果你是嵌套路由,有没有给子路由添加Meta(元数据:描述数据的数据)属性,就会显示undefined
,使用matched[0]
表示取到匹配的第一个就会找到父路由的Meta属性。
//配置路由和组件之间的对应关系
{
path: '/home',//home 前端路由地址
name: 'Home',
component: Home, //组件名
Meta: {
title: '首页'
},
children: [
{
path: '',
redirect: '/home/news'//缺省时候重定向到/home/news
},
{
path: 'news',//子嵌套路由 无须加/
name: 'News',
component: () => import('@/components/HomeNews') //懒加载组件
},
{
path: 'message',
name: 'Message',
component: () => import('@/components/HomeMessage') //懒加载组件
}
]
},
- 启动服务发现功能已经实现。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qoYQFozd-1612964660749)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-21.gif)]
导航守卫补充——后置钩子
- 前面说了前置守卫router.beforeEach(),相对的应该也存在后置守卫(后置钩子)。
/**
* 后置钩子
*/
router.afterEach((to, from) => {
console.log('后置钩子调用了----')
})
路由独享守卫,路由私有的
{
path: '/about',//about 前端路由地址
name: 'About',
component: () => import('@/components/About'),
beforeEnter: (to, from, next) => {
console.log('来自' + from.path + ',要去' + to.path)
next()
},
Meta: {
title: '关于'
}
},
beforeEnter
的参数与全局守卫一样,修改about
路由的参数,添加路由独享守卫,此时只有跳转到about
路由,才会打印日志。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MkqiT1RM-1612964660750)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-22.png)]
组件内的守卫,直接在组件中定义的属性
beforeRouteEnter
beforeRouteUpdate
(2.2 新增)beforeRouteLeave
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`
}
}
beforeRouteEnter
守卫 不能 访问 this
,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。
不过,你可以通过传一个回调给 next
来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
注意 beforeRouteEnter
是支持给 next
传递回调的唯一守卫。对于 beforeRouteUpdate
和 beforeRouteLeave
来说,this
已经可用了,所以不支持传递回调,因为没有必要了。
beforeRouteUpdate (to, from, next) {
// just use `this`
this.name = to.params.name
next()
}
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false)
来取消。
beforeRouteLeave (to, from , next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的
beforeEach
守卫。 - 在重用的组件里调用
beforeRouteUpdate
守卫 (2.2+)。 - 在路由配置里调用
beforeEnter
。 - 解析异步路由组件。
- 在被激活的组件里调用
beforeRouteEnter
。 - 调用全局的
beforeResolve
守卫 (2.5+)。 - 导航被确认。
- 调用全局的
afterEach
钩子。 - 触发 DOM 更新。
- 用创建好的实例调用
beforeRouteEnter
守卫中传给next
的回调函数。
17.7 keep-alive
<script type="text/ecmascript-6">
export default {
name: 'Home',
created() {
console.log('Home组件被创建了')
},
destoryed() {
console.log('Home组件被销毁了')
}
}
</script>
- 启动项目,某些时候可能有这样的需求,如图所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r0MX5LAC-1612964660751)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-23.gif)]
分析
-
在首页和关于组件之间路由跳转的时候,Home组件一直重复创建和销毁的过程,每次创建都是新的Home组件,但是我有这样的需求。当我点击首页消息页面,随后跳转到关于页面,又跳转到首页,此时我希望显示的是首页的消息页面而不是默认的新闻页面,此时就需要
keep-alive
来使组件保持状态,缓存起来,离开路由后,Home组件生命周期的**destroyed()
不会被调用**,Home组件不会被销毁。 -
keep-alive
是Vue内置的一个组件,可以使被包含的组件保留状态,或者避免重新渲染。 -
router-view
也是一个组件,如果用<keep-alive><router-vie/></keep-alive>
,将其包起来,所有路径匹配到的视图组件都会被缓存。
<keep-alive>
<router-view/>
</keep-alive>
- 再次启动项目,发现还是新闻页面?难道是
keep-alive
无效?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-30UwVxCo-1612964660753)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-24.gif)]
- 仔细看控制台发现,在跳转关于页面的时候Home组件并没有被销毁,说明
keep-alive
生效了。仔细查看路由配置发现,/home
被默认重定向到了/home/news
。所以在访问/home
的时候每次出来的都是新闻。
思路
-
将默认的重定向去掉,但是第一次进入首页,那新闻页面内容又不会显示了。
// { // path: '', // redirect: '/home/news'//缺省时候重定向到/home/news // },
-
为了第一次能使新闻页面内容显示,可以使用
created()
,将路由用代码的方式手动重定向,也就是push。created() { console.log('Home组件被创建了') this.$router.push('/home/news') },
-
由于
keep-alive
组件只创建一次,第一次进入Home组件的时候,新闻页面显示正常,当第二次跳转首页的时候,因为不会再调用created()
,所以新闻页面又不会显示了。 -
为了解决问题,在Home组件中引入
activated()
和deactivated()
两个函数,这2个函数与keep-alive
有关,不使用keep-alive
的这两个函数无效。 -
为了使第二次进入首页新闻页面可以生效,使用
activated()
在Home组件使活跃状态时候就重定向data() { return { path: '/home/news' } }, activated(){ console.log('调用actived') this.$router.push(this.path)//在活跃的时候将保存的路由给当前路由 }, deactivated(){ console.log('调用actived') console.log(this.$route.path) this.path = this.$route.path//变成不活跃状态,将最后的路由保存起来 }
-
发现还是不行,由于
deactivated()
调用的时候,此时路由已经跳转,所以不能在此方法中修改路由,因为修改的是to路由。 -
使用路由守卫(组件内守卫),
beforeRouteLeave (to, from , next)
在离开路由的时候将当前的路由赋值给path并保存起来。activated(){ console.log('调用actived') this.$router.push(this.path) }, // deactivated(){ // console.log('调用actived') // console.log(this.$route.path) // this.path = this.$route.path // }, beforeRouterLeave(to, from, next) { console.log(this.$route.path) this.path = this.$route.path next() }
-
此时问题完全解决了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rdQdcXKb-1612964660754)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-25.gif)]
keep-alive的属性
<keep-alive>
<router-view/>
</keep-alive>
<keep-alive exclude='Profile,User'>
<router-view/>
</keep-alive>
- 此时
Profile
和User
组件(这里组件需要有name属性,分别为Profile
和User
)就被排除在外,每次都会创建和销毁。相对应的也有include
属性,顾名思义就是包含,只有选中的才有keep-alive
。
<keep-alive include='Profile,User'>
<router-view/>
</keep-alive>
include
和exclude
都是使用字符串和正则表达式,使用字符串的时候,注意“,”之后之前都别打空格。
17.8 综合练习-实现Tab-Bar
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gILBhF0V-1612964660755)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-26.gif)]
实现Tab-Bar思路
- 下方单独的
Tab-Bar
组件如何封装? Tab-Bar
中显示的内容由外部决定- 定义插槽
- flex布局平分
Tab-Bar
- 自定义
Tab-Bar-Item
,可以传入图片和文字 - 传入高亮图片
Tab-Bar-Item
绑定路由数据- 安装路由:
npm install vue-router --save
- 在
router/index.js
配置路由信息,并创建对应的组件 main.js
中注册router
App.vue
中使用router-link
和router-view
- 安装路由:
- 点击item跳转到对应的路由,并且动态决定
isActive
- 监听
item
的点击,通过this.$router.replace()
替换路由路径 - 通过
this.$route.path.indexOf(this.link)!==-1
来判断是否使active
- 监听
- 动态计算active样式
- 封装新的计算属性:
this.isActive?{'color': 'red'}:{}
- 封装新的计算属性:
代码实现
- 使用
vue init webpack 02-vue-router-tabbar-v1
新建一个项目工程(使用vuecli2
)。
-
在文件夹assest下新建css/base.css,用于初始化css
base.css
body { padding: 0; margin: 0; }
<template> <div id="app"> <div id="tar-bar"> <div class="tar-bar-item">首页</div> <div class="tar-bar-item">分类</div> <div class="tar-bar-item">购物车</div> <div class="tar-bar-item">我的</div> </div> </div> </template> <script> export default { name: 'App' } </script> <style> /* style中引用使用@import */ @import url('./assets/css/base.css'); #tar-bar { display: flex; background-color: #f6f6f6; position: fixed; left: 0; right: 0; bottom: 0; Box-shadow: 0 -1px 1px rgba(100, 100, 100, .2); } .tar-bar-item { flex: auto; text-align: center; height: 49px; font-size: 20px; } </style>
使用npm run dev,查看网页效果
思考:如果每次都要复用tabbar,那每次都需要复制粘贴,应该要把tabbar抽离出来,vue就是组件化思想。
-
将tabbar抽离成组件
在components下新建tabbar文件夹,新建
TarBar.vue
和TabBarItem.vue
,TabBarItem
组件是在组件TarBar
中抽取出来的,可以传入图片和文字(比如首页),所有需要使用插槽<slot>
代替。TarBar.vue
TabBar弄一个slot插槽用于插入TabBarItem组件(可能插入多个).
> TabBarItem.vue
```vue
<template>
<div class="tab-bar-item">
<!-- item-icon表示图片插槽 item-text表示文字插槽,例如首页 -->
<slot name="item-icon"></slot>
<slot name="item-text"></slot>
</div>
</template>
<script type="text/ecmascript-6">
export default {
name: 'TabBarItem'
}
</script>
<style scoped>
.tab-bar-item {
flex: auto;
text-align: center;
height: 49px;
font-size: 14px;
}
.tab-bar-item img {
height: 24px;
width: 24px;
margin-top: 3px;
vertical-align: middle;
margin-bottom: 2px;
}
</style>
MainTabBar.vue
<template>
<div class="main-tab-bar">
<TabBar>
<TabBarItem path="/home" activeColor="blue">
<img slot="item-icon" src="~assets/img/tabbar/home.png" alt="" srcset="">
<template v-slot:item-text>
<div>首页</div>
</template>
</TabBarItem>
<TabBarItem path="/categories">
<template #item-icon>
<img src="~assets/img/tabbar/categories.png" alt="" srcset="">
</template>
<template #item-text>
<div>分类</div>
</template>
</TabBarItem>
<TabBarItem path="/shop">
<template #item-icon>
<img src="~assets/img/tabbar/shopcart.png" alt="" srcset="">
</template>
<template #item-text>
<div>购物车</div>
</template>
</TabBarItem>
<TabBarItem path="/me">
<template #item-icon>
<img src="~assets/img/tabbar/profile.png" alt="" srcset="">
</template>
<template #item-text>
<div>我的</div>
</template>
</TabBarItem>
</TabBar>
</div>
</template>
<script type="text/ecmascript-6">
import TabBar from "@/components/tabbar/TabBar"
import TabBarItem from "@/components/tabbar/TabBarItem"
export default {
name: "MainTabBar",
components: {
TabBar,
TabBarItem
}
}
</script>
<style scoped>
</style>
- 在MainTabBar组件中加入另外2个组件。
注意此处使用
~assets
和@/components
是使用了别名配置,[详情请看17.8.3别名配置](#17.8.3 别名配置)
- 最后在app.vue中导入MainTabBar组件。
<template>
<div id="app">
<MainTabBar></MainTabBar>
</div>
</template>
<script>
import MainTabBar from '@/components/MainTabBar'
export default {
name: 'App',
components: {
MainTabBar
}
}
</script>
<style>
/* style中引用使用@import */
@import url('./assets/css/base.css');
</style>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9LnDfBZN-1612964660756)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-28.png)]
-
- 这里需要用到路由的
active-class
。
思路:引用2张图片,一张是正常颜色一张是红色,使用
v-if
和v-else
来处理是否变色,在路由处于活跃状态的时候,变红色。TabBarItem.vue组件
<template> <div class="tab-bar-item" :style="activeStyle" @click="itemClick"> <!-- item-icon表示图片插槽 item-text表示文字插槽,例如首页 --> <div v-if="!isActive"> <slot name="item-icon"></slot> </div> <div v-else> <slot name="item-icon-active"></slot> </div> <div :class="{active:isActive}"> <slot name="item-text"></slot> </div> </div> </template> <script type="text/ecmascript-6"> export default { name: 'TabBarItem', props:{ path:String, activeColor:{ type:String, default:'red' } }, computed: { isActive(){ return this.$route.path.indexOf(this.path) !== -1 }, activeStyle(){ return this.isActive ? {color: this.activeColor} : {} } }, methods: { itemClick(){ this.$router.push(this.path) } } } </script> <style scoped> .tab-bar-item { flex: auto; text-align: center; height: 49px; font-size: 14px; } .tab-bar-item img { height: 24px; width: 24px; margin-top: 3px; vertical-align: middle; margin-bottom: 2px; } </style>
- 使用
props
获取传递的值,这里传递是激活颜色,默认是红色 - 设置计算属性
isActive
和activeStyle
,分别表示激活状态和激活的样式 - 定义
itemClick()
方法用于获取点击事件,点击后使用代码实现路由跳转,这里使用默认的hash模式 - 使用
v-if
和v-else
来进行条件判断
MainTabBar.vue组件
<TabBarItem path="/home"> <img slot="item-icon" src="~assets/img/tabbar/home.png" alt="" srcset=""> <img slot="item-icon-active" src="~assets/img/tabbar/home_active.png" alt="" srcset=""> <template v-slot:item-text> <div>首页</div> </template> </TabBarItem>
- 这里需要用到路由的
-
配置路由信息,参考之前的代码
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) const routes = [ { path: '/', redirect: '/home'//缺省时候重定向到/home }, { path: '/home', component: () => import ('../views/home/Home.vue') }, { path: '/categories', component: () => import ('../views/categories/Categories.vue') }, { path: '/shop', component: () => import ('../views/shop/Shop.vue') }, { path: '/profile', component: () => import ('../views/profile/Profile.vue') }, ] export default new Router({ routes, // linkActiveClass:"active" })
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PGDc493j-1612964660758)(C:/Users/86136/Desktop/repository/notes/13-Vue/images/17-29.png)]
-
修改main.js和App.vue
import Vue from 'vue' import App from './App' import router from './router' Vue.config.productionTip = false /* eslint-disable no-new */ new Vue({ el: '#app', router, render: h => h(App) })
<template> <div id="app"> <router-view></router-view> <MainTabBar></MainTabBar> </div> </template> <script> import MainTabBar from '@/components/MainTabBar' export default { name: 'App', components: { MainTabBar } } </script> <style> /* style中引用使用@import */ @import url('./assets/css/base.css'); </style>
别名配置
- 经常的我们向引入图片文件等资源的时候使用相对路径,诸如
../assets/xxx
这样的使用../
获取上一层,如果有多个上层就需要../../xxx
等等这样不利于维护代码。此时就需要一个能获取到指定目录的资源的就好了。
配置
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'@': resolve('src'),
'assets': resolve('src/assets'),
'components': resolve('src/components'),
'views': resolve('scr/views')
}
},
- 这里
@
指定目录是src
,例如@/components
表示src/components
目录,assets
表示src/assets
前缀,如果是assets/img
就表示src/assets/img
目录。
(十八)Promise
18.1 什么是Promise
-
简单说Promise是异步编程的一种解决方案。
-
Promise是ES6中的特性。
什么是异步操作?
如何处理异步事件?
$.ajax({
success:function(){
...
}
})
$.ajax({
success:function(){
$.ajax({
...
})
}
})
- 如果还需要再次网络请求,那么又要嵌套一层,这样的代码层次不分明很难读,也容易出问题。
18.2 Promise的基本使用
什么时候使用Promise?
- 如何解决异步请求冗余这样的问题,promise就是用于封装异步请求的。
Promise对象
new Promise((resolve, reject) => {})
- Promise对象的参数是一个函数
(resolve, reject) => {}
- 这个函数又有2个参数分别是
resolve
和reject
。这2个参数本身也是函数 - 后面还有回调函数
then(func)
的参数也是一个函数。
模拟定时器的异步事件
- 用定时器模拟网络请求,定时一秒为网络请求事件,用console.log()表示需要执行的代码。
//1.使用setTimeout模拟嵌套的三次网络请求
setTimeout(() => {//第一次请求
console.log("hello world")//第一次处理代码
setTimeout(() => {//第二次请求
console.log("hello vuejs")//第二次处理代码
setTimeout(() => {//第三次请求
console.log("hello java")//第三次处理代码
}, 1000)
}, 1000)
}, 1000)
-
一层套一层,看起是不是很绕。
-
使用promise来处理异步操作
//参数 -> 函数
// resolve和reject本身也是函数
//then()的参数也是一个函数
new Promise((resolve, reject) => {
setTimeout(() => {//第一次网络请求
resolve()
}, 1000)
}).then(() => {
console.log("hello world")//第一次处理代码
return new Promise((resolve, reject) => {
setTimeout(() => {//第二次网络请求
resolve()
}, 1000).then(() => {
console.log("hello vuejs")//第二次处理代码
return new Promise((resolve, reject) => {
setTimeout(() => {//第三次网络请求
resolve()
}, 1000)
}).then(() => {
console.log("hello java")//第三次处理代码
})
})
})
})
- 是不是觉得代码还要更复杂了?仔细看看第一个如果使用了多个就找不到对应关系了。相反第二个流程就很清楚,调用
resolve()
就能跳转到then()
方法就能执行处理代码,then()
回调的返回值又是一个Promise
对象。层次很明显,只要是then()
必然就是执行处理代码,如果还有嵌套必然就是返回一个Promise对象,这样调用就像java中的StringBuffer的append()方法一样,链式调用。
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success')
}, 1000).then(success => {
console.log(success)
})
})
- setTimeout()模拟的是网络请求,而then()执行的是网络请求后的代码,这就将网络请求和请求得到响应后的操作分离了,每个地方干自己的事情。在resolve中传参了,那么在then()方法中的参数就有这个参数,例如data。
网络请求中也会有失败情况?例如网络堵塞。
- 如何处理失败情况,此时就要用到reject()
new Promise((resolve, reject) => {
setTimeout(() => {
reject('error message')
}, 1000).catch(error => {
console.log(error)
})
})
- 此时
reject(error)
,catch()
方法捕获到reject()
中的error。
合起来
new Promise((resolve, reject) => {
setTimeout(() => {
// 成功的时候调用resolve()
// resolve('hello world')
// 失败的时候调用reject()
reject('error message')
}, 1000).then(success => {
console.log(success)
}).catch(error => {
console.log(error)
})
})
- 拿ajax来举例子:
new Promise((resolve, reject) => {
$.ajax({
success:function(){
// 成功的时候调用resolve()
// resolve('hello world')
// 失败的时候调用reject()
reject('error message')
}
}).then(success => {
console.log(success)
}).catch(error => {
console.log(error)
})
})
18.3 Promise的三种状态
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IfayZAO3-1612964660759)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\18-1.png)]
- pending:等待状态,比如正在进行的网络请求还未响应,或者定时器还没有到时间
- fulfilled:满足状态,当我们主动回调了resolve函数,就处于满足状态,并会回调then()
- reject:拒绝状态,当我们主动回调reject函数,就处于该状态,并且会回调catch()
18.4 Promise的链式调用
- 网络请求响应结果为 hello ,打印hello
- 处理: hello world ,打印hello world
- 处理: hello world,vuejs ,打印hello world,vuejs
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello')
}, 1000)
}).then(res => {
console.log(res)//打印hello
return new Promise(resolve => {
resolve(res + ' world')
}).then(res => {
console.log(res)//打印hello world
return new Promise(resolve => {
resolve(res + ',vuejs')
}).then(res => {
console.log(res)//打印hello world,vuejs
})
})
})
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello')
}, 1000)
}).then(res => {
console.log(res)//打印hello
return Promise.resolve(res + ' world')
}).then(res => {
console.log(res)//打印hello world
return Promise.resolve(res + ',vuejs')
}).then(res => {
console.log(res)//打印hello world,vuejs
})
- 还可以直接省略掉
Promise.resolve()
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello')
}, 1000)
}).then(res => {
console.log(res)//打印hello
return res + ' world'
}).then(res => {
console.log(res)//打印hello world
return res + ',vuejs'
}).then(res => {
console.log(res)//打印hello world,vuejs
})
- 如果中途发生异常,可以通过
catch()
捕获异常
new Promise((resolve, reject) => {
setTimeout(() => {
resolve('hello')
}, 1000)
}).then(res => {
console.log(res)//打印hello
return res + ' world'
}).then(res => {
console.log(res)
// return Promise.reject('error message')//发生异常
throw 'error message' //抛出异常
}).then(res => {
console.log(res)//打印hello world,vuejs
}).catch(error => {
console.log(error)
})
- 也可以通过
throw
抛出异常,类似java
throw 'error message' //抛出异常
18.5 Promise的all使用
ajax实现
$.ajax({
...//结果A
resultA = true
callback()
})
$.ajax({
...//结果B
resultB = true
callback()
})
//回调函数
function callback(){
if(resultA&&resultB){
...
}
}
Promise实现
Promise.all([
new Promise((resolve, resjct) => {
$.ajax({
url: 'url1',
success: function (data) {
resolve(data)
}
})
}),
new Promise((resolve, resjct) => {
$.ajax({
url: 'url2',
success: function (data) {
resolve(data)
}
})
}).then(results => {
console.log(results)
})
])
- 上面是伪代码,只是包装了ajax,ajaxA和ajaxB的结果都放在
resolve()
中,Promise将其放在results
中了,使用setTimeout
模拟。
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => {// 请求A
resolve('结果A')
}, 1000)
}),
new Promise((resolve, reject) => {
setTimeout(() => {// 请求B
resolve('结果B')
}, 1000)
})
]).then(results => {
console.log(results)
})
(十九)Vuex
19.1 什么是Vuex
Vuex就是一个状态管理模式,为什么叫模式?因为Vuex包含了一套对state(状态)的操作规范,集中存储管理应用的所有组件的状态。
状态管理
-
简单来说就是管理各个组件共享的数据,类似session
-
session可以存数据,存的过程就是管理,数据的每一次赋值就是当次状态。
-
Vuex在Vue实例顶层中。
-
Vuex也可以理解为java中的一个map,这个map是static(静态资源)的,每次获取map的值,都需要调用java的api,比如map.get(key)获取对应的值,也可以放入数据map.put(data),而且这个数据是所有类都可以调用的,只需要导入这个map就能使用里面的共享数据。
-
不了解java也没关系,你也可以理解成为百度百科就是一个容纳百科知识的容器,你要搜vuex,百科就会出现vuex的描述,这个搜索就是获取状态,如果你角色百科的vuex描述有误。你也可以发起修改操作,改成正确的vuex描述,这个修改操作就是修改vuex在百科里面的状态。当然你可以搜索修改vuex,别人也可以,因为这个状态是共享的。
-
简单来看实现这个功能好像我们自己封装一个对象就能实现,但是Vuex有一个特性就是响应式。如果我们自己封装对象想做到完美响应式比较麻烦,所有Vuex帮我们做了这个事情。
-
什么状态需要Vuex去管理?
Vuex简单模型
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g9Qzjn6g-1612964660760)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-01.png)]
- state,驱动应用的数据源,相当于组件的data对象;
- view,以声明方式将 state 映射到视图;
- actions,响应在 view 上的用户输入导致的状态变化。
这是一个单页面数据流向,比如想要修改用户昵称,当前用户昵称的状态是A,通过输入框输入了新的昵称B,调用ajax请求后端修改成功后将state改成B,然后视图响应用户昵称的变化从A到B。
但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:
- 多个视图依赖于同一状态。
- 来自不同视图的行为需要变更同一状态。
- 所以我们需要vuex的规范操作来管理状态。
19.2 Vuex基本使用
1.新建一个vuecli2工程
使用vue init webpack 01-vuex-vuecli2
新建一个vue项目。
vue init webpack 01-vuex-vuecli2
2.修改App.vue
并新建一个Hellovuex.vue
组件
<template>
<div id="app">
<h3>{{ message }}</h3>
<h3>{{ count }}</h3>
<button @click="count++">+</button>
<button @click="count--">-</button>
<hello-vuex :count="count"/>
</div>
</template>
<script>
import Hellovuex from './components/Hellovuex'
export default {
name: 'App',
data () {
return {
message: '',
count: 0
}
},
components: {
Hellovuex
}
}
</script>
Hellovuex.vue
<template>
<div class="hello">
<h2>{{ message }}</h2>
<h2>{{ count }}</h2>
</div>
</template>
<script>
export default {
name: 'Hellovuex',
data () {
return {
message: 'Hellovuex'
}
},
props: {
count: Number
}
}
</script>
此时我们使用了父子组件通信来完成子组件Hellovuex
获取父组件的count
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aarSlnnC-1612964660761)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-02.gif)]
- 如果不是父子组件如何通信,此时就需要vuex了,还是这这2个组件,现在不使用父子通信,直接使用vuex。
3.使用vuex管理状态
-
使用
npm install vuex --save
安装Vuex -
安装插件Vue.use(Vuex),在src下新建一个store文件夹,新建一个index.js
import Vue from 'vue' import Vuex from 'vuex' // 1.安装插件 Vue.use(Vuex) // 2.创建对象 const store = new Vuex.Store({ state: { // 状态集合 count: 0 // 具体的状态数据 } }) // 3.导出store对象 export default store
-
修改
App.vue
和Hellovuex.vue
,直接使用$store.state.count
获取count值<template> <div id="app"> <h3>{{ message }}</h3> <h3>{{ $store.state.count }}</h3> <button @click="$store.state.count++">+</button> <button @click="$store.state.count--">-</button> <hello-vuex /> </div> </template> <script> import Hellovuex from './components/Hellovuex' export default { name: 'App', data () { return { message: '' } }, components: { Hellovuex } } </script>
<template> <div class="hello"> <h2>{{ message }}</h2> <h2>这是Hellovuex的count:{{ $store.state.count }}</h2> </div> </template> <script> export default { name: 'Hellovuex', data () { return { message: 'Hellovuex' } } } </script>
-
一般不会直接使用
$store.state.count
获取vuex中的状态,也不是直接使用$store.state.count++
来操作vuex中的状态。
19.3 Vuex的流程
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J1nte1rh-1612964660762)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-03.png)]
-
Vue Components是vue组件
-
Mutations :更改 Vuex 的 store 中的状态的唯一方法是提交 mutation
-
State 是vuex中状态的集合
-
Actions与Mutations 类似,经常与后端交互,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作,mutation不能进行异步操作
修改index.js使用mutation
// 2.创建对象
const store = new Vuex.Store({
state: { // 状态集合
count: 0 // 具体的状态数据
},
mutations: { // 操作修改state(状态)
increment (state) { // 增加
state.count++
},
decrement (state) { // 减少
state.count--
}
}
})
修改App.vue提交mutation
<template>
<div id="app">
<h3>{{ message }}</h3>
<h3>{{ $store.state.count }}</h3>
<button @click="add">+</button>
<button @click="sub">-</button>
<hello-vuex />
</div>
</template>
<script>
import Hellovuex from './components/Hellovuex'
export default {
name: 'App',
data () {
return {
message: ''
}
},
components: {
Hellovuex
},
methods: {
add () {
this.$store.commit('increment')
},
sub () {
this.$store.commit('decrement')
}
}
}
</script>
测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPBMgZil-1612964660763)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-04.gif)]
- 测试发现没有问题与直接使用
$store.state.count++
效果一致,通过提交mutation修改了状态state,在vue-devtools
中也能跟踪state变化以及提交的mutation。
19.4 Vuex的核心概念
- State —— “状态”
- Getters
- Mutation —— “改变”
- Action —— “行为”
- Moudule —— “模块”
State(单一状态树)
-
Vuex 使用单一状态树——是的,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
-
简单说就是把数据所有有关的数据封装到一个对象中,这个对象就是store实例,无论是数据的状态(state),以及对数据的操作(mutation、action)等都在store实例中,便于管理维护操作。
-
state的操作在19.2.Vuex的基本使用已经了解,直接通过
this.$store.state
获取state对象。
Getters
const store = new Vuex.Store({
state: { // 状态集合
students: [
{id: 110, name: 'zzz', age: '18'},
{id: 111, name: 'ttt', age: '20'},
{id: 112, name: 'yyy', age: '22'},
{id: 113, name: 'zty', age: '25'}
]
}
})
computed: {
stuCount() {
return this.$store.state.students.filter(student => student.age > 20).length
}
}
在store实例中定义getters
getters: {
getStudentCounts: state => {
return state.students.filter(s => s.age > 20).length
}
}
computed: {
stuCount() {
return this.$store.getters.getStudents
}
}
定义getters
getters: {
getStuById: state => id => {
return state.students.find(s => s.id === id)
}
}
通过方法访问
computed: {
stuById() {
return this.$store.getters.getStuById(110)
}
}
- 传入学生ID为110,输出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WREMauV7-1612964660764)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-05.png)]
Mutation(状态更新)
-
Vuex的store状态更新的唯一方式:提交Mutation
-
Mutation主要包括两个部分:
- 字符串的事件类型(type)
-
mutations规范
- mutations中的每个方法尽可能完成的事件比较单一
-
Mutation的定义方式:
mutation: { increment (state) { state.count++ } } // increment 为事件类型
-
通过Mutation更新
mutation () { this.$store.commit('increment') }
mutation接受单个参数
<button @click="addCount(5)">+5</button>
<button @click="addCount(10)">+10</button>
- 新增addCount方法:
addCount (count) {
this.$store.commit('addCount', count) // 将count传入
}
- 新增一个mutation
addCount (state, count) { // 第二个参数是count,第一个始终是state
state.count+=count
}
- 测试:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NGPbEfqa-1612964660765)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-06.gif)]
mutation接受多个参数
-
新增按钮
<button @click="addStu()">新增一个指定的学生</button>
-
新增mutation
addStu (state, stu) { state.students.push(stu) // 向数组中添加指定的stu console.log(state.students.find(s => s.id === stu.id)) // 输出打印查看state中是否有新增stu }
-
新增方法
addStu () { const stu = { id: 114, name: 'ytz', age: '35' } this.$store.commit('addStu', stu) }
-
测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sh97oPvO-1612964660765)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-07.png)]
mutation的提交风格
-
普通提交风格
this.$store.commot('increment', count)
此时count传过去的就是count=10
-
特殊的提交封装
this.$store.commit({ type: 'addCount', count //count:count })
addCount (state, payload) { // 此时传入的就不是一个count值了,而是一个对象 state.count += payload.count }
- 此时count传过去是一个对象payload(载荷)
{ type: 'incrementCount', count: 10 }
Vuex的响应式原理
-
Vuex的store的state是响应式的,当state中的数据发生改变时,Vue组件会自动更新。
-
响应式需要遵循规则
-
user: { name: 'zhangsan', sex: '男' }
-
<h3>{{ $store.state.user }}</h3> <button @click="updateInfo()">修改信息</button>
-
updateInfo () { this.$store.commit('updateInfo', 12) }
-
在mutation中添加updateInfo()
updateInfo (state, age) { state.user.age = age }
-
点击修改信息按钮,发现state的值变化了,但是页面没有响应变化
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bt1q2cCh-1612964660766)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-08.png)]
-
updateInfo (state, age) { // state.user.age = age Vue.set(state.user, 'age', 12) }
-
再次点击修改信息按钮,发现变响应式了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gfUi8kA7-1612964660768)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-09.png)]
总结
-
state未初始化属性(
age
)- 使用直接赋值的方式不能响应式
- 需要使用
Vue.set(state.user, 'age', 12)
-
state已经初始化了,可以使用直接赋值方式
-
// 该方法没有响应式,需要使用vue.delete // delete state.user.age Vue.delete(state.user, age)// 响应式删除age
mutation的类型常量
- 定义一个
mutation-type.js
的常量
export const UPDATEINFO = 'updateInfo'
import { UPDATEINFO } from './store/mutation-type'
[UPDATEINFO] () {
this.$store.commit(UPDATEINFO, 12)
}
import { UPDATEINFO } from './mutation-type'
[UPDATEINFO] (state, age) {
// state.user.age = age
Vue.set(state.user, 'age', 12)
// 该方法没有响应式,需要使用vue.delete
// delete state.user.age
// Vue.delete(state.user, age)// 响应式删除age
}
- 这样保证了所有的方法都定义在
mutation-types.js
中,不会出问题。
Actions
- 使用mutation操作更新state的时候,使用异步修改数据。
- 有逻辑判断时使用
[UPDATEINFO] (state, age) {
Vue.set(state.user, 'age', 12)
setTimeout(() => { // 延时模拟异步网络请求
state.user.name = 'lisi'
}, 1000)
}
- 点击修改信息按钮
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vGtjrmqT-1612964660769)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-10.png)]
- 发现页面的数据改变了,但是vue-devtools工具中并未跟踪到改变。所以我们不要在mutation中进行异步操作。
定义
- Action 类似于 mutation,不同在于:
- Action 提交的是 mutation,而不是直接变更状态。
- Action 可以包含任意异步操作。
- 新增一个mutation
updateName (state, name) {
state.user.name = name
}
- 新增一个actions
actions: {
// context:上下文
aUpdateInfo (context, name) {
setTimeout(() => {
context.commit(UPDATEINFO, 12)
}, 1000)
}
}
<h3>异步修改的信息:{{ $store.state.user }}</h3>
<button @click="aUpdateInfo()">异步修改信息</button>
- 给按钮新增方法
aUpdateInfo () {
this.$store.dispatch('aUpdateInfo', 'lisi')
}
- 点击
异步修改信息
按钮测试
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RSbDdi8t-1612964660770)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-11.png)]
- 在点击按钮之后,信息修改了,dev-tools也能跟踪到state的变化。通过
$store.dispacth()
方法来调用actions,发送异步请求,在actions中需要提交mutation来修改state。
-
actions回调,在异步操作后,成功或者失败都应该会有回调,
$store.dispacth()
返回一个Promise对象,修改actions,返回一个Promise对象,成功调用resolve(msg)
,将成功的msg
传入(不理解的请看一下18章的Promise对象详解)。actions: { // context:上下文 aUpdateInfo (context, name) { let msg = '响应成功' return new Promise((resolve, reject) => { setTimeout(() => { context.commit(UPDATEINFO, 12) resolve(msg) }, 1000) }) } }
-
修改
aUpdateInfo()
方法,获取回调参数msg
,此时的response
就是actions中回调的msg
,也可以支持失败的回调,只要actions中使用了reject,在aUpdateInfo()
方法中catch回调结果就能获取resjct对象回传结果。aUpdateInfo () { this.$store.dispatch('aUpdateInfo', 'lisi').then(response => { console.log(response) }) }
-
再次点击
异步修改信息
,打印结果信息[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VqqxpQaK-1612964660771)(C:\Users\86136\Desktop\repository\notes\13-Vue\images\19-12.png)]
Actions 支持同样的载荷方式(payload)和对象方式进行分发
// 以载荷形式分发
store.dispatch('aUpdateInfo', {
name: 'lisi'
})
// 以对象形式分发
store.dispatch({
type: 'aUpdateInfo',
name: 'lisi'
})
moudules(模块)
-
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
-
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
-
比如这样
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状
模块的局部状态
-
模块内部的mutation 和 getter,接收的第一个参数是模块的局部状态对象。
-
模块内部的 action,局部状态是
context.state
,根节点状态则为context.rootState
。 -
对于模块内部的 getter,第三个参数是根节点状态。
const moduleA = { state: () => ({ count: 0 }), mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, actions: { incrementIfOddOnRootSum (context) { if ((context.state.count + context.rootState.count) % 2 === 1) { context.commit('increment') } } }, getters: { doubleCount (state, getters, rootState) { console.log(rootState.count) // 获取的是根状态的count return state.count * 2 } } }
注意actions的context
actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((context.state.count + context.rootState.count) % 2 === 1) { context.commit('increment') } } }
{ state, commit, rootState }
对应context
对象中的属性,使用ES6的对象解构。
项目结构
- Vuex 并不限制你的代码结构。但是,它规定了一些需要遵守的规则:
- 应用层级的状态应该集中到单个 store 对象中。
- 提交 mutation 是更改状态的唯一方法,并且这个过程是同步的。
- 异步逻辑都应该封装到 action 里面。
-
只要你遵守以上规则,如何组织代码随你便。如果你的 store 文件太大,只需将 action、mutation 和 getter 分割到单独的文件。
-
对于大型应用,我们会希望把 Vuex 相关代码分割到模块中。下面是项目结构示例:
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
(二十)Axios
20.1 为什么选择Axios
补充
20.2 axios框架基本使用
多种请求方式
- axios( config )
- axios.request(config)
- axios.get(url [,config])
- axios.delete(url [,config])
- axios.head(url [,config])
- axios.post(url [,data[, config]])
- axios.put(url [,data[, config]])
- axios.patch(url [,data[, config]])
axios()
// 默认使用GET方法
axios({
url: 'http://123.207.32.32:8000/home/multidata',
method: 'GET'
}).then(res => {
console.log(res)
})
get请求——参数拼接
axios({
url: 'http://123.207.32.32:8000/home/multidata',
method: 'GET',
params: {
type: 'pop',
page: 1
}
}).then(res => {
console.log(res)
})
20.3 axios发送并发请求
ajax: Promise.all ([])
axios: axios.all ([])
格式
axios.all([axios(),axios()]).then(res => {})
axios.all([
axios({
url: 'http://123.207.32.32:8000/home/multidata'
}),
axios({
url: 'http://123.207.32.32:8000/home/data'
})
])
.then(res => {
console.log(res)
})
分割结果
axios.all([
axios.get('http://123.207.32.32:8000/home/multidata'),
axios.get('http://123.207.32.32:8000/home/data',{
params: { type: 'shell', page: 1 }})])
.then(axios.spread((res1, res2) => {
console.log(res1)
console.log(res2)
}))
20.4 axios的配置信息
实例
axios.defaults.baseURL = 'http://123.207.32.32:8000'
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.timeout = 1000
// 全局配置
axios.defaults.baseURL = 'http://123.207.32.32:8000'
// 并发请求
axios.all([
axios({
url: '/home/multidata'
}),
axios({
url: '/home/data'
})
])
.then(res => {
console.log(res)
})
常见配置选项
-
请求地址
- url:‘/user’
-
请求类型
- method:‘get’
-
请求路径
- baseURL:‘http://www.xxx.com/api’
-
请求前的数据处理
- transformRequest: [function(data){}]
-
URL查询对象
- params: { id:12 }
20.5 axios的实例和模块封装
创建axios实例
const instance1 = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 使用
instance1({
url: '/home/multidata'
}).then(res => {
console.log(res)
})
instance1({
url: 'home/data',
params: {
type: 'pop',
page: 1
}
}).then(res => {
console.log(res)
})
每个实例可以看成不同的服务器。
封装成单独文件
分析封装的优点
- 第一次封装
export function request (config, success, failure) {
// 1.创建axios实例
const instance = axios.creat({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
instance(config)
.then(res => {
success(res)
})
.catch(err => {
failure(err)
})
}
// 其他文件调用
import { request } from './network/request.js'
request({
url: '/home/data'
}, res => {
console.log(res)
}, err => {
console.log(err)
})
- 第二次封装
export function request (config) {
// 1.创建axios实例
const instance = axios.creat({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
instance(config.baseConfig)
.then(res => {
config.success(res)
})
.catch(err => {
config.failure(err)
})
}
// 调用
request({
baseConfig: {
},
success: function (res) {
},
failure: function (ree) {
console.log(err)
}
})
- 方案三
export function request (config) {
return new Promise((resolve, reject) => {
// 1.创建axios实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 2.发送请求
instance(config)
.then(res => {
resolve(res)
})
.catch(err => {
reject(err)
})
})
}
// 调用
request({
url: '/home/data'
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
- 最终方案
export function request (config) {
// 1.创建axios实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 2.发送请求
return instance(config)
}
// 调用
request({
url: '/home/data'
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
20.6 axios拦截器使用
-
作用
- 用于在发送每次请求或者得到响应后,进行对应处理
-
使用
// 3.拦截器
instance.interceptors.request.use(config => {
console.log(config)
// (1)处理数据、信息转化
// (2)比如每次发送网络请求时,希望在界面显示一个请求图标
// (3)某些网络请求必须携带一些特殊信息(token)
return config // 拦截后需要还回去
}, err => {
console.log(err)
})
instance.interceptors.response.use(res => {
console.log(res)
// 取出数据
}, err => {
console.log(err)
})
(二十一)Better Scroll
21.1 基本使用
安装
<script src="https://unpkg.com/@better-scroll/core@latest/dist/core.js"></script>
使用
// js代码
const ct = document.querySelector('.content')
const bscroll = new BScroll(ct, {
})
<div class="content">
<ul>
<li></li>
...
</ul>
</div>
21.2 参数
const ct = document.querySelector('.content')
const bscroll = new BScroll(ct, {
probeType: 2
})
probeType
实时侦测滚动位置,默认值0
- 值
- 0/1都不监听
- 2:手指滚动过程中侦测,离开后惯性滚动不侦测
- 3:只要滚动都侦测
pullUpLoad
上拉加载功能
- 值
- Boolean
- object
useTransition
解决滑动时字体变模糊问题
- 值
- true
21.3 BScroll可滚动区域问题
-
BScroll在决定有多少区域可以滚动时,是根据scrollerHeight属性决定
-
事件总线 $bus
-
作用:管理事件
-
区别Vuex管理状态
-
使用
-
Vue.prototype.$bus = new Vue()
-
goodslistitem发射事件
this.$bus.$emit('itemImageLoad')
-
home监听该事件
this.$bus.on('itemImageLoad', () => {})
-
-
防抖函数
防抖:在规定时间内执行多次事件只执行最后一次事件
商城项目1.0
@L_313_404@Project setup
npm install
Compies and hot-reloads for development
npm run serve
Compiles and minifies for production
npm run build
目录结构
┌- ..
├- public/
├- favicon.ico // ico
├- index.html
├- src/
├- assets/ // 项目资源
├- css/
- normalize.css // 第三方统一样式
- base.css // 自定义统一样式
├- img/
├- components // 公共组件
├- common/ // 纯公共组件(扩展其他项目)
├- navbar/ // 公共导航条组件
- NavBar.vue
├- tabbar/ // 底部导航栏组件
- TabBar.vue
- TabBarItem.vue
├- swiper/ // 轮播图组件
- index.js
- Swiper.vue
- SwiperItem.vue
├- content/ // 当前项目公共组件
├- mainTabBar// 底部导航栏(业务版)
- MainTabBar.vue
├- common/ // 公共js文件
├- network/ // 网络请求文件
├- home.js // 主页网络请求文件封装
├- request.js // 全局网络请求封装
├- router/ // 路由文件
├- index.js
├- store/ // 状态管理文件
├- views/ // 页面视图
├- category/
├- Category.vue // 分类页面主组件
├- home/ // 主页视图
├- childComps// 主页功能小组件
- HomeSwiper.vue
├- Home.vue // home主组件
├- profile/
├- Profile.vue// 个人页面主组件
├- shotcart/
├- Shotcart.vue// 购物车主组件
├- App.vue // root组件
├- main.js // 启动文件
├- vue.config.js // 自定义配置文件
├- .gitignore // git提交忽略文件
├- babel.config.js
├- package.json // 版本控制
├- package-lock.json // 版本锁
├- LICENSE // 许可
├- README.md // what u see
└-
步骤
- 导入base.css样式
- normalize.css
- 配置文件路径别名
- 配置代码风格文件
- .editorconfig
- 导航模块划分
- tabbar -> 路由映射关系
- 首页开发
- navbar封装
- 网络数据的请求
- 轮播图
- 推荐信息
附录
[1] 首页保存商品的数据结构设计
goods(流行/新款/精选) goods: { 'pop': {page: 5, list: [150]}, 'news': {page: 1, list: [90]}, 'sell': {page: 1, list: [30]} }
附录
tabControl的吸顶效果
获取到tabControl的offsetTop
- 必须知道滚动到多少时,开始有吸顶效果,这个时候就需要获取tabControl的offsetTop
- 但是,如果直接在mounted中获取tabControl的offsetTop,那么值不正确
- 如何获取正确的值?
监听滚动,动态改变TabControl的样式
- 问题一:下面的商品内容会突然上移
- 问题二:tabControl虽然设置了fixed,都是也随着BS一起滚出去了
- 其他方案
让Home保持原来状态
让Home不要随意销毁掉
- keep-alive
让home中保持原来位置
FastClick点击事件优化
图片懒加载 vue-lazyload库
基本使用
- 安装
npm i vue-lazyload
-
在main.js导入
-
import VueLazyLoad from 'vue-lazyload'
-
Vue.use(VueLazyLoad)
-
-
修改img
- :scr→ v-lazy
参数
- 占位图:loading
- …
CSS单位转化插件 px2vw
部署
- windows-Nginx
# 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
# (二十)Axios
## 20.1 为什么选择Axios
- 可以在浏览器发送XMH请求
- 在nodejs发送http请求
- 支持Promise API
- 拦截请求和响应
- 转换请求和响应数据
> 补充
>
> axios:ajax I/O system 理解
## 20.2 axios框架基本使用
### 多种请求方式
- axios( config )
- axios.request(config)
- axios.get(url [,config])
- axios.delete(url [,config])
- axios.head(url [,config])
- axios.post(url [,data[, config]])
- axios.put(url [,data[, config]])
- axios.patch(url [,data[, config]])
### axios()
```javascript
// 默认使用GET方法
axios({
url: 'http://123.207.32.32:8000/home/multidata',
method: 'GET'
}).then(res => {
console.log(res)
})
get请求——参数拼接
axios({
url: 'http://123.207.32.32:8000/home/multidata',
method: 'GET',
params: {
type: 'pop',
page: 1
}
}).then(res => {
console.log(res)
})
20.3 axios发送并发请求
ajax: Promise.all ([])
axios: axios.all ([])
格式
axios.all([axios(),axios()]).then(res => {})
axios.all([
axios({
url: 'http://123.207.32.32:8000/home/multidata'
}),
axios({
url: 'http://123.207.32.32:8000/home/data'
})
])
.then(res => {
console.log(res)
})
分割结果
axios.all([
axios.get('http://123.207.32.32:8000/home/multidata'),
axios.get('http://123.207.32.32:8000/home/data',{
params: { type: 'shell', page: 1 }})])
.then(axios.spread((res1, res2) => {
console.log(res1)
console.log(res2)
}))
20.4 axios的配置信息
实例
axios.defaults.baseURL = 'http://123.207.32.32:8000'
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.timeout = 1000
// 全局配置
axios.defaults.baseURL = 'http://123.207.32.32:8000'
// 并发请求
axios.all([
axios({
url: '/home/multidata'
}),
axios({
url: '/home/data'
})
])
.then(res => {
console.log(res)
})
常见配置选项
-
请求地址
- url:‘/user’
-
请求类型
- method:‘get’
-
请求路径
- baseURL:‘http://www.xxx.com/api’
-
请求前的数据处理
- transformRequest: [function(data){}]
-
URL查询对象
- params: { id:12 }
20.5 axios的实例和模块封装
创建axios实例
const instance1 = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 使用
instance1({
url: '/home/multidata'
}).then(res => {
console.log(res)
})
instance1({
url: 'home/data',
params: {
type: 'pop',
page: 1
}
}).then(res => {
console.log(res)
})
每个实例可以看成不同的服务器。
封装成单独文件
分析封装的优点
- 第一次封装
export function request (config, success, failure) {
// 1.创建axios实例
const instance = axios.creat({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
instance(config)
.then(res => {
success(res)
})
.catch(err => {
failure(err)
})
}
// 其他文件调用
import { request } from './network/request.js'
request({
url: '/home/data'
}, res => {
console.log(res)
}, err => {
console.log(err)
})
- 第二次封装
export function request (config) {
// 1.创建axios实例
const instance = axios.creat({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
instance(config.baseConfig)
.then(res => {
config.success(res)
})
.catch(err => {
config.failure(err)
})
}
// 调用
request({
baseConfig: {
},
success: function (res) {
},
failure: function (ree) {
console.log(err)
}
})
- 方案三
export function request (config) {
return new Promise((resolve, reject) => {
// 1.创建axios实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 2.发送请求
instance(config)
.then(res => {
resolve(res)
})
.catch(err => {
reject(err)
})
})
}
// 调用
request({
url: '/home/data'
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
- 最终方案
export function request (config) {
// 1.创建axios实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 2.发送请求
return instance(config)
}
// 调用
request({
url: '/home/data'
}).then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
20.6 axios拦截器使用
-
作用
- 用于在发送每次请求或者得到响应后,进行对应处理
-
使用
// 3.拦截器
instance.interceptors.request.use(config => {
console.log(config)
// (1)处理数据、信息转化
// (2)比如每次发送网络请求时,希望在界面显示一个请求图标
// (3)某些网络请求必须携带一些特殊信息(token)
return config // 拦截后需要还回去
}, err => {
console.log(err)
})
instance.interceptors.response.use(res => {
console.log(res)
// 取出数据
}, err => {
console.log(err)
})
(二十一)Better Scroll
21.1 基本使用
安装
<script src="https://unpkg.com/@better-scroll/core@latest/dist/core.js"></script>
使用
// js代码
const ct = document.querySelector('.content')
const bscroll = new BScroll(ct, {
})
<div class="content">
<ul>
<li></li>
...
</ul>
</div>
21.2 参数
const ct = document.querySelector('.content')
const bscroll = new BScroll(ct, {
probeType: 2
})
probeType
实时侦测滚动位置,默认值0
- 值
- 0/1都不监听
- 2:手指滚动过程中侦测,离开后惯性滚动不侦测
- 3:只要滚动都侦测
pullUpLoad
上拉加载功能
- 值
- Boolean
- object
useTransition
解决滑动时字体变模糊问题
- 值
- true
21.3 BScroll可滚动区域问题
-
BScroll在决定有多少区域可以滚动时,是根据scrollerHeight属性决定
-
事件总线 $bus
-
作用:管理事件
-
区别Vuex管理状态
-
使用
-
Vue.prototype.$bus = new Vue()
-
goodslistitem发射事件
this.$bus.$emit('itemImageLoad')
-
home监听该事件
this.$bus.on('itemImageLoad', () => {})
-
-
防抖函数
防抖:在规定时间内执行多次事件只执行最后一次事件
商城项目1.0
Project setup
npm install
Compies and hot-reloads for development
npm run serve
Compiles and minifies for production
npm run build
目录结构
┌- ..
├- public/
├- favicon.ico // ico
├- index.html
├- src/
├- assets/ // 项目资源
├- css/
- normalize.css // 第三方统一样式
- base.css // 自定义统一样式
├- img/
├- components // 公共组件
├- common/ // 纯公共组件(扩展其他项目)
├- navbar/ // 公共导航条组件
- NavBar.vue
├- tabbar/ // 底部导航栏组件
- TabBar.vue
- TabBarItem.vue
├- swiper/ // 轮播图组件
- index.js
- Swiper.vue
- SwiperItem.vue
├- content/ // 当前项目公共组件
├- mainTabBar// 底部导航栏(业务版)
- MainTabBar.vue
├- common/ // 公共js文件
├- network/ // 网络请求文件
├- home.js // 主页网络请求文件封装
├- request.js // 全局网络请求封装
├- router/ // 路由文件
├- index.js
├- store/ // 状态管理文件
├- views/ // 页面视图
├- category/
├- Category.vue // 分类页面主组件
├- home/ // 主页视图
├- childComps// 主页功能小组件
- HomeSwiper.vue
├- Home.vue // home主组件
├- profile/
├- Profile.vue// 个人页面主组件
├- shotcart/
├- Shotcart.vue// 购物车主组件
├- App.vue // root组件
├- main.js // 启动文件
├- vue.config.js // 自定义配置文件
├- .gitignore // git提交忽略文件
├- babel.config.js
├- package.json // 版本控制
├- package-lock.json // 版本锁
├- LICENSE // 许可
├- README.md // what u see
└-
步骤
- 导入base.css样式
- normalize.css
- 配置文件路径别名
- 配置代码风格文件
- .editorconfig
- 导航模块划分
- tabbar -> 路由映射关系
- 首页开发
- navbar封装
- 网络数据的请求
- 轮播图
- 推荐信息
附录
[1] 首页保存商品的数据结构设计
goods(流行/新款/精选) goods: { 'pop': {page: 5, list: [150]}, 'news': {page: 1, list: [90]}, 'sell': {page: 1, list: [30]} }
附录
tabControl的吸顶效果
获取到tabControl的offsetTop
- 必须知道滚动到多少时,开始有吸顶效果,这个时候就需要获取tabControl的offsetTop
- 但是,如果直接在mounted中获取tabControl的offsetTop,那么值不正确
- 如何获取正确的值?
监听滚动,动态改变TabControl的样式
- 问题一:下面的商品内容会突然上移
- 问题二:tabControl虽然设置了fixed,都是也随着BS一起滚出去了
- 其他方案
让Home保持原来状态
让Home不要随意销毁掉
- keep-alive
让home中保持原来位置
FastClick点击事件优化
图片懒加载 vue-lazyload库
基本使用
- 安装
npm i vue-lazyload
-
在main.js导入
-
import VueLazyLoad from 'vue-lazyload'
-
Vue.use(VueLazyLoad)
-
-
修改img
- :scr→ v-lazy
参数
- 占位图:loading
- …
CSS单位转化插件 px2vw
部署
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。