Vue2笔记整理

Vue2笔记整理

Vue2简介

什么是Vue

一套用于构建用户界面的渐进式JavaScript框架

  • 构建用户界面
    • 用Vue向HTML页面中填充数据非常方便
  • 框架
    • 框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能
    • vue的指令,组件(是对UI结构的复用),路由,vuex,vue组件库

在这里插入图片描述

Vue的特点

  1. 采用组件化模式,提高代码复用率,且让代码更好维护
  2. 声明式编码,让编码人员无需操作DOM,提高代码开发效率
  3. 使用虚拟DOM+Diff算法,尽量复用DOM节点

Vue2官网

Vue的特性

  • 数据驱动视图

    • 数据的变化会驱动视图的自动更新
    • 优点:程序员只管把数据维护好,那么页面结构会被Vue自动渲染出来
  • 双向数据绑定

    • 在网页中,form表单负责采集数据,Ajax负责提交数据
    • JS数据的变化会被自动渲染到页面上
    • 页面上表单收集的数据发生变化的时候,会被Vue自动获取到,并更新到JS数据中

初识Vue

全局配置项

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

官网

Hello Vue小案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <!-- 引入vue.js -->
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <!-- 容器 -->
    <div id="app">
      <h1>Hello, {{ name }}</h1>
    </div>
    <script>
      // 创建Vue的实例
      const vm = new Vue({
        // 用于指定当前Vue的实例为哪个容器服务,值为css选择器字符串
        el: "#app",
        // 数据
        data: {
          name: "前端Vue",
        },
      });
    </script>
  </body>
</html>
  • Vue实例和容器是一一对应的
  • 真实开发中只有一个Vue实例,并且会配合着组件一起使用
  • 一旦data中的数据发生改变,那么容器中用到该数据的地方也会自动更新
  • 上面代码中使用了插值语法,我们马上就来学习插值语法

el和data配置项的两种写法

第一种写法我们已经在小案例中使用过了,这里我们来看第二种写法

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <h1>你好,{{name}}</h1>
    </div>
    <script>
      const vm = new Vue({
        // el: "#app",
        // data: {
        //   name: "前端Vue",
        // },
        // 这是项目中必须要求的写法
        data() {
          return {
            name: "前端Vue",
          };
        },
      }).$mount("#app");
    </script>
  </body>
</html>

Vue模板语法

Vue模板语法有两大类,分别为插值语法和指令语法

插值语法

内容渲染指令(v-text和v-html)

内容渲染指令渲染DOM元素的文本内容,常用的有如下两个指令,两个指令都可以使用data中的属性

v-text指令和v-html指令

v-text指令的缺点:会覆盖元素内部原有的内容

v-html指令的作用:可以把带有标签的字符串渲染成真正的HTML内容,同样会覆盖元素内部原有的内容

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <!-- 将username的值渲染到标签体中 -->
      <p v-text="username"></p>
      <!-- 会覆盖性别:,也就是会覆盖标签体中的内容 -->
      <p v-text="gender">性别:</p>
      <!-- 将带有标签的字符串渲染成HTML内容 -->
      <p v-html="html"></p>
      <!-- 同样会覆盖标签体中的内容 -->
      <p v-html="html">数据</p>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          username: "张三",
          gender: "女",
          html: "<div>张三</div>",
        },
      });
    </script>
  </body>
</html>

插值表达式

  • 功能:用于解析标签体的内容
  • 写法:{{ JS表达式 }},且可以直接读取到data中的属性
  • 备注:插值表达式只能用在元素的内容节点之中,不能用在元素的属性节点当中。{{ JS表达式 }}使用的最多,一般情况下会使用插值表达式,而不会使用内容渲染指令
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <h1>插值语法</h1>
      <h3>你好,{{ name }}</h3>
      <hr />
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          name: "jack",
        },
      });
    </script>
  </body>
</html>

指令语法

功能:用于解析标签

备注:Vue有很多的指令,且都是以v-开头的形式

属性绑定指令(v-bind和v-model)

v-bind指令:可以简写成:,为元素的属性动态绑定值,是单向数据绑定,即数据变化视图也会变化,但视图内容的改变不会使得数据源改变

举例:<a v-bind:href="JS表达式">百度一下</a>,且可以直接读取到data中的所有属性

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <h1>指令语法</h1>
      <!-- v-bind指令:动态绑定属性值 -->
      <a v-bind:href="url">百度一下</a>
      <!-- v-bind的简写形式 -->
      <a :href="url">百度一下</a>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          url: "https://www.baidu.com/",
        },
      });
    </script>
  </body>
</html>

v-model指令:为元素的属性动态绑定值,是双向数据绑定,只能用在输入类元素上,也可以简写

举例:<input v-model="JS表达式" type="text" /><br />,同样可以读取到data中的所有属性

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <!-- 单向数据绑定:<input v-bind:value="name" type="text" /><br /> -->
      单向数据绑定:<input :value="name" type="text" /><br />
      <!-- 双向数据绑定:<input v-model:value="name" type="text" /><br /> -->
      <!-- 简写方式 -->
      双向数据绑定:<input v-model="name" type="text" /><br />
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          name: "张三",
        },
      });
    </script>
  </body>
</html>

为了方便对用户输入的内容进行处理,Vue为v-model提供了3个修饰符分别是

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KYfnsmnT-1684385078455)(image/image-20230427212049349.png)]

实例代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <!-- 单向数据绑定:<input v-bind:value="name" type="text" /><br /> -->
      单向数据绑定:<input :value="name" type="text" /><br />
      <!-- 双向数据绑定:<input v-model:value="name" type="text" /><br /> -->
      双向数据绑定:<input v-model="name" type="text" /><br />
      <!-- 自动将用户输入的内容转换成数字类型 -->
      双向数据绑定:<input v-model.number="age" type="text" /><br />
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          name: "张三",
          age: 18,
        },
      });
    </script>
  </body>
</html>

事件绑定指令(v-on)

语法

vue提供了v-on事件绑定指令,用来辅助程序员为DOM元素绑定事件监听,语法格式如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <p>count的值是:{{ count }}</p>
      <button v-on:click="add">+1</button>
      <button v-on:click="sub">-1</button>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          count: 0,
        },
        // methods的作用就是定义事件的处理函数
        methods: {
          add() {
            // console.log("add");
            // count += 1;
            console.log(vm === this); // true
            this.count += 1;
          },
          sub() {
            // console.log("sub");
            this.count -= 1;
          },
        },
      });
    </script>
  </body>
</html>
传递参数

同样可以传递参数,实例代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <p>count的值是:{{ count }}</p>
      <button v-on:click="add(2)">+1</button>
      <button v-on:click="sub">-1</button>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          count: 0,
        },
        methods: {
          add(n) {
            this.count += n;
          },
          sub() {
            this.count -= 1;
          },
        },
      });
    </script>
  </body>
</html>
传递参数后接收事件对象($event)

实例代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <p>count的值是:{{ count }}</p>
      <!-- Vue提供了内置的变量名字叫做$event,接收事件对象 -->
      <button v-on:click="add(2, $event)">+1</button>
      <button v-on:click="sub">-1</button>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          count: 0,
        },
        methods: {
          add(n, e) {
            console.log(e);
            this.count += n;
          },
          sub() {
            this.count -= 1;
          },
        },
      });
    </script>
  </body>
</html>
v-on指令的简写

v-on指令可以简写成@,看如下代码

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <p>count的值是:{{ count }}</p>
      <!-- 这是简写形式 -->
      <button @click="add">+1</button>
      <button @click="sub">-1</button>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          count: 0,
        },
        // methods的作用就是定义事件的处理函数
        methods: {
          add() {
            // console.log("add");
            // count += 1;
            console.log(vm === this); // true
            this.count += 1;
          },
          sub() {
            // console.log("sub");
            this.count -= 1;
          },
        },
      });
    </script>
  </body>
</html>
事件修饰符

在事件处理函数中调用e.preventDefault()e.stopPropagation()是非常常见的需求,因此Vue提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制,常用的5个事件修饰符如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7OyRUXvC-1684385078455)(image/image-20230427222112537.png)]

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <!-- 修饰符可以连着写 -->
      <a @click.prevent.stop="show" href="https:www.baidu.com">百度一下</a>
      <a @click.prevent="show" href="https:www.baidu.com">百度一下</a>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {},
        methods: {
          show(e) {
            // e.preventDefault();
            console.log("点击了a链接");
          },
        },
      });
    </script>
  </body>
</html>
按键修饰符

在监听键盘事件时,我们经常需要判断详细的按键,此时可以为键盘相关的事件添加键盘修饰符,例如如下代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <!-- 按键修饰符,指定按键触发事件 -->
      <!-- 同样可以连着使用 -->
      <input type="text" @keyup.esc="clearInput" />
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {},
        methods: {
          clearInput(e) {
            console.log("触发了clearInput");
            // 清空文本框
            e.target.value = "";
          },
        },
      });
    </script>
  </body>
</html>
Vue中常用的按键别名
按键名别名
回车键enter
删除键(捕获删除和退格键)delete
退出键esc
空格键space
换行键(配合keydown一起使用)tab
上键up
下键down
左键left
右键right

Vue未提供别名的按键,可以使用按键原始的key值去绑定,如果key值是两个单词则需要小写,且采用短横线命名,实例如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <!-- Vue未提供别名的按键,可以使用按键原始的key值去绑定,
      如果key值是两个单词则需要小写,且采用短横线命名 -->
      <input type="text" @keyup.caps-lock="clearInput" />
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {},
        methods: {
          clearInput(e) {
            console.log("触发了clearInput");
            // 清空文本框
            e.target.value = "";
          },
        },
      });
    </script>
  </body>
</html>
快速查看key和keyCode
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <input type="text" @keyup="submit" />
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {},
        methods: {
          submit(e) {
            console.log(e.key, e.keyCode);
          },
        },
      });
    </script>
  </body>
</html>
系统修饰键的绑定

系统修饰键(用法特殊),包括ctrl、alt、shift和meta

  1. 配合keyup使用,按下修饰键的同时,在按下其他键,随后释放其他键,事件才被触发
  2. 配合keydown使用,正常触发事件

也可以使用keyCode去指定具体的按键(不推荐)

配置按键别名

Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名,键码即为keyCode

条件渲染指令(v-if和v-show)

条件渲染指令用来辅助开发者按需控制DOM的显示和隐藏,条件渲染指令有如下两个分别是v-ifv-show,实例代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <button @click="change">toggle flag</button>
      <!-- 动态的删除或添加DOM -->
      <p v-if="flag">这是 v-if 控制的元素</p>
      <!-- 添加display: none样式 -->
      <p v-show="flag">这是 v-show 控制的元素</p>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          // 如果flag为true,则显示被控制的元素,反之,则隐藏
          flag: true,
        },
        methods: {
          change() {
            // 切换flag变量
            this.flag = !this.flag;
            // console.log(this.flag);
          },
        },
      });
    </script>
  </body>
</html>
v-if的配套指令
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <button @click="change">toggle flag</button>
      <!-- 动态的删除或添加DOM -->
      <p v-if="flag">这是 v-if 控制的元素</p>
      <!-- 添加display: none样式 -->
      <p v-show="flag">这是 v-show 控制的元素</p>
      <hr />
      <div v-if="type === 'A'">优秀</div>
      <div v-else-if="type === 'B'">良好</div>
      <div v-else-if="type === 'C'">一般</div>
      <div v-else></div>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          // 如果flag为true,则显示被控制的元素,反之,则隐藏
          flag: true,
          type: "A",
        },
        methods: {
          change() {
            // 切换flag变量
            this.flag = !this.flag;
            // console.log(this.flag);
          },
        },
      });
    </script>
  </body>
</html>

列表渲染指令(v-for)

Vue提供了列表渲染指令用来辅助开发者基于一个数组来循环渲染一个列表结构,v-for指令需要使用item in items形式的特殊语法,其中,items是待循环的数组,item是被循环的每一项

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <table border="1" cellpadding="1" cellspacing="0" width="500px">
        <tr>
          <th>索引</th>
          <th>id</th>
          <th>姓名</th>
        </tr>
        <tr v-for="(item, index) in list">
          <td>{{ index }}</td>
          <td>{{ item.id}}</td>
          <td>{{ item.name }}</td>
        </tr>
      </table>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          list: [
            { id: 1, name: "唐三" },
            { id: 2, name: "小舞" },
            { id: 3, name: "奥斯卡" },
            { id: 4, name: "宁荣荣" },
          ],
        },
      });
    </script>
  </body>
</html>
v-for中绑定key

