Vue是渐进式JavaScript框架,该笔记讲解的是Vue2基础。
Part1------Vue2基础
1、安装Vue开发环境
- 下载依赖包vue.js,并在html文件中引入;
- 关闭控制台提示"打开生产模式"
- 关闭控制台提示"安装开发者工具"
- 配置页签图标
- 尝试打印Vue构造函数(直接复制
favicon.ico
到根目录)
2、Vue初体验
js
<!-- 准备一个容器 -->
<div id='demo'>
<!-- demo容器中的代码称为模板,Vue+HTML-->
<!-- data中的数据发生改变,模板中用到的数据就会自动更新 -->
<p>我是{{stuName}},今年{{age}}岁</p>
</div>
<script>
//创建一个Vue的实例对象
//传入一个配置对象(配置对象与普通对象的区别是:键名固定)
new Vue({
//键名:el(element):用于指定当前Vue实例对象服务于哪个容器
//键值:css选择器字符串
el:'#demo',
//键名:data用于配置数据,data值可以直接在模板中使用
//键值:data值暂时是一个对象,后期会写成一个函数
data:{
stuName: 'Evelyn',
age: 22
}
})
</script>
- 容器和Vue实例是一一对应的,(一夫一妻制),每一个实例会管控一个根容器。
- 真实开发中,整个Vue项目中,只有一个Vue实例,会搭配组件一起使用。
{{xxx}}
中的xxx
要写js
表达式,且xxx
可以自动读取到data
中的属性。
语句和表达式:
表达式:表达式会产生值,可以放在任何一个需要值的地方(let+变量能接收值)
如:Number类型、String类型、三元表达式、map循环、加减乘除运算
语句/代码:不会产生值,只是控制代码走向
如:if...else判断、for循环、try...catch捕获异常
【注】如果实例管控的根容器,使用了实例中data没有定义的属性,
3、模板语法
在Vue世界中,有两种模板语法:插值语法和指令语法。
- 插值语法:专用于操作标签体 ,也称为胡子语法
{{ ... }}
- 指令语法:专用于操作标签(标签体、标签属性、标签事件、循环标签)
3-1 插值语法
<div a='1+1' v-bind:b='1+1'></div>
属性值结果:a='1+1' b='2'
3-2 指令语法
(1) v-bind
v-bind:专用于操作标签属性v-bind:标签属性="变量读取的表达式"
v-bind的简写形式:<a :href="url">去百度</a>
(2) v-model
v-model:用于实现数据的双向绑定,双向绑定只能用于输入/表单类元素,如:input、CheckBox、radio这些输入类表单(可以交互)v-model:value='xxx'
,
v-model:简写形式:`v-model='xxx'
4、数据绑定
Vue中有两种绑定数据的方式:
- 单向数据绑定(
v-bind
):数据只能从data
流向页面。 - 双向数据绑定(
v-model
):数据不仅能从data
流向页面,也能从页面流向data
【注】:插值语法、v-bind都是单向绑定,v_model是双向绑定。
html
<div id="root">
单向绑定:<p>我是{{stuName}}</p>
单向绑定:<a v-bind:href="url">去百度</a>
双向绑定:<input type="text" v-model:value="slogan">
单向绑定的简写:<a :href="url">去百度</a>
双向绑定的简写:<input type="text" v-model="address">
</div>
<script>
new Vue({
el: '#root',
data: {
stuName: 'Evelyn',
url: 'https://www.baidu.com',
slogan: '巴啦啦能量',
address: '魔仙堡'
}
})
</script>
5、el的三种写法:
js
//1. 值为css选择器字符串
//2. 值为DOM元素(了解)
//3. 如下:
new Vue({
data:{ ... }
}).$mount('#demo')
6、data的两种写法
js
//1、data值为对象
//2、data值为函数
new Vue({
el:'#demo',
//对象中方法的简写(不要写成箭头函数)
data(){
//返回一个对象
return {
stuName:'Evelyn'
}
}
})
7、软件设计模式------MVVM
M
:Model(模型)V
:View(视图)VM
:ViewModel(视图模型)
【注】M其实就是数据,V是页面,VM是桥梁
【注】因此常用view model的缩写vm这个变量,表示Vue的实例
8、vm对象
vm是Vue的实例对象,是通过new Vue({...})得到的!
VM身上的属性:
- $... 给程序员使用
- _... 底层使用
- data中的数据(键名)
在模板里面可以写什么?VM身上以及沿着原型链身上的(vm------>Vue------>Object)
9、vue2响应式原理 ------ Object.defineProperty
vue2之所以能够实现响应式(数据变化影响页面变化,数据驱动视图渲染),是由
Object.defineProperty
实现的。
Object.defineProperty
用于给一个对象追加属性,且可以进行高级定制。
- get方法,也称为getter,要有返回值;
getter何时调用? ------ 要读取某一个值的时候,调用getter(getter中的this指向当前操作的对象)
- set方法,也称为setter;
setter何时调用?------ 修改/设置对象的;
js
let p1 = {}
//'属性名称',要加引号
Object.defineProperty(p1 , ' age ' , {
value : 22 ,
enumerable : true , //控制属性是否可以被枚举
writable : true , //控制属性是否可以被修改
configurable : true , //控制属性是否可以被删除
// get函数(getter)中的this是谁? ------ 当前对象(person)
get : function get(){
console.log('getter执行了' , this)
return number
},
// set函数(setter)何时会被调用? ------ 有人修改person对象的age属性时执行
set : function set(value){
console.log('有人修改了person的age属性,值为:' , value , this)
number = value
}
})
delete p1.age;
console.log(p1.age);
10、Vue中的数据代理⭐
实例对象vm身上的_data属性
{name:''}
,原本访问vm._data.name
,通过数据代理,可以直接访问Vue实例对象,进而访问其身上的属性。
11、Vue中的事件绑定 v-on
Vue中使用
v-on:click=事件回调名称
,可简写:@事件回调名称=clickHandler
【注】:methods 中的方法:一定要写成普通函数
,只有这样this才指向vm,实现事件中能使用data 由vue管理的函数,都要写成普通函数!!!
12、Vue中的事件传参⭐
- 1、不传参(回调之后不加括号)
@click="clickHandler"
事件回调中会收到一个事件对象参数event。
- 2、传1个/多个参数
@click="clickHandler(100 , 200 , ...)"
事件回调中会收到一个/多个参数(100,200, ...)
- 3、传参+事件对象参数
@click="clickHandler(100,$event)"
事件回调中,可以收到事件对象参数,也可以收到传递过来的其他参数。
- 4、传递的参数也可以是data中的数据
@click="clickHandler(name)"
注意:在Vue中模板数据,不需要写this。
13、事件修饰符
- 1、prevent 阻止默认行为(如:阻止表单中按钮的默认提交);
- 2、stop 阻止事件冒泡;
- 3、once 事件只能触发一次;
【注】:修饰符可以串联 @click.prevent.stop='事件'
14、键盘事件
@keyup.enter='事件名'
【注】:(容器)模板中的内容已经在读取vm了,所以不需要再添加this。但是script标签中不能直接找,否则会以为是变量,因此要借助于this!
15、计算属性
计算属性是指:要用的数据/属性不存在,要根据已有数据/属性计算得来。
【注】计算属性底层是通过
Objcet.defineProperty
实现的(getter和setter)【注】计算属性的灵魂是"return"
15-1 前言案例------获取全名
不使用计算属性,在methods中实现获取全名
js
<div id="demo">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
全名:<span>{{getFullName()}}</span><br>
全名:<span>{{getFullName()}}</span><br>
全名:<span>{{getFullName()}}</span><br>
全名:<span>{{getFullName()}}</span><br>
全名:<span>{{getFullName()}}</span><br>
全名:<span>{{getFullName()}}</span><br>
全名:<span>{{getFullName()}}</span><br>
<p>计数器 <span>{{num}}</span></p>
<button @click="add">+10</button>
</div>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
firstName: 'song',
lastName: 'Eveyln',
num: 100
},
methods: {
getFullName() {
console.log('@')//测试方法调用
return this.firstName + this.lastName
},
add() {
this.num += 10;
}
}
})
</script>
【存在问题】:
- 没有缓存,多次使用同样的值,函数会执行多次,效率不高。
- 无关数据若发生变化,也会导致方法执行。(如案例中的num属性变化)
15-2 获取全名案例------计算属性实现
js
<div id="demo">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
全名:<span>{{fullName}}</span>
<button @click="changeFullName">修改fullName</button>
</div>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
firstName: 'song',
lastName: 'Evelyn'
},
methods: {
changeFullName() {
this.fullName = '宋威龙'
}
},
computed: {
fullName: {
// getter中的this是谁?vm
// getter何时调用?
// 1.初次读取
// 2.计算fullName属性时所依赖的数据发生变化时
get() {
console.log('getter被调用...')
return this.firstName.slice(0, 1).toUpperCase() + this.firstName.slice(1) + '-' + this.lastName
},
// setter中的this是谁?vm
// setter何时调用?
// 当且仅当fullName被修改时(直接给fullName赋值,而不是他所依赖的)
set(value) {
console.log(value)//宋威龙
console.log('setter被调用...')
}
}
}
})
</script>
【注】:通过在输入框修改firstName和lastName的方式,从而间接修改fullName的方式,不会调用setter,必须是将fullName=...,直接重新的赋值才算是修改fullName,进而可触发setter。
15-3 计算属性的简写
只有当计算属性不会被修改,(不需要
set
函数时),才能用简写形式。
【注】:如果在简写形式中,计算属性使用了v-model双向数据绑定,如果在表单中修改内容,会报错,因为没有setter,如姓名案例中直接修改fullName=...
js
computed: {
// 计算属性fullName是一个属性,fullName不是一个函数,只是简写形式,而是指返回值
fullName() {
console.log('getter被调用...')
return this.firstName.slice(0, 1).toUpperCase() + this.firstName.slice(1) + '-' + this.lastName
},
}
16、监视属性
监视属性又称侦听器 ,其作用是:当被监视的属性变化时, 回调函数
handler
将会自动调用。
- Vue底层本身就有监视数据的功能,数据变,页面上用到数据的地方就会自动更新(数据驱动视图);
- Vue给程序员也提供了监视配置,用于让程序员在数据变化时,执行更多的逻辑。
js
new Vue({
watch:{
//要监视的属性名
isHot:{
//1.handler何时执行?当被监视的属性变化时, 回调函数handler自动调用
//2.handler中this指向?vm
handler(newValue , oldValue){
//第一个形参:newValue,现在的值
//第二个形参:oldValue
}
}
}
})
- 被监视的可以是data数据/属性,也可以是计算属性/数据;
- 监视的属性必须存在,才能进行监视,若监视了不存在的属性,也不会报错!
16-1.立即监视
Vue初次渲染时,数据还没有发生变化,就调用一下监视的回调函数
handler
配置:
immediate:true
16-2.深度监视(不常用)
配置深度监视可以监测对象内部属性的改变(对象嵌套很深时要用到)
配置:
deep:true
html
<div id="demo">
<ul>
<li>{{Person.name}}</li>
<li>{{Person.age}}</li>
<li>{{Person.mood}}</li>
</ul>
<button @click="changeName">修改姓名</button>
<button @click="changePerson">修改Person整体</button>
</div>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
Person: {
name: 'Evelyn',
age: 22,
mood: 'nice'
},
},
methods: {
changeName() {
this.Person.name = 'Evelynnnnn'
},
changePerson() {
this.Person = {
name: '宋威龙',
age: 23,
mood: 'just so so'
}
}
},
watch: {
Person: {
deep: true,
handler(newValue, oldValue) {
// Vue中存在的问题,如果监视的是对象整体,那么新值旧值打印出来就是一样的
// 对象,引用数据类型,地址值不变(不是因为浏览器的惰性加载)
// oldValue,该参数值基本不用
console.log('新值', newValue)
console.log('旧值', oldValue)
console.log('handler执行...')
}
}
}
})
</script>
如果监视的是基本数据类型, 如果监视的是引用数据类型,要的是对象地址值变化,而不是对象中的某个属性发生变化
16-3 监视的简写(用得多)
当不需要【立即监视】、【深度监视】的时候,才可以用简写形式。
html
<!-- 准备好一个容器-->
<div id="demo">
姓:<input type="text" v-model="firstName">
名:<input type="text" v-model="lastName">
全名:<span>{{fullName}}</span>
</div>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
firstName: 'Evelyn',
lastName: 'Song',
fullName: '',
},
watch: {
//需要深度监视,因此不能采用简写形式
firstName: {
immediate: true,
handler(value) {
this.fullName = value.slice(0, 1).toUpperCase() + value.slice(1) + this.lastName
}
},
lastName(value) {
this.fullName = this.firstName.slice(0, 1).toUpperCase() + this.firstName.slice(1) + value
}
}
})
</script>
17、总结:computed 和 watch⭐
computed
- 侧重在【算】,核心是:计算出来的值。
- 靠
return
来输出计算的结果。 - 不能异步计算。
watch
- 侧重在【监视】,核心是:
xxx
变了,我要做???
事。 - 无需
return
,核心是内部的逻辑。 - 能开启异步任务。
computed能完成的功能,watch都能完成,但computed会更简便,更语义化;
watch能完成的功能,computed不一定能完成(如异步功能的实现);
【注】:watch能实现异步,computed不能实现异步。因为计算属性中最关键的是return(异步任务中通常有回调函数,比如定时器、网络请求)(return是同步代码,异步代码的运行结果还没执行完毕,已经return完毕了)
js
// 监视属性,实现异步
watch: {
firstName: {
// immediate: true,
handler(value) {
setTimeout(() => {
this.fullName = value.slice(0, 1).toUpperCase() + value.slice(1) + this.lastName
}, 1000)
}
},
lastName(value) {
setTimeout(() => {
this.fullName = this.firstName.slice(0, 1).toUpperCase() + this.firstName.slice(1) + value
}, 1000)
}
}
// 尝试使用计算属性,实现异步
computed: {
fullName() {
// 达咩!
// 计算属性最核心的是return
let str = ''
setTimeout(() => {
// 定时器函数,返回值是timerID
str = this.firstName.slice(0, 1).toUpperCase() + this.firstName.slice(1) + this.lastName
}, 1000)
return str;
// 同步任务优先执行,模板中读取计算属性,str读取都是'',计算属性才返回
}
}
18、Vue中函数的使用原则
【原则】:由Vue管理的函数写
普通
,不由Vue管理的函数写箭头
。【目的】:遵循以上原则的目的是:让this指向vm!!!
【注】:因为Vue管理的普通函数,写在Vue实例中,this指向Vue实例对象------vm;而箭头函数没有自己的this,往外找,在其声明环境中(函数/块级作用域中,对象不能算作作用域),正好是vm管理的普通函数的this指向。
【注】:Vue管理的函数是指:data中的函数、methods中的函数、computed中的函数、watch中的函数...
js
new Vue({
el: '#demo',
data: {
num: 100
},
methods: {
asyncAdd() {
console.log(this) // Vue的实例对象,Vue{}
setTimeout(() => {
//箭头函数没有自己的this指向
//往外看,就是asyncAdd函数所在的环境
console.log(this)// Vue的实例对象,Vue{}
this.num += 100;
}, 1000)
}
}
})
19、条件渲染
(1)v-show
语法:v-show="表达式"
如何隐藏掉不展示的元素:style="display: none;"
(2)v-if
v-if条件判断,如果不满足条件,展示了一个空的注释<!-- -->
(开发版本中) v-if
v-else-if
v-else
不允许被打断
【区别】:v-show处理的是display样式,v-if处理的是创建DOM元素,所以,切换频繁,真实开发中,往往使用v-if解决问题,使用v-show解决某个DOM元素时有时无。
- v-show:设置隐藏是给绑定的DOM元素添加CSS样式:display:none,但是DOM元素仍然存在;
- v-if:设置隐藏是将DOM元素整个删除,此时DOM元素不再存在。
20、列表渲染v-for
想生成多个谁,就在谁的身上写 v-for="p in persons"
v-for所在标签,及其所有子标签
【人员搜索案例】------watch实现
html
<div id="demo">
<input type="text" v-model="keyword">
<ul>
<li v-for="p in filPersons">{{p.name}}</li>
</ul>
</div>
<script type="text/javascript">
new Vue({
el: '#demo',
data: {
persons: [{ id: 1, name: '宋威龙' }, { id: 2, name: '袁小宇' }, { id: 3, name: '袁美丽' }, { id: 4, name: '宋二丽' }],
keyword: '',
filPersons: []
},
//监视属性
watch: {
//要监视的是:页面中 keyword 的变化
keyword: {
//开启立即监视,
//因为每个字符串中都包含有空字符
immediate: true,
handler(value) {
console.log(value)
//数组的filter方法,返回一个新数组
this.filPersons = this.persons.filter(item => {
// 字符串的includes方法,返回值是一个布尔值
return item.name.includes(value)
})
}
}
}
})
</script>
【人员搜索案例】------computed实现(easier!)
js
new Vue({
el: '#demo',
data: {
persons: [{ id: 1, name: '宋威龙' }, { id: 2, name: '袁小宇' }, { id: 3, name: '袁美丽' }, { id: 4, name: '宋二丽' }],
keyword: ''
},
computed: {
// 要计算的是展示在页面上的人员数组------filPersons
filPersons() {
//计算属性一定要有return
//他返回的是经过keyword过滤后的数组
return this.persons.filter(item => {
return item.name.includes(this.keyword)
})
}
}
})
【注】:任何一个字符串中都包含空字符串(includes和indexOf)
21、其他指令(很少用)
(1)v-text
作用:向其所在的节点中渲染文本内容。
与插值语法的区别:v-text
会替换掉节点中的内容,{{xx}}
则不会。
(2)v-html
作用:向指定节点中渲染包含
html
结构的文本。
与插值语法的区别:
v-html
会替换掉节点中所有的内容,{{xx}}
则不会。v-html
可以识别html
结构。 【注】v-html
存在一些安全性问题,因为结构中很有可能包含恶意脚本。
(3)v-once
v-once
所在节点在初次动态渲染后,就视为静态内容了。- 以后数据的改变不会引起
v-once
所在结构的更新,可以用于优化性能。
(4)v-pre
跳过这个元素和它的子元素的编译过程,一般用在大量不使用
Vue
语法的结构中。
(5)v-cloak
本质是一个特殊属性,
Vue
接管容器后,会删掉v-cloak
属性。使用
css
配合v-cloak
可以解决网速慢时,页面展示出{{xxx}}
的问题。
22、数据劫持⭐
概念:前端的响应式框架(Vue、React)通常都会对数据进行劫持,这样当数据发生变化时,才能自动更新相关的视图,或执行其他逻辑。(响应式)
-
读取数据:proxyGetter------>reactiveGetter
-
修改数据:proxySetter------>reactiveSetter(改数据、更新页面)
23、总结数据代理和数据劫持(概念层面)
-
数据代理(简单,
vm
身上的那点事):-
目的:让程序员更加方便的读取、修改到
_data
中属性。 -
原理:
Object.defineProperty
。 -
体现:
vm
身上有_data
里的所有属性,且有每一个属性,都有自己的proxyGetter
、proxySetter
。
- 当修改
vm
上的属性时,该属性对应的proxySetter
就会调用,去修改_data
中对应的属性。 - 当读取
vm
上的属性时,该属性对应的proxyGetter
就会调用,去读取_data
中对应的属性。
-
-
数据劫持(
_data
里的那点事):-
目的:为了实现响应式(什么是响应式?------ 数据变页面自动更新),有了数据劫持,就可以捕获到数据的改变,进而重新解析模板,更新界面。
-
原理:
Object.defineProperty
。 -
体现:
_data
身上的每一个属性不直接给值,都变为:reactiveSetter
、reactiveGetter
形式。
- 当修改
_data
上的属性时,该属性对应的reactiveSetter
就会调用。且在reactiveSetter
中Vue会:维护数据、更新页面。 - 当读取
_data
上的属性时,该属性对应的reactiveGetter
就会调用,返回对应的值。
-
24、Vue中操作数组(Vue3解决了)⭐
在Vue2中,永远不要通过操作数组的下标来操作元素,要使用数组的7个方法(对原数组产生影响),因为Vue底层重写了这7个原生方法。
Vue中如果要更改数组中某一个元素对象,由于没有对应的getter和setter,所以不支持修改, 在Vue中修改数组,不能通过数组下标,只能通过数组中的7个原生方法。
在Vue中,调用数组原生方法,已经不是Array.prototype上的原生方法了,而是经过了Vue封装的(验证:this.arr.splice === Array.prototype.splice,结果是false)
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
25、Vue生命周期
生命周期函数,又称生命周期钩子,又称生命周期 (所有生命周期函数中的this均指向vm)
1)初始化阶段
beforeCreate
:数据代理、数据劫持之前
(该阶段无法访问到vm中的data、methods,也没有_data)
created
:完成数据代理、数据劫持
(该阶段可以访问到vm中的data、methods,以及_data)
2)挂载阶段
beforeMount
(挂载之前)
将虚拟DOM转化为真实DOM,插入页面。
mounted
(挂载完毕)⭐
该阶段:对DOM操作、开启定时器、发送ajax、订阅消息、绑定自定义事件
3)更新阶段
beforeUpdate
(更新之前)
数据更新,页面没有更新
updated
(更新完毕)
数据更新,页面随之更新
4)销毁阶段
beforeDestroy
(vm销毁之前)⭐
关闭定时器、取消订阅、解绑自定义事件
destroyed
(已销毁)
vm还在,还能打印输出,但是失去了响应式的能力(数据驱动视图)
- 哪个钩子中不能访问
data
中的数据、methods
中的方法? ------ beforeCreate - 想给
vm
上追加一些属性,最早可以在哪个钩子中操作? ------ beforeCreate data
中的数据、methods
中的方法,最早可以在哪个钩子中获取? ------ created- 哪个钩子中数据和页面其实是不同步的?------ beforeUpdate
- 常用的钩子有哪些?
mounted
: 发送ajax
请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】beforeDestroy
: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
Part2------Vue2脚手架
- Step1:全局安装webpack和webpack-cli两个包。
npm i webapck -g
,npm i webpack-cli -g
; - Step2:在你想要放置项目的目录下,使用
vue create 项目名称
,创建一个基于vue-cli脚手架的Vue项目; - Step3:记得安装less-loader,
npm i less less-loader
; 【注】:安装此插件,可以识别Vue2的语法(.vue文件有高亮)
1、组件
1-1 对组件的理解
组件化是指每一个组件中包含html、css、js这三部分构成的一个
.vue文件
,可复用。
html结构------css样式------js交互
- template------结构
- script------脚本
- style------样式
1-2 组件的基本使用
- Step1:创建/定义一个组件
Vue.extend({配置对象})
【注意】:组件中配置项data:要写成函数的形式(return),这是因为每次调用data函数
,都会在堆内存形成不同的地址空间
,既是修改属性值,也不会影响其他人读取。如某一组件调用多次,它们之间互不干扰(数据不会被污染)。
js
const 组件名 = Vue.extend({
//html结构
template:``,
//数据
data(){
return {...}
},
//交互
methods:{
show
}
})
- Step2:注册组件
增加配置项 components:{}
js
new Vue({
el : '#demo' ,
// 传入一个对象,,每一个组件都是键值对
// 键名:自定义,在调用组件时使用,一般要和定义组件同名
// 方便ES6中,键名和键值重复,对象的简写
components : { 组件名1 , 组件名2 }
})
- Step3:调用组件
【注】:最终容器/模板中什么都不写,在vm中的template配置项中调用组件。
【注】:data中以后都只写函数形式+return!!!
【注】:注册组件时用什么,调用组件时就用什么
【注】:定义组件时,强烈建议:定义组件的name
配置项,这样不管怎么注册,怎么调用,在开发者工具
中都是你定义好的name值,一般情况下,三者保持一致!
1-3 组件命名
- 一个单词组成:
- 第一种写法:(首字母小写):
user
- 第二种写法:(首字母大写):
User
------ 推荐
- 多个单词组成:
- 第一种写法:
kebab-case
命名,例如:evelyn-song
- 第二种写法:
CamelCase
命名,例如:evelynSong
【注】:如果要在容器 / 模板中调用组件,组件调用应该是双标签,不然有bug,但是一般我们要保持容器为空,将所有结构写在vm的template配置项
1-4 App组件的使用
为了减轻vm的负担,注册组件以及调用组件,都由App组件完成,boss只负责和App组件对接。
1-5 全局注册组件的使用
如果很多组件都要用到某一组件,全局注册组件,这样每次只需调用
Vue.component('组件名',组件名)
【注】:全局注册组件,要写在创建组件之后。不然会报错;
【注】:
- Vue哪来的?------ 引入来的
- VueComponent哪来的?------ Vue.extend(),方法调用时的返回值 在调用组件时,,底层会找到VueComponent构造函数,通过new VueComponent,传入我们在创建组件(Vue.extend())时的配置对象,VueCompomnent的实例对象------组件实例对象(vc),vm和vc很相似,只不过vm中有
el
配置项,他可以指定服务于哪个容器。 - 每次调用Vue.extend() ===> 生成一个VueComponent构造函数
- 每次调用组件<组件名/> ===> new VueComponent ===> 生成一个组件实例对象(vm)
1-6 Vue.extend的简写形式
省略Vue.extend(),只写里面的配置对象。
因为底层会帮我们进行判断,如果我们没有写Vue.extend(),他会先帮我们生成一个VueComponent,然后new VueComponent生成vc
2、Vue脚手架------webpack&vite(主要针对vue3)
- babel.configu.js: jsx------>js tsx------>ts,语法转换文件;
- jsconfig.json:(paths:@/配置文件路径,指的是src文件夹);
- vue.config.js:脚手架最为重要的配置文件;
- public/index.html:主入口文件;
【注1】:关闭语法检查lintOnSave:false
(在vue.config.js配置文件中);
// eslint-disbale-next-line
临时关闭下一行的语法检查;
/*eslint-disable*/
在该文件内关闭语法检查
【注2】:若修改src以外的文件,如配置文件,要重启脚手架npm run serve
。
3、局部样式 scoped
<style scoped> <style/>
只能控制该组件的样式。
【原理】:css选择器:加了一个属性选择器[data-v-..],App组件一定不加scoped属性。
【区别】:在Vue中通过scoped
关键字实现样式的模块化,而在React中需要手动创建xx.module.css
文件来实现样式模块化。
4、ref属性
标识一个标签,获取该标签DOM元素,是id的代替者。
获取该DOM元素时,采用this.$refs.xxx
。
5、动态样式绑定
5-1 class类样式
class类样式有三种写法,分别是:字符串、对象、数组。
js
<!-- 动态绑定class样式一 ------ 字符串写法:适用于类名不确定-->
<div class="basic" :class="str"> Evelyn Song </div>
<button @click="str='happy'"> Evelyn Song </button>
// css
.basic { ... }
.happy { ... }
<!-- 动态绑定class样式二 ------ 对象写法:适用于 个数确定,类名也确定,但不确定是否要使用-->
<div class="basic" :class=" obj "> Evelyn Song </div>
<div class="basic" :class="{ hasBg:true }"> Evelyn Song </div>
<!-- 通过点击事件,控制是否使用该样式 -->
<button @click="obj.hasBg = !obj.hasBg"> Evelyn Song </button>
// css
.hasBg { ... }
<!-- 动态绑定class样式三 ------ 数组写法:适用于个数、类名都不确定 -->
<!-- 在动态绑定class样式时,还可以使用三元表达式,动态地计算。 -->
<div :class="isActive ? '' : ''"> Evelyn Song </div>
5-2 style样式
style样式有三种写法,分别是:字符串、对象、数组。
js
<template>
<div>
<!-- 字符串写法 -->
<h2 :style="str">你好啊</h2>
<!-- 对象写法,用得巨多!!! -->
<h2 :style="obj">你好啊</h2>
<!-- 对象写法 -->
<h2 :style="arr">你好啊</h2>
</div>
</template>
<script>
export default {
name:'App',
data() {
return {
str:'color:red',
obj:{
color:'purple',
border:'1px solid black',
fontSize:'80px',
backgroundColor:'orange'
},
arr:[
{color:'purple',border:'1px solid black'},
{fontSize:'80px',backgroundColor:'orange'}
]
}
},
}
</script>
Part3------Vue2组件通信方式
1、props ⭐
props用于父传子,通过标签属性的方式,
<组件名 :key="value"/>
;Vue中,prop不能修改,但如果修改的是对象中的某;一个属性,控制台不会报错,但是不推荐!
1-1 接收props方式一 ------ 数组
props:['属性1' , '属性2' , '属性3']
1-2 接收props方式二 ------ 限制数据类型
js
props:{
name:String,
age:Number
}
1-3 接收props方式三 ------ 限制数据类型 + 配置必要性 + 指定默认值
js
props:{
name:{
type:String,
default:'Evelyn',
required:true
}
}
1-4 prop实现子传父
父给子传递一个函数,在子组件中调用函数,并传递参数。(因为不能在子组件中修改props数据,所以在子组件中调用函数,通过传参的方式,然后在父组件中真正去修改。)
2、v-model ⭐
2-1. v-model 绑定在表单元素上
为什么在表单元素上,要对v-model进行拆分?
因为双向绑定的数据,有可能是来自props,而props中的数据不能直接修改,所以要进行一个拆分,针对三种表单元素的类型:
-
(1)普通输入框 ------ 拆分为
value属性
和input事件
; -
(2)radio单选框 ------ 拆分为
checked属性
和input/change/click事件
; -
(3)checkbox复选框 ------ 拆分为
checked属性
和input/change/click事件
; -
【注】:区分input事件和change事件触发条件的区别?
- change:输入框内容发生变化 + 输入框失去焦点 ==> 触发
- input :输入框内容发生变化 ==> 触发
2-2. v-model 绑定在自定义组件上
3、自定义事件
自定义事件是实现子组件给父组件传递数据。 触发:this.emit ,解绑:this.off
【注1】:自定义事件和props子传父是不一样的!!!
自定义事件
------在父组件中,给子组件身上绑定一个自定义事件<Child @send-toy="getToy"/>
,然后在子组件中触发事件,并传递数据,在父组件中设置回调,接收数据。props
------ 是通过标签属性传递一个函数,这个函数要在子组件中调用。
【注2】: 写在标签上的是原生DOM事件,写在组件上的就是自定义事件; 而内置事件是靠鼠标键盘等触发的,自定义事件是靠$emit触发的。
【注3】:如果非要在组件上绑定原生DOM事件(点击事件),需要使用native
修饰符,并且会把原生事件加在组件最外侧元素上。
【注4】:官方推荐,自定义事件命名方式,要写成串式
,比如:@send-data
形式。
4、全局事件总线
总线适用于任意组件间通信,但是总线一般用于两个组件之间的临时通信。
- Step1:安装总线
js
new Vue({
beforeCreate() {
Vue.prototype.$bus = this
}
})
-
Step2:使用总线
- 需要数据的一方,绑定自定义事件,留下回调。
jsmethods: { test(value){ // 根据需求,编写收到数据后的逻辑。。。 } }, mounted() { // 给$bus绑定事件并指定事件的回调,只要$bus的xxx事件被触发,test方法就会调用。 this.$bus.$on('xxxx',this.test) }
- 提供 数据的一方,触发自定义事件
this.$bus.$emit('xxx',数据)
-
Step3:解绑自定义事件(最好在
beforeDestroy
钩子中,用$off
去解绑当前组件所用到的事件,否则会导致$bus
很"重"!)
5、自定义指令
自定义指令,目前项目应用是按钮级别的权限,后续补充。。。
- 局部自定义指令
新增配置项,directives:{ 指令名 : 回调名 }
- 全局自定义指令
js
// 在main.js中全局注册自定义指令v-permissionbtn
app.directive('permissionbtn', (el, binding) => {
// el表示是当前绑定该自定义指令的元素,可以直接操作DOM元素
// binding是一个对象
if ( !userInfoStore.userInfo.buttons.includes(binding.value) ) {
el.remove()
}
})
6、插槽
以前,在父组件中调用子组件,直接调用即可
<Child></Child>
,子组件开始标签和结束标签之间的内容,不会被显示出来。但是使用插槽之后,是由父组件决定子组件中的模板结构,<Child> ...HTML结构... </Child>
,在子组件中需要使用<slot></slot>
标签开一个槽位(占位作用)。插槽分为默认插槽、具名插槽、作用域插槽。
6-1 默认插槽
js
// 父组件中
<Child>
<!-- 默认插槽 -->
<div>
<h2>todos列表</h2>
<ul>
<li v-for="item in 父组件data数据" :key="item.id">{{ item.title }}</li>
</ul>
</div>
</Child>
// 子组件中
<slot></slot>
6-2 具名插槽
当存在多个插槽时,为了区分,所以采用具名插槽加以区分。
【注】:子组件的slot标签上新增一个name属性name="xxx"
,父祖家中的组件标签中新增一个slot属性slot="xxx"
。
6-3 作用域插槽
作用域插槽,是因为子组件想要给父组件传递数据,或者说,插槽模板中的数据要由子组件决定。(需要注意的是,在作用域插槽中,最外层需要有一层template标签包裹)
js
// 父组件中,直接使用v-slot,可以接收子组件通过标签属性传递过来的数据。
<Child>
<template v-slot="{ todos }">
<ol>
<li v-for="n in todos" :key="n.id">{{n.title}}</li>
</ol>
</template>
</Child>
// 子组件中(子组件中data数据)
<slot :todos="todos"></slot>
7、vuex ⭐
在Vue中实现集中式状态(数据)管理的一个
Vue
插件,专门管理(读/写)多个组件的共享状态(数据),一种重要的组件通信方式,且适用于任意组件间通信。Vuex中主要是由actions、mutations、state三大部分组成,actions中主要执行一些异步任务,只有在mutationa中才可以修改数据。
7-1 搭建Vuex环境
- Step1:安装Vuex
npm i vuex@3
(vue2中使用3版本,Vue3中使用4版本) - Step2:创建文件
/src/store/index.js
js
import Vuex from 'vuex'
import Vue from 'vue'
// 使用vuex插件
Vue.use(Vuex)
export default new Vuex.Store({
actions:{ ... },
mutations:{ ... },
state:{ ... }
})
- Step3:在
main.js
文件中导入store对象
js
import Vue from 'vue'
import App from './App.vue'
//导入仓库对象
import store from './store/index'
Vue.config.productionTip = false;
let vm = new Vue({
el: "#app",
render: h => h(App),
store
})
// 如何检查vuex安装是否成功?
// 在Vue实例对象vm、组件实例对象vc身上看是否有$store,开发者工具是否有vuex的选项卡
console.log(vm);
【注1】:npm view vuex versions
可以查看vuex包的所有版本
【注2】:关于Vuex安装版本错误
【注3】:不要在main.js文件中,配置Vue.use(Vuex)
,因为在脚手架中会有变量提升,会提升所有的import
语句。
7-2 Vuex案例练习(state------actions------mutations)
/src/components/Count.vue
文件:
js
<template>
<div id="child">
<h2>当前sum的值是:{{ this.$store.state.sum }}</h2>
<select v-model="num">
<option :value="1">1</option>
<option :value="2">2</option>
<option :value="3">3</option>
</select>
<button @click="increase">+</button>
<button @click="decrease">-</button>
<button @click="increaseOdd">sum为奇数时再加</button>
<button @click="increaseWait">等两秒再加</button>
</div>
</template>
<script>
export default {
name: "Count",
data() {
return {
num: 1,
};
},
methods: {
// 增加
increase() {
this.$store.dispatch("add", this.num);
},
// 减少
// 当actions中代码业务逻辑很简单时,可以直接在$store对象身上调用commit方法
decrease() {
this.$store.commit("DEC", this.num);
},
// sum是奇数时再加
increaseOdd() {
this.$store.dispatch("addOdd", this.num);
},
// 异步,等一秒再加
increaseWait() {
this.$store.dispatch("addWait", this.num);
},
},
};
</script>
/src/store/main.js
文件:
js
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
export default new Vuex.Store({
actions: {
add(context, value) {
// context:集装箱对象,属性值有commit,dispatch,getters,rootGetters,rootState,state
// value:表示从组件传递过来的数据,接着还来传递给mutations
context.commit('ADD', value)
},
addOdd({ state, commit }, value) {
if (state.sum % 2) {
commit('ADD', value);
}
},
addWait({ commit }, value) {
setTimeout(() => {
commit('ADD', value)
}, 1000);
}
},
mutations: {
ADD(state, value) {
state.sum += value
},
DEC(state, value) {
state.sum -= value
}
},
state: {
sum: 1
}
})
【注1】:读取vuex中的数据:this.$store.state.sum
(在模板中也要写this)
【注2】:Vue Component相当于是顾客,dispatch是点单,commit是服务员给后厨提交订单,Mutations是后厨,只有"后厨"才可以真正地修改state,state相当于是做菜的原材料;
【注3】:在mutations中,不能写任何的判断,不能写网络请求,单纯写修改数据,且只有在mutations修改数据,开发者工具才能捕捉到,所有的业务逻辑(判断、异步),都写在actions中。
【注4】:如果在actions中不涉及判断逻辑/异步任务,则可以在组件中直接this.$store.commit(...)。
7-3 getters
当state中的数据要经过处理之后再使用时,可以利用getters,但是要依赖于state(所以getters很像是Vuex层面的计算属性)
js
// 在new Vuex.Store({})配置项中多加一项:getters:{ return ... }
// state表示最新的状态数据
getters(state){
//getters也有返回值
return state.name.toUpperCase()
}
使用时:this.$store.getters.xxx
7-4 mapState和mapGetters
代码风格指南表明,模板中尽量简单,所以设计了
mapState
和mapGetters
:
- mapState:把Vuex中的
state
中的数据读出来,生成对应的计算属性- mapGetters:把Vuex中的
getters
中的数据读出来,生成对应的计算属性
【注】:mapState属性写在计算属性中,使用展开运算符computed:{ ... }
,需要注意的是!使用之前要引入,import { mapState } from 'vuex'
,使用之前,在组件中读取state中的数据this.$store.state.xxx
,使用了mapState之后,直接xxx
。
js
computed: {
// mapState------写法一------对象写法
...mapState({ sum: "sum", name: "name" }),
// mapState------写法二------数组写法(常用!!)
...mapState(["sum", "name"]),
// mapGetters------写法一------对象写法
...mapGetters({ upperName: "upperName", bigSum: "bigSum" }),
// mapGetters------写法二------数组写法
...mapGetters(["upperName", "bigSum"]),
},
7-5 命名空间(Vuex的模块化)
目的:将Vuex中的
state,actions,mutatins
进行分类,且每个分类都对应一个.js
文件。
- Step1:创建
vuex
中的user
模块:src/store/modules/user.js
,用于保存用户相关数据。
js
const actions = {
addAge({state,commit},value){
commit('ADD_AGE',value)
}
}
const mutations = {
ADD_AGE(state,value){
state.age += value
}
}
const state = {
age:18
}
export default {
namespaced:true, // 开启命名空间
actions,
mutations,
state
}
- Step2:创建
vuex
中的home
模块:src/store/modules/home.js
,用于保存主页相关的数据;
js
const actions = {
addA({state,commit},value){
commit('ADD_A',value)
}
}
const mutations = {
ADD_A(state,value){
state.a += value
}
}
const state = {
a:100,
b:200
}
export default {
namespaced:true, // 开启命名空间
actions,
mutations,
state
}
- Step3:在
src/store/index.js
中合并:home
模块、user
模块
js
import Vue from 'vue'
import Vuex,{Store} from 'vuex'
import user from './modules/user'
import home from './modules/home'
Vue.use(Vuex)
const actions = {}
const mutations = {}
const state = {}
export default new Store({
actions,
mutations,
state,
modules:{
user,
home
}
})
-
Step4:在组件中使用Vuex中的数据
...mapState(所属命名空间名称,[数据1,数据2])
js...mapState('user',['name','age']), ...mapState('home',['a','b'])
this.$store.dispatch('所属命名空间名称/action名',数据)
jsthis.$store.dispatch('user/addAge',1) this.$store.dispatch('home/addA',1)
【注】:Vue3就没有命名空间的概念了,因为pinia本身就有模块化。
Part4------Vue中的路由⭐
前端路由不同于后端路由,后端路由每次都要带着URL向服务器发请求,
前端路由,是基于SPA应用,整个项目只有一个页面,根据路由匹配加载不同的组件。