# Vue2

初始Vue

课程简介

1673878084955

1673878124980

1673878174537

Vue简介

Vue是什么

1673878207116

谁开发的Vue

1673878243175

Vue的特点

1673878289914

1673878318714

1673878402295

1673878430786

1673878484229

1673878508465

1673878545870

Vue官网使用指南

Vue.js官网 : https://cn.vuejs.org/index.html

搭建Vue开发环境

vuejs的引入

1
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>

Vue Devtools

关闭生产提醒

1
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

页签图标

1
//略...随便找一个放根目录中就行 (favicon.ico)

Hello小案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<!-- 准备好一个容器 -->
<div id="app">
<!-- 04 渲染数据到页面中 -->
<h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
</div>

<script type="text/javascript" >
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

//01 创建Vue实例
new Vue({
el:'#app', //02 el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
data:{ //03 data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
name:'atguigu',
address:'北京'
}
})
</script>
</body>

对Hello案例的分析

  • 01 想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象;

  • 02 app 容器里的代码依然符合 html 规范,只不过混入了一些特殊的 Vue 语法;

  • 03 app 容器里的代码被称为【Vue模板】;

  • 04 Vue 实例和容器是一一对应的;

  • 05 真实开发中只有一个 Vue 实例,并且会配合着组件一起使用;

  • 06 {{xxx}} 中的 xxx 要写 js 表达式,且 xxx 可以自动读取到 data 中的所有属性;

  • 07 一旦 data 中的数据发生改变,那么页面中用到该数据的地方也会自动更新;

  • 08 js 表达式 和 js 代码(语句)的区别:

    • 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
      • (1). a
      • (2). a+b
      • (3). demo(1)
      • (4). x === y ? ‘a’ : ‘b’
    • js 代码(语句)
      • (1). if(){ }
      • (2). for(){ }
  • 09 vue 实例与容器的对应

    • 01 vue 实例与容器只能一一对应
    • 02 一个 Vue 实例管理多个容器,只会接管第一个容器
    • 03 多个 vue 实例管理一个容器,第一个容器能正常解析,第二个报错

模板语法

  • Vue 模板语法有 2 大类:
  • 01 插值语法:
    • 功能:用于解析标签体内容。
    • 写法:{{xxx}},xxx 是 js 表达式,且可以直接读取到 data 中的所有属性。
  • 02 指令语法:
    • 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…..)。
    • 举例:v-bind:href="xxx" 或 简写为 :href="xxx",xxx 同样要写 js 表达式,且可以直接读取到 data 中的所有属性。
    • 备注:Vue 中有很多的指令,且形式都是:v-????,此处我们只是拿 v-bind 举个例子。

插值语法

1
2
3
4
5
<!-- 准备好一个容器-->
<div id="root">
<h1>插值语法</h1>
<h3>你好,{{name}}</h3>
</div>
1
2
3
4
5
6
7
8
9
10
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
el:'#root',
data:{
name:'jack'
}
})
</script>

指令语法

1
2
3
4
5
6
<!-- 准备好一个容器-->
<div id="root">
<h1>指令语法</h1>
<a v-bind:href="school.url.toUpperCase()" x="hello">点我去{{school.name}}学习1</a>
<a :href="school.url" x="hello">点我去{{school.name}}学习2</a>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el:'#root',
data:{
school:{
name:'尚硅谷',
url:'http://www.atguigu.com',
}
}
})
</script>

数据绑定

  • Vue 中有 2 种数据绑定的方式:
    • 01 **单向绑定(v-bind)**:数据只能从 data 流向页面。
    • 02 **双向绑定(v-model)**:数据不仅能从 data 流向页面,还可以从页面流向 data。
    • 03 备注:
      • 01 双向绑定一般都应用在表单类元素上(如:input、select等)
      • 02 v-model:value 可以简写为 v-model,因为 v-model 默认收集的就是value值

单向数据绑定

1
2
3
4
5
6
7
8
9
10
11
<!-- 准备好一个容器-->
<div id="root">
<!-- 普通写法 -->
单向数据绑定:<input type="text" v-bind:value="name"><br/>

<!-- 简写 -->
单向数据绑定:<input type="text" :value="name"><br/>

<!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
<!-- <h2 v-model:x="name">你好啊</h2> -->
</div>
1
2
3
4
5
6
7
8
9
10
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
el:'#root',
data:{
name:'尚硅谷'
}
})
</script>

双向数据绑定

1
2
3
4
5
6
7
8
9
10
11
<!-- 准备好一个容器-->
<div id="root">
<!-- 普通写法 -->
双向数据绑定:<input type="text" v-model:value="name"><br/>

<!-- 简写 v-model 默认绑定是是 value 属性-->
双向数据绑定:<input type="text" v-model="name"><br/>

<!-- 如下代码是错误的,因为 v-model 只能应用在表单类元素(输入类元素)上 -->
<!-- <h2 v-model:x="name">你好啊</h2> -->
</div>
1
2
3
4
5
6
7
8
9
10
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
el:'#root',
data:{
name:'尚硅谷'
}
})
</script>

el和data的两种写法

  • data 与 el 的2种写法
  • 01 el 有 2 种写法
    • (1). new Vue 时候配置 el 属性。
    • (2). 先创建 Vue 实例,随后再通过 vm.$mount('#root') 指定 el 的值。
  • 02 data 有 2 种写法
    • (1). 对象式
    • (2). 函数式
    • 如何选择:目前哪种写法都可以,以后学习到组件时,data 必须使用函数式,否则会报错。
  • 03 一个重要的原则:
    • 由 Vue 管理的函数,一定不要写箭头函数,一旦写了箭头函数,this 就不再是 Vue 实例了,而是指向 window。
1
2
3
4
<!-- 准备好一个容器-->
<div id="root">
<h1>你好,{{name}}</h1>
</div>

el的两种写法

1
2
3
4
5
6
7
8
9
10
11
12
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

//el的两种写法
const app = new Vue({
//el:'#root', //第一种写法
data:{
name:'尚硅谷'
}
})
app.$mount('#root') //第二种写法
</script>

data的两种写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

//data的两种写法
new Vue({
el:'#root',
/****** data的第一种写法:对象式 ******/
/* data:{
name:'尚硅谷'
} */

/****** data的第二种写法:函数式 ******/
data(){
console.log('@@@',this) //此处的this是Vue实例对象,写成箭头函数则指向 window
return{
name:'尚硅谷'
}
}
})
</script>

理解MVVM

image-20220317224107033

Vue中的MVVM

  • MVVM 模型
    • M:模型(Model) :data 中的数据
    • V:视图(View) :模板代码
    • VM:视图模型(ViewModel):Vue 实例
  • 观察发现:
    • 01 data 中所有的属性,最后都出现在了 vm 身上。
    • 02 vm 身上所有的属性 及 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
<body>
<!-- 准备好一个容器 View -->
<div id="root">
<h1>学校名称:{{name}}</h1>
<h1>学校地址:{{address}}</h1>
<!-- <h1>测试一下1:{{1+1}}</h1>
<h1>测试一下2:{{$options}}</h1>
<h1>测试一下3:{{$emit}}</h1>
<h1>测试一下4:{{_c}}</h1> -->
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({ //ViewModel
el:'#root',
data:{ // Model
name:'尚硅谷',
address:'北京',
}
})
console.log(vm)
</script>

数据代理

Object.defineProperty方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<script type="text/javascript" >
let person = {
name:'张三',
sex:'男',
}

//给 person 对象设置 age 属性
Object.defineProperty(person,'age',{
value:18, //设置的属性值
enumerable:true, //控制属性是否可以枚举,默认值是false
writable:true, //控制属性是否可以被修改,默认值是false
configurable:true //控制属性是否可以被删除,默认值是false
})
//打印由 person 对象的所有的属性名组成的数组, age 要可枚举,才能遍历到,未设置则默认为不可枚举
console.log(Object.keys(person))// ['name', 'sex', 'age']
console.log(person)//{name: '张三', sex: '男', age: 18}
</script>
</body>
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
<body>
<script type="text/javascript">
let number = 18
let person = {
name: '张三',
sex: '男',
}

Object.defineProperty(person, 'age', {
//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值 number 就是 age 的值
get(){
console.log('有人读取age属性了')
return number
},

//当有人修改person的age属性时,set函数(setter)就会被调用,且 value 会收到修改后的具体值
set(value){
console.log('有人修改了age属性,且值是',value)
number = value
}
})

//打印由 person 对象的所有的属性名组成的数组, age 要可枚举,才能遍历到,未设置则默认为不可枚举
console.log(Object.keys(person))// ['name', 'sex']
console.log(person)//{name: '张三', sex: '男'}
</script>
</body>

理解数据代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<body>
<!-- 数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)-->
<script type="text/javascript" >
let obj = {x:100}
let obj2 = {y:200}

Object.defineProperty(obj2,'x',{
get(){
return obj.x
},
set(value){
obj.x = value
}
})
</script>
</body>

Vue中的数据代理

  • 01 Vue 中的数据代理:
    • 通过 vm 对象来代理 data 对象中属性的操作(读/写)
  • 02 Vue 中数据代理的好处:
    • 更加方便的操作 data 中的数据
  • 03 基本原理:
    • 通过 Object.defineProperty() 把 data 对象中所有属性添加到 vm 上。
    • 为每一个添加到 vm 上的属性,都指定一个 getter/setter。
    • 在 getter/setter 内部去操作(读/写)data 中对应的属性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
name:'尚硅谷',
address:'宏福科技园'
}
})
</script>

1673878604013

事件处理

事件的基本使用

  • 01 使用 v-on:xxx 或 @xxx 绑定事件,其中 xxx 是事件类型;
  • 02 事件的回调需要配置在 methods 对象中,最终会放在 vm 上;
  • 03 methods中 配置的函数,不要用箭头函数!否则 this 就不是 Vue 的实例对象 vm 了, 而是 window;
  • 04 methods 中配置的函数,都是被 Vue 所管理的函数,this 的指向是 vm 或 组件实例对象;
  • 05 @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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<!--点击事件绑定的 完整写法 和 简写 -->
<button v-on:click="showInfo">点我提示信息</button>
<button @click="showInfo">点我提示信息</button>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#root',
data:{
name:'尚硅谷',
},
methods:{
showInfo(event){
console.log(event.target.innerText) //绑定事件时不传参,会默认传递 event 事件对象
console.log(this) //此处的this是Vue的实例对象 vm ,若此处写成箭头函数,则 this 为 window
alert('同学你好!')
},
}
})
</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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<button @click="showInfo1">点我提示信息1(不传参)</button>
<button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
name:'尚硅谷',
},
methods:{
showInfo1(event){
console.log(event.target.innerText) //绑定事件时不传参,会默认传递 event 事件对象
alert('同学你好!')
},
showInfo2(event,number){ //若需要event对象和传递参数,则在绑定事件时需要用 $event 占位,否则event事件对象无法传递
console.log(event,number)
alert('同学你好!!')
}
}
})
</script>

事件修饰符

  • Vue 中的事件修饰符:
    • 1 .prevent:阻止默认事件(常用);
    • 2 .stop:阻止事件冒泡(常用);
    • 3 .once:事件只触发一次(常用);
    • 4 .capture:使用事件的捕获模式;
    • 5 .self:只有 event.target 是当前操作的元素时才触发事件;
    • 6 .passive:事件的默认行为立即执行,无需等待事件回调执行完毕;

阻止默认事件 .prevent

1
2
<!-- .prevent 阻止默认事件(常用) -->
<a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>

阻止事件冒泡 .stop

1
2
3
4
5
6
7
<!-- .stop  阻止事件冒泡(常用) -->
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我提示信息</button>

<!-- 修饰符可以连续写 -->
<a href="http://www.atguigu.com" @click.prevent.stop="showInfo">点我提示信息</a>
</div>

事件只触发一次 .once

1
2
<!-- 事件只触发一次(常用) -->
<button @click.once="showInfo">点我提示信息</button>

事件的捕获模式 .capture

1
2
3
4
5
6
7
<!-- 使用事件的捕获模式 该事件将会在捕获阶段进行处理-->
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">
div2
</div>
</div>

只有是操作的元素时才触发

1
2
3
4
<!-- .self 只有event.target是当前操作的元素时才触发事件; -->
<div class="demo1" @click.self="showInfo">
<button @click="showInfo">点我提示信息</button>
</div>

默认行为立即执行

1
2
3
4
5
6
7
<!-- .passive 事件的默认行为立即执行,无需等待事件回调执行完毕; -->
<ul @wheel.passive="demo" class="list">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>