绑定key这项操作是必须的,且强烈推荐这么做,不要使用index作为key值,key的值只能是数字或字符串,key的值必须具有唯一性,建议将数据的id属性作为key值

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <table border="1" cellpadding="1" cellspacing="0" width="500px">
        <tr>
          <th>索引</th>
          <th>id</th>
          <th>姓名</th>
        </tr>
        <!-- 官方推荐绑定一个key -->
        <tr v-for="(item, index) in list" :key="item.id">
          <td>{{ index }}</td>
          <td>{{ item.id}}</td>
          <td>{{ item.name }}</td>
        </tr>
      </table>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          list: [
            { id: 1, name: "唐三" },
            { id: 2, name: "小舞" },
            { id: 3, name: "奥斯卡" },
            { id: 4, name: "宁荣荣" },
          ],
        },
      });
    </script>
  </body>
</html>

v-once指令

v-once指令所在节点在初次动态渲染后就视为静态内容了,以后数据的改变不会引起视图发生变化,可以优化性能

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <h2 v-once>初始化的n的值是: {{n}}</h2>
      <h2>当前的n的值是: {{n}}</h2>
      <button @click="n++">+1</button>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          n: 1,
        },
      });
    </script>
  </body>
</html>

v-pre指令

作用:跳过其所在节点的编译过程,可以利用它跳过没有使用指令语法,没有使用插值语法的节点,会加快编译

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <!-- 跳过这行代码的编译 -->
      <h2 v-pre>Vue其实很简单</h2>
      <h2>当前的n的值是: {{n}}</h2>
      <button @click="n++">+1</button>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          n: 1,
        },
      });
    </script>
  </body>
</html>

MVVM

MVVM是Vue实现数据驱动视图和双向数据绑定的核心原理,MVVM指的是Model(数据源)、View(视图)和ViewModel(Vue实例),他把每个HTML页面都拆分成了这三个部分,如图所示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uW57sOJw-1684385078456)(image/image-20230426141253956.png)]

观察发现:data中所有的属性都出现在了vm身上,vm的所有属性和Vue原型上所有的属性在模板中都可以直接使用

回顾defineProperty

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      let person = {
        name: "张三",
        sex: "男",
      };
      Object.defineProperty(person, "age", {
        value: 18, // 将属性的值设置为18
        enumerable: true, // 控制属性是否可以枚举,默认是false
        writable: true, // 控制属性是否可以被修改,默认是false
        configurable: true, // 控制属性是否可以被删除或修改,默认false
      });
      console.log(person);
    </script>
  </body>
</html>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      let number = 18;
      let person = {
        name: "张三",
        sex: "男",
      };
      Object.defineProperty(person, "age", {
        // 当读取person的age属性时,get(getter)函数就会被调用,且返回值就是age的值
        get() {
          console.log("有人读取了age属性");
          return number;
        },
        // 当修改person的age属性时,set(setter)函数就会被调用,且会收到修改的具体的值
        set(value) {
          console.log("有人修改了age属性,值是" + value);
          number = value;
        },
      });
      console.log(person);
    </script>
  </body>
</html>

简单实现数据代理

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- 数据代理:通过一个对象代理对另一个对象的属性操作 -->
    <script>
      let obj = { x: 100 };
      let obj2 = { y: 200 };
      Object.defineProperty(obj2, "x", {
        get() {
          return obj.x;
        },
        set(value) {
          obj.x = value;
        },
      });
    </script>
  </body>
</html>

Vue中的数据代理

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <h2>姓名:{{name}}</h2>
      <h2>性别:{{sex}}</h2>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          name: "张三",
          sex: "男",
        },
      });
    </script>
  </body>
</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eUB42opO-1684385078456)(image/image-20230426212822930.png)]

Vue中的数据代理就是通过Object.defineProperty()把data对象中所有的属性添加到vm上,且数据从data到vm中的过程,又为每一个属性添加上getter和setter,用户修改或者读取时,也是通过getter和setter方法访问_data, _data返回或操作data中的数据实现的。

过滤器(Vue3以不在使用)

私有过滤器

过滤器(Filters)是Vue为开发者提供的功能,常用于文本的格式化,过滤器可以用在两个地方,插值表达式和v-bind属性绑定。

Filters 应该被添加在JavaScript表达式的尾部,由管道符进行调用,实例代码如下

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <p>message的值是: {{message | capitalize}}</p>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          message: "hello vue.js",
        },
        // 过滤器函数必须定义到filters节点下
        filters: {
          // 第一个参数永远都是管道符前面待处理的值
          capitalize(val) {
            // 将首字母大写
            const str = val.charAt(0).toUpperCase();
            const other = val.slice(1);
            // 强调一下:过滤器一定要有返回值
            return str + other;
          },
        },
      });
    </script>
  </body>
</html>

全局过滤器

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <p>message的值是: {{message | capitalize}}</p>
    </div>
    <div id="app2">
      <p>message的值是: {{message | capitalize}}</p>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          message: "hello vue.js",
        },
        // 过滤器函数必须定义到filters节点下
        filters: {
          capitalize(val) {
            // 将首字母大写
            const str = val.charAt(0).toUpperCase();
            const other = val.slice(1);
            // 强调一下:过滤器一定要有返回值
            return str + other;
          },
        },
      });
      const mv2 = new Vue({
        el: "#app2",
        data: {
          message: "xiaolv",
        },
        // 还需要我们重新定义一个过滤器,这也证明了上面的过滤器是私有的
      });
    </script>
  </body>
</html>

全局过滤器定义方法如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <p>message的值是: {{message | capitalize}}</p>
    </div>
    <div id="app2">
      <p>message的值是: {{message | capitalize}}</p>
    </div>
    <script>
      // 定义全局过滤器
      Vue.filter("capitalize", (val) => {
        return val.charAt(0).toUpperCase() + val.slice(1);
      });
      const vm = new Vue({
        el: "#app",
        data: {
          message: "hello vue.js",
        },
      });
      const vm2 = new Vue({
        el: "#app2",
        data: {
          message: "h你我皆是黑马",
        },
      });
    </script>
  </body>
</html>
  • 如果全局过滤器和私有过滤器名字冲突了,则优先调用私有过滤器

使用全局过滤器格式化时间

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <!-- dayjs是用来格式化日期的JavaScript库 -->
  <script src="https://unpkg.com/dayjs@1.8.21/dayjs.min.js"></script>
  <body>
    <div id="app">
      {{date | dateFormat}}
      <p>message的值是: {{message | capitalize}}</p>
    </div>
    <div id="app2">
      <p>message的值是: {{message | capitalize}}</p>
    </div>
    <script>
      console.log();
      // 全局过滤器
      Vue.filter("capitalize", (val) => {
        return val.charAt(0).toUpperCase() + val.slice(1);
      });
      Vue.filter("dateFormat", (time) => {
        return time.format("YYYY-MM-DD HH:mm:ss");
      });
      const vm = new Vue({
        el: "#app",
        data: {
          message: "hello vue.js",
          // 直接调用dayjs()得到的是当前时间
          date: dayjs(),
        },
      });
      const vm2 = new Vue({
        el: "#app2",
        data: {
          message: "h你我皆是黑马",
        },
      });
    </script>
  </body>
</html>

连续调用多个过滤器

过滤器可以串联的进行调用,例如

<!-- 把message的值交给filterA进行处理 -->
<!-- 把filterA处理的结果在交给filterB进行处理 -->
<!-- 最终把filterB处理的结果作为最终的值渲染到页面上 -->
{{ message | filterA | filterB }}

过滤器传参

过滤器的本质是JavaScript函数,因此可以接收参数,格式如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w2e4T9YI-1684385078457)(image/image-20230501161621899.png)]

侦听器

什么是watch

watch允许开发者监视数据的变化,从而对数据的变化做特定的操作

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <input type="text" v-model="username" />
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          username: "张三",
        },
        // 监听器
        watch: {
          // 当数据变化触发函数
          // 监视谁就把谁当做方法名
          // 新值在形参的前面
          username(newVal, oldVal) {
            console.log(newVal, oldVal);
          },
        },
      });
    </script>
  </body>
</html>

应用场景(用户名是否被占用)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.6.0.min.js"></script>
  <body>
    <div id="app">
      <input type="text" v-model="username" />
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          username: "张三",
        },
        watch: {
          username(newVal, oldVal) {
            if (newVal === "") return;
            $.get(
              "https://www.escook.cn/api/finduser/" + newVal,
              function (result) {
                console.log(result);
              }
            );
          },
        },
      });
    </script>
  </body>
</html>

侦听器的immediate选项

有时候,我们需要进入页面就执行侦听器的内容,函数侦听器无法满足我们的需求,因此要对侦听器的格式进行改进,实例代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.6.0.min.js"></script>
  <body>
    <div id="app">
      <input type="text" v-model="username" />
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          username: "张三",
        },
        watch: {
          username: {
            // 侦听器的处理函数
            handler: function (newVal, oldVal) {
              if (newVal === "") return;
              $.get(
                "https://www.escook.cn/api/finduser/" + newVal,
                function (result) {
                  console.log(result);
                }
              );
            },
            // 进入页面是否触发侦听函数,默认false
            immediate: true,
          },
        },
      });
    </script>
  </body>
</html>

侦听器的deep选项

深度侦听,实例代码如下

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.6.0.min.js"></script>
  <body>
    <div id="app">
      <input type="text" v-model="info.username" />
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          info: {
            username: "张三",
          },
        },
        watch: {
          // 如果侦听的是对象,则无法侦听属性的变化
          info: {
            // 侦听器的处理函数
            handler: function (newVal, oldVal) {
              console.log(newVal, oldVal);
              if (newVal === "") return;
              $.get(
                "https://www.escook.cn/api/finduser/" + newVal,
                function (result) {
                  console.log(result);
                }
              );
            },
            // 进入页面是否触发侦听函数,默认false
            immediate: true,
            // 深度侦听
            deep: true,
          },
        },
      });
    </script>
  </body>
</html>

直接侦听对象的属性

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <script src="https://ajax.aspnetcdn.com/ajax/jquery/jquery-3.6.0.min.js"></script>
  <body>
    <div id="app">
      <input type="text" v-model="info.username" />
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          info: {
            username: "张三",
          },
        },
        watch: {
          // 直接侦听对象的属性
          "info.username": {
            // 侦听器的处理函数
            handler: function (newVal, oldVal) {
              console.log(newVal, oldVal);
              if (newVal === "") return;
              $.get(
                "https://www.escook.cn/api/finduser/" + newVal,
                function (result) {
                  console.log(result);
                }
              );
            },
            // 进入页面是否触发侦听函数,默认false
            immediate: true,
          },
        },
      });
    </script>
  </body>
</html>

计算属性

如果不使用计算属性,那么则很难实现一些需求,比如下面代码所实现的效果

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .box {
        width: 200px;
        height: 200px;
        border: 1px solid #ccc;
      }
    </style>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <div>
        <span>R:</span>
        <input type="text" v-model.number="r" />
      </div>
      <div>
        <span>G:</span>
        <input type="text" v-model.number="g" />
      </div>
      <div>
        <span>B:</span>
        <input type="text" v-model.number="b" />
      </div>
      <hr />

      <div class="box" :style="{backgroundColor: `rgb(${r}, ${g}, ${b})`}">
        {{`rgb(${r}, ${g}, ${b})`}}
      </div>
      <button @click="show">按钮</button>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          r: 0,
          g: 0,
          b: 0,
        },
        methods: {
          show() {
            console.log(`rgb(${this.r}, ${this.g}, ${this.b})`);
          },
        },
      });
    </script>
  </body>
</html>
  • 上面的代码中模板字符串被重复使用,这是我们不会乐意的结果

什么是计算属性

计算属性指的是通过一系列运算之后,最终得到一个属性值

这个动态计算出来的属性值可以被模板结构或methods方法调用,以上面的需求为例,示例代码如下

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .box {
        width: 200px;
        height: 200px;
        border: 1px solid #ccc;
      }
    </style>
  </head>
  <script
    src="https://unpkg.com/vue@2.7.14/dist/vue.js"
    type="text/javascript"
  ></script>
  <body>
    <div id="app">
      <div>
        <span>R:</span>
        <input type="text" v-model.number="r" />
      </div>
      <div>
        <span>G:</span>
        <input type="text" v-model.number="g" />
      </div>
      <div>
        <span>B:</span>
        <input type="text" v-model.number="b" />
      </div>
      <hr />

      <div class="box" :style="{backgroundColor: rgb}">{{rgb}}</div>
      <button @click="show">按钮</button>
    </div>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          r: 0,
          g: 0,
          b: 0,
        },
        // 所有的计算属性都有定义到computed节点下
        computed: {
          rgb() {
            return `rgb(${this.r}, ${this.g}, ${this.b})`;
          },
        },
        methods: {
          show() {
            console.log(this.rgb);
          },
        },
      });
    </script>
  </body>
</html>
  • 计算属性被定义时是方法,在使用时是属性

  • data中只要有计算属性所依赖数据的变化,计算属性就会随之改变,这也是极好的

vue-cli的基本使用

vue-cli的基础知识

Vue组件

什么是组件化开发

