复习Vue16、常用API
说明
16.1、 Vue.set
向响应式对象中添加一个属性,并确保这个新属性同样是响应式的,且会触发视图更新。
使用方法:Vue.set(target,propertyName,value)
下面通过一个案例来演示一下,这个案例是在以前所做的用户列表的案例上进行修改的,
这里需求是给每个用户动态的添加身高。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>列表渲染</title>
<style>
.actived {
background-color: #dddddd;
}
</style>
</head>
<body>
<div id="app">
<p v-if="users.length===0">没有任何用户数据</p>
<ul v-else>
<li
v-for="(item,index) in users"
:key="item.id"
:style="{backgroundColor:selectItem===item?'#dddddd':'transparent'}"
@mousemove="selectItem=item"
>
编号:{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}
</li>
</ul>
<p>
总人数:{{totalCount}}
</p>
</div>
<!-- <script src="vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
selectItem: "",
num: 100,
totalCount: 0,
users: [],
},
//组件实例已创建时
async created() {
const users = await this.getUserList();
this.users = users;
//批量更新用户身高
this.batchUpdate();
},
methods: {
//批量更新身高,动态的给users中添加身高属性
batchUpdate() {
this.users.forEach((c) => {
c.height = 0;
});
},
getTotal: function () {
console.log("methods");
return this.users.length + "个";
},
getUserList: function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
]);
}, 2000);
});
},
},
watch: {
users: {
immediate: true, //立即执行
handler(newValue, oldValue) {
this.totalCount = newValue.length + "个人";
},
},
},
});
</script>
</body>
</html>
在上面的代码中,我首先把列表中,展示的内容做了一个修改,这里不在显示索引值,而是展示身高。
编号:{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}
但是我们知道在users
中是没有height
这个属性的,所以下面可以动态添加这个属性。
所以在create
方法中,调用了batchUpdate
方法,来动态更新。
//组件实例已创建时
async created() {
const users = await this.getUserList();
this.users = users;
//批量更新用户身高
this.batchUpdate();
},
在methods
中,添加了batchUpdate
方法。
//批量更新身高,动态的给users中添加身高属性
batchUpdate() {
this.users.forEach((c) => {
c.height = 0;
});
},
在上面的代码中,对users
进行遍历,每遍历一次,取出一个对象后,动态添加一个属性height
,并且初始值为0.
这样刷新浏览器,可以看到对应的效果。
下面,我们在做一个功能,就是用户在一个文本框中,输入一个身高值,单击按钮,统一把所有用户的身高进行更新。
首先在data
中添加一个属性height
,该属性会与文本框进行绑定。
data: {
selectItem: "",
num: 100,
totalCount: 0,
users: [],
height: 0,
},
下面创建文本框,以及更新按钮
<p>
<input type="text" v-model.number="height" />
<button @click="batchUpdate">批量更新用户身高</button>
</p>
这里我们可以看到,我们是用文本框中输入的值,更新了users
数组中的height
属性的值。
但是,当我们在浏览器中,单击按钮进行更新的时候,发现不起作用。
因为,现在动态所添加的height
属性并不是响应式的。
但是,当把鼠标移动到列表项的时候,数据发生了变化,就是因为这时触发了我们给列表所添加的mousemove
这个事件,导致页面重新刷新,这时发现数据发生变化了。
那么我们应该怎样解决这个问题呢?
这就需要,在batchUpdate
方法中,使用Vue.set()
方法
batchUpdate() {
this.users.forEach((c) => {
// c.height = this.height;
Vue.set(c, "height", this.height);
});
},
修改的代码含义就是通过Vue.set
方法,给users
数组中每个对象,设置一个height
属性,这时该属性就变成了响应式的,同时把 data
中的height
属性的值赋值给height
.
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>列表渲染</title>
<style>
.actived {
background-color: #dddddd;
}
</style>
</head>
<body>
<div id="app">
<p v-if="users.length===0">没有任何用户数据</p>
<ul v-else>
<p>
<input type="text" v-model.number="height" />
<button @click="batchUpdate">批量更新用户身高</button>
</p>
<li
v-for="(item,index) in users"
:key="item.id"
:style="{backgroundColor:selectItem===item?'#dddddd':'transparent'}"
@mousemove="selectItem=item"
>
编号:{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}
</li>
</ul>
<p>
总人数:{{totalCount}}
</p>
</div>
<!-- <script src="vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: "#app",
data: {
selectItem: "",
num: 100,
totalCount: 0,
users: [],
height: 0,
},
//组件实例已创建时
async created() {
const users = await this.getUserList();
this.users = users;
//批量更新用户身高
this.batchUpdate();
},
methods: {
//批量更新身高,动态的给users中添加身高属性
batchUpdate() {
this.users.forEach((c) => {
// c.height = this.height;
Vue.set(c, "height", this.height);
});
},
getTotal: function () {
console.log("methods");
return this.users.length + "个";
},
getUserList: function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
]);
}, 2000);
});
},
},
watch: {
users: {
immediate: true, //立即执行
handler(newValue, oldValue) {
this.totalCount = newValue.length + "个人";
},
},
},
});
</script>
</body>
</html>
16.2 、Vue.delete
删除对象的属性,如果对象是响应式的,确保删除能触发更新视图。
使用方式:Vue.delete(target,propertyName)
如果使用delete obj['property']
是不能更新页面的。
以上两个方法Vue.set()
和Vue.delete()
等同于以下两个实例方法
vm.$set()
vm.$delete()
vm
表示的是Vue
的实例。
所以我们在batchUpdate
中也可以采用如下的方式,来批量更新用户的身高数据。
batchUpdate() {
this.users.forEach((c) => {
// c.height = this.height;
// Vue.set(c, "height", this.height);
this.$set(c, "height", this.height);
});
},
16.3、 vm.$on
与vm.$emit
16.3.1 、列表组件设计
这两个api
在前面的课程中,我们也已经讲解过,主要用来实现:事件总线。
下面,我们将这两个API
应用到用户列表这个案例中。主要是把事件总线
这个应用再次复习一下。
当然,这里首先是把用户列表这个案例,按照我们前面所学习的组件的知识,进行拆分一下,实现组件化的应用。
初步改造后的代码,如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>列表渲染</title>
<style>
.actived {
background-color: #dddddd;
}
</style>
</head>
<body>
<div id="app">
<!-- 批量更新身高 -->
<p>
<input type="text" v-model.number="height" />
<button @click="batchUpdate">批量更新用户身高</button>
</p>
<!-- 用户列表组件 -->
<user-list :users="users"></user-list>
<p>
总人数:{{totalCount}}
</p>
</div>
<!-- <script src="vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 用户列表组件创建
Vue.component("user-list", {
data() {
return {
selectItem: "",
};
},
props: {
users: {
type: Array,
default: [],
},
},
template: `
<div>
<p v-if="users.length===0">没有任何用户数据</p>
<ul v-else>
<li
v-for="(item,index) in users"
:key="item.id"
:style="{backgroundColor:selectItem===item?'#dddddd':'transparent'}"
@mousemove="selectItem=item"
>
编号:{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}
</li>
</ul>
</div>
`,
});
new Vue({
el: "#app",
data: {
num: 100,
totalCount: 0,
users: [],
height: 0,
},
//组件实例已创建时
async created() {
const users = await this.getUserList();
this.users = users;
//批量更新用户身高
this.batchUpdate();
},
methods: {
//批量更新身高,动态的给users中添加身高属性
batchUpdate() {
this.users.forEach((c) => {
// c.height = this.height;
// Vue.set(c, "height", this.height);
this.$set(c, "height", this.height);
});
},
getTotal: function () {
console.log("methods");
return this.users.length + "个";
},
getUserList: function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve([{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
]);
}, 2000);
});
},
},
watch: {
users: {
immediate: true, //立即执行
handler(newValue, oldValue) {
this.totalCount = newValue.length + "个人";
},
},
},
});
</script>
</body>
</html>
在上面的代码中,我们首先创建了一个user-list
组件,该组件首先会通过props
接收传递过来的用户数据。
在这里我们将props
定义成了对象的形式,这样更容易进行数据类型的校验,同时还可以设置默认值。
接下来将原来定义在<div id="app"></div>
中的用户列表,要剪切到user-list
组件的template
属性中,同时,我们知道在列表中会用到selectItem
属性,所以在user-list
的data
中定义该属性,父组件就不用在定义该属性了。
下面,我们在<div id="app"></div>
中使用该组件,并且传递了用户数据。
<!-- 用户列表组件 -->
<user-list :users="users"></user-list>
现在用户列表的组件,在这里我们就创建好了。
16.3.2 用户添加组件设计
下面我们在创建一个组件,该组件封装了一个文本框和添加用户信息的按钮。
代码如下:
//新增用户组件
Vue.component("user-add", {
data() {
return {
userInfo: "",
};
},
template: `
<div>
<p>
<input type="text" v-model="userInfo" v-on:keydown.enter="addUser" />
</p>
<button @click="addUser">新增用户</button>
</div>
`,
methods: {
addUser() {
//将输入的用户数据通知给父组件,来完成新增用户操作.
this.$emit("add-user", this.userInfo);
this.userInfo = "";
},
},
});
在上面的代码中,我们创建了user-add
这个组件,该组件最终呈现的就是就是一个文本框与一个添加按钮。并且通过v-model
将userInfo
属性与文本框进行了绑定。同时,单击按钮的时候,执行addUser
方法,在该方法中,通过$emit
想父组件发送了一个事件,同时将用户在文本框中输入的数据也传递过去。
然后清空文本框,
下面看一下父组件的处理。
<!-- 新增用户 -->
<user-add @add-user="addUser"></user-add>
在<div id="app"></div>
中使用user-add
这个组件,同时接受传递过来的事件add-user
,然后执行addUser
方法。
下面看一下addUser
这个方法的具体实现。
在vue
实例的methods
属性中,添加addUser
这个方法。
//添加用户的信息
addUser(userInfo) {
this.users.push({
id: this.users[this.users.length - 1].id + 1,
name: userInfo,
});
},
接受用户在文本框中输入的数据,然后添加到users
数组中。
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>列表渲染</title>
<style>
.actived {
background-color: #dddddd;
}
</style>
</head>
<body>
<div id="app">
<!-- 批量更新身高 -->
<p>
<input type="text" v-model.number="height" />
<button @click="batchUpdate">批量更新用户身高</button>
</p>
<!-- 新增用户 -->
<user-add @add-user="addUser"></user-add>
<!-- 用户列表组件 -->
<user-list :users="users"></user-list>
<p>
总人数:{{totalCount}}
</p>
</div>
<!-- <script src="vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//新增用户组件
Vue.component("user-add", {
data() {
return {
userInfo: "",
};
},
template: `
<div>
<p>
<input type="text" v-model="userInfo" v-on:keydown.enter="addUser" />
</p>
<button @click="addUser">新增用户</button>
</div>
`,
methods: {
addUser() {
//将输入的用户数据通知给父组件,来完成新增用户操作.
this.$emit("add-user", this.userInfo);
this.userInfo = "";
},
},
});
// 用户列表
Vue.component("user-list", {
data() {
return {
selectItem: "",
};
},
props: {
users: {
type: Array,
default: [],
},
},
template: `
<div>
<p v-if="users.length===0">没有任何用户数据</p>
<ul v-else>
<li
v-for="(item,index) in users"
:key="item.id"
:style="{backgroundColor:selectItem===item?'#dddddd':'transparent'}"
@mousemove="selectItem=item"
>
编号:{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}
</li>
</ul>
</div>
`,
});
new Vue({
el: "#app",
data: {
num: 100,
totalCount: 0,
users: [],
height: 0,
},
//组件实例已创建时
async created() {
const users = await this.getUserList();
this.users = users;
//批量更新用户身高
this.batchUpdate();
},
methods: {
//添加用户的信息
addUser(userInfo) {
this.users.push({
id: this.users[this.users.length - 1].id + 1,
name: userInfo,
});
},
//批量更新身高,动态的给users中添加身高属性
batchUpdate() {
this.users.forEach((c) => {
// c.height = this.height;
// Vue.set(c, "height", this.height);
this.$set(c, "height", this.height);
});
},
getTotal: function () {
console.log("methods");
return this.users.length + "个";
},
getUserList: function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
]);
}, 2000);
});
},
},
watch: {
users: {
immediate: true, //立即执行
handler(newValue, oldValue) {
this.totalCount = newValue.length + "个人";
},
},
},
});
</script>
</body>
</html>
16.3.3、 自定义组件实现双向绑定
在上一个案例中,我们创建了一个user-add
这个组件,完成用户信息的添加。
并且在该组件的内部,维护了所添加的用户信息。
假如,我不想让user-add
这个组件来维护这个用户信息,而是让父组件来维护,应该怎样处理呢?
<!-- 新增用户 -->
<user-add @add-user="addUser" v-model="userInfo"></user-add>
将userInfo
的值给v-model
.
所以在父组件中要定义userInfo
new Vue({
el: "#app",
data: {
num: 100,
totalCount: 0,
users: [],
height: 0,
userInfo: "abc",
},
下面看一下user-add
组件的修改
Vue.component("user-add", {
// data() {
// return {
// userInfo: "",
// };
// },
props: ["value"],
template: `
<div>
<p>
<input type="text" :value="value" @input="onInput" v-on:keydown.enter="addUser" />
</p>
<button @click="addUser">新增用户</button>
</div>
`,
methods: {
addUser() {
//将输入的用户数据通知给父组件,来完成新增用户操作.
// this.$emit("add-user", this.userInfo);
this.$emit("add-user");
// this.userInfo = "";
},
onInput(e) {
this.$emit("input", e.target.value);
},
},
});
在user-add
组件中,定义props
接收传递过来的值,也就是userInfo
的值会传递给value
下面修改user-add
组件中的模板,文本框绑定value
值。通过给其添加input
事件,在文本框中输入值后,调用onInput
方法,在该方法中获取用户在文本框中输入的值,然后发送input
事件。对应的值传递给父组件中的userInfo
同时单击“新增用户”按钮的时候,执行addUser
方法,在该方法中发送事件add-user
,也不需要传递数据了。
同时,父组件中的addUser
方法实现如下:
addUser() {
this.users.push({
id: this.users[this.users.length - 1].id + 1,
name: this.userInfo,
});
this.userInfo = "";
},
直接从data
中获取userInfo
的数据。
总结:
以下的写法
<user-add @add-user="addUser" v-model="userInfo"></user-add>
等价以下的写法
<user-add
v-bind:value="userInfo"
v-on:input="userInfo = $event"
></user-add>
也就是说v-model
就是v-bind
与v-on
的语法糖。
在这里我们将userInfo
的值给了value
属性,而value
属性传递到了user-add
组件中,所以在user-add
组件中要通过props
来接收value
属性的值。
在user-add
组件的文本中,输入内容后触发@input
事件,对应的会调用onInput
方法,在该方法中,执行了
this.$emit("input", e.target.value);
发送了input
事件,并且传递了用户在文本框中输入的值。
那很明显,这时会触发下面代码中的input
事件,将传递过来的值给userInfo
属性。
<user-add
v-bind:value="userInfo"
v-on:input="userInfo = $event"
></user-add>
以上就是v-model
的原理,希望仔细体会,这也是面试经常会被问到的问题。
16.3.4. 使用插槽完成内容分发
关于插槽的内容,在前面的的课程中我们已经学习过了,那么什么是内容分发呢?
其实就是在使用组件的时候,我们提供具体的数据内容,然后这些内容会插入到组件内部插槽的位置,这就是所谓的内容分发。
下面,要做的事情就是创建一个信息的提示窗口。例如:当添加用户成功后,给出相应的提示。
首先先创建样式:
<style>
.actived {
background-color: #dddddd;
}
.message-box {
padding: 10px 20px;
background-color: #4fc;
border: 1px solid #42b;
}
.message-box-close {
float: right;
}
</style>
下面创建对应的组件。
//创建弹出的组件
Vue.component("message", {
//show表示的含义,控制弹出窗口的显示与隐藏。
//slot:表示占坑。也就是窗口中的内容,是通过外部组件传递过来的。
props: ["show"],
template: `<div class='message-box' v-if="show">
<slot></slot>
<span class="message-box-close">关闭</span>
</div>`,
});
使用上面的组件
<div id="app">
<!-- 弹窗组件 -->
<message :show="isShow">
添加用户成功
</message>
<!-- 批量更新身高 -->
</div> ;
在data
中定义isShow
属性,初始值为false
.
new Vue({
el: "#app",
data: {
num: 100,
totalCount: 0,
users: [],
height: 0,
userInfo: "abc",
isShow: false,
},
下面就是当用户完成添加的时候,弹出该窗口。
//添加用户的信息
addUser() {
this.users.push({
id: this.users[this.users.length - 1].id + 1,
name: this.userInfo,
});
this.userInfo = "";
//完成用户添加后,给出相应的提示信息
this.isShow = true;
},
在addUser
方法中完成了用户信息的添加后,将isShow
的属性值设置为true
.
这时弹出了对应的窗口。
下面要考虑的就是,单击窗口右侧的“关闭”按钮,将窗口关闭这个效果应该怎样实现。
首先给关闭
按钮添加单击事件。
如下所示:
//创建弹出的组件
Vue.component("message", {
//show表示的含义,控制弹出窗口的显示与隐藏。
//slot:表示占坑。也就是窗口中的内容,是通过外部组件传递过来的。
props: ["show"],
template: `<div class='message-box' v-if="show">
<slot></slot>
<span class="message-box-close" @click='$emit("close",false)'>关闭</span>
</div>`,
});
当单击关闭按钮后,会发送一个close
事件,同时传递的值为false
.
下面回到父组件中,对close
事件进行处理。
<!-- 弹窗组件 -->
<message :show="isShow" @close="closeWindow">
添加用户成功
</message>
当close
事件触发后,执行closeWindow
方法。
//关闭窗口
closeWindow(data) {
this.isShow = data;
},
在closeWindow
方法中,根据子组件传递过来的值false
,修改isShow
属性的值,这时isShow
的值为false
.这时窗口关闭。
下面要解决的问题就是,在使用弹窗组件的时候,不仅能传递窗口的内容,还能传递其它的内容,例如标题等。
那应该怎样处理呢?
这里,可以使用具名插槽
代码如下:
<!-- 弹窗组件 -->
<message :show="isShow" @close="closeWindow">
<!-- titile的插槽 -->
<template v-slot:title>
<h2>恭喜</h2>
</template>
<!-- 默认插槽 -->
<template>
添加用户成功
</template>
</message>
下面修改一下message
组件中的内容。
//创建弹出的组件
Vue.component("message", {
//show表示的含义,控制弹出窗口的显示与隐藏。
//slot:表示占坑。也就是窗口中的内容,是通过外部组件传递过来的。
props: ["show"],
template: `<div class='message-box' v-if="show">
<!--具名插槽-->
<slot name="title">默认标题</slot>
<slot></slot>
<span class="message-box-close" @click='$emit("close",false)'>关闭</span>
</div>`,
});
在上面定义message
组件的时候,指定了具名插槽
,名称为title
.要与在父组件中使用message
组件的时候指定的名称保持一致,同时这里如果没有传递任何内容,将会显示"默认标题"。
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>列表渲染</title>
<style>
.actived {
background-color: #dddddd;
}
.message-box {
padding: 10px 20px;
background-color: #4fc;
border: 1px solid #42b;
}
.message-box-close {
float: right;
}
</style>
</head>
<body>
<div id="app">
<!-- 弹窗组件 -->
<message :show="isShow" @close="closeWindow">
<!-- titile的插槽 -->
<template v-slot:title>
<h2>恭喜</h2>
</template>
<!-- 默认插槽 -->
<template>
添加用户成功
</template>
</message>
<!-- 批量更新身高 -->
<p>
<input type="text" v-model.number="height" />
<button @click="batchUpdate">批量更新用户身高</button>
</p>
<!-- 新增用户 -->
<user-add @add-user="addUser" v-model="userInfo"></user-add>
<!-- 用户列表组件 -->
<user-list :users="users"></user-list>
<p>
总人数:{{totalCount}}
</p>
</div>
<!-- <script src="vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//创建弹出的组件
Vue.component("message", {
//show表示的含义,控制弹出窗口的显示与隐藏。
//slot:表示占坑。也就是窗口中的内容,是通过外部组件传递过来的。
props: ["show"],
template: `<div class='message-box' v-if="show">
<!--具名插槽-->
<slot name="title">默认标题</slot>
<slot></slot>
<span class="message-box-close" @click='$emit("close",false)'>关闭</span>
</div>`,
});
//新增用户组件
Vue.component("user-add", {
// data() {
// return {
// userInfo: "",
// };
// },
props: ["value"],
template: `
<div>
<p>
<input type="text" :value="value" @input="onInput" v-on:keydown.enter="addUser" />
</p>
<button @click="addUser">新增用户</button>
</div>
`,
methods: {
addUser() {
//将输入的用户数据通知给父组件,来完成新增用户操作.
// this.$emit("add-user", this.userInfo);
this.$emit("add-user");
// this.userInfo = "";
},
onInput(e) {
this.$emit("input", e.target.value);
},
},
});
// 用户列表
Vue.component("user-list", {
data() {
return {
selectItem: "",
};
},
props: {
users: {
type: Array,
default: [],
},
},
template: `
<div>
<p v-if="users.length===0">没有任何用户数据</p>
<ul v-else>
<li
v-for="(item,index) in users"
:key="item.id"
:style="{backgroundColor:selectItem===item?'#dddddd':'transparent'}"
@mousemove="selectItem=item"
>
编号:{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}
</li>
</ul>
</div>
`,
});
new Vue({
el: "#app",
data: {
num: 100,
totalCount: 0,
users: [],
height: 0,
userInfo: "abc",
isShow: false,
},
//组件实例已创建时
async created() {
const users = await this.getUserList();
this.users = users;
//批量更新用户身高
this.batchUpdate();
},
methods: {
//关闭窗口
closeWindow(data) {
this.isShow = data;
},
//添加用户的信息
addUser() {
this.users.push({
id: this.users[this.users.length - 1].id + 1,
name: this.userInfo,
});
this.userInfo = "";
//完成用户添加后,给出相应的提示信息
this.isShow = true;
},
//批量更新身高,动态的给users中添加身高属性
batchUpdate() {
this.users.forEach((c) => {
// c.height = this.height;
// Vue.set(c, "height", this.height);
this.$set(c, "height", this.height);
});
},
getTotal: function () {
console.log("methods");
return this.users.length + "个";
},
getUserList: function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
]);
}, 2000);
});
},
},
watch: {
users: {
immediate: true, //立即执行
handler(newValue, oldValue) {
this.totalCount = newValue.length + "个人";
},
},
},
});
</script>
</body>
</html>
16.3.5 vm.$on
与vm.$emit
应用
现在,关于用户管理这个案例的一些组件拆分,以及插槽的应用在这我们已经构建好了。
下面就看一下vm.$on
与vm.$emit
的应用。
根据前面的学习,我们知道vm.$on
与vm.$emit
的典型应用就是事件总线。
也就是通过在Vue
原型上添加一个Vue
实例作为事件总线,实现组件间相互通信,而且不受组件间关系的影响
Vue.prototype.$bus=new Vue()
在所有组件最上面创建事件总线,
这样做的好处就是在任意组件中使用this.$bus
访问到该Vue
实例。
下面,我们来看一下事件总线的用法。
首先,我们这里先把事件总线创建出来。
//创建事件总线
Vue.prototype.$bus = new Vue();
下面,在创建一个警告的窗口,也就是当单击“新增用户”按钮的时候,如果用户没有填写用户名给出相应册错误提示。
在这里先把样式修改一下:
<style>
.actived {
background-color: #dddddd;
}
.message-box {
padding: 10px 20px;
}
.success {
background-color: #4fc;
border: 1px solid #42b;
}
.warning {
background-color: red;
border: 1px solid #42b;
}
.message-box-close {
float: right;
}
</style>
然后创建出对应的窗口。
<!-- 警告 -->
<message :show="showWarn" @close="closeWindow" class="warning">
<!-- titile的插槽 -->
<template v-slot:title>
<h2>警告</h2>
</template>
<!-- 默认插槽 -->
<template>
请输入用户名
</template>
</message>
注意:在上面的代码中,我们使用showWarn
这个属性控制警告窗口的显示与隐藏。
同时,为其添加了warning
样式,对应的成功的窗口需要添加success
样式。
同时在data
中定义showWarn
属性。
new Vue({
el: "#app",
data: {
num: 100,
totalCount: 0,
users: [],
height: 0,
userInfo: "abc",
isShow: false,
showWarn: false, // 控制警告窗口的显示与隐藏
},
下面要修改的就是当单击"新增用户"按钮的时候,对addUser
方法的修改
//添加用户的信息
addUser() {
if (this.userInfo) {
this.users.push({
id: this.users[this.users.length - 1].id + 1,
name: this.userInfo,
});
this.userInfo = "";
//完成用户添加后,给出相应的提示信息
this.isShow = true;
} else {
// 显示错误警告信息
this.showWarn = true;
}
},
判断userInfo
中是否有值,如果没有值,展示出错误警告信息。
通过浏览器,进行测试。发现如果用户没有在文本框中输入用户名,直接单击了“新增用户”,这时给出了错误提示的窗口。
但是用户没有关闭错误提示的窗口,而是直接在文本框中输入了用户名,然后又点击了"新增用户"按钮,这时“成功窗口”与“警告窗口”都显示出来了。
下面需要解决这个问题。
Vue.component("message", {
//show表示的含义,控制弹出窗口的显示与隐藏。
//slot:表示占坑。也就是窗口中的内容,是通过外部组件传递过来的。
props: ["show"],
template: `<div class='message-box' v-if="show">
<!--具名插槽-->
<slot name="title">默认标题</slot>
<slot></slot>
<span class="message-box-close" @click='$emit("close",false)'>关闭</span>
</div>`,
mounted() {
//给总线绑定`message-close`事件
//也就是监听是否有`message-close`事件被触发。
this.$bus.$on("message-close", () => {
this.$emit("close", false);
});
},
});
在message
组件加载完后,给事件总线绑定了message-close
事件,当该事件触发后还是向父组件发送了close
事件,这一点与单击关闭按钮是一样的。
下面,怎样触发总线的message-close
事件呢?
我们可以在窗口中添加一个“清空提示栏”按钮,单击该按钮的时候可以触发message-close
事件,从而关闭提示窗口。
<!-- 清空提示栏 -->
<div class="toolbar">
<button @click="$bus.$emit('message-close')">
清空提示栏
</button>
</div>
单击"清空提示栏"按钮后,触发事件总线的message-close
事件。
最后完善一下closeWindow
方法,该方法控制整个提示窗口的关闭
//关闭窗口
closeWindow(data) {
this.isShow = data;
this.showWarn = data;
},
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>列表渲染</title>
<style>
.actived {
background-color: #dddddd;
}
.message-box {
padding: 10px 20px;
}
.success {
background-color: #4fc;
border: 1px solid #42b;
}
.warning {
background-color: red;
border: 1px solid #42b;
}
.message-box-close {
float: right;
}
</style>
</head>
<body>
<div id="app">
<!-- 弹窗组件 -->
<message :show="isShow" @close="closeWindow" class="success">
<!-- titile的插槽 -->
<template v-slot:title>
<h2>恭喜</h2>
</template>
<!-- 默认插槽 -->
<template>
添加用户成功
</template>
</message>
<!-- 警告 -->
<message :show="showWarn" @close="closeWindow" class="warning">
<!-- titile的插槽 -->
<template v-slot:title>
<h2>警告</h2>
</template>
<!-- 默认插槽 -->
<template>
请输入用户名
</template>
</message>
<!-- 清空提示栏 -->
<div class="toolbar">
<button @click="$bus.$emit('message-close')">
清空提示栏
</button>
</div>
<!-- 批量更新身高 -->
<p>
<input type="text" v-model.number="height" />
<button @click="batchUpdate">批量更新用户身高</button>
</p>
<!-- 新增用户 -->
<user-add @add-user="addUser" v-model="userInfo"></user-add>
<!-- 用户列表组件 -->
<user-list :users="users"></user-list>
<p>
总人数:{{totalCount}}
</p>
</div>
<!-- <script src="vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//创建事件总线
Vue.prototype.$bus = new Vue();
//创建弹出的组件
Vue.component("message", {
//show表示的含义,控制弹出窗口的显示与隐藏。
//slot:表示占坑。也就是窗口中的内容,是通过外部组件传递过来的。
props: ["show"],
template: `<div class='message-box' v-if="show">
<!--具名插槽-->
<slot name="title">默认标题</slot>
<slot></slot>
<span class="message-box-close" @click='$emit("close",false)'>关闭</span>
</div>`,
mounted() {
//给总线绑定`message-close`事件
//也就是监听是否有`message-close`事件被触发。
this.$bus.$on("message-close", () => {
this.$emit("close", false);
});
},
});
//新增用户组件
Vue.component("user-add", {
// data() {
// return {
// userInfo: "",
// };
// },
props: ["value"],
template: `
<div>
<p>
<input type="text" :value="value" @input="onInput" v-on:keydown.enter="addUser" />
</p>
<button @click="addUser">新增用户</button>
</div>
`,
methods: {
addUser() {
//将输入的用户数据通知给父组件,来完成新增用户操作.
// this.$emit("add-user", this.userInfo);
this.$emit("add-user");
// this.userInfo = "";
},
onInput(e) {
this.$emit("input", e.target.value);
},
},
});
// 用户列表
Vue.component("user-list", {
data() {
return {
selectItem: "",
};
},
props: {
users: {
type: Array,
default: [],
},
},
template: `
<div>
<p v-if="users.length===0">没有任何用户数据</p>
<ul v-else>
<li
v-for="(item,index) in users"
:key="item.id"
:style="{backgroundColor:selectItem===item?'#dddddd':'transparent'}"
@mousemove="selectItem=item"
>
编号:{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}
</li>
</ul>
</div>
`,
});
new Vue({
el: "#app",
data: {
num: 100,
totalCount: 0,
users: [],
height: 0,
userInfo: "abc",
isShow: false,
showWarn: false, // 控制警告窗口的显示与隐藏
},
//组件实例已创建时
async created() {
const users = await this.getUserList();
this.users = users;
//批量更新用户身高
this.batchUpdate();
},
methods: {
//关闭窗口
closeWindow(data) {
this.isShow = data;
this.showWarn = data;
},
//添加用户的信息
addUser() {
if (this.userInfo) {
if (this.users.length > 0) {
this.users.push({
id: this.users[this.users.length - 1].id + 1,
name: this.userInfo,
});
this.userInfo = "";
//完成用户添加后,给出相应的提示信息
this.isShow = true;
}
} else {
// 显示错误警告信息
this.showWarn = true;
}
},
//批量更新身高,动态的给users中添加身高属性
batchUpdate() {
this.users.forEach((c) => {
// c.height = this.height;
// Vue.set(c, "height", this.height);
this.$set(c, "height", this.height);
});
},
getTotal: function () {
console.log("methods");
return this.users.length + "个";
},
getUserList: function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
]);
}, 2000);
});
},
},
watch: {
users: {
immediate: true, //立即执行
handler(newValue, oldValue) {
this.totalCount = newValue.length + "个人";
},
},
},
});
</script>
</body>
</html>
16.4 vm.$once
与vm.$off
关于这两个方法,大家只需要了解一下就可以了。
vm.$once
监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。
vm.$once('test', function (msg) { console.log(msg) })
vm.$off
移除自定义事件监听器。
-
如果没有提供参数,则移除所有的事件监听器;
-
如果只提供了事件,则移除该事件所有的监听器;
-
如果同时提供了事件与回调,则只移除这个回调的监听器
vm.$off() // 移除所有的事件监听器
vm.$off('test') // 移除该事件所有的监听器
vm.$off('test', callback) // 只移除这个回调的监听器
16.5 ref
和vm.$refs
ref
被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的$refs
对象上,如果在普通的DOM
元素上使用,引用指向的就是DOM
元素;如果用在子组件上,引用就指向组件的实例。
如下代码示例,是用来设置输入框的焦点
<input type="text" ref="inp" />
mounted(){
//mounted之后才能访问到inp
this.$refs.inp.focus()
}
下面在用户管理案例中,看一下具体的实现效果。
//新增用户组件
Vue.component("user-add", {
// data() {
// return {
// userInfo: "",
// };
// },
props: ["value"],
template: `
<div>
<p>
<input type="text" :value="value" @input="onInput" v-on:keydown.enter="addUser" ref="inp" />
</p>
<button @click="addUser">新增用户</button>
</div>
`,
methods: {
addUser() {
//将输入的用户数据通知给父组件,来完成新增用户操作.
// this.$emit("add-user", this.userInfo);
this.$emit("add-user");
// this.userInfo = "";
},
onInput(e) {
this.$emit("input", e.target.value);
},
},
mounted() {
this.$refs.inp.focus();
},
});
在上面的代码中,我们首先给user-add
组件模板中的文本框添加了ref
属性。
然后,在其所对应的mounted
方法中,通过$refs
找到文本框,然后为其添加焦点。
回到浏览器中,刷新浏览器,可以看到对应的文本框获取了焦点。
下面,我们在将弹出窗口修改一下:
下面修改一下message
模板中的内容。
//创建弹出的组件
Vue.component("message", {
//show表示的含义,控制弹出窗口的显示与隐藏。
//slot:表示占坑。也就是窗口中的内容,是通过外部组件传递过来的。
// props: ["show"],
data() {
return {
show: false,
};
},
template: `<div class='message-box' v-if="show">
<!--具名插槽-->
<slot name="title">默认标题</slot>
<slot></slot>
<span class="message-box-close" @click='toggle'>关闭</span>
</div>`,
mounted() {
//给总线绑定`message-close`事件
//也就是监听是否有`message-close`事件被触发。
this.$bus.$on("message-close", () => {
// this.$emit("close", false);
this.toggle();
});
},
methods: {
toggle() {
this.show = !this.show;
},
},
});
在上面的代码中,取消了props
,而定义了data
属性,表明的含义就是整个窗口的状态的控制,也就是提示窗口的显示与隐藏,都是有自己控制,而不是受外部传递的参数来进行控制了。
同时,在该组件中,添加了toggle
方法,修改对应的show
的状态。
所以模板中,按钮的单击事件触发以后,调用的就是toggle
方法,也就是单击了窗口的右侧的关闭按钮,是通过调用toggle
方法来完成,窗口的关闭。
同样事件message-close
触发以后,也是调用toggle
方法来关闭窗口。
下面看一下关于message
模板的使用。
<!-- 弹窗组件 -->
<message ref="msgSuccess" class="success">
<!-- titile的插槽 -->
<template v-slot:title>
<h2>恭喜</h2>
</template>
<!-- 默认插槽 -->
<template>
添加用户成功
</template>
</message>
在上面的代码中,我们为message
组件,添加了ref
属性。
同理表示警告的窗口,也需要添加ref
的属性.
<!-- 警告 -->
<message ref="msgWaring" class="warning">
<!-- titile的插槽 -->
<template v-slot:title>
<h2>警告</h2>
</template>
<!-- 默认插槽 -->
<template>
请输入用户名
</template>
</message>
关于data
中定义的isShow
与showWarn
就可以取消了。
data: {
num: 100,
totalCount: 0,
users: [],
height: 0,
userInfo: "abc",
// isShow: false,
// showWarn: false, // 控制警告窗口的显示与隐藏
},
当用户点击“新增用户”按钮的时候,执行addUser
方法,下面也需要对该方法进行如下修改:
//添加用户的信息
addUser() {
if (this.userInfo) {
if (this.users.length > 0) {
this.users.push({
id: this.users[this.users.length - 1].id + 1,
name: this.userInfo,
});
this.userInfo = "";
//完成用户添加后,给出相应的提示信息
// this.isShow = true;
this.$refs.msgSuccess.toggle();
}
} else {
// 显示错误警告信息
// this.showWarn = true;
this.$refs.msgWaring.toggle();
}
},
在上面的代码中,我们都是通过$ref
找到对应的窗口,然后调用toggle
方法,来修改对应的状态。
因为,我们前面讲过如果ref
用在子组件上,引用就指向组件的实例.所以可以调用组件内部的toggle
方法。
完整代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>列表渲染</title>
<style>
.actived {
background-color: #dddddd;
}
.message-box {
padding: 10px 20px;
}
.success {
background-color: #4fc;
border: 1px solid #42b;
}
.warning {
background-color: red;
border: 1px solid #42b;
}
.message-box-close {
float: right;
}
</style>
</head>
<body>
<div id="app">
<!-- 弹窗组件 -->
<message ref="msgSuccess" class="success">
<!-- titile的插槽 -->
<template v-slot:title>
<h2>恭喜</h2>
</template>
<!-- 默认插槽 -->
<template>
添加用户成功
</template>
</message>
<!-- 警告 -->
<message ref="msgWaring" class="warning">
<!-- titile的插槽 -->
<template v-slot:title>
<h2>警告</h2>
</template>
<!-- 默认插槽 -->
<template>
请输入用户名
</template>
</message>
<!-- 清空提示栏 -->
<div class="toolbar">
<button @click="$bus.$emit('message-close')">
清空提示栏
</button>
</div>
<!-- 批量更新身高 -->
<p>
<input type="text" v-model.number="height" />
<button @click="batchUpdate">批量更新用户身高</button>
</p>
<!-- 新增用户 -->
<user-add @add-user="addUser" v-model="userInfo"></user-add>
<!-- 用户列表组件 -->
<user-list :users="users"></user-list>
<p>
总人数:{{totalCount}}
</p>
</div>
<!-- <script src="vue.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//创建事件总线
Vue.prototype.$bus = new Vue();
//创建弹出的组件
Vue.component("message", {
//show表示的含义,控制弹出窗口的显示与隐藏。
//slot:表示占坑。也就是窗口中的内容,是通过外部组件传递过来的。
// props: ["show"],
data() {
return {
show: false,
};
},
template: `<div class='message-box' v-if="show">
<!--具名插槽-->
<slot name="title">默认标题</slot>
<slot></slot>
<span class="message-box-close" @click='toggle'>关闭</span>
</div>`,
mounted() {
//给总线绑定`message-close`事件
//也就是监听是否有`message-close`事件被触发。
this.$bus.$on("message-close", () => {
// this.$emit("close", false);
//当警告窗口和提示信息的窗口,展示出来了才关闭。
if (this.show) {
this.toggle();
}
});
},
methods: {
toggle() {
this.show = !this.show;
},
},
});
//新增用户组件
Vue.component("user-add", {
// data() {
// return {
// userInfo: "",
// };
// },
props: ["value"],
template: `
<div>
<p>
<input type="text" :value="value" @input="onInput" v-on:keydown.enter="addUser" ref="inp" />
</p>
<button @click="addUser">新增用户</button>
</div>
`,
methods: {
addUser() {
//将输入的用户数据通知给父组件,来完成新增用户操作.
// this.$emit("add-user", this.userInfo);
this.$emit("add-user");
// this.userInfo = "";
},
onInput(e) {
this.$emit("input", e.target.value);
},
},
mounted() {
this.$refs.inp.focus();
},
});
// 用户列表
Vue.component("user-list", {
data() {
return {
selectItem: "",
};
},
props: {
users: {
type: Array,
default: [],
},
},
template: `
<div>
<p v-if="users.length===0">没有任何用户数据</p>
<ul v-else>
<li
v-for="(item,index) in users"
:key="item.id"
:style="{backgroundColor:selectItem===item?'#dddddd':'transparent'}"
@mousemove="selectItem=item"
>
编号:{{item.id}} 姓名:{{item.name}}---身高:{{item.height}}
</li>
</ul>
</div>
`,
});
new Vue({
el: "#app",
data: {
num: 100,
totalCount: 0,
users: [],
height: 0,
userInfo: "abc",
// isShow: false,
// showWarn: false, // 控制警告窗口的显示与隐藏
},
//组件实例已创建时
async created() {
const users = await this.getUserList();
this.users = users;
//批量更新用户身高
this.batchUpdate();
},
methods: {
//关闭窗口
closeWindow(data) {
this.isShow = data;
this.showWarn = data;
},
//添加用户的信息
addUser() {
if (this.userInfo) {
if (this.users.length > 0) {
this.users.push({
id: this.users[this.users.length - 1].id + 1,
name: this.userInfo,
});
this.userInfo = "";
//完成用户添加后,给出相应的提示信息
// this.isShow = true;
this.$refs.msgSuccess.toggle();
}
} else {
// 显示错误警告信息
// this.showWarn = true;
this.$refs.msgWaring.toggle();
}
},
//批量更新身高,动态的给users中添加身高属性
batchUpdate() {
this.users.forEach((c) => {
// c.height = this.height;
// Vue.set(c, "height", this.height);
this.$set(c, "height", this.height);
});
},
getTotal: function () {
console.log("methods");
return this.users.length + "个";
},
getUserList: function () {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
id: 1,
name: "张三",
},
{
id: 2,
name: "李四",
},
{
id: 3,
name: "老王",
},
]);
}, 2000);
});
},
},
watch: {
users: {
immediate: true, //立即执行
handler(newValue, oldValue) {
this.totalCount = newValue.length + "个人";
},
},
},
});
</script>
</body>
</html>
下面在对ref
与vm.$refs
的使用做一个总结:
ref
是作为渲染结果被创建的,在初始渲染时不能访问它们。也就是必须在mounted
构造函数中。$refs
不是响应式的,不要试图用它在模板中做数据绑定。