键盘事件

  • 01 Vue 中常用的按键别名:
    • 回车 => enter
    • 删除 => delete (捕获“删除”和“退格”键)
    • 退出 => esc
    • 空格 => space
    • 换行 => tab (特殊,必须配合 keydown 去使用,因为配合 keyup 使用时,没触发事件,焦点就已经切走)
    • 上 => up
    • 下 => down
    • 左 => left
    • 右 => right
  • 02 Vue 未提供别名的按键,可以使用按键原始的 key 值去绑定,但注意要转为 kebab-case(短横线命名)
  • 03 系统修饰键(用法特殊):ctrl、alt、shift、meta
    • (1).配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
    • (2).配合 keydown 使用:正常触发事件。
  • 04 也可以使用 keyCode 去指定具体的按键(不推荐)
  • 05 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
24
25
26
27
28
29
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo">
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

//自定义了一个别名按键
Vue.config.keyCodes.huiche = 13

new Vue({
el:'#root',
data:{
name:'尚硅谷'
},
methods: {
showInfo(e){
// e.key:键名 e.keyCode: 键码
console.log(e.key,e.keyCode)
// 输入框的value值
console.log(e.target.value)
}
},
})
</script>

计算属性

姓名案例

插值语法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<!-- 准备好一个容器-->
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
全名:<span>{{firstName}}-{{lastName}}</span>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
}
})
</script>

methods方法实现

  • {{..}} 中写方法自调用,是将该方法的返回值插入此处,需要加括号,不同于前面的事件回调.
  • 模板每次重新解析,该方法就会重新执行,没有缓存, 不同于计算属性只执行一次,计算属性有缓存
  • 一但 data 中的数据改变,则模块将会重新解析,用到 data 数据的地方就会重新渲染
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
<body>
<!-- 准备好一个容器-->
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
全名:<span>{{fullName()}}</span>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
methods: {
fullName(){
//方法中的 this 指向 vue实例对象(vm)
console.log('@---fullName')
return this.firstName + '-' + this.lastName
}
},
})
</script>

计算属性-完整写法

    1. 计算属性定义:要用的属性不存在,要通过已有属性计算得来。
    1. 计算属性原理:底层借助了Objcet.defineproperty 方法提供的 getter 和 setter
    1. get 函数什么时候执行
      1. 初次读取时会执行一次。
      1. 当依赖的属性发生改变时会被再次调用。
    1. 优势:与 methods 实现相比,内部有缓存机制(复用),效率更高,调试方便。若依赖的属性不变,则多次调用也只会执行一次
    1. 备注:
      1. 计算属性最终会出现在 vm 上,直接读取使用即可。
      1. 如果计算属性要被修改,那必须写 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
34
35
36
37
38
39
40
41
<body>
<!-- 准备好一个容器-->
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
全名:<span>{{fullName}}</span> <br/><br/>
全名:<span>{{fullName}}</span> <br/><br/>
全名:<span>{{fullName}}</span> <br/><br/>
全名:<span>{{fullName}}</span>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
computed:{
fullName:{
//get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
//get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
get(){
//console.log('get被调用了')
console.log(this) //此处的this是vue的实例对象(vm),Vue内部维护好的,若写成箭头函数,则this指向window
return this.firstName + '-' + this.lastName
},
//set什么时候调用? 当fullName被修改时会调用set。set中要引起计算时依赖的数据发生改变 value:用于接收设置的新值
set(value){
//console.log('set',value)
const arr = value.split('-')
this.firstName = arr[0]
this.lastName = arr[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
<body>
<!-- 准备好一个容器-->
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
全名:<span>{{fullName}}</span> <br/><br/>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
},
computed:{
//简写,读取该计算属性时,会充当 get 调用
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
25
26
27
28
29
30
31
32
<body>
<div id="root">
<!-- 3.计算属性的使用 -->
<h2>今天天气很{{info}}</h2>
<!-- 4.事件的触发 -->
<button @click="changeWeather">切换天气</button>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
//0.准备的数据,用于控制切换
isHot:true,
},
computed:{
//2.根据data中的数据,计算出需要的属性,依赖的数据改变,就会重新调用,返回值将作为该计算属性的属性值
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
//1.通过事件回调,修改data中的数据
changeWeather(){
this.isHot = !this.isHot
}
},
})
</script>

监视属性

  • 监视属性 watch:
      1. 监视属性,可以监视 data 中的数据改变,也可以监视计算属性的改变
      1. 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
      1. 监视的属性必须存在,才能进行监视!!若监视的属性不存在,不会报错,新值旧值都是 undefined
      1. 监视的两种写法:
        1. new Vue 时传入watch 配置
        1. 通过 vm.$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
<body>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
watch:{
//监视属性 isHot 的变化
isHot:{
immediate:true, //初始化时让handler调用一下,无需等待要监视的数据的改变就会调用一次,新值为data中的值,旧值为undefined
//handler什么时候调用?当isHot发生改变时。
handler(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
27
28
29
30
31
32
33
34
35
36
<body>
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
}
})

// 监视属性 isHot 的变化
vm.$watch('isHot',{
immediate:true, //初始化时让handler调用一下
//handler什么时候调用?只要监视的属性 isHot 发生改变,就会调用。
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})
</script>

深度监视

  • 深度监视:
      1. Vue 中的 watch 默认不监测对象内部值的改变(一层)。
      1. 配置 deep:true 可以监测对象内部值改变(多层)。
  • 备注:
      1. Vue 自身可以监测对象内部值的改变,但 Vue 提供的 watch 默认不可以!
      1. 使用 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
<body>
<div id="root">
<h3>a的值是:{{numbers.a}}</h3>
<button @click="numbers.a++">点我让a+1</button>
<h3>b的值是:{{numbers.b}}</h3>
<button @click="numbers.b++">点我让b+1</button>
<button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
<h3> {{numbers.c.d.e}} </h3>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
numbers:{
a:1,
b:1,
c:{
d:{
e:100
}
}
}
},
watch:{
//监视多级结构中所有属性的变化,只要内部的数据发生变化,该监视属性就会执行
numbers:{
deep:true,//深度监视
handler(){
console.log('numbers改变了')
}
}
}
})
</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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>今天天气很{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
isHot:true,
},
computed:{
info(){
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
watch:{
//简写,不需要写其他配置项时,才可以简写
isHot(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue,this)
}
}
})
</script>

监视属性简写 写法二

1
2
3
4
5
6
7
8
9
10
11
12
13
//正常写法
vm.$watch('isHot',{
immediate:true, //初始化时让handler调用一下
deep:true,//深度监视
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
})

//简写,不需要写其他配置项时,才可以简写
vm.$watch('isHot',(newValue,oldValue)=>{
console.log('isHot被修改了',newValue,oldValue,this)
})

计算属性与监视属性的对比

  • computed 和 watch 之间的区别:
      1. computed 能完成的功能,watch 都可以完成。
      1. watch 能完成的功能,computed 不一定能完成,例如:watch 可以进行异步操作。
  • 两个重要的小原则:
      1. 所被 Vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或 组件实例对象。
      1. 所有不被 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
<body>
<div id="root">
姓:<input type="text" v-model="firstName"> <br/><br/>
名:<input type="text" v-model="lastName"> <br/><br/>
全名:<span>{{fullName}}</span> <br/><br/>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
fullName:'张-三'
},
watch:{
firstName(val){ // val 为新值
setTimeout(()=>{
console.log(this)
this.fullName = val + '-' + this.lastName
},1000);
},
lastName(val){ // val 为新值
this.fullName = this.firstName + '-' + val
}
}
})
</script>

绑定样式

绑定class类名样式

  • 绑定样式:
    1. class 样式
    • 写法:class=”xxx” , xxx 可以是字符串、对象、数组。
      • 字符串写法适用于:类名不确定,要动态获取。
      • 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
      • 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
    1. style 样式
    • :style=”{fontSize: xxx}” 其中 xxx 是动态值。
    • :style=”[a,b]” 其中 a、b 是样式对象。
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
<body>
<!-- 准备好一个容器-->
<div id="root">
<!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>

<!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div> <br/><br/>

<!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
<div class="basic" :class="classObj">{{name}}</div> <br/><br/>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false

const vm = new Vue({
el:'#root',
data:{
name:'尚硅谷',
mood:'normal',
classArr:['atguigu1','atguigu2','atguigu3'],
classObj:{
atguigu1:false,
atguigu2:false,
}
},
methods: {
changeMood(){
const arr = ['happy','sad','normal']
const index = Math.floor(Math.random()*3)
this.mood = arr[index]
}
},
})
</script>

绑定style样式

  • style 样式
    • :style=”{fontSize: xxx}” 其中 xxx 是动态值。
    • :style=”[a,b]” 其中 a、b 是样式对象。
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
<body>
<div id="root">
<!-- 绑定style样式--对象写法 -->
<div class="basic" :style="styleObj">{{name}}</div> <br/><br/>
<!-- 绑定style样式--数组写法 -->
<div class="basic" :style="styleArr">{{name}}</div>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false

const vm = new Vue({
el:'#root',
data:{
name:"hello world~~",
styleObj:{
fontSize: '40px',
color:'red',
},
styleArr:[
{
fontSize: '40px',
color:'blue',
},
{
backgroundColor:'gray'
}
]
},
})
</script>

条件渲染

v-if与v-show

  • 条件渲染:

  • v-if:

    • 写法:
      • v-if = “表达式”
      • v-else-if = “表达式”
      • v-else = “表达式”
    • 适用于:切换频率较低的场景。
    • 特点:不展示的 DOM 元素会直接被移除。
    • 注意:v-if 可以和 :v-else-if、v-else 一起使用,但要求结构不能被“打断”。
  • v-show

    • 写法:v-show = “表达式”
    • 适用于:切换频率较高的场景。
    • 特点:不展示的 DOM 元素未被移除,仅仅是使用行内样式 (display:none;) 隐藏掉
  • 备注:使用 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
37
38
39
40
41
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>

<!-- 使用v-show做条件渲染 -->
<h2 v-show="false">欢迎来到{{name}}</h2>
<h2 v-show="1 === 1">欢迎来到{{name}}</h2>

<!-- 使用v-if做条件渲染 -->
<h2 v-if="false">欢迎来到{{name}}</h2>
<h2 v-if="1 === 1">欢迎来到{{name}}</h2>

<!-- v-else 和 v-else-if -->
<div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<div v-else-if="n === 3">Vue</div>
<div v-else>哈哈</div>

<!-- v-if与template的配合使用 template 不能与 v-show 一起使用,因为 template 标签不会渲染到页面中, v-show 无法添加行内样式用于隐藏 -->
<template v-if="n === 1">
<h2>你好</h2>
<h2>尚硅谷</h2>
<h2>北京</h2>
</template>

</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false

const vm = new Vue({
el:'#root',
data:{
name:'尚硅谷',
n:0
}
})
</script>

v-if与v-show的区别

  • v-if 为 false 时,其控制的结构在从 DOM 树中移除
  • v-show 为 false 时,其控制的结构不会从 DOM 树中移除,只是给结构标签添加了行内样式 display:none;

列表渲染

v-for指令

    1. 用于展示列表数据
    1. 语法:v-for="(item, index) in xxx" :key="yyy"
    1. 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
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
<body>
<!-- 准备好一个容器-->
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
</li>
</ul>

<!-- 遍历对象 -->
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,k) of car" :key="k">
{{k}}-{{value}}
</li>
</ul>

<!-- 遍历字符串 -->
<h2>测试遍历字符串(用得少)</h2>
<ul>
<li v-for="(char,index) of str" :key="index">
{{char}}-{{index}}
</li>
</ul>

<!-- 遍历指定次数 , number 从 1 开始依次递增 1 -->
<h2>测试遍历指定次数(用得少)</h2>
<ul>
<li v-for="(number,index) of 5" :key="index">
{{index}}-{{number}}
</li>
</ul>
</div>

<script type="text/javascript">
Vue.config.productionTip = false

new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20}
],
car:{
name:'奥迪A8',
price:'70万',
color:'黑色'
},
str:'hello'
}
})
</script>
</body>

key作用与原理

  • 面试题:react、vue 中的 key 有什么作用?(key 的内部原理)

  • 虚拟 DOM 中 key 的作用:

    • key 是虚拟 DOM 对象的标识,当数据发生变化时,Vue 会根据【新数据】生成【新的虚拟DOM】, 随后 Vue 进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
  • 对比规则:

    • 旧虚拟 DOM 中找到了与新虚拟 DOM 相同的 key:
      - ① 经过新旧虚拟 DOM 对比后,发现内容没变, 直接使用之前的真实 DOM!

      • ② 经过新旧虚拟 DOM 对比后,发现内容变了, 则生成新的真实 DOM,随后替换掉页面中之前的真实 DOM。
    • 若在旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key, 则根据新的虚拟 DOM,创建新的真实 DOM,随后渲染到到页面。

  • 用 index 作为 key 可能会引发的问题:

    • 若对数据进行:逆序添加、逆序删除等破坏顺序的操作时:

      • 会产生没有必要的真实 DOM 更新 ==> 界面效果没问题, 但效率低。
    • 如果结构中还包含输入类的 DOM:

      • 会产生错误 DOM 更新 ==> 界面有问题。
  • 开发中如何选择 key:

    • 最好使用每条数据的唯一标识作为 key, 比如 id、手机号、身份证号、学号等唯一值。
    • 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。
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
<body>
<div id="root">
<!-- 遍历数组 -->
<h2>人员列表(遍历数组)</h2>
<button @click.once="add">添加一个老刘</button>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
<input type="text">
</li>
</ul>
</div>