组件化开发指的是根据封装的思想,把页面上可重用的UI结构封装为组件,从而方便项目的开发和维护

  • Vue是一个支持组件化开发的前端框架
  • Vue中规定:组件的后缀名是.vue,App.vue就是一个组件

Vue组件的三个组成部分

template:组件的模板结构

script:组件的JavaScript行为

style:组件的样式

<!-- 组件的三大组成部分 -->
<template>
  <div class="box">
    <h3>这里是用户自定义的Test.vue组件</h3>
    <p>{{ username }}</p>
  </div>
</template>

<script>
export default {
  // 必须使用函数形式
  data() {
    return {
      username: "张三",
    };
  },
};
</script>

<style>
.box {
  background-color: pink;
}
</style>

组件中定义方法

<!-- 组件的三大组成部分 -->
<template>
  <div class="box">
    <h3>这里是用户自定义的Test.vue组件</h3>
    <p>{{ username }}</p>
    <button @click="changeName">修改用户名</button>
  </div>
</template>

<script>
export default {
  // 必须使用函数形式
  data() {
    return {
      username: "张三",
    };
  },
  methods: {
    changeName() {
      console.log(this);
      this.username = "潜龙";
    },
  },
};
</script>

<style>
.box {
  background-color: pink;
}
</style>

模板语法中不能出现两个根节点,只能有一个根节点

<!-- 组件的三大组成部分 -->
<template>
  <div class="box">
    <h3>这里是用户自定义的Test.vue组件</h3>
    <p>{{ username }}</p>
    <button @click="changeName">修改用户名</button>
  </div>
  <!-- 不能同时出现两个根节点 -->
  <!-- <div></div> -->
</template>

<script>
export default {
  // 必须使用函数形式
  data() {
    return {
      username: "张三",
    };
  },
  methods: {
    changeName() {
      console.log(this);
      this.username = "潜龙";
    },
  },
};
</script>

<style>
.box {
  background-color: pink;
}
</style>

启用less语法

<!-- 组件的三大组成部分 -->
<template>
  <div class="box">
    <h3>这里是用户自定义的Test.vue组件</h3>
    <p>{{ username }}</p>
    <button @click="changeName">修改用户名</button>
  </div>
  <!-- 不能同时出现两个根节点 -->
  <!-- <div></div> -->
</template>

<script>
export default {
  // 必须使用函数形式
  data() {
    return {
      username: "张三",
    };
  },
  methods: {
    changeName() {
      console.log(this);
      this.username = "潜龙";
    },
  },
};
</script>

<style lang="less">
/** less语法启用 */
.box {
  background-color: pink;
  h3 {
    color: red;
  }
}
</style>

Vue组件使用的三个步骤

  1. 使用import语法导入需要的组件
  2. 使用components节点注册组件
  3. 以标签形式使用刚才注册的组件

App.vue组件代码如下:

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 3. 以标签形式使用Left组件 -->
      <Left></Left>
      <Right></Right>
    </div>
  </div>
</template>

<script>
// 1. 导入Left组件
import Left from "@/components/Left.vue";
import Right from "@/components/Right.vue";

export default {
  // 2. 注册组件
  components: {
    Left,
    Right,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

Left.vue组件代码如下:

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
  </div>
</template>

<script>
export default {}
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

Right.vue代码如下:

<template>
  <div class="right-container">
    <h3>Right 组件</h3>
  </div>
</template>

<script>
export default {}
</script>

<style lang="less">
.right-container {
  padding: 0 20px 20px;
  background-color: lightskyblue;
  min-height: 250px;
  flex: 1;
}
</style>

main.js文件代码如下

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

路径提示插件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rpP8wqHf-1684385078457)(image/image-20230505121337520.png)]

插件配置代码如下

// 导入文件时是否携带文件的扩展名
"path-autocomplete.extensionOnImport": true,
// 配置 @ 的路径提示
"path-autocomplete.pathMappings": {
  "@": "${folder}/src"
}

注册全局组件

通过components节点注册的组件是私有的,当某一个组件需要频繁的用到时,是很繁琐的,因此我们迫切需要注册全局组件来方便开发。

在Vue项目的main.js文件中,通过Vue.component()方法,可以注册全局组件,代码如下:

main.js文件代码

// 导入Vue这个包,得到Vue这个函数
import Vue from "vue";
// 导入App.vue根组件,将来要把App.vue中的模板结构渲染到HTML页面中
import App from "./App.vue";
// 导入Count组件
import Count from "@/components/Count.vue";

Vue.config.productionTip = false;

// 参数一:字符串格式,表示组件的注册名称
// 参数二:需要被全局注册的组件
Vue.component("MyCount", Count);

new Vue({
  render: (h) => h(App),
}).$mount("#app");

Count.vue组件的代码如下:

<template>
  <div>
    <h5>Count 组件</h5>
  </div>
</template>

<script>
export default {};
</script>

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

使用全局组件

Left.vue文件代码如下:

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <hr />
    <!-- 使用标签的形式使用全局组件 -->
    <MyCount></MyCount>
  </div>
</template>

<script>
export default {};
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

组件的props

props是组件的自定义属性,在封装通用组件的时候,合理的使用props可以极大的提高组件的复用性

Count.vue文件如下

<template>
  <div>
    <h5>Count 组件</h5>
    <p>count的值是: {{ count }}</p>
    <button @click="add">+1</button>
  </div>
</template>

<script>
export default {
  // props是自定义属性
  props: ["init"],
  data() {
    return {
      count: this.init,
    };
  },
  methods: {
    add() {
      this.count++;
    },
  },
};
</script>

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

Right.vue文件如下:

<template>
  <div class="right-container">
    <h3>Right 组件</h3>
    <hr />

    <MyCount :init="6"></MyCount>
  </div>
</template>

<script>
export default {};
</script>

<style lang="less">
.right-container {
  padding: 0 20px 20px;
  background-color: lightskyblue;
  min-height: 250px;
  flex: 1;
}
</style>

Left.vue文件如下:

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <hr />
    <!-- 使用标签的形式使用全局组件 -->
    <!-- 使用init属性 -->
    <MyCount :init="9"></MyCount>
  </div>
</template>

<script>
export default {};
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

props是只读的,无法修改

组件的props的默认值

在声明自定义属性时,可以通过default来定义属性的默认值,实例代码如下

Count.vue文件如下

<template>
  <div>
    <h5>Count 组件</h5>
    <p>count的值是: {{ count }}</p>
    <button @click="add">+1</button>
  </div>
</template>

<script>
export default {
  // props是自定义属性
  props: {
    // 指向自己的配置选项
    init: {
      // 默认值
      default: 0,
    },
  },
  data() {
    return {
      count: this.init,
    };
  },
  methods: {
    add() {
      this.count++;
    },
  },
};
</script>

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

props的type值类型

Count.vue文件如下:

<template>
  <div>
    <h5>Count 组件</h5>
    <p>count的值是: {{ count }}</p>
    <button @click="add">+1</button>
  </div>
</template>

<script>
export default {
  // props是自定义属性
  props: {
    // 指向自己的配置选项
    init: {
      // 默认值
      default: 0,
      // 值类型
      type: Number,
    },
  },
  data() {
    return {
      count: this.init,
    };
  },
  methods: {
    add() {
      this.count++;
    },
  },
};
</script>

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

props的required必填项

Count.vue文件如下

<template>
  <div>
    <h5>Count 组件</h5>
    <p>count的值是: {{ count }}</p>
    <button @click="add">+1</button>
  </div>
</template>

<script>
export default {
  // props是自定义属性
  props: {
    // 指向自己的配置选项
    init: {
      // 默认值
      default: 0,
      // 值类型
      type: Number,
      // 必填项属性校验
      required: true,
    },
  },
  data() {
    return {
      count: this.init,
    };
  },
  methods: {
    add() {
      this.count++;
    },
  },
};
</script>

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

组件的样式冲突

默认情况下,组件的样式会全局生效,因此很容易造成组件之间的样式冲突问题

导致组件样式冲突的根本原因是:

单页面应用程序中,所有组件的DOM结构,都是基于唯一的index.html页面进行呈现的

每个组件中的样式都会影响整个index.html页面中的DOM元素

解决样式冲突

Left.vue文件

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <hr />
    <!-- 使用标签的形式使用全局组件 -->
    <!-- 使用init属性 -->
    <MyCount :init="9"></MyCount>
  </div>
</template>

<script>
export default {};
</script>

<style lang="less" scoped>
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
h3 {
  color: red;
}
</style>

原理是添加了一个data-v-3c83f0b7的自定义属性(3c83f0b7是一个标识符)

使用deep修改子组件样式

Left.vue文件

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <hr />
    <!-- 使用标签的形式使用全局组件 -->
    <!-- 使用init属性 -->
    <MyCount :init="9"></MyCount>
  </div>
</template>

<script>
export default {};
</script>

<style lang="less" scoped>
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
h3 {
  color: red;
}
/** 当使用第三方组件库的时候,如果有修改组件默认样式的需求,需要用到/deep/ */
/deep/ h5 {
  color: pink;
}
</style>

组件的生命周期

生命周期(Life Cycle):是指一个组件从创建—》运行—》销毁的整个阶段,强调一个时间段。

生命周期函数是由Vue提供的内置函数,会伴随着组件的生命周期自动按次序执行

下面是来自官网的图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mmer9YR2-1684385078458)(image/lifecycle.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gaR5xDoF-1684385078459)(image/image-20230507142701512.png)]

创建时

Test.vue文件

<template>
  <div class="test-container">
    <h3>Test.vue {{ books.length }} 本图书</h3>
  </div>
</template>

<script>
export default {
  props: ["info"],
  data() {
    return {
      message: "hello vue",
      // 定义books数组,存储的是所有图书的列表数据
      books: [],
    };
  },
  methods: {
    show() {
      console.log("调用了Test组件的show方法");
    },
    // 利用Ajax请求图书列表
    initBookList() {
      const xhr = new XMLHttpRequest();
      xhr.addEventListener("load", () => {
        const result = JSON.parse(xhr.responseText);
        this.books = result.data;
      });
      xhr.open("GET", "http://www.liulongbin.top:3006/api/getBooks");
      xhr.send();
    },
  },
  // 创建阶段的第一个生命周期函数
  beforeCreate() {
    // 调用props,methods,data
    // console.log(this.info);
    // console.log(this.message);
    // this.show();
  },
  // 创建阶段的第二个生命周期函数
  // 这个阶段是一个很重要的阶段
  created() {
    console.log(this.info);
    console.log(this.message);
    this.show();
    // 生命周期created函数非常常用
    // 经常在它里面调用methods中的方法,请求服务器的数据
    // 并且,把请求到的数据,转存到data中,供template 模板渲染的时候使用
    this.initBookList();
  },
  beforeMount() {
    const dom = document.querySelector("h3");
    console.log("beforeMount");
    console.log(dom); // null
  },
  // 如果要操作当前组件的DOM,最早只能在mounted阶段执行
  mounted() {
    const dom = document.querySelector("h3");
    console.log(dom); // 可以访问DOM结构
  },
};
</script>

<style lang="less" scoped>
.test-container {
  background-color: pink;
  height: 200px;
}
</style>

App.vue文件

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <Test info="你好"></Test>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 3. 以标签形式使用Left组件 -->
      <Left></Left>
      <Right></Right>
    </div>
  </div>
</template>

<script>
// 1. 导入Left组件
import Left from "@/components/Left.vue";
import Right from "@/components/Right.vue";
import Test from "@/components/Test.vue";

export default {
  // 2. 注册组件
  components: {
    Left,
    Right,
    Test,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

更新时

<template>
  <div class="test-container">
    <h3>Test.vue</h3>
    <p>message的值是: {{ message }}</p>
    <button @click="changeMessage">修改message的值</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "hello vue",
    };
  },
  methods: {
    changeMessage() {
      this.message += "-";
    },
  },
  // 当数据变化时执行这两个生命周期函数
  beforeUpdate() {
    console.log("beforeUpdate");
    console.log(this.message); // 新数据
    console.log(document.querySelector("p").innerHTML); // 旧DOM
  },
  // 一般情况下这个生命周期函数用的多一些
  updated() {
    console.log("updated");
    console.log(this.message); // 新数据
    console.log(document.querySelector("p").innerHTML); // 新DOM
  },
};
</script>

<style lang="less" scoped>
.test-container {
  background-color: pink;
  height: 200px;
}
</style>

销毁时

App.vue文件

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <Test info="你好" v-if="flag"></Test>
    <button @click="changeFlag">切换状态</button>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 3. 以标签形式使用Left组件 -->
      <Left></Left>
      <Right></Right>
    </div>
  </div>
</template>

<script>
// 1. 导入Left组件
import Left from "@/components/Left.vue";
import Right from "@/components/Right.vue";
import Test from "@/components/Test.vue";

