这里我们接着上一篇继续 juejin.cn/post/711112…
15、Vue组件化的理解
定义:组件是可复用的Vue实例,准确讲它是VueComponent的实例,继承自Vue
优点:组件化可以增加代码的复用性,可维护性和可测试性。
使用场景:什么时候使用组件?以下分类可以作为参数
第一:通用组件:实现最基本的功能,具有通用性,复用性。例如按钮组件,输入框组件,布局组件等。(Element UI组件库就是属于这种通用的组件)
第二:业务组件,用于完成具体的业务,具有一定的复用性。例如登录组件,轮播图组件。
第三:页面组件,组织应用各部分独立内容,需要时在不同页面组件间切换,例如:商品列表页,详情页组件。
如何使用组件
- 定义:
Vue.component(),components选项 - 分类:有状态组件(有data属性),
functional - 通信:
props,$emit()/$on(),provide/inject - 内容分发:
<slot>,<template>,v-slot - 使用及优化:
is,keep-alive,异步组件(这些内容在后面的课程中会详细的讲解)
组件的本质
vue中的组件经历如下过程 组件配置 => VueComponent实例 => render() => Virtual DOM=> DOM 所以组件的本质是产生虚拟DOM
关于这块内容,在后面的课程中还会深入的探讨,包虚拟dom,以及vue的源码。
16、常用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>
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>
在这里我们需要在文本框中输入的值为数字类型,所以添加了一个number的后缀。现在,文本框与height属性绑定在一起了。下面单击按钮后,还是去执行batchUpdate方法。
//批量更新身高,动态的给users中添加身高属性
batchUpdate() {
this.users.forEach((c) => {
c.height = this.height;
});
},
这里我们可以看到,我们是用文本框中输入的值,更新了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>
<input type="text" v-model.number="height" />
<button @click="batchUpdate">批量更新用户身高</button>
</p>
<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>
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);
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.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 列表组件设计
主要用来实现:事件总线。 初步改造后的代码,如下:
<!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>
// 用户列表组件创建
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>
//新增用户组件
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>
//创建弹出的组件
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>
//创建事件总线
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.$on('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>
//创建事件总线
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不是响应式的,不要试图用它在模板中做数据绑定。
17、过滤器
17.1 过滤器基本使用
在Vue中,过滤器的作用就是格式化数据,也就是对数据的过滤处理,比如将字符串格式化为首字母大写
或者将日期格式化为指定的格式等。
下面先看一下自定义过滤器的语法
Vue.filter('过滤器名称',function(value){
//value参数表示要处理的数据
//过滤器业务逻辑,最终将处理后的数据进行返回
})
定义好以后可以使用。使用的方式如下:
<div>{{msg|upper}}</div>
<div>{{msg|upper|lower}}</div>
具体的程序如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>过滤器基本使用</title>
</head>
<body>
<div id="app">
<input type="text" v-model="msg" />
<div>
<!--使用过滤器-->
{{msg|upper}}
</div>
</div>
<script src="vue.js"></script>
<script>
//定义过滤器,让输入的单词首字母变成大写.
Vue.filter("upper", function (value) {
//获取首字母让其转换成大写,然后拼接后面的内容。
return value.charAt(0).toUpperCase() + value.slice(0);
});
const vm = new Vue({
el: "#app",
data: {
msg: "",
},
});
</script>
</body>
</html>
过滤器在使用的时候,可以采用如下的方式:
<div>{{msg|upper|lower}}</div>
也就是,先对msg中的数据使用upper过滤器,得到的结果在交给lower过滤器进行处理。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>过滤器基本使用</title>
</head>
<body>
<div id="app">
<input type="text" v-model="msg" />
<div>
{{msg|upper}}
</div>
<div>
{{msg|upper|lower}}
</div>
</div>
<script src="vue.js"></script>
<script>
//定义过滤器,让输入的单词首字母变成大写.
Vue.filter("upper", function (value) {
//获取首字母让其转换成大写,然后拼接后面的内容。
return value.charAt(0).toUpperCase() + value.slice(0);
});
Vue.filter("lower", function (value) {
return value.charAt(0).toLowerCase() + value.slice(0);
});
const vm = new Vue({
el: "#app",
data: {
msg: "",
},
});
</script>
</body>
</html>
上面定义的顾虑器是全局的过滤器,当然也可以定义局部过滤器。
局部过滤器只能在其所定义的组件内使用。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>过滤器基本使用</title>
</head>
<body>
<div id="app">
<input type="text" v-model="msg" />
<div>
{{msg|upper}}
</div>
<div>
{{msg|upper|lower}}
</div>
</div>
<script src="vue.js"></script>
<script>
//定义过滤器,让输入的单词首字母变成大写.
// Vue.filter("upper", function (value) {
// //获取首字母让其转换成大写,然后拼接后面的内容。
// return value.charAt(0).toUpperCase() + value.slice(0);
// });
Vue.filter("lower", function (value) {
return value.charAt(0).toLowerCase() + value.slice(0);
});
const vm = new Vue({
el: "#app",
data: {
msg: "",
},
//局部过滤器
filters: {
upper: function (value) {
return value.charAt(0).toUpperCase() + value.slice(0);
},
},
});
</script>
</body>
</html>
在上面的代码中,我们通过fileters定义了一个局部的过滤器upper.
在前面,我们也说过Vue实例本身就是一个组件。
17.2 带参数的过滤器
带参数的过滤器定义如下:
Vue.filter('format',function(value,arg1){
//value表示要过滤的数据。
//arg1,表示传递过来的参数
})
使用的方式如下
<div>
{{data|format(`yyyy-MM-dd`)}}
</div>
要处理的数据data交给了过滤器中回调函数的value参数,yyyy-MM-dd交给了arg1.
如下代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>过滤器参数</title>
</head>
<body>
<div id="app">
<div>
{{date|format('abc','hello')}}
</div>
</div>
<script src="vue.js"></script>
<script>
Vue.filter("format", function (value, arg, arg1) {
console.log(arg, arg1);
return value;
});
const vm = new Vue({
el: "#app",
data: {
date: new Date(),
},
});
</script>
</body>
</html>
在上面的代码中,我们定义了format过滤器,然后在使用的时候,我们是将date日期数据给了value
abc这个字符串给了arg,hello给了arg1.
下面,我们把日期给具体的处理一下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>过滤器参数</title>
</head>
<body>
<div id="app">
<div>
{{date|format('yyyy-MM-dd')}}
</div>
</div>
<script src="vue.js"></script>
<script>
Vue.filter("format", function (value, arg, arg1) {
let result = "";
result +=
value.getFullYear() +
"-" +
(value.getMonth() + 1) +
"-" +
value.getDate();
return result;
});
const vm = new Vue({
el: "#app",
data: {
date: new Date(),
},
});
</script>
</body>
</html>
18、自定义指令
18.1 自定义指令基本用法
为什么需要自定义指令呢?
因为内置指令不满足需求。
下面看一下基本的创建自定义指令语法:
Vue.directive('focus',{
inserted:function(el){
//获取元素焦点
el.focus();
}
})
自定义指令用法
<input type="text" v-focus>
下面看一下具体的代码。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>自定义指令基本使用</title>
</head>
<body>
<div id="app">
<input type="text" v-focus />
</div>
<script src="vue.js"></script>
<script>
Vue.directive("focus", {
inserted: function (el) {
//el:表示指令所绑定的元素
el.focus();
},
});
const vm = new Vue({
el: "#app",
data: {},
});
</script>
</body>
</html>
在上面的代码中,我们通过directive方法创建了一个focus指令。
在使用该指令的时候,一定要加上v-的形式。
inserted表示的是指令的钩子函数,含义是:被绑定元素插入父节点时调用。
18.2 自定义指令-带参数
带参数的自定义指令创建的语法(改变元素背景色)
Vue.directive('color',{
inserted:function(el,binding){
//binding表示传递过来的参数
el.style.backgroundColor=binding.value.color;
}
})
指令的用法
<input type="text" v-color='{color:"orange"}' />
下面,看一下完整的代码案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>自定义指令带参数</title>
</head>
<body>
<div id="app">
<input type="text" v-color="msg" />
</div>
<script src="vue.js"></script>
<script>
//自定义指令-带参数
Vue.directive("color", {
bind: function (el, binding) {
el.style.backgroundColor = binding.value.color;
},
});
const vm = new Vue({
el: "#app",
data: {
msg: {
color: "blue",
},
},
});
</script>
</body>
</html>
通过上面的代码,可以看到定义了一个color的指令,在使用的时候传递了msg对象。
所以这个对象会给binding这个参数,我们通过这个参数的value 属性获取msg对象中的color属性的值,然后用来设置文本框的背景色。
这里使用了bind这个钩子函数:只调用一次,第一次绑定指令到元素时调用,我们可以在此绑定只执行一次的初始化动作。
18.3 自定义局部指令
局部指令的基本语法:
directives:{
focus:{
//指令的定义
inserted:function(el){
el.focus()
}
}
}
在Vue实例中添加directives
具体实现的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>局部指令</title>
</head>
<body>
<div id="app">
<input type="text" v-color="msg" />
</div>
<script src="vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
msg: {
color: "red",
},
},
directives: {
color: {
bind: function (el, binding) {
el.style.backgroundColor = binding.value.color;
},
},
},
});
</script>
</body>
</html>
局部指令只在所定义的组件中使用。
19、渲染函数
Vue推荐在绝大数情况下使用模板来创建你的HTML。然后在一些场景中,你真的需要JavaScript的完全编程的能力,也就是使用javaScript来创建HTML,这时你可以用渲染函数,它比模板更接近编译器。
这里我们先来做一个基本的了解,为后期的深入学习打好一个基础。
下面先看一下render函数的基本结构。
render:function(createElement){
//createElement函数返回的结果为VNode. VNode就是虚拟dom,用js对象来模拟真实的DOM.
retrun createElement(
tag, //标签名称
data,// 传递数据
children //子节点数组
)
}
下面我们在用户管理这个案例中,使用render函数来创建一个组件。
具体的代码如下:
// heading组件
//<heading :level="1">{{title}}</heading> //这时要创建的组件
// <h2 title=""></h2> //这时上面的组件最终渲染的结果
Vue.component("heading", {
props: {
level: {
type: String,
required: true,
},
},
render(h) { //h 就是createElement函数
return h(
"h" + this.level, //参数1,表示要创建的元素
this.$slots.default //参数3,子节点VNode数组。(这里没有使用参数2,{{tile}}就是一个子元素)
);
},
});
接下来就可以使用heading组件了。
<!-- 使用render函数创建的头部组件 -->
<heading level="1">
{{title}}
</heading>
当然,这里需要在data中定义title属性。
data: {
num: 100,
totalCount: 0,
users: [],
height: 0,
userInfo: "abc",
title: "用户管理",
// isShow: false,
// showWarn: false, // 控制警告窗口的显示与隐藏
},
完整代码如下(24、render函数.html):
<!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>
<!-- 使用render函数创建的头部组件 -->
<heading level="1">
{{title}}
</heading>
<!-- 清空提示栏 -->
<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>
//创建事件总线
Vue.prototype.$bus = new Vue();
// heading组件
//<heading :level="1">{{title}}</heading> //这时要创建的组件
// <h2 title=""></h2> //这时上面的组件最终渲染的结果
Vue.component("heading", {
props: {
level: {
type: String,
required: true,
},
},
render(h) {
return h(
"h" + this.level, //参数1,表示要创建的元素
this.$slots.default //参数3,子节点VNode数组。(这里没有使用参数2,{{tile}}就是一个子元素)
);
},
});
//创建弹出的组件
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",
title: "用户管理",
// 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>
虚拟DOM
Vue通过建立一个虚拟DOM来追踪自己要如何改变真实DOM.
createElement参数
前面说过,createElement函数有三个参数。
createElement(
//{string |Object|Function}
//第一个参数,可以是字符串,也可以是对象或者是函数
‘div’
,
// 第二个参数是对象,表示的是一个与模板中属性对应的数据对象。该参数可选
{
},
//第三个参数是一个数组,表示的是子节点数组
[
]
)
下面,给heading组件添加第一个属性。
<!-- 使用render函数创建的头部组件 -->
<heading level="1" :title="title">
{{title}}
</heading>
在上面的代码中,我们给heading组件动态添加了一个title属性。而我们知道heading组件,最终渲染成的是h1的元素,最终效果为:<h1 title='aaa'>的形式。
// heading组件
//<heading :level="1">{{title}}</heading> //这时要创建的组件
// <h2 title=""></h2> //这时上面的组件最终渲染的结果
Vue.component("heading", {
props: {
level: {
type: String,
required: true,
},
title: {
type: String,
default: "",
},
},
render(h) {
return h(
"h" + this.level, //参数1,表示要创建的元素
{ attrs: { title: this.title } }, //参数2
this.$slots.default //参数3,子节点VNode数组。(这里没有使用参数2,{{tile}}就是一个子元素)
);
},
});
在上面的代码中,我们在render函数中给h函数添加了第二个参数,给最终生成的元素添加了attrs属性。
20、函数式组件
组件没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法时,可以将组件标记为functional.这意味它无状态(没有响应式数据),也没有实例(没有this上下文)
因为只是函数,所以渲染的开销相对来说,较小。
函数化的组件中的 Render 函数,提供了第二个参数 context 作为上下文,data、props、slots、children 以及 parent 都可以通过 context 来访问。
这块内容简单了解一下就可以。
21、混入
混入(mixin)提供了一种非常灵活的方式,来分发Vue组件中的可复用功能,一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项被“混合”进入该组件本身的选项。
// 定义一个混入对象
var myMixin={
created:function(){
this.hello()
},
methods:{
hello:function(){
console.log('hello world')
}
}
}
Vue.component('comp',{
mixins:[myMixin]
})
“混入”可以提高组件的复用功能,例如:上面所写的hello这个方法,不仅在一个组件中使用,还会
在其它组件中使用.那么,我们的处理方式就是,可以将hello 这个方法单独定义在一个地方,如果某个组件想要使用,可以直接将该方法注入到组件中。
混入的对象中属性相同会冲突,以组件的值优先。组件中没有的属性,混入对象中的生效。
同名钩子函数将会合并成一个数组,都会调用,混入函数先调用
值为对象的选项,如methods,components,directives等,将会合并为一个新对象,如果键名冲突,组件的值优先
22、插件
前面我们讲解的混入,组件封装等都可以提高组件的复用功能。
但是这种方式不适合分发,也就是不适合将这些内容上传到github上,npm上。而这种情况最适合通过插件来实现。
插件通常用来为Vue添加全局功能。插件的功能范围一般有下面几种:
- 添加全局方法或者属性。例如:'element'
- 添加全局资源
- 通过全局混入来添加一些组件选项。例如
vue-router - 添加
vue实例方法,通过把它们添加到Vue.prototype上实现 - 一个库,提供自己的
API,同时提供上面提到的一个或多个功能,例如vue-router
插件声明
Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或 property
Vue.myGlobalMethod = function () {
// 逻辑...
}
// 2. 添加全局资源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 逻辑...
}
...
})
// 3. 注入组件选项
Vue.mixin({
created: function () {
// 逻辑...
}
...
})
// 4. 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 逻辑...
}
}