<script type="text/javascript">
Vue.config.productionTip = false

new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20}
]
},
methods: {
add(){
const p = {id:'004',name:'老刘',age:40}
this.persons.unshift(p)
}
},
})
</script>

1648213206274

1673878700524

列表过滤案例(补)

用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
<body>
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="(p,index) of filPerons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>

<script type="text/javascript">
Vue.config.productionTip = false

//用watch实现
// 简述:v-model双向数据绑定输入框和keyWord,输入内容,keyWord改变,则监听属性执行过滤,返回过滤后的数组赋值给 filPerons,再动态渲染
new Vue({
el:'#root',
data:{
keyWord:'',
persons:[
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'003',name:'周杰伦',age:21,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
],
filPerons:[]
},
watch:{
keyWord:{
immediate:true,//上来就执行一次监听属性
handler(val){
this.filPerons = this.persons.filter((p)=>{
return p.name.indexOf(val) !== -1
})
}
}
}
})
})
</script>

用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
<body>
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<ul>
<li v-for="(p,index) of filPerons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>

<script type="text/javascript">
Vue.config.productionTip = false

//用computed实现
//简述:v-model双向数据绑定输入框和keyWord,输入内容,keyWord改变,则计算属性执行过滤,返回过滤后的数组filPerons,再根据 filPerons 动态渲染
new Vue({
el:'#root',
data:{
keyWord:'',
persons:[
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'003',name:'周杰伦',age:21,sex:'男'},
{id:'004',name:'温兆伦',age:22,sex:'男'}
]
},
computed:{
//只要计算属性依赖的数据发生改变,就会重新执行计算属性,重新渲染模板
filPerons(){
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
48
49
50
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字" v-model="keyWord">
<!-- 2.点击按钮,修改sortType的值 -->
<button @click="sortType = 2">年龄升序</button>
<button @click="sortType = 1">年龄降序</button>
<button @click="sortType = 0">原顺序</button>
<ul>
<li v-for="(p,index) of filPerons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
<input type="text">
</li>
</ul>
</div>

<script type="text/javascript">
Vue.config.productionTip = false

new Vue({
el:'#root',
data:{
keyWord:'',
sortType:0, //1.准备一个变量用于控制要如何排序 0原顺序 1降序 2升序
persons:[
{id:'001',name:'马冬梅',age:30,sex:'女'},
{id:'002',name:'周冬雨',age:31,sex:'女'},
{id:'003',name:'周杰伦',age:18,sex:'男'},
{id:'004',name:'温兆伦',age:19,sex:'男'}
]
},
computed:{
//只要计算属性依赖的数据发生改变,就会重新执行计算属性,重新渲染模板
filPerons(){
const arr = this.persons.filter((p)=>{
return p.name.indexOf(this.keyWord) !== -1
})
//3.判断一下是否需要排序 如果 this.sortType == 0 不排序
if(this.sortType){
arr.sort((p1,p2)=>{
//如果 this.sortType === 1 降序 如果 this.sortType !== 1 升序
return this.sortType === 1 ? p2.age-p1.age : p1.age-p2.age
})
}
return arr
}
}
})
</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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>人员列表</h2>
<button @click="updateMei">更新马冬梅的信息</button>
<ul>
<li v-for="(p,index) of persons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>

<script type="text/javascript">
Vue.config.productionTip = false

const vm = new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'马冬梅',age:30,sex:'女'},
{id:'002',name:'周冬雨',age:31,sex:'女'},
{id:'003',name:'周杰伦',age:18,sex:'男'},
{id:'004',name:'温兆伦',age:19,sex:'男'}
]
},
methods: {
updateMei(){
// this.persons[0].name = '马老师' //奏效
// this.persons[0].age = 50 //奏效
// this.persons[0].sex = '男' //奏效
// this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} //不奏效,数据确实是被改掉了,但数据不是响应式的
this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'})//奏效,数据响应式
}
}
})
</script>

Vue检测数据的原理_对象(?)

1
//...

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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h1>学生信息</h1>
<button @click="addSex">添加一个性别属性,默认值是男</button>
<h2>姓名:{{student.name}}</h2>
<h2 v-if="student.sex">性别:{{student.sex}}</h2>
<h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
student:{
name:'tom',
age:{
rAge:40,
sAge:29,
}
}
},
methods: {
addSex(){
// Vue.set(this.student,'sex','男') //Vue.set()方法的写法一 数据是响应式的,但是第一个参数不能是vue的实例对象vm,也不能是data,否则报错
this.$set(this.student,'sex','男') //Vue.set()方法的写法二 数据是响应式的,但是第一个参数不能是vue的实例对象vm,也不能是data,否则报错
}
}
})
</script>

Vue检测数据的原理_数组(?)

1
//...

Vue监视数据总结

  • Vue 监视数据的原理:

    • vue 会监视 data 中所有层次的数据。
    1. 如何监测对象中的数据?
    • 通过 setter 实现监视,且要在 new Vue 时就传入要监测的数据。
      1. 对象中后追加的属性,Vue 默认不做响应式处理
      1. 如需给后添加的属性做响应式,请使用如下API:
      • Vue.set(target,propertyName/index,value) 或 vm.$set(target,propertyName/index,value)
    1. 如何监测数组中的数据?
    • 通过包裹数组更新元素的方法实现,本质就是做了两件事:
    • (1) 调用原生对应的方法对数组进行更新。
    • (2) 重新解析模板,进而更新页面。
    1. 在 Vue 修改数组中的某个元素一定要用如下方法:
      1. 使用这些API: push()、pop()、shift()、unshift()、splice()、sort()、reverse()
      1. Vue.set()vm.$set()
  • 特别注意:Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象 data 添加属性,否则报错!!!

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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h1>学生信息</h1>
<button @click="student.age++">年龄+1岁</button> <br/>
<button @click="addSex">添加性别属性,默认值:男</button> <br/>
<button @click="student.sex = '未知' ">修改性别</button> <br/>
<button @click="addFriend">在列表首位添加一个朋友</button> <br/>
<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br/>
<button @click="addHobby">添加一个爱好</button> <br/>
<button @click="updateHobby">修改第一个爱好为:开车</button> <br/>
<button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br/>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-if="student.sex">性别:{{student.sex}}</h3>
<h3>爱好:</h3>
<ul>
<li v-for="(h,index) in student.hobby" :key="index">
{{h}}
</li>
</ul>
<h3>朋友们:</h3>
<ul>
<li v-for="(f,index) in student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

const vm = new Vue({
el:'#root',
data:{
student:{
name:'tom',
age:18,
hobby:['抽烟','喝酒','烫头'],
friends:[
{name:'jerry',age:35},
{name:'tony',age:36}
]
}
},
methods: {
addSex(){
// Vue.set(this.student,'sex','男')
this.$set(this.student,'sex','男')
},
addFriend(){
this.student.friends.unshift({name:'jack',age:70})
},
updateFirstFriendName(){
this.student.friends[0].name = '张三'
},
addHobby(){
this.student.hobby.push('学习')
},
updateHobby(){
// this.student.hobby.splice(0,1,'开车')
// Vue.set(this.student.hobby,0,'开车')
this.$set(this.student.hobby,0,'开车')
},
removeSmoke(){
this.student.hobby = this.student.hobby.filter((h)=>{
return h !== '抽烟'
})
}
}
})
</script>

收集表单数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 
收集表单数据:
若:<input type="text"/>,则v-model收集的是value值,用户输入的就是value 值。
若:<input type="radio"/>,则 v-model 收集的是value值,且要给标签配置value 值。
若:<input type="checkbox"/>
1.没有配置input 的value属性,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
2.配置input的 value属性:
(1)v-model 的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
(2)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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<body>
<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="http://www.atguigu.com">《用户协议》</a>

<!-- 阻止提交按钮的默认行为: 01.给from绑定submit事件并结合.prevent 02.使用 input type="button" -->
<button>提交</button>
</form>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false

new Vue({
el:'#root',
data:{
userInfo:{
account:'',
password:'',
age:18,
sex:'female',
hobby:[],
city:'beijing',
other:'',
agree:''
}
},
methods: {
demo(){
console.log(JSON.stringify(this.userInfo))
}
}
})
</script>

过滤器

1
2
3
4
5
6
7
8
9
10
<!-- 
过滤器:
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。
语法:
1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}
2.使用过滤器:{{ xxx | 过滤器名}} 或 v-bind:属性 = "xxx | 过滤器名"
备注:
1.过滤器也可以接收额外参数、多个过滤器也可以串联
2.并没有改变原本的数据, 是产生新的对应的数据
-->
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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>显示格式化后的时间</h2>
<!-- 计算属性实现 -->
<h3>现在是:{{fmtTime}}</h3>

<!-- methods实现 -->
<h3>现在是:{{getFmtTime()}}</h3>

<!-- 过滤器实现 从左往右,将左边的结果作为参数传递给右边,最后的返回值替换掉整个{{}} -->
<h3>现在是:{{time | timeFormater}}</h3>
<!-- 过滤器实现(传参) 第一个参数不写,内部也是必传的 -->
<h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
<h3 :x="msg | mySlice">尚硅谷</h3>
</div>

<div id="root2">
<h2>{{msg | mySlice}}</h2>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false
//全局过滤器
Vue.filter('mySlice',function(value){
return value.slice(0,4)
})

new Vue({
el:'#root',
data:{
time:1621561377603, //时间戳
msg:'你好,尚硅谷'
},
computed: {
fmtTime(){
//对于dayjs()方法:不传递参数,就会默认使用当前的时间戳作为参数进行处理
// CDN: https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.0/dayjs.min.js
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'){
// console.log('@',value)
return dayjs(value).format(str)
}
}
})

new Vue({
el:'#root2',
data:{
msg:'hello,atguigu!'
}
})
</script>

内置指令

v-text指令

  • v-text 指令:
    • 作用:向其所在的节点中渲染文本内容。不能解析 html 标签

    • 与插值语法的区别:v-text 会替换掉节点中的内容, 则不会。

1
2
3
4
5
6
7
8
9
10
<!-- 
我们学过的指令:
v-bind : 单向绑定解析表达式, 可简写为 :xxx
v-model : 双向数据绑定
v-for : 遍历数组/对象/字符串
v-on : 绑定事件监听, 可简写为@
v-if : 条件渲染(动态控制节点是否存存在)
v-else : 条件渲染(动态控制节点是否存存在)
v-show : 条件渲染 (动态控制节点是否展示)
-->
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<!-- 准备好一个容器-->
<div id="root">
<div>你好,{{name}}</div>
<div v-text="name"></div>
<div v-text="str"></div>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
el:'#root',
data:{
name:'尚硅谷',
str:'<h3>你好啊!</h3>'
}
})
</script>

v-html指令

  • 谷歌浏览器插件 : Cookie-Editor , 用于批量导出谷歌浏览器的 cookie 信息

1673878745259

  • v-html 指令:
    1. 作用:向指定节点中渲染包含 html 结构的内容。
    1. 与插值语法的区别:
      1. v-html 会替换掉节点中所有的内容, 则不会。
      1. v-html 可以识别 html 结构。
    1. 严重注意:v-html 有安全性问题!!!!
    • 在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击。
    • 一定要在可信的内容上使用 v-html,永不要用在用户提交的内容上!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<!-- 准备好一个容器-->
<div id="root">
<div>你好,{{name}}</div>
<div v-html="str"></div>
<div v-html="str2"></div>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
el:'#root',
data:{
name:'尚硅谷',
str:'<h3>你好啊!</h3>',
str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',
}
})
</script>

v-cloak指令

  • v-cloak 指令(没有值):
    • 本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性。
    • 使用 css 配合 v-cloak 可以解决网速慢时页面展示出 的问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<style>
[v-clock]{
display:none;
}
</style>