export default {
  data() {
    return {
      flag: true,
    };
  },
  methods: {
    changeFlag() {
      this.flag = !this.flag;
    },
  },
  // 2. 注册组件
  components: {
    Left,
    Right,
    Test,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

Test.vue文件

<template>
  <div class="test-container">
    <h3>Test.vue</h3>
    <p>message的值是: {{ message }}</p>
    <button @click="changeMessage">修改message的值</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "hello vue",
    };
  },
  methods: {
    changeMessage() {
      this.message += "-";
    },
  },
  beforeDestroy() {
    console.log("beforeDestroy");
    console.log(this.message); // 可以访问
  },
  destroyed() {
    console.log("destroyed");
  },
};
</script>

<style lang="less" scoped>
.test-container {
  background-color: pink;
  height: 200px;
}
</style>

组件的数据共享

组件的关系

在项目的开发过程中,组件之间最常见的关系分为如下两种

  1. 父子关系
  2. 兄弟关系

父组件向子组件共享数据

父组件向子组件传递数据需要使用自定义属性,实例代码如下

父组件

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left :msg="message" :userinfo="userInfo"></Left>
      <Right></Right>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Right from "@/components/Right.vue";

export default {
  components: {
    Left,
    Right,
  },
  data() {
    return {
      message: "hello vue.js",
      userInfo: {
        name: "新海诚",
        age: "50",
      },
    };
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

子组件

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <div>message的值是:{{ msg }}</div>
    <div>userInfo的值是:{{ userinfo }}</div>
  </div>
</template>

<script>
export default {
  props: ["msg", "userinfo"],
};
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

子组件向父组件共享数据

子组件向父组件传值需要用到自定义事件,代码如下

子组件

<template>
  <div class="right-container">
    <h3>Right 组件 --- {{ count }}</h3>
    <button @click="add">+1</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // 1. 这是子组件的数据,传给父组件
      count: 0,
    };
  },
  methods: {
    add() {
      this.count++;
      // 修改数据时,通过$emit()触发自定义事件
      this.$emit("numChange", this.count);
    },
  },
};
</script>

<style lang="less">
.right-container {
  padding: 0 20px 20px;
  background-color: lightskyblue;
  min-height: 250px;
  flex: 1;
}
</style>

父组件

<template>
  <div class="app-container">
    <h1>App 根组件 --- {{ countFromSon }}</h1>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left :msg="message" :userinfo="userInfo"></Left>
      <!-- 3. 接收数据 -->
      <Right @numChange="getNewCount"></Right>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Right from "@/components/Right.vue";

export default {
  components: {
    Left,
    Right,
  },
  data() {
    return {
      message: "hello vue.js",
      userInfo: {
        name: "新海诚",
        age: "50",
      },
      countFromSon: 0,
    };
  },
  methods: {
    // 2. 定义接收数据的函数
    getNewCount(val) {
      this.countFromSon = val;
    },
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

兄弟组件的数据共享

兄弟组件数据共享的方式是EvemtBus

  1. 创建eventBus.js模块,并向外共享一个Vue的实例对象
  2. 在数据发送方,调用bus.$emit('事件名称', 要发送的数据)方法触发自定义事件
  3. 在数据接收方,调用bus.$on('事件名称', 事件处理函数)方法注册一个自定义事件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QZREwCT6-1684385078459)(image/image-20230509145416874.png)]

发送方

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <div>message的值是:{{ msg }}</div>
    <div>userInfo的值是:{{ userinfo }}</div>
    <button @click="msg = 'abc'">修改message</button>
    <hr />

    <button @click="sendStr">发送数据给Right</button>
  </div>
</template>

<script>
// 导入eventBus.js
import eventBus from "./eventBus";

export default {
  props: ["msg", "userinfo"],
  data() {
    return {
      // 发送str数据
      str: "《因为太怕痛就全点防御力了》",
    };
  },
  methods: {
    // 发送数据的函数
    sendStr() {
      // 通过eventBus模块来触发自定义事件
      eventBus.$emit("share", this.str);
    },
  },
};
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

eventBus

// 导入Vue构造函数
import Vue from "vue";

// 导出Vue实例对象
export default new Vue();

接收方

<template>
  <div class="right-container">
    <h3>Right 组件 --- {{ count }}</h3>
    <button @click="add">+1</button>
    <hr />
    <p>{{ strFromLeft }}</p>
  </div>
</template>

<script>
// 导入eventBus
import eventBus from "./eventBus.js";
export default {
  data() {
    return {
      // 1. 这是子组件的数据,传给父组件
      count: 0,
      // 接收Left的数据
      strFromLeft: "",
    };
  },
  methods: {
    add() {
      this.count++;
      // 修改数据时,通过$emit()触发自定义事件
      this.$emit("numChange", this.count);
    },
  },
  // 生命周期函数
  created() {
    // 接收Left的数据函数
    eventBus.$on("share", (val) => {
      console.log(val); // success
      this.strFromLeft = val;
    });
  },
};
</script>

<style lang="less">
.right-container {
  padding: 0 20px 20px;
  background-color: lightskyblue;
  min-height: 250px;
  flex: 1;
}
</style>

ref引用

ref用来辅助开发者在不依赖于其他手段的情况下,获取 DOM 元素或组件的引用

每个Vue的组件实例上,都包含一个$refs对象,里面存储着对应的 DOM 元素 或 组件的引用。默认情况下,组件的 $refs 指向一个空对象

引用 DOM 实例代码如下

<template>
  <div class="app-container">
    <!-- 1. 添加ref属性,以便获取DOM元素 -->
    <h1 ref="myh1">App 根组件</h1>
    <button @click="showThis">打印this</button>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
    </div>
  </div>
</template>

<script>
export default {
  methods: {
    showThis() {
      // console.log(this.$refs.myh1);
      // 2. 获取 DOM 元素
      const domH1 = this.$refs.myh1;
      // 3. 操作DOM
      domH1.style.backgroundColor = "red";
    },
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

引用 组件 示例代码如下

<template>
  <div class="left-container">
    <h3>Left 组件--- {{ count }}</h3>
    <button @click="count++">+1</button>
    <button @click="resetCount">重置</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    resetCount() {
      this.count = 0;
    },
  },
};
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>
<template>
  <div class="app-container">
    <!-- 1. 添加ref属性,以便获取DOM元素 -->
    <h1 ref="myh1">App 根组件</h1>
    <button @click="showThis">打印this</button>
    <button @click="resetCount">重置Left组件的count</button>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 添加ref属性,以便操作组件数据 -->
      <Left ref="myLeft"></Left>
    </div>
  </div>
</template>

<script>
// 引入Left组件
import Left from "./components/Left.vue";

