Vue基础
Vue导入
1 2 3 4 5 6 7 8 9
| <!-- 在线导入 --> <!-- 开发环境版本,包含了用帮助的命令行警告 --> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <!-- 生产环境版本,优化了尺寸和速度 --> <script src="https://cdn.jsdelivr.net/npm/vue"></script>
<!-- 本地导入 --> <script src="node_modules/vue/dist/vue.js"></script>
|
Vue基本语法
钩子函数
类似于Android中活动的周期函数。
生命周期
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <template> <div id="app"> <input type="button" value="修改msg" @click="msg='No'"> <h3 id="h3"> {{msg}} </h3> </div> </template>
<script> // 创建 Vue 实例,得到 ViewModel var vm = new Vue({ el: '#app', data: { msg: 'ok' }, methods: { show(){ console.log("执行了show方法") } }, beforeCreate(){ //这是我们遇到的第一个生命周期函数,表示实例完全被创建出来之前,会执行它 console.log(this.msg) this.show() // 注意:在 beforeCreate 生命周期函数执行的时候,data 和 methods 中 数据都还没有初始化 }, created(){ // 这是遇到的第二个生命周期函数 console.log(this.msg) this.show() // 在 created 中, data 和 methods 都已经初始化好了 // 如果要调用 methods 中的方法, 或者操作 data 中的数库, 最早,只能在 created }, beforeMount(){ // 这是遇到的第3个生命周期函数,表示 模板已经在内存中编辑完成了,但是尚未把模板渲染到页面中 console.log(document.getElementById('h3').innerText) // 在 beforeMount 执行的时候,页面中的元素,还没有被真正替换过来,只是之前写的一些模板字符串 }, mounted(){ // 这是遇到的第4个生命周期函数,表示,内存中的模板,已经真实的挂载到了页面中,用户已经可以看到渲染好的页面 console.log(document.getElementById('h3').innerText) // 注意:mounted 是 实例创建期间的最后一个生命周期函数,当执行完 mounted 就表示,实例已经被完全创建好了,此时,如果没有其他操作的话,这个实例,就静静的躺在内存中,一动不动(组件已经脱离了创建阶段,进入到运行阶段) // 如果要通过某些插件操作页面上的DOM节点,最早要在 mounted 中进行 }, // 接下来是运行中的两个事件 beforeUpdate(){ // 这时候,表示 我们的界面还没有被更新(数据更新了) console.log('界面上元素的内容:' + document.getElementById('h3').innerText) // ok console.log('data 中的 msg 数据是:' + this.msg) // No // 得出结论: 当执行 beforeUpdate 的时候,页面中的显示的数据,还是旧的,此时 data 数据是最新的,页面尚未和最新的数据保持同步 } }) </script>
|
插值表达式
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script type="text/javascript"> //创建vue对象 var app = new Vue({ //让vue接管div标签 el:"#app", // 这里的app就是要管理组件的id //定义数据,里边包含一个属性name,值为"白大锅" data:{ name:"白大锅" } }); </script>
<h1>欢迎来到-->{{ name }}</h1>
|
显示数据
v-text和v-html专门用来展示数据, 其作用和插值表达式类似,可以避免插值闪烁问题。插值闪烁: 在数据未加载完成时,页面会显示出原始的大括号, 过一会才会展示正常数据.
1 2
| v-text:<span v-text="msg"></span> <!-- 相当于<span>{{msg}}</span> --> v-html:<span v-html="msg"></span> <!-- 相当于<span>{{msg}}</span> -->
|
1 2
| v-text:把数据当作纯文本显示. v-html:遇到html标签,会正常解析
|
数据双向绑定
v-model
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <body> <div id="app"> 用户名: <input type="text" v-model="username"/> </div>
</body> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ //该属性值和文本框的value属性值,保持一致 username:"" } });
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| //复选框 <div id="app"> <input type="checkbox" value="Java" v-model="language">Java<br> <input type="checkbox" value="Python" v-model="language">Python<br> <input type="checkbox" value="Swift" v-model="language">Swift<br> </div> <script type="text/javascript"> var app = new Vue({ el:"#app", data:{ //数组中的值,就是被选中的元素的value属性值 language:["Java","Python"] } }); </script>
|
数据代理
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script> // 数据代理:通过一个对象代理对另一个对象中属性的操作(读、写) let obj1 = {x:100} let onj2 = {y:200} Object.defineProperty(obj2,'x',{ get(){ return obj1.x; }, set(value){ obj1.x = value; } }) </script>
|
事件绑定
1 2 3
| <div> <button style="width: 100px;height: 50px;" v-on:click="showinfo1(12)">BUTTON</button> </div>
|
1 2 3 4 5
| methods:{ showinfo1(data) { this.username = "点击了按钮"; } }
|
事件修饰符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>事件修饰符</title> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> <h2>欢迎来到{{name}}学习</h2> <!-- 阻止默认事件 --> <a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a> // 这里点击之后会阻止浏览器跳转至href页面
<!-- 阻止事件冒泡 --> <div class="demo1" @click="showInfo"> <button @click.stop="showInfo">点我提示信息</button> // 点击内部的按钮时会防止触发外部div的点击事件 </div>
<!-- 事件只触发一次 --> <button @click.once="showInfo">点我提示信息</button>
<!-- 使用事件的捕获模式 --> <div class="box1" @click.capture="showMsg(1)"> div1 <div class="box2" @click="showMsg(2)"> div2 // 与冒泡的顺序相反,使用事件捕获,外部组件会先一步调用 </div> </div>
<!-- 只有event.target是当前操作的元素时才触发事件 --> <div class="demo1" @click.self="showInfo"> <button @click="showInfo">点我提示信息</button> // 点击button时,当没有添加self修饰符时,冒泡的触发target全为button,当我们添加self修饰符时,由于target为button,因此不会触发外部事件。与阻止冒泡类似 </div>
<!-- 事件的默认行为立即执行,无需等待事件回调执行完毕 --> <ul @wheel.passive="demo" class="list"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul>
</div> </body>
<script type="text/javascript"> Vue.config.productionTip = false
new Vue({ el:'#root', data:{ name:'尚硅谷' }, methods:{ showInfo(e){ alert('同学你好!') }, showMsg(msg){ console.log(msg) }, demo(){ // 无须等待此函数运行完成 for (let i = 0; i < 100000; i++) { console.log('#') } console.log('累坏了') } } }) </script> </html>
|
- prevent:阻止默认事件(常用)
- stop:阻止事件冒泡(常用)
- once:事件只触发一次(常用)
- capture:使用事件的捕获模式
- self:只有
event.target
是当前操作的元素时才触发事件
- passive:事件的默认行为立即执行,无需等待事件回调执行完毕
计算属性
1 2 3 4 5 6 7 8 9 10 11 12
| computed:{ fullName:{ get(){ return this.firstName + '-' + this.lastName }, set(value){ const arr = value.split('-') this.firstName = arr[0] this.lastName = arr[1] } } }
|
定义:要用的属性不存在,需要通过已有属性计算得来。
原理:底层借助了Objcet.defineproperty()
方法提供的getter和setter。
get函数什么时候执行?
初次读取时会执行一次
当依赖的数据发生改变时会被再次调用
优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
监视属性
1 2 3 4 5 6 7 8 9
| watch:{ isHot:{ immediate:true, //初始化时让handler调用一下 //handler什么时候调用?当isHot发生改变时 handler(newValue,oldValue){ console.log('isHot被修改了',newValue,oldValue) } } }
|
总结:
监视属性watch:
- 当被监视的属性变化时,回调函数自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视
- 监视有两种写法:
- 创建Vue时传入watch配置
- 通过
vm.$watch
监视 通常在开发时,开始不晓得需要监视,后面添加。
1 2 3 4 5 6
| vm.$watch('isHot',{ immediate:true, handler(newValue,oldValue){ console.log('isHot被修改了',newValue,oldValue) } })
|
深度监视
<script>
Vue.config.productionTip = false
new Vue({
el:'#root',
data:{
isHot:true,
numbers:{
a:1,
b:1,
}
},
watch:{
//监视多级结构中所有属性的变化
numbers:{
deep:true,
handler(){
console.log('numbers改变了')
}
}
//监视多级结构中某个属性的变化
/* 'numbers.a':{
handler(){
console.log('a被改变了')
}
} */
}
})
</script>
</body>
</html>
深度监视:
- Vue中的watch默认不监测对象内部值的改变(一层)
- 在watch中配置
deep:true
可以监测对象内部值的改变(多层)
总结:
- computed和watch之间的区别:
- computed能完成的功能,watch都可以完成
- watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
- 两个重要的小原则:
- 所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
- 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象。
条件渲染
1 2 3 4 5 6 7 8 9
| <h2 v-show="true">Hello,{{name}}!</h2> 适用于:切换频率较高的场景 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
<div v-if="n === 1">Angular</div> <div v-else-if="n === 2">React</div> <div v-else>Vue</div> 适用于:切换频率较低的场景 特点:不展示的DOM元素直接被移除
|
列表过滤
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>列表过滤</title> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> <h2>人员列表</h2> <input type="text" placeholder="请输入名字" v-model="keyWord"> <ul> <li v-for="(p,index) of filPersons" :key="index"> {{p.name}}-{{p.age}}-{{p.sex}} </li> </ul> </div>
<script type="text/javascript"> Vue.config.productionTip = false
new Vue({ el:'#root', data:{ keyWord:'', persons:[ {id:'001',name:'马冬梅',age:19,sex:'女'}, {id:'002',name:'周冬雨',age:20,sex:'女'}, {id:'003',name:'周杰伦',age:21,sex:'男'}, {id:'004',name:'温兆伦',age:22,sex:'男'} ] }, computed:{ // 利用计算属性过滤信息 filPersons(){ return this.persons.filter((p)=>{ return p.name.indexOf(this.keyWord) !== -1 }) } } }) </script> </body> </html>
|
总结:
Vue监视数据的原理:
vue会监视data中所有层次的数据
如何监测对象中的数据?
通过setter实现监视,且要在new Vue
时就传入要监测的数据
- 对象中后追加的属性,Vue默认不做响应式处理
- 如需给后添加的属性做响应式,请使用如下API:
Vue.set(target,propertyName/index,value)
vm.$set(target,propertyName/index,value)
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新
- 重新解析模板,进而更新页面
在Vue修改数组中的某个元素一定要用如下方法:
- 使用这些API:
push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
Vue.set()
或 vm.$set()
过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>过滤器</title> <script type="text/javascript" src="../js/vue.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/dayjs/1.10.6/dayjs.min.js"></script> </head> <body> <div id="root"> <h2>时间</h2> <h3>当前时间戳:{{time}}</h3> <h3>转换后时间:{{time | timeFormater()}}</h3> <h3>转换后时间:{{time | timeFormater('YYYY-MM-DD HH:mm:ss')}}</h3> <h3>截取年月日:{{time | timeFormater() | mySlice}}</h3> </div> </body>
<script type="text/javascript"> Vue.config.productionTip = false //全局过滤器,在任意的Vue对象中都可以使用 Vue.filter('mySlice',function(value){ return value.slice(0,11) }) new Vue({ el:'#root', data:{ time:1626750147900, }, //局部过滤器 filters:{ timeFormater(value, str="YYYY年MM月DD日 HH:mm:ss"){ return dayjs(value).format(str) } } }) </script> </html>
|
总结:
过滤器:
- 定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
- 语法:
- 注册过滤器:
Vue.filter(name,callback)
或 new Vue{filters:{}}
- 使用过滤器:
{{ xxx | 过滤器名}}
或 v-bind:属性 = "xxx | 过滤器名"
- 备注:
- 过滤器可以接收额外参数,多个过滤器也可以串联
- 并没有改变原本的数据,而是产生新的对应的数据
自定义指令
总结:
自定义指令定义语法:
局部指令:
new Vue({
directives:{指令名:配置对象}
})
123
1 2 3 4 5 6
| 2. ```javascript new Vue({ directives:{指令名:回调函数} }) 123
|
全局指令:
Vue.directive(指令名,配置对象)
Vue.directive(指令名,回调函数)
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| Vue.directive('fbind',{ bind(element,binding){ element.value = binding.value }, inserted(element,binding){ element.focus() }, update(element,binding){ element.value = binding.value } }) 1234567891011121314
|
配置对象中常用的3个回调函数:
bind(element,binding)
:指令与元素成功绑定时调用
inserted(element,binding)
:指令所在元素被插入页面时调用
update(element,binding)
:指令所在模板结构被重新解析时调用
备注:
指令定义时不加“v-”,但使用时要加“v-”
指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名
1 2 3 4 5 6 7 8 9 10 11
| new Vue({ el:'#root', data:{ n:1 }, directives:{ 'big-number'(element,binding){ element.innerText = binding.value * 10 } } })
|
生命周期
- 又名:生命周期回调函数、生命周期函数、生命周期钩子
- 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
- 生命周期函数中的this指向是vm 或 组件实例对象
Vue组件化编程
模块与组件、模块化与组件化
模块
- 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
- 为什么:js 文件很多很复杂
- 作用:复用 js,简化 js 的编写,提高 js 运行效率
组件
- 定义:用来实现局部功能的代码和资源的集合(html/css/js/image…)
- 为什么:一个界面的功能很复杂
- 作用:复用编码,简化项目编码,提高运行效率
模块化
当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用
组件化
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用
非单文件组件
基本使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>基本使用</title> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> <h1>{{msg}}</h1> <hr> <school></school> <hr> <student></student> </div> </body>
<script type="text/javascript"> Vue.config.productionTip = false
//第一步:创建school组件 const school = Vue.extend({ //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。 template:` <div class="demo"> <h2>学校名称:{{schoolName}}</h2> <h2>学校地址:{{address}}</h2> </div> `, data(){ return { schoolName:'尚硅谷', address:'北京昌平' } } })
//第一步:创建student组件 const student = Vue.extend({ template:` <div> <h2>学生姓名:{{studentName}}</h2> <h2>学生年龄:{{age}}</h2> </div> `, data(){ return { studentName:'JOJO', age:20 } } }) //创建vm new Vue({ el:'#root', data:{ msg:'你好,JOJO!' }, //第二步:注册组件(局部注册) components:{ school, student } }) </script> </html>
|
总结:
Vue中使用组件的三大步骤:
- 定义组件(创建组件)
- 注册组件
- 使用组件(写组件标签)
如何定义一个组件?
使用Vue.extend(options)
创建,其中options
和new Vue(options)
时传入的options
几乎一样,但也有点区别:
el不要写,为什么?
最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
data必须写成函数,为什么?
避免组件被复用时,数据存在引用关系
如何注册组件?
- 局部注册:
new Vue
的时候传入components
选项
- 全局注册:
Vue.component('组件名',组件)
注意:创建自定义组件无论是否用到了Vue实例,都必须要有Vue实例,并且只能在Vue实例绑定的标签内才能使用自定义组件!!!
组件与Vue的内置关系
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>一个重要的内置关系</title> <script type="text/javascript" src="../js/vue.js"></script> </head> <body> <div id="root"> <school></school> </div> </body>
<script type="text/javascript"> Vue.config.productionTip = false Vue.prototype.x = 99
const school = Vue.extend({ name:'school', template:` <div> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showX">点我输出x</button> </div> `, data(){ return { name:'尚硅谷', address:'北京' } }, methods: { showX(){ console.log(this.x) } }, })
const vm = new Vue({ el:'#root', data:{ msg:'你好' }, components:{school} }) </script> </html>
|
- 一个重要的内置关系:
VueComponent.prototype.__proto__ === Vue.prototype
- 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性、方法
使用Vue CLI脚手架
具体步骤
- 如果下载缓慢请配置 npm 淘宝镜像:
npm config set registry http://registry.npm.taobao.org
- 全局安装@vue/cli:
npm install -g @vue/cli
- 切换到你要创建项目的目录,然后使用命令创建项目:
vue create xxxx
- 选择使用vue的版本
- 启动项目:
npm run serve
- 暂停项目:Ctrl+C
Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,请执行:
ref属性
与html的id类似
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <template> <div> <h1 ref="title">{{msg}}</h1> <School ref="sch"/> <button @click="show" ref="btn">点我输出ref</button> </div> </template>
<script> import School from './components/School.vue' export default { name:'App', components: { School }, data() { return { msg:'欢迎学习Vue!' } }, methods:{ show(){ console.log(this.$refs.title) console.log(this.$refs.sch) console.log(this.$refs.btn) } } } </script>
|
总结:
ref
属性:
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在
html
标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc)
- 使用方式:
- 打标识:
<h1 ref="xxx"></h1>
或 <School ref="xxx"></School>
- 获取:
this.$refs.xxx
props配置项
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <div> <Student name="JOJO" sex="男酮" :age="20" /> <--数值类型或函数类型的参数最好使用`:`--> </div> </template>
<script> import Student from './components/Student.vue' export default { name:'App', components: { Student }, } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <template> <div> <h1>{{msg}}</h1> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <h2>学生年龄:{{age}}</h2> </div> </template>
<script> export default { name:'Student', data() { return { msg:"我是一名来自枝江大学的男酮,嘿嘿,我的金轮~~", } }, // 简单声明接收 // props:['name','age','sex']
// 接收的同时对数据进行类型限制 /* props:{ name:String, age:Number, sex:String } */
// 接收的同时对数据进行类型限制 + 指定默认值 + 限制必要性 props:{ name:{ type:String, required:true, }, age:{ type:Number, default:99 }, sex:{ type:String, required:true } } } </script>
|
总结:
props
配置项:
功能:让组件接收外部传过来的数据
传递数据:<Demo name="xxx"/>
接收数据:
第一种方式(只接收):props:['name']
第二种方式(限制数据类型):props:{name:String}
第三种方式(限制类型、限制必要性、指定默认值):
1 2 3 4 5 6 7
| props:{ name:{ type:String, required:true, default:'JOJO' } }
|
props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据
mixin混入
局部混入
src/mixin.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| export const mixin = { methods: { showName() { alert(this.name) } }, mounted() { console.log("你好呀~") } } 12345678910 src/components/School.vue <template> <div> <h2 @click="showName">学校姓名:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template>
<script> import {mixin} from '../mixin' export default { name:'School', data() { return { name:'尚硅谷', address:'北京' } }, mixins:[mixin] } </script> 12345678910111213141516171819202122
|
src/components/Student.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div> <h2 @click="showName">学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> </div> </template>
<script> //引入混入 import {mixin} from '../mixin' export default { name:'Student', data() { return { name:'JOJO', sex:'男' } }, mixins:[mixin] } </script> 12345678910111213141516171819202122
|
src/App.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <template> <div> <School/> <hr/> <Student/> </div> </template>
<script> import Student from './components/Student.vue' import School from './components/School.vue'
export default { name:'App', components: { Student,School }, } </script>
|
全局混入:
src/main.js
:
1 2 3 4 5 6 7 8 9 10 11
| import Vue from 'vue' import App from './App.vue' import {mixin} from './mixin'
Vue.config.productionTip = false Vue.mixin(mixin)
new Vue({ el:"#app", render: h => h(App) })
|
plugin插件
src/plugin.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| export default { install(Vue,x,y,z){ console.log(x,y,z) Vue.filter('mySlice',function(value){ return value.slice(0,4) })
Vue.mixin({ data() { return { x:100, y:200 } }, })
Vue.prototype.hello = ()=>{alert('你好啊')} } }
|
src/main.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import Vue from 'vue' import App from './App.vue' import plugin from './plugin'
Vue.config.productionTip = false
Vue.use(plugin,1,2,3)
new Vue({ el:"#app", render: h => h(App) })
|
src/components/School.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <div> <h2>学校姓名:{{name | mySlice}}</h2> <h2>学校地址:{{address}}</h2> </div> </template>
<script> export default { name:'School', data() { return { name:'尚硅谷atguigu', address:'北京' } } } </script> 123456789101112131415161718
|
src/components/Student.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <template> <div> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="test">点我测试hello方法</button> </div> </template>
<script> export default { name:'Student', data() { return { name:'JOJO', sex:'男' } }, methods:{ test() { this.hello() } } } </script>
|
总结:
插件:
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据
定义插件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| plugin.install = function (Vue, options) { Vue.filter(....) Vue.directive(....) Vue.mixin(....) Vue.prototype.$myMethod = function () {...} Vue.prototype.$myProperty = xxxx }
|
使用插件:Vue.use(plugin)
Scoped样式
1 2 3 4 5
| <style scoped> .demo{ background-color: chartreuse; } </style>
|
总结:
scoped
样式:
- 作用:让样式在局部生效,防止冲突
- 写法:
<style scoped>
scoped
样式一般不会在App.vue
中使用
WebStorage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>localStorage</title> </head> <body> <h2>localStorage</h2> <button onclick="saveDate()">点我保存数据</button><br/> <button onclick="readDate()">点我读数据</button><br/> <button onclick="deleteDate()">点我删除数据</button><br/> <button onclick="deleteAllDate()">点我清空数据</button><br/>
<script> let person = {name:"JOJO",age:20}
function saveDate(){ localStorage.setItem('msg','localStorage') localStorage.setItem('person',JSON.stringify(person)) } function readDate(){ console.log(localStorage.getItem('msg')) const person = localStorage.getItem('person') console.log(JSON.parse(person)) } function deleteDate(){ localStorage.removeItem('msg') localStorage.removeItem('person') } function deleteAllDate(){ localStorage.clear() } </script> </body> </html>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>sessionStorage</title> </head> <body> <h2>sessionStorage</h2> <button onclick="saveDate()">点我保存数据</button><br/> <button onclick="readDate()">点我读数据</button><br/> <button onclick="deleteDate()">点我删除数据</button><br/> <button onclick="deleteAllDate()">点我清空数据</button><br/>
<script> let person = {name:"JOJO",age:20}
function saveDate(){ sessionStorage.setItem('msg','sessionStorage') sessionStorage.setItem('person',JSON.stringify(person)) } function readDate(){ console.log(sessionStorage.getItem('msg')) const person = sessionStorage.getItem('person') console.log(JSON.parse(person)) } function deleteDate(){ sessionStorage.removeItem('msg') sessionStorage.removeItem('person') } function deleteAllDate(){ sessionStorage.clear() } </script> </body> </html>
|
总结:
- 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
- 浏览器端通过
Window.sessionStorage
和Window.localStorage
属性来实现本地存储机制
- 相关API:
xxxStorage.setItem('key', 'value')
:该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
xxxStorage.getItem('key')
:该方法接受一个键名作为参数,返回键名对应的值
xxxStorage.removeItem('key')
:该方法接受一个键名作为参数,并把该键名从存储中删除
xxxStorage.clear()
:该方法会清空存储中的所有数据
- 备注:
SessionStorage
存储的内容会随着浏览器窗口关闭而消失
LocalStorage
存储的内容,需要手动清除才会消失
xxxStorage.getItem(xxx)
如果 xxx 对应的 value 获取不到,那么getItem()
的返回值是null
JSON.parse(null)
的结果依然是null
组件的自定义事件
绑定
src/App.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <template> <div class="app"> <!-- 通过父组件给子组件传递函数类型的props实现父给子传递数据 --> <School :getSchoolName="getSchoolName"/> // :为单向数据绑定
<!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第一种写法,使用@或v-on) --> <!-- <Student @jojo="getStudentName"/> --> <!--这里给Student组件绑定了一个自定义的jojo事件。-->
<!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第二种写法,使用ref) --> <Student ref="student"/> <!--这里相当于给Student组件添加了一个id--> </div> </template>
<script> import Student from './components/Student.vue' import School from './components/School.vue'
export default { name:'App', components: { Student,School }, methods:{ getSchoolName(name){ console.log("已收到学校的名称:"+name) }, getStudentName(name){ console.log("已收到学生的姓名:"+name) } }, mounted(){ <!--使用这种方式绑定自定义事件更加的灵活--> this.$refs.student.$on('jojo',this.getStudentName) } } </script>
<style scoped> .app{ background-color: gray; padding: 5px; } </style>
|
src/components/Student.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">点我传递学生姓名</button> </div> </template>
<script> export default { name:'Student', data() { return { name:'JOJO', sex:'男' } }, methods:{ sendStudentName(){ <!--触发jojo事件--> this.$emit('jojo',this.name) } } } </script>
<style scoped> .student{ background-color: chartreuse; padding: 5px; margin-top: 30px; } </style>
|
效果:
解绑
src/App.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <template> <div class="app"> <Student @jojo="getStudentName"/> </div> </template>
<script> import Student from './components/Student.vue'
export default { name:'App', components: { Student }, methods:{ getStudentName(name){ console.log("已收到学生的姓名:"+name) } } } </script>
<style scoped> .app{ background-color: gray; padding: 5px; } </style>
|
src/components/Student.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">点我传递学生姓名</button> <button @click="unbind">解绑自定义事件</button> </div> </template>
<script> export default { name:'Student', data() { return { name:'JOJO', sex:'男' } }, methods:{ sendStudentName(){ this.$emit('jojo',this.name) }, unbind(){ // 解绑一个自定义事件 // this.$off('jojo') // 解绑多个自定义事件 // this.$off(['jojo']) // 解绑所有自定义事件 this.$off() } } } </script>
<style scoped> .student{ background-color: chartreuse; padding: 5px; margin-top: 30px; } </style>
|
效果:
总结:
组件的自定义事件:
一种组件间通信的方式,适用于:==子组件 > 父组件
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
绑定自定义事件:
第一种方式,在父组件中:<Demo @atguigu="test"/>
或 <Demo v-on:atguigu="test"/>
第二种方式,在父组件中:
1 2 3 4 5
| <Demo ref="demo"/> ... mounted(){ this.$refs.demo.$on('atguigu',data) }
|
若想让自定义事件只能触发一次,可以使用once
修饰符,或$once
方法
触发自定义事件:this.$emit('atguigu',数据)
解绑自定义事件:this.$off('atguigu')
组件上也可以绑定原生DOM事件,需要使用native
修饰符
注意:通过this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
全局事件总线
全局事件总线(GlobalEventBus):
一种组件间通信的方式,适用于任意组件间通信
安装全局事件总线:
1 2 3 4 5 6 7 8
| new Vue({ ... beforeCreate() { Vue.prototype.$bus = this }, ... })
|
使用事件总线:
接收数据:A组件想接收数据,则在A组件中给$bus
绑定自定义事件,事件的回调留在A组件自身
1 2 3 4 5 6 7 8 9 10
| export default { methods(){ demo(data){...} } ... mounted() { this.$bus.$on('xxx',this.demo) } }
|
提供数据:this.$bus.$emit('xxx',data)
最好在beforeDestroy
钩子中,用$off
去解绑当前组件所用到的事件
消息的订阅与发布
与全局事件总线类似都可以实现任意组件间的通行。
src/components/School.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template>
<script> import pubsub from 'pubsub-js' // 引入消息队列的库
export default { name:'School', data() { return { name:'尚硅谷', address:'北京', } }, methods:{ demo(msgName,data) { console.log('我是School组件,收到了数据:',data) } }, mounted() { this.pubId = pubsub.subscribe('demo',this.demo) //订阅消息 }, beforeDestroy() { pubsub.unsubscribe(this.pubId) //取消订阅 } } </script>
<style scoped> .school{ background-color: skyblue; padding: 5px; } </style>
|
src/components/Student.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <template> <div class="student"> <h2>学生姓名:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <button @click="sendStudentName">把学生名给School组件</button> </div> </template>
<script> import pubsub from 'pubsub-js'
export default { name:'Student', data() { return { name:'JOJO', sex:'男', } }, methods: { sendStudentName(){ pubsub.publish('demo',this.name) //发布消息 } } } </script>
<style scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>
|
总结:
消息订阅与发布(pubsub):
消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信
使用步骤:
安装pubsub:npm i pubsub-js
引入:import pubsub from 'pubsub-js'
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身
1 2 3 4 5 6 7 8 9 10
| export default { methods(){ demo(data){...} } ... mounted() { this.pid = pubsub.subscribe('xxx',this.demo) } } 123456789
|
提供数据:pubsub.publish('xxx',data)
最好在beforeDestroy
钩子中,使用pubsub.unsubscribe(pid)
取消订阅
$nextTick
$nextTick(回调函数)可以将回调延迟到下次DOM更新循环之后执行
通常调用函数去更新界面信息时,模板的重新解析会在函数的全部执行完成之后。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| handleEdit(todo){ // 如果todo自身有isEdit属性就将isEdit改成true if(Object.prototype.hasOwnProperty.call(todo,'isEdit')){ todo.isEdit = true }else{ // 如果没有就向todo中添加一个响应式的isEdit属性并设为true this.$set(todo,'isEdit',true) } // 当Vue重新编译模板之后执行$nextTick()中的回调函数 this.$nextTick(function(){ // 使input框获取焦点 this.$refs.inputTitle.focus() }) },
|
Vue中的Ajax
vue脚手架配置代理
下载axios库: npm install axios
解决跨域问题
cors 后端解决
jsonp 前后端一同解决,一般很少用到,且只能解决get跨域问题
代理服务器 使用最多
使用vue-cli
vue.config.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| module.exports = { pages: { index: { entry: 'src/main.js', }, }, lintOnSave:false,
devServer: { proxy: { '/jojo': { target: 'http://localhost:5000', pathRewrite:{'^/jojo':''}, }, '/atguigu': { target: 'http://localhost:5001', pathRewrite:{'^/atguigu':''}, } } } }
|
src/App.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| <template> <div id="root"> <button @click="getStudents">获取学生信息</button><br/> <button @click="getCars">获取汽车信息</button> </div> </template>
<script> import axios from 'axios' export default { name:'App', methods: { getStudents(){ // 发送请求,通过代理服务器进行转发 axios.get('http://localhost:8080/jojo/students').then( response => { console.log('请求成功了',response.data) }, error => { console.log('请求失败了',error.message) } ) }, getCars(){ axios.get('http://localhost:8080/atguigu/cars').then( response => { console.log('请求成功了',response.data) }, error => { console.log('请求失败了',error.message) } ) } } } </script>
|
vue-resource
下载 vue-resource库:npm i vue-resource
在Vue2中已不再更新,Vue官方也不
slot插槽
默认插槽
组件标签中间的组件放置的位置。
src/App.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <template> <div class="container"> <Category title="美食" > <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> </Category>
<Category title="游戏" > <ul> <li v-for="(g,index) in games" :key="index">{{g}}</li> </ul> </Category>
<Category title="电影"> <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> </Category> </div> </template>
<script> import Category from './components/Category' export default { name:'App', components:{Category}, data() { return { games:['植物大战僵尸','红色警戒','空洞骑士','王国'] } }, } </script>
<style scoped> .container{ display: flex; justify-content: space-around; } </style>
|
src/components/Category.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| <template> <div class="category"> <h3>{{title}}分类</h3> <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> </div> </template>
<script> export default { name:'Category', props:['title'] } </script>
<style scoped> .category{ background-color: skyblue; width: 200px; height: 300px; } h3{ text-align: center; background-color: orange; } video{ width: 100%; } img{ width: 100%; } </style>
|
具名插槽
为每个插槽设置名称,为组件中的每个子组件放进对应的插槽中,否则将会将所有的子组件复制相同的
src/App.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| <template> <div class="container"> <Category title="美食" > <img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> <a slot="footer" href="http://www.atguigu.com">更多美食</a> </Category>
<Category title="游戏" > <ul slot="center"> <li v-for="(g,index) in games" :key="index">{{g}}</li> </ul> <div class="foot" slot="footer"> <a href="http://www.atguigu.com">单机游戏</a> <a href="http://www.atguigu.com">网络游戏</a> </div> </Category>
<Category title="电影"> <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> <template v-slot:footer> <div class="foot"> <a href="http://www.atguigu.com">经典</a> <a href="http://www.atguigu.com">热门</a> <a href="http://www.atguigu.com">推荐</a> </div> <h4>欢迎前来观影</h4> </template> </Category> </div> </template>
<script> import Category from './components/Category' export default { name:'App', components:{Category}, data() { return { games:['植物大战僵尸','红色警戒','空洞骑士','王国'] } }, } </script>
<style> .container,.foot{ display: flex; justify-content: space-around; } h4{ text-align: center; } </style>
|
src/components/Category.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| <template> <div class="category"> <h3>{{title}}分类</h3> <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> <slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot> <slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot> </div> </template>
<script> export default { name:'Category', props:['title'] } </script>
<style scoped> .category{ background-color: skyblue; width: 200px; height: 300px; } h3{ text-align: center; background-color: orange; } video{ width: 100%; } img{ width: 100%; } </style>
|
作用域插槽
相当于在子组件中传递值给父组件。
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
具体编码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 父组件中: <Category> <template scope="scopeData"> <!-- 生成的是ul列表 --> <ul> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category>
<Category> <template slot-scope="scopeData"> <!-- 生成的是h4标题 --> <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4> </template> </Category> 子组件中: <template> <div> <slot :games="games"></slot> </div> </template> <script> export default { name:'Category', props:['title'], //数据在子组件自身 data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'] } }, } </script>
|
Vuex
解决多组件之间数据共享问题。
什么时候使用:
- 多组件依赖于同一状态
- 来自不同组件的行为需要变更同一转态
工作原理图
原理图解释:107_尚硅谷Vue技术_Vuex工作原理图_哔哩哔哩_bilibili
下载Vuex : npm i vuex
搭建Vuex
下载 Vuex:npm i vuex
创建src/store/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = {}
const mutations = {}
const state = {}
export default new Vuex.Store({ actions, mutations, state })
|
在src/main.js
中创建 vm 时传入store
配置项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import Vue from 'vue' import App from './App.vue' import Vuex from 'vuex' import store from './store'
Vue.config.productionTip = false
Vue.use(Vuex)
new Vue({ el:"#app", render: h => h(App), store })
|
使用Vuex编写
src/components/Count.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <template> <div> <h1>当前求和为:{{$store.state.sum}}</h1> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementOdd">当前求和为奇数再加</button> <button @click="incrementWait">等一等再加</button> </div> </template>
<script> export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, methods: { increment(){ this.$store.commit('ADD',this.n) }, decrement(){ this.$store.commit('SUBTRACT',this.n) }, incrementOdd(){ this.$store.dispatch('addOdd',this.n) }, incrementWait(){ this.$store.dispatch('addWait',this.n) }, }, } </script>
<style> button{ margin-left: 5px; } </style>
|
src/store/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = { addOdd(context,value){ console.log("actions中的addOdd被调用了") if(context.state.sum % 2){ context.commit('ADD',value) } }, addWait(context,value){ console.log("actions中的addWait被调用了") setTimeout(()=>{ context.commit('ADD',value) },500) }, }
const mutations = { ADD(state,value){ state.sum += value }, SUBTRACT(state,value){ state.sum -= value } }
const state = { sum:0 }
export default new Vuex.Store({ actions, mutations, state })
|
总结:
Vuex的基本使用:
初始化数据state
,配置actions
、mutations
,操作文件store.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = { jia(context,value){ context.commit('JIA',value) }, }
const mutations = { JIA(state,value){ state.sum += value } }
const state = { sum:0 }
export default new Vuex.Store({ actions, mutations, state, })
|
组件中读取vuex中的数据:$store.state.sum
组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)
或 $store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions
,即不写dispatch
,直接编写commit
getters
src/Count.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| <template> <div> <h1>当前求和为:{{$store.state.sum}}</h1> <h3>当前求和的10倍为:{{$store.getters.bigSum}}</h3> <!--需要对state中的数据进行加工--> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementOdd">当前求和为奇数再加</button> <button @click="incrementWait">等一等再加</button> </div> </template>
<script> export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, methods: { increment(){ this.$store.commit('ADD',this.n) }, decrement(){ this.$store.commit('SUBTRACT',this.n) }, incrementOdd(){ this.$store.dispatch('addOdd',this.n) }, incrementWait(){ this.$store.dispatch('addWait',this.n) }, }, } </script>
<style> button{ margin-left: 5px; } </style>
|
src/store/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = { addOdd(context,value){ console.log("actions中的addOdd被调用了") if(context.state.sum % 2){ context.commit('ADD',value) } }, addWait(context,value){ console.log("actions中的addWait被调用了") setTimeout(()=>{ context.commit('ADD',value) },500) }, }
const mutations = { ADD(state,value){ state.sum += value }, SUBTRACT(state,value){ state.sum -= value } }
const state = { sum:0 }
const getters = { bigSum(){ return state.sum * 10 } }
export default new Vuex.Store({ actions, mutations, state, getters })
|
总结:
getters
配置项的使用:
概念:当state
中的数据需要经过加工后再使用时,可以使用getters
加工
在store.js
中追加getters
配置
1 2 3 4 5 6 7 8 9 10 11 12 13
| ... const getters = { bigSum(state){ return state.sum * 10 } }
export default new Vuex.Store({ ... getters }) 123456789101112
|
组件中读取数据:$store.getters.bigSum
四个map方法的使用
mapState与mapGetters
src/store/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = { addOdd(context,value){ console.log("actions中的addOdd被调用了") if(context.state.sum % 2){ context.commit('ADD',value) } }, addWait(context,value){ console.log("actions中的addWait被调用了") setTimeout(()=>{ context.commit('ADD',value) },500) }, }
const mutations = { ADD(state,value){ state.sum += value }, SUBTRACT(state,value){ state.sum -= value } }
const state = { sum:0, name:'JOJO', school:'尚硅谷', }
const getters = { bigSum(){ return state.sum * 10 } }
export default new Vuex.Store({ actions, mutations, state, getters })
|
src/components/Count.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <template> <div> <h1>当前求和为:{{sum}}</h1> <h3>当前求和的10倍为:{{bigSum}}</h3> <h3>我是{{name}},我在{{school}}学习</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementOdd">当前求和为奇数再加</button> <button @click="incrementWait">等一等再加</button> </div> </template>
<script> import {mapState,mapGetters} from 'vuex'
export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, methods: { increment(){ this.$store.commit('ADD',this.n) }, decrement(){ this.$store.commit('SUBTRACT',this.n) }, incrementOdd(){ this.$store.dispatch('addOdd',this.n) }, incrementWait(){ this.$store.dispatch('addWait',this.n) }, }, computed:{ // 借助mapState生成计算属性(数组写法) // ...mapState(['sum','school','name']), // 借助mapState生成计算属性(对象写法) ...mapState({sum:'sum',school:'school',name:'name'}), // 冒号前面的字符串相当于别名,可以自定义 ...mapGetters(['bigSum']) } } </script>
<style> button{ margin-left: 5px; } </style>
|
mapActions与mapMutation
src/components/Count.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <template> <div> <h1>当前求和为:{{sum}}</h1> <h3>当前求和的10倍为:{{bigSum}}</h3> <h3>我是{{name}},我在{{school}}学习</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment(n)">+</button> <button @click="decrement(n)">-</button> <button @click="incrementOdd(n)">当前求和为奇数再加</button> <button @click="incrementWait(n)">等一等再加</button> </div> </template>
<script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, methods: { // 借助mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'ADD',decrement:'SUBTRACT'}),
// 借助mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'addOdd',incrementWait:'addWait'}) }, computed:{ // 借助mapState生成计算属性(数组写法) // ...mapState(['sum','school','name']), // 借助mapState生成计算属性(对象写法) ...mapState({sum:'sum',school:'school',name:'name'}),
...mapGetters(['bigSum']) } } </script>
<style> button{ margin-left: 5px; } </style>
|
总结:
mapActions方法:用于帮助我们生成与actions
对话的方法,即:包含$store.dispatch(xxx)
的函数
1 2 3 4 5 6 7 8
| methods:{ ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
...mapActions(['jiaOdd','jiaWait']) } 1234567
|
mapMutations方法:用于帮助我们生成与mutations
对话的方法,即:包含$store.commit(xxx)
的函数
1 2 3 4 5 6 7 8
| methods:{ ...mapMutations({increment:'JIA',decrement:'JIAN'}), ...mapMutations(['JIA','JIAN']), } 1234567
|
备注:mapActions
与mapMutations
使用时,若需要传递参数,则需要在模板中绑定事件时传递好参数,否则参数是事件对象
多组件共享数据
mapActions与mapMutation
src/App.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <div class="container"> <Count/> <hr/> <Person/> </div> </template>
<script> import Count from './components/Count' import Person from './components/Person'
export default { name:'App', components:{Count,Person} } </script>
|
src/store/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const actions = { addOdd(context,value){ console.log("actions中的addOdd被调用了") if(context.state.sum % 2){ context.commit('ADD',value) } }, addWait(context,value){ console.log("actions中的addWait被调用了") setTimeout(()=>{ context.commit('ADD',value) },500) }, }
const mutations = { ADD(state,value){ state.sum += value }, SUBTRACT(state,value){ state.sum -= value }, ADD_PERSON(state,value){ console.log('mutations中的ADD_PERSON被调用了') state.personList.unshift(value) } }
const state = { sum:0, name:'JOJO', school:'尚硅谷', personList:[ {id:'001',name:'JOJO'} ] }
const getters = { bigSum(){ return state.sum * 10 } }
export default new Vuex.Store({ actions, mutations, state, getters })
|
src/components/Count.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <template> <div> <h1>当前求和为:{{sum}}</h1> <h3>当前求和的10倍为:{{bigSum}}</h3> <h3>我是{{name}},我在{{school}}学习</h3> <h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment(n)">+</button> <button @click="decrement(n)">-</button> <button @click="incrementOdd(n)">当前求和为奇数再加</button> <button @click="incrementWait(n)">等一等再加</button> </div> </template>
<script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, methods: { ...mapMutations({increment:'ADD',decrement:'SUBTRACT'}), ...mapActions({incrementOdd:'addOdd',incrementWait:'addWait'}) }, computed:{ ...mapState(['sum','school','name','personList']),, ...mapGetters(['bigSum']) } } </script>
<style> button{ margin-left: 5px; } </style>
|
src/components/Person.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <template> <div> <h1>人员列表</h1> <h3 style="color:red">Count组件求和为:{{sum}}</h3> <input type="text" placeholder="请输入名字" v-model="name"> <button @click="add">添加</button> <ul> <li v-for="p in personList" :key="p.id">{{p.name}}</li> </ul> </div> </template>
<script> import {nanoid} from 'nanoid' //雪花算法自动生成id export default { name:'Person', data() { return { name:'' } }, computed:{ personList(){ return this.$store.state.personList }, sum(){ return this.$store.state.sum } }, methods: { add(){ const personObj = {id:nanoid(),name:this.name} this.$store.commit('ADD_PERSON',personObj) this.name = '' } } } </script>
|
模块化命名空间
src/store/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import Vue from 'vue'
import Vuex from 'vuex'
import countOptions from './count'
import personOptions from './person'
Vue.use(Vuex)
export default new Vuex.Store({ modules:{ countAbout:countOptions, personAbout:personOptions, } })
|
src/store/count.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| export default{ namespaced:true, actions:{ addOdd(context,value){ console.log("actions中的addOdd被调用了") if(context.state.sum % 2){ context.commit('ADD',value) } }, addWait(context,value){ console.log("actions中的addWait被调用了") setTimeout(()=>{ context.commit('ADD',value) },500) } }, mutations:{ ADD(state,value){ state.sum += value }, SUBTRACT(state,value){ state.sum -= value } }, state:{ sum:0, name:'JOJO', school:'尚硅谷', }, getters:{ bigSum(state){ return state.sum * 10 } } } 1234567891011121314151617181920212223242526272829303132333435
|
src/store/person.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| import axios from "axios" import { nanoid } from "nanoid"
export default{ namespaced:true, actions:{ addPersonWang(context,value){ if(value.name.indexOf('王') === 0){ context.commit('ADD_PERSON',value) }else{ alert('添加的人必须姓王!') } }, addPersonServer(context){ axios.get('http://api.uixsj.cn/hitokoto/get?type=social').then( response => { context.commit('ADD_PERSON',{id:nanoid(),name:response.data}) }, error => { alert(error.message) } ) } }, mutations:{ ADD_PERSON(state,value){ console.log('mutations中的ADD_PERSON被调用了') state.personList.unshift(value) } }, state:{ personList:[ {id:'001',name:'JOJO'} ] }, getters:{ firstPersonName(state){ return state.personList[0].name } } }
|
src/components/Count.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| <template> <div> <h1>当前求和为:{{sum}}</h1> <h3>当前求和的10倍为:{{bigSum}}</h3> <h3>我是{{name}},我在{{school}}学习</h3> <h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment(n)">+</button> <button @click="decrement(n)">-</button> <button @click="incrementOdd(n)">当前求和为奇数再加</button> <button @click="incrementWait(n)">等一等再加</button> </div> </template>
<script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, methods: { ...mapMutations('countAbout',{increment:'ADD',decrement:'SUBTRACT'}), ...mapActions('countAbout',{incrementOdd:'addOdd',incrementWait:'addWait'}) }, computed:{ ...mapState('countAbout',['sum','school','name']), ...mapGetters('countAbout',['bigSum']), ...mapState('personAbout',['personList']) } } </script>
<style> button{ margin-left: 5px; } </style> 123456789101112131415161718192021222324252627282930313233343536373839404142434445
|
src/components/Person.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| <template> <div> <h1>人员列表</h1> <h3 style="color:red">Count组件求和为:{{sum}}</h3> <h3>列表中第一个人的名字是:{{firstPersonName}}</h3> <input type="text" placeholder="请输入名字" v-model="name"> <button @click="add">添加</button> <button @click="addWang">添加一个姓王的人</button> <button @click="addPerson">随机添加一个人</button> <ul> <li v-for="p in personList" :key="p.id">{{p.name}}</li> </ul> </div> </template>
<script> import {nanoid} from 'nanoid' export default { name:'Person', data() { return { name:'' } }, computed:{ personList(){ return this.$store.state.personAbout.personList }, sum(){ return this.$store.state.countAbout.sum }, firstPersonName(){ return this.$store.getters['personAbout/firstPersonName'] } }, methods: { add(){ const personObj = {id:nanoid(),name:this.name} this.$store.commit('personAbout/ADD_PERSON',personObj) this.name = '' }, addWang(){ const personObj = {id:nanoid(),name:this.name} this.$store.dispatch('personAbout/addPersonWang',personObj) this.name = '' }, addPerson(){ this.$store.dispatch('personAbout/addPersonServer') } }, } </script>
|
总结:
模块化+命名空间:
目的:让代码更好维护,让多种数据分类更加明确
修改store.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const countAbout = { namespaced:true, state:{x:1}, mutations: { ... }, actions: { ... }, getters: { bigSum(state){ return state.sum * 10 } } }
const personAbout = { namespaced:true, state:{ ... }, mutations: { ... }, actions: { ... } }
const store = new Vuex.Store({ modules: { countAbout, personAbout } }) 12345678910111213141516171819202122232425
|
开启命名空间后,组件中读取state
数据:
1 2 3 4 5
| this.$store.state.personAbout.list
...mapState('countAbout',['sum','school','subject']), 1234
|
开启命名空间后,组件中读取getters
数据:
1 2 3 4 5
| this.$store.getters['personAbout/firstPersonName']
...mapGetters('countAbout',['bigSum']) 1234
|
开启命名空间后,组件中调用dispatch
:
1 2 3 4 5
| this.$store.dispatch('personAbout/addPersonWang',person)
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) 1234
|
开启命名空间后,组件中调用commit
:
1 2 3 4
| this.$store.commit('personAbout/ADD_PERSON',person)
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
|
Vue Router路由管理器
相关理解
vue-router的理解
对SPA应用的理解
- 单页 Web 应用(single page web application,SPA)
- 整个应用只有一个完整的页面
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新
- 数据需要通过ajax请求获取
路由的理解
- 什么是路由?
- 一个路由就是一组映射关系(key - value)
- key 为路径,value 可能是 function 或 componen
- 路由分类
- 后端路由:
- 理解:value 是 function,用于处理客户端提交的请求
- 工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
- 前端路由:
- 理解:value 是 component,用于展示页面内容
- 工作过程:当浏览器的路径改变时,对应的组件就会显示
基本路由
下载vue-router
:npm i vue-router
src/router/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import VueRouter from "vue-router";
import Home from '../components/Home' import About from '../components/About'
export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] })
|
src/main.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import Vue from 'vue' import App from './App.vue' import VueRouter from 'vue-router' import router from './router'
Vue.config.productionTip = false Vue.use(VueRouter)
new Vue({ el:"#app", render: h => h(App), router })
|
src/App.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <template> <div> <div class="row"> <div class="col-xs-offset-2 col-xs-8"> <div class="page-header"><h2>Vue Router Demo</h2></div> </div> </div> <div class="row"> <div class="col-xs-2 col-xs-offset-2"> <div class="list-group"> <!-- 原始html中我们使用a标签实现页面跳转 --> <!-- <a class="list-group-item active" href="./about.html">About</a> <a class="list-group-item" href="./home.html">Home</a> --> <!-- Vue中借助router-link标签实现路由的切换 --> <router-link class="list-group-item" active-class="active" to="/about"> About </router-link> <router-link class="list-group-item" active-class="active" to="/home"> Home </router-link> </div> </div> <div class="col-xs-6"> <div class="panel"> <div class="panel-body"> <!-- 指定组件的呈现位置 --> <router-view></router-view> </div> </div> </div> </div> </div> </template>
<script> export default { name:'App', } </script>
|
src/components/Home.vue
:
1 2 3 4 5 6 7 8 9 10
| <template> <h2>我是Home组件的内容</h2> </template>
<script> export default { name:'Home' } </script> 123456789
|
src/components/About.vue
:
1 2 3 4 5 6 7 8 9 10
| <template> <h2>我是About组件的内容</h2> </template>
<script> export default { name:'About' } </script> 123456789
|
总结:
安装vue-router
,命令:npm i vue-router
应用插件:Vue.use(VueRouter)
编写router配置项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import VueRouter from 'vue-router'
import About from '../components/About' import Home from '../components/Home'
const router = new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] })
export default router
|
实现切换(active-class
可配置高亮样式):
1 2
| <router-link active-class="active" to="/about">About</router-link> 1
|
指定展示位:<router-view></router-view>
几个注意事项
- 路由组件通常存放在
pages
文件夹,一般组件通常存放在components
文件夹
- 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息
- 整个应用只有一个router,可以通过组件的
$router
属性获取到
多级路由
src/pages/Home.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| <template> <div> <h2>Home组件内容</h2> <div> <ul class="nav nav-tabs"> <li> <router-link class="list-group-item" active-class="active" to="/home/news"> News </router-link> </li> <li> <router-link class="list-group-item" active-class="active" to="/home/message"> Message </router-link> </li> </ul> <router-view></router-view> </div> </div> </template>
<script> export default { name:'Home' } </script>
|
src/pages/News.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <ul> <li>news001</li> <li>news002</li> <li>news003</li> </ul> </template>
<script> export default { name:'News' } </script>
|
src/pages/Message.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <ul> <li> <a href="/message1">message001</a> </li> <li> <a href="/message2">message002</a> </li> <li> <a href="/message/3">message003</a> </li> </ul> </template>
<script> export default { name:'Message' } </script>
|
src/router/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import VueRouter from "vue-router";
import Home from '../pages/Home' import About from '../pages/About' import News from '../pages/News' import Message from '../pages/Message'
export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News }, { path:'message', component:Message } ] } ] })
|
效果:
在Home的组建中又建立了路由指向了News与Message
配置路由规则,使用children配置项:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ { path:'news', component:News }, { path:'message', component:Message } ] } ]
|
跳转(要写完整路径):<router-link to="/home/news">News</router-link>
路由的query参数
src/router.index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import VueRouter from "vue-router";
import Home from '../pages/Home' import About from '../pages/About' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail'
export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News }, { path:'message', component:Message, children:[ { path:'detail', component:Detail } ] } ] } ] })
|
src/pages/Detail.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <ul> <li>消息编号:{{$route.query.id}}</li> <li>消息标题:{{$route.query.title}}</li> </ul> </template>
<script> export default { name:'Detail' } </script>
|
src/pages/Message.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <template> <div> <ul> <li v-for="m in messageList" :key="m.id"> <!-- 跳转路由并携带query参数,to的字符串写法 --> <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`"> {{m.title}} </router-link> -->
<!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:m.id, title:m.title } }"> {{m.title}} </router-link> </li> </ul> <hr/> <router-view></router-view> </div> </template>
<script> export default { name:'News', data(){ return{ messageList:[ {id:'001',title:'消息001'}, {id:'002',title:'消息002'}, {id:'003',title:'消息003'} ] } } } </script>
|
总结:
传递参数:
1 2 3 4 5 6 7 8 9 10 11 12
| <!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }">跳转</router-link>
|
接收参数:
1 2
| $route.query.id $route.query.title
|
路由命名
命名路由:
作用:可以简化路由的跳转
如何使用:
给路由命名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' path:'welcome', component:Hello, } ] } ] }
|
简化跳转:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <!--简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link>
<!--简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link>
<!--简化写法配合传递参数 --> <router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }" >跳转</router-link>
|
路由的parames参数
src/router/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import VueRouter from "vue-router";
import Home from '../pages/Home' import About from '../pages/About' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail'
export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News }, { path:'message', component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', component:Detail } ] } ] } ] })
|
src/pages/Message.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <template> <div> <ul> <li v-for="m in messageList" :key="m.id"> <!-- 跳转路由并携带params参数,to的字符串写法 --> <!-- <router-link :to="`/home/message/detail/${m.id}/${m.title}`"> {{m.title}} </router-link> -->
<!-- 跳转路由并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:m.id, title:m.title } }"> {{m.title}} </router-link> </li> </ul> <hr/> <router-view></router-view> </div> </template>
<script> export default { name:'News', data(){ return{ messageList:[ {id:'001',title:'消息001'}, {id:'002',title:'消息002'}, {id:'003',title:'消息003'} ] } } } </script>
|
src/pages/Detail.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <ul> <li>消息编号:{{$route.params.id}}</li> <li>消息标题:{{$route.params.title}}</li> </ul> </template>
<script> export default { name:'Detail' } </script>
|
总结:
配置路由,声明接收params
参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| { path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', component:Detail } ] } ] }
|
传递参数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>
|
特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!
接收参数:
1 2
| $route.params.id $route.params.title
|
params传递参
注:使用params传参只能使用name进行引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| this.$router.push({ name:'second', params: { id:'20180822', name: 'query' } })
this.id = this.$route.params.id ; this.name = this.$route.params.name ;
{ path: '/second/:id/:name', name: 'second', component: () => import('@/view/second') }
|
querys与parames区别
- 传参可以使用params和query两种方式。
- 使用params传参只能用name来引入路由,即
:to
里面只能是name:’xxxx’,不能是path:’/xxx’,因为params只能用name来引入路由,如果这里写成了path,接收参数页面会是undefined!!!。
- 使用query传参使用path来引入路由。
- params是路由的一部分,必须要在路由后面添加参数名。query是拼接在url后面的参数,没有也没关系。
- 二者还有点区别,直白的来说query相当于get请求,页面跳转的时候,可以在地址栏看到请求参数,而params相当于post请求,参数不会再地址栏中显示。
路由的props配置
src/pages/Message.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| <template> <div> <ul> <li v-for="m in messageList" :key="m.id"> <router-link :to="{ name:'xiangqing', params:{ id:m.id, title:m.title } }"> {{m.title}} </router-link> </li> </ul> <hr/> <router-view></router-view> </div> </template>
<script> export default { name:'News', data(){ return{ messageList:[ {id:'001',title:'消息001'}, {id:'002',title:'消息002'}, {id:'003',title:'消息003'} ] } } } </script>
|
src/router/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import VueRouter from "vue-router";
import Home from '../pages/Home' import About from '../pages/About' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail'
export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News }, { path:'message', component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', component:Detail,
props($route){ return { id:$route.params.id, title:$route.params.title, } } } ] } ] } ] })
|
src/pages/Detail.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <ul> <li>消息编号:{{id}}</li> <li>消息标题:{{title}}</li> </ul> </template>
<script> export default { name:'Detail', props:['id','title'] } </script>
|
总结:
作用:让路由组件更方便的收到参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| { name:'xiangqing', path:'detail/:id', component:Detail,
props(route){ return { id:route.query.id, title:route.qu }
|
路由跳转的replace方法
src/pages/Home.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <div> <h2>Home组件内容</h2> <div> <ul class="nav nav-tabs"> <li> <router-link replace class="list-group-item" active-class="active" to="/home/news">News</router-link> </li> <li> <router-link replace class="list-group-item" active-class="active" to="/home/message">Message</router-link> </li> </ul> <router-view></router-view> </div> </div> </template>
<script> export default { name:'Home' } </script>
|
总结:
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:
push
和replace
,其中push
是追加历史记录,replace
是替换当前记录。路由跳转时候默认为push
方式.使用replace方式不能使用回退返回上一界面
- 开启
replace
模式:<router-link replace ...>News</router-link>
编程式路由导航
src/components/Banner.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| <template> <div class="col-xs-offset-2 col-xs-8"> <div class="page-header"> <h2>Vue Router Demo</h2> <button @click="back">后退</button> <button @click="forward">前进</button> <button @click="test">测试一下go</button> </div> </div> </template>
<script> export default { name:'Banner', methods:{ back(){ this.$router.back() }, forward(){ this.$router.forward() }, test(){ this.$router.go(3) } }, } </script>
|
src/pages/Message.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <template> <div> <ul> <li v-for="m in messageList" :key="m.id"> <router-link :to="{ name:'xiangqing', params:{ id:m.id, title:m.title } }"> {{m.title}} </router-link> <button @click="showPush(m)">push查看</button> <button @click="showReplace(m)">replace查看</button> </li> </ul> <hr/> <router-view></router-view> </div> </template>
<script> export default { name:'News', data(){ return{ messageList:[ {id:'001',title:'消息001'}, {id:'002',title:'消息002'}, {id:'003',title:'消息003'} ] } }, methods:{ showPush(m){ this.$router.push({ name:'xiangqing', query:{ id:m.id, title:m.title } }) }, showReplace(m){ this.$router.replace({ name:'xiangqing', query:{ id:m.id, title:m.title } }) } } } </script>
|
src/router/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| import VueRouter from "vue-router";
import Home from '../pages/Home' import About from '../pages/About' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail'
export default new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home, children:[ { path:'news', component:News }, { path:'message', component:Message, children:[ { name:'xiangqing', path:'detail', component:Detail, props($route){ return { id:$route.query.id, title:$route.query.title, } } } ] } ] } ] })
|
src/pages/Detail.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <ul> <li>消息编号:{{id}}</li> <li>消息标题:{{title}}</li> </ul> </template>
<script> export default { name:'Detail', props:['id','title'] } </script>
|
效果:
总结:
作用:不借助<router-link>
实现路由跳转,让路由跳转更加灵活
具体编码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } })
this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.forward() this.$router.back() this.$router.go()
|
this.$router.go(val) 介绍:
在history记录中前进或者后退val步,
1 2 3 4 5 6 7 8
| - 当val为-1时回到上一页。 - 当val为0时刷新当前页面。 - 当val为1到下一页。
this.$router.go(1) this.$router.go(-1)
|
this.$router.push(path) 介绍:
1 2 3
| - 跳转到指定URL,向history栈添加一个新的记录; - 点击后退会返回至上一个页面
|
举例:
1 2 3 4 5
| this.$router.push('/index') this.$router.push({path:'/index'}) this.$router.push({path:'/index',query:{name: '123'}}) this.$router.push({name:'index',params:{name:'123'}})
|
this.$router.replace介绍:
跳转到指定URL,替换history栈中最后一个记录,点击后退会返回至上上一个页面
1
| this.$router.replace(path);
|
缓存路由组件
src/pages/News.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <ul> <li>news001 <input type="text"></li> <li>news002 <input type="text"></li> <li>news003 <input type="text"></li> </ul> </template>
<script> export default { name:'News' } </script>
|
src/pages/Home.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| <template> <div> <h2>Home组件内容</h2> <div> <ul class="nav nav-tabs"> <li> <router-link replace class="list-group-item" active-class="active" to="/home/news">News</router-link> </li> <li> <router-link replace class="list-group-item" active-class="active" to="/home/message">Message</router-link> </li> </ul> <keep-alive include="News"> // include中的组件在重新打开时会保留上次的数据 <router-view></router-view> </keep-alive> </div> </div> </template>
<script> export default { name:'Home' } </script>
|
总结:
作用:让不展示的路由组件保持挂载,不被销毁
具体编码:
1 2 3 4 5 6 7 8 9
| //缓存一个路由组件 <keep-alive include="News"> //include中写想要缓存的路由组件名,不写表示全部缓存 <router-view></router-view> </keep-alive>
//缓存多个路由组件 <keep-alive :include="['News','Message']"> <router-view></router-view> </keep-alive>
|
activated和deactivated
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| `src/pages/News.vue`:
<template> <ul> <li :style="{opacity}">欢迎学习vue</li> <li>news001 <input type="text"></li> <li>news002 <input type="text"></li> <li>news003 <input type="text"></li> </ul> </template>
<script> export default { name:'News', data(){ return{ opacity:1 } }, activated(){ console.log('News组件被激活了') this.timer = setInterval(() => { this.opacity -= 0.01 if(this.opacity <= 0) this.opacity = 1 },16) }, deactivated(){ console.log('News组件失活了') clearInterval(this.timer) } } </script>
|
总结:
activated
和deactivated
是路由组件所独有的两个钩子,用于捕获路由组件的激活状态
具体使用:
activated
路由组件被激活时触发
deactivated
路由组件失活时触发
只有在缓存的组件上能使用这两个周期。
1 2 3 4
| <keep-alive include="Home"> <router-view name="homeIndex"></router-view> <router-view></router-view> </keep-alive>
|
多个router-view,在component中添加指定的key
(23条消息) Vue之多个router-view应用_router-view 多个_Radom7的博客-CSDN博客
路由守卫
全局路由守卫
meta:路由元信息:router提供的一个容器,可以设置我们自己设置的信息。
src/router/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| import VueRouter from "vue-router";
import Home from '../pages/Home' import About from '../pages/About' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail'
const router = new VueRouter({ routes:[ { name:'guanyv', path:'/about', component:About, meta:{title:'关于'} }, { name:'zhuye', path:'/home', component:Home, meta:{title:'主页'}, children:[ { name:'xinwen', path:'news', component:News, meta:{isAuth:true,title:'新闻'} }, { name:'xiaoxi', path:'message', component:Message, meta:{isAuth:true,title:'消息'}, children:[ { name:'xiangqing', path:'detail', component:Detail, meta:{isAuth:true,title:'详情'}, props($route){ return { id:$route.query.id, title:$route.query.title, } } } ] } ] } ] })
router.beforeEach((to,from,next) => { console.log('前置路由守卫',to,from) if(to.meta.isAuth){ if(localStorage.getItem('school')==='atguigu'){ next() }else{ alert('学校名不对,无权限查看!') } }else{ next() } })
router.afterEach((to,from)=>{ console.log('后置路由守卫',to,from) document.title = to.meta.title || '硅谷系统' })
export default router
|
独享路由守卫
src/router/index.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| import VueRouter from "vue-router";
import Home from '../pages/Home' import About from '../pages/About' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail'
const router = new VueRouter({ routes:[ { name:'guanyv', path:'/about', component:About, meta:{title:'关于'} }, { name:'zhuye', path:'/home', component:Home, meta:{title:'主页'}, children:[ { name:'xinwen', path:'news', component:News, meta:{title:'新闻'}, beforeEnter(to,from,next){ console.log('独享路由守卫',to,from) if(localStorage.getItem('school') === 'atguigu'){ next() }else{ alert('暂无权限查看') } } }, { name:'xiaoxi', path:'message', component:Message, meta:{title:'消息'}, children:[ { name:'xiangqing', path:'detail', component:Detail, meta:{title:'详情'}, props($route){ return { id:$route.query.id, title:$route.query.title, } } } ] } ] } ] })
router.afterEach((to,from)=>{ console.log('后置路由守卫',to,from) document.title = to.meta.title || '硅谷系统' })
export default router
|
组件内路由守卫
src/pages/About.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <h2>我是About组件的内容</h2> </template>
<script> export default { name:'About', //通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { console.log('About--beforeRouteEnter',to,from) if(localStorage.getItem('school')==='atguigu'){ next() }else{ alert('学校名不对,无权限查看!') } }, //通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { console.log('About--beforeRouteLeave',to,from) next() } } </script>
|
总结:
作用:对路由进行权限控制
分类:全局守卫、独享守卫、组件内守卫
全局守卫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ if(localStorage.getItem('school') === 'atguigu'){ next() }else{ alert('暂无权限查看') } }else{ next() } })
router.afterEach((to,from) => { console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title }else{ document.title = 'vue_test' } }) 1234567891011121314151617181920212223
|
独享守卫:
1 2 3 4 5 6 7 8 9
| beforeEnter(to,from,next){ console.log('beforeEnter',to,from) if(localStorage.getItem('school') === 'atguigu'){ next() }else{ alert('暂无权限查看') } } 12345678
|
组件内守卫:
1 2 3 4
| beforeRouteEnter (to, from, next) {...},
beforeRouteLeave (to, from, next) {...},
|
路由的两种工作模式
对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值 eg:localhost:8080/#/home
hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器
hash模式:
- 地址中永远带着#号,不美观
- 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法
- 兼容性较好
history模式:
地址干净,美观
兼容性和hash模式相比略差
应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题(前端请求静态资源时会将路径识别为网络请求传给后端,后端找不到该请求路径,出现404错误)
解决方案一:使用nginx分辨是前端路由还是后端路由
使用node.js的中间件connect-history-api-fallback
1 2 3 4
| 路由的两种模式切换: 在路由的配置中添加属性: mode:"history" //为history mode:"hash" //为hash
|
应用打包上线:
- 使用
npm run build
将编写号的Vue文件转为纯粹的html、hs、css资源放在dist文件夹下。
- 部署:
- 新建一个文件夹,使用
npm init
命令初始化项目生成pakeage.json文件(它会将我们在项目开发中所要用到的包,以及项目的详细信息等记录在这个项目中)
- 使用
npm i express
安装Express框架(Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。)
- 新建一个static文件夹,将打包好的dist文件下的内容复制好
- 新建一个服务器文件server.js
1 2 3 4 5 6 7 8 9 10 11 12 13
| const express = require('express')
const app = express()
app.use(express.static(__dirname+'/static')) // 指定静态资源
app.listen(5005,(err=>{ if(!errr) { console.log("服务器启动成功") } }))
|
Vue UI组件库
常用UI组件库
移动端常用UI组件库
- Vant
- Cube UI
- Mint UI
PC端常用UI组件库
- Element UI
- IView UI
element-ui基本使用
安装 element-ui:npm i element-ui -S
src/main.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import Vue from 'vue' import App from './App.vue'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.config.productionTip = false
Vue.use(ElementUI)
new Vue({ el:"#app", render: h => h(App), })
|
src/App.vue
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <div> <br> <el-row> <el-button icon="el-icon-search" circle></el-button> <el-button type="primary" icon="el-icon-edit" circle></el-button> <el-button type="success" icon="el-icon-check" circle></el-button> <el-button type="info" icon="el-icon-message" circle></el-button> <el-button type="warning" icon="el-icon-star-off" circle></el-button> <el-button type="danger" icon="el-icon-delete" circle></el-button> </el-row> </div> </template>
<script> export default { name:'App', } </script>
|
element-ui按需引入
安装 babel-plugin-component:npm install babel-plugin-component -D
修改 babel-config-js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| module.exports = { presets: [ '@vue/cli-plugin-babel/preset', ["@babel/preset-env", { "modules": false }] ], plugins: [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] } 123456789101112131415
|
src/main.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import Vue from 'vue' import App from './App.vue'
import { Button,Row } from 'element-ui'
Vue.config.productionTip = false
Vue.component(Button.name, Button); Vue.component(Row.name, Row);
new Vue({ el:"#app", render: h => h(App), })
|