<body>
<div id="root">
<h2 v-cloak>{{name}}</h2>
</div>
<!-- 目的仅为延时引入vue -->
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
el:'#root',
data:{
name:'尚硅谷'
}
})
</script>

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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2 v-once>初始化的n值是:{{n}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

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
<body>
<div id="root">
<h2 v-pre>Vue其实很简单</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
el:'#root',
data:{
n: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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>{{name}}</h2>
<h2>当前的n值是:<span v-text="n"></span> </h2>
<!-- 若指令是驼峰命名,则需要用 " - " 分隔 -->
<!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> -->
<h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
<button @click="n++">点我n+1</button>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false

new Vue({
el:'#root',
data:{
name:'尚硅谷',
n:1
},
directives:{
//局部指令:
//big 函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
//第一个参数:绑定该指令所在的真实 DOM 节点 第二个参数:是一个对象,包含有指令名,value值,绑定的数据等...
//定义自定义指令时,不用写" v- ",但是使用指令时,要写成" v-big "
big(element,binding){
console.log('big',this) //注意此处的this是window
element.innerText = binding.value * 10 // binding.value 就是使用该指令时绑定的 n 值
}
}
})
</script>

自定义指定-对象式

  • 需求1:定义一个 v-big 指令,和 v-text 功能类似,但会把绑定的数值放大 10 倍。
  • 需求2:定义一个 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
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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2>{{name}}</h2>
<h2>当前的n值是:<span v-text="n"></span> </h2>
<!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> -->
<h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
<button @click="n++">点我n+1</button>
<hr/>
<input type="text" v-fbind:value="n">
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false

//定义全局指令
Vue.directive('fbind',{
//指令与元素成功绑定时(一上来)
bind(element,binding){
element.value = binding.value
},
//指令所在元素被插入页面时
inserted(element,binding){
element.focus()
},
//指令所在的模板被重新解析时
update(element,binding){
element.value = binding.value
}
})

new Vue({
el:'#root',
data:{
name:'尚硅谷',
n:1
},
directives:{
//big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
/* 'big-number'(element,binding){
// console.log('big')
element.innerText = binding.value * 10
}, */
big(element,binding){
console.log('big',this) //注意此处的this是window
// console.log('big')
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
}
}
}
})
</script>

自定义指令-总结

  • 自定义指令总结:
    1. 定义语法:
1
2
3
4
5
6
7
// (1).局部指令:
// new Vue({ new Vue({
// directives:{指令名:配置对象} 或 directives{指令名:回调函数}
// }) })

// (2).全局指令:
// Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
    1. 配置对象中常用的 3 个回调:
    • bind:指令与元素成功绑定时调用。
    • inserted:指令所在元素被插入页面时调用。
    • update:指令所在模板结构被重新解析时调用。
    1. 备注:
    • 指令定义时不加 v-,但使用时要加 v-;
    • 指令名如果是多个单词,要使用 kebab-case 命名方式,不要用 camelCase 命名。

生命周期

生命周期

引出生命周期

  • 生命周期:
    • 又名:生命周期回调函数、生命周期函数、生命周期钩子。
    • 是什么: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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h2 v-if="a">你好啊</h2>
<h2 :style="{opacity}">欢迎学习Vue</h2>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
el:'#root',
data:{
a:false,
opacity:1
},
methods: {

},
//Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted(){
console.log('mounted',this) //this指向vue的实例对象vm
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
61
62
63
64
65
66
67
68
<body>
<!-- 准备好一个容器-->
<div id="root" :x="n">
<h2 v-text="n"></h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="add">点我n+1</button>
<button @click="bye">点我销毁vm</button>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
el: '#root',
data: {
n: 1
},
methods: {
add() {
console.log('add')
this.n++
},
bye() {
console.log('bye')
this.$destroy()
}
},
watch: {
n() {
console.log('n变了')
}
},
beforeCreate() {
// beforeCreate: 在初始化数据监听,数据代理之前调用 无法通过vm访问到data中的数据和methods中的方法
console.log('beforeCreate')
},
created() {
// created : 在数据监听,数据代理初始化完成后,在开始解析模板,声明虚拟DOM之前调用,可以通过vm访问到data中的数据和methods中的方法
console.log('created')
},
beforeMount() {
// beforeMount : 在生成虚拟DOM完毕后,在虚拟DOM转为真实DOM之前调用. 页面中的是未解析的DOM结构,此时对DOM的操作都无效
console.log('beforeMount')
},
mounted() {
// mounted : 在生成真实DOM后调用,页面中是经过Vue编译的DOM,此时对DOM操作有效
// 初始化过程结束,一般在此进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等初始化操作...
console.log('mounted')
},
beforeUpdate() {
// beforeUpdate : 新虚拟DOM生成前调用,此时数据是新的,但还没有生成新的虚拟DOM,页面也还是旧的,页面尚未和数据保持同步
console.log('beforeUpdate')
},
updated() {
// updated : 页面更新完毕时调用,在此之前已完成新虚拟DOM的创建及新旧虚拟DOM的比较,并完成了页面的更新...此时数据是新的,页面和数据保持同步
console.log('updated')
},
beforeDestroy() {
//beforeDestroy : 在销毁之前调用,此时data,methods,指令等都还可用,一般在此阶段执行关闭定时器,取消消息订阅,解绑自定义事件等收尾操作...
console.log('beforeDestroy')
},
destroyed() {
// destroyed : 销毁完毕后执行
console.log('destroyed')
},
})
</script>

生命周期总结

  • 常用的生命周期钩子:
      1. mounted: 发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
      1. beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
  • 关于销毁 Vue 实例
      1. 销毁后借助 Vue 开发者工具看不到任何信息。
      1. 销毁后自定义事件会失效,但原生 DOM 事件依然有效。
      1. 一般不会在 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
36
37
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2 :style="{opacity}">欢迎学习Vue</h2>
<button @click="opacity = 1">透明度设置为1</button>
<button @click="stop">点我停止变换</button>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
el:'#root',
data:{
opacity:1
},
methods: {
stop(){
this.$destroy()
}
},
//Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
mounted(){
console.log('mounted',this)
this.timer = setInterval(() => {
console.log('setInterval')
this.opacity -= 0.01
if(this.opacity <= 0) this.opacity = 1
},16)
},
beforeDestroy() {
clearInterval(this.timer)
console.log('vm即将驾鹤西游了')
},
})
</script>

组件

对组件的理解

1673878815411

1673878846287

1673878918321

非单文件组件

  • Vue 中使用组件的三大步骤:

    • 一、定义组件(创建组件)
    • 二、注册组件
    • 三、使用组件(写组件标签)
  • 一、如何定义一个组件?

    • 使用 Vue.extend(options) 创建,其中 options 和 new Vue(options) 时传入的那个 options 几乎一样,但也有点区别;
        1. el 不要写,为什么? ——— 最终所有的组件都要经过一个 vm 的管理,由 vm 中的 el 决定服务哪个容器。
        1. data 必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
      • 备注:使用 template 可以配置组件结构。
  • 二、如何注册组件?

  • 局部注册:靠 new Vue 的时候传入 components 选项

  • 全局注册:靠 Vue.component(‘组件名’,组件)

三、使用组件:

1
<school></school>
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
<body>
<!-- 准备好一个容器-->
<div id="root">
<hello></hello>
<hr>
<h1>{{msg}}</h1>
<hr>
<!-- 第三步:编写组件标签 -->
<school></school>
<hr>
<!-- 第三步:编写组件标签 -->
<student></student>
</div>

<div id="root2">
<hello></hello>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false

//第一步:创建school组件
const school = Vue.extend({
template:`
<div class="demo">
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
`,
// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
data(){
return {
schoolName:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.schoolName)
}
},
})