export default {
  components: {
    Left,
  },
  methods: {
    showThis() {
      // console.log(this.$refs);
      // 2. 获取 DOM 元素
      const domH1 = this.$refs.myh1;
      // 3. 操作DOM
      domH1.style.backgroundColor = "red";
    },
    resetCount() {
      // 获取组件实例对象
      const component = this.$refs.myLeft;
      // 操作数据
      component.count = 0;
    },
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

ref案例(this.nextTick(cb)方法)

<template>
  <div class="app-container">
    <!-- 1. 添加ref属性,以便获取DOM元素 -->
    <h1 ref="myh1">App 根组件</h1>
    <button @click="showThis">打印this</button>
    <button @click="resetCount">重置Left组件的count</button>
    <hr />

    <input ref="myInput" v-if="inputVisible" type="text" @blur="showButton" />
    <button v-else @click="showInput">展示输入框</button>

    <hr />
    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 添加ref属性,以便操作组件数据 -->
      <Left ref="myLeft"></Left>
    </div>
  </div>
</template>

<script>
// 引入Left组件
import Left from "./components/Left.vue";

export default {
  components: {
    Left,
  },
  data() {
    return {
      // 控制输入框和按钮的按需切换
      inputVisible: false,
    };
  },
  methods: {
    showThis() {
      // console.log(this.$refs);
      // 2. 获取 DOM 元素
      const domH1 = this.$refs.myh1;
      // 3. 操作DOM
      domH1.style.backgroundColor = "red";
    },
    resetCount() {
      // 获取组件实例对象
      const component = this.$refs.myLeft;
      // 操作数据
      component.count = 0;
    },
    // 展示输入框
    showInput() {
      this.inputVisible = true;
      // const myInput = this.$refs.myInput;
      // error in v-on handler: "TypeError: Cannot read properties of undefined (reading 'focus')"
      // myInput.focus();
      // console.log(myInput); // undefined

      // 延迟代码执行DOM渲染完成后
      this.$nextTick(() => {
        const myInput = this.$refs.myInput;
        myInput.focus();
      });
    },
    // 展示按钮
    showButton() {
      this.inputVisible = false;
    },
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

动态组件

基本使用

Vue提供了一个内置的<component>组件,专门用来实现动态组件的渲染,示例代码如下

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 占位符,为组件占位,is属性表示展示的组件名字 -->
      <component :is="comName"></component>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Right from "@/components/Right.vue";
export default {
  data() {
    return {
      // 展示的组件的名字
      comName: "Left",
    };
  },
  components: {
    Left,
    Right,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

动态展示组件

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <button @click="comName = 'Left'">展示Left</button>
    <button @click="comName = 'Right'">展示Right</button>
    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 占位符,为组件占位,is属性表示展示的组件名字 -->
      <component :is="comName"></component>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Right from "@/components/Right.vue";
export default {
  data() {
    return {
      // 展示的组件的名字
      comName: "Left",
    };
  },
  components: {
    Left,
    Right,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

使用keep-alive保持状态

Vue提供了一个内置的<keep-alive>组件,用来缓存动态组件,示例代码如下

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <button @click="comName = 'Left'">展示Left</button>
    <button @click="comName = 'Right'">展示Right</button>
    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 占位符,为组件占位,is属性表示展示的组件名字 -->
      <!-- keep-alive 包裹动态组件,使其在隐藏时,不销毁组件实例,保持组件状态 -->
      <keep-alive>
        <component :is="comName"></component>
      </keep-alive>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Right from "@/components/Right.vue";
export default {
  data() {
    return {
      // 展示的组件的名字
      comName: "Left",
    };
  },
  components: {
    Left,
    Right,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>
<template>
  <div class="left-container">
    <h3>Left 组件--- {{ count }}</h3>
    <button @click="count++">+1</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // 希望在组件隐藏时,同样保持该状态
      count: 0,
    };
  },
};
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

对于我们开发人员来说,有时候我们需要在组件被缓存时,做一件事或者在组件被激活做一件事,这时我们需要用到<keep-alive>生命周期函数

当组件被缓存时,会自动触发组件的deactivated生命周期函数

当组件被激活时,会自动触发组件的activated生命周期函数

<template>
  <div class="left-container">
    <h3>Left 组件--- {{ count }}</h3>
    <button @click="count++">+1</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      // 希望在组件隐藏时,同样保持该状态
      count: 0,
    };
  },
  // 生命周期函数
  activated() {
    console.log("组件被激活了");
  },
  deactivated() {
    console.log("组件被缓存了");
  },
};
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>

keep-alive的include和exclude属性

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <button @click="comName = 'Left'">展示Left</button>
    <button @click="comName = 'Right'">展示Right</button>
    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 占位符,为组件占位,is属性表示展示的组件名字 -->
      <!-- keep-alive 包裹动态组件,使其在隐藏时,不销毁组件实例,保持组件状态 -->
      <!-- 告诉哪些组件可以被缓存,只缓存Left和Right -->
      <keep-alive include="Left,Right">
        <component :is="comName"></component>
      </keep-alive>
      <!-- 用逗号分割多个组件,逗号之后千万不要加空格!!!!!! -->
      <!-- <keep-alive include="Left,Right">
        <component :is="comName"></component>
      </keep-alive> -->
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Right from "@/components/Right.vue";
export default {
  data() {
    return {
      // 展示的组件的名字
      comName: "Left",
    };
  },
  components: {
    Left,
    Right,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

组件的注册名称和组件的name

组件的注册名称:以标签的形式把注册好的组件渲染和使用到页面结构之中,使用组件的注册名称

组件的name:调试工具中看见组件的name以及结合keep-alive实现组件的缓存时需要用到

插槽

什么是插槽

插槽(Slot)是Vue为组件的封装者提供的能力,允许开发者在封装组件时,把不确定的希望由用户指定的部分定义为插槽

插槽的基本使用

插槽的基本使用代码如下

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <hr />
    <!-- 定义了插槽后才能生效 -->
    <slot></slot>
  </div>
</template>

<script>
export default {};
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>
<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 如果我们不声明插槽,在内容节点声明的内容会被忽略 -->
        <p>这是在Left的内容区域声明的 p 标签</p>
      </Left>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
export default {
  components: {
    Left,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

v-slot指令

v-slot指令用于指定用户自定义的内容渲染到哪个插槽中,一般配合插槽的name属性一起使用,代码如下

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <hr />
    <!-- 定义了插槽后才能生效 -->
    <!-- vue官方规定,每一个插槽都要有一个name属性 -->
    <!-- 如果省略不写,则name默认等于default -->
    <slot name="default"></slot>
  </div>
</template>

<script>
export default {};
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>
<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 如果我们不声明插槽,在内容节点声明的内容会被忽略 -->
        <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中 -->
        <!-- 如果要把内容填充到指定名称的插槽中,需要使用v-slot指令 -->
        <!-- v-slot指令要在后面跟上插槽的名字 -->
        <!-- v-slot指令不能用在元素上,必须用在template这个标签上 -->
        <!-- template是一个虚拟标签,只有包裹的作用,不会被渲染为HTML元素 -->
        <template v-slot:default>
          <p>这是在Left的内容区域声明的 p 标签</p>
        </template>
      </Left>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
export default {
  components: {
    Left,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

v-slot的简写

v-slot指令的简写是#,代码如下

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 如果我们不声明插槽,在内容节点声明的内容会被忽略 -->
        <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中 -->
        <!-- 如果要把内容填充到指定名称的插槽中,需要使用v-slot指令 -->
        <!-- v-slot指令要在后面跟上插槽的名字 -->
        <!-- v-slot指令不能用在元素上,必须用在template这个标签上 -->
        <!-- template是一个虚拟标签,只有包裹的作用,不会被渲染为HTML元素 -->
        <template #default>
          <p>这是在Left的内容区域声明的 p 标签</p>
        </template>
      </Left>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
export default {
  components: {
    Left,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

插槽的默认内容(后备内容)

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <hr />
    <!-- 定义了插槽后才能生效 -->
    <!-- vue官方规定,每一个插槽都要有一个name属性 -->
    <!-- 如果省略不写,则name默认等于default -->
    <slot name="default">这是 default 插槽的默认内容</slot>
  </div>
</template>

<script>
export default {};
</script>

<style lang="less">
.left-container {
  padding: 0 20px 20px;
  background-color: orange;
  min-height: 250px;
  flex: 1;
}
</style>
<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 如果我们不声明插槽,在内容节点声明的内容会被忽略 -->
        <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中 -->
        <!-- 如果要把内容填充到指定名称的插槽中,需要使用v-slot指令 -->
        <!-- v-slot指令要在后面跟上插槽的名字 -->
        <!-- v-slot指令不能用在元素上,必须用在template这个标签上 -->
        <!-- template是一个虚拟标签,只有包裹的作用,不会被渲染为HTML元素 -->
        <!-- <template #default>
          <p>这是在Left的内容区域声明的 p 标签</p>
        </template> -->
      </Left>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
export default {
  components: {
    Left,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

具名插槽

在使用插槽时应该给插槽添加name属性以方便用户使用,这也是优秀的组件封装者采用的方法

<template>
  <div class="article-container">
    <!-- 渲染文章的标题 -->
    <div class="header-box">
      <slot name="title"></slot>
    </div>
    <!-- 渲染文章的内容 -->
    <div class="content-box">
      <slot name="content"></slot>
    </div>
    <!-- 渲染文章的作者 -->
    <div class="footer-box">
      <slot name="author"></slot>
    </div>
  </div>
</template>

<script>
export default {
  // 首字母大写
  name: "Article",
};
</script>

<style lang="less" scoped>
.article-container {
  > div {
    min-height: 150px;
  }
  .header-box {
    background-color: pink;
  }
  .content-box {
    background-color: lightblue;
  }
  .footer-box {
    background-color: lightsalmon;
  }
}
</style>
<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />
    <Article>
      <template #title>
        <h3>一首诗</h3>
      </template>

      <template #content>
        <div>
          <p>东风夜放花千树,更吹落、星如雨。</p>
          <p>宝马雕车香满路,凤箫声动,玉壶光转,一夜鱼龙舞。</p>
          <p>蛾儿雪柳黄金缕,笑语盈盈暗香去。</p>
          <p>众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。</p>
        </div>
      </template>

      <template #author>
        <p>辛弃疾</p>
      </template>
    </Article>

    <hr />
    <div class="box" style="display: none">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 如果我们不声明插槽,在内容节点声明的内容会被忽略 -->
        <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中 -->
        <!-- 如果要把内容填充到指定名称的插槽中,需要使用v-slot指令 -->
        <!-- v-slot指令要在后面跟上插槽的名字 -->
        <!-- v-slot指令不能用在元素上,必须用在template这个标签上 -->
        <!-- template是一个虚拟标签,只有包裹的作用,不会被渲染为HTML元素 -->
        <!-- <template #default>
          <p>这是在Left的内容区域声明的 p 标签</p>
        </template> -->
      </Left>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Article from "@/components/Article.vue";
export default {
  components: {
    Left,
    Article,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

作用域插槽

在具名插槽的后面添加属性以希望用户在使用开发者封装的组件时用到这个属性的值,这就是作用域插槽,代码如下

<template>
  <div class="article-container">
    <!-- 渲染文章的标题 -->
    <div class="header-box">
      <slot name="title"></slot>
    </div>
    <!-- 渲染文章的内容 -->
    <div class="content-box">
      <!-- 在封装组件时,为预留的插槽提供属性对应的值,这种用法叫做作用域插槽 -->
      <slot name="content" msg="hello vue"></slot>
    </div>
    <!-- 渲染文章的作者 -->
    <div class="footer-box">
      <slot name="author"></slot>
    </div>
  </div>
</template>

<script>
export default {
  // 首字母大写
  name: "Article",
};
</script>

<style lang="less" scoped>
.article-container {
  > div {
    min-height: 150px;
  }
  .header-box {
    background-color: pink;
  }
  .content-box {
    background-color: lightblue;
  }
  .footer-box {
    background-color: lightsalmon;
  }
}
</style>
<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />
    <Article>
      <template #title>
        <h3>一首诗</h3>
      </template>
      <!-- 作用域插槽的使用 -->
      <template #content="scope">
        <div>
          <p>东风夜放花千树,更吹落、星如雨。</p>
          <p>宝马雕车香满路,凤箫声动,玉壶光转,一夜鱼龙舞。</p>
          <p>蛾儿雪柳黄金缕,笑语盈盈暗香去。</p>
          <p>众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。</p>
          {{ scope.msg }}
        </div>
      </template>

      <template #author>
        <p>辛弃疾</p>
      </template>
    </Article>

    <hr />
    <div class="box" style="display: none">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 如果我们不声明插槽,在内容节点声明的内容会被忽略 -->
        <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中 -->
        <!-- 如果要把内容填充到指定名称的插槽中,需要使用v-slot指令 -->
        <!-- v-slot指令要在后面跟上插槽的名字 -->
        <!-- v-slot指令不能用在元素上,必须用在template这个标签上 -->
        <!-- template是一个虚拟标签,只有包裹的作用,不会被渲染为HTML元素 -->
        <!-- <template #default>
          <p>这是在Left的内容区域声明的 p 标签</p>
        </template> -->
      </Left>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Article from "@/components/Article.vue";
export default {
  components: {
    Left,
    Article,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

作用域插槽的解构赋值

代码如下

<template>
  <div class="article-container">
    <!-- 渲染文章的标题 -->
    <div class="header-box">
      <slot name="title"></slot>
    </div>
    <!-- 渲染文章的内容 -->
    <div class="content-box">
      <!-- 在封装组件时,为预留的插槽提供属性对应的值,这种用法叫做作用域插槽 -->
      <slot name="content" msg="hello vue" :user="userinfo"></slot>
    </div>
    <!-- 渲染文章的作者 -->
    <div class="footer-box">
      <slot name="author"></slot>
    </div>
  </div>
</template>

<script>
export default {
  // 首字母大写
  name: "Article",
  data() {
    return {
      userinfo: {
        name: "zs",
        age: "20",
      },
    };
  },
};
</script>

<style lang="less" scoped>
.article-container {
  > div {
    min-height: 150px;
  }
  .header-box {
    background-color: pink;
  }
  .content-box {
    background-color: lightblue;
  }
  .footer-box {
    background-color: lightsalmon;
  }
}
</style>
<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <hr />
    <Article>
      <template #title>
        <h3>一首诗</h3>
      </template>
      <!-- 作用域插槽的使用 -->
      <!-- 对作用域插槽进行解构赋值 -->
      <template #content="{ msg, user }">
        <div>
          <p>东风夜放花千树,更吹落、星如雨。</p>
          <p>宝马雕车香满路,凤箫声动,玉壶光转,一夜鱼龙舞。</p>
          <p>蛾儿雪柳黄金缕,笑语盈盈暗香去。</p>
          <p>众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。</p>
          {{ msg }}
          {{ user }}
        </div>
      </template>

      <template #author>
        <p>辛弃疾</p>
      </template>
    </Article>

    <hr />
    <div class="box" style="display: none">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 如果我们不声明插槽,在内容节点声明的内容会被忽略 -->
        <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中 -->
        <!-- 如果要把内容填充到指定名称的插槽中,需要使用v-slot指令 -->
        <!-- v-slot指令要在后面跟上插槽的名字 -->
        <!-- v-slot指令不能用在元素上,必须用在template这个标签上 -->
        <!-- template是一个虚拟标签,只有包裹的作用,不会被渲染为HTML元素 -->
        <!-- <template #default>
          <p>这是在Left的内容区域声明的 p 标签</p>
        </template> -->
      </Left>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Article from "@/components/Article.vue";
export default {
  components: {
    Left,
    Article,
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

自定义指令

Vue允许开发者自定义指令

私有自定义指令

基本使用

在每个Vue组件中,可以在directives节点下声明私有自定义指令,示例代码如下

<template>
  <div class="app-container">
    <!-- 自定义指令的使用 -->
    <h1 v-color>App 根组件</h1>
    <hr />
    <Article>
      <template #title>
        <h3>一首诗</h3>
      </template>
      <!-- 作用域插槽的使用 -->
      <!-- 对作用域插槽进行解构赋值 -->
      <template #content="{ msg, user }">
        <div>
          <p>东风夜放花千树,更吹落、星如雨。</p>
          <p>宝马雕车香满路,凤箫声动,玉壶光转,一夜鱼龙舞。</p>
          <p>蛾儿雪柳黄金缕,笑语盈盈暗香去。</p>
          <p>众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。</p>
          {{ msg }}
          {{ user }}
        </div>
      </template>

      <template #author>
        <p>辛弃疾</p>
      </template>
    </Article>

    <hr />
    <div class="box" style="display: none">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 如果我们不声明插槽,在内容节点声明的内容会被忽略 -->
        <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中 -->
        <!-- 如果要把内容填充到指定名称的插槽中,需要使用v-slot指令 -->
        <!-- v-slot指令要在后面跟上插槽的名字 -->
        <!-- v-slot指令不能用在元素上,必须用在template这个标签上 -->
        <!-- template是一个虚拟标签,只有包裹的作用,不会被渲染为HTML元素 -->
        <!-- <template #default>
          <p>这是在Left的内容区域声明的 p 标签</p>
        </template> -->
      </Left>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Article from "@/components/Article.vue";
export default {
  components: {
    Left,
    Article,
  },
  // 定义自定义指令
  directives: {
    color: {
      // 为绑定的 HTML 元素设置红色的文字
      // 执行时机:当指令第一次被绑定到DOM元素的时候会执行bind
      bind(el) {
        // 形参中的el是绑定了此指令的 原生的 DOM 对象
        el.style.color = "red";
      },
    },
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

获取指令绑定的值

通过binding.value获取指令绑定的值

<template>
  <div class="app-container">
    <!-- 自定义指令的使用 -->
    <!-- 获取指令绑定的值 -->
    <h1 v-color="color">App 根组件</h1>
    <hr />
    <Article>
      <template #title>
        <h3>一首诗</h3>
      </template>
      <!-- 作用域插槽的使用 -->
      <!-- 对作用域插槽进行解构赋值 -->
      <template #content="{ msg, user }">
        <div>
          <p>东风夜放花千树,更吹落、星如雨。</p>
          <p>宝马雕车香满路,凤箫声动,玉壶光转,一夜鱼龙舞。</p>
          <p>蛾儿雪柳黄金缕,笑语盈盈暗香去。</p>
          <p>众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。</p>
          {{ msg }}
          {{ user }}
        </div>
      </template>

      <template #author>
        <p>辛弃疾</p>
      </template>
    </Article>

    <hr />
    <div class="box" style="display: none">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 如果我们不声明插槽,在内容节点声明的内容会被忽略 -->
        <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中 -->
        <!-- 如果要把内容填充到指定名称的插槽中,需要使用v-slot指令 -->
        <!-- v-slot指令要在后面跟上插槽的名字 -->
        <!-- v-slot指令不能用在元素上,必须用在template这个标签上 -->
        <!-- template是一个虚拟标签,只有包裹的作用,不会被渲染为HTML元素 -->
        <!-- <template #default>
          <p>这是在Left的内容区域声明的 p 标签</p>
        </template> -->
      </Left>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Article from "@/components/Article.vue";
export default {
  data() {
    return {
      color: "blue",
    };
  },
  components: {
    Left,
    Article,
  },
  // 定义自定义指令
  directives: {
    color: {
      // 为绑定的 HTML 元素设置红色的文字
      // 执行时机:当指令第一次被绑定到DOM元素的时候会执行bind
      bind(el, binding) {
        // 形参中的el是绑定了此指令的 原生的 DOM 对象
        // 形参中的binding是可以拿到用户通过等于号传过来的值的
        el.style.color = binding.value;
      },
    },
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

update函数

update函数是自定义指令的一个配置项,作用是在 DOM 节点更新时重新执行函数体,实例代码如下

<template>
  <div class="app-container">
    <!-- 自定义指令的使用 -->
    <!-- 获取指令绑定的值 -->
    <h1 v-color="color">App 根组件</h1>
    <button @click="color = 'red'">改变 color 的颜色值</button>
    <hr />
    <Article>
      <template #title>
        <h3>一首诗</h3>
      </template>
      <!-- 作用域插槽的使用 -->
      <!-- 对作用域插槽进行解构赋值 -->
      <template #content="{ msg, user }">
        <div>
          <p>东风夜放花千树,更吹落、星如雨。</p>
          <p>宝马雕车香满路,凤箫声动,玉壶光转,一夜鱼龙舞。</p>
          <p>蛾儿雪柳黄金缕,笑语盈盈暗香去。</p>
          <p>众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。</p>
          {{ msg }}
          {{ user }}
        </div>
      </template>

      <template #author>
        <p>辛弃疾</p>
      </template>
    </Article>

    <hr />
    <div class="box" style="display: none">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 如果我们不声明插槽,在内容节点声明的内容会被忽略 -->
        <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中 -->
        <!-- 如果要把内容填充到指定名称的插槽中,需要使用v-slot指令 -->
        <!-- v-slot指令要在后面跟上插槽的名字 -->
        <!-- v-slot指令不能用在元素上,必须用在template这个标签上 -->
        <!-- template是一个虚拟标签,只有包裹的作用,不会被渲染为HTML元素 -->
        <!-- <template #default>
          <p>这是在Left的内容区域声明的 p 标签</p>
        </template> -->
      </Left>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Article from "@/components/Article.vue";
export default {
  data() {
    return {
      color: "blue",
    };
  },
  components: {
    Left,
    Article,
  },
  // 定义自定义指令
  directives: {
    color: {
      // 为绑定的 HTML 元素设置红色的文字
      // 执行时机:当指令第一次被绑定到DOM元素的时候会执行bind
      bind(el, binding) {
        // 形参中的el是绑定了此指令的 原生的 DOM 对象
        // 形参中的binding是可以拿到用户通过等于号传过来的值的
        el.style.color = binding.value;
      },
      // 在 DOM 更新的时候 会触发 update 函数
      update(el, binding) {
        el.style.color = binding.value;
      },
    },
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

函数简写形式

如果bindupdate函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式,代码如下

<template>
  <div class="app-container">
    <!-- 自定义指令的使用 -->
    <!-- 获取指令绑定的值 -->
    <h1 v-color="color">App 根组件</h1>
    <button @click="color = 'red'">改变 color 的颜色值</button>
    <hr />
    <Article>
      <template #title>
        <h3>一首诗</h3>
      </template>
      <!-- 作用域插槽的使用 -->
      <!-- 对作用域插槽进行解构赋值 -->
      <template #content="{ msg, user }">
        <div>
          <p>东风夜放花千树,更吹落、星如雨。</p>
          <p>宝马雕车香满路,凤箫声动,玉壶光转,一夜鱼龙舞。</p>
          <p>蛾儿雪柳黄金缕,笑语盈盈暗香去。</p>
          <p>众里寻他千百度,蓦然回首,那人却在,灯火阑珊处。</p>
          {{ msg }}
          {{ user }}
        </div>
      </template>

      <template #author>
        <p>辛弃疾</p>
      </template>
    </Article>

    <hr />
    <div class="box" style="display: none">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 如果我们不声明插槽,在内容节点声明的内容会被忽略 -->
        <!-- 默认情况下,在使用组件的时候,提供的内容都会被填充到名字为default的插槽之中 -->
        <!-- 如果要把内容填充到指定名称的插槽中,需要使用v-slot指令 -->
        <!-- v-slot指令要在后面跟上插槽的名字 -->
        <!-- v-slot指令不能用在元素上,必须用在template这个标签上 -->
        <!-- template是一个虚拟标签,只有包裹的作用,不会被渲染为HTML元素 -->
        <!-- <template #default>
          <p>这是在Left的内容区域声明的 p 标签</p>
        </template> -->
      </Left>
    </div>
  </div>
</template>

<script>
import Left from "@/components/Left.vue";
import Article from "@/components/Article.vue";
export default {
  data() {
    return {
      color: "blue",
    };
  },
  components: {
    Left,
    Article,
  },
  // 定义自定义指令
  directives: {
    color(el, binding) {
      el.style.color = binding.value;
    },
  },
};
</script>

<style lang="less">
.app-container {
  padding: 1px 20px 20px;
  background-color: #efefef;
}
.box {
  display: flex;
}
</style>

全局自定义指令

在main.js入口文件中定义全局自定义指令,代码如下

import Vue from "vue";
import App from "./App.vue";

Vue.config.productionTip = false;

// 简写如下
Vue.directive("color", function(el, binding) {
  el.style.color = binding.value;
});
// Vue.directive("color", {
//   bind(el, binding) {
//     el.style.color = binding.value;
//   },
//   update(el, binding) {
//     el.style.color = binding.value;
//   },
// });

new Vue({
  render: (h) => h(App),
}).$mount("#app");

路由

路由:hash地址和组件的对应关系

前端路由的工作方式

  1. 用户点击了页面上的路由链接
  2. 导致了URL地址栏中的Hash值发生了变化
  3. 前端路由监听到了Hash地址的变化
  4. 前端路由把当前Hash地址对应的组件渲染到浏览器中

vue-router的基本使用

vue-router是vue.js官方给出的路由解决方案,它只能结合Vue项目进行使用,能够轻松的管理SPA项目中组件的切换

vue-router的官网

vue-router的安装和配置的步骤

  1. 安装vue-router包
  2. 创建路由模块
  3. 导入并挂载路由模块
  4. 声明路由链接和占位符
  5. 配置对应关系

安装vue-router包

安装命令:npm i vue-router

创建路由模块

在src源代码目录下,新建router / index.js 路由模块,并初始化如下代码

// 导入Vue和VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'

// 调用Vue.use()函数,把 VueRouter 安装为Vue的插件
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter()

// 暴露
export default router

导入并挂载路由模块

在 main.js 文件中 编写如下代码

import Vue from 'vue'
import App from './App.vue'

// 导入 bootstrap 样式
import 'bootstrap/dist/css/bootstrap.min.css'
// 全局样式
import '@/assets/global.css'
import router from './router/index'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  // 在 Vue 项目中要想把路由用起来,必须把路由实例对象通过下面的方式进行挂载
  router: router
}).$mount('#app')

声明路由链接和占位符

<template>
  <div class="app-container">
    <h1>App2 组件</h1>
    <!-- 声明链接 -->
    <a href="#/home">首页</a>
    <a href="#/movie">电影</a>
    <a href="#/about">关于</a>

    <hr />
    <!-- 只要在项目中安装和配置了 vue-router,就可以使用 router-view 这个组件了 -->
    <!-- 它是占位符 -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style lang="less" scoped>
.app-container {
  background-color: #efefef;
  overflow: hidden;
  margin: 10px;
  padding: 15px;
  > a {
    margin-right: 10px;
  }
}
</style>

配置对应关系

在路由模块中编写如下代码

// 导入Vue和VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'

import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import Movie from '@/components/Movie.vue'

// 调用Vue.use()函数,把 VueRouter 安装为Vue的插件
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter({
  // routes 是一个数组,是定义 hash 地址与组件的对应关系
  routes: [
    { path: '/home', component: Home },
    { path: '/movie', component: Movie },
    { path: '/about', component: About }
  ]
})

// 暴露
export default router

使用router-link替代a链接

<template>
  <div class="app-container">
    <h1>App2 组件</h1>
    <!-- 声明链接 -->
    <router-link to="/home">首页</router-link>
    <router-link to="/movie">电影</router-link>
    <router-link to="/about">关于</router-link>

    <hr />
    <!-- 只要在项目中安装和配置了 vue-router,就可以使用 router-view 这个组件了 -->
    <!-- 它是占位符 -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style lang="less" scoped>
.app-container {
  background-color: #efefef;
  overflow: hidden;
  margin: 10px;
  padding: 15px;
  > a {
    margin-right: 10px;
  }
}
</style>

路由重定向

路由重定向指的是用户在访问地址 A 的时候,强制用户跳转到地址 C,从而展示特定的组件页面,通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便的设置路由的重定向。

在路由模块编写如下代码

// 导入Vue和VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'

import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import Movie from '@/components/Movie.vue'

// 调用Vue.use()函数,把 VueRouter 安装为Vue的插件
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter({
  // routes 是一个数组,是定义 hash 地址与组件的对应关系
  routes: [
    // 当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    { path: '/movie', component: Movie },
    { path: '/about', component: About }
  ]
})

// 暴露
export default router

嵌套路由

<template>
  <div class="about-container">
    <h3>About 组件</h3>

    <router-link to="/about/tab1">tab1</router-link>
    <router-link to="/about/tab2">tab2</router-link>

    <hr>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'About'
}
</script>

<style lang="less" scoped>
.about-container {
  min-height: 200px;
  background-color: skyblue;
  padding: 15px;
  > a {
    margin-right: 10px;
  }
}
</style>
// 导入Vue和VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'

import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import Movie from '@/components/Movie.vue'
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components//tabs/Tab2.vue'

// 调用Vue.use()函数,把 VueRouter 安装为Vue的插件
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter({
  // routes 是一个数组,是定义 hash 地址与组件的对应关系
  routes: [
    // 当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    { path: '/movie', component: Movie },
    {
      path: '/about',
      component: About,
      // 通过 children 属性,嵌套声明子级路由规则
      children: [
        { path: 'tab1', component: Tab1 },
        { path: 'tab2', component: Tab2 }
      ]
    }
  ]
})

// 暴露
export default router

默认子路由

默认子路由:如果 children 数组中,某个路由规则的 path 值为空字符串,则这条路由规则叫做默认子路由

<template>
  <div class="about-container">
    <h3>About 组件</h3>

    <router-link to="/about">tab1</router-link>
    <router-link to="/about/tab2">tab2</router-link>

    <hr>
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'About'
}
</script>

<style lang="less" scoped>
.about-container {
  min-height: 200px;
  background-color: skyblue;
  padding: 15px;
  > a {
    margin-right: 10px;
  }
}
</style>
// 导入Vue和VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'

import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import Movie from '@/components/Movie.vue'
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components//tabs/Tab2.vue'

// 调用Vue.use()函数,把 VueRouter 安装为Vue的插件
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter({
  // routes 是一个数组,是定义 hash 地址与组件的对应关系
  routes: [
    // 当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    { path: '/movie', component: Movie },
    {
      path: '/about',
      component: About,
      // 通过路由重定向的方式配置默认子路由
      // redirect: '/about/tab1',
      // 通过 children 属性,嵌套声明子级路由规则
      children: [
        // 或者将 path 的值改为空字符串
        { path: '', component: Tab1 },
        { path: 'tab2', component: Tab2 }
      ]
    }
  ]
})

// 暴露
export default router

动态路由

动态路由指的是把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性

<template>
  <div class="app-container">
    <h1>App2 组件</h1>
    <!-- 声明链接 -->
    <router-link to="/home">首页</router-link>
    <router-link to="/movie/1">电影1</router-link>
    <router-link to="/movie/2">电影2</router-link>
    <router-link to="/movie/3">电影3</router-link>
    <router-link to="/about">关于</router-link>

    <hr />
    <!-- 只要在项目中安装和配置了 vue-router,就可以使用 router-view 这个组件了 -->
    <!-- 它是占位符 -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style lang="less" scoped>
.app-container {
  background-color: #efefef;
  overflow: hidden;
  margin: 10px;
  padding: 15px;
  > a {
    margin-right: 10px;
  }
}
</style>
// 导入Vue和VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'

import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import Movie from '@/components/Movie.vue'
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components//tabs/Tab2.vue'

// 调用Vue.use()函数,把 VueRouter 安装为Vue的插件
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter({
  // routes 是一个数组,是定义 hash 地址与组件的对应关系
  routes: [
    // 当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    // 通过下面的方式将 id 定义为参数项
    // 需求:在 Movie 组件中,希望根据 id 的值,展示对应电影的详细信息
    { path: '/movie/:id', component: Movie },
    {
      path: '/about',
      component: About,
      // 通过路由重定向的方式配置默认子路由
      // redirect: '/about/tab1',
      // 通过 children 属性,嵌套声明子级路由规则
      children: [
        // 或者将 path 的值改为空字符串
        { path: '', component: Tab1 },
        { path: 'tab2', component: Tab2 }
      ]
    }
  ]
})

// 暴露
export default router

获取动态参数

$route.params获取动态参数

<template>
  <div class="movie-container">
    <!-- 获取 id -->
    <!-- $route 是路由的参数对象 -->
    <!-- $router 是路由的导航对象 -->
    <h3>Movie 组件--- {{ $route.params.id }}</h3>
    <button @click="showThis">打印 this</button>
  </div>
</template>

<script>
export default {
  name: 'Movie',
  methods: {
    showThis() {
      console.log(this)
    }
  }
}
</script>

<style lang="less" scoped>
.movie-container {
  min-height: 200px;
  background-color: lightsalmon;
  padding: 15px;
}
</style>

通过 props 获取动态参数

// 导入Vue和VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'

import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import Movie from '@/components/Movie.vue'
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components//tabs/Tab2.vue'

// 调用Vue.use()函数,把 VueRouter 安装为Vue的插件
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter({
  // routes 是一个数组,是定义 hash 地址与组件的对应关系
  routes: [
    // 当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    // 通过下面的方式定义为参数项
    // 在 Movie 组件中,希望根据 id 的值,展示对应电影的详细信息
    // 为当前组件开启props传参
    { path: '/movie/:id', component: Movie, props: true },
    {
      path: '/about',
      component: About,
      // 通过路由重定向的方式配置默认子路由
      // redirect: '/about/tab1',
      // 通过 children 属性,嵌套声明子级路由规则
      children: [
        // 或者将 path 的值改为空字符串
        { path: '', component: Tab1 },
        { path: 'tab2', component: Tab2 }
      ]
    }
  ]
})

// 暴露
export default router
<template>
  <div class="movie-container">
    <!-- 获取 id -->
    <!-- $route 是路由的参数对象 -->
    <!-- $router 是路由的导航对象 -->
    <h3>Movie 组件--- {{ id }}</h3>
    <button @click="showThis">打印 this</button>
  </div>
</template>

<script>
export default {
  name: 'Movie',
  props: ['id'],
  methods: {
    showThis() {
      console.log(this)
    }
  }
}
</script>

<style lang="less" scoped>
.movie-container {
  min-height: 200px;
  background-color: lightsalmon;
  padding: 15px;
}
</style>

路径参数和查询参数

<template>
  <div class="app-container">
    <h1>App2 组件</h1>
    <!-- 声明链接 -->
    <router-link to="/home">首页</router-link>
    <!-- 这是路径参数 -->
    <!-- 还有一种查询参数(问号后面的参数):它需要通过 $route.query 来访问查询参数 -->
    <router-link to="/movie/1">电影1</router-link>
    <router-link to="/movie/2">电影2</router-link>
    <router-link to="/movie/3">电影3</router-link>
    <router-link to="/about">关于</router-link>

    <hr />
    <!-- 只要在项目中安装和配置了 vue-router,就可以使用 router-view 这个组件了 -->
    <!-- 它是占位符 -->
    <router-view></router-view>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style lang="less" scoped>
.app-container {
  background-color: #efefef;
  overflow: hidden;
  margin: 10px;
  padding: 15px;
  > a {
    margin-right: 10px;
  }
}
</style>
  • 注意:路由参数对象($route)中有 fullPathpath 两个属性,前者是完整路径,携带了查询参数,后者是路径,只有路径参数

声明式导航和编程式导航

在浏览器中,点击链接实现导航的方式,叫做声明式导航

在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航

vue-router 的编程式导航 API

  1. this.$router.push('hash地址')

    • 跳转到指定的 hash 地址,并增加一条历史记录
  2. this.$router.replace('hash地址')

    • 跳转到指定的 hash 地址,并替换掉当前的历史记录
  3. this.$router.go(数值n)

    • 可以在浏览历史中前进和后退

在实际开发中,一般只会前进和后退一层页面,因此 vue-router 提供了如下两个便捷方法

  1. $router.back()

    • 后退一层
  2. $router.forward()

    • 前进一层

导航守卫

导航守卫可以控制路由的访问权限,示意图如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ip5kH7sK-1684385078460)(image/image-20230515201904486.png)]

全局前置守卫

每次发生路由的导航跳转时,都会触发全局前置守卫,因此,在全局前置守卫当中,程序员可以对每个路由 进行访问权限的控制

// 导入Vue和VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'

import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import Movie from '@/components/Movie.vue'
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components//tabs/Tab2.vue'

// 调用Vue.use()函数,把 VueRouter 安装为Vue的插件
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter({
  // routes 是一个数组,是定义 hash 地址与组件的对应关系
  routes: [
    // 当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    // 通过下面的方式定义为参数项
    // 在 Movie 组件中,希望根据 id 的值,展示对应电影的详细信息
    // 为当前组件开启props传参
    { path: '/movie/:id', component: Movie, props: true },
    {
      path: '/about',
      component: About,
      // 通过路由重定向的方式配置默认子路由
      // redirect: '/about/tab1',
      // 通过 children 属性,嵌套声明子级路由规则
      children: [
        // 或者将 path 的值改为空字符串
        { path: '', component: Tab1 },
        { path: 'tab2', component: Tab2 }
      ]
    }
  ]
})

// 为 router 实例对象,声明全局前置守卫
// 只要发生路由的跳转,必然会触发 beforeEach 指定的回调函数
router.beforeEach(function(to, from, next) {
  // to 将要访问的路由的信息对象
  console.log(to)
  // from 将要离开的路由的信息对象
  console.log(from)
  // next 是一个函数,调用 next() 表示放行,允许这次路由导航
  // 一定要调用一次,否则全部不放行
  next()
})

// 暴露
export default router

案例

Main.vue文件如下

<template>
  <div>
    <h3>Main 后台</h3>
  </div>
</template>

<script>
export default {}
</script>

<style>
</style>

Login.vue文件如下

<template>
  <div>
    <h3>Login 登录</h3>
  </div>
</template>

<script>
export default {}
</script>

<style>
</style>

Home.vue文件如下

<template>
  <div class="home-container">
    <h3>Home 组件</h3>

    <router-link to="/main">去后台</router-link>
  </div>
</template>

<script>
export default {
  name: 'Home'
}
</script>

<style lang="less" scoped>
.home-container {
  min-height: 200px;
  background-color: pink;
  padding: 15px;
}
</style>

路由模块文件如下

// 导入Vue和VueRouter的包
import Vue from 'vue'
import VueRouter from 'vue-router'

import Home from '@/components/Home.vue'
import About from '@/components/About.vue'
import Movie from '@/components/Movie.vue'
import Tab1 from '@/components/tabs/Tab1.vue'
import Tab2 from '@/components//tabs/Tab2.vue'
import Login from '@/components/Login.vue'
import Main from '@/components/Main.vue'

// 调用Vue.use()函数,把 VueRouter 安装为Vue的插件
Vue.use(VueRouter)

// 创建路由的实例对象
const router = new VueRouter({
  // routes 是一个数组,是定义 hash 地址与组件的对应关系
  routes: [
    // 当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    // 通过下面的方式定义为参数项
    // 在 Movie 组件中,希望根据 id 的值,展示对应电影的详细信息
    // 为当前组件开启props传参
    { path: '/movie/:id', component: Movie, props: true },
    {
      path: '/about',
      component: About,
      // 通过路由重定向的方式配置默认子路由
      // redirect: '/about/tab1',
      // 通过 children 属性,嵌套声明子级路由规则
      children: [
        // 或者将 path 的值改为空字符串
        { path: '', component: Tab1 },
        { path: 'tab2', component: Tab2 }
      ]
    },
    { path: '/login', component: Login },
    { path: '/main', component: Main }
  ]
})

// 为 router 实例对象,声明全局前置守卫
// 只要发生路由的跳转,必然会触发 beforeEach 指定的回调函数
router.beforeEach(function(to, from, next) {
  if (to.path === '/main') {
    const token = localStorage.getItem('token')
    if (token) {
      next()
    } else {
      next('/login')
    }
  } else {
    next()
  }
})

// 暴露
export default router

Vuex(Pinia—Vue3)

Vuex是什么

Vuex是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间的数据共享

Vuex的优点

  1. 能够在vuex中集中管理共享的数据,易于开发和后期维护,
  2. 能够高效的实现组件之间的数据共享,提高开发效率
  3. 存储在vuex中的数据都是响应式的,能够实时保持数据与页面的同步

Vuex存储的数据

一般情况下,只有组件之间共享的数据,才有必要存储到vuex当中,对于组件之中的私有数据依旧存储到组件自身的 data 中即可

Vuex的安装和配置步骤

  1. 安装Vuex包
  2. 创建Vuex模块
  3. 导入并挂载Vuex

安装Vuex包

安装命令:npm i vuex@3.6.2 (3.6.2好像是现在的默认版本了)

创建Vuex模块

src 的源代码目录下,创建 store / index.js Vuex模块,并初始化如下代码

// 导入Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 把 Vuex 安装为 Vue 的插件
Vue.use(Vuex)

// 暴露 Vuex.Store 对象
export default new Vuex.Store()

导入并挂载 Vuex

在 main.js 文件 中编写如下代码

import Vue from 'vue'
import App from './App.vue'
// 导入 Vuex.Store 对象
import store from './store'

Vue.config.productionTip = false

new Vue({
  // 挂载
  store,
  render: h => h(App)
}).$mount('#app')

完成上面的简单配置后就可以在Vue 2 的项目中愉快的使用 Vuex 了

Vuex 的基本使用

State(公共数据)

State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 的 State 中进行存储

存储公共数据

在 Vuex 模块文件中 编写如下代码

// 导入Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 把 Vuex 安装为 Vue 的插件
Vue.use(Vuex)

// 暴露 Vuex.Store 对象
export default new Vuex.Store({
  // 存储公共数据
  state: {
    count: 0
  }
})

这样所有的组件都可以使用 count 的值了,代码如下

通过 $store.state 访问公共数据

App.vue根组件代码如下

<template>
  <div class="app-container">
    <h1>App 根组件</h1>
    <Addition></Addition>
    <hr>
    <Subtraction></Subtraction>
  </div>
</template>

<script>
import Addition from './components/Addition.vue'
import Subtraction from './components/Subtraction.vue'

export default {
  name: 'App',
  components: {
    Addition,
    Subtraction
  }
}
</script>

<style lang="less" scoped>
.app-container {
  background-color: #efefef;
  overflow: hidden;
  margin: 10px;
  padding: 15px;
  > a {
    margin-right: 10px;
  }
}
</style>

Addition.vue 文件如下

<template>
  <div>
    <!-- 通过 $store.state.count 访问数据 -->
    <h3>最新的Count值为: {{ $store.state.count }}</h3>
    <button>+1</button>
  </div>
</template>

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

<style>
</style>
通过 mapState 函数访问公共数据

Subtraction.vue文件如下

<template>
  <div>
    <h3>最新的Count值为: {{ count }}</h3>
    <button>-1</button>
  </div>
</template>

<script>
// 导入 mapState 函数
import { mapState } from 'vuex'

export default {
  name: 'Subtraction',
  data() {
    return {}
  },
  computed: {
    // 通过 mapState 访问公共数据
    ...mapState(['count'])
  }
}
</script>

<style>
</style>

项目开发中,我比较喜欢的是第一种方式,但是用第二种用的多一点。

Mutations(变更数据)

Mutations用于变更 Store 中的数据

  1. 只能通过 Mutations 变更 Store 中的数据,不可以直接操作 Store 中的数据
  2. 通过这种方式操作起来虽然繁琐一些,但是可以集中监控所有数据的变化
定义变更数据的函数

Vuex 模块文件如下

// 导入Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 把 Vuex 安装为 Vue 的插件
Vue.use(Vuex)

// 暴露 Vuex.Store 对象
export default new Vuex.Store({
  // 存储公共数据
  state: {
    count: 0
  },
  // 变更状态必须在 mutations 下进行
  mutations: {
    add(state) {
      state.count++
    }
  }
})
触发 mutations 函数方式一

Addition.vue文件如下

<template>
  <div>
    <!-- 通过 $store.state.count 访问数据 -->
    <h3>最新的Count值为: {{ $store.state.count }}</h3>
    <button @click="handleCount">+1</button>
  </div>
</template>

<script>
export default {
  name: 'Addition',
  data() {
    return {}
  },
  methods: {
    handleCount() {
      // 触发 mutations 的方式一
      this.$store.commit('add')
    }
  }
}
</script>

<style>
</style>
第一种方式给 mutations 传参

Vuex模块文件如下

// 导入Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 把 Vuex 安装为 Vue 的插件
Vue.use(Vuex)

// 暴露 Vuex.Store 对象
export default new Vuex.Store({
  // 存储公共数据
  state: {
    count: 0
  },
  // 变更状态必须在 mutations 下进行
  mutations: {
    // 第一个参数永远是 state
    // 第二个参数可以接收参数
    add(state, step) {
      state.count += step
    }
  }
})

Addition.vue文件如下

<template>
  <div>
    <!-- 通过 $store.state.count 访问数据 -->
    <h3>最新的Count值为: {{ $store.state.count }}</h3>
    <button @click="handleCount">+1</button>
  </div>
</template>

<script>
export default {
  name: 'Addition',
  data() {
    return {}
  },
  methods: {
    handleCount() {
      // 触发 mutations 的方式一
      // commit 函数有第二个参数用于传参
      this.$store.commit('add', 2)
    }
  }
}
</script>

<style>
</style>
触发 mutations 函数方式二

Vuex 模块文件如下

// 导入Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 把 Vuex 安装为 Vue 的插件
Vue.use(Vuex)

// 暴露 Vuex.Store 对象
export default new Vuex.Store({
  // 存储公共数据
  state: {
    count: 0
  },
  // 变更状态必须在 mutations 下进行
  mutations: {
    // 第一个参数永远是 state
    // 第二个参数可以接收参数
    add(state, step) {
      state.count += step
    },
    sub(state) {
      state.count--
    }
  }
})

Subtraction.vue文件如下

<template>
  <div>
    <h3>最新的Count值为: {{ count }}</h3>
    <button @click="sub">-1</button>
  </div>
</template>

<script>
// 导入 mapState 函数
// 导入 mapMutations 函数
import { mapState, mapMutations } from 'vuex'

export default {
  name: 'Subtraction',
  data() {
    return {}
  },
  computed: {
    // 通过 mapState 访问公共数据
    ...mapState(['count'])
  },
  methods: {
    // 通过 mapMutations 访问函数的方式二
    ...mapMutations(['sub'])
  }
}
</script>

<style>
</style>
第二种方式给 mutations 传参

Vuex 模块文件如下

// 导入Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 把 Vuex 安装为 Vue 的插件
Vue.use(Vuex)

// 暴露 Vuex.Store 对象
export default new Vuex.Store({
  // 存储公共数据
  state: {
    count: 0
  },
  // 变更状态必须在 mutations 下进行
  mutations: {
    // 第一个参数永远是 state
    // 第二个参数可以接收参数
    add(state, step) {
      state.count += step
    },
    sub(state, step) {
      state.count -= step
    }
  }
})

Subtraction.vue文件如下

<template>
  <div>
    <h3>最新的Count值为: {{ count }}</h3>
    <button @click="sub(2)">-1</button>
  </div>
</template>

<script>
// 导入 mapState 函数
// 导入 mapMutations 函数
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

export default {
  name: 'Subtraction',
  data() {
    return {}
  },
  computed: {
    // 通过 mapState 访问公共数据
    ...mapState(['count'])
  },
  methods: {
    // 通过 mapMutations 访问函数的方式二
    ...mapMutations(['sub']),
  }
}
</script>

<style>
</style>
注意

不要在 mutations 函数中使用异步操作,否则会对调式工具有破坏性影响,影响我们的开发体验

比如如下代码,在 Vuex 模块文件中

// 导入Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 把 Vuex 安装为 Vue 的插件
Vue.use(Vuex)

// 暴露 Vuex.Store 对象
export default new Vuex.Store({
  // 存储公共数据
  state: {
    count: 0
  },
  // 变更状态必须在 mutations 下进行
  mutations: {
    // 第一个参数永远是 state
    // 第二个参数可以接收参数
    add(state, step) {
      // 在 mutations 函数中使用了定时器异步操作
      setTimeout(() => {
        state.count += step
      }, 1000)
    },
    sub(state) {
      state.count--
    }
  }
})

在上面的代码中,我们使用了定时器异步操作,这样会让我们的调试工具无法工作,从而影响开发体验,这不是最佳实践,那如果我们有这样的需求,需要用到 Vuex 的 Action

Action

Action 用于处理异步任务

如果通过异步操作变更数据,必须通过 Action,而不能使用 Mutation,但是在 Action 中还是要通过触发 Mutation 的方式间接变更数据

定义 Actions 函数

Vuex 模块文件如下,我们在 actions 节点下定义了异步操作的函数

// 导入Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 把 Vuex 安装为 Vue 的插件
Vue.use(Vuex)

// 暴露 Vuex.Store 对象
export default new Vuex.Store({
  // 存储公共数据
  state: {
    count: 0
  },
  // 变更状态必须在 mutations 下进行
  mutations: {
    // 第一个参数永远是 state
    // 第二个参数可以接收参数
    add(state, step) {
      state.count += step
    },
    sub(state) {
      state.count--
    }
  },
  // 定义异步操作的函数
  actions: {
    // 第一个参数是上下文,可以看成$store
    addAsync(context, step) {
      setTimeout(() => {
        // 触发 mutations 函数
        // commit 函数有第二个参数用于传参
        context.commit('add', step)
      }, 1000)
    }
  }
})
触发 actions 函数的方式一(代码中有传参的方式)

Addition.vue 文件如下

<template>
  <div>
    <!-- 通过 $store.state.count 访问数据 -->
    <h3>最新的Count值为: {{ $store.state.count }}</h3>
    <button @click="handleCount">+1</button>
    <button @click="handleCountAsync">+1 Async</button>
  </div>
</template>

<script>
export default {
  name: 'Addition',
  data() {
    return {}
  },
  methods: {
    handleCount() {
      // 触发 mutations 的方式一
      // commit 函数有第二个参数用于传参
      this.$store.commit('add', 2)
    },
    // 异步操作
    handleCountAsync() {
      // 触发 actions 的方式
      // dispatch 函数的第二个参数用于传参
      this.$store.dispatch('addAsync', 2)
    }
  }
}
</script>

<style>
</style>
触发 Action 函数的方式二(代码中有传参的方式)

Vuex 模块文件如下

// 导入Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 把 Vuex 安装为 Vue 的插件
Vue.use(Vuex)

// 暴露 Vuex.Store 对象
export default new Vuex.Store({
  // 存储公共数据
  state: {
    count: 0
  },
  // 变更状态必须在 mutations 下进行
  mutations: {
    // 第一个参数永远是 state
    // 第二个参数可以接收参数
    add(state, step) {
      state.count += step
    },
    sub(state, step) {
      state.count -= step
    }
  },
  // 异步操作
  actions: {
    addAsync(context, step) {
      setTimeout(() => {
        // 触发 mutations 函数
        context.commit('add', step)
      }, 1000)
    },
    subAsync(context, step) {
      setTimeout(() => {
        context.commit('sub', step)
      }, 1000)
    }
  }
})

Subtraction.vue文件如下

<template>
  <div>
    <h3>最新的Count值为: {{ count }}</h3>
    <button @click="sub(2)">-1</button>
    <!-- 传参 -->
    <button @click="subAsync(2)">-1 Async</button>
  </div>
</template>

<script>
// 导入 mapState 函数
// 导入 mapMutations 函数
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

export default {
  name: 'Subtraction',
  data() {
    return {}
  },
  computed: {
    // 通过 mapState 访问公共数据
    ...mapState(['count'])
  },
  methods: {
    // 通过 mapMutations 访问函数的方式二
    ...mapMutations(['sub']),
    ...mapActions(['subAsync'])
  }
}
</script>

<style>
</style>

Getter

Getter 用于对 Store 中的数据进行加工处理,形成新的数据

Getter 可以对 Store 中已有的数据加工处理之后形成新的数据,类似Vue的计算属性

Store 中的数据发生变化,Getter 的数据也会变化

定义 Getter 数据
// 导入Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 把 Vuex 安装为 Vue 的插件
Vue.use(Vuex)

// 暴露 Vuex.Store 对象
export default new Vuex.Store({
  // 存储公共数据
  state: {
    count: 0
  },
  // 变更状态必须在 mutations 下进行
  mutations: {
    // 第一个参数永远是 state
    // 第二个参数可以接收参数
    add(state, step) {
      state.count += step
    },
    sub(state) {
      state.count--
    }
  },
  // 异步操作
  actions: {
    addAsync(context, step) {
      setTimeout(() => {
        // 触发 mutations 函数
        context.commit('add', step)
      }, 1000)
    },
    subAsync(context) {
      setTimeout(() => {
        context.commit('sub')
      }, 1000)
    }
  },
  getters: {
    showNum(state) {
      return '当前的数量是' + state.count
    }
  }
})
通过 $store.getters 访问 Getter
<template>
  <div>
    <!-- 通过 $store.state.count 访问数据 -->
    <h3>最新的Count值为: {{ $store.state.count }}</h3>
    <h3>{{ $store.getters.showNum }}</h3>
    <button @click="handleCount">+1</button>
    <button @click="handleCountAsync">+1 Async</button>
  </div>
</template>

<script>
export default {
  name: 'Addition',
  data() {
    return {}
  },
  methods: {
    handleCount() {
      // 触发 mutations 的方式一
      // commit 函数有第二个参数用于传参
      this.$store.commit('add', 2)
    },
    // 异步操作
    handleCountAsync() {
      // 触发 actions 的方式
      // dispatch 函数的第二个参数用于传参
      this.$store.dispatch('addAsync', 2)
    }
  }
}
</script>

<style>
</style>
通过 mapGetters 函数访问 Getter
<template>
  <div>
    <h3>最新的Count值为: {{ count }}</h3>
    <h3>{{ showNum }}</h3>
    <button @click="sub">-1</button>
    <button @click="subAsync">-1 Async</button>
  </div>
</template>

<script>
// 导入 mapState 函数
// 导入 mapMutations 函数
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

export default {
  name: 'Subtraction',
  data() {
    return {}
  },
  computed: {
    // 通过 mapState 访问公共数据
    ...mapState(['count']),
    ...mapGetters(['showNum'])
  },
  methods: {
    // 通过 mapMutations 访问函数的方式二
    ...mapMutations(['sub']),
    ...mapActions(['subAsync'])
  }
}
</script>

<style>
</style>

第三方组件库

移动端

Vant 组件库官网(Vue2)

Vant 组件库官网(Vue3)

PC端

Element-ui 组件库官网(Vue2)

Element-ui 组件库官网(Vue3)

后面还会进行完善,笔记现在就先这样了,Vue3笔记点击这里

回到顶部

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 尚硅谷的Vue2笔记是学习Vue技术的好帮手。这份笔记详细地介绍了Vue的基本概念和使用方法,包括Vue的属性、指令、事件、计算属性、过滤器、组件等等。通过阅读这份笔记,我们可以了解Vue的整个生命周期,并且学习如何在Vue中绑定数据、响应事件、使用组件化等等。另外,笔记中也提到了Vue的MVVM模式、路由、状态管理、Ajax等进阶使用方法,以及Vue的一些注意点和优化技巧,这些非常实用且有助于深入学习和应用Vue技术。 总体来说,尚硅谷的Vue2笔记内容丰富、清晰易懂,适合初学者和中级开发者使用,是学习Vue技术的一份不错资料。需要注意的是,笔记中的代码及部分内容可能存在过时和不准确的情况,需要和Vue官方文档及其他权威资料进行比较和验证。 ### 回答2: 尚硅谷的Vue2笔记是一份非常全面和深入的Vue学习资料,它涵盖了Vue框架的基本概念和重要特性,包括Vue的组件化开发、指令、路由、Vuex状态管理、axios网络请求等。该笔记不仅注重理论知识的讲解,而且注重实战应用。它提供了大量的示例代码和练习项目,帮助学习者理解和掌握Vue的核心概念和技术。 在Vue2笔记中,作者从Vue的基本概念和MVVM架构模式开始讲解,然后逐步深入介绍了Vue的各种特性和用法,如组件、生命周期、计算属性、watch、事件处理、槽位、指令等等。特别是在组件化开发方面,作者详细介绍了组件之间的通信方式、props和$emit的使用、slot插槽的应用等等,这些都是Vue组件化开发中非常重要的概念。 除了组件化开发之外,Vue2笔记还详细介绍了Vue的路由、状态管理和网络请求等其他关键特性。在路由方面,作者讲解了Vue-Router的基本使用和路由守卫的应用,让学习者能够掌握Vue应用的页面导航和权限控制。在状态管理方面,作者讲解了Vuex的设计思想和使用方法,使学习者能够在复杂的应用中更好地管理和共享状态。在网络请求方面,作者介绍了axios库的使用和封装方法,帮助学习者更好地理解Vue应用中的数据请求和展示。 总的来说,尚硅谷Vue2笔记对于学习Vue框架的人来说是一份非常优秀的教材。它详细介绍了Vue的各个方面,并提供了丰富的练习项目,帮助学习者更好地掌握Vue的核心技术和应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

草莓小子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值