day01
1. Vue简介
一套用于构建用户界面的 渐进式框架
2. 初识Vue
2.1 搭建Vue开发环境
-
第一步:去
<a href="https://v2.cn.vuejs.org/">
Vue2官网</a>
,下载依赖包。 -
第二步:在
html
文件中引入vue.js
。<!-- 引入Vue --> <script type="text/javascript" src="../../js/vue.js"></script>
输出一下
Vue
构造函数:console.log(Vue)
,看是否配置成功。 -
第三步:编写脚本关闭生产提示。
//阻止 vue 在启动时生成生产提示 Vue.config.productionTip = false
注意:如果上述方法无法关闭,可直接操作
vue.js
修改productionTip
的值为false
。 -
第四步:安装开发者工具,打开浏览器的【插件管理】,直接拖动
.crx
文件到浏览器即可。 -
第五步:配置页签图标,直接复制
favicon.ico
到根目录即可。
2.2 一个Hello效果
- 实现效果:
-
核心代码:
<!-- 准备好一个容器 --> <div id="demo"> <h1>Hello,{{school}}</h1> </div> <script type="text/javascript" > // 创建Vue实例,并传入配置对象 new Vue({ el:'#demo', //用于指定当前Vue实例服务于哪个容器,值为CSS选择器字符串 data:{ // data用于配置数据,数据可以直接在模板中使用,值暂时写成一个对象 school:'尚硅谷' } }) </script>
-
总结:
- 想让
Vue
工作,就要创建一个Vue实例,且要传入一个配置对象。 demo
容器中的代码被称为模板,它依然符合HTML
规范,只是混入了一些特殊的Vue
语法。- el 配置:用于指定当前
Vue
实例服务于哪个容器,值为:css
选择器字符串。 - data 配置:用于配置数据,数据可以直接在模板中使用,值暂时写成一个对象(以后还会写成函数)。
- 想让
2.3 分析Hello效果
-
Vue
实例和容器是一一对应的,真实开发中一般只有一个Vue
实例,而且还会配合组件一起使用。 -
data
中的数据只要发生改变,模板中用到该数据的地方就会自动更新。 -
{{xxx}}
中的xxx
要写js
表达式,且xxx
可以自动读取到data
中的属性。 -
复习一个点:【
js
表达式】与【js
语句】-
表达式:会产生一个值,可以放在任何一个需要值的地方,例如:
(1).
1
(2).a
(3).x > = 18 ? '成年' : '未成年'
(4).200 - 2
(5).arr.map()
等等…
-
语句(代码): 不会产生值,只是控制代码走向,例如:
(1).
if ( ) {}
(2).for ( ) {}
(3).try {} catch (err) {}
等等…
-
-
相关代码:
<!-- 准备好一个容器 -->
<div id="demo">
<h1>Hello,{{school}}</h1>
<h1>Hello,{{address}}</h1>
<h1>Hello,{{school.toUpperCase()}}</h1>
<h1>Hello,{{school.toUpperCase().slice(0,2)}}</h1>
<h1>Hello,{{floor}}</h1>
<h1>Hello,{{floor > 6 ? '高楼层' : '低楼层'}}</h1>
<h1>Hello,{{1+1}}</h1>
<h1>Hello,1+1</h1>
<h1>Hello,{{floor+1}}</h1>
</div>
<!-- 创建Vue实例 -->
<script type="text/javascript" >
new Vue({
el:'#demo',
data:{
school:'atguigu',
address:'宏福科技园',
floor:19
}
})
</script>
3. 了解开发者工具
- 视角一
2. 视角二
4. Vue的模板语法
4.1 插值与指令
-
插值语法(简单):
功能:用于操作标签体,可以在标签体的指定位置插入数据。
写法:{{xxx}}
,xxx
是js
表达式,且可以自动读取到data
中配置的所有属性。 -
指令语法(复杂):
功能:用于操作标签(标签属性、标签体、绑定事件…)
举例:v-bind:href = "xxx"
,xxx 是js
表达式,且可以自动读取到data
中配置的所有属性。 -
备注:
Vue
中有很多的指令,形式都是v-???
,此处我们只是拿v-bind
举个例子。 -
务必搞懂如下代码:
<a
v-bind:href="url"
a="url"
v-bind:b="url"
c="1+1"
v-bind:d="1+1"
e="url.toUpperCase()"
v-bind:f="url.toUpperCase()"
>
点我去{{school}}学习
</a>
<script>
new Vue({
el:'#demo',
data:{
school:'尚硅谷',
url:'http://www.atguigu.com'
}
})
</script>
4.2 v-bind
的简写
v-bind:
可以简写为:
- 只有
v-bind
指令才能简写为:
别的指令不行。 - 其他的指令有其他的简写形式、但并不是所有的指令都有简写形式。
5. 数据绑定
-
Vue
中有两种绑定数据的方式:- 单向数据绑定(
v-bind
):数据只能从data
流向页面。 - 双向数据绑定(
v-model
):数据不仅能从data
流向页面,也能从页面流回data
。
- 单向数据绑定(
-
注意点:
v-model
目前只能用在输入类(表单类)元素上(以后还能用写在组件标签上)。v-model
默认收集的是value
值,所以v-model:value="xxx"
可以简写为v-model="xxx"
。
-
核心代码:
<!-- 准备好一个容器--> <div id="demo"> <h1>欢迎来到{{school}}</h1> <!-- 单向绑定:<input type="text" v-bind:value="address"> <br> --> <!-- 双向绑定:<input type="text" v-model:value="address"> --> <br> <!-- 上面的13、14行的简写形式如下 --> 单向绑定:<input type="text" :value="address"> <br> 双向绑定:<input type="text" v-model="address"> <!-- 下面这行代码是有问题的,会报错,因为v-model只能用在输入类(表单类)元素上 --> <!-- <a v-model:href="url">点我去{{school}}学习2</a> --> </div> <script type="text/javascript" > new Vue({ el:'#demo', data:{ school:'尚硅谷', url:'http://www.atguigu.com', address:'宏福科技园' } }) </script>
6. el 与 data 的写法
6.1 el 的三种写法
-
第一种写法:值为
css
选择器字符串new Vue({ el:'#demo' //****** })
-
第二种写法:值为一个真实
DOM
元素 —— 几乎不用,了解即可。new Vue({ el:document.getElementById('demo'), //****** })
-
第三种写法:使用
$mount
方法去替代el
配置。new Vue(/******/).$mount('#demo')
6.2 data 的两种写法
-
第一种(对象式)
data:{ school:'尚硅谷' }
-
第二种(函数式)—— 最近不用,以后会用。
data(){ return { school:'尚硅谷' } }
7. 理解MVVM
M
:模型(Model
) :data
中的数据。V
:视图(View
) :模板。VM
:视图模型(ViewModel
) : Vue实例对象。
8. 认识一下vm
-
vm
身上有很多$
开头的属性或方法,这是给我们程序员用的(但也不是都用)。 -
data
中所配置的属性,也不知道怎么了(后面会详细说),最终都出现在了vm
身上。 -
vm
身上所有的属性,以及顺着vm
能找到的东西,都能在模板中直接使用,但往往我们使用的只是那些:配置项中所写的东西。 -
务必搞懂如下代码:
<!-- 准备好一个容器--> <div id="demo"> <h1>{{school}}欢迎你!</h1> <h2>测试1:{{a}}</h2> <h2>测试2:{{b}}</h2> <h2>测试3:{{_c}}</h2> <h2>测试4:{{$attrs}}</h2> <h2>测试5:{{toString}}</h2> <h2>测试6:{{d}}</h2> </div> <script type="text/javascript"> Object.prototype.d = 9 let vm = new Vue({ el: '#demo', data: { school: '尚硅谷', a: 1, b: 2 } }) // 输出Vue实例对象 —— vm console.log(vm) </script>
day02
1. 复习Object.defineProperty
-
Object.defineProperty
,能给对象追加属性,并且可以对属性进行“高级定制”。//定义一个对象 let person = {name:'张三',sex:'女'} //通过Object.defineProperty,也可以给person对象追加一个age属性,且可以对age进行“高级定制”。 Object.defineProperty(person,'age',{ value:90,//值 enumerable:true, //控制属性是否可以枚举(是否参与遍历),默认值false configurable:true, //控制属性是否可以删除,默认值false writable:true,//控制属性是否可以被修改,默认值false })
-
Object.defineProperty
的get
与set
配置://定义一个number变量 let number = 18 // 定义一个person对象 let person = {name:'张三',sex:'女'} //借助Object.defineProperty去追加age属性 Object.defineProperty(person,'age',{ enumerable:true, //get函数(getter)何时执行?—— 有人读取person对象的age属性时执行 //get函数(getter)中的this是谁? —— 当前对象(person) get:function peiqi(){ console.log('getter执行了',this) return number }, //set函数(setter)何时会被调用? —— 有人修改person对象的age属性时执行 //get函数(setter)中的this是谁? —— 当前对象(person) set:function qiaozhi(value){ console.log('有人修改了person的age属性,值为:',value,this) number = value } })
2. 数据代理
2.1 何为数据代理
通过一个对象代理对另一个对象中属性的操作(读/写)。
2.2 Vue中的数据代理
-
前情提要:我们
new Vue(options)
时传入的那个data
,Vue
收到后放在了vm
上,名为_data
。 -
Vue
中的数据代理:通过vm
来对_data
中属性的操作(读/写)。 -
Vue
中数据代理的好处:模板中可以更加方便的操作_data
中的数据,例如:若无数据代理,这么写:
{{_data.name}}
—— 很麻烦。若有数据代理,这么写:
{{name}}
—— 香!注意:此时我们先不关注
_data
中为什么也有getter
、setter
,过几天就会说。
2.3 数据代理的原理
-
遍历
_data
对象中所有的属性,通过Object.defineProperty()
一个一个都添加到vm
上。 -
随后
vm
上就拥有了_data
中所有的属性,且都有自己的getter
、setter
。 -
getter
、setter
内部操作(读/写)的是_data
中对应的属性。
3. 事件处理
3.1 事件绑定
-
使用
v-on:xxx
或@xxx
绑定事件,xxx
是事件名,同原生DOM
事件名。<button v-on:click="showTel">点我查看学校电话1</button> <button @click="showTel">点我查看学校电话2</button>
-
事件的回调函数,要配置在
methods
中(data
中写数据、methods
中写方法)。 -
Vue
在触发事件回调时,会传入一个默认的参数 ——事件对象(event
) 。 -
methdos
中的函数最终也会出现在vm
上(但不存在数据代理)。const vm = new Vue({ el:'#demo', data:{ school:'尚硅谷', tel:'10086', }, methods:{ showTel:(event)=>{ console.log(this) alert(this.tel) } } }) console.log(vm)
通常情况下,由
Vue
管理的函数(目前只学了:data
函数、methods
中的函数),请务必写成普通函数,这样才能保证:this
是vm
,一旦写成了箭头函数,this
就不再是vm
了。
3.2 事件传参
- 不传递参数:
@click="test1"
,test1
方法会收到一个event
(事件对象)。 - 传一个参数:
@click="test2(6)"
,test2
方法只会收到一个6
。 - 传多个参数:
@click="test3(6,7,8)"
,test3
方法会收到:6、7、8
。 - 传参+事件对象:
@click="test4(6,$event)"
,test4
方法会收到:事件对象
、6
。 - 传递的参数也可以是
data
中的数据,例如@click="test5(school)"
。
以下写法有点傻:
<button @click="test($event)">按钮</button>
<button @click="test()">按钮</button>
3.3 事件修饰符
-
prevent
:可以阻止默认行为。 -
stop
:可以阻止冒泡。 -
once
:事件只触发一次。原理是当调用一次之后,vue底层会删除这个事件 -
事件修饰符可以串联:
<div @click="test" class="wraper"> <a href="xxx" @click.prevent.stop="test">按钮</a> </div>
3.4 键盘事件
-
Vue
中的按键别名:- 回车 =>
enter
- 删除 =>
delete
(退格 、 删除 按键) - 退出 =>
esc
- 空格 =>
space
- 换行 =>
tab
(必须配合keydown
去使用) - 上 =>
up
- 下 =>
down
- 左 =>
left
- 右 =>
right
- 回车 =>
-
也可以使用
event.keyCode去指定具体的按键,例如:
@keyup.13` 绑定回车。 -
有四个系统修饰键,用法比较特殊(了解即可),分别是:
ctrl
、alt
、shift
、meta
,规则如下:- 若配合
keydown
使用:正常触发事件。 - 若配合
keyup
使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
- 若配合
<input @keyup.enter="show" type="text" placeholder="按下回车提示用户输入的内容"><br>
<input @keyup.delete="show" type="text" placeholder="按下删除提示用户输入的内容"><br>
<input @keyup.esc="show" type="text" placeholder="按下退出提示用户输入的内容"><br>
<input @keyup.space="show" type="text" placeholder="按下空格提示用户输入的内容"><br>
<input @keydown.tab="show" type="text" placeholder="按下换行提示用户输入的内容"><br>
<input @keyup.up="show" type="text" placeholder="按下↑提示用户输入的内容"><br>
<input @keyup.down="show" type="text" placeholder="按下↓提示用户输入的内容"><br>
<input @keyup.left="show" type="text" placeholder="按下←提示用户输入的内容"><br>
<input @keyup.right="show" type="text" placeholder="按下→提示用户输入的内容"><br>
<br>
<input @keydown.ctrl="show" type="text" placeholder="按下ctrl"><br>
<input @keydown.alt="show" type="text" placeholder="按下alt"><br>
<input @keydown.shift="show" type="text" placeholder="按下shift"><br>
<input @keydown.meta="show" type="text" placeholder="按下meta"><br>
<br>
<input @keyup.ctrl="show" type="text" placeholder="按下ctrl"><br>
<input @keyup.alt="show" type="text" placeholder="按下alt"><br>
<input @keyup.shift="show" type="text" placeholder="按下shift"><br>
<input @keyup.meta="show" type="text" placeholder="按下meta"><br>
4. 姓名案例
4.1 插值语法实现
-
总结:用插值语法实现姓名案例,不是很好,因为模板过于复杂。
-
核心代码:
<span>{{firstName.slice(0,1).toUpperCase() + firstName.slice(1)}}-{{lastName}}</span>
4.2 methods实现
-
总结:使用
methods
去实现姓名案例,比使用插值好了一些,但存在一些问题:- 没有缓存,多次使用同样的值,函数会执行多次,效率不高。
- 无关数据若发生变化,也会导致方法执行。
-
核心代码:
methods:{ getFullName(){ console.log('getFullName') return this.firstName.slice(0,1).toUpperCase() + this.firstName.slice(1) + '-' + this.lastName }, }
5. 计算属性
学习计算属性前,请先把【姓名案例】分别用:插值语法、methods,去实现一下。
- 定义:要用的数据(属性)不存在,要根据已有数据(属性)计算得来。
- 原理:底层是通过
Objcet.defineProperty
实现的。 - 优势:与
methods
实现姓名案例相比,内部有缓存机制,效率更高,且调试方便。
5.1 get
函数
get
函数何时调用?
- 初次读取
fullName
时。- 计算
fullName
所【依赖的数据】发生改变时。get
函数中的this
谁? ——vm
。注意:计算属性最终也会出现在
vm
上,在模板中可直接使用,但千不要加.get()
!
5.2 set
函数(用的少)
set
函数何时调用?—— 仅当计算属性被修改时。
set
函数中的this
是谁? ——vm
。什么是修改计算属性?
这是修改:
this.fullName = 'li-si'
这是读取:
this.fullName
注意:
this.firstName = 'li'
,这是修改firstName
代码示例:
computed:{
fullName:{
get(){
/**********/
}
set(value){
/**********/
}
}
}
5.3 简写方式
-
什么时候才能简写?—— 计算属性不会修改时(不需要
set
函数时),才能用简写形式。 -
语法实例:
computed:{ fullName(){ return xxxxx } }
6. 天气案例
如果回调函数的逻辑很简短,那么可以写在引号里:
<button @click="isHot = !isHot">切换天气</button>
day03
1. 监视属性
1.1 基本使用
监视属性又称侦听器,学习监视属性前,请先完成:天气案例。
-
作用:当被监视的属性变化时, 回调函数(
handler
)自动调用,至于回调函数中做什么,要看具体需求。 -
具体编码:
watch:{ isHot:{ handler(newValue,oldValue){ //(新数据,旧数据) /*********/ } } }
-
注意点:
- 被监视的可以是:属性(
data
),也可以是计算属性(computed
)。 - 监视的属性必须存在,才能进行监视,若监视了不存在的属性,也不会报错!
- 被监视的可以是:属性(
1.2 立即监视
- 作用:让
Vue
初次渲染时,数据还没有发生变化,就调用一下监视的回调函数(handler
) - 具体配置:
immediate:true
1.3 深度监视
Vue
底层一直可以监测对象内部值的改变(且无论对象有“深”)。Vue
给我们提供的watch
配置,默认不监测对象内部属性的改变。- 配置
deep:true
可以监测对象内部属性的改变。 - 使用
watch
时,要根据数据的具体结构,来决定是否采用深度监视。
1.4 特殊写法
当数据层级较深,但只想监视里面的某一个数据时候,可以不开启深度监视,只针对某个属性进行监视,例如:
new Vue({
el:'#demo',
data:{
a:{
b:{
c:{
d:1
}
}
}
},
watch:{
'a.b.c.d':{
handler(value){
console.log('a.b.c.d变化了')
}
}
}
})
1.5 简写形式
-
明确:当不需要【立即监视】、【深度监视】的时候,才可以用简写形式。
-
示例代码(
isHot
函数,就相当于完整写法中的handler
):watch:{ isHot(){ /*********/ } }
1.6 $watch(了解即可)
通过 vm.$watch
也可以进行监视
vm.$watch('isHot',{
handler(newValue,oldValue){
/******/
}
})
2.梳理配置项
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>梳理配置项</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../../js/vue.js"></script>
</head>
<body>
<!-- 准备好一个容器-->
<div id="demo">
<!-- a怎么用 -->
<!-- <h1 :x="a">{{a}}</h1> -->
<!-- <input type="text" v-model="a"> -->
<!-- <button @click="a+=1">点我</button> -->
<!-- b怎么用 -->
<!-- 19行、20行,没有语法上的错误,但使用的没意义 -->
<!-- <h1 :x="b">{{b}}</h1> -->
<!-- <input type="text" v-model="b"> -->
<!-- <button @click="b">点我</button> -->
<!-- <h1>{{b()}}</h1> -->
<!-- c能怎么用 -->
<!-- <h1 :x="c">{{c}}</h1> -->
<!-- 下面这行能写,但要记得,配一个setter -->
<!-- <input type="text" v-model="c"> -->
<h1>{{c}}</h1>
</div>
<script type="text/javascript">
const vm = new Vue({
el:'#demo',
data:{
a:1,
},
methods:{
b(){
console.log('b执行了')
return 99
}
},
computed:{
c(){
return (this.a+1)*10
}
},
watch:{}
})
console.log(vm)
</script>
</body>
</html>
3. computed与watch
computed
和 watch
之间的区别与联系:
-
computed
能完成的功能,watch
都可以完成,且通常computed
的写法会更简便。例如:姓名案例,
computed
、watch
都能完成,但computed
写起来,更简单。 -
watch
能完成的功能,computed
不一定能完成。例如:还做姓名案例,但要求:名改完
1
秒钟后,全名再变。
3.1 computed
- 侧重在【算】,核心是:计算出来的值。
- 靠
return
来输出计算的结果。 - 不能异步计算。
3.2 watch
- 侧重在【监视】,核心是:
xxx
变了,我要做???
事。 - 无需
return
,靠内部逻辑去完成要做的事。 - 能开启异步任务。
4. Vue中使用函数的原则
- 由
Vue
所管理的函数,务必写成普通函数,例如:data
、methods
中的函数、computed
中的函数、watch
中的函数。 - 不是
Vue
所管理的函数,最好写成箭头函数,例如:数组方法的回调函数、ajax
请求的回调函数、定时器的回调函数、Promise
的回调函数等等。
遵循上述两个原则的目的只有一个:让
this
的指向是vm
。
5. 条件渲染
5.1 v-show
- 语法:
v-show="表达式"
。 - 适用于:切换频率较高的场景。
- 特点:不展示的
DOM
元素但是依然存在在,仅仅是使用样式display:none
隐藏掉了,不破坏DOM
结构。
5.2 v-if
-
语法:
v-if="表达式" v-else-if="表达式" v-else
-
适用于:切换频率较低的场景。
-
特点:不展示的
DOM
元素直接被移除。注意点:
v-if
可以和:v-else-if
、v-else
一起使用,但要求结构不能被“打断”。- 如果使 用
v-if
,可能会出现无法获取元素的问题。
-
实际开发中,以下这个场景我们会使用
v-if
<!-- 准备好一个容器--> <div id="demo"> <h1 v-if="person.age">真实年龄:{{person.age.realAge}}</h1> <button @click="getNetWorkData">获取网络数据</button> </div> <script type="text/javascript"> new Vue({ el:'#demo', data:{ person:{} }, methods: { getNetWorkData(){ this.person = { age:{ foreignAge:29, realAge:45 } } } }, }) </script>
6. 列表渲染
6.1 v-for
指令
- 指令:
v-for
。 - 作用:用于遍历数据,生成多个结构。
- 语法:
v-for="(item,index) in xxx" :key="????"
。
通俗理解:想生成多个谁,就在谁身上加
v-for
,别忘了写key
。
key
的使用原则(同react
):有唯一值就用唯一值(身份证号、手机号、学号…),没有就用索引值。
6.2 详聊 v-for
-
遍历数组
<ul> <li v-for="(item,index) in arr" :key="index"> {{item}} </li> </ul>
-
遍历对象
<li v-for="(value,key,index) in car" :key="index"> {{value}} - {{key}} - {{index}} </li>
-
遍历字符串
<li v-for="(char,index) in str" :key="index"> {{char}} - {{index}} </li>
-
遍历指定次数
<li v-for="(number,index) in 10" :key="index"> {{number}} - {{index}} </li>
-
v-for
很健壮,遍历如下内容都不会报错,并且不会渲染任何内容<h1 v-for="(a,b) in null">尚硅谷</h1> <h1 v-for="(a,b) in undefined">尚硅谷</h1> <h1 v-for="(a,b) in '' ">尚硅谷</h1> <h1 v-for="(a,b) in true ">尚硅谷</h1> <h1 v-for="(a,b) in false">尚硅谷</h1> <h1 v-for="(a,b) in [] ">尚硅谷</h1>
<h1 v-for="(a,b) in NaN">尚硅谷</h1>
不能循环NaN 会报错 错误的写法
6.3 人员案例
- 可以使用
watch
、computed
分别去实现一下,发现用computed
更简单。 - 用到了:字符串的
includes
方法,数组的filter
方法。
6.4 v-for和v-if 不建议连用 (vue2.0版本)
** v-for的优先级比v-if的优先级高。先for出来再v-if删除dom,会干无用功**
day04
1. 其它指令
-
我们学过的指令:
v-bind
: 单向数据绑定, 可简写为:xxx
v-model
: 双向数据绑定v-for
: 遍历数组 / 对象 / 字符串 / 指定次数v-on
: 绑定事件监听, 可简写为@
v-if
: 条件渲染(动态控制节点是否存在)v-else-if
: 条件渲染(动态控制节点是否存在)v-else
: 条件渲染(动态控制节点是否存在)v-show
: 条件渲染 (动态控制节点是否展示)
v-text
作用:向其所在的节点中渲染文本内容。
与插值语法的区别:
v-text
会替换掉节点中的内容,{{xx}}
则不会。
<h1 v-text="str">张三</h1>
v-html
作用:向指定节点中渲染包含
html
结构的文本。
与插值语法的区别:
v-html
会替换掉节点中所有的内容,{{xx}}
则不会。v-html
可以识别html
结构。- 备注:
v-html
存在一些安全性问题,因为结构中很有可能包含恶意脚本。
<h1 v-html="str"></h1>
new Vue({
el:'#demo',
data:{
str:'<a href="javascript:alert(250)">兄弟好东西,快来</a>'
}
})
v-once
v-once
所在节点在初次动态渲染后,就视为静态内容了。- 以后数据的改变不会引起
v-once
所在结构的更新,可以用于优化性能。
<div id="demo">
<h2 v-once>初始的n值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n+=1">点我n+1</button>
</div>
<script type="text/javascript">
new Vue({
el:'#demo',
data:{
n:1
}
})
</script>
v-pre
跳过这个元素和它的子元素的编译过程,一般用在大量不使用
Vue
语法的结构中。
<div v-pre>
<h1>你好同学,欢迎来到尚硅谷</h1>
<h2>想一夜暴富吗?</h2>
<h3>想一步登天吗?</h3>
<h4>想赢取白富美吗?</h4>
<p>
那要怎么做?—— 学前端!!
</p>
<strong>要好好学!</strong>
</div>
v-cloak
- 本质是一个特殊属性,
Vue
接管容器后,会删掉v-cloak
属性。- 使用
css
配合v-cloak
可以解决网速慢时,页面展示出{{xxx}}
的问题。
<style>
[v-cloak] {
display: none;
}
</style>
<body>
<div id="demo">
<h1 v-cloak>欢迎来到{{school}}</h1>
</div>
<script type="text/javascript" src="../../js/vue.js"></script>
<script type="text/javascript" >
new Vue({
el:'#demo',
data:{
school:'尚硅谷'
}
})
</script>
</body>
2. 数据劫持
2.1 何为数据劫持
-
概念:捕获对象属性变化的一种手段。
前端的响应式框架通常都会对数据进行劫持,这样当数据发生变化时,才能自动更新相关的视图,或执行其他逻辑。
2.2 Vue中的数据劫持
-
具体实现方式:
Vue
会将所有层级的属性,全都改为getter
、setter
的形式,随后放在_data
中。- 当
_data
中的数据放生变化时,对应的setter
会执行,在setter
中:①修改数据、②更新界面。
-
图示:
2.3 总结_数据代理 _数据劫持
-
数据代理(简单,
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
就会调用,返回对应的值。
3. Vue中操作数组
-
在
Vue
中,修改数组中的某一项,请务必要用如下7
个方法:push()
、pop()
、shift()
、unshift()
、splice()
、sort()
、reverse()
-
Vue
在底层包裹了数组的7
个变更方法,本质就是做了两件事: ① 调用原生对应的方法对数组进行更新。
② 更新页面。
4. 收集表单数据
- 若:
<input type="text"/>
,则v-model
收集的是value
值,用户输入的就是value
值。- 若:
<input type="radio"/>
,则v-model
收集的是value
值,且要给标签配置value
值。- 若:
<input type="checkbox"/>
- 没配置
input
的value
属性,那么收集的就是checked
(勾选 或 未勾选,是布尔值)- 配置了
input
的value
属性:
v-model
的初始值是非数组,那么收集的就是checked
(勾选 或 未勾选,是布尔值)。v-model
的初始值是数组,那么收集的就是value
组成的数组。
<body>
<!-- 准备好一个容器-->
<div id="demo">
<form>
账号:<input type="text" v-model="account"> <br><br>
密码:<input type="password" v-model="password"> <br><br>
性别:
男<input type="radio" name="sex" value="male" v-model="gender">
女<input type="radio" name="sex" value="female" v-model="gender">
<br><br>
爱好:
抽烟<input type="checkbox" value="smoke" v-model="hobby">
喝酒<input type="checkbox" value="drink" v-model="hobby">
学习<input type="checkbox" value="study" v-model="hobby">
<br><br>
所属校区:
<select v-model="city">
<option value="">请选择城市</option>
<option value="beijing">北京</option>
<option value="shenzhen">深圳</option>
<option value="shanghai">上海</option>
<option value="wuhan">武汉</option>
<option value="xian">西安</option>
<option value="chengdu">成都</option>
</select>
<br><br>
其他信息:
<textarea v-model="other"></textarea><br><br>
<input type="checkbox" v-model="agree">阅读并接受<a href="#">用户协议</a>
<br><br>
<button>提交</button>
</form>
</div>
<script type="text/javascript">
new Vue({
el:'#demo',
data:{
account:'',
password:'',
gender:'female',
hobby:[],
city:'',
other:'',
agree:false
}
})
</script>
</body>
5. 生命周期
5.1 何为生命周期
- 生命周期,又称:生命周期函数、生命周期钩子。
- 是什么? ——
Vue
在关键时刻帮我们调用的一些特殊名称的函数。
5.2 生命周期图
beforeCreate()
数据代理、数据劫持创建之前
created()
数据代理、数据劫持创建完毕
beforeMount()
挂载(真实DOM放入页面)之前
mounted()
挂载(真实DOM放入页面)完毕
beforeUpdate()
更新之前
updated()
更新完毕
beforeDestroy()
销毁之前
destroyed()
销毁完毕
- 生命周期函数的名字不可更改,但其中的具体内容,看具体需求。
- 生命周期函数中的
this
指向是vm
或 组件实例对象。
5.3 总结
- 哪个钩子中不能访问
data
中的数据、methods
中的方法? ——beforeCreate
- 想给
vm
上追加一些属性,最早可以在哪个钩子中操作? ——beforeCreate
data
中的数据、methods
中的方法,最早可以在哪个钩子中获取? ——created
- 哪个钩子中数据和页面其实是不同步的?——
beforeUpdate
- 常用的钩子有哪些?
mounted
: 发送ajax
请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】beforeDestroy
: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
day05
1. 组件化
1.1 对组件的理解
概念:应用中局部功能代码和资源的集合。
1.2 准备一个效果
展示:学校、人员 相关信息。
1.3 基本使用
Vue
中使用组件的三大步骤:
-
第一步:创建组件
- 如何创建组件?—— 使用
Vue.extend(options)
方法。 - 其中的
options
和new Vue(options)
几乎一样,但也有点小区别,区别如下:- 组件配置中不能写
el
,为什么?—— 由vm
去决定服务哪个容器,组件跟着走就ok
了。 - 组件配置中
data
必须写成函数,为什么? —— 避免组件复用时产生数据的干扰。
- 组件配置中不能写
- 如何创建组件?—— 使用
const Person = Vue.extend({
nane:'Person',
template:`
<div>
<h2>人员信息</h2>
<h4>姓名:{{personName}}</h4>
<h4>年龄:{{personAge}}</h4>
<button @click="showPersonInfo">点我提示个人信息</button>
</div>
`,
data(){
return {
personName:'瑶瑶',
personAge:18,
}
},
methods:{
showPersonInfo(){
alert(`我叫${this.personName},我今年${this.personAge}岁了`)
}
}
})
-
第二步:注册组件。
new Vue({...})
时,使用components
配置。
new Vue({
el:'#demo',
// 第三步:编写组件标签
template:`
<div>
<Person></Person>
</div>
`,
// 第二步:注册组件
//components:{Person:Person}
components:{Person}//简写
})
-
第三步:使用组件(编写组件标签)。
<Person> </Person>
1.4 组件名_组件标签
关于组件名:
- 一个单词组成:
- 第一种写法:(首字母小写):
school
- 第二种写法:(首字母大写):
School
—— 推荐- 多个单词组成:
- 第一种写法:
kebab-case
命名,例如:atguigu-school
- 第二种写法:
CamelCase
命名,例如:AtguiguSchool
可以使用
name
配置项,指定组件在开发者工具中的名字。
关于组件标签
第一种写法:
<组件名></组件名>
第二种写法:
<组件名/>
1.5 App组件的使用
通常使用App
组件,作为所有组件的根组件。
// 创建一个App组件
let App = Vue.extend({
components:{Person,School},
template:`
<div>
<Person></Person>
<hr>
<School></School>
</div>`
})
1.6 注册全局组件
- 若某个组件,需要在很多的地方使用,可以进行全局注册。
- 语法:
Vue.component('组件名',具体的组件)
Vue.component('Welcome',Welcome)
1.7 VueComponent
-
组件的本质是一个构造函数,名为:
VueComponent
。 -
VueComponent
,不是我们定义的,也不是引入了vue.js
就有的,而是Vue.extend()
生成的。 -
我们只需要写
<Person></Person>
或<Person/>
,Vue
底层就会帮我们执行:new VueComponent()
。简记:定义组件是在创建
VueComponent
,编写组件标签是在new VueComponent()
。 -
关于
this
的指向:new Vue(options)
中,this
指向是vm
。Vue.extend(options)
中:this
指向是vc
。
简记:
- 创建
vm
时,各种配置函数中的this
就是vm
。 - 创建组件时,各种配置函数中的
this
就是vc
。
-
备注:
vc
是一个小型的vm
,但与vm
也有点小不同,例如:vm
可配置el
,而vc
不能配置。
1.8 Vue.extend 的简写
-
完整写法:
//定义一个Person组件 let Person = Vue.extend({ name:'Person', template:`此处写结构` })
-
简写方式:
所谓简写方式,其实就是不写
Vue.extend()
,直接写配置对象。//定义一个Person组件 let Person = { name:'Person', template:`此处写结构` }
2. 单文件组件
-
文件的后缀名为
.vue
。 -
具体格式为:
<template> <!-- 此处写结构 --> </template> <script> export default { name:'Hello' } </script> <style> /*此处写样式*/ </style>
-
安装插件:
3.准备工作(无代码)
● 创建 Vue 脚手架
-
第一步:
npm i @vue/cli -g
(只是第一次执行,以后就不用了)。 -
第二步:在你喜欢的位置,打开命令行工具(CMD),执行命令:
vue create hello
。-
hello
是项目名称,可以随意修改,但最好别写:中文、特殊字符等。 -
若出现如下提示,按
y
即可。
/> -
请选择:
Default ([Vue 2] babel,eslint)
。 -
选择:
Use NPM
。
-
-
第三步:使用
Vscode
打开hello
文件夹,执行命令:npm run serve
,出现如下画面,代表成功。
● 脚手架结构说明
├── node_modules
项目依赖包
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
│
├── src
│ ├── assets
: 存放公共静态资源(公用的:图片、字体等等)
│ │ └── logo.png
│ │
│ │── components: 存放组件
│ │ └── HelloWorld.vue
│ │
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
│
├── .gitignore
: git 忽略清单
├── babel.config.js
: babel 配置文件
├── jsconfig.json
: VScode 配置文件
├── package-lock.json
:包版本清单
├── package.json
: 依赖包清单
├── README.md
: 应用描述文件
├── vue.config.js
: 脚手架配置文件
1. 编写一个App组件
-
删掉
public
文件夹,删掉src
文件夹,我们自己编写一个App
组件。 -
注意:
main.js
中创建vm
时,不要写template
与components
:import Vue from 'vue' import App from './App' Vue.config.productionTip = false new Vue({ el:'#app', // template:`<App/>`, //不要写这行 // components:{App} //不要写这行 // 上述的template和components合并成下面的render配置项。 render:h => h(App) //完整写法 /*render:(helper)=>{ return helper(App) }*/ })
2. 配置语法检查
-
第一种方式:
//eslint-disable-next-line
-
第二种方式:
/*eslint-disable*/
-
第三种方式:
vue.config.js
中配置 ,具体配置如下:const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ transpileDependencies: true, lintOnSave:false // ======> 追加这句话,用于关闭语法检查。 })
备注:
vue.config.js
可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
3. Person与School
-
第一步:删掉脚手架中的
public
、src
,只保留node_modules
和一些配置文件。 -
第二步:建立好如下文件(文件具体内容见代码):
├── public │ ├── favicon.ico │ └── index.html ├── src │ │── components │ │ └── Person.vue │ │ └── School.vue │ │── App.vue │ │── main.js
-
第三步:运行看效果。
必须把这个效果敲的特别熟!!!
4. 不同版本的Vue
-
vue.js
与vue.runtime.xxx.js
的区别:vue.js
是完整版的Vue,包含:核心功能 + 模板解析器。vue.runtime.xxx.js
是运行版(精简版)的Vue
,只包含:核心功能,没有模板解析器。
-
因为
vue.runtime.xxx.js
没有模板解析器,所以new Vue
时不能使用template
这个配置项。 -
vue.runtime.xxx.js
需要使用render
函数接收到的createElement
函数去指定具体内容。 -
具体编码:
//创建vm new Vue({ el:'#app', render: h => h(App) // render: (createElement) => createElement(App) })
5. 局部样式
- 作用:让样式只在局部生效,防止冲突。
- 写法:
<style scoped>
。
6. ref
-
用来给元素或子组件打标识(
id
的替代者) -
应用在
html
标签上,获取的是真实**DOM
元素**。 -
应用在组件标签上是 组件实例对象(vc)。
-
使用方式:
- 打标识:
<h1 ref="xxx">.....</h1> 或 <School ref="xxx"></School>
-
获取标识对应的内容(
DOM
元素 或 组件实例对象):this.$refs.xxx
day06
1.props
1. props 基本使用
功能:让组件接收外部传过来的数据。
-
传递数据:
<Person :name="name" :car="car" :age="age"/>
-
接收数据:
props:['name','car','age']
接收到的
prop
最终都出现在:组件实例 ——vc
上。
2. props 不可修改
props
是只读的,不可修改。
- 对于基本类型的
props
:修改会报错,但页面依然会更新。 - 对象或数组类型的
props
:- 若修改的是整个对象或数组(地址值发生变化),会报错。
- 若修改的是对象或数组中的内容(地址值不变),不会报错 —— 但依然不推荐这样做。
注意:
props
收到的属性,不能和data
冲突,若冲突了,以props
为主,且会有警告。
3. props 的三种接收方式
注意点:
-
第一种方式(只接收):
props:['name','age','car']
-
第二种方式(接收 + 限制类型):
props:{ name:String, age:Number, car:Object }
第三种方式(接收、限制类型、配置必要性、指定默认值):
props:{ name:{ type:String, default:'张三', required:true }, car:{ type:Object, default(){ return {a:1} } }, age:{ type:Number, default:90, required:true } },
4. props实现子传父
数据在哪里,修改数的方法就在哪里,或者说:只有数据的拥有者,才能修改数据。
- 父给子传递一个函数。
- 子收到函数后,在合适的时候调用函数,并传递参数,从而实现:子传父。
2. v-model 的拆分
有些时候v-model
的双向绑定会让我们犯错误,所以我们需要对v-model
进行拆分。
例如这种场景:
v-model="xxx"
中的xxx
是来自于props
中的值,而props
的值是不可修改的,这时就需要对v-model
进行拆分,核心思路是:想办法联系父亲,让父亲改。
1. 普通输入框
拆分前:
<input type="text" v-model="str">
拆分后:
<input type="text" :value="str" @input="handler1">
2. radio 输入框
拆分前:
男<input type="radio" name="sex" v-model="gender"> 女<input type="radio" name="sex" v-model="gender">
拆分后:
男<input type="radio" name="sex" :checked="gender==='male'" @input="handler2('male')"> 女<input type="radio" name="sex" :checked="gender==='female'" @input="handler2('female')">
备注:上面的
input
事件,也可以替换成click
和change
事件。
3. checkbox 输入框
拆分前:
<input type="checkbox" v-model="agree"/>
拆分后:
<input type="checkbox" :checked="isAgree" @input="handler3"/>
备注:上面的
input
事件,也可以替换成click
和change
事件。
当阻止复选框的默认行为时,只能使用click才能触发
<input
type="checkbox"
name="chk_list"
:checked="cart.isChecked"
@click.prevent="changeChange(cart, $event)"
/>
3. TodoList_静态组件
- 先把所有的
html
结构、css
样式,都放在App
组件中。 - 随后拆分成:
Header
、List
、Item
、Footer
组件。 - 把:结构、样式,都放在对应的
.vue
里。
4. TodoList_初始化数据
-
需要准备一个
todos
数组,用于保存多个todo
对象。todos:[ {id:'qw7ywqe28',title:'吃饭',done:false}, {id:'dw3iw92kj',title:'睡觉',done:true}, {id:'opkmi9s72',title:'学习',done:false}, ]
-
观察发现:
todos
数组,好多组件都要使用,所以我们选择放在App
组件中。很多组件都用的数据,可以放在他们共同的父组件中,这叫:状态提升(数据提升)。
-
App
组件中:通过props
传递给List
组件。<List :todos="todos"/>
-
List
组件中:接收todos
数组,使用v-for
遍历,生成多个Item
组件,同时传递每一个todo
对象给Item
组件。<template> <ul class="todo-main"> <Item v-for="t in todos" :key="t.id" :todoObj="t"/> </ul> </template> <script> import Item from './Item.vue' export default { name: "List", components:{Item}, props:['todos'] }; </script>
-
Item
组件接收todo
对象,并展示。<template> <li> <label> <input type="checkbox" :checked="todoObj.done"/> <span>{{todoObj.title}}</span> </label> <button class="btn btn-danger" style="display: none">删除</button> </li> </template> <script> export default { name: "Item", props:['todoObj'] }; </script>
5. TodoList_添加 todo
-
由于数据在
App
组件中,所以在App
中创建一个addTodo
方法,专门用于添加todo
。 -
App
组件中,通过props
将addTodo
传给Header
组件。注意判断重复数据,使用数组的
find
方法。<template> <!------------> <Header :addTodo="addTodo"/> <!------------> </template> <script> export default { /*****************/ methods: { // 用于添加一个todo对象 addTodo(obj){ // 看一下当前的todos中是否有与要添加的名字重复的todo项 const result = this.todos.find( item => item.title === obj.title) // 如果没有,就添加 if(result){ // 如果重复提示一下 alert('不能重复添加') }else { // 没有重复,执行添加 this.todos.unshift(obj) } } }, /*****************/ }; </script>
-
Header
组件中,接收addTodo
,需要添加的时候,直接调用即可。注意点:我们使用了
nanoid
这个库,去生成唯一标识。<template> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="handleAdd" /> </div> </template> <script> import {nanoid} from 'nanoid' export default { name:'Header', props:['addTodo'], methods: { // 按下回车的回调函数 handleAdd(event){ // 1.获取用户的输入 let {value} = event.target if(value.trim()){ // 2.将用户输入的title,包装成一个todo对象 const todoObj = {id:nanoid(),title:value,done:false} // 3.将todo对象传给App组件 this.addTodo(todoObj) // 4.清空输入 event.target.value = '' }else{ alert('输入不能为空!') } } }, } </script>
6. TodoList_删除 todo
-
由于数据在
App
组件中,所以在App
中创建一个deleteTodo
方法,用于删除某个todo
。methods: { /**********/ deleteTodo(id){ this.todos = this.todos.filter( t => t.id !== id) } /**********/ },
-
App
组件中,通过props
将deleteTodo
传给List组件。<List :todos="todos" :deleteTodo="deleteTodo"/>
-
List
组件接收到deleteTodo
后,进一步传给Item
组件<template> <ul class="todo-main"> <Item v-for="t in todos" :key="t.id" :todo="t" :deleteTodo="deleteTodo"/> </ul> </template> <script> import Item from './Item.vue' export default { name: "List", components:{Item}, props:['todos','deleteTodo'] }; </script>
-
Item
组件中,接收deleteTodo
,并在需要删除的时候使用<template> <li> <label> <input type="checkbox" v-model="todo.done"/> <span>{{todo.title}}</span> </label> <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> </li> </template> <script> export default { name: "Item", props:['todo','deleteTodo'], methods: { handleDelete(id){ if(confirm('确定删除吗?')){ this.deleteTodo(id) } } }, }; </script>
7. TodoList_勾选 todo
分析:
-
用
v-model
可以实现勾选,虽说没报错,但终归还是修改了props
—— 不是很推荐。 -
更好的做法是:在
Item
中接收到来自App
的方法,实现勾选,具体代码如下:-
在
App
组件中,定义一个checkTodo
方法,逐层传递给Item
(期间要经历List
)<template> <!---------> <List :todos="todos" :deleteTodo="deleteTodo" :checkTodo="checkTodo"/> <!---------> </template> <script> /*****/ methods:{ checkTodo(id){ this.todos.forEach((item)=>{ if(item.id === id){ item.done = !item.done } }) } } /*****/ </script>
-
在
List
中接收checkOne
,随后继续传给Item
。 -
最后在
Item
组件中使用:<input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)" /> <script> handleCheck(id){ this.checkTodo(id) } </script>
-
8 . TodoList_底部计算
直接使用计算属性,代码如下:
computed:{
//总数
total(){
return this.todos.length
},
//已完成的总数
doneCount(){
let n = 0
this.todos.forEach(item => {
if(item.done){
n+=1
}
});
return n
}
}
9. TodoList_全选 or 全不选
-
在
App
组件中,定义好一个checkAll
方法,用于全选<Footer :todos="todos" :clearAllDone="clearAllDone" :checkAllTodo="checkAllTodo"/> <script> methods: { checkAllTodo(done) { this.todos.forEach((t) => { t.done = done; }); }, }, </script>
-
在
Footer
组件中<input type="checkbox" v-model="isAll"/>
-
isAll
改为计算属性isAll:{ get(){ return this.todos.every((todo)=>{ return todo.done }) }, set(checked){ this.checkAll(checked) } }
10. TodoList_清除已完成
-
App
组件中,通过props
将clearAllDone
传给Footer
组件。<Footer :clearAllDone="clearAllDone"/> <script> methods: { clearAllDone(){ this.todos = this.todos.filter( t => !t.done) }, }, </script>
-
Footer
组件中,接收clearAllDone
,并使用<button class="btn btn-danger" @click="handleClear">清除已完成任务</button> <script> export default { name: "Footer", props:['clearAllDone','todos'], methods:{ handleClear(){ if(confirm('确定清除已完成吗?')){ this.clearAllDone() } } }, }; </script>
11.强制使闭合代码
//#region 开启位置
/* const result = this.todos.find( (item) => {
return item.title === obj.title
}) */
//#endregion 结束位置
day07
1. 浏览器本地存储
-
localStorage、sessionStorage
可实现浏览器本地存储,它们统称为Web Storage
。 -
内容的格式为:字符串类型的键值对,存储大小一般为:
5MB~10MB
。 -
相关API:
-
存数据
//如果key存在,则更新其对应的value。 xxxxStorage.setItem('key', 'value');
-
读数据
//该方法接收一个key作为参数,返回对应的value。 var data = xxxxxStorage.getItem('ren');
-
删数据
//该方法接收key作为参数,会把对应的项删除。 xxxxxStorage.removeItem('key');
-
清空数据
//该方法会清空存储中的所有内容。 xxxxxStorage.clear()
-
-
备注:
sessionStorage
存储的内容会随着浏览器窗口关闭而消失。localStorage
存储的内容,需要:手动编写代码 或 清空了浏览器所有的缓存,才会消失。xxxxxStorage.getItem(xxx)
,如果xxx
对应的value
获取不到,那么返回值是null
!JSON.parse(null)
的返回值是null
。
2. TodoList 本地存储版
-
需求:刷新页面,关闭浏览器,
TodoList
列表不清空,依然是之前的数据。 -
思路:将数据存储进
localStorage
中。 -
具体操作:在
App
组件中监视todos
数组,只要数组发生变化,就存入localStorage
。 -
代码:
//...... data() { return { //所有要做的事 todos:JSON.parse(localStorage.getItem('todos')) || [] } } //...... watch:{ todos:{ deep:true, handler(value){ localStorage.setItem('todos',JSON.stringify(value)) } } }
3. 组件自定义事件
3.1 基本使用
-
一种组件间通信的方式,适用于:子组件 ===> 父组件
-
使用流程:在【父】中给【子】绑定自定义事件(事件的回调在父组件中)。
-
在父组件中,给子组件绑定自定义事件,写法如下:
<Child @send-toy="getToy"/>
-
子组件触发自定义事件:
this.$emit('send-toy',数据)
备注1:多个单词组成的事件名,官方推荐使用
kebab-case
命名,例如:@hello-world
备注2:
-
普通
DOM
事件(click
、mouseenter
、keyup
事件)靠鼠标键盘的交互去触发事件。 -
组件的自定义事件,靠
vc.$emit('事件名',数据)
去触发。
-
3.2 两种绑定方式
-
第一种方式:直接在子组件标签上绑定事件
<Child @send-toy="getToy"></Child>
-
第二种方式:使用ref绑定组件,然后在mounted钩子函数中绑定事件
<Child ref="c1"></Child> <!------> <script> methods: { getToy(value){ console.log('Child组件实例上(vc)的getToy事件被触发了',value) // 将收到的玩具,存入自己的toy中 this.toy = value } }, mounted(){ this.$refs.c1.$on('send-toy',this.getToy) } </script>
3.3 解绑事件
如何解绑自定义事件 —— this.$off('事件名')
解绑指定的事件。
this.$off()
解绑所有的事件
注意1:
$off
如果不传参数,那就是解绑其身上所有的自定义事件。注意2:给谁绑的事件,就找谁去触发事件,就找谁去解绑事件。
3.4 native
修饰符
当在组件上使用原生的事件时会被当做自定义事件,
//当做自定义事件使用
<Child @click="getToy" />
组件上也可以绑定原生DOM
事件,会把原生事件加在组件最外侧元素上,需要使用native
修饰符。
//添加native会转变为原生DOM事件
<Child @click.native="nativeGetToy" />
3.5 总结
props
可以实现:子 → 父、父 → 子,自定义事件只能:子 → 父props
需要接收,随后使用;自定义事件无需接收,直接触发即可。- 自定义事件,可通过工具查看具体信息,便于调试。
3.6 补充 $once
使用:vm.$once(‘事件名称’,callback)
说明:监听当前实例(vm)中的自定义事件,事件可以由$emit定义,但是只会触发一次,触发后即解除//监听事件被触发
this.$once('ChildClickFn', arg => {
console.log('我是用$once监听事件并触发的,参数是:', arg);
});
4. TodoList_自定义事件版
TodoList
中的【添加todo】功能,属于典型的子传父,即:Header
传给App
。- 把添【添加todo】改成自定义事件版。
5. 一个重要的内置关系
6. 全局事件总线
-
一种组件间通信的方式,适用于:任意组件间通信。
-
安装总线:
new Vue({ /*****/ beforeCreate() { //安装全局事件总线,$bus就是当前应用的vm。 Vue.prototype.$bus = this } /*****/ })
-
使用总线:
-
接收数据的组件:给
$bus
绑定自定义事件,事件的回调留在组件自身。methods: { test(value){ //根据需求,编写收到数据后的逻辑 } }, mounted() { //给$bus绑定事件并指定事件的回调,只要$bus的xxx事件被触发,test方法就会调用。 this.$bus.$on('xxxx',this.test) } beforeDestroy(){ this.$bus.$off('xxxx') //解除单个 this.$bus.$off(["xxxx", "xxxxx"])//解除多个 }
-
-
提供数据的组件:触发
$bus
对应的自定义事件this.$bus.$emit('xxx',数据)
- 最好在
beforeDestroy
钩子中,用$off
去解绑当前组件所用到的事件,否则会导致$bus
很“重”!
7. TodoList 案例_事件总线
TodoList
中的【删除todo】功能,属于孙传祖,即:Item
传给App
。- 把添【删除todo】改成自定义事件版。
8. 配置代理解决跨域
8.1 方法一
-
在
vue.config.js
中添加如下配置: -
devServer:{ proxy:"http://localhost:5000" //代理收到请求后转发的地址(真正有数据的服务器地址) }
8.2 方法二
-
编写
vue.config.js
配置具体代理规则: -
module.exports = { devServer: { open:true,//自动打开浏览器 proxy: { '/api1': {// 匹配所有以 '/api1'开头的请求路径 target: 'http://localhost:5000',// 代理目标的基础路径 changeOrigin: true, pathRewrite: {'^/api1': ''} }, '/api2': {// 匹配所有以 '/api2'开头的请求路径 target: 'http://localhost:5001',// 代理目标的基础路径 changeOrigin: true, pathRewrite: {'^/api2': ''} } } } } /* changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000 changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080 changeOrigin默认值为true */
day08
1. github 搜索案例
public
文件夹中的内容,最终不会参与打包。src
文件夹中的内容,最终会经过打包。
2. 自定义指令
-
需求:自定义一个
v-big
指令,功能和v-text
类似,但是能把内容放大10倍。 -
定义局部指令,定义组件时,追加一个配置 ——
directives
{ <h4>当前的n值是:<span v-text="n"></span></h4> <h4>当前的n值*10:<span v-big="n"></span></h4> <h4>当前的n值美化后:<span v-very-beauty="n"></span></h4> //cut 是全局的 <h4>当前的str截取第一位后:<span v-cut="str"></span></h4> } { //... directives:{ 指令名:回调函数 } //...element是指令所在元素,binding是一个对象 big(element,,binding){ console.log('big',element,binding.value) element.innerText += binding.value * 10 }, 'very-beauty'(element,binding){ element.innerText = binding.value element.style.backgroundColor = 'red', element.style.color = 'yellow' }, })
第二个参数binding的打印
-
定义全局指令
Vue.directive(指令名,回调函数) Vue.directive('cut',function(element,{value}){ element.innerText = value.slice(0,1) })
-
总结:
- 指令定义时不加
v-
,但使用时必须要加v-
。 - 指令名如果是多个单词,要使用
kebab-case
命名方式,不要用camelCase
命名! - 指令回调中的
this
不是vm
也不是vc
。
- 指令定义时不加
3. 插槽
- 作用:能实现父组件向子组件插入
html
结构,也是一种组件通信的方式,适用于 父组件 => 子组件 。 - 分类:默认插槽、具名插槽、作用域插槽。
6.1 默认插槽
父组件中:
<Category>
<h2>热门游戏列表</h2>
<ul>
<li v-for="g in games" :key="g.id">{{g.name}}</li>
</ul>
</Category>
子组件中:
<template>
<div class="category">
<!-- 默认插槽 -->
<slot></slot>
</div>
</template>
6.2 具名插槽
父组件中:
<Category>
<h2 slot="title">热门游戏列表</h2>
<ul slot="content">
<li v-for="g in games" :key="g.id">{{g.name}}</li>
</ul>
</Category>
子组件中:
<template>
<div class="category">
<!-- 具名插槽 -->
<slot name="title"></slot>
<slot name="content"></slot>
</div>
</template>
6.3 作用域插槽
-
理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(新闻数据在
News
组件中,但使用数据所遍历出来的结构由App
组件决定) -
举例理解:
压岁钱是孩子的,但是压岁钱怎么花,都能买买什么,由其父亲决定。
-
具体编码:
父组件中: <News> <!-- 如果News那边是默认插槽,有这么几种写法(写法比较多,推荐大家记住第三种写法): --> <!-- <template scope="{newsList}"> --> <!-- <template slot-scope="{newsList}"> --> <!-- <template v-slot="{newsList}"> --> <!-- <template #default="{newsList}"> --> <!-- 如果News那边是具名插槽(插槽名叫demo),有这么几种写法(推荐记住第三种写法): --> <!-- <template scope="{newsList}" slot="demo"> --> <!-- <template slot-scope="{newsList}" slot="demo"> --> <!-- <template v-slot:demo="{newsList}"> --> <template #demo="{newsList}"> <ul> <li v-for="n in newsList" :key="n.id">{{n.title}}</li> </ul> </template> </News> 子组件中: <template> <div class="news"> <!-- 具名插槽 --> <slot name="demo" a="1" b="2" c="3" :newsList="newsList"></slot> <!-- 默认插槽 --> <!-- <slot a="1" b="2" c="3" :newsList="newsList"></slot> --> </div> </template> <script> export default { name:'News', data() { return { newsList:[ {id:'ytfdtrasfdtr1',title:'《震惊!某男明星塌房》',isHot:true}, {id:'ytfdtrasfdtr2',title:'《毛不易,又来北京啦》',isHot:false}, {id:'ytfdtrasfdtr3',title:'《北京持续高温预警》',isHot:false} ] } }, } </script>
day09
1. 过滤器(了解)
特别注意:过滤器已经在
Vue3
中移除了,我们只需要了解一下即可。
-
概念:对要显示的数据进行特定处理后再显示(适用于一些简单的处理)
-
注册(配置)过滤器:
filters:{ //upper这个函数何时调用? —— 有人使用这个过滤器的时候。 //upper这个函数中的this是谁?—— 反正不是vm,也不是vc。 //upper需不需要写返回值?—— 需要,返回值会呈现在页面上 //upper收到什么参数?—— 过滤器前方的表达式执行的结果 upper(value,num){//从第二个参数开始是过滤器传递过来的值 return value.toUpperCase() }, cut(value){ return value.slice(0,2) } }
-
全局过滤器
Vue.filter('upper',function(){ return value.toUpperCase() })
-
使用过滤器:xxx是表达式
- 正常使用:
{{ xxx | 过滤器}}
- 可以传参:
{{ xxx | 过滤器(666)}}
- 可以串联:
{{ xxx | 过滤器1(666) | 过滤器2(777) }}
- 正常使用:
过滤器回调中的
this
不是vm
,也不是vc
!(这是一个特殊情况)。
2. Vue 插件
Vue
插件的本质是什么?—— 本质是一个包含install
方法的对象。Vue
插件有什么用?—— 增强你的Vue
。- 如何安装(应用)一个
Vue
的插件呢? —— 使用Vue.use(插件)
即可。
插件的存在,让
Vue
的可玩性变得很大,可以集成很多优秀的:组件、指令、过滤器、原型方法 等。
export default {
/*
1.install方法何时调用?—— 当有人使用该插件的时候
2.install中的this是谁?—— 当前插件对象(几乎不用)
3.install接收什么参数?—— 第一个参数为Vue构造函数,第二个为传递过来的参数
*/
install(Vue,n){
// 注册一个全局组件
Vue.component('Hello',Hello)
// 注册一个全局自定义指令
Vue.directive('big',function(element,{value}){
element.innerText = value*10
})
// 注册一个全局的过滤器
Vue.filter('cut',function(value){
return value.slice(0,n)
})
// Vue的原型上放置一个方法
Vue.prototype.welcome = function(){
alert('欢迎你呀')
}
// Vue的原型上放置一个数据
Vue.prototype.version = 'V1.0'
}
}
//使用
import plugin from './plugins/index'
Vue.use(plugin,4)
3. 样式绑定
3.1 class 样式绑定
写法 :class="xxx"
,其中的 xxx
可以是:①字符串、②对象、③数组。
- 字符串写法适用于:类名不确定。
- 对象写法适用于:个数确定、类名确定,但不确定用不用。—— 用的多!
- 数组写法适用于:个数、类名都不确定。
<!-- 动态绑定class样式 —— 第一种写法:字符串写法 -->
<div class="basic" :class="str">你好啊</div>
<!-- 动态绑定class样式 —— 第二种写法:对象写法 -->
<div class="basic" :class="obj">你好啊</div>
<!-- 动态绑定class样式 —— 第三种写法:数组写法 -->
<div class="basic" :class="arr">你好啊</div>
data() {
return {
str:'happy',
obj:{
atguigu1:false,
atguigu2:false,
atguigu3:false,
},
arr:['atguigu1','atguigu2','atguigu3']
}
},
3.2 style 样式绑定
写法 :style="xxx"
,其中的 xxx
也可以是:①字符串、②对象、③数组。
- 字符串写法适用于:属性名、属性值,都不确定。—— 了解即可。
- 对象写法适用于:属性名确定,但值不确定。 —— 熟练掌握。
- 数组写法适用于:属性名、属性值,都不确定 ——了解即可。
<!-- 字符串写法 -->
<h2 :style="str">你好啊</h2>
<!-- 对象写法 -->
<h2 :style="obj">你好啊</h2>
<!-- 数组写法 -->
<h2 :style="arr">你好啊</h2>
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'}
]
}
},
4. Vue 封装的:动画、过渡
4.1 Vue 封装的动画
- 第一步:在目标元素外包裹
<transition name="xxx">
; - 第二步:编写样式
- 进入时样式:
xxx-enter-active
- 离开时样式:
xxx-leave-active
- 进入时样式:
<transition name="hello1">
<h1 v-show="isShow">你好啊</h1>
</transition>
@keyframes atguigu {
from{
transform: translateX(0);
}
to{
transform: translateX(-100%);
}
}
.hello1-leave-active {
animation: atguigu 1s linear;
}
.hello1-enter-active {
animation: atguigu 1s linear reverse;
}
4.2 Vue 封装的过渡
-
第一步:在目标元素外包裹
<transition name="xxx">
; -
第二步:编写样式
-
进入样式:
- 进入的起点(
xxx-enter
) - 进入的终点(
xxx-enter-to
)
- 进入的起点(
-
离开样式
- 离开的始点(
xxx-leave
) - 离开的终点(
xxx-leave-to
)
- 离开的始点(
-
-
给发生变化的元素加:
transition: 1s all linear;
<--html-->
<transition name="title">
<h1 v-show="isShow">你好啊</h1>
</transition>
h1 {
background-color: orange;
font-size: 60px;
transition: 1s linear;
}
/* 离开的起点、来的终点 */
.title-leave,.title-enter-to {
transform: translateX(0);
}
/* 离开的终点、来的起点 */
.title-leave-to,.title-enter {
transform: translateX(-100%);
}
4.3 集成第三方动画库
-
第一步:安装,
npm i animate.css
-
第二步:引入,
import 'animate.css';
-
第三步:
<transition enter-active-class="animate提供的进入类名" leave-active-class="animate提供的离开类名" > <h2 class="animate__animated" v-if="a">你好啊!</h2> </transition>
5. (拓展)动态组件渲染
component 由is属性决定渲染哪个组件
is中可以写字符串
写字符串的时候,写已注册的组件名
5.1普通使用
<component is="Home"></component>
import Home from './Home.vue'
import Search from './Search.vue'
export default {
name: "Comp",
components: { Home, Search },
}
5.2 动态起来
<button @click="compName = 'abc'">展示首页</button>
<button @click="compName = 'Search'">展示搜索页</button>
<component :is="compName"></component>
<script>
import Home from './Home.vue'
import Search from './Search.vue'
export default {
name: "Comp",
components: { abc: Home, Search },
data() {
return {
compName: 'abc'
}
}
}
</script>
5.3 自动引入 require.context()
使用:
<template>
<div class="box">
<h4>自动引入</h4>
<!--
v-for循环对象的时候 value是属性值,key是属性名
value是组件的配置项,
key 是组件名
-->
<div v-for="(value, key) in compObj" :key="key">
<component :is="value"></component>
</div>
</div>
</template>
<script>
// require.context() 是webpack提供的
// 功能: 匹配获得某个路径下对应类型的所有文件
// 参数:
// 参数一: 要匹配的路径(相对路径)
// 参数二: 是否要进行深度匹配(是否匹配参数一路径下的子路径)
// 参数三: 要匹配的文件
// 返回值: 返回值是一个函数,这个函数不传参会报错,不能直接调用
// 需要传参路径才能被调用
// 如何理解:
// 去'./support'这个路径下匹配所有的.vue文件,false代表不深度匹配,得到一个容器,是一个函数
// 这个函数需要传入路径才能被调用,调用之后可以得到该路径文件的内容
const ctx = require.context('./support', false, /.vue$/)
console.log( ctx('./Home.vue') ) // ctx传入的路径是相对路径,相对于参数一的路径
// 这个容器 ctx 是个函数,它身上由三个方法,我们只关注一个 keys()
// ctx.keys() 拿到容器中两个文件的路径
// console.log( ctx.keys() )
// let compObj = {}
// ctx.keys().forEach(path => { // 容器文件的路径
// console.log( ctx(path) )
// const comp = ctx(path).default // 拿到组件的配置项
// compObj[comp.name] = comp
// })
// console.log(compObj)
// {
// 组件名: 组件配置项,
// 组件名: 组件配置项
// }
// {
// Home: Home的配置项,
// Seach: Search的配置项
// }
export default {
name: "autoimport",
computed: {
compObj() {
let compObj = {}
// ctx.keys() -> ['./Home.vue', './Search.vue', './Detail.vue']
ctx.keys().forEach(item => { // item 是容器文件的路径
const comp = ctx(item).default // 拿到组件的配置项
compObj[comp.name] = comp
})
console.log(compObj)
return compObj
}
}
}
</script>
<style scoped>
</style>
目录结构
6. $set的使用
作用:给数据添加响应式的数据
vue2 的 API 中 $set
export default {
data() {
return {
name: '张三'
}
}
}
// 这里的 name 是响应式数据
this.name = "李四" // 页面会自动更新
this.age = 18 // 期望有一个age的响应式数据,但是这样写并不能把age属性变成响应式的
this.$set(this, 'age', 18) // 这样添加age属性才能变成响应式的
// 底层实现 Object.defineProperty(this, 'age', { get() {}, set(){} })
vue3 中移除了 $set (不需要了),为什么?
目标:为了将数据变成一个响应的
实现:
- 在vue2中,响应式数据的实现是使用 Object.defineProperty() 这个方法实现的,这个方法是针对某一个对象下的某个属性的,如果在初始化数据的时候,没有这个数据,使用 $set 底层调用 Object.defineProperty() 给当前实例(数据)添加响应式
- 在vue3中,响应式数据的底层实现是 proxy,当创建出一个数据的时候
new Proxy(obj, { set(){}, get() {} })
, 参数二是配置对象的拦截器的,这里真对的是这个对象,当给这个对象添加属性的时候,例如obj.inputVisible = true
是会经过 set 方法的,所以不需要单独对 inputVisible 进行处理了
7、nextTick
nextTick接受一个回调函数 可以在DOM更新之后拿取页面的新的DOM
this.$nextTick(()=>{
在这里获取dom更新后的值
})
8、scoped 的作用
作用:scoped将样式限制在当前组件所有元素和子组件的根标签上
加scoped会发生什么事情?
干了两件事:
- 当组件内的style加了scoped会给【当前组件所有标签】和【子组件的根标签】加一个属性 data-v-xxx
data-v-xxx 这个xxx是一个hash值,唯一的(整个页面唯一的),vue给我们加的 - 加scoped这个组件中的style中的css样式会变
不加scoped的样式
h2 {
color: red;
}
加了scoped的样式
h2[data-v-xxx] {
color: red;
}
<style lang="scss" scoped>
//修改element-ui中的样式这样直接改不生效 因为样式只是加在组件的根标签上 所有不生效
.el-carousel__button {
width: 20px;
height: 20px;
background-color: red;
border-radius: 50%;
}
</style>
如何解决?
使用深度作用选择器,总共有三种
- css写法
格式:
.a >>> .b { 样式 }
>>> .b { 样式 }
- less写法
.a /deep/ .b { 样式 }
/deep/ .b { 样式 }
- scss 和 less 都好使 -------------- 注意: v-deep() 写法是 vue 给我们提供的
- vue3写法
.a::v-deep(.b) { 样式 }
::v-deep(.b) { 样式 }
- vue2写法
.a ::v-deep .b { 样式 }
::v-deep .b { 样式 }