//第一步:创建student组件
const student = Vue.extend({
template:`
<div>
<h2>学生姓名:{{studentName}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
studentName:'张三',
age:18
}
}
})

//第一步:创建hello组件
const hello = Vue.extend({
template:`
<div>
<h2>你好啊!{{name}}</h2>
</div>
`,
data(){
return {
name:'Tom'
}
}
})

//第二步:全局注册组件
Vue.component('hello',hello)

//创建vm
new Vue({
el:'#root',
data:{
msg:'你好啊!'
},
//第二步:注册组件(局部注册)
components:{
school,
student
}
})

new Vue({
el:'#root2',
})
</script>

组件的几个注意点

    1. 关于组件名:
    • 一个单词组成:
      • 第一种写法(首字母小写):school
      • 第二种写法(首字母大写):School
    • 多个单词组成:
      • 第一种写法(kebab-case命名):my-school
      • 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
    • 备注:
      • 组件名尽可能回避 HTML 中已有的元素名称,例如:h2、H2 都不行。
      • 可以使用 name 配置项指定组件在开发者工具中呈现的名字。
    1. 关于组件标签:
  • 第一种写法:<school></school>

  • 第二种写法:<school/>

  • 备注:不用使用脚手架时,<school/> 会导致后续组件不能渲染。

    1. 一个简写方式:
    • 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
<body>
<!-- 准备好一个容器-->
<div id="root">
<h1>{{msg}}</h1>
<school></school>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false

//定义组件
const s = Vue.extend({
name:'atguigu',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
}
})

new Vue({
el:'#root',
data:{
msg:'欢迎学习Vue!'
},
components:{
school:s
}
})
</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
<body>
<!-- 准备好一个容器-->
<div id="root">

</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

//定义student组件
const student = Vue.extend({
name:'student',
template:`
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生年龄:{{age}}</h2>
</div>
`,
data(){
return {
name:'尚硅谷',
age:18
}
}
})

//定义school组件
const school = Vue.extend({
name:'school',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<student></student>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
},
//注册组件(局部) 将 student 组件注册为 school 组件的子组件
components:{
student
}
})

//定义hello组件
const hello = Vue.extend({
template:`<h1>{{msg}}</h1>`,
data(){
return {
msg:'欢迎来到尚硅谷学习!'
}
}
})

//定义app组件
const app = Vue.extend({
template:`
<div>
<hello></hello>
<school></school>
</div>
`,
components:{ // 将 school 组件和 hello 组件注册为 app 组件的子组件
school,
hello
}
})

//创建vm
new Vue({
template:'<app></app>',
el:'#root',
//注册组件(局部)
components:{app}
})
</script>

VueComponent

  • 关于 VueComponent:

    1. school 组件本质是一个名为 VueComponent 的构造函数,且不是程序员定义的,是 Vue.extend 生成的。
    1. 我们只需要写<school/><school></school>,Vue 解析时会帮我们创建 school 组件的实例对象,即 Vue 帮我们执行的:new VueComponent(options)。
    1. 特别注意:每次调用 Vue.extend,返回的都是一个全新的 VueComponent!!!!
    1. 关于 this 指向:
      1. 组件配置中:data 函数、methods 中的函数、watch 中的函数、computed 中的函数 它们的 this 均是【VueComponent 实例对象】。
      1. new Vue(options) 配置中:data函数、methods中的函数、watch中的函数、computed中的函数, 它们的 this 均是【Vue 实例对象】。
    1. VueComponent 的实例对象,以后简称 vc(也可称之为:组件实例对象)。Vue 的实例对象,以后简称 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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<body>
<!-- 准备好一个容器-->
<div id="root">
<school></school>
<hello></hello>
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false

//定义school组件
const school = Vue.extend({
name:'school',
template:`
<div>
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
`,
data(){
return {
name:'尚硅谷',
address:'北京'
}
},
methods: {
showName(){
console.log('showName',this)
}
},
})

const test = Vue.extend({
template:`<span>atguigu</span>`
})

//定义hello组件
const hello = Vue.extend({
template:`
<div>
<h2>{{msg}}</h2>
<test></test>
</div>
`,
data(){
return {
msg:'你好啊!'
}
},
components:{test}
})


// console.log('@',school) //组件实例对象
// console.log('#',hello)

//创建vm
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
<template>
<div class="demo">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
</template>

<script>
export default {
name:'School',
data(){
return {
name:'尚硅谷',
address:'北京昌平'
}
},
methods: {
showName(){
alert(this.name)
}
},
}
</script>

<style>
.demo{
background-color: orange;
}
</style>
  • 通常:有一个 .html 文件,引入 main.js 文件,在 main.js 文件中,创建 Vue 实例对象,将 App.vue 组件注册在 main.js 中,其他次一级的组件注册在 App.vue 组件中

脚手架

安装脚手架

1
npm install @vue/cli -g

用脚手架创建项目

1
vue create xxx  // xxx 为项目名称

脚手架结构分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
├── 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:包版本控制文件

项目的运行流程

  1. 执行 npm run serve 命令时,会执行 package.json 文件中的 scripts 下的 serve 配置项的 配置命令 : vue-cli-service serve
  2. 执行 vue-cli-service serve 命令,就会最先执行 main.js 入口文件

render函数(?)

1

修改默认配置

vue.config.js 配置文件
入口
关闭eslin校验

ref属性

  1. 被用来给元素或子组件注册引用信息(id 的替代者)
  2. 应用在 html 标签上获取的是真实 DOM 元素,应用在组件标签上是组件实例对象(vc)
  3. 使用方式:
    1. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>
    2. 获取: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
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
<School ref="sch"/>
</div>
</template>

<script>
//引入School组件
import School from './components/School'

export default {
name:'App',
components:{School},
data() {
return {
msg:'欢迎学习Vue!'
}
},
methods: {
showDOM(){
console.log(this.$refs.title) //真实DOM元素
console.log(this.$refs.btn) //真实DOM元素
console.log(this.$refs.sch) //School组件的实例对象(vc)
}
},
}
</script>

props 配置

  1. 功能:让组件接收外部传过来的数据

  2. 传递数据:<Demo name="xxx"/>

  3. 接收数据:

    1. 第一种方式(只接收):props:['name']

    2. 第二种方式(限制类型):props:{name:String}

    3. 第三种方式(限制类型、必要性、指定默认值):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//app.vue,向子组件传递数据
<template>
<div>
<Student name="李四" sex="女" :age="18"/>
</div>
</template>

<script>
import Student from './components/Student'

export default {
name:'App',
components:{Student}
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// student组件:接收父组件传递过来的数据
<template>
<div>
<h1>{{msg}}</h1>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<h2>学生年龄:{{myAge+1}}</h2>
<button @click="updateAge">尝试修改收到的年龄</button>
</div>
</template>

<script>
export default {
name:'Student',
data() {
console.log(this)
return {
msg:'我是一个尚硅谷的学生',
myAge:this.age
}
},
methods: {
updateAge(){
this.myAge++
}
},
//01.简单声明接收
// props:['name','age','sex']

//02.接收的同时对数据进行类型限制
/* props:{
name:String,
age:Number,
sex:String
} */

//03.接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
props:{
name:{
type:String, //name的类型是字符串
required:true, //name是必要的
},
age:{
type:Number,
default:99 //默认值
},
sex:{
type:String,
required:true
}
}
}
</script>

备注:props 是只读的,Vue 底层会监测你对 props 的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制 props 的内容到 data 中一份,然后去修改 data 中的数据。

mixin混入-全局混合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//mixin.js
export const hunhe = {
methods: {
showName(){
alert(this.name)
}
},
mounted() {
console.log('你好啊!')
},
}

export const hunhe2 = {
data() {
return {
x:100,
y:200
}
},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入混合
import {hunhe,hunhe2} from './mixin'
//关闭Vue的生产提示
Vue.config.productionTip = false

//全局混合
Vue.mixin(hunhe)
Vue.mixin(hunhe2)

//创建vm
new Vue({
el:'#app',
render: h => h(App)
})
1
2
3
4
5
6
7
//school.vue: 在子组件中就可以使用混合中的数据和方法
<template>
<div>
<h2 @click="showName">学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>

mixin混入_局部混合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div>
<!--3.使用混合中的属性和方法,如果有数据冲突,则使用组件data中的数据-->
<h2 @click="showName">学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</template>

<script>
//1.引入hunhe
import {hunhe,hunhe2} from '../mixin'

export default {
name:'School',
data() {
return {
name:'尚硅谷',
address:'北京',
x:666
}
},
//2.使用混合
mixins:[hunhe,hunhe2],
}
</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
//plugins.js 插件
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
}
})

//定义混入
Vue.mixin({
data() {
return {
x:100,
y:200
}
},
})

//给Vue原型上添加一个方法(vm和vc就都能用了)
Vue.prototype.hello = ()=>{alert('你好啊')}
}
}

导入插件

  • 在 main.js 中引入和使用插件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import plugins from './plugins'

//关闭Vue的生产提示
Vue.config.productionTip = false

//应用(使用)插件
Vue.use(plugins,1,2,3)

//创建vm
new Vue({
el:'#app',
render: h => h(App)
})

使用插件

  • 在组件中就可以使用插件中的方法
1
2
3
4
5
6
7
8
//student.vue
<template>
<div>
<h2>学生姓名:{{name}}</h2>
<h2>学生性别:{{sex}}</h2>
<input type="text" v-fbind:value="name">
</div>
</template>
1
2
3
4
5
6
7
8
//school.vue
<template>
<div>
<h2>学校名称:{{name | mySlice}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="test">点我测试一个hello方法</button>
</div>
</template>

scoped样式

  1. scroped: 添加该属性,则样式只能该组件使用(局部),未添加 scoped 属性,其他组件也能使用该样式(全局)
  2. lang:添加该属性表示语言,如若写 less ,则需要加 lang=”less”, 不写 lang 属性,默认是 css,若直接写 less 代码,就会直接报警告
1
2
3
4
5
6
7
8
<style lang="less" scoped>
.demo{
background-color: pink;
.atguigu{
font-size: 40px;
}
}
</style>

todolist案例

静态组件的拆分

  • 拆分过程省略,仅提供项目结构 和 HTML结构,CSS样式

  • App.vue 中引入注册 MyHeader MyList MyFooter 组件,在 MyList 组件中引入注册 MyItem 组件

1
2
3
4
5
6
7
8
//项目结构
- src
| - components
| ∟ MyHeader.vue
| ∟ MyList.vue
| ∟ MyFooter.vue
| ∟ MyItem.vue
| - 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
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React App</title>

<link rel="stylesheet" href="index.css">
</head>
<body>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认"/>
</div>
<ul class="todo-main">
<li>
<label>
<input type="checkbox"/>
<span>xxxxx</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
<li>
<label>
<input type="checkbox"/>
<span>yyyy</span>
</label>
<button class="btn btn-danger" style="display:none">删除</button>
</li>
</ul>
<div class="todo-footer">
<label>
<input type="checkbox"/>
</label>
<span>
<span>已完成0</span> / 全部2
</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</div>
</div>

</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*base*/
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;
}

/*header*/
.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);
}

/*main*/
.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;
}
/*item*/
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;
}

/*footer*/
.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;
}

初始化列表

  • 01 在 MyList.vue 中定义数据,并根据数据,动态遍历展示 MyItem 组件,并传递遍历出来的数据给 MyItem 组件
1
2
3
4
5
6
7
<template>
<div>
<ul class="todo-main">
<my-item v-for="todoobj in todos" :key="todoobj.id" :todo="todoobj"></my-item>
</ul>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
import MyItem from "../components/MyItem.vue";
export default {
name:"MyList",
components: {
MyItem,
},
data() {
return {
todos:[
{id:"001",title:"抽烟",done:true},
{id:"002",title:"喝酒",done:false},
{id:"003",title:"开车",done:true},
]
};
},
methods: {},
};
</script>
  • 02 在 MyItem 组件中接收数据,并动态展示数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done"/>
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" style="display: none">删除</button>
</li>
</div>
</template>

<script>
export default {
name:"MyItem",
props:["todo"],
components: {},
data() {
return {};
},
methods: {},
};
</script>

添加功能

  • 01 将上面定义的todos数据,移到 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
<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,
},
data() {
return {
todos:[
{id:"001",title:"抽烟",done:true},
{id:"002",title:"喝酒",done:false},
{id:"003",title:"开车",done:true},
]
}
},
};
</script>
  • 02 在 App.vue 组件中将 todos 数据传递给 MyList.vue 组件
1
2
3
4
5
6
7
8
9
10
11
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<my-header></my-header>
<my-list :todos="todos"></my-list>
<my-footer></my-footer>
</div>
</div>
</div>
</template>
  • 03 在 MyList.vue 组件中接收数据,并动态展示
1
2
3
4
5
6
7
8
9
10
<script>
import MyItem from "../components/MyItem.vue";
export default {
name:"MyList",
props:["todos"],
components: {
MyItem,
},
};
</script>
  • 04 在 App.vue 中传递一个自定义方法给 MyHeader.vue 组件
1
2
3
4
5
6
7
8
9
10
11
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<my-header :addTodo="addTodo"></my-header>
<my-list :todos="todos"></my-list>
<my-footer></my-footer>
</div>
</div>
</div>
</template>
  • 05 在 MyHeader.vue 组件中,
    • 05.1 接收App,vue组件传递过来的自定义方法,

    • 05.2 给输入框添加键盘事件,在事件处理回调中,将用户输入的值,包装成一个对象,

    • 05.3 将对象传递给 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
<template>
<div>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
</div>
</div>
</template>

<script>
import {nanoid} from "nanoid"
export default {
props:["addTodo"],
components: {},
data() {
return {title:""};
},
methods: {
add(){
//校验数据
if(!this.title.trim()){
return alert("输入不能为空")
}
//将输入的数据包装成一个todo对象 也可以使用: title:e.target.value
const todoObj = {id:nanoid(),title:this.title,done:false}
//通知App组件添加一个todo对象
this.addTodo(todoObj)
//清空输入框内容
this.title = ""
}
},
};
</script>
  • 06 在 App.vue 组件中调用 自定义方法,并接收 MyHeader.vue 组件传递过来的参数,并将该参数添加到 todos 数组中
1
2
3
4
5
6
methods: {
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
}
},

勾选和取消勾选

  • 01 在 MyItem.vue 组件中,
    • 01 给复选框添加事件,并传递 todo.id 给事件回调,
    • 02 接收由 App.vue 组件经逐层传递来的自定义方法
    • 03 在回调函数中触发由 App.vue 组件经逐层传递来的自定义方法,并传递 id 给该方法并调用
1
2
3
4
5
6
7
8
9
10
11
<template>
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" style="display: none">删除</button>
</li>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
export default {
name:"MyItem",
props:["todo","checkTodo"],
components: {},
data() {
return {};
},
methods: {
handleCheck(id){
this.checkTodo(id)
}
},
};
</script>
  • 02 在 App.vue 组件中,自定义一个方法,用于对done进行取反
    • 注意:
      • 数据在哪里,则操作数据的方法就在哪里
      • 在 MyItem.vue 中双向数据绑定 todo.id 也可以实现,但是不推荐,因为通过 props 接收的数据,不应被修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
methods: {
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
//勾选 or 取消勾选
checkTodo(id){
//对todos中的每一项进行遍历,根据传递过来的id进行done的取反
this.todos.forEach((todo) => {
if(todo.id === id){
todo.done = !todo.done
}
})
}
},
  • 03 在 App.vue 组件中,将自定义的方法传递给 MyList.vue 组件
1
2
3
4
5
6
7
8
9
10
11
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<my-header :addTodo="addTodo"></my-header>
<my-list :todos="todos" :checkTodo="checkTodo"></my-list>
<my-footer></my-footer>
</div>
</div>
</div>
</template>
  • 04 在 MyList.vue 组件中接收 App.vue 组件 传递过来的自定义 checkTodo 方法,并再次传递给 MyItem.vue 组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
<ul class="todo-main">
<my-item v-for="todoobj in todos" :key="todoobj.id" :todo="todoobj" :checkTodo="checkTodo"></my-item>
</ul>
</div>
</template>

<script>
import MyItem from "../components/MyItem.vue";
export default {
name:"MyList",
props:["todos","checkTodo"],
components: {
MyItem,
},
};
</script>
  • 05 在MyItem.vue 组件中接收并调用 自定义 checkTodo 方法 (同01)

删除功能

  • 01 在MyItem.vue 组件中默认隐藏删除按钮(略),当鼠标移动到li标签上时,添加li的背景色,并显示button按钮:
1
2
3
4
5
6
li:hover{
background-color: #ddd;
}
li:hover button{
display: block;
}
  • 02 在MyItem.vue 组件中,给button按钮添加点击事件,并将todo.id传递给事件回调
1
2
3
4
5
6
7
8
9
10
11
<template>
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</div>
</template>
  • 03 在MyItem.vue 组件中,设置事件回调,并传递id给由 App.vue 逐层传递过来的 deleteTodo 自定义事件,并调用该事件
1
2
3
4
5
6
7
//删除
handleDelete(id){
if(confirm('确定删除吗?')){
//通知App.vue,删除todos中的指定id的数据
this.deleteTodo(id)
}
}
  • 04 在 App.vue 组件中,自定义方法 deleteTodo ,根据传递过来的id值,过滤 todos
1
2
3
4
5
6
//删除一个todo
deleteTodo(id){
this.todos = this.todos.filter((todo) => {
return todo.id !== id
})
}
  • 05 在App.vue 组件中,将自定义方法 deleteTodo 逐层传递给 MyItem.vue 组件
1
2
3
4
5
6
7
8
9
10
11
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<my-header :addTodo="addTodo"></my-header>
<my-list :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></my-list>
<my-footer></my-footer>
</div>
</div>
</div>
</template>
  • 06 在MyItem.vue 组件中接收由 App.vue 逐层传递过来的 deleteTodo 自定义事件,并触发(同03)
1
props:["todo","checkTodo","deleteTodo"],

底部统计

  • 01 在App.vue 组件中,将todos数据传递给 MyFooter.vue 组件
1
<my-footer :todos="todos"></my-footer>
  • 02 在 MyFooter.vue 组件中接收 todos 数据,并根据todos数据得出数组长度,并计算统计出已完成的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template>
<div>
<div class="todo-footer">
<label>
<input type="checkbox" />
</label>
<span> <span>已完成{{doneTotal}}</span> / 全部{{todos.length}}</span>
<button class="btn btn-danger">清除已完成任务</button>
</div>
</div>
</template>

<script>
export default {
props:["todos"],
computed:{
doneTotal(){
return this.todos.reduce((pre,current) => {
return pre + (current.done ? 1 : 0)
},0)
}
}
};
</script>

底部交互

  • 01 在 MyFooter.vue 组件中,通过计算属性,计算出总个数和已完成个数

  • 02 根据总个数和已完成个数是否相等,得出全选框的true/false,并绑定到全选框的checked属性

  • 03 给全选框添加事件,并在事件回调中将checked的值,通过App.vue传递过来的自定义方法checkAlltodo,传递给App.vue

  • 04 在App.vue的自定义方法checkAlltodo中,根据全选复选框的true/false,遍历todos,并修改done,并将checkAlltodo自定义方法传递给 MyFooter.vue 组件

  • 05 在 MyFooter.vue 组件中,给清除已完成任务的button按钮添加事件 clearAll ,并在事件回调中触发 App.vue传递过来的自定义方法 clearAllTodo

  • 06 在App.vue的自定义方法clearAllTodo中,对todos数组进行筛选,清除 done 值为 true 的数据

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
<template>
<div>
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" :checked="isAll" @change="checkAll"/>
</label>
<span> <span>已完成{{doneTotal}}</span> / 全部{{total}}</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</div>
</template>

<script>
export default {
props:["todos","checkAlltodo","clearAllTodo"],
computed:{
//计算总个数
total(){
return this.todos.length
},
//计算已完成个数
doneTotal(){
return this.todos.reduce((pre,current) => {
return pre + (current.done ? 1 : 0)
},0)
},
//当已完成个数与总个数相等时,并且总数大于0时,设置全选框为true
isAll(){
return this.doneTotal === this.total && this.total > 0
}
},
methods: {
//全选
checkAll(e){
this.checkAlltodo(e.target.checked)
},
//清除已完成
clearAll(){
this.clearAllTodo()
}
},
};
</script>

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
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<my-header :addTodo="addTodo"></my-header>
<my-list :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></my-list>
<my-footer :todos="todos" :checkAlltodo="checkAlltodo" :clearAllTodo="clearAllTodo"></my-footer>
</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,
},
data() {
return {
todos:[
{id:"001",title:"抽烟",done:true},
{id:"002",title:"喝酒",done:false},
{id:"003",title:"开车",done:true},
]
}
},
methods: {
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
//勾选 or 取消勾选
checkTodo(id){
//对todos中的每一项进行遍历,根据传递过来的id进行done的取反
this.todos.forEach((todo) => {
if(todo.id === id){
todo.done = !todo.done
}
})
},
//删除一个todo
deleteTodo(id){
this.todos = this.todos.filter((todo) => {
return todo.id !== id
})
},
//全选 or 取消全选 根据全选复选框的true/false,遍历todos,并修改done
checkAlltodo(done){
this.todos.forEach((todo) => {
todo.done = done
})
},
//清除所有已完成的
clearAllTodo(){
this.todos = this.todos.filter((todo) => {
return !todo.done
})
}
},
};
</script>

完整代码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
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
107
108
109
110
111
112
113
114
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<my-header :addTodo="addTodo"></my-header>
<my-list :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></my-list>
<my-footer :todos="todos" :checkAlltodo="checkAlltodo" :clearAllTodo="clearAllTodo"></my-footer>
</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,
},
data() {
return {
todos:[
{id:"001",title:"抽烟",done:true},
{id:"002",title:"喝酒",done:false},
{id:"003",title:"开车",done:true},
]
}
},
methods: {
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
//勾选 or 取消勾选
checkTodo(id){
//对todos中的每一项进行遍历,根据传递过来的id进行done的取反
this.todos.forEach((todo) => {
if(todo.id === id){
todo.done = !todo.done
}
})
},
//删除一个todo
deleteTodo(id){
this.todos = this.todos.filter((todo) => {
return todo.id !== id
})
},
//全选 or 取消全选 根据全选复选框的true/false,遍历todos,并修改done
checkAlltodo(done){
this.todos.forEach((todo) => {
todo.done = done
})
},
//清除所有已完成的
clearAllTodo(){
this.todos = this.todos.filter((todo) => {
return !todo.done
})
}
},
};
</script>

<style>
/*base*/
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

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>
<div class="todo-header">
<input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/>
</div>
</div>
</template>

<script>
import {nanoid} from "nanoid"
export default {
props:["addTodo"],
components: {},
data() {
return {title:""};
},
methods: {
add(){
//校验数据
if(!this.title.trim()){
return alert("输入不能为空")
}
//将输入的数据包装成一个todo对象 也可以使用: title:e.target.value
const todoObj = {id:nanoid(),title:this.title,done:false}
//通知App组件添加一个todo对象
this.addTodo(todoObj)
//清空输入框内容
this.title = ""
}
},
};
</script>

<style scoped>
/*header*/
.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

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>
<ul class="todo-main">
<my-item v-for="todoobj in todos" :key="todoobj.id" :todo="todoobj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></my-item>
</ul>
</div>
</template>

<script>
import MyItem from "../components/MyItem.vue";
export default {
name:"MyList",
props:["todos","checkTodo","deleteTodo"],
components: {
MyItem,
},
};
</script>

<style scoped>
/*main*/
.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

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
<template>
<div>
<li>
<label>
<input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/>
<span>{{todo.title}}</span>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
</li>
</div>
</template>

<script>
export default {
name:"MyItem",
props:["todo","checkTodo","deleteTodo"],
components: {},
data() {
return {};
},
methods: {
//勾选or取消勾选
handleCheck(id){
this.checkTodo(id)
},
//删除
handleDelete(id){
if(confirm('确定删除吗?')){
//通知App.vue,删除todos中的指定id的数据
this.deleteTodo(id)
}
}
},
};
</script>

<style scoped>
/*item*/
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: #ddd;
}
li:hover button{
display: block;
}
</style>

完整代码MyFooter

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
<template>
<div>
<div class="todo-footer" v-show="total">
<label>
<input type="checkbox" :checked="isAll" @change="checkAll"/>
</label>
<span> <span>已完成{{doneTotal}}</span> / 全部{{total}}</span>
<button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
</div>
</div>
</template>

<script>
export default {
props:["todos","checkAlltodo","clearAllTodo"],
computed:{
//计算总个数
total(){
return this.todos.length
},
//计算已完成个数
doneTotal(){
return this.todos.reduce((pre,current) => {
return pre + (current.done ? 1 : 0)
},0)
},
//当已完成个数与总个数相等时,并且总数大于0时,设置全选框为true
isAll(){
return this.doneTotal === this.total && this.total > 0
}
},
methods: {
//全选
checkAll(e){
this.checkAlltodo(e.target.checked)
},
//清除已完成
clearAll(){
this.clearAllTodo()
}
},
};
</script>

<style scoped>
/*footer*/
.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>

WebStorage

  • 1 存储内容大小一般支持 5MB 左右(不同浏览器可能还不一样)

  • ⒉浏览器端通过 Window.sessionStorageWindow.localStorage 属性来实现本地存储机制。

  • 3 相关API:

    • 01 ×XxxxStorage.setItem( ‘ key ‘ , ‘value’ ) ;该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    • 02 xxxxxStorage.getItem( ‘ person ‘ );该方法接受一个键名作为参数,返回键名对应的值。

    • 03 xxxXxStorage.removeltem( ‘ key ‘);该方法接受一个键名作为参数,并把该键名从存储中删除。

    • 04 xxxxxStorage.clear() 该方法会清空存储中的所有数据。

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
<body>
<h2>localStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>

<script type="text/javascript" >
let p = {name:'张三',age:18}
function saveData(){
//设置一项本地存储
localStorage.setItem('msg','hello!!!')
localStorage.setItem('msg2',666)
localStorage.setItem('person',JSON.stringify(p))//转为字符串类型
}
function readData(){
//读取一项本地存储
console.log(localStorage.getItem('msg'))
console.log(localStorage.getItem('msg2'))
const result = localStorage.getItem('person')
console.log(JSON.parse(result))//转为JSON类型
}
function deleteData(){
//删除一项本地存储
localStorage.removeItem('msg2')
}
function deleteAllData(){
//清空所有本地存储
localStorage.clear()
}
</script>
</body>

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
<body>
<h2>sessionStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>

<script type="text/javascript" >
let p = {name:'张三',age:18}

function saveData(){
//设置一项会话存储
sessionStorage.setItem('msg','hello!!!')
sessionStorage.setItem('msg2',666)
sessionStorage.setItem('person',JSON.stringify(p))//转为字符串类型
}
function readData(){
//读取一项会话存储
console.log(sessionStorage.getItem('msg'))
console.log(sessionStorage.getItem('msg2'))
const result = sessionStorage.getItem('person')
console.log(JSON.parse(result))//转为JSON类型
}
function deleteData(){
//删除一项会话存储
sessionStorage.removeItem('msg2')
}
function deleteAllData(){
//清空所有会话存储
sessionStorage.clear()
}
</script>
</body>

todolist案例的本地存储

  • 01 深度监视todos
  • 02 将todos的最新值,转为json对象,保存到本地
  • 03 读取本地存储的todos,后面的空数组表示本地存储没有值时的默认值
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
<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,
},
data() {
return {
//03 读取本地存储的todos,后面的空数组表示本地存储没有值时的默认值
todos:JSON.parse(localStorage.getItem('todos')) || []
}
},
methods: {
//添加一个todo
addTodo(todoObj){
this.todos.unshift(todoObj)
},
//勾选 or 取消勾选
checkTodo(id){
//对todos中的每一项进行遍历,根据传递过来的id进行done的取反
this.todos.forEach((todo) => {
if(todo.id === id){
todo.done = !todo.done
}
})
},
//删除一个todo
deleteTodo(id){
this.todos = this.todos.filter((todo) => {
return todo.id !== id
})
},
//全选 or 取消全选 根据全选复选框的true/false,遍历todos,并修改done
checkAlltodo(done){
this.todos.forEach((todo) => {
todo.done = done
})
},
//清除所有已完成的
clearAllTodo(){
this.todos = this.todos.filter((todo) => {
return !todo.done
})
}
},
watch:{
//01 深度监视todos
todos:{
deep:true,
handler(value){
//02 将todos的最新值保存到本地 转为json对象
localStorage.setItem('todos',JSON.stringify(value))
}
}
}
};
</script>

自定义事件

自定义方法_子传父组件

  • 01 在父组件 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
<template>
<div class="app">
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<!--2.传递自定义方法-->
<School :getSchoolName="getSchoolName" />
</div>
</template>

<script>
import School from "./components/School";

export default {
name: "App",
components: { School },
methods: {
//1.定义自定义方法
//5.通过形参接收子组件传递的数据
getSchoolName(name) {
console.log("App收到了学校名:", name);
}
},
};
</script>
  • 02 在子组件 School.vue 中通过 props 接收父组件传递过来的自定义事件

  • 03 在事件回调方法中,调用父组件传递过来的自定义事件,并将子组件中的数据作为参数传递给父组件,在父组件的 methods 方法定义中通过形参接收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="sendSchoolName">把学校名称给App</button>
</div>
</template>

<script>
export default {
name:'School',
//3.接收自定义方法
props:["getSchoolName"],
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
methods: {
sendSchoolName(){
//4.在事件回调中调用自定义方法并传参
this.getSchoolName(this.name)
}
},
}
</script>

自定义事件_子传父组件01

  • 01 在子组件 Student.vue 中的点击事件回调中,通过 $emit 方法发射自定义事件
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="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">把学生名称给App</button>
</div>
</template>

<script>
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
methods: {
sendStudentName() {
//(触发Student组件实例身上的atguigu事件)
//1.子组件中触发一个自定义事件,并携带参数
this.$emit("atguigu",this.name);
},
},
};
</script>
  • 02 在父组件 App.vue 中,给子组件的组件实例对象绑定一个自定义事件
  • 03 在自定义事件的回调中,接收子组件传递的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>
<div class="app">
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<!--(给子组件的组件实例对象绑定自定义事件)-->
<!-- 2.在父组件中,在子组件的组件实例对象上绑定自定义事件,并设置事件回调-->
<Student v-on:atguigu.once="getStudentName"/>
</div>
</template>

<script>
import Student from "./components/Student";

export default {
name: "App",
components: { Student },
methods: {
//3.自定义事件的回调,并接收子组件传递的参数
getStudentName(name,...params) {
console.log("App收到了学生名:", name,params);
},
},
};
</script>

自定义事件_子传父组件02

  • 01 在子组件 Student.vue 中的点击事件回调中,通过 $emit 方法发射自定义事件
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="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">把学生名称给App</button>
</div>
</template>

<script>
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
methods: {
sendStudentName() {
//(触发Student组件实例身上的atguigu事件)
//1.子组件中触发一个自定义事件,并携带参数
this.$emit("atguigu",this.name);
},
},
};
</script>
  • 02 当 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
<template>
<div class="app">
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student"/>
</div>
</template>

<script>
import Student from "./components/Student";

export default {
name: "App",
components: { Student },
methods: {
getStudentName(name,...params) {
console.log("App收到了学生名:", name,params);
},
},
mounted() {
// 当组件挂载完毕时,触发自定义事件及其回调,(可以加定时器延时触发自定义事件)
// 若第二个参数写成普通函数,则函数内的this指向触发自定义事件atguigu的组件实例对象
// 若第二个参数写成箭头函数,这函数类的this指向当前的组件实例对象
this.$refs.student.$on('atguigu',this.getStudentName)//推荐写法,可以多次触发该自定义事件
// this.$refs.student.$once('atguigu',this.getStudentName)//只能触发一次该自定义事件
},
};
</script>

解绑自定义事件

  • 01 this.$off(“atguigu”) 解绑单个自定义事件
  • 02 this.$off([“atguigu”,”demo”]) 解绑多个自定义事件
  • 03 this.$off() 解绑所有自定义事件
  • 04 this.$destroy(); 销毁组件实例也会解绑所有自定义事件

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
<template>
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">把学生名称给App</button>
<button @click="unbind">解绑atguigu事件</button>
<button @click="death">销毁当前Student组件的实例(vc)</button>
</div>
</template>

<script>
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
methods: {
sendStudentName() {
//触发Student组件实例身上的atguigu事件
this.$emit("atguigu", this.name);
this.$emit("demo");
},
unbind() {
//this.$off("atguigu")//1.解绑单个自定义事件
//this.$off(["atguigu","demo"])//2.解绑多个自定义事件
this.$off(); //3.解绑所有自定义事件
},
death() {
//销毁当前Student组件的实例,销毁后所有Student实例的自定义事件全都失效。
//(数据的响应式失效,但是原生的点击事件还能用)
//5.若 App.vue 组件被销毁,则其所有的子组件实例也会被销毁
this.$destroy(); //4.销毁组件实例
},
},
};
</script>

.native修饰符

  • 01 若给组件标签添加原生 dom 事件,则会被解析成自定义事件,需要使用自定义事件的触发方法触发

  • 02 添加 .native 修饰符,就能作为原生 dom 事件使用

1
<Student ref="student" @click.native="show"/>

todolist案例_自定义事件

  • 01 App.vue:
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<!--1.绑定自定义事件 addTodo -->
<my-header @addTodo="addTodo"></my-header>
<my-list :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></my-list>
<!--2.绑定自定义事件 checkAlltodo 和 clearAllTodo -->
<my-footer :todos="todos" @checkAlltodo="checkAlltodo" @clearAllTodo="clearAllTodo"></my-footer>
</div>
</div>
</div>
</template>

02 MyHeader.vue: 不再需要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
<script>
import {nanoid} from "nanoid"
export default {
components: {},
data() {
return {title:""};
},
methods: {
add(){
//校验数据
if(!this.title.trim()){
return alert("输入不能为空")
}
//将输入的数据包装成一个todo对象 也可以使用: title:e.target.value
const todoObj = {id:nanoid(),title:this.title,done:false}
//通知App组件添加一个todo对象
//3.发射自定义事件
this.$emit('addTodo',todoObj)
//清空输入框内容
this.title = ""
}
},
};
</script>

03 **Myfooter.vue: **不再需要props接收,仅需在对应的键盘事件回调中发射自定义事件即可

1
2
3
4
5
6
7
8
9
10
methods: {
//全选
checkAll(e){
this.$emit("checkAlltodo",e.target.checked)
},
//清除已完成
clearAll(){
this.$emit("clearAllTodo")
}
},

全局事件总线

全局事件总线的作用

  • 实现任意组件间的数据通信

全局事件总线的使用

  • 01 在入口文件 main.js 中安装全局事件总线
1
2
3
4
5
6
new Vue({
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //01 安装全局事件总线
},
}).$mount('#app')
  • 02 在 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
<template>
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>

<script>
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
methods: {
sendStudentName() {
//2.发射一个自定义事件,并携带参数
this.$bus.$emit("hello", this.name);
},
},
};
</script>
  • 03 在 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
<template>
<div class="school">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>

<script>
export default {
name: "School",
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
mounted() {
//3.触发一个自定义事件,并接收参数
this.$bus.$on("hello", (data) => {
console.log("我是School组件,收到了数据", data);
});
},
beforeDestroy() {
//4.在组件实例销毁前解绑自定义事件
this.$bus.$off("hello");
},
};
</script>

todolist案例-全局事件总线

  • 01 在入口文件 main.js 中安装全局事件总线
1
2
3
4
5
6
new Vue({
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this//01 安装全局事件总线
},
}).$mount('#app')
  • 02 在 App.vue 组件中的生命周期函数中:即获取数据的一方

  • 03 绑定自定义事件及其回调函数,以及解绑自定义事件,无需再传递两个方法给 MyList.vue 组件

1
2
3
4
5
6
7
8
9
10
mounted() {
//2.绑定自定义事件
this.$bus.$on('checkTodo',this.checkTodo)
this.$bus.$on('deleteTodo',this.deleteTodo)
},
beforeDestroy() {
//3.解绑自定义事件
this.$bus.$off("checkTodo")
this.$bus.$off("deleteTodo")
},
  • 04 在 MyItem.vue 组件中:在事件回调函数中触发自定义事件,并携带参数,即提供数据的一方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
methods: {
//勾选or取消勾选
handleCheck(id) {
//4.触发事件
this.$bus.$emit("checkTodo", id);
},
//删除
handleDelete(id) {
if (confirm("确定删除吗?")) {
//4.触发事件,通知App.vue,删除todos中的指定id的数据
this.$bus.$emit("deleteTodo", id);
}
},
},

消息的订阅与发布

消息订阅与发布的作用

  • 可以实现任意组件间的数据传递

消息订阅与发布的使用

  • 01 安装 (npm i pubsub-js) 并导入 pubsub-js 包
  • 02 订阅消息,一但订阅的消息发布,就执行后面的回调,返回一个 id,返回的 id 用于取消订阅
  • 04 取消消息的订阅

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
<template>
<div class="school">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>

<script>
//1.npm i pubsub-js 并导入pubsub-js包
import pubsub from 'pubsub-js'
export default {
name: "School",
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
mounted() {
//2.订阅了hello消息,一但发布,就执行后面的回调,返回一个id,返回的id用于取消订阅,
// 即消息的接受者
// 对于回调函数:写成普通函数,这this为undefined,写成箭头函数,this指向组件实例对象
// 事件回调也可以写在 methods 中,就没有this指向混乱的问题
// 回调参数一:消息名 参数二:传递过来的数据
this.pubId = pubsub.subscribe('hello',(msgName,data) => {
console.log(this);//VueComponent
console.log("发布了hello消息,回调执行~~",msgName,data);
})
},
beforeDestroy() {
//4.取消消息的订阅
pubsub.unsubscribe(this.pubId)
},
};
</script>
  • 03 发布消息,并携带参数

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
<template>
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>

<script>
import pubsub from 'pubsub-js'
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
methods: {
sendStudentName() {
//03.发布了hello的消息,并携带参数,即消息的提供者
pubsub.publish("hello",this.name)
},
},
};
</script>

todolist案例_订阅与发布

  • 01 在 App.vue 组件中导入 pubsub 包
1
import pubsub from 'pubsub-js'
  • 02 在 App.vue 组件生命周期函数中订阅和取消订阅消息(需要在deleteTodo方法的形参最前面添加一个下划线占位,因为该回调有两个参数)
1
2
3
4
5
6
7
8
9
10
11
12
mounted() {
//绑定自定义事件
this.$bus.$on('checkTodo',this.checkTodo)
//1.订阅消息
this.pubId = pubsub.subscribe("deleteTodo",this.deleteTodo)
},
beforeDestroy() {
//解绑自定义事件
this.$bus.$off("checkTodo")
//2.取消消息的订阅
pubsub.unsubscribe(this.pubId)
},
  • 03 在 MyItem.vue 组件中导入pubsub 包
1
import pubsub from "pubsub-js";
  • 04 在 MyItem.vue 组件中,在点击事件的回调函数中发布消息
1
2
3
4
5
6
7
//删除
handleDelete(id) {
if (confirm("确定删除吗?")) {
//03.发布了hello的消息,并携带参数,即消息的提供者
pubsub.publish("deleteTodo", id);
}
},

todolist的编辑_$nextTick

  • 01 添加编辑按钮, (DOM 结构见后面,提供 CSS 样式,CSS 样式放在 App.vue中)
1
2
3
4
5
6
.btn-edit {
color: #fff;
background-color: #49a5da;
border: 1px solid #1c69c0;
margin-right: 4px;
}
  • 02 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
<template>
<div>
<li>
<label>
<input
type="checkbox"
:checked="todo.done"
@change="handleCheck(todo.id)"
/>
<span v-show="!todo.isEdit">{{ todo.title }}</span>
<!-- 04 添加input输入框并绑定失去焦点事件及其回调 -->
<!-- 05 通过todo.isEdit控制输入框和文字的交替显示-->
<input
v-show="todo.isEdit"
type="text"
:value="todo.title"
@blur="handleBlur(todo,$event)"
ref="inputTitle"
/>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">
删除
</button>
<!-- 01.添加编辑按钮并绑定点击事件及其回调 -->
<button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit">编辑</button>
</li>
</div>
</template>

<script>
import pubsub from "pubsub-js";
export default {
name: "MyItem",
props: ["todo"],
components: {},
data() {
return {};
},
methods: {
//勾选or取消勾选
handleCheck(id) {
this.$bus.$emit("checkTodo", id);
},
//删除
handleDelete(id) {
if (confirm("确定删除吗?")) {
pubsub.publish("deleteTodo", id);
}
},
//编辑
//02 编辑按钮的点击事件回调
handleEdit(todo) {
//03 判断todo对象中是否有isEdit属性
if (Object.prototype.hasOwnProperty.call(todo,'isEdit')) {
todo.isEdit = true;
} else {
this.$set(todo, "isEdit", true);
}
//DOM节点更新到页面完毕后执行回调
this.$nextTick(function(){
this.$refs.inputTitle.focus()
})
},
//06.输入框失去焦点事件回调
handleBlur(todo,e) {
//6.1 isEdit 控制输入框的隐藏,文字的显示
todo.isEdit = false;
//6.2 判空校验
if(!e.target.value.trim()) return alert('输入不能为空!')
//6.3 通过全局事件总线,发射一个自定义事件,并携带参数
this.$bus.$emit("updateTodo",todo.id,e.target.value)
},
},
};
</script>
  • 03 在App.vue中绑定自定义事件,修改todo
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
<script>
import pubsub from "pubsub-js";
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,
},
data() {
return {
//03 读取本地存储的todos,后面的空数组表示本地存储没有值时的默认值
todos: JSON.parse(localStorage.getItem("todos")) || [],
};
},
methods: {
//添加一个todo
addTodo(todoObj) {
this.todos.unshift(todoObj);
},
//勾选 or 取消勾选
checkTodo(id) {
//对todos中的每一项进行遍历,根据传递过来的id进行done的取反
this.todos.forEach((todo) => {
if (todo.id === id) {
todo.done = !todo.done;
}
});
},
//09.更新todo
updateTodo(id,title) {
//对todos中的每一项进行遍历,根据传递过来的id进行done的取反
this.todos.forEach((todo) => {
if (todo.id === id) {
todo.title = title
}
});
},
//删除一个todo
deleteTodo(_, id) {
this.todos = this.todos.filter((todo) => {
return todo.id !== id;
});
},
//全选 or 取消全选 根据全选复选框的true/false,遍历todos,并修改done
checkAlltodo(done) {
this.todos.forEach((todo) => {
todo.done = done;
});
},
//清除所有已完成的
clearAllTodo() {
this.todos = this.todos.filter((todo) => {
return !todo.done;
});
},
},
watch: {
//01 深度监视todos
todos: {
deep: true,
handler(value) {
//02 将todos的最新值保存到本地
localStorage.setItem("todos", JSON.stringify(value));
},
},
},
mounted() {
//绑定自定义事件
this.$bus.$on("checkTodo", this.checkTodo);
//订阅消息
this.pubId = pubsub.subscribe("deleteTodo", this.deleteTodo);
//07. 绑定updateTodo自定义事件及其回调
this.$bus.$on("updateTodo", this.updateTodo);

},
beforeDestroy() {
//解绑自定义事件
this.$bus.$off("checkTodo");
//取消消息的订阅
pubsub.unsubscribe(this.pubId);
//08. 解绑updateTodo自定义事件
this.$bus.$off("updateTodo");
},
};
</script>

过渡与动画

动画1

  • 01 那个结构需要动画,就用 transition 包裹即可,transition 标签不会渲染到页面中
  • 02 name 属性用于设置对应动画的类名,不写 name 属性,则使用类名: .v-enter-active{ },若有多个动画,会有影响
  • 03 appear 属性:用于控制页面刷新就立即执行一次进入动画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template>

<script>
export default {
name: "Test",
data() {
return {
isShow: true,
};
},
};
</script>

<style scoped>
h1 {
background-color: orange;
}

.hello-enter-active {
animation: atguigu 0.5s linear;
}

.hello-leave-active {
animation: atguigu 0.5s linear reverse;
}

@keyframes atguigu {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0px);
}
}
</style>

动画2

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="isShow = !isShow">显示/隐藏</button>
<transition name="hello" appear>
<h1 v-show="isShow">尚硅谷!</h1>
</transition>
</div>
</template>

<script>
export default {
name:'Test',
data() {
return {
isShow:true
}
},
}
</script>

<style scoped>
h1{
background-color: orange;
}
/* 进入的起点、离开的终点 */
.hello-enter,.hello-leave-to{
transform: translateX(-100%);
}
/* 进入和离开的过程中 */
.hello-enter-active,.hello-leave-active{
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to,.hello-leave{
transform: translateX(0);
}
</style>

动画3_多个元素的动画

  • 01 使用 transition-group 标签,且每个元素都要指定 key 值。
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="isShow = !isShow">显示/隐藏</button>
<transition-group name="hello" appear>
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
</div>
</template>

<script>
export default {
name:'Test',
data() {
return {
isShow:true
}
},
}
</script>

<style scoped>
h1{
background-color: orange;
}
/* 进入的起点、离开的终点 */
.hello-enter,.hello-leave-to{
transform: translateX(-100%);
}
.hello-enter-active,.hello-leave-active{
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to,.hello-leave{
transform: translateX(0);
}
</style>

第三方动画库的使用

animate.css : https://animate.style/

  • 01 安装: npm install animate.css –save

  • 02 导入: import “animate.css”;

  • 03 在 transition 标签的 name 属性添加 animate_ _animated animate__bounce

  • 04 所要使用的动画效果的类名,写在 enter-active-class 和 leave-active-class 中,类名去 https://animate.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
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__rubberBand"
leave-active-class="animate__backOutUp"
>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
</div>
</template>

<script>
import "animate.css";
export default {
name: "Test",
data() {
return {
isShow: true,
};
},
};
</script>

<style scoped>
h1 {
background-color: orange;
}
</style>

todolist案例_动画

  • 01 添加动画css样式
  • 02 给MyItem组件添加动画

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
56
57
58
59
60
<template>
<div>
<ul class="todo-main">
<transition-group name="todo" appear>
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
/>
</transition-group>
</ul>
</div>
</template>

<script>
import MyItem from "../components/MyItem.vue";
export default {
name:"MyList",
props:["todos"],
components: {
MyItem,
},
};
</script>

<style scoped>
/*main*/
.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: atguigu 0.5s linear;
}

.todo-leave-active{
animation: atguigu 0.5s linear reverse;
}

@keyframes atguigu {
from{
transform: translateX(100%);
}
to{
transform: translateX(0px);
}
}
</style>

配置代理服务器

  • 01 什么是跨域: 协议,主机,端口号只要有一个不同,就算跨域
  • 02 跨域是发起了请求,服务器收到了请求,作出响应返回了数据,但是浏览器发现跨域了,不给用户显示数据

axios的使用

  • 01 安装 axios
1
npm i axios
  • 02 前端使用 axios 发起请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div id="app">
<button @click="getStudents">获取学生信息</button>
</div>
</template>

<script>
import axios from 'axios'
export default {
name: 'App',
methods: {
getStudents(){
// 此处的地址是本地代理服务器的地址,代理服务器的地址,与本地服务器地址一样,但是需要写完整接口路径
axios.get("http://localhost:8080/students").then((res) => {
console.log("请求数据成功",res.data);
},(err) => {
console.log("请求数据失败",err.message);
})
}
},
}
</script>

<style>
</style>
  • 此时存在跨越问题,无法获得数据

配置代理服务器

  • 01 若请求的资源,在本地(8080 端口)有,就不会走代理服务器,比如 public 文件夹中的资源,(该文件夹中的资源,都算是 8080 端口的资源) ,

  • 02 不能配置多个目标服务器,即只能配置一个代理,且无法灵活控制是否走代理

vue.config.js 中:

1
2
3
4
5
6
7
8
module.exports = {
//...

// 开启代理服务器,解决跨域
devServer: {
proxy: 'http://localhost:5000', // 此处的地址,是远程服务器的地址,只需要写到端口号即可
}
}
  • 03 服务器开启服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const express = require('express')
const app = express()

app.use((request,response,next)=>{
console.log('有人请求服务器1了');
// console.log('请求来自于',request.get('Host'));
// console.log('请求的地址',request.url);
next()
})

app.get('/students',(request,response)=>{
const students = [
{id:'001',name:'tom',age:18},
{id:'002',name:'jerry',age:19},
{id:'003',name:'tony',age:120},
]
response.send(students)
})

app.listen(5000,(err)=>{
if(!err) console.log('服务器1启动成功了,请求学生信息地址为:http://localhost:5000/students');
})

配置多代理服务器

  • 01 前端发起请求 (向前端本地接口发起请求即可,前缀要紧跟端口号)
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 id="app">
<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/atguigu/students").then((res) => {
console.log("请求数据成功",res.data);
},(err) => {
console.log("请求数据失败",err.message);
})
},
getCars(){
axios.get("http://localhost:8080/demo/cars").then((res) => {
console.log("请求数据成功",res.data);
},(err) => {
console.log("请求数据失败",err.message);
})
}
},
}
</script>

02 在 vue.config.js 中配置多代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module.exports = {
//...

devServer: {
proxy: {
"/atguigu": {
//目标地址,远程服务器地址,只需要写到端口号即可
target: "http://localhost:5000",
//重写路径
pathRewrite: { "^/atguigu": "" },
ws: true, //用于支持websocket
changeOrigin: false,//用于控制请求头中的host值
},
"/demo": {
//目标地址,远程服务器地址,只需要写到端口号即可
target: "http://localhost:5001",
//重写路径
pathRewrite: { "^/demo": "" },
ws: true, //用于支持websocket
changeOrigin: false,//用于控制请求头中的host值
},
},
},
};

gitHub案例

  • 案例效果展示:

1651571995263

App.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<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 {
components: {
Search,
List,
},
methods: {},
};
</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>
<div class="row">
<!-- 展示用于列表 -->
<div
v-show="info.users.length"
class="card"
v-for="user in 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">加载中...</h1>
<!-- 展示错误信息 -->
<h1 v-show="info.errMsg">{{ info.errMsg }}</h1>
</div>
</div>
</template>

<script>
export default {
name: "list",
components: {},
data() {
return {
info: {
users: [],
isFirst: true,
isLoading: false,
errMsg: "",
},
};
},
methods: {},
mounted() {
this.$bus.$on("updateListData", (dataObj) => {
this.info = { ...this.info, ...dataObj };
});
},
};
</script>

<style scoped>
.album {
min-height: 50rem; /* Can be removed; just added for demo purposes */
padding-top: 3rem;
padding-bottom: 3rem;
background-color: #f7f7f7;
}

.card {
float: left;
width: 33.333%;
padding: 0.75rem;
margin-bottom: 2rem;
border: 1px solid #efefef;
text-align: center;
}

.card > img {
margin-bottom: 0.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
<template>
<div>
<section class="jumbotron">
<h3 class="jumbotron-heading">Search Github Users</h3>
<div>
<input
type="text"
placeholder="enter the name you search"
v-model="keyword"
/>&nbsp;
<button @click="searchUsers">Search</button>
</div>
</section>
</div>
</template>

<script>
import axios from "axios";
export default {
name: "search",
components: {},
data() {
return {
keyword: "",
};
},
methods: {
searchUsers() {
//请求前更新List的数据
this.$bus.$emit("updateListData", {
isFirst: false,
isLoading: true,
errMsg: "",
users: [],
});
//接口后台已经解决了跨域的问题
axios.get(`https://api.github.com/search/users?q=${this.keyword}`).then(
(res) => {
//请求成功后更新List的数据
this.$bus.$emit("updateListData", {
isLoading: false,
errMsg: "",
users: res.data.items,
});
},
(err) => {
//请求失败后更新List的数据
this.$bus.$emit("updateListData", {
isLoading: false,
errMsg: err.message,
users: [],
});
}
);
},
},
};
</script>

