Vue核心3

79 阅读7分钟

这里我们接着上一篇继续 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
  • 使用及优化:iskeep-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.$onvm.$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-listdata中定义该属性,父组件就不用在定义该属性了。

下面,我们在<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-modeluserInfo属性与文本框进行了绑定。同时,单击按钮的时候,执行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-bindv-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.$onvm.$emit应用

现在,关于用户管理这个案例的一些组件拆分,以及插槽的应用在这我们已经构建好了。

下面就看一下vm.$onvm.$emit的应用。

根据前面的学习,我们知道vm.$onvm.$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.$oncevm.$off

vm.$once 监听一个自定义事件,但是只触发一次。一旦触发之后,监听器就会被移除。

vm.$on('test', function (msg) { console.log(msg) })

vm.$off

移除自定义事件监听器。

  • 如果没有提供参数,则移除所有的事件监听器;
  • 如果只提供了事件,则移除该事件所有的监听器;
  • 如果同时提供了事件与回调,则只移除这个回调的监听器
vm.$off() // 移除所有的事件监听器 
vm.$off('test') // 移除该事件所有的监听器
vm.$off('test', callback) // 只移除这个回调的监听器

16.5 refvm.$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中定义的isShowshowWarn就可以取消了。

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>

下面在对refvm.$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) {
    // 逻辑...
  }
}

www.cnblogs.com/luozhihao/p…