<style scoped lang="less"></style>

main.js:

1
2
3
4
5
6
new Vue({
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this
}
}).$mount('#app')

vue-resource插件发起请求

  • 01 安装vue-resource插件
1
npm i vue-resource
  • 02 在 main.js 中引入与使用插件
1
2
3
4
5
6
7
//01. 引入vue-resource插件
import vueResource from "vue-resource";

Vue.config.productionTip = false;

//02. 使用vue-resource插件
Vue.use(vueResource);
  • 03 发起请求 与上面gitHub案例的不同之处就是不用再引入axios,发起请求时只需要将 axios 字样改成 this.$http 即可,其他一模一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
this.$http.get(`https://api.github.com/search/users?q=${this.keyword}`).then(
(res) => {
//请求成功后更新List的数据
this.$bus.$emit("updateListData", {
isLoading: false,
errMsg: "",
users: res.data.items,
});
},
(err) => {
//请求失败后更新List的数据
this.$bus.$emit("updateListData", {
isLoading: false,
errMsg: err.message,
users: [],
});
}
);

插槽

默认插槽

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
<template>
<div class="container">
<Category title="美食" >
<img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
</Category>

<Category title="游戏" >
<ul>
<li v-for="(g,index) in games" :key="index">{{g}}</li>
</ul>
</Category>

<Category title="电影">
<video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
</Category>
</div>
</template>

<script>
import Category from './components/Category'
export default {
name:'App',
components:{Category},
data() {
return {
foods:['火锅','烧烤','小龙虾','牛排'],
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》']
}
},
}
</script>

<style scoped>
.container{
display: flex;
justify-content: space-around;
}
</style>

Category.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
</div>
</template>

<script>
export default {
name:'Category',
props:['title']
}
</script>

<style scoped>
.category{
background-color: skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
background-color: orange;
}
video{
width: 100%;
}
img{
width: 100%;
}
</style>

具名插槽

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
<template>
<div class="container">
<Category title="美食">
<img
slot="center"
src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg"
alt=""
/>
<a slot="footer" href="http://www.atguigu.com">更多美食</a>
</Category>

<Category title="游戏">
<ul slot="center">
<li v-for="(g, index) in games" :key="index">{{ g }}</li>
</ul>
<div class="foot" slot="footer">
<a href="http://www.atguigu.com">单机游戏</a>
<a href="http://www.atguigu.com">网络游戏</a>
</div>
</Category>

<Category title="电影">
<video
slot="center"
controls
src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
></video>
<template v-slot:footer>
<div class="foot">
<a href="http://www.atguigu.com">经典</a>
<a href="http://www.atguigu.com">热门</a>
<a href="http://www.atguigu.com">推荐</a>
</div>
<h4>欢迎前来观影</h4>
</template>
</Category>
</div>
</template>

<script>
import Category from "./components/Category";
export default {
name: "App",
components: { Category },
data() {
return {
foods: ["火锅", "烧烤", "小龙虾", "牛排"],
games: ["红色警戒", "穿越火线", "劲舞团", "超级玛丽"],
films: ["《教父》", "《拆弹专家》", "《你好,李焕英》", "《尚硅谷》"],
};
},
};
</script>

<style scoped>
.container,
.foot {
display: flex;
justify-content: space-around;
}
h4 {
text-align: center;
}
</style>

Category.vue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<div class="category">
<h3>{{title}}分类</h3>
<!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
<slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot>
<slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot>
</div>
</template>

<script>
export default {
name:'Category',
props:['title']
}
</script>

<style scoped>
.category{
background-color: skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
background-color: orange;
}
video{
width: 100%;
}
img{
width: 100%;
}
</style>

作用域插槽

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
<template>
<div class="container">

<Category title="游戏">
<template scope="atguigu">
<ul>
<li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li>
</ul>
</template>
</Category>

<Category title="游戏">
<template scope="{games}">
<ol>
<li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li>
</ol>
</template>
</Category>

<Category title="游戏">
<template slot-scope="{games}">
<h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
</template>
</Category>

</div>
</template>

<script>
import Category from './components/Category'
export default {
name:'App',
components:{Category},
}
</script>

<style scoped>
.container,.foot{
display: flex;
justify-content: space-around;
}
h4{
text-align: center;
}
</style>

Category.vue:

  • 通过作用域插槽,将组件 Category.vue 中的数据通过插槽传递给 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
<template>
<div class="category">
<h3>{{title}}分类</h3>
<slot :games="games" msg="hello">我是默认的一些内容</slot>
</div>
</template>

<script>
export default {
name:'Category',
props:['title'],
data() {
return {
games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
}
},
}
</script>

<style scoped>
.category{
background-color: skyblue;
width: 200px;
height: 300px;
}
h3{
text-align: center;
background-color: orange;
}
video{
width: 100%;
}
img{
width: 100%;
}
</style>