• Vue基础


    目录


    参考

    API参考

    插件参考

    组件库参考

    • 参考:Vant组件库:轻量、可靠的移动端 Vue 组件库
    • 参考:Vue Tables-2:提供功能齐全的工具及以便Vue创建漂亮而实用的数据表格
    • 参考:Handsontable:页面端的表格交互插件,以及加载显示表格内容
    • 参考:Ag-Grid Vue:基于Vue的数据表格组件
    • 参考:Vue-Easytable:强大的Vue表格组件之一
    • 参考:Vue-Good-Table:基于Vue的数据表格组件,简单干净,具有排序、列过滤、分页等基本功能
    • 参考:Vue Toastification:提供轻巧、简单和漂亮的吐司提示,支持Vue 3
    • 参考:Vue Toasted:Vue最好的toast(提示)插件之一
    • 参考:Vue Notifaction
    • 参考:Vue Awesome Notifaction:轻量级,完全可自定义
    • 参考:Vue Wait:用于Vue、VueX等应用的复杂装载器和进度管理插件
    • 参考:Vue Content Loader:基于Vue.js的SVG占位符加载,可自定义SVG组件
    • 参考:Epic Spinners:纯CSS打造的网页Loading效果
    • 参考:Vue Radial Progress:径向进度条效果的加载器组件
    • 参考:Vue Feather Icons:开源图标库
    • 参考:Vue Awesome:流行的图标字体库
    • 参考:Vue Material Design Icons:单文件组件的SVG Material Design图标集合
    • 参考:Vue Apexcharts:现代的JS图表库
    • 参考:Vue-ECharts:基于Vue2.0和EChart封装的图标组件
    • 参考:Vue Timer Hook:计时器模块

    调试参考


    日志

    • 2022年03月08日19:55:26 初始版本
    • 2022年06月14日10:18:04 提取【模板】内容

    内部模板参考

    • [新建Vue CLI工程(基础工程)【模板】](# 新建Vue CLI工程(基础工程)【模板】)
    • [新建Vue CLI工程(带路由工程)【模板】](# 新建Vue CLI工程(带路由工程)【模板】)
    • [Vant组件库基本使用【模板】](# Vant组件库基本使用【模板】)
    • [Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)
    • [Vite安装和配置less【模板】](# Vite安装和配置less【模板】)
    • [Vue CLI创建Vue3.x项目【模板】](# Vue CLI创建Vue3.x项目【模板】)
    • [Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)
    • [UI库ElementUI【模板】](# UI库ElementUI【模板】)
    • [UI库AntDesign【模板】](# UI库AntDesign【模板】)
    • [Vite安装和配置Router【模板】](# Vite安装和配置Router【模板】)

    vue-devtools调试工具

    安装完成后需要进行配置。

    • “管理扩展程序 > Vue.js devtools”,“有权访问的网站”配置为“在所有网站上”。
    • “管理扩展程序 > Vue.js devtools”,“允许访问文件网址”配置为开启。

    Vue简介

    什么是Vue

    1. 构建用户界面
      • 用 vue 往 html 页面中填充数据,非常的方便
    2. 框架
      • 框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能!
      • 要学习 vue,就是在学习 vue 框架中规定的用法!

    vue全家桶

    • vue(核心库)vu
    • vue-router(路由方案)
    • vuex(状态管理方案)
    • vue组件库(快速搭建页面UI效果的方案)

    以及辅助 vue 项目开发的一系列工具

    • vue-cli(npm 全局包:一键生成工程化的 vue 项目 - 基于 webpack、大而全)
    • vite(npm 全局包:一键生成工程化的 vue 项目 - 小而巧)
    • vue-devtools(浏览器插件:辅助调试的工具)
    • vetur(vscode 插件:提供语法高亮和智能提示)

    模块化、组件化、规范化、自动化

    Vue的两个特性

    1. 数据驱动视图:在使用了 vue 的页面中,vue 会监听数据的变化,从而自动重新渲染页面的结构。

      • 数据的变化会驱动视图自动更新。
      • 好处:程序员只管把数据维护好,那么页面结构会被 vue 自动渲染出来!
    2. 双向数据绑定: 在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源 中。

      在网页中,form 表单负责采集数据,Ajax 负责提交数据

      • js 数据的变化,会被自动渲染到页面上
      • 页面上表单采集的数据发生变化的时候,会被 vue 自动获取到,并更新到JS数据中

    注意:数据驱动视图和双向数据绑定的底层原理是 MVVM(Mode 数据源、View 视图、ViewModel 就是 vue 的实例)

    MVVM

    MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel,它把每个 HTML 页面都拆分成了这三个部分。

    在 MVVM 概念中:

    • Model 表示当前页面渲染时所依赖的数据源。
    • View 表示当前页面所渲染的 DOM 结构。
    • ViewModel 表示 vue 的实例,它是 MVVM 的核心。

    MVVM 的工作原理

    ViewModel 作为 MVVM 的核心,是它把当前页面的数据源(Model)和页面的结构(View)连接在了一起。

    • 当数据源发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面的结构
    • 当表单元素的值发生变化时,也会被 VM 监听到,VM 会把变化过后最新的值自动同步到 Model 数据源中

    HTML中引入Vue

    <!-- Vue.js 2.X -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    
    <!-- Vue.js 3.X -->
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    

    Vue的基本使用

    1. 导入 vue.js 的 script 脚本文件
    2. 在页面中声明一个将要被 vue 所控制的 DOM 区域
    3. 创建 vm 实例对象(vue 实例对象)

    在VS Code中新增一个自定义模板“template-vue2.x”:

    1. Ctrl + Alt + P调出控制台,输入“snippets”。

    2. 在弹出的选项中选择“Preferences:Configure User Snippets”。

    3. 选择“新建全局代码片段文件”。

    4. 输入名称“template-vue2.x”。

    5. 将以下代码形成一个自定义模板。

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8" />
          <title>My test page</title>
          <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        </head>
      
        <body>
          <!-- 希望Vue能够控制下面的这个div,帮我们再把数据填充到div内部 -->
          <div id="app">
            <p>{{ message1 }}</p>
            <p>{{ message2 }}</p>
          </div>
      
          <script>
            // 创建Vue实例对象
            new Vue({
              // el属性是固定的写法,表示当前VM实例要控制页面的哪个区域,接收的值是一个选择器
              el: "#app",
              // data对象为需要渲染到页面的数据
              data: {
                message1: "Hello Vue.js来了!",
                message2: "This is my first project!",
              },
            });
          </script>
        </body>
      </html>
      
      
    6. 模板中填写内容。

      {
      	"Print to console": {
      		"prefix": "tepmlate-Vue2.x",
      		"body": [
      			"<!DOCTYPE html>",
      			"<html>",
      			"  <head>",
      			"    <meta charset='utf-8' />",
      			"    <title>My test page</title>",
      			"    <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->",
      			"    <script src='https://cdn.jsdelivr.net/npm/vue/dist/vue.js'></script>",
      			"  </head>",
      			"",
      			"  <body>",
      			"    <!-- 希望Vue能够控制下面的这个div,帮我们再把数据填充到div内部 -->",
      			"    <div id='app'>",
      			"      <p>{{ message1 }}</p>",
      			"      <p>{{ message2 }}</p>",
      			"    </div>",
      			"",
      			"    <script>",
      			"      // 创建Vue实例对象",
      			"      new Vue({",
      			"        // el属性是固定的写法,表示当前VM实例要控制页面的哪个区域,接收的值是一个选择器",
      			"        el: '#app',",
      			"        // data对象为需要渲染到页面的数据",
      			"        data: {",
      			"          message1: 'Hello Vue.js来了!',",
      			"          message2: 'This is my first project!',",
      			"        },",
      			"      });",
      			"    </script>",
      			"  </body>",
      			"</html>",
      			"",
      		],
      		"description": "A Vue2.x file template.",
      	}
      }
      

    解析:

    • data指向的对象,就是Model数据源。
    • el指向的选择器,就是View视图区域。
    • new Vue()构造函数,得到的vm实例对象,就是ViewModel。

    Vue的指令

    指令(Directives)是 vue 为开发者提供的模板语法,用于辅助开发者渲染页面的基本结构。

    vue 中的指令按照不同的用途可以分为如下 6 大类:

    1. 内容渲染指令
    2. 属性绑定指令
    3. 事件绑定指令
    4. 双向绑定指令
    5. 条件渲染指令
    6. 列表渲染指令

    注意:指令是 vue 开发中最基础、最常用、最简单的知识点。

    内容渲染指令

    内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下 3 个:

    • v-text
    • {{ }}
    • v-html

    说明:

    1. v-text 指令的缺点:会覆盖元素内部原有的内容!
    2. {{ }} 插值表达式:在实际开发中用的最多,只是内容的占位符,不会覆盖原有的内容!
    3. 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>
    
    <body>
      <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
      <div id="app">
        <p v-text="username"></p>
        <p v-text="gender">性别:</p>
    
        <hr>
    
        <p>姓名:{{ username }}</p>
        <p>性别:{{ gender }}</p>
    
        <hr>
    
        <div v-text="info"></div>
        <div>{{ info }}</div>
        <div v-html="info"></div>
      </div>
    
      <!-- 1. 导入 Vue 的库文件,在 window 全局就有了 Vue 这个构造函数 -->
      <script src="./lib/vue-2.6.12.js"></script>
      <!-- 2. 创建 Vue 的实例对象 -->
      <script>
        // 创建 Vue 的实例对象
        const vm = new Vue({
          // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
          el: '#app',
          // data 对象就是要渲染到页面上的数据
          data: {
            username: 'zhangsan',
            gender: '女',
            info: '<h4 style="color: red; font-weight: bold;">欢迎大家来学习 vue.js</h4>'
          }
        })
      </script>
    </body>
    
    </html>
    

    v-text

    <!-- 把username对应的值,渲染到第一个p标签中 -->
    <p v-text="username"></p>
    
    <!-- 把gender对应的值,渲染到第二个p标签中 -->
    <!-- 注意:第二个p标签中,默认的文本“性别”会被gender的值覆盖掉 -->
    <p v-text="gender">性别:</p>
    

    注意:v-text 指令会覆盖元素内默认的值。

    {{ }} 语法

    vue 提供的 {{ }} 语法,专门用来解决 v-text 会覆盖默认文本内容的问题。这种 {{ }} 语法的专业名称是插值表达式(英文名为:Mustache)。

    <!-- 使用{{}}插值表达式,将对应的值渲染到元素的内容节点中 -->
    <!-- 同时保留元素自身的默认值 -->
    <p>姓名:{{username}}</p>
    <p>性别:{{gender}}</p>
    

    注意:相对于 v-text 指令来说,插值表达式在开发中更常用一些!因为它不会覆盖元素中默认的文本内容。

    v-html

    v-text 指令和插值表达式只能渲染纯文本内容。如果要把包含 HTML 标签的字符串渲染为页面的 HTML 元素, 则需要用到 v-html 这个指令:

    <!-- 假设data中定义了名为info的数据,数据的值为包含HTML标签的字符串 -->
    <!-- '<h4 style="color: red; font-weight: bold;">欢迎大家来学习 vue.js</h4>' -->
    <div v-html="info"></div>
    

    属性绑定指令(v-bind/:)

    如果需要为元素的属性动态绑定属性值,则需要用到 v-bind 属性绑定指令。

    注意:插值表达式只能用在元素的内容节点中,不能用在元素的属性节点中!

    • 在 vue 中,可以使用 v-bind: 指令,为元素的属性动态绑定值;

    • 简写是英文的 :

    • 在使用 v-bind 属性绑定期间,如果绑定内容需要进行动态拼接,则字符串的外面应该包裹单引号,例如:

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8" />
          <title>My test page</title>
          <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        </head>
      
        <body>
          <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
          <div id="app">
            <input type="text" :placeholder="tips" />
            <hr />
            <!-- vue 规定 v-bind: 指令可以简写为 : -->
            <img :src="photo" alt="" style=" 150px" />
      
            <hr />
            <div>1 + 2 的结果是:{{ 1 + 2 }}</div> <!-- 1 + 2 的结果是:3 -->>
            <div>
              {{ tips }} 反转的结果是:{{ tips.split('').reverse().join('') }} <!-- 请输入用户名 反转的结果是:名户用入输请 -->>
            </div>
            <div :title="'box' + index">这是一个 div</div>
            <a :href="url">百度一下</a>
          </div>
      
          <!-- 2. 创建 Vue 的实例对象 -->
          <script>
            // 创建 Vue 的实例对象
            const vm = new Vue({
              // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
              el: "#app",
              // data 对象就是要渲染到页面上的数据
              data: {
                tips: "请输入用户名",
                photo: "https://cn.vuejs.org/images/logo.svg",
                index: 3,
                url: "www.baidu.com",
              },
            });
          </script>
        </body>
      </html>
      

    使用 Javascript 表达式

    在 vue 提供的模板渲染语法中,除了支持绑定简单的数据值之外,还支持 Javascript 表达式的运算,例如:

    {{ number + 1 }}
    {{ message.split('').reverse().join('') }}
    {{ ok ? 'YES' : 'NO' }}
    
    <div v-bind:id>"'list-' + id"</div>
    

    Class与Style绑定

    绑定class

    1. 直接绑定方式
    2. 绑定数组方式
    3. 绑定对象方式

    举例

    • 方式一:直接绑定方式

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8" />
          <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
          <meta http-equiv="Content-Language" content="zh-cn" />
          <title>My test page</title>
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      
          <style>
            .class-active {
              color: red;
            }
            .class-font-size {
              font-size: 75px;
            }
          </style>
        </head>
      
        <body>
          <div id="app">
            <div
              v-bind:class="{'class-active':isActive, 'class-font-size':isFontSet}"
            >
              我是一些文字的内容
            </div>
          </div>
      
          <script>
            var app = new Vue({
              el: "#app",
              data: {
                isActive: true,
                isFontSet: false,
              },
            });
          </script>
        </body>
      </html>
      
      
    • 方式二(1):绑定数组方式

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8" />
          <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
          <meta http-equiv="Content-Language" content="zh-cn" />
          <title>My test page</title>
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      
          <style>
            .class-active {
              color: red;
            }
      
            .class-font-size {
              font-size: 75px;
            }
          </style>
        </head>
      
        <body>
          <div id="app">
            <div v-bind:class="className">我是一些文字的内容</div>
          </div>
      
          <script>
            var app = new Vue({
              el: "#app",
              data: {
                className: ["class-active", "class-font-size"],
              },
            });
          </script>
        </body>
      </html>
      
      
    • 方式二(2):绑定数组方式

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8" />
          <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
          <meta http-equiv="Content-Language" content="zh-cn" />
          <title>My test page</title>
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      
          <style>
            .class-active {
              color: red;
            }
      
            .class-font-size {
              font-size: 75px;
            }
          </style>
        </head>
      
        <body>
          <div id="app">
            <div v-bind:class="[classActive, classFontSize]">我是一些文字的内容</div>
            <!-- classActive 和 classFontSize 都是变量 -->
          </div>
      
          <script>
            var app = new Vue({
              el: "#app",
              data: {
                classActive: "class-active",
                classFontSize: "class-font-size",
              },
            });
          </script>
        </body>
      </html>
      
      
    • 方式三:绑定对象方式

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8" />
          <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
          <meta http-equiv="Content-Language" content="zh-cn" />
          <title>My test page</title>
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      
          <style>
            .class-active {
              color: red;
            }
      
            .class-font-size {
              font-size: 75px;
            }
          </style>
        </head>
      
        <body>
          <div id="app">
            <div v-bind:class="classObject">我是一些文字的内容</div>
          </div>
      
          <script>
            var app = new Vue({
              el: "#app",
              data: {
                classObject: {
                  "class-active": true,
                  "class-font-size": true,
                },
              },
            });
          </script>
        </body>
      </html>
      
      

    绑定内联样式

    • 方式一(1):对象方式

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8" />
          <meta http-equiv="Content-Type" content="text/html; charset=gb2312" />
          <meta http-equiv="Content-Language" content="zh-cn" />
          <title>My test page</title>
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        </head>
      
        <body>
          <div id="app">
            <div v-bind:style="{color: theColor, 'font-size': fontSize + 'px'}">
              我是一些文字的内容
            </div>
          </div>
      
          <script>
            var app = new Vue({
              el: "#app",
              data: {
                theColor: "red",
                fontSize: 40,
              },
            });
          </script>
        </body>
      </html>
      
      
    • 方式一(2):对象方式

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8" />
          <title>My test page</title>
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        </head>
      
        <body>
          <div id="app">
            <div :style="styleObj" @click="handleDivClick">我是一些文字的内容</div>
          </div>
      
          <script>
            var app = new Vue({
              el: "#app",
              data: {
                styleObj: {
                  color: "black",
                  "font-size": "35px",
                },
              },
              methods: {
                handleDivClick: function () {
                  this.styleObj.color =
                    this.styleObj.color === "black" ? "red" : "black";
                },
              },
            });
          </script>
        </body>
      </html>
      
      
    • 方式二:数组方式

      <!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8" />
          <title>My test page</title>
          <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        </head>
      
        <body>
          <div id="app">
            <div :style="[styleObj, {'font-size': '35px'}]" @click="handleDivClick">
              我是一些文字的内容
            </div>
          </div>
      
          <script>
            var app = new Vue({
              el: "#app",
              data: {
                styleObj: {
                  color: "black",
                },
              },
              methods: {
                handleDivClick: function () {
                  this.styleObj.color =
                    this.styleObj.color === "black" ? "red" : "black";
                },
              },
            });
          </script>
        </body>
      </html>
      
      

    事件绑定指令(v-on/@)

    vue 提供了 v-on 事件绑定指令,用来辅助程序员为 DOM 元素绑定事件监听。

    通过 v-on 绑定的事件处理函数,需要在 methods 节点中进行声明。

    由于 v-on 指令在开发中使用频率非常高,因此,vue 官方为其提供了简写形式(简写为英文的 @ )。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <p>count 的值是:{{ count }}</p>
          <!-- 在绑定事件处理函数的时候,可以使用 () 传递参数 -->
          <!-- v-on: 指令可以被简写为 @ -->
          <button @click="add(1)">+1</button>
          <button @click="sub">-1</button>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              count: 0,
            },
            // methods 的作用,就是定义事件的处理函数
            methods: {
              add(n) {
                // 在 methods 处理函数中,this 就是 new 出来的 vm 实例对象
                // console.log(vm === this)
                console.log(vm);
                // vm.count += 1
                this.count += n;
              },
              sub() {
                // console.log('触发了 sub 处理函数')
                this.count -= 1;
              },
            },
          });
        </script>
      </body>
    </html>
    

    注意:原生 DOM 对象有 onclick、oninput、onkeyup 等原生事件,替换为 vue 的事件绑定形式后, 分别为:v-on:click、v-on:input、v-on:keyup

    事件参数对象

    在原生的 DOM 事件绑定中,可以在事件处理函数的形参处,接收事件参数对象 event。

    同理,在 v-on 指令 (简写为 @ )所绑定的事件处理函数中,同样可以接收到事件参数对象 event,

    绑定事件并传参

    在使用 v-on 指令绑定事件时,可以使用 ( ) 进行传参。

    事件对象$event

    $event 是 vue 提供的特殊变量,用来表示原生的事件参数对象 event。$event 可以解决事件参数对象 event 被覆盖的问题。

    $event 的应用场景:如果默认的事件对象 e 被覆盖了,则可以手动传递一个$event。

    需求:单机button时候,count数值+1,当count数值为偶数时候,改变button的背景颜色。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <p>count 的值是:{{ count }}</p>
          <!-- 在绑定事件处理函数的时候,可以使用 () 传递参数 -->
          <!-- v-on: 指令可以被简写为 @ -->
          <!-- vue 提供了内置变量,名字叫做 $event,它就是原生 DOM 的事件对象 e -->
          <button @click="add($event, 1)">+1</button>
          <!-- 未传递参数时,默认接收到参数e -->
          <button @click="sub">-1</button>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              count: 0,
            },
            // methods 的作用,就是定义事件的处理函数
            methods: {
              add(e, n) {
                // 在 methods 处理函数中,this 就是 new 出来的 vm 实例对象
                // console.log(vm === this)
                console.log(vm);
                // vm.count += 1;
                this.count += n;
                e.target.style.backgroundColor = this.count % 2 === 0 ? "blue" : "";
              },
              sub(e) {
                // console.log('触发了 sub 处理函数')
                console.log(e);
                this.count -= 1;
                e.target.style.backgroundColor = this.count % 2 === 0 ? "red" : "";
              },
            },
          });
        </script>
      </body>
    </html>
    

    事件修饰符

    在事件处理函数中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。

    因此, vue 提供了事件修饰符的概念,来辅助程序员更方便的对事件的触发进行控制。

    常用的 5 个事件修饰符如下:

    事件修饰符 说明
    .prevent 阻止默认行为(例如:阻止 a 连接的跳转、阻止表单的提交等)
    .stop 阻止事件冒泡
    .capture 以捕获模式触发当前的事件处理函数
    .once 绑定的事件只触发1次
    .self 只有在 event.target 是当前元素自身时触发事件处理函数

    语法格式如下:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <a href="https://www.baidu.com" @click.prevent="show">跳转到百度首页</a>
          <hr />
          <div
            style="
              height: 150px;
              background-color: orange;
              padding-left: 100px;
              line-height: 150px;
            "
            @click="divHandler"
          >
            <button @click.stop="btnHandler">按钮</button>
          </div>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {},
            // methods 的作用,就是定义事件的处理函数
            methods: {
              show() {
                console.log("单击了a链接");
              },
              divHandler() {
                console.log("divHandler");
              },
              btnHandler() {
                console.log("btnHandler");
              },
            },
          });
        </script>
      </body>
    </html>
    

    按键修饰符

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

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <input type="text" @keyup.esc="clearInput" @keyup.enter="commitAjax">
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {},
            // methods 的作用,就是定义事件的处理函数
            methods: {
              clearInput(e) {
                console.log('触发了clearInput方法');
                e.target.value = '';
              },
              commitAjax() {
                console.log('触发了commitAjax方法');
              },
            },
          });
        </script>
      </body>
    </html>
    

    示例2(按键修饰符、系统修饰健、鼠标按钮修饰符)

    <!DOCTYPE html>
    <html>
    
    <head>
        <meta charset="utf-8">
        <title>My test page</title>
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    </head>
    
    <body>
        <div id="app">
            <!-- 1、按键修饰符 -->
            <!-- Vue 允许为 v-on 在监听键盘事件时添加按键修饰符 -->
    
            <!-- 只有在 `key` 是 `Enter` 时调用 `vm.submit()` -->
            <input v-on:keyup.enter="submit1">
    
            <!-- 可以直接将 KeyboardEvent.key 暴露的任意有效按键名转换为 kebab-case 来作为修饰符 -->
            <!-- 示例中,处理函数只会在 $event.key 等于 PageDown 时被调用 -->
            <input v-on:keyup.page-down="onPageDown">
    
            <!-- **按键码,keyCode 的事件用法已经被废弃了并可能不会被最新的浏览器支持** -->
            <input v-on:keyup.13="submit2">
    
            <!-- 按键码 -->
            <!-- .enter
                 .tab
                 .delete (捕获“删除”和“退格”键)
                 .esc
                 .space
                 .up
                 .down
                 .left
                 .right -->
    
            <!-- 2、系统修饰键 -->
            <!-- 2.1.0 新增 -->
            <!-- .ctrl
                 .alt
                 .shift
                 .meta -->
    
            <!-- Alt + C -->
            <input @keyup.alt.67="clear">
    
            <!-- Ctrl + Click -->
            <div @click.ctrl="doSomething">Do something</div>
    
            <!-- 请注意修饰键与常规按键不同,在和 keyup 事件一起用时,事件触发时修饰键必须处于按下状态。
                 换句话说,只有在按住 ctrl 的情况下释放其它按键,才能触发 keyup.ctrl。
                 而单单释放 ctrl 也不会触发事件。如果你想要这样的行为,请为 ctrl 换用 keyCode:keyup.17 -->
    
            <!-- 2.5.0 新增 -->
            <!-- .exact 修饰符允许你控制由精确的系统修饰符组合触发的事件。 -->
            <!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
            <button @click.ctrl="onClick1">A</button>
    
            <!-- 有且只有 Ctrl 被按下的时候才触发 -->
            <button @click.ctrl.exact="onCtrlClick">A</button>
    
            <!-- 没有任何系统修饰符被按下的时候才触发 -->
            <button @click.exact="onClick2">A</button>
    
            <!-- 3、鼠标按钮修饰符 -->
            <!-- 2.2.0 新增 -->
            <!-- .left
                 .right
                 .middle -->
            <button @click.left="onClickLeft">Left</button>
            <button @click.right="onClickRight">Right</button>
            <button @click.middle="onClickMiddle">Middle</button>
    
        </div>
    
        <script>
            var app = new Vue({
                el: '#app',
                data: {
                    name: 'Vue.js',
                },
                computed: {
                },
                methods: {
                    submit1: function () {
                        alert("submit1");
                    },
                    onPageDown: function () {
                        alert("onPageDown");
                    },
                    submit2: function () {
                        alert("submit2");
                    },
                    clear: function () {
                        alert("clear");
                    },
                    doSomething: function () {
                        alert("doSomething");
                    },
                    onClick1: function () {
                        alert("onClick1");
                    },
                    onCtrlClick: function () {
                        alert("onCtrlClick");
                    },
                    onClick2: function () {
                        alert("onClick2");
                    },
                    onClickLeft: function () {
                        alert("onClickLeft");
                    },
                    onClickRight: function () {
                        alert("onClickRight");
                    },
                    onClickMiddle: function () {
                        alert("onClickMiddle");
                    },
                }
            })
        </script>
    </body>
    
    </html>
    

    双向绑定指令v-model

    vue 提供了 v-model 双向数据绑定指令,用来辅助开发者在不操作 DOM 的前提下,快速获取表单的数据。

    v-model其实是一个语法糖,通过props和emit组合而成。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <p>用户的名字是:{{ username }}</p>
          <input type="text" v-model="username" />
          <hr />
          <input type="text" :value="username" />
          <hr />
          <p>选中的城市是{{ city }}</p>
          <select v-model="city">
            <option value="">请选择城市</option>
            <option value="1">北京</option>
            <option value="2">上海</option>
            <option value="3">广州</option>
          </select>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              username: "ZhangSan",
              city: "2",
            },
          });
        </script>
      </body>
    </html>
    

    v-model指令的修饰符

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

    修饰符 作用 示例
    .number 自动将用户的输入值转为数值类型。 <input v-model.number="age" />
    .trim 自动过滤用户输入的首尾空白字符。 <input v-model.trim="msg" />
    .lazy 在“change”时而非“input”时更新,例如在输入框中的行为初始不会同步到数据源,直到失去鼠标焦点,才会把数据源的数据同步到数据源。 <input v-model.lazy="msg" />
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <input type="text" v-model.number="n1" /> +
          <input type="text" v-model.number="n2" /> =
          <span>{{ n1 + n2 }}</span>
          <hr />
          <input type="text" v-model.trim="username" />
          <button @click="getUserName">获取用户名</button>
          <hr />
          <input type="text" v-model.lazy="username">
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              username: "ZhangSan",
              n1: 1,
              n2: 2,
            },
            methods: {
              getUserName() {
                console.log("获取到的用户名为:'" + this.username + "'");
              },
            },
          });
        </script>
      </body>
    </html>
    

    条件渲染指令

    条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。

    条件渲染指令有如下两个,分别是:

    • v-if
    • v-show

    v-if 和 v-show 的区别

    实现原理不同:

    • v-if 指令会动态地创建或移除 DOM 元素,从而控制元素在页面上的显示与隐藏;
    • v-show 指令会动态为元素添加或移除 style="display: none;" 样式,从而控制元素的显示与隐藏;

    性能消耗不同:

    v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此:

    • 如果需要非常频繁地切换,则使用 v-show 较好
    • 如果在运行时条件很少改变,则使用 v-if 较好

    v-else

    v-if 可以单独使用,或配合 v-else 指令一起使用:

    注意:v-else 指令必须配合 v-if 指令一起使用,否则它将不会被识别!

    v-else-if

    v-else-if 指令,顾名思义,充当 v-if 的“else-if 块”,可以连续使用:

    注意:v-else-if 指令必须配合 v-if 指令一起使用,否则它将不会被识别!

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <button @click="toggleFlagValue">切换flag的值</button>
          <p v-if="flag">这是被 v-if 控制的元素</p>
          <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>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              // 如果 flag 为 true,则显示被控制的元素;如果为 false 则隐藏被控制的元素
              flag: true,
              type: "A",
            },
            methods: {
              toggleFlagValue() {
                this.flag = !this.flag;
              },
            },
          });
        </script>
      </body>
    </html>
    

    v-for列表渲染指令

    vue 提供了 v-for 列表渲染指令,用来辅助开发者基于一个数组来循环渲染一个列表结构。

    v-for 指令需要使 用 item in items 形式的特殊语法,其中:

    • items 是待循环的数组
    • item 是被循环的每一项

    v-for中的索引

    v-for 指令还支持一个可选的第二个参数,即当前项的索引。语法格式为 (item, index) in items。

    注意:v-for指令中的item项和index索引都是形参,可以根据需要进行重命名。例如(user, i) in userlist。

    使用key维护列表的状态

    当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种默认的性能优化策略,会导致有状态的列表无法被正确更新。

    为了给 vue 一个提示,以便它能跟踪每个节点的身份,从而在保证有状态的列表被正确更新的前提下,提升渲 染的性能。

    此时,需要为每项提供一个唯一的key属性。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <table class="table table-bordered table-hover table-striped">
            <thead>
              <th>索引</th>
              <th>ID</th>
              <th>姓名</th>
            </thead>
            <tbody>
              <!-- 官方建议:只要用到了 v-for 指令,那么一定要绑定一个 :key 属性 -->
              <!-- 而且,尽量把 id 作为 key 的值 -->
              <!-- 官方对 key 的值类型,是有要求的:字符串或数字类型 -->
              <!-- key 的值是千万不能重复的,否则会终端报错:Duplicate keys detected -->
              <!-- <tr v-for="(item, index) in list" :key="item.id" :title="item.name + index"> -->
              <tr v-for="(item, index) in list" :key="item.id">
                <td>{{ index }}</td>
                <td>{{ item.id }}</td>
                <td>{{ item.name }}</td>
              </tr>
            </tbody>
          </table>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              list: [
                { id: 1, name: "张三" },
                { id: 2, name: "李四" },
                { id: 3, name: "王五" },
                { id: 4, name: "张三" },
              ],
            },
          });
        </script>
      </body>
    </html>
    

    key 的注意事项

    1. key 的值只能是字符串或数字类型。
    2. key 的值必须具有唯一性(即:key 的值不能重复)。
    3. 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)。
    4. 使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性,例如在新增条目的状态下,索引不具有对应条目的唯一性)。
    5. 建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)。
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <input type="text" v-model="name" />
          <button @click="addNewUser">添加用户</button>
    
          <!-- 用户列表区域 -->
          <ul>
            <li v-for="(user, index) in userList" :id="user.id">
              <input type="checkbox" name="" id="" />姓名:{{ user.name }}
            </li>
          </ul>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              // 输入的用户名
              name: "",
              // 用户列表
              userList: [
                { id: 1, name: "ZhangSan" },
                { id: 2, name: "LiSi" },
              ],
              // 下一个可用的ID值
              nextId: 3,
            },
            methods: {
              addNewUser() {
                this.userList.unshift({ id: this.nextId, name: this.name });
                this.name = "";
                this.nextId++;
              },
            },
          });
        </script>
      </body>
    </html>
    

    Label的for属性

    for 属性规定 label 与哪个表单元素绑定。

    品牌列表案例

    添加品牌

    品牌名称:“请输入品牌名称” 【添加品牌】

    # 品牌名称 状态 创建时间 操作
    1 宝马 已启用 2020-11-03 11:03:56 删除
    2 奥迪 已禁用 2020-11-03 11:03:56 删除
    3 奔驰 已启用 2020-11-03 11:03:56 删除
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <!-- 新 Bootstrap 核心 CSS 文件 -->
        <link
          rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css"
        />
        <link rel="stylesheet" href="./css/brandlist.css" />
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <!-- 卡片区域 -->
          <div class="card">
            <div class="card-header">添加品牌</div>
            <div class="card-body">
              <!-- 添加品牌的表单区域 -->
              <!-- form 表单元素有 submit 事件 -->
              <form @submit.prevent="add">
                <div class="form-row align-items-center">
                  <div class="col-auto">
                    <div class="input-group mb-2">
                      <div class="input-group-prepend">
                        <div class="input-group-text">品牌名称</div>
                      </div>
                      <input
                        type="text"
                        class="form-control"
                        placeholder="请输入品牌名称"
                        v-model.trim="brand"
                      />
                    </div>
                  </div>
                  <div class="col-auto">
                    <button type="submit" class="btn btn-primary mb-2">添加</button>
                  </div>
                </div>
              </form>
            </div>
          </div>
    
          <!-- 表格区域 -->
          <table class="table table-bordered table-hover table-striped">
            <thead>
              <tr>
                <th scope="col">#</th>
                <th scope="col">品牌名称</th>
                <th scope="col">状态</th>
                <th scope="col">创建时间</th>
                <th scope="col">操作</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="item in list" :key="item.id">
                <td>{{ item.id }}</td>
                <td>{{ item.name }}</td>
                <td>
                  <div class="custom-control custom-switch">
                    <!-- 使用 v-model 实现双向数据绑定 -->
                    <input
                      type="checkbox"
                      class="custom-control-input"
                      :id="'cb' + item.id"
                      v-model="item.status"
                    />
                    <!-- 使用 v-if 结合 v-else 实现按需渲染 -->
                    <label
                      class="custom-control-label"
                      :for="'cb' + item.id"
                      v-if="item.status"
                    >
                      已启用
                    </label>
                    <label
                      class="custom-control-label"
                      :for="'cb' + item.id"
                      v-else
                    >
                      已禁用
                    </label>
                  </div>
                </td>
                <td>{{ item.time }}</td>
                <td>
                  <a href="javascript:;" @click="remove(item.id)">删除</a>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              // 用户输入的品牌名称
              brand: "",
              // nextId 是下一个,可用的 id
              nextId: 4,
              // 品牌列表的数据
              list: [
                { id: 1, name: "宝马", status: true, time: new Date() },
                { id: 2, name: "奔驰", status: false, time: new Date() },
                { id: 3, name: "奥迪", status: true, time: new Date() },
              ],
            },
            methods: {
              // 点击链接,删除对应的品牌信息
              remove(id) {
                this.list = this.list.filter((item) => item.id !== id);
              },
              // 阻止表单的默认提交行为之后,触发 add 方法
              add() {
                // 如果判断到 brand 的值为空字符串,则 return 出去
                if (this.brand === "") {
                  return alert("必须填写品牌名称!");
                }
    
                // 如果没有被 return 出去,应该执行添加的逻辑
                // 1. 先把要添加的品牌对象,整理出来
                const obj = {
                  id: this.nextId,
                  name: this.brand,
                  status: true,
                  time: new Date(),
                };
                // 2. 往 this.list 数组中 push 步骤 1 中得到的对象
                this.list.push(obj);
                // 3. 清空 this.brand;让 this.nextId 自增 +1
                this.brand = "";
                this.nextId++;
              },
            },
          });
        </script>
      </body>
    </html>
    

    Vue过滤器

    Vue过滤器适用于Vue2.x,而Vue3.x不再支持该功能。

    过滤器定义

    过滤器可以用在两个地方:插值表达式v-bind 属性绑定。

    过滤器应该被添加在 JavaScript 表达式的尾部,由“管道符”进行调用。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <p>message的值是:{{ message | capi }}</p>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              message: "hello, vue.js.",
            },
            // 过滤器函数,必须被定义到filters节点之下
            // 过滤器本质上是函数
            // 在过滤器的形参中就可以获取到管道符前面的值
            filters: {
              capi(val) {
                console.log(val);
                const firstChar = val.charAt(0).toUpperCase();
                const otherChar = val.slice(1);
                // 强调:过滤器中,一定要有返回值
                // 过滤器函数形参中的val一定是过滤器前面的值
                return firstChar + otherChar;
              },
            },
          });
        </script>
      </body>
    </html>
    

    私有过滤器和全局过滤器

    在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前 vm 实例所控制的 el 区域内使用。 如果希望在多个 vue 实例之间共享过滤器,则可以按照如下的格式定义全局过滤器:

    注意:如果全局过滤器和私有过滤器名字一致,此时按照“就近原则”,调用的是“私有过滤器”。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app1">
          <p>message的值是:{{ message | capi }}</p>
        </div>
    
        <div id="app2">
          <p>message的值是:{{ message | capi }}</p>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 使用Vue.filter()定义全局过滤器
          Vue.filter("capi", (val) => {
            console.log(val);
            const firstChar = val.charAt(0).toUpperCase();
            const otherChar = val.slice(1);
            // 强调:过滤器中,一定要有返回值
            // 过滤器函数形参中的val一定是过滤器前面的值
            return firstChar + otherChar;
          });
    
          // 创建 Vue 的实例对象
          const vm1 = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app1",
            // data 对象就是要渲染到页面上的数据
            data: {
              message: "hello, vue.js.",
            },
          });
    
          // 创建 Vue 的实例对象
          const vm2 = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app2",
            // data 对象就是要渲染到页面上的数据
            data: {
              message: "hello, world.",
            },
          });
        </script>
      </body>
    </html>
    

    连续调用多个过滤器

    过滤器可以串联地进行调用。

    过滤器传参

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <p>
            message的值是:{{ message | capitalize("参数1", "参数2") | maxLength }}
          </p>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 全局过滤器,使用Vue.filter()定义全局过滤器
          Vue.filter("capitalize", (val, arg1, arg2) => {
            console.log(
              "filter of capitalize: " + val + ", arg1: " + arg1 + ", arg2: " + arg2
            );
            const firstChar = val.charAt(0).toUpperCase();
            const otherChar = val.slice(1);
            // 强调:过滤器中,一定要有返回值
            // 过滤器函数形参中的val一定是过滤器前面的值
            return firstChar + otherChar;
          });
    
          // 全局过滤器,使用Vue.filter()定义全局过滤器
          // 控制文本的最大长度
          Vue.filter("maxLength", (val) => {
            console.log("filter of maxLength: " + val);
            if (val.length <= 0) {
              return val;
            }
            // 强调:过滤器中,一定要有返回值
            // 过滤器函数形参中的val一定是过滤器前面的值
            return val.slice(0, 11) + "...";
          });
    
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              message: "hello, vue.js. hello, vue.js.",
            },
          });
        </script>
      </body>
    </html>
    

    过滤器的兼容性

    过滤器仅在 vue 2.x 和 1.x 中受支持,在 vue 3.x 的版本中剔除了过滤器相关的功能。

    在企业级项目开发中:

    • 如果使用的是 2.x 版本的 vue,则依然可以使用过滤器相关的功能。
    • 如果项目已经升级到了 3.x 版本的 vue,官方建议使用计算属性或方法代替被剔除的过滤器功能。

    具体的迁移指南,请参考 vue 3.x 的官方文档给出的说明。

    Vue侦听器

    什么是侦听器

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

    • 方法格式的侦听器:
      • 缺点1:无法在刚进入页面的时候,自动触发。
      • 缺点2:如果侦听的是一个对象,如果对象中的属性发生了变化,不会触发侦听器
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <input type="text" v-model="username" />
          <p>用户名:{{username}}</p>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              username: "",
            },
            // 所有的侦听器,都应该被定义到watch节点下
            watch: {
              // 侦听器本质上是一个函数,要监视那个数据的变化,就把数据名作为方法名即可
              // 新值在前,旧值在后
              username(newVal, oldVal) {
                console.log(
                  `监听到了username的变化, newVal: ${newVal}, oldVal: ${oldVal}`
                );
              },
            },
          });
        </script>
      </body>
    </html>
    

    判断用户是否被占用

    // 监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用:
    watch: {
        // 监听 username 值的变化
        async username(newVal) {
            if (newVal === '') return;
            // 使用 axios 发起请求,判断用户名是否可用
            const { data: res } = await axios.get('https://www.escook.cn/api/finduser/' + newVal);
            console.log(res);
        }
    }
    

    侦听器-immediate选项

    默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器立即被调用,则需要使 用 immediate 选项。

    对象格式的侦听器:可以通过immediate选项,在刚进入页面的时候,让侦听器自动触发一次。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <input type="text" v-model="username" />
          <p>用户名:{{username}}</p>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              username: "ZhangSan",
            },
            // 所有的侦听器,都应该被定义到watch节点下
            watch: {
              // 侦听器本质上是一个函数,要监视那个数据的变化,就把数据名作为方法名即可
              // 新值在前,旧值在后
              username: {
                handler(newVal, oldVal) {
                  console.log(
                    `监听到了username的变化, newVal: ${newVal}, oldVal: ${oldVal}`
                  );
                },
                // immediate选项的默认值是false
                // immediate的作用是:控制侦听器是否自动触发一次
                immediate: true,
              },
            },
          });
        </script>
      </body>
    </html>
    

    侦听器-deep选项(深度监听,监听对象属性)

    对象格式的侦听器:可以通过deep选项,让侦听器深度监听对象中每个属性的变化。

    方式一:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <input type="text" v-model="info.username" />
          <p>用户名:{{info.username}}</p>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              info: {
                username: "ZhangSan",
              },
            },
            // 所有的侦听器,都应该被定义到watch节点下
            watch: {
              info: {
                handler(newVal) {
                  console.log(`监听到了username的变化, newVal: ${newVal.username}`);
                },
                // 开启深度监听,只要对象中任何一个属性变化了,都会触发对象侦听器
                deep: true,
              },
            },
          });
        </script>
      </body>
    </html>
    

    方式二:直接侦听子属性的变化

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <div id="app">
          <input type="text" v-model="info.username" />
          <p>用户名:{{info.username}}</p>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 的实例对象
          const vm = new Vue({
            // el 属性是固定的写法,表示当前 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",
            // data 对象就是要渲染到页面上的数据
            data: {
              info: {
                username: "ZhangSan",
              },
            },
            // 所有的侦听器,都应该被定义到watch节点下
            watch: {
              // 如果要侦听的对象的子属性的变化,则必须包裹一层单引号
              "info.username": {
                handler(newVal, oldVal) {
                  console.log(
                    `监听到了username的变化, newVal: ${newVal}, oldVal: ${oldVal}`
                  );
                },
              },
            },
          });
        </script>
      </body>
    </html>
    

    Vue计算属性

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

    这个动态计算出来的属性值可以被模板结构或 methods 方法使用。

    计算属性的特点

    1. 虽然计算属性在声明的时候被定义为方法,但是计算属性的本质是一个属性。
    2. 计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行运算。

    优点

    1. 实现了代码的复用。
    2. 只要计算属性中依赖的数据源变化了,则计算属性会自动重新求值。

    原始代码:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <style>
          .box {
             200px;
            height: 200px;
            border: 1px solid #ccc;
          }
        </style>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <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 盒子 -->
          <div class="box" :style="{ backgroundColor: `rgb(${r}, ${g}, ${b})` }">
            {{ `rgb(${r}, ${g}, ${b})` }}
          </div>
          <button @click="show">按钮</button>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 实例,得到 ViewModel
          var 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>
    

    计算属性改造案例,优化之后的代码:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <style>
          .box {
             200px;
            height: 200px;
            border: 1px solid #ccc;
          }
        </style>
      </head>
    
      <body>
        <!-- 希望 Vue 能够控制下面的这个 div,帮我们在把数据填充到 div 内部 -->
        <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 盒子 -->
          <div class="box" :style="{ backgroundColor: rgb }">{{ rgb }}</div>
          <button @click="show">按钮</button>
        </div>
    
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 实例,得到 ViewModel
          var vm = new Vue({
            el: "#app",
            data: {
              // 红色
              r: 0,
              // 绿色
              g: 0,
              // 蓝色
              b: 0,
            },
            // 所有的计算属性都要定义到computed中
            // computed属性要定义程方法格式
            computed: {
              // rgb作为一个计算属性,被定义成了方法格式
              // 最终,在这个方法中,要返回一个生成好的rgb(x, x, x)的字符串
              rgb() {
                return `rgb(${this.r}, ${this.g}, ${this.b})`;
              },
            },
            methods: {
              // 点击按钮,在终端显示最新的颜色
              show() {
                console.log(this.rgb);
              },
            },
          });
        </script>
      </body>
    </html>
    

    axios

    说明:

    axios专注于网络请求的库。

    参考的请求URL,可以从F12找到一个网络请求即可(通过Open in new tab)。

    axios基本使用

    在Vue、React中都会用到axios来请求数据。

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
      </head>
    
      <body>
        <script>
          // http://www.liulongbin.top:3006/api/getbooks
    
          // 1. 调用 axios 方法得到的返回值是 Promise 对象
          axios({
            // 请求方式
            method: "GET",
            // 请求的地址
            url: "http://www.liulongbin.top:3006/api/getbooks",
            // URL 中的查询参数(GET请求)
            params: {
              id: 1,
            },
            // 请求体参数(PUT请求)
            data: {},
          }).then(function (result) {
            console.log(result);
          });
        </script>
      </body>
    </html>
    

    axios发起POST请求

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
      </head>
    
      <body>
        <button id="btnPOST">发起POST请求</button>
        <button id="btnGET">发起GET请求</button>
    
        <script>
          document
            .querySelector("#btnPOST")
            .addEventListener("click", async function () {
              // 如果调用某个方法的返回值是 Promise 实例,则前面可以添加 await!
              // await 只能用在被 async “修饰”的方法中
              const { data } = await axios({
                method: "POST",
                url: "http://www.liulongbin.top:3006/api/post",
                data: {
                  name: "zs",
                  age: 20,
                },
              });
    
              console.log(data);
            });
    
          document
            .querySelector("#btnGET")
            .addEventListener("click", async function () {
              // 解构赋值的时候,使用 : 进行重命名
              // 1. 调用 axios 之后,使用 async/await 进行简化
              // 2. 使用解构赋值,从 axios 封装的大对象中,把 data 属性解构出来
              // 3. 把解构出来的 data 属性,使用 冒号 进行重命名,一般都重命名为 { data: res }
              const { data: res } = await axios({
                method: "GET",
                url: "http://www.liulongbin.top:3006/api/getbooks",
              });
    
              console.log(res.data);
            });
    
          // $.ajax()   $.get()  $.post()
          // axios()    axios.get()    axios.post()    axios.delete()   axios.put()
        </script>
      </body>
    </html>
    

    axios直接发起GET和POST请求(推荐)

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
      </head>
    
      <body>
        <button id="btnPOST">发起POST请求</button>
        <button id="btnGET">发起GET请求</button>
    
        <script>
          document
            .querySelector("#btnGET")
            .addEventListener("click", async function () {
              // axios.get("url地址", {
              //   // GET 参数
              //   params: {},
              // });
              const { data: res } = await axios.get(
                "http://www.liulongbin.top:3006/api/getbooks",
                {
                  params: { id: 1 },
                }
              );
              console.log(res);
            });
    
          document
            .querySelector("#btnPOST")
            .addEventListener("click", async function () {
              // axios.post('url', { /* POST 请求体数据 */ })
              const { data: res } = await axios.post(
                "http://www.liulongbin.top:3006/api/post",
                { name: "zs", gender: "女" }
              );
              console.log(res);
            });
        </script>
      </body>
    </html>
    

    Vue CLI中axios的使用

    建议先看完Vue CLI,再看本章节内容。

    axios的基本用法

    • 参考:“VUE CLI > 新建一个工程(基础工程)”

    新建一个工程,包含一个App.vue根组件和CompLeft.vue、Compight.vue两个子组件。

    1. 安装axios
    npm install axios --save
    
    1. 在CompLeft.vue组件中发起GET请求。

      <template>
        <div class="left-container">
          <h3>Left组件</h3>
          <button @click="getInfo">发起GET请求</button>
        </div>
      </template>
      
      <script>
      import axios from 'axios'
      
      export default {
        methods: {
          async getInfo() {
            const { data: res } = await axios.get(
              "http://liulongbin.top:3006/api/get"
            );
            console.log("GET request res: " + JSON.stringify(res));
          },
        },
      };
      </script>
      
      <style lang="less" scoped>
      .left-container {
        background-color: lightgreen;
        min-height: 200px;
        flex: 1;
      }
      </style>
      
      
    2. 在Compight.vue组件中发起POST请求。

      <template>
        <div class="right-container">
          <h3>Right组件</h3>
          <button @click="postInfo">发起POST请求</button>
        </div>
      </template>
      
      <script>
      import axios from 'axios'
      
      export default {
          methods: {
          async postInfo() {
            const { data: res } = await axios.post(
              "http://liulongbin.top:3006/api/post"
            );
            console.log("POST request res: " + JSON.stringify(res));
          },
        },
      };
      </script>
      
      <style lang="less" scoped>
      .right-container {
        background-color: lightcoral;
        min-height: 200px;
        flex: 1;
      }
      </style>
      
      
    3. App.vue

      <template>
        <div id="app">
          <h1>App根组件</h1>
          <div class="box">
            <Left></Left>
            <right></right>
          </div>
        </div>
      </template>
      
      <script>
      import Left from "@/components/CompLeft.vue";
      import Right from "@/components/CompRight.vue";
      
      export default {
        name: "App",
        components: {
          Left,
          Right,
        },
      };
      </script>
      
      <style lang="less" scoped>
      .box {
        display: flex;
      }
      </style>
      
      

    把axios挂载到Vue原型上并配置请求根路径

    改装以上工程。

    1. 在main.js中,把axios中挂载到Vue原型上,自定义一个属性:$http,并给axios配置一个默认的URL根路径。

      import Vue from 'vue'
      import App from './App.vue'
      import axios from 'axios'
      
      Vue.config.productionTip = false
      
      // 全局配置axios的请求根路径
      axios.defaults.baseURL = 'http://liulongbin.top:3006'
      // 把axios挂载到Vue.prototype上,供每个.vue组件的实例直接使用
      // 后期在每个.vue组件要发起请求,直接调用this.$http.get / this.$http.post即可
      //  但是,把axios挂载到Vue原型上,无法实现API接口的复用。
      //  即如果某个API在多个组件中都有使用,需要在每个组件中都需要重新调用该API接口。
      Vue.prototype.$http = axios
      
      new Vue({
        render: h => h(App)
      }).$mount('#app')
      
      
    2. CompLeft.vue组件修改

      <template>
        <div class="left-container">
          <h3>Left组件</h3>
          <button @click="getInfo">发起GET请求</button>
        </div>
      </template>
      
      <script>
      export default {
        methods: {
          async getInfo() {
            const { data: res } = await this.$http.get("/api/get");
            console.log("GET request res: " + JSON.stringify(res));
          },
        },
      };
      </script>
      
      <style lang="less" scoped>
      .left-container {
        background-color: lightgreen;
        min-height: 200px;
        flex: 1;
      }
      </style>
      
      
    3. CompRight.vue组件修改

      <template>
        <div class="right-container">
          <h3>Right组件</h3>
          <button @click="postInfo">发起POST请求</button>
        </div>
      </template>
      
      <script>
      export default {
        methods: {
          async postInfo() {
            const { data: res } = await this.$http.post("/api/post");
            console.log("POST request res: " + JSON.stringify(res));
          },
        },
      };
      </script>
      
      <style lang="less" scoped>
      .right-container {
        background-color: lightcoral;
        min-height: 200px;
        flex: 1;
      }
      </style>
      
      

    Vue CLI

    单页面应用程序

    Single Page Application,顾名思义,指的是一个Web网站中只有唯一一个HTML页面,所有的功能与交互都在这唯一的一个页面内完成。

    什么是vue-cli

    vue-cli 是 Vue.js 开发的标准工具。

    它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程。

    引用自 vue-cli 官网上的一句话: 程序员可以专注在撰写应用上,而不必花好几天去纠结 webpack 配置的问题。

    安装和使用

    vue-cli 是 npm 上的一个全局包,使用 npm install 命令,即可方便的把它安装到自己的电脑上:

    npm install -g @vue/cli
    

    安装完成后,执行以下命令查看是否安装成功。

    vue -V
    
    # 返回结果示例
    @vue/cli 5.0.1
    

    在XX目录/文件夹中,基于 vue-cli 快速生成工程化的 Vue 项目:

    vue create 项目名称
    

    新建一个Vue CLI工程 *

    新建Vue CLI工程(基础工程)【模板】

    1. 执行命令,新建一个工程

      vue create demo-1
      
    2. 手动配置工程

      # Manually select features
      # 新增勾选“CSS Pre-processors”
      # 以2.x为例,选择“2.x”
      # 选择“less”CSS预处理器
      # 选择“ESLint with error prevention only”或者“ESLint + Standard config”,根据需要选择
      # 最后咨询是否保存为模板,这里选择“N”
      

    新建Vue CLI工程(带路由工程)【模板】

    说明:建议看完路由章节内容,再关注本章节,带路由的工程创建。

    1. 执行命令,新建一个工程

      vue create demo-1
      
    2. 手动配置工程

      # Manually select features
      # 新增勾选“Router”和“CSS Pre-processors”
      # 以2.x为例,选择“2.x”
      # 选择“less”CSS预处理器
      # 选择“ESLint with error prevention only”或者“ESLint + Standard config”,根据需要选择
      # 最后咨询是否保存为模板,这里选择“N”
      

    说明:与路由切换相关的组件,建议放到views文件夹中。反之,建议放到components文件夹中。

    新建两个子组件

    1. 新建组件CompLeft.vue组件内容:

      <template>
        <div class="left-container">
          <h3>Left组件</h3>
        </div>
      </template>
      
      <script>
      export default {};
      </script>
      
      <style lang="less" scoped>
      .left-container {
        background-color: lightgreen;
        min-height: 200px;
        flex: 1;
      }
      </style>
      
      
    2. 新建组件CompRight.vue组件内容:

      <template>
        <div class="right-container">
          <h3>Right组件</h3>
        </div>
      </template>
      
      <script>
      export default {};
      </script>
      
      <style lang="less" scoped>
      .right-container {
        background-color: lightcoral;
        min-height: 200px;
        flex: 1;
      }
      </style>
      
      
    3. 删除components文件夹中的HelloWorld.vue组件。

    4. 修改App.vue组件内容:

      <template>
        <div id="app">
          <h1>App根组件</h1>
          <div class="box">
            <Left></Left>
            <right></right>
          </div>
        </div>
      </template>
      
      <script>
      import Left from "@/components/CompLeft.vue";
      import Right from "@/components/CompRight.vue";
      
      export default {
        name: "App",
        components: {
          Left,
          Right,
        },
      };
      </script>
      
      <style lang="less" scoped>
      .box {
        display: flex;
      }
      </style>
      
      

    说明:如需要关闭ESLint,可以在vue.config.js文件中增加以下一条语句:

    lintOnSave: false // 关闭ESLint检查
    

    Vue项目的运行流程

    在工程化的项目中,vue 要做的事情很单纯:通过 main.js 把 App.vue 渲染到 index.html 的指定区域中。

    其中:

    1. App.vue 用来编写待渲染的模板结构
    2. index.html 中需要预留一个 el 区域
    3. main.js 把 App.vue 渲染到了 index.html 所预留的区域中

    目录结构

    │  .gitignore
    │  babel.config.js				# Babel插件配置文件
    │  jsconfig.json
    │  package-lock.json
    │  package.json
    │  README.md
    │  vue.config.js
    │
    ├─node_modules					# 依赖包目录
    ├─public
    │      favicon.ico
    │      index.html				# 主页,生成的文件会被注入到此文件中
    │
    └─src							# 源代码目录
        │  App.vue					# 项目的根组件
        │  main.js					# 项目的入口文件,整个项目的运行,要先执行main.js
        │
        ├─assets					# 静态资源目录,例如图片资源、CSS样式表
        │      logo.png
        │
        └─components				# 组件,封装的、可复用的组件
                HelloWorld.vue
    

    main.js文件解析

    // 导入Vue包,得到Vue构造函数
    import Vue from 'vue'
    // 导入App.vue根组件,将来要把App.vue中的模板结构,渲染到HTML页面中
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    // 创建Vue的实例对象
    new Vue({
      // 把render函数指定的组件,渲染到HTML页面中
      render: h => h(App),
    }).$mount('#app')
    
    // 使用$mount方法指定到app区域,作用与el属性完全一样。两种方式选择其一即可。
    

    测试$mount方法

    等价于:el: '#app'

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>My test page</title>
        <!-- 1. 导入Vue库文件,再Windows全局就有了Vue这个构造函数 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <style>
          .box {
             200px;
            height: 200px;
            border: 1px solid #ccc;
          }
        </style>
      </head>
    
      <body>
        <div id="app">
          <input type="text" v-model="username" />
        </div>
        <!-- 2. 创建 Vue 的实例对象 -->
        <script>
          // 创建 Vue 实例,得到 ViewModel
          var vm = new Vue({
            data: {
              username: "ZhangSan",
            },
          });
          vm.$mount("#app");
        </script>
      </body>
    </html>
    

    Vue组件

    什么是组件化开发

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

    Vue是一个支持组件化开发的前端框架。

    Vue中规定:组件的后缀名是 .vue。之前接触到的 App.vue 文件本质上就是一个 vue 的组件。

    Vue组件的三个组成部分

    每个 .vue 组件都由 3 部分构成,分别是:

    • template:组件的模板结构。
    • script:组件的 JavaScript 行为。
    • style:组件的样式 其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。

    template

    vue 规定:每个组件对应的模板结构,需要定义到template节点中。

    注意:

    • template 是 vue 提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的 DOM 元素
    • template 中只能包含唯一的根节点

    script

    vue 规定:开发者可以在script节点中封装组件的JavaScript业务逻辑。

    .vue 组件中的 data 必须是函数

    vue 规定:.vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象。

    style

    vue 规定:组件内的style节点是可选的,开发者可以在style节点中编写样式美化当前组件的 UI 结构。

    新建文件Test.vue

    <template>
      <div class="text-box">
        <h3>这是一个自定义组件--{{ username }}</h3>
      </div>
    </template>
    
    <script>
    // 默认导出,固定写法
    export default {
      // data数据源
      // 注意:.vue组件中的data不能像之前一样,不能指向对象
      // 注意:组件中的data必须是一个函数
      data() {
        // return出去的对象中,可以定义数据
        return {
          username: "admin",
        };
      },
    };
    </script>
    
    <style>
    </style>
    

    在组件中定义methods方法

    <template>
      <div class="text-box">
        <h3>这是一个自定义组件--{{ username }}</h3>
        <button @click="changeName">修改名字</button>
      </div>
    </template>
    
    <script>
    // 默认导出,固定写法
    export default {
      // data数据源
      // 注意:.vue组件中的data不能像之前一样,不能指向对象
      // 注意:组件中的data必须是一个函数
      data() {
        // return出去的对象中,可以定义数据
        return {
          username: "admin",
        };
      },
      methods: {
        // 在组件中,this就表示当前组件的实例对象
        changeName() {
          this.username = "娃哈哈";
        },
        // 当前组件中的侦听器
        watch: {},
        // 当前组件中的计算属性
        computed: {},
        // 当前组件中的过滤器
        filters: {},
      },
    };
    </script>
    
    <style>
    .text-box {
      background-color: pink;
    }
    </style>
    

    让style中支持 less 语法

    在style标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式。

    <style lang="less">
    .test-box {
      background-color: pink;
      h3 {
        color: red;
      }
    }
    </style>
    

    使用组件的三个步骤

    • 步骤1:使用 import 语法导入需要的组件。

      import Left from '@/components/Left.vue'
      
    • 步骤2:使用components节点注册组件。

      export default {
      	components: {
      		Left
      	}
      }
      
    • 步骤3:以标签形式使用刚才注册的组件。

      <div class="box">
          <Left></Left>
      </div>
      

    通过components注册的是私有子组件

    在组件 A 的 components 节点下,注册了组件 F。

    则组件 F 只能用在组件 A 中;不能被用在组件 C 中。

    请大家思考两个问题:

    ① 为什么 F 不能用在组件 C 中?

    ② 怎样才能在组件 C 中使用 F?

    注册全局组件

    方式一(main.js中定义)

    在 vue 项目的 main.js 入口文件中,通过 Vue.component() 方法,可以注册全局组件。

    // 导入需要被全局注册的组件
    import TestName from '@/components/TestName'
    
    // 参数1:字符串格式,表示组件的“注册名称”
    // 参数2:需要被全局注册的对应组件
    Vue.component('MyTestName', TestName);
    

    在使用时候,只需要将注册的全局组件当成一个常规组件使用即可。

    例如在App.vue中需要使用该组件。

    <template>
      <div id="app">
        <MyTestName></MyTestName>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    方式二(App.vue中定义,推荐)

    App.vue

    <template>
      <div id="app">
        <MyTestName :init="9"></MyTestName>
      </div>
    </template>
    
    <script>
    // 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    
    export default {
      name: "App",
      // 2. 注册组件
      components: {
        MyTestName,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    组件的自定义属性props

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

    语法使用如下:

    export default {
        // 组件的自定义属性
        props: ['自定义属性A', '自定义属性B', '其他自定义属性...'],
        
        // 组件的私有属性
        data() {
            return {},
        }
    }
    

    举例说明。

    TestName.vue

    <template>
      <div class="test-container">
        <h3 id="myh3">Test.vue 组件 --- {{ books.length }} 本图书</h3>
        <p id="pppp">message 的值是:{{ message }}</p>
        <button @click="message += '~'">修改 message 的值</button>
      </div>
    </template>
    
    <script>
    export default {
      // props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
      // 自定义属性的名字,是封装者自定义的(只要名称合法即可)
      props: ["info"],
      data() {
        return {
          message: "hello vue.js",
          // 定义 books 数组,存储的是所有图书的列表数据。默认为空数组!
          books: [],
        };
      },
    };
    </script>
    
    <style></style>
    

    调用的组件中,通过属性的方式传递参数值即可。

    App.vue

    <template>
      <div id="app">
        <MyTestName int="9"></MyTestName>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    结合v-bind使用自定义属性

    默认传递过去的是字符串数值。如需要传递数字值,使用v-bind属性即可。

    <template>
      <div id="app">
        <MyTestName :int="9"></MyTestName>
      </div>
    </template>
    

    props是只读的

    vue 规定:组件中封装的自定义属性是只读的,程序员不能直接修改 props 的值。否则会直接报错。

    要想修改 props 的值,可以把 props 的值转存到 data 中,因为 data 中的数据都是可读可写的!

    export default {
      	// props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
      	// 自定义属性的名字,是封装者自定义的(只要名称合法即可)
        // props中的数据,可以直接在末班结构中被使用
        // 注意:props中的数据是只读的,不要直接修改props的值,否则终端会报错!
        props: ['init'],
        
        // 组件的私有属性
        data() {
            return {
                count: this.init, // 把this.init的值转存到count
            },
        }
    }
    

    TestName.vue

    <template>
      <div class="test-container">
        <p id="pppp">count 的值是:{{ count }}</p>
        <input type="text" v-model="count" />
        <br />
        <br />
        <button @click="countPlusOne">Count+1</button>
      </div>
    </template>
    
    <script>
    export default {
      props: ["init"],
      data() {
        return {
          count: this.init,
        };
      },
      methods: {
        countPlusOne() {
          console.log(this);
          this.count++;
        },
      },
    };
    </script>
    
    <style></style>
    

    组件调用方,App.vue中,以属性形式传递参数。

    <template>
      <div id="app">
        <MyTestName :init="9"></MyTestName>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    props中default默认值

    在声明自定义属性时,可以通过 default 来定义属性的默认值。

    export default {
      	// props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
      	// 自定义属性的名字,是封装者自定义的(只要名称合法即可)
        // props中的数据,可以直接在末班结构中被使用
        // 注意:props中的数据是只读的,不要直接修改props的值,否则终端会报错!
        props: {
            // 自定义属性A:{ /* 配置项 */ }
            // 自定义属性B:{ /* 配置项 */ }
            // 自定义属性C:{ /* 配置项 */ }
            init: {
                // 如果外界使用TestName组件的时候,没有传递init属性,则默认值生效
                // 用default属性定义属性的默认值
                default: 0,
            },
        },
        
        // 组件的私有属性
        data() {
            return {
                count: this.init, // 把this.init的值转存到count
            },
        }
    }
    

    props中type值类型

    在声明自定义属性时,可以通过 type 来定义属性的值类型。

    export default {
      	// props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
      	// 自定义属性的名字,是封装者自定义的(只要名称合法即可)
        // props中的数据,可以直接在末班结构中被使用
        // 注意:props中的数据是只读的,不要直接修改props的值,否则终端会报错!
        props: {
            // 自定义属性A:{ /* 配置项 */ }
            // 自定义属性B:{ /* 配置项 */ }
            // 自定义属性C:{ /* 配置项 */ }
            init: {
                // 如果外界使用TestName组件的时候,没有传递init属性,则默认值生效
                // 用default属性定义属性的默认值
                default: 0,
                // 用type属性定义属性的值类型
                // 如果传递过来的值不符合此类型,则会在终端报错
                type: Number,
            },
        },
        
        // 组件的私有属性
        data() {
            return {
                count: this.init, // 把this.init的值转存到count
            },
        }
    }
    

    props的required必填项

    在声明自定义属性时,可以通过 required 选项,将属性设置为必填项,强制用户必须传递属性的值。

    export default {
      	// props是自定义属性,允许使用者通过自定义属性,为当前组件指定初始值
      	// 自定义属性的名字,是封装者自定义的(只要名称合法即可)
        // props中的数据,可以直接在末班结构中被使用
        // 注意:props中的数据是只读的,不要直接修改props的值,否则终端会报错!
        props: {
            // 自定义属性A:{ /* 配置项 */ }
            // 自定义属性B:{ /* 配置项 */ }
            // 自定义属性C:{ /* 配置项 */ }
            init: {
                // 如果外界使用TestName组件的时候,没有传递init属性,则默认值生效
                // 用default属性定义属性的默认值
                default: 0,
                // 用type属性定义属性的值类型
                // 如果传递过来的值不符合此类型,则会在终端报错
                type: Number,
                // 必填项校验
                required: true,
            },
        },
        
        // 组件的私有属性
        data() {
            return {
                count: this.init, // 把this.init的值转存到count
            },
        }
    }
    

    组件之间的样式冲突问题

    默认情况下,写在 .vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。

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

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

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

    如何解决组件样式冲突的问题

    为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域。

    为了提高开发效率和开发体验,vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题

    TestName.vue

    <template>
      <div class="test-container">
        <p id="pppp">count 的值是:{{ count }}</p>
        <input type="text" v-model="count" />
        <br />
        <br />
        <button @click="countPlusOne">Count+1</button>
      </div>
    </template>
    
    <script>
    export default {
      props: {
        init: {
          default: 15,
          type: Number,
          required: true,
        },
      },
      data() {
        return {
          count: this.init,
        };
      },
      methods: {
        countPlusOne() {
          console.log(this);
          this.count++;
        },
      },
    };
    </script>
    
    <style scoped>
    /* style节点的scoped属性,用来自动为每个组件分配一个唯一的“自定义属性”
        并自动为当前组件的DOM标签和style样式应用这个自定义属性,防止组件的样式冲突问题 */
    p {
      color: blue;
    }
    </style>
    

    生成的代码:

    <div data-v-7fcd5826="" class="test-container">
      <p data-v-7fcd5826="" id="pppp">count 的值是:9</p>
      <input data-v-7fcd5826="" type="text" /><br data-v-7fcd5826="" /><br
        data-v-7fcd5826=""
      /><button data-v-7fcd5826="">Count+1</button>
    </div>
    

    /deep/ 样式穿透

    如果给当前组件的 style 节点添加了 scoped 属性,则当前组件的样式对其子组件是不生效的。如果想让某些样

    式对子组件生效,可以使用 /deep/ 深度选择器。

    Vue组件的实例对象

    • 组件定义的是一个模板结构。

    • 实际组件使用的场景,即为一个组件实例对象。

    Vue生命周期

    生命周期&生命周期函数

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

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

    注意:生命周期强调的是时间段,生命周期函数强调的是时间点。

    生命周期图示

    新建一个组件:TestLife.vue

    <template>
      <div class="test-container">
        <h3>Test.vue组件</h3>
      </div>
    </template>
    
    <script>
    export default {};
    </script>
    
    <style scoped>
    .test-container {
      background-color: aqua;
      height: 100px;
    }
    </style>
    

    在App.vue中注册全局组件,并使用。

    <template>
      <div id="app">
        <MyTestLife></MyTestLife>
        <MyTestName :init="9"></MyTestName>
      </div>
    </template>
    
    <script>
    // 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    import MyTestLife from "@/components/TestLife";
    
    export default {
      name: "App",
      // 2. 注册组件
      components: {
        MyTestName,
        MyTestLife,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    • 了解beforeCreate和created生命周期的特点
    • 了解beforeMount和mounted生命周期函数
    • 组件运行阶段的生命周期函数
    • 组件销毁阶段的生命周期函数

    新建一个测试生命周期的组件TestLife.vue。

    <template>
      <div class="test-container">
        <h3 id="myh3">Test.vue组件——{{ books.length }}本图书</h3>
        <p v-for="item in books" :key="item.id">{{ item.name }}</p>
        <p id="myId">{{ message }}</p>
        <button @click="changeMessage">改变message的值</button>
      </div>
    </template>
    
    <script>
    export default {
      // 组件的自定义属性
      props: ["info"],
      // 组件的私有属性
      data() {
        return {
          message: "hello, vue.js",
          books: [
            { id: 1, name: "张三书" },
            { id: 2, name: "李四书" },
          ],
        };
      },
      methods: {
        show() {
          console.log("调用了TestLife组件的show()方法");
        },
        initBookList() {},
        changeMessage() {
          this.message += "~";
        },
      },
    
      /***** 1. 组件创建阶段 *****/
      // -->>(过程)初始化事件和生命周期函数
      // 组件的props/data/methods尚未被创建,都处于不可用状态
      beforeCreate() {
        console.log("--生命周期: beforeCreate()");
        // console.log(this.info); // 不可用
        // console.log(this.message); // 不可用
        // this.show(); // 不可用
      },
      // -->>(过程)初始化props、data、methods
      // 组件的props/data/methods已创建好,都处于可用状态
      // 但是组件的模板结构尚未生成,不能操作DOM元素
      // created生命周期函数,非常常用
      // 经常在它里面,调用methods中的方法,气你跪求服务器的数据
      // 并且,把请求到的数据,转存到data中,供template模板渲染的时候使用
      created() {
        console.log("--生命周期: create() **");
        console.info(this.info); // 可用
        console.info(this.message); // 可用
        this.show(); // 可用
    
        // const dom = document.querySelector('#myh3'); // 不可用
        // console.log(dom); // 不可用
      },
      // -->>(过程)基于数据和模板,在内存中编译生成HTML结构
      // 将要把内存中编译好的HTML结构渲染到浏览器中
      // 此时浏览器中还没有当前组件的DOM结构
      beforeMount() {
        console.log("--生命周期: beforeMount()");
        // const dom = document.querySelector('#myh3'); // 不可用
        // console.log(dom); // 不可用
      },
      // -->>(过程)用内存中编译生成的HTML结构,替换掉el属性指定的DOM元素
      // 已经把内存中的HTML结构,成功的渲染到了浏览器之中。此时浏览器中已然包含了当前组件的DOM结构
      mounted() {
        console.log("--生命周期: mounted() **"); // 可用
        const dom = document.querySelector("#myh3"); // 可用
        console.log(dom);
      },
      /***** 2. 组件运行阶段 *****/
      /*****  此阶段数据最少执行0次(无数据变化) *****/
      /*****  此阶段数据最多执行N次(数据变化N次) *****/
      /*****  当数据变化之后,为了能够操作最新的DOM结构,必须把代码写到updated生命周期中 *****/
      // 将要根据变化过后,最新的数据,重新渲染组件的模板结构
      // 此时数据已经变化,但DOM元素内容未更新
      beforeUpdate() {
        console.log("--生命周期: beforeUpdate()");
        console.log("数据值(已更新): " + this.message);
        const domId = document.querySelector("#myId"); // 可用
        console.log("DOM元素值(未更新): " + domId.innerHTML);
      },
      // -->>(过程)根据最新的数据,重新渲染组件的DOM结构
      // 已经根据最新的数据,完成了组件DOM结构的重新渲染
      updated() {
        console.log("--生命周期: updated() **");
        console.log("数据值(已更新): " + this.message);
        const domId = document.querySelector("#myId");
        console.log("DOM元素值(已更新): " + domId.innerHTML);
      },
      /***** 3. 组件销毁阶段 *****/
      // 将要销毁此组件,此时尚未销毁,组件处于正常工作的状态
      beforeDestroy() {
        console.log("--生命周期: beforeDestroy()");
      },
      // -->>(过程)销毁当前组件的数据侦听器、子组件、事件监听
      // 组件已经被销毁,此组件在浏览器中对应的DOM结构已被完全移除
      destroyed() {
        console.log("--生命周期: destroyed()");
      },
    };
    </script>
    
    <style scoped>
    .test-container {
      background-color: aqua;
      height: 180px;
    }
    </style>
    

    在App.vue组件中进行传参和测试组件的销毁阶段。

    <template>
      <div id="app">
        <h1>App根组件</h1>
        <button @click="flag = !flag">Toggle TestLife Flag</button>
    
        <MyTestLife info="你好" v-if="flag"></MyTestLife>
        <MyTestName :init="9"></MyTestName>
      </div>
    </template>
    
    <script>
    // 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    import MyTestLife from "@/components/TestLife";
    
    export default {
      name: "App",
      data() {
        return {
          flag: true,
        };
      },
      // 2. 注册组件
      components: {
        MyTestName,
        MyTestLife,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    组件之间的数据共享

    组件之间的关系

    最常见的组件之间的关系

    • 父子关系
    • 兄弟关系

    父组件向子组件共享数据(自定义属性)

    父组件App.vue:

    • 简单类型是传递值到子组件
    • 复杂类型传递的是对象的引用到子组件
    <template>
      <div id="app">
        <h1>App根组件</h1>
        <MyTestName :msg="message" :user="userInfo"></MyTestName>
      </div>
    </template>
    
    <script>
    // 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          message: "hello, vue.js",
          userInfo: { name: "张三", age: 20 },
        };
      },
      // 2. 注册组件
      components: {
        MyTestName,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    子组件TestName.vue:

    子组件中自定义属性(props)接收数据

    说明:不要直接修改props中的值。可以在子组件中定义私有属性进行接收和改变。

    <template>
      <div class="test-container">
        <h5>Son组件</h5>
        <p>父组件传递过来的msg值是: {{ msg }}</p>
        <p>父组件传递过来的user值是: {{ user }}</p>
      </div>
    </template>
    
    <script>
    export default {
      // 组件的自定义属性
      props: ["msg", "user"],
      // 组件的私有属性
      data() {
        return {
          msgIn: this.msg,
          userIn: this.user,
        };
      },
    };
    </script>
    
    <style scoped>
    p {
      color: blue; /* 不加/deep/时,生成的选择器格式为 p[data-v-052242de] */
    }
    </style>
    

    子组件向父组件传递数据(自定义事件)

    子组件向父组件共享数据使用自定义事件。示例代码:

    子组件触发XX自定义事件:

    <script>
    export default {
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          count: 0, // 需要传递给父组件的值
        };
      },
      methods() {
          add() {
              this.count += 1;
              // 修改数据时,通过$emit()触发自定义事件
              this.$emit('numChange', this.count);
          }
      }
    };
    </script>
    

    父组件监听XX自定义事件:

    <Son @numChange="getNewCount"></Son>
    
    <script>
    export default {
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          countFromSon: 0,
        };
      },
      methods() {
          getNewCount(val) {
              this.countFromSon = val;
          }
      }
    };
    </script>
    

    举例说明

    App.vue

    <template>
      <div id="app">
        <h1>App根组件</h1>
        <p >子组件传递过来的数据值:{{ countFromSon }}</p>
        <MyTestName :msg="message" :user="userInfo" @numChange="getNewCount"></MyTestName>
      </div>
    </template>
    
    <script>
    // 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          message: "hello, vue.js",
          userInfo: { name: "张三", age: 20 },
          countFromSon: 0,
        };
      },
      methods: {
        getNewCount(val) {
          this.countFromSon = val;
        },
      },
      // 2. 注册组件
      components: {
        MyTestName,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    TestName.vue

    <template>
      <div class="test-container">
        <h5>Son组件</h5>
        <p>父组件传递过来的msg值是: {{ msg }}</p>
        <p>父组件传递过来的user值是: {{ user }}</p>
        <p>需要传递给父组件的count值:{{ count }}</p>
        <button @click="add()">count数值+1</button>
      </div>
    </template>
    
    <script>
    export default {
      // 组件的自定义属性
      props: ["msg", "user"],
      // 组件的私有属性
      data() {
        return {
          msgIn: this.msg,
          userIn: this.user,
          count: 0, // 需要传递给父组件的值
        };
      },
      methods: {
        add() {
          this.count += 1;
          // 修改数据时,通过$emit()触发自定义事件
          this.$emit('numChange', this.count);
        }
      },
    };
    </script>
    
    <style scoped>
    p {
      color: blue; /* 不加/deep/时,生成的选择器格式为 p[data-v-052242de] */
    }
    </style>
    

    兄弟组件之间的数据共享

    在 Vue2.x 中,兄弟组件之间数据共享的方案是 EventBus。

    在components文件夹中,新建一个EventBus的JS文件(eventBus.js):

    import Vue from 'vue'
    // 向外共享 Vue 的实例对象
    export default new Vue();
    

    兄弟组件A(数据发送方):

    // 1. 导入eventBus.js模块
    import bus from './eventBus.js'
    
    <script>
    export default {
        data() {
            return {
                msg: 'hello vue.js'
            }
        },
        methods: {
            sendMsg() {
                // 2. 通过eventBus触发自定义事件,发送数据
                bus.$emit('share', this.msg)
            }
        }
    }
    </script>
    

    兄弟组件C(数据接收方):

    // 1. 导入eventBus.js模块
    import bus from './eventBus.js'
    
    <script>
    export default {
        data() {
            return {
                msgFromLeft: '',
            }
        },
        created() {
            // 2. 通过eventBus监听自定义事件,接收数据
            bus.$on('share', val => {
                this.msgFromLeft = val;
            })
        }
    }
    </script>
    

    EventBus 的使用步骤

    ① 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象

    ② 在数据发送方,调用 bus.$emit('事件名称', 要发送的数据) 方法触发自定义事件

    ③ 在数据接收方,调用 bus.$on('事件名称', 事件处理函数) 方法注册一个自定义事件

    Ref引用

    什么是ref引用

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

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

    组件的 $refs 指向一个空对象。

    使用ref引用DOM元素

    如果想要使用 ref 引用页面上的 DOM 元素,则可以按照如下的方式进行操作:

    App.vue

    <template>
      <div id="app">
        <h1 ref="myh1">App根组件</h1>
        <p>子组件传递过来的数据值:{{ countFromSon }}</p>
        <button @click="getRef">切换H1的颜色</button>
      </div>
    </template>
    
    <script>
    // 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          message: "hello, vue.js",
          userInfo: { name: "张三", age: 20 },
          countFromSon: 0,
        };
      },
      methods: {
        getNewCount(val) {
          console.log("numChange事件被触发了");
          this.countFromSon = val;
        },
        getRef() {
          console.log(this);
          this.$refs.myh1.style.color = "red";
        },
      },
      // 2. 注册组件
      components: {},
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    使用ref进行父组件调用子组件的方法

    TestName.vue

    <template>
      <div class="test-container">
        <h5>Son组件</h5>
        <p>父组件传递过来的msg值是: {{ msg }}</p>
        <p>父组件传递过来的user值是: {{ user }}</p>
        <p>子组件的count值:{{ count }}</p>
        <button @click="add">count数值+1</button>
        <button @click="resetCount">count数值充值</button>
      </div>
    </template>
    
    <script>
    export default {
      // 组件的自定义属性
      props: ["msg", "user"],
      // 组件的私有属性
      data() {
        return {
          msgIn: this.msg,
          userIn: this.user,
          count: 0,
        };
      },
      methods: {
        add() {
          this.count += 1;
          // 修改数据时,通过$emit()触发自定义事件
          this.$emit("numChange", this.count);
        },
        resetCount() {
          this.count = 0;
        },
      },
    };
    </script>
    
    <style scoped>
    p {
      color: blue; /* 不加/deep/时,生成的选择器格式为 p[data-v-052242de] */
    }
    </style>
    

    App.vue,通过ref属性调用子组件的方法

    <template>
      <div id="app">
        <h1 ref="myH1">App根组件</h1>
        <button @click="toggleH1Color">切换H1标题颜色</button>
        <button @click="resetSubComCount">重置子组件的count数值</button>
        <MyTestName
          :msg="message"
          :user="userInfo"
          ref="componentTestName"
        ></MyTestName>
      </div>
    </template>
    
    <script>
    // 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          message: "hello, vue.js",
          userInfo: { name: "张三", age: 20 },
          countFromSon: 0,
        };
      },
      methods: {
        toggleH1Color() {
          console.log(this);
          this.$refs.myH1.style.color = "red";
        },
        resetSubComCount() {
          console.log(this);
          this.$refs.componentTestName.resetCount();
        },
      },
      // 2. 注册组件
      components: {
        MyTestName,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    动态组件

    什么是动态组件

    动态组件指的是动态切换组件的显示与隐藏。

    如何实现动态组件渲染

    vue 提供了一个内置的<component>组件(只是一个占位符),专门用来实现动态组件的渲染。示例代码如下:

    data() {
    	// 1. 当前要渲染的组件名称
    	return { comName: 'Left' }
    }
    
    <!-- 2. 通过is属性,动态指定要渲染的组件 -->
    <component :is="comName"></component>
    
    <!-- 3. 点击按钮,动态切换组件的名称 -->
    <button @click="comName = 'Left'">展示Left组件</button>
    <button @click="comName = 'Right'">展示Right组件</button>
    
    • component标签是Vue内置的,作用:组件的占位符
    • is属性的值,表示要渲染的组件的名字

    举例说明,前提子组件TestName.vue已经创建。App.vue组件:

    <template>
      <div id="app">
        <h1 ref="myH1">App根组件</h1>
        <button @click="componentToggle">展示组件TestName</button>
         <component :is="componentId"></component>
      </div>
    </template>
    
    <script>
    // 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          componentId: "",
        };
      },
      methods: {
        componentToggle() {
          console.log(this.componentId);
          if (this.componentId !== "MyTestName") {
            this.componentId = "MyTestName";
          } else {
            this.componentId = "";
          }
        },
      },
      // 2. 注册组件
      components: {
        MyTestName,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
    

    使用keep-alive保持状态

    默认情况下,切换动态组件时无法保持组件的状态。

    此时可以使用 vue 内置的 <keep-alive> 组件保持动态组件的状态。

    <keep-alive>可以把内部的组件进行缓存,而不是销毁组件。

    示例代码如下:

    <keep-alive>
        <component :is="componentId"></component>
    </keep-alive>
    

    举例,App.vue

    <template>
      <div id="app">
        <h1 ref="myH1">App根组件</h1>
        <button @click="componentToggle">展示组件TestName</button>
        <keep-alive>
          <component :is="componentId"></component>
        </keep-alive>
      </div>
    </template>
    
    <script>
    // 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          componentId: "",
        };
      },
      methods: {
        componentToggle() {
          console.log(this.componentId);
          if (this.componentId !== "MyTestName") {
            this.componentId = "MyTestName";
          } else {
            this.componentId = "";
          }
        },
      },
      // 2. 注册组件
      components: {
        MyTestName,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    keep-alive对应的生命周期函数

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

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

    <template>
      <div id="app">
        <h1 ref="myH1">App根组件</h1>
        <button @click="componentToggle">展示组件TestName</button>
        <keep-alive>
          <component :is="componentId"></component>
        </keep-alive>
      </div>
    </template>
    
    <script>
    // 1. 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          componentId: "",
        };
      },
      methods: {
        componentToggle() {
          console.log(this.componentId);
          if (this.componentId !== "MyTestName") {
            this.componentId = "MyTestName";
          } else {
            this.componentId = "";
          }
        },
      },
      // 2. 注册组件
      components: {
        MyTestName,
      },
      created() {
        console.log("TestName组件被创建了");
      },
      destroyed() {
        console.log("TestName组件被销毁了");
      },
      // 当组件第一次被创建的时候,既会执行created生命周期,也会执行activated生命周期
      // 当组件被激活的时候,只会触发activated生命周期,不再触发created生命周期,因为组件没有被重新创建
      activated() {
        console.log("组件被激活了,activated");
      },
      deactivated() {
        console.log("组件被去激活了,deactivated");
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    keep-alive的include/exclude属性

    include 属性用来指定:只有名称匹配的组件会被缓存。多个组件名之间使用英文的逗号分隔:

    <keep-alive include="Left,Right">
        <component :is="componentId"></component>
    </keep-alive>
    

    exclude 属性用来指定:除了名称匹配的其他的组件会被缓存。多个组件名之间使用英文的逗号分隔:

    <keep-alive exclude="Right">
        <component :is="componentId"></component>
    </keep-alive>
    
    • 在使用keep-alive时候,可以通过include属性指定哪些组件需要被缓存。
    • 在使用keep-alive时候,可以通过exclude属性指定哪些组件不需要被缓存。
    • 但是,不要同时使用include和exclude这两个属性。

    了解组件注册名称和组件声明时name的区别

    • 组件的“注册名称”主要应用场景:以标签的形式,把注册好的组件,渲染和使用到页面结构之中。
    • 组件声明时候的“name”名称的主要应用场景:结合<keep-alive>标签实现组件缓存功能;以及在调试工具中看到组件的name名称。
    <script>
    export default {
      // 当提供了name属性之后,组件的名称,就是name属性的值
      name: 'MyComponent',
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {};
      },
      methods: {},
    };
    </script>
    

    components属性中名称为组件的注册名称

    <script>
    export default {
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {};
      },
      components: {
        // 如果在声明组件的时候,没有为组件指定name名称,则组件的名称默认就是“注册时候的名称”
        TestName,
      }
      methods: {},
    };
    </script>
    

    插槽

    什么是插槽

    插槽(slot)是 vue 为组件的封装者提供的能力。允许开发者在封装组件时,把不确定的、希望由用户指定的

    部分定义为插槽。

    可以把插槽认为是组件封装期间,为用户预留的内容的占位符。以实现组件的复用。

    体验插槽的基础用法

    在封装组件时,可以通过 <slot> 元素定义插槽,从而为用户预留内容占位符。

    父组件,App.vue:

    <template>
      <div id="app">
        <h1>App根组件</h1>
        <MyTestName>
          <p>这是子组件的内容区域,声明在父组件的p标签中</p>
        </MyTestName>
      </div>
    </template>
    
    <script>
    // 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          componentId: "",
        };
      },
      methods: {},
      // 注册组件
      components: {
        MyTestName,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    子组件,TestName.vue:

    <template>
      <div class="test-container">
        <h5>Son组件</h5>
        <!-- 声明一个插槽区域 -->
        <!-- Vue官方规定:每一个slot插槽,都要有一个name名称 -->
        <!-- 如果省略了slot的name属性,则有一个默认名称,叫做default -->
        <slot name="default"></slot>
      </div>
    </template>
    
    <script>
    export default {
      name: "MyComponent",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {};
      },
      methods: {},
    };
    </script>
    
    <style scoped>
    p {
      color: blue; /* 不加/deep/时,生成的选择器格式为 p[data-v-052242de] */
    }
    </style>
    

    v-slot指令以及插槽的后备内容

    在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的

    形式提供其名称。

    具名插槽的定义和使用

    如果在封装组件时需要预留多个插槽节点,则需要为每个 <slot> 插槽指定具体的 name 名称。这种带有具体

    名称的插槽叫做“具名插槽”。

    父组件,App.vue:

    <template>
      <div id="app">
        <h1>App根组件</h1>
        <MyTestName>
          <!-- 1. 如果要把内容填充到指定名称的插槽中,需要使用“v-slot:”这个指令 -->
          <!-- 2. v-slot: 后面要跟上插槽的名称 -->
          <!-- 3. v-slot: 指令不能直接用在元素身上,必须用在template标签上 -->
          <!-- 4. template这个标签,它是一个虚拟的标签,只起到包裹性质的作用,但是,不会被渲染为任何实质性的html元素 -->
          <!-- 5. v-slot: 指令的间歇形式是“#” -->
          <template v-slot:header>
            <h>滕王阁序</h>
          </template>
    	
    	  <!-- v-slot: 指令的间歇形式是“#” -->
          <template #default>
            <p>豫章故郡,洪都新府。</p>
            <p>星分翼轸,地接衡庐。</p>
            <p>襟三江而带五湖,控蛮荆而引瓯越。</p>
          </template>
    
          <template v-slot:footer>
            <p>落款:王勃</p>
          </template>
        </MyTestName>
      </div>
    </template>
    
    <script>
    // 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          componentId: "",
        };
      },
      methods: {},
      // 注册组件
      components: {
        MyTestName,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    子组件,TestName.vue,包含了插槽的后备内容。

    <template>
      <div class="test-container">
        <h5>Son组件</h5>
        <!-- 声明一个插槽区域 -->
        <!-- Vue官方规定:每一个slot插槽,都要有一个name名称 -->
        <!-- 如果省略了slot的name属性,则有一个默认名称,叫做default -->
        <div class="header-box">
          <slot name="header"></slot>
        </div>
        <div class="content-box">
          <!-- 在声明插槽时候,提供了一个默认的内容 -->
          <!-- 当用户没有指定内容时候,该后备内容即会生效 -->
          <slot name="default">这是default插槽的默认内容</slot>
        </div>
        <div class="footer-box">
          <slot name="footer"></slot>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: "MyComponent",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {};
      },
      methods: {},
    };
    </script>
    
    <style lang="less" scoped>
    .test-container {
      > div {
        min-height: 30px;
      }
      .header-box {
        background-color: pink;
        height: 10px;
      }
      .content-box {
        background-color: lightblue;
        height: 100px;
      }
      .footer-box {
        background-color: lightgreen;
        height: 10px;
      }
    }
    </style>
    

    作用域插槽的基本用法

    在封装组件的过程中,可以为预留的 <slot> 插槽绑定 props 数据,这种带有 props 数据的 <slot> 叫做“作用域插槽”。

    父组件,App.vue

    <template>
      <div id="app">
        <h1>App根组件</h1>
        <MyTestName>
          <!-- 1. 如果要把内容填充到指定名称的插槽中,需要使用“v-slot:”这个指令 -->
          <!-- 2. v-slot: 后面要跟上插槽的名称 -->
          <!-- 3. v-slot: 指令不能直接用在元素身上,必须用在template标签上 -->
          <!-- 4. template这个标签,它是一个虚拟的标签,只起到包裹性质的作用,但是,不会被渲染为任何实质性的html元素 -->
          <!-- 5. v-slot: 指令的间歇形式是“#” -->
          <template v-slot:header>
            <h>滕王阁序</h>
          </template>
    
          <template #default="scope">
            <p>豫章故郡,洪都新府。</p>
            <p>星分翼轸,地接衡庐。</p>
            <p>襟三江而带五湖,控蛮荆而引瓯越。</p>
            <p>{{ scope.msg }}</p>
          </template>
    
          <template v-slot:footer>
            <p>落款:王勃</p>
          </template>
        </MyTestName>
      </div>
    </template>
    
    <script>
    // 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          componentId: "",
        };
      },
      methods: {},
      // 注册组件
      components: {
        MyTestName,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    子组件,TestName.vue

    <template>
      <div class="test-container">
        <h5>Son组件</h5>
        <!-- 声明一个插槽区域 -->
        <!-- Vue官方规定:每一个slot插槽,都要有一个name名称 -->
        <!-- 如果省略了slot的name属性,则有一个默认名称,叫做default -->
        <div class="header-box">
          <slot name="header"></slot>
        </div>
        <div class="content-box">
          <!-- 在封装组件时,为预留的<slot>提供属性对应的值,这种用法,叫作作用域插槽 -->
          <slot name="default" msg="hello vue.js"></slot>
        </div>
        <div class="footer-box">
          <slot name="footer"></slot>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: "MyComponent",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {};
      },
      methods: {},
    };
    </script>
    
    <style lang="less" scoped>
    .test-container {
      > div {
        min-height: 30px;
      }
      .header-box {
        background-color: pink;
        height: 10px;
      }
      .content-box {
        background-color: lightblue;
        height: 130px;
      }
      .footer-box {
        background-color: lightgreen;
        height: 10px;
      }
    }
    </style>
    

    作用域插槽的结构解析

    作用域插槽对外提供的数据对象,可以使用解构赋值简化数据的接收过程。

    父组件,App.vue

    <template>
      <div id="app">
        <h1>App根组件</h1>
        <MyTestName>
          <!-- 1. 如果要把内容填充到指定名称的插槽中,需要使用“v-slot:”这个指令 -->
          <!-- 2. v-slot: 后面要跟上插槽的名称 -->
          <!-- 3. v-slot: 指令不能直接用在元素身上,必须用在template标签上 -->
          <!-- 4. template这个标签,它是一个虚拟的标签,只起到包裹性质的作用,但是,不会被渲染为任何实质性的html元素 -->
          <!-- 5. v-slot: 指令的间歇形式是“#” -->
          <template v-slot:header>
            <h>滕王阁序</h>
          </template>
    
          <!-- 作用域插槽对外提供的数据对象,可以通过“结构赋值”简化接收的过程 -->
          <template #default="{ msg, user }">
            <p>豫章故郡,洪都新府。</p>
            <p>星分翼轸,地接衡庐。</p>
            <p>襟三江而带五湖,控蛮荆而引瓯越。</p>
            <p>{{ msg }}</p>
            <p>{{ user }}</p>
          </template>
    
          <template v-slot:footer>
            <p>落款:王勃</p>
          </template>
        </MyTestName>
      </div>
    </template>
    
    <script>
    // 导入需要使用的.vue组件 import TestName from '@/components/TestName.vue'
    import MyTestName from "@/components/TestName";
    
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          componentId: "",
        };
      },
      methods: {},
      // 注册组件
      components: {
        MyTestName,
      },
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    子组件,TestName.vue

    <template>
      <div class="test-container">
        <h5>Son组件</h5>
        <!-- 声明一个插槽区域 -->
        <!-- Vue官方规定:每一个slot插槽,都要有一个name名称 -->
        <!-- 如果省略了slot的name属性,则有一个默认名称,叫做default -->
        <div class="header-box">
          <slot name="header"></slot>
        </div>
        <div class="content-box">
          <!-- 在封装组件时,为预留的<slot>提供属性对应的值,这种用法,叫作作用域插槽 -->
          <slot name="default" msg="hello vue.js" :user="userInfo"></slot>
        </div>
        <div class="footer-box">
          <slot name="footer"></slot>
        </div>
      </div>
    </template>
    
    <script>
    export default {
      name: "MyComponent",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          userInfo: {
            name: "张三",
            age: 24,
          },
        };
      },
      methods: {},
    };
    </script>
    
    <style lang="less" scoped>
    .test-container {
      > div {
        min-height: 30px;
      }
      .header-box {
        background-color: pink;
        height: 10px;
      }
      .content-box {
        background-color: lightblue;
        height: 170px;
      }
      .footer-box {
        background-color: lightgreen;
        height: 10px;
      }
    }
    </style>
    

    自定义指令

    什么是自定义指令

    vue 官方提供了 v-text、v-for、v-model、v-if 等常用的指令。除此之外 vue 还允许开发者自定义指令。

    vue 中的自定义指令分为两类,分别是:

    • 私有自定义指令

    • 全局自定义指令

    私有自定义指令

    在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令。

    App.vue

    <template>
      <div id="app">
        <h1 v-color>App根组件</h1>
        <p>测试内容</p>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {};
      },
      // 私有自定义指令的节点
      directives: {
        // 定义名为color的指令,指向一个配置对象
        color: {
          // 当指令第一次被绑定到元素上的时候,会立即触发bind函数
          // 形参中el表示当前指令所绑定到的哪个DOM对象
          bind(el) {
            console.log(el);
            el.style.color = 'red';
          }
        },
      },
      methods: {},
      // 注册组件
      components: {},
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    使用binding.value获取指令绑定的值

    App.vue

    <template>
      <div id="app">
        <h1 v-color>App根组件</h1>
        <p v-color="'red'">测试内容</p>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {};
      },
      // 私有自定义指令的节点
      directives: {
        // 定义名为color的指令,指向一个配置对象
        color: {
          // 当指令第一次被绑定到元素上的时候,会立即触发bind函数
          // 形参中el表示当前指令所绑定到的哪个DOM对象
          bind(el, binding) {
            console.log(el);
            console.log(binding);
            el.style.color = binding.value ? binding.value : "blue";
          },
        },
      },
      methods: {},
      // 注册组件
      components: {},
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
    

    update函数

    App.vue

    <template>
      <div id="app">
        <h1 v-color="color">App根组件</h1>
        <p v-color="'red'">测试内容</p>
        <button @click="color = 'yellow'">改变color的颜色值</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          color: "blue",
        };
      },
      // 私有自定义指令的节点
      directives: {
        // 定义名为color的指令,指向一个配置对象
        color: {
          // 当指令第一次被绑定到元素上的时候,会立即触发bind函数
          // 形参中el表示当前指令所绑定到的哪个DOM对象
          // binding函数只调用1次
          // 当指令第一次绑定到元素时调用,当DOM更新时bind函数不会被触发
          bind(el, binding) {
            // console.log(el);
            console.log(binding);
            el.style.color = binding.value;
            console.log("el.style.color: " + el.style.color);
          },
          // update函数会在每次DOM更新时被调用
          update(el, binding) {
            el.style.color = binding.value;
          },
        },
      },
      methods: {},
      // 注册组件
      components: {},
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    

    自定义指令-函数简写

    如果 bind 和update 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式:

    <template>
      <div id="app">
        <h1 v-color="color">App根组件</h1>
        <p v-color="'red'">测试内容</p>
        <button @click="color = 'yellow'">改变color的颜色值</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          color: "blue",
        };
      },
      // 私有自定义指令的节点
      directives: {
        // bind 和update 函数中的逻辑完全相同,可以缩写至一个方法中
        color(el, binding) {
          // console.log(el);
          console.log(binding);
          console.log("触发了v-color的bind函数");
          el.style.color = binding.value;
        },
      },
      methods: {},
      // 注册组件
      components: {},
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
    

    全局自定义指令(推荐)

    全局共享的自定义指令需要通过“Vue.directive()”进行声明。

    在main.js中进行全局声明。main.js

    import Vue from 'vue'
    import App from './App.vue'
    
    Vue.config.productionTip = false
    
    // 私有自定义指令的节点
    Vue.directive('color', (el, binding) => {
      el.style.color = binding.value;
    });
    
    new Vue({
      render: h => h(App),
    }).$mount('#app')
    
    

    App.vue

    <template>
      <div id="app">
        <h1 v-color="color">App根组件</h1>
        <p v-color="'red'">测试内容</p>
        <button @click="color = 'yellow'">改变color的颜色值</button>
      </div>
    </template>
    
    <script>
    export default {
      name: "App",
      // 组件的自定义属性
      props: [],
      // 组件的私有属性
      data() {
        return {
          color: "blue",
        };
      },
      methods: {},
      // 注册组件
      components: {},
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
    

    路由

    路由(英文:router)就是对应关系。

    SPA 与前端路由

    SPA 指的是一个 web 网站只有唯一的一个 HTML 页面,所有组件的展示与切换都在这唯一的一个页面内完成。

    此时,不同组件之间的切换需要通过前端路由来实现。

    结论:在 SPA 项目中,不同功能之间的切换,要依赖于前端路由来完成!

    什么是前端路由

    通俗易懂的概念:Hash 地址与组件之间的对应关系。

    前端路由的工作方式

    ① 用户点击了页面上的路由链接

    ② 导致了 URL 地址栏中的 Hash 值发生了变化

    ③ 前端路由监听了到 Hash 地址的变化

    ④ 前端路由把当前 Hash 地址对应的组件渲染都浏览器中

    结论:前端路由,指的是 Hash 地址与组件之间的对应关系!

    实现简易的前端路由

    1. 通过 <component> 标签,结合 comName 动态渲染组件。示例代码如下:

      <!-- 通过is属性,指定要展示的组件的名称 -->
      <component :is="compName"></component>
      
      <script>
      export default {
          name: 'App',
          data() {
              return {
                  // 要展示的组件的名称
                  compName: 'Home'
              }
          }
      }
      </script>
      
    2. 在 App.vue 组件中,为 <a> 链接添加对应的 hash 值

      <a href="#/home">Home</a>&nbsp;
      <a href="#/movie">Movie</a>&nbsp;
      <a href="#/about">About</a>
      
    3. 在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称。

      <script>
      export default {
          created() {
              window.onhashchange = () => {
                  switch (location.hash) {
                      case '#/home': // 单击了“首页”的链接
                          this.compName = 'Home';
                          break;
      				case '#/movie': // 单击了“电影”的链接
                          this.compName = 'Movie';
                          break;
      				case '#/about': // 单击了“关于”的链接
                          this.compName = 'About';
                          break;  
                  }
              }
          }
      }
      </script>
      

    举例说明

    1. 在src/components文件夹中,新建子组件CompHome.vue

      <template>
        <div class="home-container">
          <h3>Home组件</h3>
        </div>
      </template>
      
      <script>
      export default {
        methods: {},
      };
      </script>
      
      <style lang="less" scoped>
      .home-container {
        background-color: skyblue;
        min-height: 200px;
      }
      </style>
      
      
    2. 在src/components文件夹中,新建子组件CompMovie.vue

      <template>
        <div class="movie-container">
          <h3>Movie组件</h3>
        </div>
      </template>
      
      <script>
      export default {
        methods: {},
      };
      </script>
      
      <style lang="less" scoped>
      .movie-container {
        background-color: lightgreen;
        min-height: 200px;
      }
      </style>
      
      
    3. 在src/components文件夹中,新建子组件CompAbout.vue

      <template>
        <div class="about-container">
          <h3>About组件</h3>
        </div>
      </template>
      
      <script>
      export default {
        methods: {},
      };
      </script>
      
      <style lang="less" scoped>
      .about-container {
        background-color: orange;
        min-height: 200px;
      }
      </style>
      
      
    4. 修改App.vue组件

      <template>
        <div id="app">
          <h1>App根组件</h1>
          <div class="app-container">
            <a href="#/home">首页</a>
            <a href="#/movie">电影</a>
            <a href="#/about">关于</a>
            <hr />
      
            <component :is="compName"></component>
          </div>
        </div>
      </template>
      
      <script>
      import Home from "@/components/CompHome.vue";
      import Movie from "@/components/CompMovie.vue";
      import About from "@/components/CompAbout.vue";
      
      export default {
        name: "App",
        components: {
          Home,
          Movie,
          About,
        },
        data() {
          return {
            compName: '',
          }
        },
        created() {
          window.onhashchange = () => {
            switch (location.hash) {
              case "#/home": // 单击了“首页”的链接
                this.compName = "Home";
                break;
              case "#/movie": // 单击了“电影”的链接
                this.compName = "Movie";
                break;
              case "#/about": // 单击了“关于”的链接
                this.compName = "About";
                break;
            }
          };
        },
      };
      </script>
      
      <style lang="less" scoped>
      .app-container {
        background-color: #efefef;
        overflow: hidden;
        margin: 10px;
        padding: 15px;
        > a {
          margin-right: 10px;
        }
      }
      </style>
      
      

    vue-router的基本用法

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

    中组件的切换。

    vue-router 的官方文档地址:https://router.vuejs.org/zh/

    vue-router安装和配置的步骤

    ① 安装 vue-router 包

    ② 创建路由模块

    ③ 导入并挂载路由模块

    ④ 声明路由链接和占位符

    1. 项目中安装vue-router。可以执行以下命令安装:

      npm i vue-router -S
      
    2. 创建路由模块。在src源代码目录下,新建router/index.js 路由模块,并初始化如下的代码:

      // src/router/index.js 就是当前项目的路由模块
      // 1. 导入Vue和VueRouter的包
      import Vue from 'vue'
      import VueRouter from 'vue-router'
      
      // 2. 把 VueRouter 安装为 Vue 项目的插件
      // Vue.use() 函数的作用,就是来安装插件的
      Vue.use(VueRouter)
      
      // 3. 创建路由的实例对象
      const router = new VueRouter()
      
      // 4. 向外共享路由的实例对象
      export default router
      
      

      如果需要加上组件的路由,可以在创建路由的实例对象中,增加一个routes属性,定义hash地址和组件之间的对应关系

      // src/router/index.js 就是当前项目的路由模块
      // 导入Vue和VueRouter的包
      import Vue from 'vue'
      import VueRouter from 'vue-router'
      
      // 导入需要路由的组件
      import Home from '@/components/CompHome.vue'
      import Movie from '@/components/CompMovie.vue'
      import About from '@/components/CompAbout.vue'
      
      // 把 VueRouter 安装为 Vue 项目的插件
      // Vue.use() 函数的作用,就是来安装插件的
      Vue.use(VueRouter)
      
      // 创建路由的实例对象
      const router = new VueRouter({
          // routes是一个数组,作用:定义“hash地址”与“组件”之间的对应关系
          routes: [
      		// 重定向的路由规则
              { path: '/', component: Home },
      		// 路由规则
              { path: '/home', component: Home },
              { path: '/movie', component: Movie },
              { path: '/about', component: About }
          ]
      })
      
      // 向外共享路由的实例对象
      export default router
      
      
    3. 导入并挂载路由模块。在src/main.js 入口文件中,导入并挂载路由模块。示例代码如下:

      import Vue from 'vue'
      import App from './App2.vue'
      // 导入路由模块,目的:拿到路由的实例对象
      // 在进行模块化导入的时候,如果给定的是文件夹,则默认导入这个文件夹下,名字叫做 index.js 的文件,因此此处可以直接引导到文件夹,不用引导到具体的文件中
      import router from '@/router'
      
      Vue.config.productionTip = false
      
      new Vue({
        render: h => h(App),
        // 在 Vue 项目中,要想把路由用起来,必须把路由实例对象,通过下面的方式进行挂载
        // router: 路由的实例对象
        router
      }).$mount('#app')
      
      

    声明路由链接和占位符

    在src/App.vue 组件中,使用vue-router 提供的<router-link><router-view> 声明路由链接和占位符:

    <template>
      <div id="app">
        <h1>App根组件</h1>
        <div class="app-container">
          <!-- <a href="#/home">首页</a>> -->
          <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>
      </div>
    </template>
    

    路由重定向

    路由重定向指的是:用户在访问地址A 的时候,强制用户跳转到地址C ,从而展示特定的组件页面。

    通过路由规则的redirect属性,指定一个新的路由地址,可以很方便地设置路由的重定向:

    router/index.js中修改如下:

    // src/router/index.js 就是当前项目的路由模块
    // 导入Vue和VueRouter的包
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    // 导入需要路由的组件
    import Home from '@/components/CompHome.vue'
    import Movie from '@/components/CompMovie.vue'
    import About from '@/components/CompAbout.vue'
    
    // 把 VueRouter 安装为 Vue 项目的插件
    // Vue.use() 函数的作用,就是来安装插件的
    Vue.use(VueRouter)
    
    // 创建路由的实例对象
    const router = new VueRouter({
        // routes是一个数组,作用:定义“hash地址”与“组件”之间的对应关系
        routes: [
    		// 重定向的路由规则
            { path: '/', component: Home },
    		// 路由规则
            { path: '/home', component: Home },
            { path: '/movie', component: Movie },
            { path: '/about', component: About }
        ]
    })
    
    // 向外共享路由的实例对象
    export default router
    
    

    嵌套路由

    通过路由实现组件的嵌套展示,叫作嵌套路由。

    声明子路由链接和子路由占位符

    在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符。

    <template>
        <div class="ablut-container">
            <h3>About组件</h3>
            <!-- 1. 在关于页面中,声明两个子路由链接 -->
            <router-link to="/about/tab1">tab1</router-link>
            <router-link to="/about/tab2">tab2</router-link>
    
            <hr />
    
            <!-- 2. 在关于页面中,声明子路由的占位符 -->
            <router-view></router-view>
        </div>
    </template>
    

    通过 children 属性声明子路由规则

    在 src/router/index.js 路由模块中,导入需要的组件,并使用 children 属性声明子路由规则:

    import Tab1 from '@components/tabs.Tab1.vue'
    import Tab2 from '@components/tabs.Tab2.vue'
    
    const router = new VueRouter({
        routes: [
            { // about页面的路由规则(父级路由规则)
                path: '/about',
                component: About,
                children: [ // 1. 通过children属性,嵌套声明子级路由规则
                    { path: 'tab1', components: Tab1 }, // 2. 访问/about/tab1时,展示Tab1组件
                    { path: 'tab2', components: Tab2 }, // 2. 访问/about/tab2时,展示Tab2组件
                ]
            }
        ]
    })
    

    完整示例 *

    1. 新建Vue CLI工程,参见[新建Vue CLI工程(带路由工程)【模板】](# 新建Vue CLI工程(带路由工程)【模板】)。

    2. 在src/views文件夹中,新建子组件CompHome.vue

      <template>
        <div class="home-container">
          <h3>Home组件</h3>
        </div>
      </template>
      
      <script>
      export default {
        methods: {},
      };
      </script>
      
      <style lang="less" scoped>
      .home-container {
        background-color: skyblue;
        min-height: 200px;
      }
      </style>
      
      
    3. 在src/views文件夹中,新建子组件CompMovie.vue

      <template>
        <div class="movie-container">
          <h3>Movie组件</h3>
        </div>
      </template>
      
      <script>
      export default {
        methods: {},
      };
      </script>
      
      <style lang="less" scoped>
      .movie-container {
        background-color: lightgreen;
        min-height: 200px;
      }
      </style>
      
      
    4. 在src/views文件夹中,新建子组件CompAbout.vue

      <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 {
        methods: {},
      };
      </script>
      
      <style lang="less" scoped>
      .about-container {
        background-color: orange;
        min-height: 200px;
      }
      </style>
      
      
    5. 在src/views文件夹中,新建文件夹tabs。

    6. 在tabs文件夹中新建子组件CompTab1.vue。

      <template>
        <div class="tab1-container">
          <h3>About中的Tab1组件</h3>
        </div>
      </template>
      
      <script>
      export default {
        methods: {},
      };
      </script>
      
      <style lang="less" scoped>
      .tab1-container {
        background-color: royalblue;
        min-height: 100px;
      }
      </style>
      
      
    7. 在tabs文件夹中新建子组件CompTab2.vue。

      <template>
        <div class="about-container">
          <h3>About中的Tab2组件</h3>
        </div>
      </template>
      
      <script>
      export default {
        methods: {},
      };
      </script>
      
      <style lang="less" scoped>
      .about-container {
        background-color: rebeccapurple;
        min-height: 100px;
      }
      </style>
      
      
    8. 在router/indes.js中,创建路由规则。

      import Vue from 'vue'
      import VueRouter from 'vue-router'
      import CompHome from '@/views/CompHome.vue'
      import CompMovie from '@/views/CompMovie.vue'
      import CompAbout from '@/views/CompAbout.vue'
      import CompTab1 from '@/views/tabs/CompTab1'
      import CompTab2 from '@/views/tabs/CompTab2'
      
      Vue.use(VueRouter)
      
      const routes = [
        // 重定向的路由规则
        { path: '/', name: 'CompHome', component: CompHome },
        // 路由规则
        { path: '/home', name: 'CompHome', component: CompHome },
        { path: '/movie', name: 'CompMovie', component: CompMovie },
        {
          path: '/about',
          name: 'CompAbout',
          component: CompAbout,
          // redirect: '/about/tab1', // 默认子路由方式一:增加重定向功能
          children: [
            // 子路由规则,注意path不能加/前缀
            // 默认子路由方式二:如果children数组中,某个路由规则的path值为空字符串,则这条路由规则路由为默认子路由
            { path: '', name: 'CompTab1', component: CompTab1 },
            { path: 'tab1', name: 'CompTab1', component: CompTab1 },
            { path: 'tab2', name: 'CompTab2', component: CompTab2 },
          ]
        },
      ]
      
      const router = new VueRouter({
        routes
      })
      
      export default router
      
      
    9. 修改App.vue根组件内容。

      <template>
        <div id="app">
          <nav>
            <router-link to="/home">Home</router-link> |
            <router-link to="/movie">Movie</router-link> |
            <router-link to="/about">About</router-link>
          </nav>
          <router-view/>
        </div>
      </template>
      
      <style lang="less">
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
      }
      
      nav {
        padding: 30px;
      
        a {
          font-weight: bold;
          color: #2c3e50;
      
          &.router-link-exact-active {
            color: #42b983;
          }
        }
      }
      </style>
      
      

    动态路由

    动态路由的概念

    动态路由指的是:把 Hash 地址中可变的部分定义为参数项,从而提高路由规则的复用性。
    在 vue-router 中使用英文的冒号(:)来定义路由的参数项。router/index.js示例代码如下:

    // 路由中的动态参数以:进行声明,冒号后面的是动态参数的名称,id为自定义的一个参数
    { path: 'movie/:id', component: Movie }
    
    // 将以下3个路由规则,合并成了一个,提高了路由规则的复用性
    { path: 'movie/1', component: Movie }
    { path: 'movie/2', component: Movie }
    { path: 'movie/3', component: Movie }
    

    示例,以嵌套路由中的完整示例修改说明。

    1. App.vue修改为如下所示。

      <template>
        <div id="app">
          <nav>
            <router-link to="/home">Home</router-link> |
            <!-- 注意1:在hash地址中,/后面的参数项,叫作路径参数 -->
            <!-- 在路由参数对象中,需要使用 this.$route.param来访问路径参数 -->
            <!-- 注意2:在hash地址中,?后面的参数项,叫作查询参数 -->
            <!-- 在查询参数对象中,需要使用 this.$route.query来访问查询参数 -->
            <!-- 注意3:在this.$route中,path只是路径部分,fullPath是完整的地址 -->
            <router-link to="/movie/1">Movie-洛神</router-link> |
            <router-link to="/movie/2?name=zs&age=24">Movie-雷神</router-link> |
            <router-link to="/movie/3">Movie-复联</router-link> |
            <router-link to="/about">About</router-link>
          </nav>
          <router-view/>
        </div>
      </template>
      
      <style lang="less">
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
      }
      
      nav {
        padding: 30px;
      
        a {
          font-weight: bold;
          color: #2c3e50;
      
          &.router-link-exact-active {
            color: #42b983;
          }
        }
      }
      </style>
      
      
    2. 在router/indes.js中,修改为如下所示。

      import Vue from 'vue'
      import VueRouter from 'vue-router'
      import CompHome from '@/views/CompHome.vue'
      import CompMovie from '@/views/CompMovie.vue'
      import CompAbout from '@/views/CompAbout.vue'
      import CompTab1 from '@/components/tabs/CompTab1'
      import CompTab2 from '@/components/tabs/CompTab2'
      
      Vue.use(VueRouter)
      
      const routes = [
        // 重定向的路由规则
        { path: '/', name: 'CompHome', component: CompHome },
        // 路由规则
        { path: '/home', name: 'CompHome', component: CompHome },
        // 需求:在Movie组件中,希望根据mid的值,展示对应电影的详情信息
        { path: '/movie/:mid', name: 'CompMovie', component: CompMovie },
        {
          path: '/about',
          name: 'CompAbout',
          component: CompAbout,
          // redirect: '/about/tab1', // 默认子路由方式一:增加重定向功能
          children: [
            // 子路由规则,注意path不能加/前缀
            // 默认子路由方式二:如果children数组中,某个路由规则的path值为空字符串,则这条路由规则路由为默认子路由
            { path: '', name: 'CompTab1', component: CompTab1 },
            { path: 'tab1', name: 'CompTab1', component: CompTab1 },
            { path: 'tab2', name: 'CompTab2', component: CompTab2 },
          ]
        },
      ]
      
      const router = new VueRouter({
        routes
      })
      
      export default router
      
      

    方式一:$route.params 参数对象

    在动态路由渲染出来的组件中,可以使用 this.$route.params 对象访问到动态匹配的参数值。
    在CompMovie.vue组件中接收参数。

    <template>
      <div class="movie-container">
        <h3>Movie组件</h3>
        <h3>this.$route.params.xx方式:路径参数 -- {{ this.$route.params.mid }}</h3>
        <h3>this.$route.query.xx方式:路径参数 -- {{ this.$route.query }}</h3>
        <button @click="showThis">打印this</button>
      </div>
    </template>
    
    <script>
    export default {
      methods: {
        showThis() {
          console.log(this);
        }
      },
    };
    </script>
    
    <style lang="less" scoped>
    .movie-container {
      background-color: lightgreen;
      min-height: 200px;
    }
    </style>
    
    

    方式二:使用props接收路由参数

    1. 修改router/index.js,为Movie组件开启props传参。

      import Vue from 'vue'
      import VueRouter from 'vue-router'
      import CompHome from '@/views/CompHome.vue'
      import CompMovie from '@/views/CompMovie.vue'
      import CompAbout from '@/views/CompAbout.vue'
      import CompTab1 from '@/components/tabs/CompTab1'
      import CompTab2 from '@/components/tabs/CompTab2'
      
      Vue.use(VueRouter)
      
      const routes = [
        // 重定向的路由规则
        { path: '/', name: 'CompHome', component: CompHome },
        // 路由规则
        { path: '/home', name: 'CompHome', component: CompHome },
        // 为Movie组件开启props传参,从而方便的拿到动态参数的值
        { path: '/movie/:mid', name: 'CompMovie', component: CompMovie, props: true },
        {
          path: '/about',
          name: 'CompAbout',
          component: CompAbout,
          // redirect: '/about/tab1', // 默认子路由方式一:增加重定向功能
          children: [
            // 子路由规则,注意path不能加/前缀
            // 默认子路由方式二:如果children数组中,某个路由规则的path值为空字符串,则这条路由规则路由为默认子路由
            { path: '', name: 'CompTab1', component: CompTab1 },
            { path: 'tab1', name: 'CompTab1', component: CompTab1 },
            { path: 'tab2', name: 'CompTab2', component: CompTab2 },
          ]
        },
      ]
      
      const router = new VueRouter({
        routes
      })
      
      export default router
      
      
    2. 在CompMovie.vue组件中通过props自定义属性接收参数。

      <template>
        <div class="movie-container">
          <h3>Movie组件 -- {{ mid }}</h3>
          <h3>this.$route.params.xx:路径参数 -- {{ this.$route.params.mid }}</h3>
          <h3>this.$route.query.xx:查询参数 -- {{ this.$route.query }}</h3>
          <h3>this.$route.fullPath:完整地址 -- {{ this.$route.fullPath }}</h3>
          <button @click="showThis">打印this</button>
        </div>
      </template>
      
      <script>
      export default {
        props: ["mid"],
        methods: {
          showThis() {
            console.log(this);
          }
        },
      };
      </script>
      
      <style lang="less" scoped>
      .movie-container {
        background-color: lightgreen;
        min-height: 200px;
      }
      </style>
      
      

    声明式导航&编程式导航

    • 在浏览器中,点击链接实现导航的方式,叫做声明式导航。例如:
      普通网页中点击 <a> 链接、vue 项目中点击 <router-link> 都属于声明式导航。

    • 在浏览器中,调用 API 方法实现导航的方式,叫做编程式导航。例如:
      普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航。

    vue-router中的编程式导航API

    vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是:

    • this.$router.push('hash 地址')
      • 跳转到指定 hash 地址,并增加一条历史记录
    • this.$router.replace('hash 地址')
      • 跳转到指定的 hash 地址,并替换掉当前的历史记录
    • this.$router.go(数值 n)
      • 实现导航历史前进、后退

    $router.push跳转指定页面

    调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。

    会增加一个历史记录,即可以返回上一个网址中。

    修改上述示例的CompHome.vue组件为例:

    <template>
      <div class="home-container">
        <h3>Home组件</h3>
        <button @click="gotoMovie">跳转到“洛神”页面</button>
      </div>
    </template>
    
    <script>
    export default {
      methods: {
        gotoMovie() {
          // 通过编程式导航跳转到指定页面
          this.$router.push('/movie/1');
        }
      },
    };
    </script>
    
    <style lang="less" scoped>
    .home-container {
      background-color: skyblue;
      min-height: 200px;
    }
    </style>
    
    

    $router.replace跳转指定页面

    调用 this.$router.replace() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。

    修改上述示例的CompHome.vue组件为例:

    <template>
      <div class="home-container">
        <h3>Home组件</h3>
        <button @click="gotoMovie">跳转到“洛神”页面</button>
      </div>
    </template>
    
    <script>
    export default {
      methods: {
        gotoMovie() {
          // 通过编程式导航跳转到指定页面
          this.$router.replace('/movie/1');
        }
      },
    };
    </script>
    
    <style lang="less" scoped>
    .home-container {
      background-color: skyblue;
      min-height: 200px;
    }
    </style>
    
    

    $router.go()

    调用 this.$router.go() 方法,可以在浏览历史中前进和后退。

    <template>
      <div class="home-container">
        <h3>Home组件</h3>
        <button @click="goBack">后退</button>
      </div>
    </template>
    
    <script>
    export default {
      methods: {
        gotoMovie() {
          // 后退到之前的组件页面
          // 如果后退的层数超过上限,则原地不动
          this.$router.goBack('-1');
        }
      },
    };
    </script>
    
    

    $router.go 的简化用法:

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

    • $router.back():在历史记录中,后退到上一个页面
    • $router.forward():在历史记录中,前进到下一个页面

    导航守卫

    导航守卫可以控制路由的访问权限。

    全局前置守卫

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

    // 创建路由实例对象
    const router = new VueRouter({ ... });
                                  
    // 调用路由实例对象的beforeEach方法,即可声明“全局前置守卫”
    // 每次发生路由导航跳转的时候,都会自动触发fn这个“回调函数”
    router.beforeEach(fn);
    

    守卫方法的3个形参(控制后台主页的访问权限)

    router/index.js:

    // 为 router 实例对象,声明全局前置导航守卫
    // 只要发生了路由的跳转,必然会触发 beforeEach 指定的 function 回调函数
    router.beforeEach(function(to, from, next) {
      // to 表示将要访问的路由的信息对象
      // from 表示将要离开的路由的信息对象
      // next() 函数表示放行的意思
      // 分析:
      // 1. 要拿到用户将要访问的 hash 地址
      // 2. 判断 hash 地址是否等于 /main。
      // 2.1 如果等于 /main,证明需要登录之后,才能访问成功
      // 2.2 如果不等于 /main,则不需要登录,直接放行  next()
      // 3. 如果访问的地址是 /main。则需要读取 localStorage 中的 token 值
      // 3.1 如果有 token,则放行
      // 3.2 如果没有 token,则强制跳转到 /login 登录页
      if (to.path === '/main') {
        // 要访问后台主页,需要判断是否有 token
        const token = localStorage.getItem('token')
        if (token) {
          next()
        } else {
          // 没有登录,强制跳转到登录页
          next('/login')
        }
      } else {
        next()
      }
    })
    

    next函数的 3 种调用方式

    • 当前用户拥有后台主页的访问权限,直接放行:next()
    • 当前用户没有后台主页的访问权限,强制其跳转到登录页面:next('/login')
    • 当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)

    Vant组件库

    Vant组件库基本使用【模板】

    • 参考:Vant组件库:轻量、可靠的移动端 Vue 组件库。
    1. 新建VUE CLI工程。

    2. 参考快速上手 - Vant 3。包含安装Vant、安装vite-plugin-style-import插件(自动按需引入组件的样式)、配置插件。

    3. 以Button按钮为例说明。

      1. 在main.js中注册全局组件,增加以下代码。

        import { Button } from "vant";
        
        Vue.use(Button);
        
      2. 在应用组件中直接使用该组件即可。例如在App.vue中使用Button组件。

        <van-button type="primary">主要按钮</van-button>
        
      3. 重新编译代码

        npm run serve
        

    案例说明

    1. 新建VUE CLI工程,带路由和less功能。

    2. 配置初始工程。

      1. 删除components文件夹中的文件。
      2. 删除views文件夹中的文件。
      3. 删除router/index.js中的默认路由配置。
      4. F12切换浏览器为移动端的浏览效果。
    3. 安装和导入Vant组件以及vite-plugin-style-import插件及插件配置。

    4. 在views文件夹,新建Home文件夹和CompHome.vue文件。

      <template>
        <div class="home-container">
          <h3>Home组件</h3>
        </div>
      </template>
      
      <script>
      export default {};
      </script>
      
      <style lang="less" scoped></style>
      
      
    5. 在views文件夹,新建User文件夹和CompUser.vue文件。

      <template>
        <div class="user-container">
          <h3>User组件</h3>
        </div>
      </template>
      
      <script>
      export default {};
      </script>
      
      <style lang="less" scoped></style>
      
      
    6. main.js中引入组件。

      import Vue from 'vue'
      import App from './App.vue'
      import router from './router'
      import { Button } from 'vant';
      import { Tabbar, TabbarItem } from 'vant';
      
      Vue.use(Button);
      Vue.use(Tabbar);
      Vue.use(TabbarItem);
      
      Vue.config.productionTip = false
      
      new Vue({
        router,
        render: h => h(App)
      }).$mount('#app')
      
      
    7. 使用Tabbar 标签栏组件,并开启路由模式。App.vue

      <template>
        <div id="app">
          <h1>App根组件</h1>
          <van-button type="primary">主要按钮</van-button>
          <van-button type="success">成功按钮</van-button>
      
          <router-view />
      
          <van-tabbar route>
            <van-tabbar-item replace to="/home" icon="home-o">首页</van-tabbar-item>
            <van-tabbar-item replace to="/user" icon="user-o">我的</van-tabbar-item>
          </van-tabbar>
        </div>
      </template>
      
      <style lang="less" scoped>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
      }
      </style>
      
      
    8. 配置路由规则。router/index.js。

      import Vue from 'vue'
      import VueRouter from 'vue-router'
      import Home from '@/views/Home/CompHome.vue'
      import User from '@/views/User/CompUser.vue'
      
      Vue.use(VueRouter)
      
      // 路由规则配置
      const routes = [
        // 定义“首页”的路由规则
        { path: '/', component: Home },
        { path: '/home', component: Home },
        // 定义“我的”的路由规则
        { path: '/user', component: User },
      ]
      
      const router = new VueRouter({
        routes
      })
      
      export default router
      
      

    VueX使用

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

    1. Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
    2. 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

    操作步骤:

    1. 新建VUE CLI工程,带路由、Vuex和less功能。

    2. 配置初始工程。

      1. components/HelloWorld.vue中,删除多余的展示内容,只保留msg的信息。
      2. views/HomeView.vue中,只保留<HelloWorld msg="Welcome to Your Vue.js App"/>的展示内容。

    state

    1. store/index.js中新增一个状态num。

      import Vue from 'vue'
      import Vuex from 'vuex'
      
      Vue.use(Vuex)
      
      export default new Vuex.Store({
        // state相当于组件中的data,专门用来存放全局的数据
        state: { // 用来存放数据,类似于组件中的data
          num: 0,
        },
        getters: { // 计算属性,类似于组件中的computed
        },
        mutations: { // 存放方法,类似于组件中的methods
        },
        actions: { // Action类似于mutation,不同在于:Action提交的是mutation,而不是直接变更状态
        },
        modules: {
        }
      })
      
      
    2. 修改App.vue内容。

      <template>
        <div id="app">
          <h3>App根组件的数字:{{ num }}</h3>
          <nav>
            <router-link to="/">Home</router-link> |
            <router-link to="/about">About</router-link>
          </nav>
          <router-view />
        </div>
      </template>
      
      <script>
      import store from "./store";
      export default {
        computed: {
          num() {
            return store.state.num;
          }
        },
      };
      </script>
      
      <style lang="less">
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
      }
      
      nav {
        padding: 30px;
      
        a {
          font-weight: bold;
          color: #2c3e50;
      
          &.router-link-exact-active {
            color: #42b983;
          }
        }
      }
      </style>
      
      
    3. 修改components/HomeView.vue内容。

      <template>
        <div class="home">
          <HelloWorld msg="Welcome to Your Vue.js App"/>
          <p>Home页面的数字:{{ $store.state.num }}</p>
        </div>
      </template>
      
      <script>
      // @ is an alias to /src
      import HelloWorld from '@/components/HelloWorld.vue'
      
      export default {
        name: 'HomeView',
        components: {
          HelloWorld
        }
      }
      </script>
      
      
    4. 修改components/AboutView.vue内容(通过计算属性的方式进行展示)。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <p>About页面的数字:{{ num }}</p>
        </div>
      </template>
      
      <script>
      export default {
        data() {
          return {}
        },
        computed: {
          num() {
            return this.$store.state.num;
          },
        },
      };
      </script>
      
      

    getters

    将组件中统一使用的computed都放到getters里面来操作。

    1. 修改store/index.js内容。

      import Vue from 'vue'
      import Vuex from 'vuex'
      
      Vue.use(Vuex)
      
      export default new Vuex.Store({
        // state相当于组件中的data,专门用来存放全局的数据
        state: {
          num: 10,
          toDos: [
            { id: 1, text: 'text1...', done: true },
            { id: 2, text: 'text2...', done: false },
            { id: 3, text: 'text3...', done: true },
          ],
        },
        // getters相当于组件中的computed,getters是全局的,computed是组件内部使用的
        getters: {
          // 通过属性访问
          getNum(state) {
            return state.num;
          },
          // 通过属性访问
          doneTodos(state) {
            return state.toDos.filter((todo) => todo.done);
          },
       // 通过属性访问
          // Getter 也可以接受其他 getter 作为第二个参数
          getTodosCount(state, getters) {
            return getters.doneTodos.length;
          },
          // 通过方法访问
          getTodoById: (state) => (id) => {
            return state.toDos.filter((todo) => todo.id === id);
          }
        },
        mutations: {
        },
        actions: {
        },
        modules: {
        }
      })
      
      
    2. 修改App.vue内容。

      <template>
        <div id="app">
          <h3>App根组件的数字:{{ $store.getters.getNum }}</h3>
          <nav>
            <router-link to="/">Home</router-link> |
            <router-link to="/about">About</router-link>
          </nav>
          <router-view />
        </div>
      </template>
      
      <script>
      
      export default {};
      </script>
      
      <style lang="less">
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
      }
      
      nav {
        padding: 30px;
      
        a {
          font-weight: bold;
          color: #2c3e50;
      
          &.router-link-exact-active {
            color: #42b983;
          }
        }
      }
      </style>
      
      
    3. 修改components/HomeView.vue内容。

      <template>
        <div class="home">
          <HelloWorld msg="Welcome to Your Vue.js App" />
          <p>Home页面的数字:{{ $store.getters.getNum }}</p>
          <p>doneTodos:{{ $store.getters.doneTodos }}</p>
          <p>getTodosCount:{{ $store.getters.getTodosCount }}</p>
          <p>getTodoById:{{ $store.getters.getTodoById(2) }}</p>
        </div>
      </template>
      
      <script>
      // @ is an alias to /src
      import HelloWorld from "@/components/HelloWorld.vue";
      
      export default {
        name: "HomeView",
        components: {
          HelloWorld,
        },
      };
      </script>
      
      
    4. 修改components/AboutView.vue内容(通过计算属性的方式进行展示)。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <p>About页面的数字:{{ num }}</p>
          <p>getTodosCount:{{ getTodosCount }}</p>
        </div>
      </template>
      
      <script>
      export default {
        data() {
          return {};
        },
        computed: {
          num() {
            return this.$store.getters.getNum;
          },
          getTodosCount() {
            return this.$store.getters.getTodosCount;
          },
        },
      };
      </script>
      
      

    mutations

    更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler) 。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。

    一条重要的原则就是要记住 mutation 必须是同步函数

    提交负荷

    1. 修改store/index.js内容。

      import Vue from 'vue'
      import Vuex from 'vuex'
      
      Vue.use(Vuex)
      
      export default new Vuex.Store({
        // state相当于组件中的data,专门用来存放全局的数据
        state: {
          num: 10,
          toDos: [
            { id: 1, text: 'text1...', done: true },
            { id: 2, text: 'text2...', done: false },
            { id: 3, text: 'text3...', done: true },
          ],
        },
        // getters相当于组件中的computed,getters是全局的,computed是组件内部使用的
        getters: {
          // 通过属性访问
          getNum(state) {
            return state.num;
          },
          // 通过属性访问
          doneTodos(state) {
            return state.toDos.filter((todo) => todo.done);
          },
          // 通过属性访问
          // Getter 也可以接受其他 getter 作为第二个参数
          getTodosCount(state, getters) {
            return getters.doneTodos.length;
          },
          // 通过方法访问
          getTodoById: (state) => (id) => {
            return state.toDos.filter((todo) => todo.id === id);
          }
        },
        mutations: {
          numPlusOne(state) {
            state.num++;
          },
          // 可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload)
          numPlusX(state, x) {
            // x (payload)是一个形参,如果组件在commit时,有传这个参数过来,就存在,如果没有传过来,就是undefined
            state.num += x ? x : 1;
          },
          // 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读
          numPlusObjectX(state, payload) {
            state.num += payload.amount;
          }
        },
        actions: {
        },
        modules: {
        }
      })
      
      
    2. 修改views/HomeView.vue内容。

      <template>
        <div class="home">
          <HelloWorld msg="Welcome to Your Vue.js App" />
          <p>Home页面的数字:{{ $store.getters.getNum }}</p>
          <p>doneTodos:{{ $store.getters.doneTodos }}</p>
          <p>getTodosCount:{{ $store.getters.getTodosCount }}</p>
          <p>getTodoById:{{ $store.getters.getTodoById(2) }}</p>
      
          <button @click="numPlusOne">num数值+1</button>
          <button @click="$store.commit('numPlusX', 2)">num数值+X</button>
          <button @click="$store.commit('numPlusObjectX', { amount: 3 })">num数值+ObjX</button>
          <button @click="$store.commit({type: 'numPlusObjectX', amount: 3 })">num数值+ObjX(对象风格提交方式)</button>
        </div>
      </template>
      
      <script>
      // @ is an alias to /src
      import HelloWorld from "@/components/HelloWorld.vue";
      
      export default {
        name: "HomeView",
        components: {
          HelloWorld,
        },
        methods: {
          numPlusOne() {
            this.$store.commit("numPlusOne");
          },
        },
      };
      </script>
      
      

    actions

    actions是store中专门用来处理异步的,实际修改状态值的,还是mutations。

    Action类似于mutation,不同在于:

    • Action提交的是mutation,而不是直接变更状态。
    • Action可以包含任意异步操作。

    Action 函数接受一个与 store 实例具有相同方法和属性的context对象,因此你可以调用context.commit 提交一个mutation,或者通过context.statecontext.getters 来获取state和getters。

    1. 修改store/index.js内容。

      import Vue from 'vue'
      import Vuex from 'vuex'
      
      Vue.use(Vuex)
      
      export default new Vuex.Store({
        // state相当于组件中的data,专门用来存放全局的数据
        state: {
          num: 10,
          toDos: [
            { id: 1, text: 'text1...', done: true },
            { id: 2, text: 'text2...', done: false },
            { id: 3, text: 'text3...', done: true },
          ],
        },
        // getters相当于组件中的computed,getters是全局的,computed是组件内部使用的
        getters: {
          // 通过属性访问
          getNum(state) {
            return state.num;
          },
          // 通过属性访问
          doneTodos(state) {
            return state.toDos.filter((todo) => todo.done);
          },
          // 通过属性访问
          // Getter 也可以接受其他 getter 作为第二个参数
          getTodosCount(state, getters) {
            return getters.doneTodos.length;
          },
          // 通过方法访问
          getTodoById: (state) => (id) => {
            return state.toDos.filter((todo) => todo.id === id);
          }
        },
        mutations: {
          numPlusOne(state) {
            state.num++;
          },
          // 可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload)
          numPlusX(state, x) {
            // x (payload)是一个形参,如果组件在commit时,有传这个参数过来,就存在,如果没有传过来,就是undefined
            state.num += x ? x : 1;
          },
          // 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读
          numPlusObjectX(state, payload) {
            state.num += payload.amount;
          },
        },
        actions: {
          // Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
          // 因此可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters
          numPlusOneDelayNSec(context, nSec) {
            console.log("numPlusOneDelayNSec ing.");
            setTimeout(() => {
              context.commit('numPlusOne');
              console.log("numPlusOnesDelayNSec done.");
            }, nSec)
          },
          numPlusObjectXDelayNSec(context, payload) {
            console.log("numPlusObjectXDelayNSec ing.");
            setTimeout(() => {
              console.log('numPlusObjectXDelayNSec ing payload: ' + JSON.stringify(payload));
              console.log("numPlusObjectXDelayNSec done.");
            }, payload.nSec)
          }
        },
        modules: {
        }
      })
      
      
    2. 修改views/HomeView.vue内容。

      <template>
        <div class="home">
          <HelloWorld msg="Welcome to Your Vue.js App" />
          <p>Home页面的数字:{{ $store.getters.getNum }}</p>
          <p>doneTodos:{{ $store.getters.doneTodos }}</p>
          <p>getTodosCount:{{ $store.getters.getTodosCount }}</p>
          <p>getTodoById:{{ $store.getters.getTodoById(2) }}</p>
      
          <button @click="numPlusOne">num数值+1</button>
          <button @click="$store.commit('numPlusX', 2)">num数值+X</button>
          <button @click="$store.commit('numPlusObjectX', { amount: 3 })">
            num数值+ObjX
          </button>
          <button @click="$store.commit({ type: 'numPlusObjectX', amount: 3 })">
            num数值+ObjX(对象风格提交方式)
          </button>
      
          <button @click="$store.dispatch('numPlusOneDelayNSec', 2000)">
            numPlusOneDelayNSec(actions异步实现)
          </button>
          <!-- 以载荷形式分发 -->
          <button
            @click="
              $store.dispatch('numPlusObjectXDelayNSec', {
                nSec: 2000,
                amount: 10,
              })
            "
          >
            numPlusObjectXDelayNSec(actions异步实现)
          </button>
          <!-- 以对象形式分发 -->
          <button
            @click="
              $store.dispatch({
                type: 'numPlusObjectXDelayNSec',
                nSec: 2000,
                amount: 10,
              })
            "
          >
            numPlusObjectXDelayNSec(actions异步实现)
          </button>
        </div>
      </template>
      
      <script>
      // @ is an alias to /src
      import HelloWorld from "@/components/HelloWorld.vue";
      
      export default {
        name: "HomeView",
        components: {
          HelloWorld,
        },
        methods: {
          numPlusOne() {
            this.$store.commit("numPlusOne");
          },
        },
      };
      </script>
      
      

    辅助函数 *

    mapState和mapGetters在组件中都是写在computed里面。

    mapMutations和mapActions在组件中都是写在methods里面。

    1. store/index.js内容不变。

      import Vue from 'vue'
      import Vuex from 'vuex'
      
      Vue.use(Vuex)
      
      export default new Vuex.Store({
        // state相当于组件中的data,专门用来存放全局的数据
        state: {
          num: 10,
          toDos: [
            { id: 1, text: 'text1...', done: true },
            { id: 2, text: 'text2...', done: false },
            { id: 3, text: 'text3...', done: true },
          ],
        },
        // getters相当于组件中的computed,getters是全局的,computed是组件内部使用的
        getters: {
          // 通过属性访问
          getNum(state) {
            return state.num;
          },
          // 通过属性访问
          doneTodos(state) {
            return state.toDos.filter((todo) => todo.done);
          },
          // 通过属性访问
          // Getter 也可以接受其他 getter 作为第二个参数
          getTodosCount(state, getters) {
            return getters.doneTodos.length;
          },
          // 通过方法访问
          getTodoById: (state) => (id) => {
            return state.toDos.filter((todo) => todo.id === id);
          }
        },
        mutations: {
          numPlusOne(state) {
            state.num++;
          },
          // 可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload)
          numPlusX(state, x) {
            // x (payload)是一个形参,如果组件在commit时,有传这个参数过来,就存在,如果没有传过来,就是undefined
            state.num += x ? x : 1;
          },
          // 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读
          numPlusObjectX(state, payload) {
            state.num += payload.amount;
          },
        },
        actions: {
          // Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
          // 因此可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters
          numPlusOneDelayNSec(context, nSec) {
            console.log("numPlusOneDelayNSec ing.");
            setTimeout(() => {
              context.commit('numPlusOne');
              console.log("numPlusOnesDelayNSec done.");
            }, nSec)
          },
          numPlusObjectXDelayNSec(context, payload) {
            console.log("numPlusObjectXDelayNSec ing.");
            setTimeout(() => {
              console.log('numPlusObjectXDelayNSec ing payload: ' + JSON.stringify(payload));
              console.log("numPlusObjectXDelayNSec done.");
            }, payload.nSec)
          }
        },
        modules: {
        }
      })
      
      
    2. 修改views/HomeView.vue内容。

      <template>
        <div class="home">
          <HelloWorld msg="Welcome to Your Vue.js App" />
          <p>Home页面的数字:{{ getNum }}</p>
          <p>doneTodos:{{ doneTodos }}</p>
          <p>getTodosCount:{{ getTodosCount }}</p>
          <p>getTodoById:{{ getTodoById(2) }}</p>
      
          <button @click="numPlusOne">num数值+1</button>
          <button @click="numPlusX(2)">num数值+X</button>
          <button @click="numPlusObjectX({ amount: 3 })">num数值+ObjX</button>
          <button @click="numPlusObjectX({ amount: 3 })">
            num数值+ObjX(对象风格提交方式)
          </button>
      
          <button @click="numPlusOneDelayNSec(2000)">
            numPlusOneDelayNSec(actions异步实现)
          </button>
          <button @click="numPlusOneDelayNSecAlias(2000)">
            numPlusOneDelayNSecAlias(actions异步实现)
          </button>
          <button
            @click="
              numPlusObjectXDelayNSec({
                nSec: 2000,
                amount: 10,
              })
            "
          >
            numPlusObjectXDelayNSec(actions异步实现)
          </button>
        </div>
      </template>
      
      <script>
      // @ is an alias to /src
      import HelloWorld from "@/components/HelloWorld.vue";
      
      import { mapState, mapGetters } from "vuex";
      import { mapMutations, mapActions } from "vuex";
      
      export default {
        name: "HomeView",
        components: {
          HelloWorld,
        },
        computed: {
          ...mapState(["num"]),
          ...mapGetters(["getNum", "doneTodos", "getTodosCount", "getTodoById"]),
        },
        methods: {
          ...mapMutations(["numPlusOne", "numPlusX", "numPlusObjectX"]),
          ...mapActions([
            "numPlusOneDelayNSec", // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
            "numPlusObjectXDelayNSec",
          ]),
          ...mapActions({
            numPlusOneDelayNSecAlias: "numPlusOneDelayNSec", // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
          }),
      
          numPlusOne() {
            this.$store.commit("numPlusOne");
          },
        },
      };
      </script>
      
      
    3. 修改views/AboutView.vue内容。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <p>About页面的数字:{{ num }}</p>
          <p>getTodosCount:{{ getTodosCount }}</p>
        </div>
      </template>
      
      <script>
      import { mapState, mapGetters } from "vuex";
      import { mapMutations, mapActions } from "vuex";
      
      export default {
        data() {
          return {};
        },
        computed: {
          ...mapState(["num"]),
          ...mapGetters(["getNum", "doneTodos", "getTodosCount", "getTodoById"]),
        },
        methods: {
          ...mapMutations(["numPlusOne", "numPlusX", "numPlusObjectX"]),
          ...mapActions(["numPlusOneDelayNSec", "numPlusObjectXDelayNSec"]),
        },
      };
      </script>
      
      

    拆分写法以及使用常量替代Mutation事件类型*

    1. 在store目录下新建文件mutations_type.js。

      export const MUTATIONS_TYPE = {
          NUM_PLUS_ONE: 'NUM_PLUS_ONE',
          NUM_PLUS_X: 'NUM_PLUS_X',
          NUM_PLUS_OBJECT_X: 'NUM_PLUS_OBJECT_X',
      }
      
      export default {
          [MUTATIONS_TYPE.NUM_PLUS_ONE](state) {
              state.num++;
          },
          // 可以向 store.commit 传入额外的参数,即 mutation 的载荷(payload)
          [MUTATIONS_TYPE.NUM_PLUS_X](state, x) {
              // x (payload)是一个形参,如果组件在commit时,有传这个参数过来,就存在,如果没有传过来,就是undefined
              state.num += x ? x : 1;
          },
          // 在大多数情况下,载荷应该是一个对象,这样可以包含多个字段并且记录的 mutation 会更易读
          [MUTATIONS_TYPE.NUM_PLUS_OBJECT_X](state, payload) {
              state.num += payload.amount;
          }
      }
      
      
    2. 在store目录下新建文件actions.js。

      /* eslint-disable */
      
      const actions = {
          // Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象
          // 因此可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters
          numPlusOneDelayNSec(context, nSec) {
              console.log("numPlusOneDelayNSec ing.");
              setTimeout(() => {
                  context.commit('NUM_PLUS_ONE');
                  console.log("numPlusOnesDelayNSec done.");
              }, nSec)
          },
          numPlusObjectXDelayNSec(context, payload) {
              console.log("NUM_PLUS_OBJECT_X ing.");
              setTimeout(() => {
                  console.log('NUM_PLUS_OBJECT_X ing payload: ' + JSON.stringify(payload));
                  console.log("NUM_PLUS_OBJECT_X done.");
              }, payload.nSec)
          },
      
          // 组合 Action
          // store.dispatch 可以处理被触发的 action 的处理函数返回的 Promise,并且 store.dispatch
          makeUpActionA({ commit }) {
              // 返回一个 promise 对象
              return new Promise((resolve, reject) => {
                  setTimeout(() => {
                      commit('NUM_PLUS_ONE');
                      resolve(); // 跟一般 promise 的使用差别不大
                  }, 1000);
              })
          },
          makeUpActionB({ commit, dispatch }) {
              console.log("actions of makeUpActionA ing.")
              return dispatch('makeUpActionA').then(() => {
                  // commit('someOtherMutation');
                  console.log("actions of makeUpActionA done.")
              })
          },
      
          // 如果我们利用 async / await,我们可以如下组合 action
          // 一个 store.dispatch 在不同模块中可以触发多个 action 函数。
          // 在这种情况下,只有当所有触发函数完成后,返回的 Promise 才会执行。
          async actionA({ commit }) {
              commit('gotData', await getData());
          },
          async actionB({ dispatch, commit }) {
              await dispatch('actionA'); // 等待 actionA 完成
              commit('gotOtherData', await getOtherData());
          }
      };
      
      export default actions;
      
      
    3. 修改store/index.js内容。

      import Vue from 'vue'
      import Vuex from 'vuex'
      
      import mutations from '@/store/mutation-types'
      import actions from '@/store/actions'
      
      Vue.use(Vuex)
      
      export default new Vuex.Store({
        // state相当于组件中的data,专门用来存放全局的数据
        state: {
          num: 10,
          toDos: [
            { id: 1, text: 'text1...', done: true },
            { id: 2, text: 'text2...', done: false },
            { id: 3, text: 'text3...', done: true },
          ],
        },
        // getters相当于组件中的computed,getters是全局的,computed是组件内部使用的
        getters: {
          // 通过属性访问
          getNum(state) {
            return state.num;
          },
          // 通过属性访问
          doneTodos(state) {
            return state.toDos.filter((todo) => todo.done);
          },
          // 通过属性访问
          // Getter 也可以接受其他 getter 作为第二个参数
          getTodosCount(state, getters) {
            return getters.doneTodos.length;
          },
          // 通过方法访问
          getTodoById: (state) => (id) => {
            return state.toDos.filter((todo) => todo.id === id);
          }
        },
        mutations,
        actions,
        modules: {
        }
      })
      
      
    4. 修改App.vue文件。

      <template>
        <div id="app">
          <h3>App根组件的数字:{{ getNum }}</h3>
          <nav>
            <router-link to="/">Home</router-link> |
            <router-link to="/about">About</router-link>
          </nav>
          <router-view />
        </div>
      </template>
      
      <script>
      import { mapState, mapGetters } from "vuex";
      
      export default {
        computed: {
          ...mapState(['num']),
          ...mapGetters(['getNum', 'doneTodos', 'getTodosCount', 'getTodoById']),
        },
      };
      </script>
      
      <style lang="less">
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
      }
      
      nav {
        padding: 30px;
      
        a {
          font-weight: bold;
          color: #2c3e50;
      
          &.router-link-exact-active {
            color: #42b983;
          }
        }
      }
      </style>
      
      
    5. 修改views/HomeView.vue文件。

      <template>
        <div class="home">
          <HelloWorld msg="Welcome to Your Vue.js App" />
          <p>Home页面的数字:{{ getNum }}</p>
          <p>doneTodos:{{ doneTodos }}</p>
          <p>getTodosCount:{{ getTodosCount }}</p>
          <p>getTodoById:{{ getTodoById(2) }}</p>
      
          <button @click="NUM_PLUS_ONE">num数值+1</button>
          <button @click="NUM_PLUS_X(2)">num数值+X</button>
          <button @click="NUM_PLUS_OBJECT_X({ amount: 3 })">num数值+ObjX</button>
          <button @click="NUM_PLUS_OBJECT_X({ amount: 3 })">
            num数值+ObjX(对象风格提交方式)
          </button>
      
          <button @click="numPlusOneDelayNSec(2000)">
            numPlusOneDelayNSec(actions异步实现)
          </button>
          <button @click="numPlusOneDelayNSecAlias(2000)">
            numPlusOneDelayNSecAlias(actions异步实现)
          </button>
          <button
            @click="
              numPlusObjectXDelayNSec({
                nSec: 2000,
                amount: 10,
              })
            "
          >
            numPlusObjectXDelayNSec(actions异步实现)
          </button>
          <button @click="makeUpActionB">组合Action</button>
        </div>
      </template>
      
      <script>
      // @ is an alias to /src
      import HelloWorld from "@/components/HelloWorld.vue";
      
      import { mapState, mapGetters } from "vuex";
      import { mapMutations, mapActions } from "vuex";
      
      export default {
        name: "HomeView",
        components: {
          HelloWorld,
        },
        computed: {
          ...mapState(['num']),
          ...mapGetters(['getNum', 'doneTodos', 'getTodosCount', 'getTodoById']),
        },
        methods: {
          ...mapMutations(['NUM_PLUS_ONE', 'NUM_PLUS_X', 'NUM_PLUS_OBJECT_X']),
          ...mapActions([
            'numPlusOneDelayNSec', // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
            'numPlusObjectXDelayNSec',
            'makeUpActionB'
          ]),
          ...mapActions({
            numPlusOneDelayNSecAlias: 'numPlusOneDelayNSec', // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
          }),
      
          numPlusOne() {
            this.$store.commit('numPlusOne');
          },
        },
      };
      </script>
      
      
    6. 修改views/AboutView.vue文件。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <p>About页面的数字:{{ num }}</p>
          <p>getTodosCount:{{ getTodosCount }}</p>
        </div>
      </template>
      
      <script>
      import { mapState, mapGetters } from "vuex";
      import { mapMutations, mapActions } from "vuex";
      
      export default {
        data() {
          return {};
        },
        computed: {
          ...mapState(['num']),
          ...mapGetters(['getNum', 'doneTodos', 'getTodosCount', 'getTodoById']),
        },
        methods: {
          ...mapMutations(['NUM_PLUS_ONE', 'NUM_PLUS_X', 'NUM_PLUS_OBJECT_X']),
          ...mapActions(['numPlusOneDelayNSec', 'numPlusObjectXDelayNSec']),
        },
      };
      </script>
      
      

    module

    默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间的——这样使得多个模块能够对同一个 action 或 mutation 作出响应。Getter 同样也默认注册在全局命名空间,但是目前这并非出于功能上的目的(仅仅是维持现状来避免非兼容性变更)。必须注意,不要在不同的、无命名空间的模块中定义两个相同的 getter 从而导致错误。

    如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。

    操作步骤:

    1. 新建VUE CLI工程,带路由、Vuex和less功能。

    2. 配置初始工程。

      1. components/HelloWorld.vue中,删除多余的展示内容,只保留msg的信息。
      2. views/HomeView.vue中,只保留<HelloWorld msg="Welcome to Your Vue.js App"/>的展示内容。
    3. 在store目录下新建文件moduleA.js。

      const moduleA = {
          namespaced: true,
          state: {
              goods: [1, 2, 3, 4]
          },
          getters: {
          },
          mutations: {
              ADD_GOODS(state) {
                  state.goods.push(state.goods.length + 1);
              }
          },
          actions: {
              addGoodsDelay(context) {
                  setTimeout(() => {
                      context.commit('ADD_GOODS');
                  }, 1000);
              }
          }
      };
      
      export default moduleA;
      
    4. 在store目录下新建文件moduleB.js。

      const moduleB = {
          namespaced: true,
          state: {
              list: ['A', 'B', 'C']
          },
          getters: {
          },
          mutations: {
              ADD_LIST(state) {
                  state.list.push(state.list.length + 1);
              }
          },
          actions: {
              addListDelay(context) {
                  setTimeout(() => {
                      context.commit('ADD_LIST');
                  }, 1000);
              }
          }
      };
      
      export default moduleB;
      
    5. 修改store/index.js内容。

      import Vue from 'vue'
      import Vuex from 'vuex'
      import moduleA from '@/store/moduleA'
      import moduleB from '@/store/moduleB'
      
      Vue.use(Vuex)
      
      export default new Vuex.Store({
        state: {
        },
        getters: {
        },
        mutations: {
        },
        actions: {
        },
        modules: {
          moduleA,
          moduleB
        }
      })
      
      
    6. 修改views/HomeView.vue内容。

      <template>
        <div class="home">
          <HelloWorld msg="Welcome to Your Vue.js App" />
          <p v-for="item in goods" :key="item">{{ item }}</p>
          <button @click="ADD_GOODS">moduleA ADD_GOODS</button>
          <button @click="addGoodsDelay">moduleA addGoodsDelay</button>
        </div>
      </template>
      
      <script>
      // @ is an alias to /src
      import HelloWorld from "@/components/HelloWorld.vue";
      import { mapState } from "vuex";
      import { mapMutations, mapActions } from "vuex";
      
      export default {
        name: "HomeView",
        components: {
          HelloWorld,
        },
        computed: {
          ...mapState("moduleA", ["goods"]),
        },
        methods: {
          ...mapMutations("moduleA", ["ADD_GOODS"]),
          ...mapActions("moduleA", ["addGoodsDelay"]),
        },
      };
      </script>
      
      
    7. 修改views/AboutView.vue内容。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <p v-for="item in list" :key="item">{{ item }}</p>
          <button @click="ADD_LIST">moduleA ADD_LIST</button>
          <button @click="addListDelay">moduleA addListDelay</button>
        </div>
      </template>
      
      <script>
      import { mapState } from "vuex";
      import { mapMutations, mapActions } from "vuex";
      
      export default {
        name: "AboutView",
        computed: {
          ...mapState("moduleB", ["list"]),
        },
        methods: {
          ...mapMutations("moduleB", ["ADD_LIST"]),
          ...mapActions({
            addListDelay: "moduleB/addListDelay", // 对象格式
          }),
        },
      };
      </script>
      
      

    mutations结合axios示例

    需求背景:

    初始加载Home页面发起一个请求,并将请求的结果保存下来。

    进入About页面时候,展示在Home页面请求数据的结果内容。

    操作步骤:

    1. 新建VUE CLI工程,带路由、Vuex和less功能。

    2. 配置初始工程。

      1. components/HelloWorld.vue中,删除多余的展示内容,只保留msg的信息。
      2. views/HomeView.vue中,只保留<HelloWorld msg="Welcome to Your Vue.js App"/>的展示内容。
    3. 安装axios(npm run axios)。

    4. 在views/HomeView.vue中导入axios请求。

      ...
      <script>
      import axios from 'axios'
      ...
      </script>
      
    5. 修改store/index.js内容。

      import Vue from 'vue'
      import Vuex from 'vuex'
      
      Vue.use(Vuex)
      
      export default new Vuex.Store({
        state: {
          a: 1,
          axiosRes: {},
        },
        getters: {
        },
        mutations: {
          INIT_DATA(state, payload) {
            state.axiosRes = payload;
          },
          CHANGE_A2B(state) {
            state.a = 'BCD';
          },
        },
        actions: {
        },
        modules: {
        }
      })
      
      
    6. 修改views/HomeView.vue内容。

      <template>
        <div class="home">
          <HelloWorld msg="Welcome to Your Vue.js App" />
          <p>a: {{ a }}</p>
          <p>axiosRes: {{ axiosRes }}</p>
      
          <button @click="CHANGE_A2B">CHANGE_A2B</button>
        </div>
      </template>
      
      <script>
      // @ is an alias to /src
      import HelloWorld from "@/components/HelloWorld.vue";
      import axios from "axios";
      import { mapState } from "vuex";
      import { mapMutations } from "vuex";
      
      export default {
        name: "HomeView",
        components: {
          HelloWorld,
        },
        created() {
          this.getData();
        },
        computed: {
          ...mapState(["a", "axiosRes"]),
        },
        methods: {
          ...mapMutations(["INIT_DATA", "CHANGE_A2B"]),
          getData() {
            axios
              .post("http://liulongbin.top:3006/api/post", { page: 1, sie: 3 })
              .then((res) => {
                console.log(res);
                this.INIT_DATA(res);
              });
          },
        },
      };
      </script>
      
      
    7. 修改views/AboutView.vue内容。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <p>axiosRes: {{ axiosRes }}</p>
        </div>
      </template>
      
      <script>
      import { mapState } from "vuex";
      
      export default {
        computed: {
          ...mapState(["axiosRes"]),
        },
      };
      </script>
      
      

    actions实现全选示例

    操作步骤:

    1. 新建VUE CLI工程,带Vuex和less功能。

    2. 配置初始工程。

      1. components/HelloWorld.vue中,删除多余的展示内容,只保留msg的信息。
      2. App.vue中,只保留<HelloWorld msg="Welcome to Your Vue.js App"/>的展示内容。
    3. 修改store/index.js内容。

      import Vue from 'vue'
      import Vuex from 'vuex'
      
      Vue.use(Vuex)
      
      export default new Vuex.Store({
        state: {
          list: [
            { checked: false },
            { checked: false },
            { checked: false },
            { checked: false },
            { checked: false },
            { checked: false },
            { checked: false },
          ],
          selectedList: [],
        },
        getters: {
          isCheckedAll(state) {
            return state.list.length === state.selectedList.length;
          }
        },
        mutations: {
          CHECK_ALL(state) {
            state.selectedList = state.list.map(v =>
              v.checked = true // 全部赋值为true
            )
          },
          UNCHECK_ALL(state) {
            state.selectedList = state.list.map(v =>
              v.checked = false // 全部赋值为false
            );
            state.selectedList = []; // 恢复默认值
          },
        },
        actions: {
          toggleCheckedAllFn({ commit, getters }) {
            getters.isCheckedAll ? commit('UNCHECK_ALL') : commit('CHECK_ALL');
          }
        },
        modules: {
        }
      })
      
      
    4. 修改components/HelloWorld.vue内容。

      <template>
        <div class="hello">
          <h1>{{ msg }}</h1>
          <ul>
            <li v-for="(item, index) in list" :key="index">
              <input type="radio" :checked="item.checked" /> {{ index }}
            </li>
          </ul>
          <label @click="toggleCheckedAllFn">
            <input type="radio" :checked="isCheckedAll" />
          </label>
        </div>
      </template>
      
      <script>
      import { mapState, mapGetters } from "vuex";
      import { mapMutations, mapActions } from "vuex";
      
      export default {
        name: "HelloWorld",
        props: {
          msg: String,
        },
        computed: {
          ...mapState(["list", "selectedList"]),
          ...mapGetters(["isCheckedAll"]),
        },
        methods: {
          ...mapMutations(["CHECK_ALL", "UNCHECK_ALL"]),
          ...mapActions(["toggleCheckedAllFn"]),
        },
      };
      </script>
      
      <!-- Add "scoped" attribute to limit CSS to this component only -->
      <style scoped lang="less">
      h3 {
        margin: 40px 0 0;
      }
      ul {
        list-style-type: none;
        padding: 0;
      }
      li {
        display: inline-block;
        margin: 0 10px;
      }
      a {
        color: #42b983;
      }
      </style>
      
      

    /---- Vue3.x ----/

    Vue3.x和Vue2.x版本的对比

    Vue2.x 中绝大多数的 API 与特性,在 Vue3.x 中同样支持。同时,Vue3.x 中还新增了 3.x 所特有的功能、并
    废弃了某些 2.x 中的旧功能:

    新增的功能例如:

    组合式 API、多根节点组件、更好的 TypeScript 支持等

    废弃的旧功能如下:

    过滤器、不再支持 $on,$off 和 $once 实例方法等

    详细的变更信息,请参考官方文档给出的迁移指南:

    https://v3.vuejs.org/guide/migration/introduction.html

    Vue3.x新特性介绍

    新特性介绍

    • 重写双向数据绑定
    • VDOM性能瓶颈
    • Fragments
    • Tree-Shaking的支持
    • Composition API

    Vite

    备注:目前只支持Vue3.x的项目。

    Vite创建Vue3.x项目【模板】

    Vite特征特点

    1. Vite冷服务启动,ES6 import。
    2. 开发中热更新。
    3. 按需进行编译,不会刷新全部DOM。

    操作步骤

    1. 新建项目文件夹ViteDemo(Vite项目工程存放的位置)。

    2. 执行以下命令,创建Vite工程项目。

      # 方式一
      npm create vite@latest <project-name>
      
      # 方式二(vite2.0版本)
      npm init @vitejs/app <project-name>
      
      # 方式三(vite1.0版本)
      npm init vite-app <project-name> # 项目名称不写时,默认为vite
      
      # 直接回车,按照提示操作即可
      
    3. 使用VS Code打开工程目录。

    4. 执行以下命令安装相关依赖。

      npm install
      
    5. 执行以下命令运行代码。

      npm run dev
      

    Vite对Typescript、CSS和JSON的支持

    1. 使用Vite搭建Vue3开发环境,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。

    2. 修改文件App.vue。验证Vite对Typescript的支持。

      <script lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      import HelloWorld from "./components/HelloWorld.vue";
      const tsVal: string = "Hello TS";
      
      export default {
        name: 'app',
        components: {
          HelloWorld,
        },
        mounted() {
          console.log(tsVal);
        },
      };
      </script>
      
      <template>
        <img alt="Vue logo" src="./assets/logo.png" />
        <HelloWorld msg="Hello Vue 3 + Vite" />
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    3. 在src\assets\目录中,新建文件app.css。验证Vite对CSS文件的支持。

      body {
        background-color: aqua;
      }
      
      
    4. 修改文件App.vue。

      <script lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      import HelloWorld from "./components/HelloWorld.vue";
      import "./assets/app.css";
      
      const tsVal: string = "Hello TS";
      
      export default {
        name: "app",
        components: {
          HelloWorld,
        },
        mounted() {
          console.log(tsVal);
        },
      };
      </script>
      
      <template>
        <img alt="Vue logo" src="./assets/logo.png" />
        <HelloWorld msg="Hello Vue 3 + Vite" />
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    5. 在src\assets\目录中,新建文件config.json。验证Vite对JSON文件的支持。

      {
          "name": "张三",
          "website": "www.baidu.com"
      }
      
    6. 修改文件App.vue。

      <script lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      import HelloWorld from "./components/HelloWorld.vue";
      import "./assets/app.css";
      import data from "./assets/config.jso
          n";
      
      const tsVal: string = "Hello TS";
      
      export default {
        name: "app",
        components: {
          HelloWorld,
        },
        mounted() {
          console.log(tsVal);
          console.log(`${data.name} | ${data.website}`);
        },
      };
      </script>
      
      <template>
        <img alt="Vue logo" src="./assets/logo.png" />
        <HelloWorld msg="Hello Vue 3 + Vite" />
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      

    Vite配置文件和别名设置

    基于上述的工程进行适配修改。

    在项目根目录中新建配置文件vite.config.js。

    const { resolve } = require('path');
    
    export default {
        alias: {
            '/@/': resolve(__dirname, 'src'),
        }
    }
    
    

    Vite安装和配置less【模板】

    1. 搭建完成Vite项目。

    2. 安装依赖包。

      npm i -D less-loader less
      
    3. 使用示例,新建XX.vue。

      <template>
          <div class="content-home">
              <h3>我是Home组件</h3>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style lang="less" scoped>
      .content-home {
           200px;
          height: 200px;
          background-color: aqua;
      }
      </style>
      
      

    Vue3.x组合式API

    创建Vue3.x项目

    Vue CLI创建Vue3.x项目【模板】

    1. 执行命令,新建一个工程

      vue create vue3-project-1
      
    2. 手动配置工程

      # Manually select features
      # 新增勾选“Typescript”、“Router”、“Vuex”和“CSS Pre-processors”
      # 以3.x为例,选择“3.x”
      # Use Babel alongside TypeScript,选择“Yes”
      # 选择“less”CSS预处理器
      # 选择“ESLint with error prevention only”或者“ESLint + Standard config”,根据需要选择
      # 最后咨询是否保存为模板,这里选择“N”
      

    Vite创建Vue3.x项目【模板】

    参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。

    setup函数

    新的 setup 选项在组件被创建之前执行,一旦 props 被解析完成,它就将被作为组合式 API 的入口。

    说明:在 setup 中你应该避免使用 this,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取。

    带ref的响应式变量

    在 Vue 3.0 中,我们可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用,如下所示:

    import { ref } from 'vue'
    
    const counter = ref(0)
    

    ref 接收参数并将其包裹在一个带有 value property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值:

    import { ref } from 'vue'
    
    const counter = ref(0)
    
    console.log(counter) // { value: 0 }
    console.log(counter.value) // 0
    
    counter.value++
    console.log(counter.value) // 1
    

    操作步骤

    1. 创建Vue3.x项目,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。

    2. 修改文件src\views\AboutView.vue。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <h3>01. setup函数介绍和使用</h3>
          <h3>
            Vue2.x中的数据:{{ title01 }}----
            <button @click="getVue3Data">获取一下setup中的数据</button>
          </h3>
          <h3>
            Vue3.x中的数据:{{ title02 }}
            <button @click="getVue2Data">获取一下data中的数据</button>
          </h3>
          <h3>
            <button @click="addCount">count++ == {{ count }}</button>
          </h3>
        </div>
      </template>
      
      <script>
      import { ref } from "vue";
      export default {
        // 1. setup执行的时期:在beforeCreate和created之前执行
        // 2. setup和options api对比
        // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法
        // 4. 在Vue2.x中可以获取到setup中的数据
        // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined
        // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法
        data() {
          return {
            title01: "我是Vue2中的data中的数据!",
            // count: 1,
          };
        },
        methods: {
          // addCount() {
          //   console.log("methods--addCount");
          //   this.count++;
          // },
          getVue3Data() {
            console.log("methods--getVue3Data, title02: " + this.title02);
          },
        },
        beforeCreate() {
          console.log("Life cycle--beforeCreate");
        },
        created() {
          console.log("Life cycle--created");
        },
        // setup是在created和beforeCreate生命周期之前触发
        setup(props) {
          // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的
          console.log("setup, props: ", props);
          let title02 = ref("我是setup中的数据!");
      
          // setup中无法获取到Vue2.x中的数据
          // setup中打印this也会是undefined
          function getVue2Data() {
            console.log("setup, function--getVue2Data, title01: " + this.title01);
          }
      
          let count = ref(0);
          function addCount() {
            count.value++;
          }
      
          // 这里返回的任何内容都可以用于组件的其余部分
          return {
            title02,
            getVue2Data,
            addCount,
            count,
          };
        },
      };
      </script>
      
      

      说明:

      1. 在Vue2.x中可以获取到setup中的数据。

      2. 在setup中无法获取到Vue2.x中的数据(在setup中获取this,打印出来是undefined)。

      3. Vue2.x中的方法如果跟setup中的方法冲突,优先选择setup中的方法。

      4. 上述的方式可以使用setup的语法糖形式。不需要进行导出操作。

        <script setup>
        // 使用了setup的语法糖
        import { ref, reactive } from "vue";
        
        const coffee = ref("卡布奇洛");
        const movie = reactive({
          name: "<strong>蝙蝠侠</strong>",
          price: 55,
        });
        
        const params = reactive({
          username: "admin",
          password: "123",
        });
        
        const gender = ref("Female");
        
        const isAgree = ref(false);
        </script>
        
    3. 验证setup中props参数使用场景。

      1. 在components目录下,新建文件TestSetUpProps.vue。

        <template>
          <div>
            <h1>测试子组件</h1>
          </div>
        </template>
        
        <script lang="ts">
        export default {
        
        }
        </script>
        
        <!-- Add "scoped" attribute to limit CSS to this component only -->
        <style scoped lang="less"></style>
        
        
      2. 修改文件src\views\AboutView.vue。补充两个父组件传递给子组件的内容。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
            <h3>01. setup函数介绍和使用</h3>
            <h3>
              Vue2.x中的数据:{{ title01 }} ----
              <button @click="getVue3Data">获取一下setup中的数据</button>
            </h3>
            <h3>
              Vue3.x中的数据:{{ title02 }}
              <button @click="getVue2Data">获取一下data中的数据</button>
            </h3>
            <h3>
              <button @click="addCount">count++ == {{ count }}</button>
            </h3>
        
            <hr />
        
            <TestSetUpProps :title03="title03" :cookie="cookie"></TestSetUpProps>
          </div>
        </template>
        
        <script>
        import { ref } from "vue";
        import TestSetUpProps from "../components/TestSetUpProps.vue";
        
        export default {
          // 1. setup执行的时期:在beforeCreate和created之前执行
          // 2. setup和options api对比
          // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法
          // 4. 在Vue2.x中可以获取到setup中的数据
          // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined
          // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法
          data() {
            return {
              title01: "我是Vue2中的data中的数据!",
              // count: 1,
            };
          },
          components: {
            TestSetUpProps,
          },
          methods: {
            // addCount() {
            //   console.log("methods--addCount");
            //   this.count++;
            // },
            getVue3Data() {
              console.log("methods--getVue3Data, title02: " + this.title02);
            },
          },
          beforeCreate() {
            console.log("Life cycle--beforeCreate");
          },
          created() {
            console.log("Life cycle--created");
          },
          // setup是在created和beforeCreate生命周期之前触发
          setup(props) {
            // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的
            console.log("setup, props: ", props);
            let title02 = ref("我是setup中的数据!");
        
            // setup中无法获取到Vue2.x中的数据
            // setup中打印this也会是undefined
            function getVue2Data() {
              console.log("setup, function--getVue2Data, title01: " + this.title01);
            }
        
            let count = ref(0);
            function addCount() {
              count.value++;
            }
        
            let title03 = ref("给子组件传递的数据!");
            let cookie = ref("奥利奥!");
        
            // 这里返回的任何内容都可以用于组件的其余部分
            return {
              title02,
              getVue2Data,
              addCount,
              count,
              title03,
              cookie,
            };
          },
        };
        </script>
        
        
      3. 修改文件components/TestSetUpProps.vue,接收和显示父组件传递过来的数据内容。

        <template>
          <div>
            <h1>测试子组件</h1>
            <h3>接收来自父组件传递过来的参数 ---- {{ newVal }}</h3>
          </div>
        </template>
        
        <script lang="ts">
        import { ref } from "vue";
        
        export default {
          // 子组件接收父组件传递值,一定要用props自定义属性进行接收
          props: {
            title03: {
              type: String,
              require: true,
            },
          },
          setup(props) {
            console.log(props);
        
            const newVal = ref(props.title03 + " + 经过setup处理之后的值!");
        
            return {
              newVal,
            };
          },
        };
        </script>
        
        <!-- Add "scoped" attribute to limit CSS to this component only -->
        <style scoped lang="less"></style>
        
        
    4. 验证setup中context参数使用场景,先说明context参数的attrs和slots属性。

      1. 修改文件components/TestSetUpProps.vue。

        <template>
          <div>
            <h1>测试子组件</h1>
            <h3>接收来自父组件传递过来的参数 ---- {{ newVal }}</h3>
            <slot name="one"></slot>
          </div>
        </template>
        
        <script lang="ts">
        import { ref } from "vue";
        
        export default {
          // 子组件接收父组件传递值,一定要用props自定义属性进行接收
          props: {
            title03: {
              type: String,
              require: true,
            },
          },
          setup(props, context) {
            // context表示上下文对象,包含attrs, slots, emit
            // 	attrs,外部传递过来的属性,并且没有在props中定义
            // 	slots,插槽
            // 	emit,分发自定义的事件,相当于$emit
            console.log("setup ing..");
            console.log(props);
            console.log(context);
            console.log(JSON.stringify(context.attrs)); // {"cookie":"奥利奥!"}
            console.log(context.slots); //
        
            const newVal = ref(props.title03 + " + 经过setup处理之后的值!");
        
            return {
              newVal,
            };
          },
        };
        </script>
        
        <!-- Add "scoped" attribute to limit CSS to this component only -->
        <style scoped lang="less"></style>
        
        
      2. 修改文件src\views\AboutView.vue。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
            <h3>01. setup函数介绍和使用</h3>
            <h3>
              Vue2.x中的数据:{{ title01 }} ----
              <button @click="getVue3Data">获取一下setup中的数据</button>
            </h3>
            <h3>
              Vue3.x中的数据:{{ title02 }}
              <button @click="getVue2Data">获取一下data中的数据</button>
            </h3>
            <h3>
              <button @click="addCount">count++ == {{ count }}</button>
            </h3>
        
            <hr />
        
            <TestSetUpProps :title03="title03" :cookie="cookie">
              <template v-slot:one>
                <h3>Hello demo, 自定义插槽!!</h3>
              </template>
            </TestSetUpProps>
          </div>
        </template>
        
        <script>
        import { ref } from "vue";
        import TestSetUpProps from "../components/TestSetUpProps.vue";
        
        export default {
          // 1. setup执行的时期:在beforeCreate和created之前执行
          // 2. setup和options api对比
          // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法
          // 4. 在Vue2.x中可以获取到setup中的数据
          // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined
          // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法
          data() {
            return {
              title01: "我是Vue2中的data中的数据!",
              // count: 1,
            };
          },
          components: {
            TestSetUpProps,
          },
          methods: {
            // addCount() {
            //   console.log("methods--addCount");
            //   this.count++;
            // },
            getVue3Data() {
              console.log("methods--getVue3Data, title02: " + this.title02);
            },
          },
          beforeCreate() {
            console.log("Life cycle--beforeCreate");
          },
          created() {
            console.log("Life cycle--created");
          },
          // setup是在created和beforeCreate生命周期之前触发
          setup(props) {
            // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的
            console.log("setup, props: ", props);
            let title02 = ref("我是setup中的数据!");
        
            // setup中无法获取到Vue2.x中的数据
            // setup中打印this也会是undefined
            function getVue2Data() {
              console.log("setup, function--getVue2Data, title01: " + this.title01);
            }
        
            let count = ref(0);
            function addCount() {
              count.value++;
            }
        
            let title03 = ref("给子组件传递的数据!");
            let cookie = ref("奥利奥!");
        
            // 这里返回的任何内容都可以用于组件的其余部分
            return {
              title02,
              getVue2Data,
              addCount,
              count,
              title03,
              cookie,
            };
          },
        };
        </script>
        
        
    5. 验证setup中context参数使用场景,说明context参数的emit属性。

      1. 修改文件components/TestSetUpProps.vue。

        <template>
          <div>
            <h1>测试子组件</h1>
            <h3>接收来自父组件传递过来的参数 ---- {{ newVal }}</h3>
            <slot name="one"></slot>
            <h3><button @click="send">子向父传值</button></h3>
          </div>
        </template>
        
        <script lang="ts">
        import { ref } from "vue";
        
        export default {
          // 子组件接收父组件传递值,一定要用props自定义属性进行接收
          props: {
            title03: {
              type: String,
              require: true,
            },
          },
          setup(props, context) {
            // context表示上下文对象,包含attrs, slots, emit
            // 	attrs,外部传递过来的属性,并且没有在props中定义
            // 	slots,插槽
            // 	emit,分发自定义的事件,相当于$emit
            console.log("setup ing..");
            console.log(props);
            console.log(context);
            console.log(JSON.stringify(context.attrs)); // {"cookie":"奥利奥!"}
            console.log(context.slots); //
        
            const newVal = ref(props.title03 + " + 经过setup处理之后的值!");
        
            const price = ref("100元"); // 传递给父组件的值内容
            function send() {
              context.emit("sendPrice", price);
            }
        
            return {
              newVal,
              price,
              send,
            };
          },
        };
        </script>
        
        <!-- Add "scoped" attribute to limit CSS to this component only -->
        <style scoped lang="less"></style>
        
        
      2. 修改文件src\views\AboutView.vue。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
            <h3>01. setup函数介绍和使用</h3>
            <h3>
              Vue2.x中的数据:{{ title01 }} ----
              <button @click="getVue3Data">获取一下setup中的数据</button>
            </h3>
            <h3>
              Vue3.x中的数据:{{ title02 }}
              <button @click="getVue2Data">获取一下data中的数据</button>
            </h3>
            <h3>
              <button @click="addCount">count++ == {{ count }}</button>
            </h3>
        
            <br />
            <h3>接收子组件传递出来的数据 ---- {{ gift }}</h3>
        
            <hr />
        
            <TestSetUpProps
              :title03="title03"
              :cookie="cookie"
              @sendPrice="receiveFromSubComp"
            >
              <template v-slot:one>
                <h3>Hello demo, 自定义插槽!!</h3>
              </template>
            </TestSetUpProps>
          </div>
        </template>
        
        <script>
        import { ref } from "vue";
        import TestSetUpProps from "../components/TestSetUpProps.vue";
        
        export default {
          // 1. setup执行的时期:在beforeCreate和created之前执行
          // 2. setup和options api对比
          // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法
          // 4. 在Vue2.x中可以获取到setup中的数据
          // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined
          // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法
          data() {
            return {
              title01: "我是Vue2中的data中的数据!",
              // count: 1,
            };
          },
          components: {
            TestSetUpProps,
          },
          methods: {
            // addCount() {
            //   console.log("methods--addCount");
            //   this.count++;
            // },
            getVue3Data() {
              console.log("methods--getVue3Data, title02: " + this.title02);
            },
          },
          beforeCreate() {
            console.log("Life cycle--beforeCreate");
          },
          created() {
            console.log("Life cycle--created");
          },
          // setup是在created和beforeCreate生命周期之前触发
          setup(props) {
            // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的
            console.log("setup, props: ", props);
            let title02 = ref("我是setup中的数据!");
        
            // setup中无法获取到Vue2.x中的数据
            // setup中打印this也会是undefined
            function getVue2Data() {
              console.log("setup, function--getVue2Data, title01: " + this.title01);
            }
        
            let count = ref(0);
            function addCount() {
              count.value++;
            }
        
            let title03 = ref("给子组件传递的数据!");
            let cookie = ref("奥利奥!");
        
            let gift = ref("0元");
            function receiveFromSubComp(val) {
              console.log("子组件传递过来的数据, val", val);
              console.log("子组件传递过来的数据, val.value", val.value);
              gift.value = val.value;
              console.log("gift", gift);
            }
        
            // 这里返回的任何内容都可以用于组件的其余部分
            return {
              title02,
              getVue2Data,
              addCount,
              count,
              title03,
              cookie,
              receiveFromSubComp,
              gift,
            };
          },
        };
        </script>
        
        

    ref

    说明:处理基本数据类型的数据。

    接受一个内部值并返回一个响应式且可变的ref对象。ref对象仅有一个.value property,指向该内部值。

    1. 创建Vue3.x项目,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。

    2. 修改文件src\views\AboutView.vue。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <h3>02. ref</h3>
          <p>当前的电影是:{{ title }}</p>
          <p>当前电影的价格是:{{ price }}</p>
          <p>电影的详情是: {{ info }}</p>
          <button @click="getMovieInfo">单击查看详情</button>
      
          <br /><br />
          <input type="text" v-model="mobile" />
          <p>mobile: {{ mobile }}</p>
      
          <br />
          <p>当前咖啡商品的名字是:{{ coffee.name }}</p>
          <p>当前咖啡商品的价格是:{{ coffee.price }}</p>
        </div>
      </template>
      
      <script>
      import { ref } from "vue";
      
      export default {
        // 1. setup执行的时期:在beforeCreate和created之前执行
        // 2. setup和options api对比
        // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法
        // 4. 在Vue2.x中可以获取到setup中的数据
        // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined
        // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法
        data() {
          return {};
        },
        components: {},
        methods: {},
        // setup是在created和beforeCreate生命周期之前触发
        setup(props, context) {
          // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的
          // context表示上下文对象,包含attrs, slots, emit
          // 	attrs,外部传递过来的属性,并且没有在props中定义
          // 	slots,插槽
          // 	emit,分发自定义的事件,相当于$emit
          // 这里返回的任何内容都可以用于组件的其余部分
          console.log(props);
          console.log(context);
      
          let title = "长津湖";
          let price = 56;
          let info = ref("抗美援朝的故事");
          function getMovieInfo() {
            info.value = `当前电影的名字是:${title},当前电影的价格是:${price},详情是:${info.value}`;
          }
      
          // ref实现数据的双向绑定,实际上是通过set和get的方法来实现的
          let mobile = ref("123");
      
          // 对象类型
          const coffee = ref({
            name: "瑞纳冰拿铁",
            price: 48,
          });
          console.log("coffee.value.name", coffee.value.name);
      
          return {
            title,
            price,
            info,
            getMovieInfo,
            mobile,
            coffee,
          };
        },
      };
      </script>
      
      
    3. 模拟登录注册的效果。

      1. 修改文件src\views\AboutView.vue。编写登录框架内容。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
            <h3>02. ref</h3>
            <p>当前的电影是:{{ title }}</p>
            <p>当前电影的价格是:{{ price }}</p>
            <p>电影的详情是: {{ info }}</p>
            <button @click="getMovieInfo">单击查看详情</button>
        
            <br /><br />
            <input type="text" v-model="mobile" />
            <p>mobile: {{ mobile }}</p>
        
            <br />
            <p>当前咖啡商品的名字是:{{ coffee.name }}</p>
            <p>当前咖啡商品的价格是:{{ coffee.price }}</p>
        
            <br />
            <p>用户名:<input type="text" v-model="loginParams.username" /></p>
            <p>密码:<input type="text" v-model="loginParams.password" /></p>
            <button @click="doLogin">登录</button>
            <p>login info: {{ loginParams }}</p>
          </div>
        </template>
        
        <script>
        import { ref } from "vue";
        
        export default {
          // 1. setup执行的时期:在beforeCreate和created之前执行
          // 2. setup和options api对比
          // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法
          // 4. 在Vue2.x中可以获取到setup中的数据
          // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined
          // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法
          data() {
            return {};
          },
          components: {},
          methods: {},
          // setup是在created和beforeCreate生命周期之前触发
          setup(props, context) {
            // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的
            // context表示上下文对象,包含attrs, slots, emit
            // 	attrs,外部传递过来的属性,并且没有在props中定义
            // 	slots,插槽
            // 	emit,分发自定义的事件,相当于$emit
            // 这里返回的任何内容都可以用于组件的其余部分
            console.log(props);
            console.log(context);
        
            let title = "长津湖";
            let price = 56;
            let info = ref("抗美援朝的故事");
            function getMovieInfo() {
              info.value = `当前电影的名字是:${title},当前电影的价格是:${price},详情是:${info.value}`;
            }
        
            // ref实现数据的双向绑定,实际上是通过set和get的方法来实现的
            let mobile = ref("123");
        
            // 对象类型
            const coffee = ref({
              name: "瑞纳冰拿铁",
              price: 48,
            });
            console.log("coffee.value.name", coffee.value.name);
        
            const loginParams = ref({
              username: "",
              password: "",
            });
        
            function doLogin() {
              console.log(loginParams.value);
            }
        
            return {
              title,
              price,
              info,
              getMovieInfo,
              mobile,
              coffee,
              loginParams,
              doLogin,
            };
          },
        };
        </script>
        
        
      2. 在根目录中执行以下命令,安装axios和qs。

        npm i -D axios qs
        
      3. 修改main.ts,挂在全局对象。

        import { createApp } from "vue";
        import App from "./App.vue";
        import router from "./router";
        import store from "./store";
        import axios from "axios";
        import qs from "qs";
        
        const app = createApp(App);
        
        // 挂载全局对象
        app.config.globalProperties.foo = "Hello world!";
        app.config.globalProperties.$http = axios;
        axios.defaults.baseURL = "http://bufantec.com/api/";
        
        // 请求拦截器
        axios.interceptors.request.use(
          (config) => {
            if (config.method?.toLocaleLowerCase() === "post") {
              config.data = qs.stringify(config.data);
            }
            return config;
          },
          (err) => {
            return Promise.reject(err);
          }
        );
        
        app.use(store).use(router).mount("#app");
        
        
      4. 修改文件src\views\AboutView.vue。编写请求内容。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
            <h3>02. ref</h3>
            <p>当前的电影是:{{ title }}</p>
            <p>当前电影的价格是:{{ price }}</p>
            <p>电影的详情是: {{ info }}</p>
            <button @click="getMovieInfo">单击查看详情</button>
        
            <br /><br />
            <input type="text" v-model="mobile" />
            <p>mobile: {{ mobile }}</p>
        
            <br />
            <p>当前咖啡商品的名字是:{{ coffee.name }}</p>
            <p>当前咖啡商品的价格是:{{ coffee.price }}</p>
        
            <br />
            <p>用户名:<input type="text" v-model="loginParams.username" /></p>
            <p>密码:<input type="text" v-model="loginParams.password" /></p>
            <button @click="doLogin">登录</button>
            <p>login info: {{ loginParams }}</p>
          </div>
        </template>
        
        <script>
        // ref:创建一个包含响应式数据的引用对象,xxx.value
        // ref可以接收基本数据类型和对象类型
        import { getCurrentInstance, ref } from "vue";
        
        export default {
          // 1. setup执行的时期:在beforeCreate和created之前执行
          // 2. setup和options api对比
          // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法
          // 4. 在Vue2.x中可以获取到setup中的数据
          // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined
          // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法
          data() {
            return {};
          },
          components: {},
          methods: {},
          // setup是在created和beforeCreate生命周期之前触发
          setup(props, context) {
            // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的
            // context表示上下文对象,包含attrs, slots, emit
            // 	attrs,外部传递过来的属性,并且没有在props中定义
            // 	slots,插槽
            // 	emit,分发自定义的事件,相当于$emit
            // 这里返回的任何内容都可以用于组件的其余部分
            console.log(props);
            console.log(context);
        
            // 接收挂载的全局变量
            // 例如proxy.poo就表示"Hello world!"
            // 例如proxy.$http就表示axios请求
            const { proxy } = getCurrentInstance();
        
            let title = "长津湖";
            let price = 56;
            let info = ref("抗美援朝的故事");
            function getMovieInfo() {
              info.value = `当前电影的名字是:${title},当前电影的价格是:${price},详情是:${info.value}`;
            }
        
            // ref实现数据的双向绑定,实际上是通过set和get的方法来实现的
            let mobile = ref("123");
        
            // 对象类型
            const coffee = ref({
              name: "瑞纳冰拿铁",
              price: 48,
            });
            console.log("coffee.value.name", coffee.value.name);
        
            const loginParams = ref({
              username: "",
              password: "",
            });
        
            function doLogin() {
              console.log("setup doLogin ing..");
              console.log(loginParams.value);
              proxy.$http.post("test/user/doLogin", loginParams.value).then((res) => {
                console.log(res);
              });
            }
        
            return {
              title,
              price,
              info,
              getMovieInfo,
              mobile,
              coffee,
              loginParams,
              doLogin,
            };
          },
        };
        </script>
        
        

    reactive

    说明:处理对象数据类型的数据。

    修改文件src\views\AboutView.vue。修改对象的处理内容。修改咖啡的信息为reactive方式进行处理。

    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <h3>02. ref</h3>
        <p>当前的电影是:{{ title }}</p>
        <p>当前电影的价格是:{{ price }}</p>
        <p>电影的详情是: {{ info }}</p>
        <button @click="getMovieInfo">单击查看详情</button>
    
        <br /><br />
        <input type="text" v-model="mobile" />
        <p>mobile: {{ mobile }}</p>
    
        <br />
        <p>当前咖啡商品的名字是:{{ coffee.name }}</p>
        <p>当前咖啡商品的价格是:{{ coffee.price }}</p>
    
        <br />
        <p>用户名:<input type="text" v-model="loginParams.username" /></p>
        <p>密码:<input type="text" v-model="loginParams.password" /></p>
        <button @click="doLogin">登录</button>
        <p>login info: {{ loginParams }}</p>
      </div>
    </template>
    
    <script>
    // ref:创建一个包含响应式数据的引用对象,xxx.value
    // ref可以接收基本数据类型和对象类型
    import { getCurrentInstance, ref, reactive } from "vue";
    
    export default {
      // 1. setup执行的时期:在beforeCreate和created之前执行
      // 2. setup和options api对比
      // 3. 在Vue3.x中仍然可以使用Vue2.x相关语法
      // 4. 在Vue2.x中可以获取到setup中的数据
      // 5. 在setup中无法获取到Vue2.x中的数据,没有this,this的值是undefined
      // 6. 如果setup中的方法或者数据和Vue2.x中的方法和数据冲突了,优先使用setup中的数据或方法
      data() {
        return {};
      },
      components: {},
      methods: {},
      // setup是在created和beforeCreate生命周期之前触发
      setup(props, context) {
        // props是一个对象,组件外部传递过来的值,并且这个值是在组件内接收了的
        // context表示上下文对象,包含attrs, slots, emit
        // 	attrs,外部传递过来的属性,并且没有在props中定义
        // 	slots,插槽
        // 	emit,分发自定义的事件,相当于$emit
        // 这里返回的任何内容都可以用于组件的其余部分
        console.log(props);
        console.log(context);
    
        // 接收挂载的全局变量
        // 例如proxy.poo就表示"Hello world!"
        // 例如proxy.$http就表示axios请求
        const { proxy } = getCurrentInstance();
    
        let title = "长津湖";
        let price = 56;
        let info = ref("抗美援朝的故事");
        function getMovieInfo() {
          info.value = `当前电影的名字是:${title},当前电影的价格是:${price},详情是:${info.value}`;
        }
    
        // ref实现数据的双向绑定,实际上是通过set和get的方法来实现的
        let mobile = ref("123");
    
        // 对象类型
        const coffee = reactive({
          name: "瑞纳冰拿铁",
          price: 48,
        });
        console.log(coffee); // 直接就是Proxy的处理方式
        console.log("coffee.name", coffee.name, "coffee.price", coffee.price);
    
        const loginParams = reactive({
          username: "",
          password: "",
        });
    
        function doLogin() {
          console.log("setup doLogin ing..");
          console.log(loginParams);
          proxy.$http.post("test/user/doLogin", loginParams).then((res) => {
            console.log(res);
          });
        }
    
        return {
          title,
          price,
          info,
          getMovieInfo,
          mobile,
          coffee,
          loginParams,
          doLogin,
        };
      },
    };
    </script>
    
    

    options-api和composition-api

    Vue2.x实现一个成员的添加和删减方式

    options-api

    修改文件AboutView.vue。

    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <h3>Vue2.x实现成员添加和删减</h3>
        <p><input type="text" v-model="user.name" /></p>
        <p><input type="text" v-model="user.age" /></p>
        <p><button @click="addItem">添加一个成员</button></p>
    
        <ul>
          <li v-for="(item, index) in list" :key="index" @click="removeItem(index)">
            No.{{ index + 1 }} ---- 姓名:{{ item.name }} ---- 年龄:{{ item.age }}
          </li>
        </ul>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          list: [
            {
              name: "张三",
              age: 17,
            },
            {
              name: "李四",
              age: 19,
            },
            {
              name: "王五",
              age: 18,
            },
          ],
          user: {
            name: "LeMo",
            age: 20,
          },
        };
      },
      components: {},
      methods: {
        addItem() {
          if (
            this.user.name &&
            this.user.name !== "" &&
            this.user.age &&
            this.user.age !== ""
          ) {
            this.list.push(Object.assign({}, this.user));
            this.user.name = "";
            this.user.age = "";
          }
        },
        removeItem(i) {
          this.list = this.list.filter((val, index) => index !== i);
        },
      },
    };
    </script>
    
    

    Vue3.x实现一个成员的添加和删减方式

    composition-api

    修改文件AboutView.vue。

    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <h3>Vue3.x实现成员添加和删减</h3>
        <p><input type="text" v-model="params.item.name" /></p>
        <p><input type="text" v-model="params.item.age" /></p>
        <p><button @click="addItem">添加一个成员</button></p>
    
        <ul>
          <li
            v-for="(item, index) in list.items"
            :key="index"
            @click="removeItem(index)"
          >
            No.{{ index + 1 }} ---- 姓名:{{ item.name }} ---- 年龄:{{ item.age }}
          </li>
        </ul>
      </div>
    </template>
    
    <script>
    import { reactive } from "vue";
    
    export default {
      data() {
        return {};
      },
      components: {},
      methods: {},
      setup(props) {
        console.log(props);
    
        let list = reactive({
          items: [
            {
              name: "张三",
              age: 17,
            },
            {
              name: "李四",
              age: 19,
            },
            {
              name: "王五",
              age: 18,
            },
          ],
        });
        let params = reactive({
          item: {
            name: "阿三",
            age: 20,
          },
        });
    
        function addItem() {
          if (
            params.item.name &&
            params.item.name !== "" &&
            params.item.age &&
            params.item.age !== ""
          ) {
            list.items.push(Object.assign({}, params.item));
            params.item.name = "";
            params.item.age = "";
          }
        }
    
        function removeItem(i) {
          list.items = list.items.filter((val, index) => index !== i);
        }
    
        return {
          list,
          params,
          addItem,
          removeItem,
        };
      },
    };
    </script>
    
    

    Vue3.x实现一个成员的添加和删减方式优化1

    修改文件AboutView.vue。

    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <h3>Vue3.x实现成员添加和删减</h3>
        <p><input type="text" v-model="params.item.name" /></p>
        <p><input type="text" v-model="params.item.age" /></p>
        <p><button @click="addItem">添加一个成员</button></p>
    
        <ul>
          <li
            v-for="(item, index) in list.items"
            :key="index"
            @click="removeItem(index)"
          >
            No.{{ index + 1 }} ---- 姓名:{{ item.name }} ---- 年龄:{{ item.age }}
          </li>
        </ul>
      </div>
    </template>
    
    <script>
    import { reactive } from "vue";
    
    export default {
      data() {
        return {};
      },
      components: {},
      methods: {},
      setup(props) {
        console.log(props);
    
        let { list, removeItem } = originRemoveItem();
        let { params, addItem } = originAddItem(list);
    
        return {
          params,
          addItem,
          list,
          removeItem,
        };
      },
    };
    
    // 原始数据和删除方法
    function originRemoveItem() {
      // 原始数据
      let list = reactive({
        items: [
          {
            name: "张三",
            age: 17,
          },
          {
            name: "李四",
            age: 19,
          },
          {
            name: "王五",
            age: 18,
          },
        ],
      });
      // 删除一条数据
      function removeItem(i) {
        list.items = list.items.filter((val, index) => index !== i);
      }
    
      return {
        list,
        removeItem,
      };
    }
    
    // 默认数据和添加方法
    function originAddItem(list) {
      let params = reactive({
        item: {
          name: "阿三",
          age: 20,
        },
      });
    
      function addItem() {
        if (
          params.item.name &&
          params.item.name !== "" &&
          params.item.age &&
          params.item.age !== ""
        ) {
          list.items.push(Object.assign({}, params.item));
          params.item.name = "";
          params.item.age = "";
        }
      }
    
      return {
        params,
        addItem,
      };
    }
    </script>
    
    

    Vue3.x实现一个成员的添加和删减方式优化2

    将原始的数据删除和数据添加的方法,分别挪到独立的JS文件中。

    1. 在src目录中,新建一个目录hooks。

    2. 在hooks目录中,新建文件del.js。

      import { reactive } from 'vue'
      
      // 按需导出
      // 原始数据和删除方法
      export function originRemoveItem() {
          // 原始数据
          let list = reactive({
              items: [
                  {
                      name: "张三",
                      age: 17,
                  },
                  {
                      name: "李四",
                      age: 19,
                  },
                  {
                      name: "王五",
                      age: 18,
                  },
              ],
          });
          // 删除一条数据
          function removeItem(i) {
              list.items = list.items.filter((val, index) => index !== i);
          }
      
          return {
              list,
              removeItem,
          };
      }
      
      
    3. 在hooks目录中,新建文件add.js。

      import { reactive } from 'vue'
      
      // 按需导出
      // 默认数据和添加方法
      export function originAddItem(list) {
          let params = reactive({
              item: {
                  name: "阿三",
                  age: 20,
              },
          });
      
          function addItem() {
              if (
                  params.item.name &&
                  params.item.name !== "" &&
                  params.item.age &&
                  params.item.age !== ""
              ) {
                  list.items.push(Object.assign({}, params.item));
                  params.item.name = "";
                  params.item.age = "";
              }
          }
      
          return {
              params,
              addItem,
          };
      }
      
      
    4. 修改文件AboutView.vue。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <h3>Vue3.x实现成员添加和删减</h3>
          <p><input type="text" v-model="params.item.name" /></p>
          <p><input type="text" v-model="params.item.age" /></p>
          <p><button @click="addItem">添加一个成员</button></p>
      
          <ul>
            <li
              v-for="(item, index) in list.items"
              :key="index"
              @click="removeItem(index)"
            >
              No.{{ index + 1 }} ---- 姓名:{{ item.name }} ---- 年龄:{{ item.age }}
            </li>
          </ul>
        </div>
      </template>
      
      <script>
      import { originRemoveItem } from "../hooks/del.js";
      import { originAddItem } from "../hooks/add.js";
      
      export default {
        data() {
          return {};
        },
        components: {},
        methods: {},
        setup(props) {
          console.log(props);
          console.log(originRemoveItem);
          console.log(originAddItem);
      
          let { list, removeItem } = originRemoveItem();
          let { params, addItem } = originAddItem(list);
      
          return {
            params,
            addItem,
            list,
            removeItem,
          };
        },
      };
      </script>
      
      

    Vue3.x原理

    • 在Vue2.x中,如果属性在data()方法中未定义,随后在相应的方法中,新增属性,此时新增的属性实际上是生效的,但不会渲染到页面中(非响应式)。Vue2.x中的双向数据绑定会存在一些缺陷。
      Vue2.x使用的是 Object.prototype 方式。

      <script>
          var VM = new Vue({
              el: '#app',
              data: {
                  person: {
                      name: "张三",
                      hobby: ["打篮球", "游泳", "健身"],
                  },
              },
              methods: {
                  changePerson() {
                      // this.person.age = 18; // 不生效
                      // this.$set(this.person, "age", 19); // 生效
                      this.person = Object.assign({}, this.person, {age: 20}); // 生效
      
                      // 数组下标的方式修改元素也是不生效的
                      this.person.habby[0] = "踢足球"; // 不生效
                      this.$set(this.person.hobby, 0, "吃饭"); // 生效
                  }
              },
          })
      </script>
      
    • Vue3.x绑定原理。不存在该问题。
      Vue3.x使用的是Proxy方式。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <h3>Vue3.x的绑定原理</h3>
          <p><button @click="changeObj">修改当前对象</button></p>
          <p>{{ person }}</p>
        </div>
      </template>
      
      <script>
      import { reactive } from "vue";
      
      export default {
        setup(props) {
          console.log(props);
      
          var person = reactive({
            name: "张三",
            hobby: ["打篮球", "游泳", "健身"],
          });
      
          function changeObj() {
            person.age = 20; // 生效
            person.hobby[0] = "读书"; // 生效
          }
      
          return {
            person,
            changeObj,
          };
        },
      };
      </script>
      
      

    computed

    示例:

    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <h3>Vue3.x的computed</h3>
    
        <table border="1" cellpadding="0" cellspacing="0">
          <tr>
            <th><input type="checkbox" name="" id="" v-model="isAllChecked" /></th>
            <th>商品名字</th>
            <th>商品价格</th>
            <th>购买数量</th>
          </tr>
          <tr v-for="item in iceCream" :key="item.id">
            <td>
              <input type="checkbox" name="" id="" v-model="item.isChecked" />
            </td>
            <td>{{ item.name }}</td>
            <td>{{ item.price }}</td>
            <td>{{ item.num }}</td>
          </tr>
          <tr>
            <td>总计:</td>
            <td colspan="3">{{ totalPrice }}</td>
          </tr>
        </table>
      </div>
    </template>
    
    <script>
    import { computed, reactive } from "vue";
    
    export default {
      setup(props) {
        console.log(props);
    
        var { iceCream } = reactive({
          iceCream: [
            {
              id: 101,
              name: "哈根达斯",
              price: 98,
              num: 10,
              isChecked: true,
            },
            {
              id: 100,
              name: "DQ",
              price: 67,
              num: 15,
              isChecked: false,
            },
            {
              id: 112,
              name: "八喜",
              price: 48,
              num: 28,
              isChecked: false,
            },
            {
              id: 117,
              name: "蒙牛",
              price: 3,
              num: 34,
              isChecked: false,
            },
          ],
        });
    
        var isAllChecked = computed({
          get() {
            return iceCream.every((el) => el.isChecked);
          },
          set(value) {
            iceCream.forEach((el) => (el.isChecked = value));
          },
        });
    
        var totalPrice = computed(() => {
          return iceCream.reduce((cur, val) => {
            if (val.isChecked) {
              return cur + val.num * val.price;
            } else {
              return cur;
            }
          }, 0);
        });
    
        return {
          iceCream,
          isAllChecked,
          totalPrice,
        };
      },
    };
    </script>
    
    <style lang="less" scoped>
    table {
       500px;
      margin: 0 auto;
    
      td {
        text-align: center;
      }
    }
    </style>
    
    

    watch

    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <h3>Vue3.x的watch函数</h3>
        <h3>1. 监听ref的响应式数据</h3>
        <p>
          当前的年龄是:{{ ageRef }}
          <br />
          出生日期:<input type="text" v-model.lazy="birth" />
        </p>
    
        <br />
        <h3>2. 监听多个ref的响应式数据</h3>
        <p>
          当前的姓名是:{{ fullName }}
          <input type="text" v-model.lazy="firstName" />
          <input type="text" v-model.lazy="lastName" />
        </p>
    
        <br />
        <h3>3. 监听reactive的响应式数据 ---- {{ film }}</h3>
        <p>当前的电影票价是:<input type="number" v-model.lazy="movie.price" /></p>
        <p>当前的电影是:<input type="text" v-model.lazy="movie.title" /></p>
        <p>movie: {{ movie }}</p>
    
        <br />
        <h3>4. 监听reactive数据中的某个属性</h3>
        <p>当前的学生是:<input type="text" v-model.lazy="student.name" /></p>
        <p>student: {{ student }}</p>
      </div>
    </template>
    
    <script>
    import { watch, ref, reactive } from "vue";
    
    export default {
      setup(props) {
        console.log(props);
    
        // 1. 监听ref的响应式数据,newVal和oldVal都可以正常使用
        var birth = ref("2000-01-04");
        var ageRef = ref(0);
        watch(
          birth, // 被监听的数据
          (newVal, oldVal) => {
            console.log(newVal, oldVal);
            ageRef.value =
              new Date().getFullYear() - new Date(birth.value).getFullYear();
          },
          {
            immediate: true, // 初始状态也生效
          }
        );
    
        // 2. 监听多个ref的响应式数据
        var firstName = ref("张");
        var lastName = ref("三");
        var fullName = ref("");
        watch(
          [firstName, lastName],
          (newVal, oldVal) => {
            console.log(newVal, oldVal);
            fullName.value = newVal[0] + newVal[1];
          },
          {
            immediate: true, // 初始状态也生效
          }
        );
    
        // 3. 监听reactive的响应式数据
        // watch监听reactive响应式数据,无法正确获取oldVal
        // 默认开启的是深度监听
        var movie = reactive({
          title: "长津湖",
          price: 48,
        });
        var film = reactive({});
        watch(
          movie,
          (newVal, oldVal) => {
            console.log(newVal, oldVal);
            film.title = newVal.title;
            film.price = newVal.price;
          },
          { immediate: true }
          // { deep: false }
        );
    
        // 4. 监听reactive数据中的某个属性
        // watch监听reactive响应式数据的某个属性,可以正确获取oldVal
        var student = reactive({
          name: "李晓华",
          age: 12,
        });
        watch(
          // () => student.name,
          student.name,
          (newVal, oldVal) => {
            console.log(newVal, oldVal);
          }
        );
    
        return {
          birth,
          ageRef,
          firstName,
          lastName,
          fullName,
          movie,
          film,
          student,
        };
      },
    };
    </script>
    
    <style lang="less" scoped></style>
    
    

    数据绑定指令(举例说明)

    指令说明

    • v-text指令:向其所在的节点中渲染文本内容,放入标签则也会被当成文本解析。

    • {{}}插值表达式:基本上和v-text的作用差不多,可以理解为v-text的简写。

      v-text与差值表达式的区别:v-text会替换掉节点中的内容,原来的内容会被代替,无法与原来的内容一起出现。{{}}插值表达式则可以。

      在Vue3.x中直接在包含内容的标签上使用v-text会报错。

    • v-html指令:向指定节点中渲染包含html结构的内容,更新元素的innerHTML。

      内容按普通HTML插入,不会作为Vue模板进行编译。如果试图使用v-html组合模板,可以重新考虑通过使用组件来替代。

    操作步骤

    1. 创建Vue3.x项目,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。

    2. 在src\views目录下,新建文件InstructionView.vue。

      <template>
        <div>
          <h3>1. 数据绑定指令</h3>
          <h3>今日份咖啡:{{ coffee }}</h3>
        </div>
      </template>
      
      <script setup>
      // 使用了setup的语法糖
      import { ref, reactive } from "vue";
      
      const coffee = ref("卡布奇洛");
      const movie = reactive({
        name: "蝙蝠侠",
        price: 55,
      });
      
      const params = reactive({
        username: "admin",
        password: "123",
      });
      </script>
      
      
    3. 修改文件src\router\index.ts。

      import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
      import HomeView from "../views/HomeView.vue";
      import InstructionView from "../views/InstructionView.vue";
      
      const routes: Array<RouteRecordRaw> = [
        {
          path: "/",
          name: "home",
          component: HomeView,
        },
        {
          path: "/about",
          name: "about",
          // route level code-splitting
          // this generates a separate chunk (about.[hash].js) for this route
          // which is lazy-loaded when the route is visited.
          component: () =>
            import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
        },
        {
          path: "/instruction",
          name: "instruction",
          component: InstructionView,
        },
      ];
      
      const router = createRouter({
        history: createWebHashHistory(),
        routes,
      });
      
      export default router;
      
      
    4. 修改文件src\App.vue。

      <template>
        <nav>
          <router-link to="/">Home</router-link> |
          <router-link to="/about">About</router-link> |
          <router-link to="/instruction">instruction</router-link>
        </nav>
        <router-view />
      </template>
      
      <style lang="less">
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
      }
      
      nav {
        padding: 30px;
      
        a {
          font-weight: bold;
          color: #2c3e50;
      
          &.router-link-exact-active {
            color: #42b983;
          }
        }
      }
      </style>
      
      
    5. 在src\components目录下,新建子组件InstructionComp.vue。

      <template>
        <div>
       <h3>1. 数据绑定指令</h3>
          <!-- {{}}插值表达式指令,计算表达式等均可 -->
          <p>今日份咖啡:{{ coffee }}</p>
      
          <hr />
          <!-- v-text指令,只能渲染文本 -->
          <p v-text="coffee"></p>
          <!-- v-text指令,<strong>蝙蝠侠</strong>,不会解析strong元素 -->
          <p v-text="movie.name"></p>
      
          <hr />
          <!-- v-html指令,蝙蝠侠,会解析strong元素 -->
          <!-- v-html指令,不建议使用,存在安全风险,容易造成XSS攻击 -->
          <p v-html="movie.name"></p>
      
          <hr />
          <!-- v-model指令,双向数据绑定,作用于表单元素 -->
          <p>用户名:<input type="text" v-model="params.username" /></p>
          <p>密码:<input type="text" v-model="params.password" /></p>
          <p>params: {{ params }}</p>
          <!-- v-model指令,语法糖解析 -->
          <input
            type="text"
            :value="params.username"
            @input="params.username = $event.target.value"
          />
      
          <hr />
          <!-- v-model指令,双向数据绑定,作用于表单元素,单选框举例 -->
          <p>单选按钮:</p>
          <label for="">Male</label>
          <input type="radio" name="" id="" value="Male" v-model="gender" /> |
          <label for="">Female</label>
          <input type="radio" name="" id="" value="Female" v-model="gender" /> |
          <label for="">Other</label>
          <input type="radio" name="" id="" value="Other" v-model="gender" />
          <p>当前选择的性别是:{{ gender }}</p>
      
          <hr />
          <!-- v-model指令,双向数据绑定,作用于表单元素,单选框举例 -->
          <p>单选框:</p>
          <input type="checkbox" v-model="isAgree" />大熊协议
          <p>您当前的选择是:{{ isAgree ? "同意" : "不同意" }}</p>
      
          <hr />
          <!-- v-model指令,双向数据绑定,作用于表单元素,多选框举例 -->
          <!-- 多选框时候,存储数据的变量要以数组的形成呈现 -->
          <p>多选框:</p>
          <input type="checkbox" value="足球" v-model="hobbit" />足球
          <input type="checkbox" value="游泳" v-model="hobbit" />游泳
          <input type="checkbox" value="乒乓球" v-model="hobbit" />乒乓球
          <input type="checkbox" value="篮球" v-model="hobbit" /> 篮球
          <p>您当前的爱好是:{{ hobbit }}</p>
      
          <hr />
          <!-- v-model指令,双向数据绑定,作用于表单元素,下拉框举例 -->
          <p>select下拉框</p>
          <select name="" id="" v-model="level">
            <option value="黑铁">黑铁</option>
            <option value="青铜">青铜</option>
            <option value="白银">白银</option>
            <option value="黄金">黄金</option>
            <option value="铂金">铂金</option>
            <option value="钻石">钻石</option>
            <option value="荣耀">荣耀</option>
            <option value="王者">王者</option>
          </select>
          <p>您当前的段位是:{{ level }}</p>
      
          <hr />
          <!-- v-model指令,双向数据绑定,作用于表单元素,xx.number修饰符,数值类型字符串转换为数值 -->
          <input type="text" v-model.number="num1" /> +
          <input type="text" v-model.number="num2" /> =
          <input type="text" :value="num1 + num2" />
          <p>typeof num1: {{ typeof num1 }}</p>
      
          <hr />
          <!-- v-model指令,双向数据绑定,作用于表单元素,xx.lazy修饰符,鼠标失去焦点的时候才会触发数据的同步 -->
          <input type="text" v-model.lazy="mobile" />
          <p>mobile: {{ mobile }}</p>
      
          <hr />
          <!-- v-model指令,双向数据绑定,作用于表单元素,xx.trim修饰符,自动去掉字符串前后的空格 -->
          <input type="text" v-model.trim="params.username" />
          <p>params.username.length: {{ params.username.length }}</p>
        </div>
      </template>
      
      <script setup>
      // 使用了setup的语法糖
      import { ref, reactive } from "vue";
      
      const coffee = ref("卡布奇洛");
      const movie = reactive({
        name: "<strong>蝙蝠侠</strong>",
        price: 55,
      });
      
      const params = reactive({
        username: "admin",
        password: "123",
      });
      
      const gender = ref("Female");
      const isAgree = ref(false);
      const hobbit = ref([]);
      const level = ref("黑铁");
      
      const num1 = ref(0);
      const num2 = ref(0);
      
      const mobile = ref(0);
      </script>
      
      

    属性绑定指令

    参见[属性绑定指令(v-bind/:)](# 属性绑定指令(v-bind/:))内容进行改造成Vue3.x版本的内容。

    条件判断指令

    参见[条件渲染指令](# 条件渲染指令)内容进行改造成Vue3.x版本的内容。

    <template>
      <div>
        <h3>条件判断指令</h3>
        <div>
          <p v-if="cartList.length !== 0">
            您当前加入购物车的商品一共有:{{ cartList.length }}件
          </p>
          <p v-else>您的购物车空空如也,快去购物吧</p>
        </div>
    
        <hr />
        <div>
          多分支条件语句,根据分数判断当前考试等级
          <p>您当前的考试等级是:</p>
          <p v-if="score >= 90">A</p>
          <p v-else-if="score >= 80">B</p>
          <p v-else-if="score >= 60">C</p>
          <p v-else>D</p>
        </div>
      </div>
    </template>
    
    <script setup>
    // 使用了setup的语法糖
    import { ref, reactive } from "vue";
    
    const { cartList } = reactive({
      // cartList: [],
      cartList: [
        {
          id: 1001,
          name: "苹果",
        },
      ],
    });
    
    const score = ref(10);
    </script>
    
    

    v-for列表渲染指令

    参见[v-for列表渲染指令](# v-for列表渲染指令)内容进行改造成Vue3.x版本的内容。

    <template>
      <div>
        <h3>v-for列表渲染指令</h3>
        <!-- 在不做增删改查的时候,key绑定下标没问题 -->
        <!--  -->
        <p v-for="(item, index) in books" :key="index">
          {{ index + 1 }} ---- {{ item }}
        </p>
    
        <hr />
        <!-- 遍历对象内容 -->
        <!-- value表示对象的值 -->
        <!-- attr表示对象的属性名(key值) -->
        <p v-for="(value, attr) in actor" :key="attr">
          {{ attr }} ---- {{ value }}
        </p>
      </div>
    
      <hr />
      <!-- 循环嵌套 -->
      <div v-for="item in beverages" :key="item.id">
        <h3>{{ item.category }}</h3>
        <ul>
          <li v-for="(good, index) in item.commodity" :key="index">{{ good }}</li>
        </ul>
      </div>
    
      <hr />
      <h3>key绑定index和id的情况</h3>
      <p><button @click="addItem">添加一项数据</button></p>
      <ul>
        <!-- 不推荐如此使用,这种情况是存在问题的 -->
        <li v-for="(item, index) in todos" :key="index">
          当前的内容是:{{ item.text }} <input type="text" />
        </li>
      </ul>
    
      <!-- 
        分析:
    
        绑定index的旧虚拟DOM:
          <li :key="0">当前的内容是:吃饭 <input type=""></li>
          <li :key="1">当前的内容是:睡觉 <input type=""></li>
          <li :key="2">当前的内容是:打豆豆 <input type=""></li>
    
          绑定index的新虚拟DOM:
          index在头部添加一项数据的虚拟DOM:
    
          <li :key="0">当前的内容是:打游戏 <input type=""></li> ————key没有变化,input内容认为没有变化,实际上input内容需要跟随变化
          <li :key="1">当前的内容是:吃饭 <input type=""></li>
          <li :key="2">当前的内容是:睡觉 <input type=""></li>
          <li :key="3">当前的内容是:打豆豆 <input type=""></li>
       -->
    
      <!--
         绑定id的旧虚拟DOM:
          <li :key="1001">当前的内容是:吃饭 <input type=""></li>
          <li :key="1002">当前的内容是:睡觉 <input type=""></li>
          <li :key="1003">当前的内容是:打豆豆 <input type=""></li>
    
        绑定index的新虚拟DOM:
          <li :key="1004">当前的内容是:打游戏 <input type=""></li> ————key新增DOM
          <li :key="1001">当前的内容是:吃饭 <input type=""></li> ————key无变化
          <li :key="1002">当前的内容是:睡觉 <input type=""></li> ————key无变化
          <li :key="1003">当前的内容是:打豆豆 <input type=""></li> ————key无变化
       -->
    
      <hr />
      <h3>key绑定index和id的情况</h3>
      <p><button @click="addItem">添加一项数据</button></p>
      <ul>
        <!-- 推荐 -->
        <li v-for="item in todos" :key="item.id">
          当前的内容是:{{ item.text }} <input type="text" />
        </li>
      </ul>
    </template>
    
    <script setup>
    // 使用了setup的语法糖
    import { reactive } from "vue";
    
    // v-for:用于循环列表数据
    // 可遍历:数组、对象、字符串、指定次数
    // v-for指令:"item in data",data为数据源
    // v-for还可以接收第二个参数,指的是当前项的索引
    //  语法:v-for="(item, index) in data"
    
    // key的作用:
    // key是虚拟DOM的标识,当数据发生变化的时候,Vue会根据新数据生成新的虚拟DOM节点
    // 随后Vue进行新虚拟DOM和旧虚拟DOM的对比,对比规则:
    // 1)旧虚拟DOM找到新虚拟DOM的key值
    //  如果虚拟DOM内容没有变化,直接使用之前的真实DOM
    //  如果虚拟DOM内容发生了变化,则生成新的真实DOM,随后替换掉页面之前的真实DOM
    // 2)旧虚拟DOM未找到与新的虚拟DOM相同的key,会创建真实的DOM,随后渲染到页面
    
    const { books, actor, beverages } = reactive({
      books: [
        "西游记",
        "水浒传",
        "三国演义",
        "人性的弱点",
        "晚熟的人",
        "人类简史",
        "今日简史",
        "未来简史",
        "三体",
      ],
      actor: {
        name: "沈腾",
        age: 40,
        work: "演员",
      },
      beverages: [
        {
          id: 10011,
          category: "咖啡",
          commodity: ["拿铁", "厚乳", "瑞纳水", "卡布奇诺", "美式"],
        },
        {
          id: 10015,
          category: "茶叶",
          commodity: ["西湖龙井", "太平猴魁", "黄山毛峰", "庐山雨雾", "银针白毫"],
        },
        {
          id: 10019,
          category: "奶茶",
          commodity: ["珍珠奶茶", "燕麦奶茶", "波霸奶茶", "蓝莓奶茶", "椰果奶茶"],
        },
      ],
    });
    
    // 验证key绑定index和id的区别
    const { todos } = reactive({
      todos: [
        {
          id: 1001,
          text: "吃饭",
          done: false,
        },
        {
          id: 1002,
          text: "睡觉",
          done: true,
        },
        {
          id: 1003,
          text: "打豆豆",
          done: false,
        },
      ],
    });
    
    function addItem() {
      todos.unshift({
        id: 1004,
        text: "打游戏",
        done: false,
      });
    }
    </script>
    
    

    /---- Vue3.x+TS ----/

    Vue+TS

    模板语法&Vue指令

    模板插值语法

    1. 搭建Vue3.x+TS环境。

    2. 修改文件AblutView.vue。验证模板插值语法。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <p>{{ msg }}</p>
          <p>{{ msg.split(",") }}</p>
        </div>
      </template>
      
      <script setup lang="ts">
      let msg: string = "Hello, TS.";
      </script>
      
      

    指令

    • v-text:显示文本。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <p v-text="msg"></p>
        </div>
      </template>
      
      <script setup lang="ts">
      let msg: string = "Hello, TS.";
      </script>
      
      
    • v-html:展示富文本。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <p v-html="msg"></p>
        </div>
      </template>
      
      <script setup lang="ts">
      let msg: string = "<div>Hello, TS.</div>";
      </script>
      
      
    • v-if: 用来控制元素的显示隐藏(切换真假DOM)。

      v-else-if:表示 v-if 的“else if 块”。可以链式调用。

      v-else:v-if条件收尾语句。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <p v-if="flag === 'A'">A</p>
          <p v-else-if="flag === 'B'">B</p>
          <p v-else-if="flag === 'C'">C</p>
          <p v-else>D</p>
        </div>
      </template>
      
      <script setup lang="ts">
      let flag: string = "B";
      </script>
      
      
    • v-show:用来控制元素的显示隐藏(display none block ccss切换)。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <p v-show="flag">A</p>
          <p>B</p>
        </div>
      </template>
      
      <script setup lang="ts">
      let flag: boolean = false;
      </script>
      
      
    • v-on:简写@用来给元素添加事件。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
          <button @click="clickEvent">按键事件</button>
        </div>
      </template>
      
      <script setup lang="ts">
      const clickEvent = () => {
        console.log("触发了点击事件");
      };
      </script>
      
      
      • v-on修饰符:冒泡举例。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
            <div @click="parent">
              <button @click.stop="clickEvent">按键事件</button>
            </div>
          </div>
        </template>
        
        <script setup lang="ts">
        const parent = () => {
          console.log("我是父级的内容");
        };
        
        const clickEvent = () => {
          console.log("我是子级的内容");
        };
        </script>
        
        

        说明:如果子级没有添加.stop的话,会同时触发子级和父级的点击事件。

      • v-on修饰符:阻止表单提交案例。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
            <form action="/">
              <button @click.prevent="clickEvent" type="submit">提交</button>
            </form>
          </div>
        </template>
        
        <script setup lang="ts">
        const clickEvent = () => {
          console.log("我是子级的内容");
        };
        </script>
        
        
    • v-bind:简写:用来绑定元素的属性Attr。

      • 绑定样式举例,对象格式。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
        
            <div :style="style">我是一个测试数据</div>
          </div>
        </template>
        
        <script setup lang="ts">
        type Style = {
          color: string;
          height: string;
        };
        
        const style: Style = {
          color: "blue",
          height: "200px",
        };
        </script>
        
        
      • 绑定样式举例,数组格式。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
        
            <div :class="['a', 'b']">我是一个测试数据</div>
          </div>
        </template>
        
        <script setup lang="ts"></script>
        
        <style>
        .a {
          color: red;
        }
        
        .b {
          height: 300px;
        }
        </style>
        
        
      • 绑定样式举例,数组格式,增加条件表达式。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
        
            <div :class="[flag ? 'a' : 'b']">我是一个测试数据</div>
          </div>
        </template>
        
        <script setup lang="ts">
        const flag: boolean = true;
        </script>
        
        <style>
        .a {
          color: red;
        }
        
        .b {
          color: blue;
        }
        </style>
        
        
      • 绑定样式举例,对象格式。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
        
            <div :class="cls">我是一个测试数据</div>
          </div>
        </template>
        
        <script setup lang="ts">
        type Cls = {
          a: boolean;
          b: boolean;
        };
        
        const cls = {
          a: true,
          b: true,
        };
        </script>
        
        <style>
        .a {
          color: red;
        }
        
        .b {
          height: 200px;
        }
        </style>
        
        
    • v-for:用来遍历元素。

      • 遍历数组格式。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
        
            <div v-for="item in arr" :key="item">{{ item }}</div>
          </div>
        </template>
        
        <script setup lang="ts">
        const arr: Array<number> = [1, 2, 3, 4, 5];
        </script>
        
        <style></style>
        
        
      • 遍历对象格式。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
        
            <div v-for="item in arr" :key="item.id">{{ item.name }}</div>
          </div>
        </template>
        
        <script setup lang="ts">
        const arr: Array<any> = [
          {
            id: 1,
            name: "张三",
          },
          {
            id: 2,
            name: "李四",
          },
        ];
        </script>
        
        <style></style>
        
        
    • v-model:数据的双向绑定。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <input v-model="coffee" type="text" />
          <p>{{ coffee }}</p>
        </div>
      </template>
      
      <script setup lang="ts">
      import { ref } from "vue";
      
      const coffee = ref("卡布奇洛");
      </script>
      
      <style></style>
      
      

    ref全家桶

    • ref使用,方式一

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <p>{{ msg }}</p>
          <button @click="changeMsg">修改msg值</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { ref, Ref } from "vue";
      
      let msg: Ref<string> = ref("卡布奇洛");
      
      function changeMsg(): void {
        msg.value = "Hello TS";
      }
      
      </script>
      
      <style>
      </style>
      
      

      说明:注意被ref包装之后需要.value 来进行赋值。

    • ref使用,方式二

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <p>{{ msg }}</p>
          <button @click="changeMsg">修改msg值</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { ref } from "vue";
      
      let msg = ref<string | number>("卡布奇洛");
      
      function changeMsg(): void {
        msg.value = "Hello TS";
      }
      
      </script>
      
      <style>
      </style>
      
      
    • isRef:判断是不是一个Ref对象。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <p>{{ msg }}</p>
          <button @click="changeMsg">修改msg值</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { ref, Ref, isRef } from "vue";
      
      let msg: Ref<string> = ref("卡布奇洛");
      let notRef: number = 10;
      
      function changeMsg(): void {
        msg.value = "Hello TS";
      
        console.log(isRef(msg)); // true
        console.log(isRef(notRef)); // false
      }
      
      </script>
      
      <style>
      </style>
      
      
    • shallowRef:创建一个跟踪自身.value变化的ref,但不会使其值也变成响应式的。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <p>{{ msg }}</p>
          <button @click="changeMsg">修改msg值</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { ref, Ref, isRef, shallowRef } from "vue";
      
      type Obj = {
        name: string
      }
      let msg: Ref<Obj> = shallowRef({
        name: "张三"
      });
      
      function changeMsg(): void {
        // msg.value.name = "Hello TS"; // 不会生效
        msg.value = { name: "Hello Ts" }; // 生效
      }
      
      </script>
      
      <style>
      </style>
      
      
    • triggerRef:强制刷新页面DOM。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <p>{{ msg }}</p>
          <button @click="changeMsg">修改msg值</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { ref, Ref, isRef, shallowRef, triggerRef } from "vue";
      
      type Obj = {
        name: string
      }
      let msg: Ref<Obj> = shallowRef({
        name: "张三"
      });
      
      function changeMsg(): void {
        msg.value.name = "Hello TS"; // 不会生效
        triggerRef(msg); // 强制刷新页面DOM
      }
      
      </script>
      
      <style>
      </style>
      
      

      示例2:triggerRef存在视图更新的问题。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <p>{{ r }}</p>
          <p>{{ msg }}</p>
          <button @click="changeMsg">修改msg值1</button>
          <button @click="changeMsg2">修改msg值2</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { ref, Ref, shallowRef } from "vue";
      
      type Obj = {
        name: string
      }
      let msg: Ref<Obj> = shallowRef({
        name: "张三",
        bar: "哈哈"
      });
      
      let r = ref<string>("前缀");
      
      function changeMsg(): void {
        console.log("changeMsg ing.");
      
        msg.value.name = "Hello TS"; // 不会生效
      }
      
      function changeMsg2(): void {
        console.log("changeMsg2 ing.");
      
        r.value = "前缀被修改";
        msg.value.name = "Hello TS"; // 上行代码的修改会触发triggerRef方法导致shallowRef失效
      }
      
      </script>
      
      <style>
      </style>
      
      
    • customerRef:自定义ref。
      customRef是个工厂函数要求我们返回一个对象,并且实现get和set。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <p>{{ msg }}</p>
          <button @click="changeMsg">修改msg值</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { Ref, shallowRef, triggerRef, customRef } from 'vue'
      
      function MyRef<T>(value: T) {
        return customRef((track, trigger) => {
          return {
            get() {
              track();
              return value;
            },
            set(newVal: T) {
              console.log('set');
              value = newVal;
              trigger();
            }
          }
        })
      }
      
      let msg = MyRef('张三');
      const changeMsg = () => {
        msg.value = '李四';
      }
      </script>
      
      <style>
      </style>
      
      

    reacive全家桶

    用来绑定复杂的数据类型,例如对象和数组。

    • reactive

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <p>{{ obj }}</p>
          <p>{{ msg1 }}</p>
          <p>{{ msg2 }}</p>
      
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive } from 'vue';
      
      type Obj = {
        name: string,
        age: number
      }
      let obj = reactive<Obj>({
        name: "张三",
        age: 15
      });
      obj.name = "李四";
      
      let msg1 = reactive<number[]>([]);
      let arr1: Array<number> = [1, 2, 3, 4, 5];
      msg1 = arr1;
      
      let msg2 = reactive<number[]>([]);
      let arr2: Array<number> = [2, 3, 4, 5, 6];
      msg2.push(...arr2);
      
      </script>
      
      <style>
      </style>
      
      
    • readonly:拷贝一份proxy对象将其设置为只读。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <p>{{ obj }}</p>
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive, readonly } from 'vue';
      
      type Obj = {
        name: string,
        age: number,
        count: number
      }
      let obj = reactive<Obj>({
        name: "张三",
        age: 15,
        count: 0
      });
      
      const copy = readonly(obj);
      // copy.count++; // 报错,无法分配到 "count" ,因为它是只读属性
      
      obj.count++; // 没问题
      obj.name = "李四"; // 没问题
      
      </script>
      
      <style>
      </style>
      
      
    • shallowReactive

      • 直接代码触发方法,会导致页面的渲染修改。
        在DOM挂载阶段,是会生效的。如果挂载之后再去修改数据,是不会被改变的。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
        
            <p>{{ msg }}</p>
          </div>
        </template>
        
        <script setup lang="ts">
        import { shallowReactive } from 'vue';
        
        let msg = shallowReactive({
          name: "张三",
          age: 15,
          count: 0,
          nav: {
            bar: {
              text: "Hello"
            }
          }
        });
        
        const change1 = () => {
          msg.name = "张三改成了李四";
          console.log(msg);
        }
        
        const change2 = () => {
          msg.nav.bar.text = "我被改啦";
          console.log(msg);
        }
        
        change1();
        change2(); // 会触发页面的内容渲染修改
        
        </script>
        
        <style>
        </style>
        
        
      • 如果挂载之后再去修改数据,是不会被改变的。

        <template>
          <div>
            <div>{{ state }}</div>
            <button @click="change1">test1</button>
            <button @click="change2">test2</button>
          </div>
        </template>
        
        <script setup lang="ts">
        import { shallowReactive } from 'vue'
        
        const obj = {
          a: 1,
          first: {
            b: 2,
            second: {
              c: 3
            }
          }
        }
        
        const state = shallowReactive(obj)
        
        function change1() {
          state.a = 7
        }
        function change2() {
          state.first.b = 8
          state.first.second.c = 9
          console.log(state);
        }
        </script>
        
        <style>
        </style>
        

    to系列全家桶(toRef/toRefs/toRaw)

    • toRef:针对原始对象是否是响应式的对象使用效果有所差异。

      • 如果原始对象是非响应式的就不会更新视图,数据是会变的。toRef引用对象,会对自身造成影响,也会对原始对象造成影响,同时页面上的视图是不会发生变化的。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
        
            <button @click="change">修改</button>
            <p>{{ msg }}</p>
          </div>
        </template>
        
        <script setup lang="ts">
        import { toRef } from 'vue';
        
        let msg = {
          foo: 1,
          bar: 1
        };
        
        let state = toRef(msg, "foo");
        
        const change = () => {
          state.value++;
          console.log("原始对象:", msg);
          console.log("引用对象:", state);
        }
        
        </script>
        
        <style>
        </style>
        
        
      • 如果原始对象是一个proxy代理的响应式对象。引用对象、原始对象以及页面上的视图都是会发生变化。

        <template>
          <div class="about">
            <h1>This is an about page</h1>
        
            <button @click="change">修改</button>
            <p>{{ msg }}</p>
          </div>
        </template>
        
        <script setup lang="ts">
        import { toRef, reactive } from 'vue';
        
        let msg = reactive({
          foo: 1,
          bar: 1
        });
        
        let state = toRef(msg, "foo");
        
        const change = () => {
          state.value++;
          console.log("原始对象:", msg);
          console.log("引用对象:", state);
        }
        
        </script>
        
        <style>
        </style>
        
        
    • toRefs:可以帮我们批量创建ref对象主要是方便我们解构使用。
      非响应式举例:

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <button @click="change">修改</button>
          <p>{{ msg }}</p>
        </div>
      </template>
      
      <script setup lang="ts">
      import { toRefs, reactive } from 'vue';
      
      let msg = reactive({
        foo: 1,
        bar: 1
      });
      
      let { foo, bar } = msg;
      
      const change = () => {
        console.log(foo, bar); // 非响应式
      }
      
      </script>
      
      <style>
      </style>
      
      

      使用toRefs后,响应式举例:

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <button @click="change">修改</button>
          <p>{{ msg }}</p>
        </div>
      </template>
      
      <script setup lang="ts">
      import { toRefs, reactive } from 'vue';
      
      let msg = reactive({
        foo: 1,
        bar: 1
      });
      
      let { foo, bar } = toRefs(msg);
      
      const change = () => {
        foo.value++;
        bar.value++;
      }
      
      </script>
      
      <style>
      </style>
      
      
    • toRaw:将响应式对象转换为普通对象(节约内存)。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <button @click="change1">修改msg</button>
          <p>msg -- {{ msg }}</p>
      
          <button @click="change2">修改raw</button>
          <p>raw -- {{ raw }}</p>
        </div>
      </template>
      
      <script setup lang="ts">
      import { toRaw, reactive } from 'vue';
      
      let msg = reactive({
        foo: 1,
        bar: 1
      });
      
      let raw = toRaw(msg);
      
      const change1 = () => {
        console.log("msg", msg); // msg Proxy {foo: 7, bar: 7},视图也会变化
        msg.foo++;
        msg.bar++;
      }
      
      const change2 = () => {
        console.log("raw", raw); // raw {foo: 8, bar: 8},视图不会变化
        raw.foo = 15;
        raw.bar++;
      }
      
      </script>
      
      <style>
      </style>
      
      

    computed计算属性

    • 以姓名为例说明(常规的计算方式)

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <input type="text" v-model="firstName">
          <input type="text" v-model="lastName">
      
          <p>{{ firstName }} -- {{ lastName }}</p>
        </div>
      </template>
      
      <script setup lang="ts">
      import { computed, ref } from 'vue';
      
      let firstName = ref<string>("");
      let lastName = ref<string>("");
      
      </script>
      
      <style>
      </style>
      
      
    • 计算属性(方式一,函数形式),以计算属性的方式计算姓名全称。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <input type="text" v-model="firstName">
          <input type="text" v-model="lastName">
      
          <p>{{ name }}</p>
        </div>
      </template>
      
      <script setup lang="ts">
      import { computed, ref } from 'vue';
      
      let firstName = ref<string>("");
      let lastName = ref<string>("");
      
      const name = computed(() => {
        return `${firstName.value} -- ${lastName.value}`;
      });
      
      </script>
      
      <style>
      </style>
      
      
    • 计算属性(方式二,对象形式),以计算属性的方式计算姓名全称。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <input type="text" v-model="firstName">
          <input type="text" v-model="lastName">
      
          <p>{{ name }}</p>
        </div>
      </template>
      
      <script setup lang="ts">
      import { computed, ref } from 'vue';
      
      let firstName = ref<string>("");
      let lastName = ref<string>("");
      
      const name = computed({
        get() {
          return `${firstName.value} -- ${lastName.value}`;
        },
        set() {
          `${firstName.value} -- ${lastName.value}`;
        }
      });
      
      </script>
      
      <style>
      </style>
      
      

    computed购物车案例

    <template>
      <div>
        <table style="800px" border>
          <thead>
            <tr>
              <th>名称</th>
              <th>数量</th>
              <th>价格</th>
              <th>操作</th>
            </tr>
          </thead>
          <tbody>
            <tr :key="index" v-for="(item, index) in data">
              <td align="center">{{ item.name }}</td>
              <td align="center">
                <button @click="AddAnbSub(item, false)">-</button>
                {{ item.num }}
                <button @click="AddAnbSub(item, true)">+</button>
              </td>
              <td align="center">{{ item.num * item.price }}</td>
              <td align="center">
                <button @click="del(index)">删除</button>
              </td>
            </tr>
          </tbody>
          <tfoot>
            <tr>
              <td></td>
              <td></td>
              <td></td>
              <td align="center">总价:{{ $total }}</td>
            </tr>
          </tfoot>
        </table>
      </div>
    </template>
    
    <script setup lang="ts">
    import { computed, reactive, ref } from 'vue'
    type Shop = {
      name: string,
      num: number,
      price: number
    }
    let $total = ref<number>(0)
    const data = reactive<Shop[]>([
      {
        name: "XX的袜子",
        num: 1,
        price: 100
      },
      {
        name: "XX的裤子",
        num: 1,
        price: 200
      },
      {
        name: "XX的衣服",
        num: 1,
        price: 300
      },
      {
        name: "XX的毛巾",
        num: 1,
        price: 400
      }
    ])
    
    const AddAnbSub = (item: Shop, type: boolean = false): void => {
      if (item.num > 1 && !type) {
        item.num--
      }
      if (item.num <= 99 && type) {
        item.num++
      }
    }
    const del = (index: number) => {
      data.splice(index, 1)
    }
    
    $total = computed<number>(() => {
      return data.reduce((prev, next) => {
        return prev + (next.num * next.price)
      }, 0)
    })
    
    </script>
    
    <style>
    </style>
    

    watch侦听器

    <template>
      <div class="about">
        <h1>This is an about page</h1>
        <h3>Vue3.x的watch函数</h3>
        <h3>1. 监听ref的响应式数据</h3>
        <p>
          当前的年龄是:{{ ageRef }}
          <br />
          出生日期:<input type="text" v-model.lazy="birth" />
        </p>
    
        <br />
        <h3>2. 监听多个ref的响应式数据</h3>
        <p>
          当前的姓名是:{{ fullName }}
          <input type="text" v-model.lazy="firstName" />
          <input type="text" v-model.lazy="lastName" />
        </p>
    
        <br />
        <h3>3. 监听reactive的响应式数据 ---- {{ movie }}</h3>
        <p>当前的电影票价是:<input type="number" v-model.lazy="movie.price" /></p>
        <p>当前的电影是:<input type="text" v-model.lazy="movie.title" /></p>
        <p>movie: {{ movie }}</p>
    
        <br />
        <h3>4. 监听reactive数据中的某个属性</h3>
        <p>当前的学生是:<input type="text" v-model.lazy="student.name" /></p>
        <p>student: {{ student }}</p>
      </div>
    </template>
    
    <script setup lang="ts">
    import { watch, ref, reactive } from "vue";
    
    // 1. 监听ref的响应式数据,newVal和oldVal都可以正常使用
    var birth = ref<string>("2000-01-04");
    var ageRef = ref<number>(0);
    // 被监听的数据
    watch(birth, (newVal, oldVal) => {
      console.log(newVal, oldVal);
      ageRef.value =
        new Date().getFullYear() - new Date(birth.value).getFullYear();
    }, {
      immediate: true, // 初始状态也生效
    });
    
    // 2. 监听多个ref的响应式数据
    var firstName = ref<string>("张");
    var lastName = ref<string>("三");
    var fullName = ref<string>("");
    watch([firstName, lastName], (newVal, oldVal) => {
      console.log(newVal, oldVal);
      fullName.value = newVal[0] + newVal[1];
    }, {
      immediate: true, // 初始状态也生效
    });
    
    // 3. 监听reactive的响应式数据
    // watch监听reactive响应式数据,无法正确获取oldVal
    // 默认开启的是深度监听
    var movie = reactive({
      title: "长津湖",
      price: 48,
    });
    watch(movie, (newVal, oldVal) => {
      console.log(newVal, oldVal);
    }, { immediate: true }
      // { deep: false }
    );
    
    // 4. 监听reactive数据中的某个属性
    // watch监听reactive响应式数据的某个属性,可以正确获取oldVal
    var student = reactive({
      name: "李晓华",
      age: 12,
    });
    watch(() => student.name, (newVal, oldVal) => {
      console.log(newVal, oldVal);
    });
    
    </script>
    
    <style lang="less" scoped>
    </style>
    
    

    watchEffect高级侦听器

    立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。

    如果用到message就只会监听messag,就是用到几个监听几个,而且是非惰性 会默认调用一次。

    • watchEffect使用

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <input type="text" v-model="msg1">
          <input type="text" v-model="msg2">
        </div>
      </template>
      
      <script setup lang="ts">
      import { watchEffect, ref } from "vue";
      
      let msg1 = ref<string | number>("");
      let msg2 = ref<string | number>("");
      
      watchEffect(() => {
        console.log("----");
        console.log("msg1: ", msg1.value);
        console.log("msg2: ", msg2.value);
      });
      </script>
      
      <style lang="less" scoped>
      </style>
      
      
    • 消除副作用:就是在触发监听之前会调用一个函数可以处理你的逻辑例如防抖。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <input type="text" v-model.lazy="msg1">
        </div>
      </template>
      
      <script setup lang="ts">
      import { watchEffect, ref } from "vue";
      
      let msg1 = ref<string | number>("");
      
      watchEffect((before) => {
        before(()=> {
          console.log("before");
        })
        console.log("----");
        console.log("msg1: ", msg1.value);
      });
      </script>
      
      <style lang="less" scoped>
      </style>
      
      
    • 停止跟踪:watchEffect返回一个函数,调用后停止更新

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <input type="text" v-model.lazy="msg">
          <button @click="stop">停止侦听</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { watchEffect, ref } from "vue";
      
      let msg = ref<string | number>("");
      
      const stop = watchEffect((before) => {
        before(()=> {
          console.log("before");
        })
        console.log("----");
        console.log("msg: ", msg.value);
      });
      </script>
      
      <style lang="less" scoped>
      </style>
      
      
    • 消除副作用的刷新机制。副作用刷新时机flush一般使用post。

      pre sync post
      更新时机 组件更新前执行 强制效果始终同步触发 组件更新后执行
      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <input type="text" v-model.lazy="msg">
          <button @click="stop">停止侦听</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { watchEffect, ref } from "vue";
      
      let msg = ref<string | number>("");
      
      const stop = watchEffect((before) => {
        before(() => {
          console.log("before");
        })
        console.log("----");
        console.log("msg: ", msg.value);
      }, {
        flush: "post"
      });
      </script>
      
      <style lang="less" scoped>
      </style>
      
      
    • onTrigger可以帮我们调试watchEffect。

      <template>
        <div class="about">
          <h1>This is an about page</h1>
      
          <input type="text" v-model.lazy="msg">
          <button @click="stop">停止侦听</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { watchEffect, ref } from "vue";
      
      let msg = ref<string | number>("");
      
      const stop = watchEffect((before) => {
        before(() => {
          console.log("before");
        })
        console.log("----");
        console.log("msg: ", msg.value);
      }, {
        flush: "post",
        onTrigger(e) {
          debugger
        }
      });
      </script>
      
      <style lang="less" scoped>
      </style>
      
      

    认识组件和生命周期

    <template>
      <div class="about">
        <h1>This is an about page</h1>
    
        <div id="hello">我是Hello组件</div>
        <br />
        <p>count: {{ count }}</p>
        <button @click="count++">更新Count值</button>
      </div>
    </template>
    
    <script setup lang="ts">
    import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, ref } from "vue";
    
    let count = ref<number>(1);
    
    onBeforeMount(() => {
      let div = document.querySelector("#hello"); // 获取到的是null
      console.log("创建之前 -->> onBeforeMount", div);
    });
    
    onMounted(() => {
      let div = document.querySelector("#hello"); // 可以获取到元素值
      console.log("创建完成 -->> onMounted", div);
    });
    
    // onBeforeUpdate和onUpdated生命周期
    // 例如组件发生了更新/变化,会进入这两个生命周期
    onBeforeUpdate(() => {
      console.log("更新之前 -->> onBeforeUpdate");
    });
    
    onUpdated(() => {
      console.log("更新完成 -->> onUpdated");
    });
    
    // 组件卸载之前和卸载完成
    // 可以在组件被调用的位置加上一个v-if属性进行模拟组件卸载的效果
    onBeforeUnmount(() => {
      console.log("卸载之前 -->> onBeforeUnmount");
    });
    
    onUnmounted(() => {
      console.log("卸载完成 -->> onUnmounted");
    });
    
    </script>
    
    <style lang="less" scoped>
    </style>
    
    

    父子组件传参

    以views/HomeView.vue组件和components/HelloWorld.vue组件的传值为例说明。

    父组件给子组件传参

    通过自定义属性进行传值。

    1. src\views\HomeView.vue父组件传递参数值。
      传递字符串类型可以不用加v-bind。
      传递非字符串类型,需要加v-bind。

      <template>
        <div class="home">
       <!-- 父组件传子组件,文本类型的参数直接传参即可,其他类型参数需要在传参前面加上一个v-bind -->
          <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" :dataArr="dataArr" />
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive } from 'vue';
      import HelloWorld from '../components/HelloWorld.vue'; // @ is an alias to /src
      
      let dataArr = reactive<number[]>([1, 2, 3, 4]);
      
      
      </script>
      
      
    2. src\components\HelloWorld.vue子组件接收传值。
      通过defineProps来接收。defineProps是无须引入,直接使用即可。
      如果使用的是TypeScript,可以使用传递字面量类型的纯类型语法作为参数。
      TypeScript接收方式。

      <template>
        <div class="hello">
          <h1>{{ msg }}</h1>
          <p>{{ dataArr }}</p>
        </div>
      </template>
      
      <script setup lang="ts">
      
      type Props = {
        msg: string,
        dataArr: number[]
      }
      defineProps<Props>();
      
      </script>
      
      <!-- Add "scoped" attribute to limit CSS to this component only -->
      <style scoped lang="less">
      </style>
      
      

      TypeScript特有的默认值方式。
      withDefault是个函数也是无须引入开箱即用,接收参数中,第一个参数props函数,第二个参数是一个对象,设置默认值。

      <template>
        <div class="hello">
          <h1>{{ msg }}</h1>
          <p>{{ dataArr }}</p>
        </div>
      </template>
      
      <script setup lang="ts">
      
      type Props = {
        msg?: string,
        dataArr?: number[]
      }
      withDefaults(defineProps<Props>(), {
        msg: "张三", // 配置默认值
        dataArr: () => [6, 6, 6] // 复杂函数需要使用函数返回值得方式使用
      });
      
      </script>
      
      <!-- Add "scoped" attribute to limit CSS to this component only -->
      <style scoped lang="less">
      </style>
      
      

    子组件给父组件传参

    通过自定义事件进行传值。

    1. 修改文件src\components\HelloWorld.vue。

      <template>
        <div class="hello">
          <h1>{{ msg }}</h1>
          <p>{{ dataArr }}</p>
      
          <div>
            <button @click="clickTip1"> 子组件派发值到父组件1</button>
            <button @click="clickTip2"> 子组件派发值到父组件2</button>
          </div>
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive } from "@vue/reactivity";
      
      
      // 子组件接收父组件的传值
      type Props = {
        msg?: string,
        dataArr?: number[]
      }
      withDefaults(defineProps<Props>(), {
        msg: "张三", // 配置默认值
        dataArr: () => [6, 6, 6] // 复杂函数需要使用函数返回值得方式使用
      });
      
      
      // 子组件向父组件传值
      let list = reactive<number[]>([8, 8, 8]);
      let flag = true;
      const emit = defineEmits(['sunToFather1', 'sunToFather2']); // 自定义属性
      const clickTip1 = () => {
        emit('sunToFather1', list, flag);
      }
      const clickTip2 = () => {
        emit('sunToFather2', list);
      }
      
      </script>
      
      <!-- Add "scoped" attribute to limit CSS to this component only -->
      <style scoped lang="less">
      </style>
      
      
    2. src\views\HomeView.vue接收子组件的传参。

      <template>
        <div class="home">
          <!-- 父组件传子组件,自定义属性,文本类型的参数直接传参即可,其他类型参数需要在传参前面加上一个v-bind -->
          <!-- 父组件接收子组件传值,自定义事件 -->
          <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" :dataArr="dataArr" @sunToFather1="getList1"
            @sunToFather2="getList2" />
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive } from 'vue';
      import HelloWorld from '../components/HelloWorld.vue'; // @ is an alias to /src
      
      let dataArr = reactive<number[]>([1, 2, 3, 4]);
      
      const getList1 = (list: number[], flag: boolean) => {
        console.log(list, flag, "我是子组件传递过来的值");
      }
      
      const getList2 = (list: number[]) => {
        console.log(list, "我是子组件传递过来的值");
      }
      
      </script>
      
      

    子组件暴露给父组件的内部属性

    通过defineExpress。

    1. 子组件通过defineExpress暴露属性给父组件。

      <script setup lang="ts">
          const list = reactive<number[]>([4, 5, 6]);
          defineExpress({
              list,
          });
      </script>
      
    2. 父组件获取子组件实例通过ref。

      <template>
        <div class="home">
          <HelloWorld ref="helloWorld" />
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive, ref } from 'vue';
      import HelloWorld from '../components/HelloWorld.vue'; // @ is an alias to /src
      
      const helloWorld = ref(null);
      
      </script>
      
      

    举例说明。

    1. 修改文件src\components\HelloWorld.vue。

      <template>
        <div class="hello">
          <h1>{{ msg }}</h1>
          <p>{{ dataArr }}</p>
      
          <div>
            <button @click="clickTip1"> 子组件派发值到父组件1</button>
          </div>
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive } from "@vue/reactivity";
      import { ref } from "vue";
      
      
      // 子组件接收父组件的传值
      type Props = {
        msg?: string,
        dataArr?: number[]
      }
      withDefaults(defineProps<Props>(), {
        msg: "张三", // 配置默认值
        dataArr: () => [6, 6, 6] // 复杂函数需要使用函数返回值得方式使用
      });
      
      
      // 子组件向父组件传值
      let list = reactive<number[]>([8, 8, 8]);
      let flag = ref(true);
      const emit = defineEmits(['sunToFather1']); // 自定义属性
      const clickTip1 = () => {
        emit('sunToFather1', list, flag);
      }
      
      
      // 子组件暴露给父组件内部属性
      defineExpose({
        list,
      })
      
      </script>
      
      <!-- Add "scoped" attribute to limit CSS to this component only -->
      <style scoped lang="less">
      </style>
      
      
    2. 修改文件。

      <template>
        <div class="home">
          <!-- 父组件传子组件,自定义属性,文本类型的参数直接传参即可,其他类型参数需要在传参前面加上一个v-bind -->
          <!-- 父组件接收子组件传值,自定义事件 -->
          <HelloWorld ref="helloWorld" @sunToFather1="getList1" />
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive, ref } from 'vue';
      import HelloWorld from '../components/HelloWorld.vue'; // @ is an alias to /src
      
      const helloWorld = ref(null);
      const getList1 = () => {
        console.log(helloWorld.value); // 在Proxy > Target > Target中能拿到list的值
      }
      
      </script>
      
      

    v-model实现父子组件的双向绑定

    直接使用默认值

    1. 新建一个Vite项目。

    2. 修改文件App.vue。

      <template>
        <h3>我是父组件</h3>
        <button @click="flag = !flag">修改Flag值</button>
        <p>{{ flag }}</p>
        <HelloWorld v-model="flag" />
      </template>
      
      <script setup lang="ts">
      import { ref } from '@vue/reactivity'
      import HelloWorld from './components/HelloWorld.vue'
      
      let flag = ref<boolean>(true);
      let title = ref<string>("我是一条狗");
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    3. 修改文件HelloWorld.vue。

      
      <template>
        <div v-if="modelValue" class="hello">
          <h3>我是子组件</h3>
          <p>{{ modelValue }}</p>
        </div>
      
      </template>
      
      <script setup lang="ts">
      import { ref } from 'vue'
      
      type Props = {
        modelValue: boolean,
      }
      defineProps<Props>();
      
      </script>
      
      <style scoped>
      .hello {
        background-color: aqua;
         200px;
        height: 200px;
      }
      </style>
      
      

    子组件修改父组件值

    修改子组件HelloWorld.vue。

    
    <template>
      <div v-if="modelValue" class="hello">
        <h3>我是子组件</h3>
        <p>{{ modelValue }}</p>
        <button @click="close">Close</button>
      </div>
    
    </template>
    
    <script setup lang="ts">
    import { ref } from 'vue'
    
    type Props = {
      modelValue: boolean,
    }
    defineProps<Props>();
    
    
    const emit = defineEmits(['update:modelValue']); // 自定义属性
    const close = () => {
      emit('update:modelValue', false);
    }
    </script>
    
    <style scoped>
    .hello {
      background-color: aqua;
       200px;
      height: 200px;
    }
    </style>
    
    

    自定义名称及实现双向绑定 *

    1. 修改父组件文件src\App.vue。

      <template>
        <h3>我是父组件</h3>
        <button @click="flag = !flag">修改Flag值</button>
        <p>{{ flag }}</p>
        <p>{{ title }}</p>
        <!-- 支持传递绑定多个v-model参数,以及支持自定义名称 -->
        <HelloWorld v-model:title="title" v-model="flag" />
      </template>
      
      <script setup lang="ts">
      import { ref } from '@vue/reactivity'
      import HelloWorld from './components/HelloWorld.vue'
      
      let flag = ref<boolean>(true);
      let title = ref<string>("我是一条狗");
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    2. 修改子组件文件src\components\HelloWorld.vue。

      
      <template>
        <div v-if="modelValue" class="hello">
          <h3>我是子组件</h3>
          <p>Flag: {{ modelValue }}</p>
          <p>Title: {{ title }}</p>
          <button @click="close">CloseAndChangeTitle</button>
        </div>
      
      </template>
      
      <script setup lang="ts">
      import { ref } from 'vue'
      
      type Props = {
        modelValue: boolean,
        title: string,
      }
      defineProps<Props>();
      
      const emit = defineEmits(['update:modelValue', 'update:title']); // 自定义属性
      const close = () => {
        emit('update:modelValue', false);
        emit('update:title', '我是一只猫');
      }
      </script>
      
      <style scoped>
      .hello {
        background-color: aqua;
         200px;
        height: 200px;
      }
      </style>
      
      

    支持自定义修饰符

    1. 修改父组件文件src\App.vue。

      <template>
        <h3>我是父组件</h3>
        <button @click="flag = !flag">修改Flag值</button>
        <p>{{ flag }}</p>
        <p>{{ title }}</p>
        <!-- 支持传递绑定多个v-model参数,以及支持自定义名称 -->
        <HelloWorld v-model:title.selfAA="title" v-model.bb="flag" />
      </template>
      
      <script setup lang="ts">
      import { ref } from '@vue/reactivity'
      import HelloWorld from './components/HelloWorld.vue'
      
      let flag = ref<boolean>(true);
      let title = ref<string>("我是一条狗");
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    2. 修改子组件文件src\components\HelloWorld.vue。

      
      <template>
        <div v-if="modelValue" class="hello">
          <h3>我是子组件</h3>
          <p>Flag: {{ modelValue }}</p>
          <p>Title: {{ title }}</p>
          <button @click="close">CloseAndChangeTitle</button>
        </div>
      
      </template>
      
      <script setup lang="ts">
      import { ref } from 'vue'
      
      type Props = {
        modelValue: boolean,
        title: string,
        modelModifiers?: {
          bb: boolean,
        },
        titleModifiers?: {
          selfAA: boolean
        }
      }
      const PropsData = defineProps<Props>();
      
      const emit = defineEmits(['update:modelValue', 'update:title']); // 自定义属性
      const close = () => {
        console.log("自定义修饰符", PropsData);
      
        if (PropsData.modelModifiers?.bb) {
          emit('update:title', '我是一只猫');
        } else {
          emit('update:title', '我是一只狗');
        }
      
        emit('update:modelValue', false);
      }
      </script>
      
      <style scoped>
      .hello {
        background-color: aqua;
         200px;
        height: 200px;
      }
      </style>
      
      

    全局组件、局部组件和递归组件

    全局组件

    例如组件使用频率非常高(table,Input,button,等)这些组件,几乎每个页面都在使用便可以封装成全局组件。

    1. 新建组件src\components\Cart.vue。

      <template>
          <div class="card">
              <div class="card-header">
                  <div>标题</div>
                  <div>副标题</div>
              </div>
              <div v-if='content' class="card-content">
                  {{ content }}
              </div>
          </div>
      </template>
      
      <script setup lang="ts">
      type Props = {
          content: string
      }
      defineProps<Props>()
      
      </script>
      
      <style scoped lang='less'>
      @border: #ccc;
      
      .card {
           300px;
          border: 1px solid @border;
          border-radius: 3px;
      
          &:hover {
              box-shadow: 0 0 10px @border;
          }
      
          &-content {
              padding: 10px;
          }
      
          &-header {
              display: flex;
              justify-content: space-between;
              padding: 10px;
              border-bottom: 1px solid @border;
          }
      }
      </style>
      
    2. 在src\main.ts中引入组件,跟随在createApp(App)后面,切记不能放到mount后面。

      import { createApp } from "vue";
      import App from "./App.vue";
      import router from "./router";
      import store from "./store";
      import Cart from "./components/Cart.vue";
      
      createApp(App).component("Cart", Cart).use(store).use(router).mount("#app");
      
      
    3. 使用时直接引用该组件即可。

      <template>
        <div class="home">
          <Cart />
        </div>
      </template>
      

    局部组件

    就是在组件A通过import引入别的组件B,称之为局部组件。

    1. 新建组件src\components\Cart.vue。
      同上。

    2. 在src\views\HomeView.vue中引入Cart.vue组件。

      <template>
        <div class="home">
          <Cart content=""/>
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive, ref } from 'vue';
      import HelloWorld from '../components/HelloWorld.vue'; // @ is an alias to /src
      import Cart from '../components/Cart.vue'
      
      const helloWorld = ref(null);
      const getList1 = () => {
        console.log(helloWorld.value); // 在Proxy > Target > Target中能拿到list的值
      }
      
      </script>
      
      

    递归组件(未完成)

    原理跟我们写JS递归是一样的,自己调用自己,通过一个条件来结束递归,否则导致内存泄漏。

    1. 新建文件src\components\Tree.vue。

      <template>
          <div class="tree">
              <h3>我是Tree组件</h3>
          </div>
      </template>
      
      <script setup lang="ts">
      </script>
      
      <style scoped lang='less'>
      </style>
      
    2. 在文件src\views\HomeView.vue中,

      <template>
        <div class="home">
          <!-- 将参数传递给子组件 -->
          <Tree :data="data"></Tree>
        </div>
      </template>
      
      <script setup lang="ts">
      import Tree from '../components/Tree.vue'
      import { reactive } from "vue";
      type TreeList = {
        name: string;
        icon?: string;
        children?: TreeList[] | [];
      };
      const data = reactive<TreeList[]>([
        {
          name: "no.1",
          children: [
            {
              name: "no.1-1",
              children: [
                {
                  name: "no.1-1-1",
                },
              ],
            },
          ],
        },
        {
          name: "no.2",
          children: [
            {
              name: "no.2-1",
            },
          ],
        },
        {
          name: "no.3",
        },
      ]);
      
      </script>
      
      
    3. 子组件接收参数。修改文件src\components\Tree.vue。

    动态组件

    1. 新建组件src\components\A.vue。

      <template>
          <div class="a">
              <h3>我是组件A</h3>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style scoped lang='less'>
      .a {
          background-color: orangered;
          height: 100px;
           200px;
          margin: auto;
      }
      
    ```
    1. 新建组件src\components\B.vue。

      <template>
       <div class="b">
           <h3>我是组件B</h3>
       </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style scoped lang='less'>
      .b {
       background-color: blue;
       height: 100px;
        200px;
       margin: auto;
      }
      </style>
      
    2. 新建组件src\components\C.vue。

      <template>
       <div class="c">
           <h3>我是组件C</h3>
       </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style scoped lang='less'>
      .c {
       background-color: green;
       height: 100px;
        200px;
       margin: auto;
      }
      </style>
      
    3. 修改文件src\views\HomeView.vue。

      <template>
        <div class="home">
          <button v-for="item in data" :key="item.name" @click="switchComp(item)">{{ item.name }}</button>
      
          <component :is="cur.compName"></component>
        </div>
      </template>
      
      <script setup lang="ts">
      import { markRaw, reactive } from '@vue/reactivity'
      import A from '../components/A.vue'
      import B from '../components/B.vue'
      import C from '../components/C.vue'
      
      type Tabs = {
        name: string,
        compName: any
      }
      const data = reactive<Tabs[]>([
        {
          name: "我是A组件",
          compName: markRaw(A),
        },
        {
          name: "我是B组件",
          compName: markRaw(B),
        },
        {
          name: "我是C组件",
          compName: markRaw(C),
        },
      ]);
      
      let cur = reactive({
        compName: data[0].compName, // 定义一个默认值
      })
      
      const switchComp = (item: Tabs) => {
        cur.compName = item.compName;
      }
      
      </script>
      
      

    插槽

    匿名插槽

    1. 子组件放置一个插槽。新建组件src\components\A.vue。

      <template>
          <div class="a">
              <h3>我是组件A</h3>
      
              <slot></slot>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style scoped lang='less'>
      .a {
          background-color: orangered;
          height: 100px;
           200px;
          margin: auto;
      }
      </style>
      
    2. 父组件使用插槽。在父组件给这个插槽填充内容。修改文件src\views\HomeView.vue。

      <template>
        <div class="home">
          <h1>我是HomeView页面</h1>
          <A>
            <template v-slot>
              <h>滕王阁序</h>
            </template>
          </A>
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive } from '@vue/reactivity'
      import A from '../components/A.vue'
      </script>
      
      

    具名插槽

    具名插槽其实就是给插槽取个名字。一个子组件可以放多个插槽,而且可以放在不同的地方,而父组件填充内容时,可以根据这个名字把内容填充到对应插槽中。

    1. 新建组件src\components\A.vue。

      <template>
          <div class="a">
              <h3>我是组件A</h3>
      
              <slot name="header"></slot>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style scoped lang='less'>
      .a {
          background-color: orangered;
          height: 100px;
           200px;
          margin: auto;
      }
      </style>
      
    2. 新建组件src\components\B.vue。

      <template>
       <div class="b">
           <h3>我是组件B</h3>
      
           <slot name="middle"></slot>
       </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style scoped lang='less'>
      .b {
       background-color: blue;
       height: 100px;
        200px;
       margin: auto;
      }
      </style>
      
    3. 新建组件src\components\C.vue。

      <template>
       <div class="c">
           <h3>我是组件C</h3>
      
           <slot name="footer"></slot>
       </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style scoped lang='less'>
      .c {
       background-color: green;
       height: 100px;
        200px;
       margin: auto;
      }
      </style>
      
    4. 修改文件src\views\HomeView.vue。

      <template>
        <div class="home">
          <h1>我是HomeView页面</h1>
          <A>
            <template #header>
              <h>滕王阁序</h>
            </template>
          </A>
          <B>
            <template #middle>
              <h>正文,哈哈</h>
            </template>
          </B>
          <C>
            <template #footer>
              <h>结尾了哦</h>
            </template>
          </C>
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive } from '@vue/reactivity'
      import A from '../components/A.vue'
      import B from '../components/B.vue'
      import C from '../components/C.vue'
      </script>
      
      

    作用域插槽

    1. 子组件放置一个插槽。新建组件src\components\A.vue。

      <template>
          <div class="a">
              <h3>我是组件A</h3>
              <div v-for="(item, index) in data" :key="item.name">
                  <slot :data="item" :index="index"></slot>
              </div>
          </div>
      </template>
      
      <script setup lang="ts">
      import { reactive } from "vue";
      
      type Names = {
          name: string,
          age: number,
      }
      const data = reactive<Names[]>([
          {
              name: "张三",
              age: 16,
          },
          {
              name: "李四",
              age: 15,
          },
          {
              name: "王五",
              age: 18,
          },
      ]);
      </script>
      
      <style scoped lang='less'>
      .a {
          background-color: orangered;
          height: 280px;
           200px;
          margin: auto;
      }
      </style>
      
    2. 父组件使用插槽。在父组件给这个插槽填充内容。修改文件src\views\HomeView.vue。

      <template>
        <div class="home">
          <h1>我是HomeView页面</h1>
      
          <A>
            <template #default="scoop">
              <h>滕王阁序</h>
              <p>{{ scoop.data.name }} -- {{ scoop.index }}</p>
            </template>
          </A>
      
          <!-- 结构解析 -->
          <A>
            <template #default="{ data, index }">
              <h>滕王阁序</h>
              <p>{{ data.name }} -- {{ index }}</p>
            </template>
          </A>
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive } from '@vue/reactivity'
      import A from '../components/A.vue'
      </script>
      
      

    动态插槽

    1. 新建组件src\components\A.vue。

      <template>
          <div class="a">
              <h3>我是组件A</h3>
      
              <slot name="header"></slot>
              <p>header -- default</p>
              <slot></slot> <!-- name="default" -->
              <p>default -- footer</p>
              <slot name="footer"></slot>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style scoped lang='less'>
      .a {
          background-color: orangered;
          height: 200px;
           200px;
          margin: auto;
      }
      </style>
      
      
    2. 修改文件src\views\HomeView.vue。

      <template>
        <div class="home">
          <h1>我是HomeView页面</h1>
      
          <A>
            <template #[name]>
              <h>滕王阁序</h>
            </template>
          </A>
        </div>
      </template>
      
      <script setup lang="ts">
      import { reactive, ref } from '@vue/reactivity'
      import A from '../components/A.vue'
      
      const name = ref("footer");
      </script>
      
      

    异步组件&代码分包&suspense

    异步组件

    在大型应用中,我们可能需要将应用分割成小一些的代码块,并且减少主包的体积。

    这时候就可以使用异步组件。

    Teleport传送组件

    Teleport Vue 3.0新特性之一。

    Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。

    主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响

    使用方法

    • 通过to属性 插入指定元素位置 to="body" 便可以将Teleport 内容传送到指定位置。

      <template>
        <div class="home">
          <h1>我是HomeView页面</h1>
      
          <Teleport to='body'>
            Hello teleport
          </Teleport>
        </div>
      </template>
      
      <script setup lang="ts">
      </script>
      
      
    • 可以自定义传送位置,支持class id等选择器。可以使用多个。

    <template>
      <div class="home">
        <h1>我是HomeView页面</h1>
    
        <Teleport to='#app'>
          Hello teleport
        </Teleport>
        <Teleport to='#app'>
          Hello teleport
        </Teleport>
    
      </div>
    </template>
    
    <script setup lang="ts">
    </script>
    
    

    keep-alive缓存组件

    有时候我们不希望组件被重新渲染影响使用体验;或者处于性能考虑,避免多次重复渲染降低性能。而是希望组件可以缓存下来,维持当前的状态。这时候就需要用到keep-alive组件。

    开启keep-alive生命周期的变化

    • 初次进入时:onMounted> onActivated
    • 退出后触发deactivated
    • 再次进入:
    • 只会触发onActivated
    • 事件挂载的方法等,只执行一次的放在 onMounted中;组件每次进去执行的方法放在onActivated中。

    操作步骤

    1. 新建文件src\components\A.vue。

      <template>
          <div class="a">
              <h3>我是组件A</h3>
              <table>
                  <tr>
                      <td>帐号</td>
                      <td><input type="text" v-model="form.login"></td>
                  </tr>
                  <tr>
                      <td>密码</td>
                      <td><input type="text" v-model="form.psd"></td>
                  </tr>
                  <tr>
                      <td>验证码</td>
                      <td><input type="text" v-model="form.code"></td>
                  </tr>
              </table>
              <button @click="submit">登录</button>
          </div>
      </template>
      
      <script setup lang="ts">
      import { reactive } from "@vue/reactivity";
      import { onActivated, onDeactivated, onMounted, onUnmounted } from "@vue/runtime-core";
      
      const form = reactive({
          login: "",
          psd: "",
          code: "",
      });
      
      const submit = () => {
          console.log(form);
      };
      
      
      onMounted(() => {
          console.log("Register onMounted.");
      });
      
      onUnmounted(() => {
          console.log("Register onUnmounted.");
      });
      
      onActivated(() => {
          console.log("Register onActivated.");
      });
      
      onDeactivated(() => {
          console.log("Register onDeactivated.");
      });
      
      </script>
      
      <style scoped lang='less'>
      .a {
          background-color: olive;
          height: 200px;
           300px;
          margin: auto;
      }
      </style>
      
      
    2. 新建文件src\components\B.vue。

      <template>
          <div class="a">
              <h3>我是组件B</h3>
              <table>
                  <tr>
                      <td>帐号</td>
                      <td><input type="text" v-model="form.login"></td>
                  </tr>
                  <tr>
                      <td>密码</td>
                      <td><input type="text" v-model="form.psd"></td>
                  </tr>
              </table>
              <button @click="register">注册</button>
          </div>
      </template>
      
      <script setup lang="ts">
      import { reactive } from "@vue/reactivity";
      
      const form = reactive({
          login: "",
          psd: "",
      });
      
      const register = () => {
          console.log(form);
      }
      </script>
      
      <style scoped lang='less'>
      .a {
          background-color: green;
          height: 200px;
           300px;
          margin: auto;
      }
      </style>
      
      
    3. 修改文件src\views\HomeView.vue。

      <template>
        <div class="home">
          <h1>我是HomeView页面</h1>
          <button @click="switchComp">切换</button>
      
          <keep-alive>
            <A v-if="flag"></A>
            <B v-else></B>
          </keep-alive>
        </div>
      </template>
      
      <script setup lang="ts">
      import { ref } from 'vue';
      import A from '../components/A.vue'
      import B from '../components/B.vue'
      
      let flag = ref<boolean>(true);
      
      const switchComp = () => {
        flag.value = !flag.value;
      }
      
      </script>
      
      

      参数介绍

      include和exclude prop允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示。

      <keep-alive :include="" :exclude="" :max=""></keep-alive>
      
      1. src\components\B.vue新建文件src\components\A.vue。

        ...
        
        <script lang="ts">
        export default {
            name: 'A'
        }
        </script>
        
      2. 修改文件src\components\B.vue。

        ...
        
        <script lang="ts">
        export default {
            name: 'B'
        }
        </script>
        
      3. 修改文件src\views\HomeView.vue。

        <template>
          <div class="home">
            <h1>我是HomeView页面</h1>
            <button @click="switchComp">切换</button>
        
            <keep-alive :include="['A', 'B']">
              <A v-if="flag"></A>
              <B v-else></B>
            </keep-alive>
          </div>
        </template>
        
        <script setup lang="ts">
        import { ref } from 'vue';
        import A from '../components/A.vue'
        import B from '../components/B.vue'
        
        let flag = ref<boolean>(true);
        
        const switchComp = () => {
          flag.value = !flag.value;
        }
        
        </script>
        
        

    transition动画组件

    基本使用

    修改文件src\views\HomeView.vue。

    <template>
      <div class="home">
        <h1>我是HomeView页面</h1>
        <button @click="flag = !flag">切换</button>
        <transition name="fade">
          <div class="box" v-if="flag"></div>
        </transition>
      </div>
    </template>
    
    <script setup lang="ts">
    import { ref } from "vue";
    
    let flag = ref<boolean>(true);
    </script>
    
    <style scoped>
    .box {
       200px;
      height: 200px;
      background-color: red;
    }
    
    .fade-enter-from {
      background: red;
       0px;
      height: 0px;
      transform: rotate(360deg)
    }
    
    .fade-enter-active {
      transition: all 2.5s linear;
    }
    
    .fade-enter-to {
      background: yellow;
       200px;
      height: 200px;
    }
    
    .fade-leave-from {
       200px;
      height: 200px;
      transform: rotate(360deg)
    }
    
    .fade-leave-active {
      transition: all 1s linear;
    }
    
    .fade-leave-to {
       0px;
      height: 0px;
    }
    </style>
    
    

    自定义过渡类名并结合Animate三方库使用

    transition props

    • enter-from-class
    • enter-active-class
    • enter-to-class
    • leave-from-class
    • leave-active-class
    • leave-to-class

    自定义过渡时间(单位:毫秒),或者分别指定进入和离开的持续时间。

    <transition :duration="1000">...</transition>
    
    <transition :duration="{ enter: 1000, leave: 800 }">...</transition>
    

    通过自定义class结合CSS动画库Animate CSS。

    1. 安装依赖库。

      npm i -D animate.css
      
    2. 引入import "animate.css",并使用动画库。
      修改文件src\views\HomeView.vue。

      <template>
        <div class="home">
          <h1>我是HomeView页面</h1>
          <button @click="flag = !flag">切换</button>
          <transition
            leave-active-class="animate__animated animate__fadeInDownBig"
            enter-active-class="animate__animated animate__bounceInRight"
          >
            <div v-if="flag" class="box"></div>
          </transition>
        </div>
      </template>
      
      <script setup lang="ts">
      import { ref } from "vue";
      import "animate.css";
      
      let flag = ref<boolean>(true);
      </script>
      
      <style scoped>
      .box {
         200px;
        height: 200px;
        background-color: red;
      }
      </style>
      

    transition的8个生命周期结合GSAP三方库使用

    @before-enter="beforeEnter" // 对应enter-from
    @enter="enter" // 对应enter-active
    @after-enter="afterEnter" // 对应enter-to
    @enter-cancelled="enterCancelled" // 显示过度打断
    @before-leave="beforeLeave" // 对应leave-from
    @leave="leave" // 对应enter-active
    @after-leave="afterLeave" // 对应leave-to
    @leave-cancelled="leaveCancelled" // 离开过度打断
    

    修改文件src\views\HomeView.vue。

    <template>
      <div class="home">
        <h1>我是HomeView页面</h1>
        <button @click="flag = !flag">切换</button>
        <transition
          leave-active-class="animate__animated animate__fadeInDownBig"
          enter-active-class="animate__animated animate__bounceInRight"
          @before-enter="enterFrom"
          @enter="enterActive"
          @after-enter="enterTo"
          @enter-cancelled="enterCancelled"
          @before-leave="leaveFrom"
          @leave="leaveActive"
          @after-leave="leaveTo"
          @leave-cancelled="leaveCancelled"
        >
          <div v-if="flag" class="box"></div>
        </transition>
      </div>
    </template>
    
    <script setup lang="ts">
    import { ref } from "vue";
    import "animate.css";
    
    let flag = ref<boolean>(true);
    
    const enterFrom = (el: Element) => {
      console.log("进入之前", el);
    };
    
    const enterActive = (el: Element, done: Function) => {
      console.log("过渡曲线", el);
      setTimeout(() => {
        done();
      }, 3000);
    };
    
    const enterTo = (el: Element) => {
      console.log("过渡完成", el);
    };
    
    const enterCancelled = (el: Element) => {
      console.log("过渡被打断", el);
    };
    
    const leaveFrom = (el: Element) => {
      console.log("过渡完成", el);
    };
    
    const leaveActive = (el: Element, done: Function) => {
      console.log("离开之前", el);
      setTimeout(() => {
        done();
      }, 5000);
    };
    
    const leaveTo = (el: Element) => {
      console.log("过渡曲线", el);
    };
    
    const leaveCancelled = (el: Element) => {
      console.log("离开被打断", el);
    };
    </script>
    
    <style scoped>
    .box {
       200px;
      height: 200px;
      background-color: red;
    }
    </style>
    

    结合GSAP三方库使用

    1. 安装依赖库。

      npm i -D gsap
      
    2. 引入import gsap "gsap",并使用动画库。
      修改文件src\views\HomeView.vue。

      <template>
        <div class="home">
          <h1>我是HomeView页面</h1>
          <button @click="flag = !flag">切换</button>
          <transition
            leave-active-class="animate__animated animate__fadeInDownBig"
            enter-active-class="animate__animated animate__bounceInRight"
            @before-enter="enterFrom"
            @enter="enterActive"
            @leave="leaveActive"
          >
            <div v-if="flag" class="box"></div>
          </transition>
        </div>
      </template>
      
      <script setup lang="ts">
      import { ref } from "vue";
      import gsap from "gsap";
      
      let flag = ref<boolean>(true);
      
      const enterFrom = (el: Element) => {
        console.log("进入之前", el);
        gsap.set(el, {
           0,
          height: 0,
        });
      };
      
      const enterActive = (el: Element, done: gsap.Callback) => {
        console.log("过渡曲线", el);
        gsap.to(el, {
           200,
          height: 200,
          onComplete: done,
        });
      };
      
      const leaveActive = (el: Element, done: gsap.Callback) => {
        console.log("离开之前", el);
        gsap.to(el, {
           0,
          height: 0,
          onComplete: done,
        });
      };
      </script>
      
      <style scoped>
      .box {
         200px;
        height: 200px;
        background-color: red;
      }
      </style>
      
      

    appear属性页面加载完成的开始动画

    通过这个属性可以设置初始节点过度,就是页面加载完成就开始动画,对应三个状态。

    只是针对加载完成的开始动画。

    appear
    appear-from-class=""
    appear-active-class=""
    appear-to-class=""
    

    修改文件src\views\HomeView.vue。

    <template>
      <div class="home">
        <h1>我是HomeView页面</h1>
        <button @click="flag = !flag">切换</button>
        <transition
          appear
          appear-from-class="from"
          appear-active-class="active"
          appear-to-class="to"
        >
          <div v-if="flag" class="box"></div>
        </transition>
      </div>
    </template>
    
    <script setup lang="ts">
    import { ref } from "vue";
    
    let flag = ref<boolean>(true);
    </script>
    
    <style scoped>
    .box {
       200px;
      height: 200px;
      background-color: red;
    }
    
    .from {
       0;
      height: 0;
    }
    
    .active {
      transition: all 2s ease;
    }
    
    .to {
       200px;
      height: 200px;
    }
    </style>
    
    

    也可以结合Animate三方库一起使用。

    修改文件src\views\HomeView.vue。

    <template>
      <div class="home">
        <h1>我是HomeView页面</h1>
        <button @click="flag = !flag">切换</button>
        <transition
          appear
          appear-active-class="animate__animated animate__fadeInDownBig"
        >
          <div v-if="flag" class="box"></div>
        </transition>
      </div>
    </template>
    
    <script setup lang="ts">
    import { ref } from "vue";
    import "animate.css";
    
    let flag = ref<boolean>(true);
    </script>
    
    <style scoped>
    .box {
       200px;
      height: 200px;
      background-color: red;
    }
    </style>
    
    

    transition-group过渡列表

    过渡列表

    <template>
      <div class="home">
        <h1>我是HomeView页面</h1>
        <button @click="addItem">ADD</button>
        <button @click="deleteItem">POP</button>
        <div class="wraps">
          <!-- tag="section"为可选配置,配置之后,会多包裹一层div -->
          <transition-group
            enter-active-class="animate__animated animate__fadeInDownBig"
            leave-active-class="animate__animated animate__hinge"
          >
            <div class="item" v-for="(item, index) in list" :key="index">
              {{ item }}
            </div>
          </transition-group>
        </div>
      </div>
    </template>
    
    <script setup lang="ts">
    import { reactive } from "vue";
    import "animate.css";
    
    let list = reactive<number[]>([1, 2, 3, 5, 6, 6, 4]);
    const addItem = () => {
      list.push(list.length + 1);
    };
    
    const deleteItem = () => {
      list.pop();
    };
    </script>
    
    <style lang="less" scoped>
    .wraps {
      display: flex;
      flex-wrap: wrap;
      word-break: break-all;
      border: 1px solid #ccc;
      .item {
        margin: 10px;
        font-size: 30px;
      }
    }
    </style>
    
    

    列表的移动过渡

    参见参考内容。

    列表的状态过渡

    参见参考内容。

    依赖注入(Provide/Inject)

    通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。

    官网的解释很让人疑惑,那我翻译下这几句话:

    provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。

    1. 修改文件src\views\HomeView.vue。

      <template lang="">
          <div class="content-home">
              <h3>我是HomeView组件</h3>
              <p>{{ flag }}</p>
              <A></A>
          </div>
      </template>
      
      <script setup lang="ts">
      import { ref } from '@vue/reactivity';
      import { provide } from '@vue/runtime-core';
      import A from '../components/A.vue';
      // 使用ref或者reactive才能将传递的参数变成响应式的参数
      let flag = ref<number>(1);
      provide('flag', flag);
      </script>
      
      <style deep lang="less">
      .content-home {
        background: blue;
        color: #fff;
         200px;
        height: 200px;
      }
      </style>
      
      
    2. 新建文件src\components\A.vue。

      <template lang="">
        <div class="content-a">
          <h3>我是A组件</h3>
          <p>{{ flag }}</p>
          <button @click="change">Change</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { inject, ref } from "@vue/runtime-core";
      import { Ref } from "vue";
      
      const flag = inject<Ref<number>>('flag', ref(1));
      
      const change = () => {
        flag.value += 1;
      };
      </script>
      
      <style deep lang="less">
      .content-a {
        background: green;
        color: #fff;
         200px;
        height: 100px;
      }
      </style>
      
      

    兄弟组件传参(Mitt)

    结合Mitt库使用Event Bus实现兄弟组件的传参。

    1. 安装依赖库。

      npm i -D mitt
      
    2. 修改文件src\main.ts。
      全局总线,vue入口文件main.js中挂载全局属性。

      import { createApp } from "vue";
      import App from "./App.vue";
      import router from "./router";
      import store from "./store";
      import mitt from "mitt";
      
      const Mit = mitt();
      
      // TypeScript注册
      // 由于必须要拓展ComponentCustomProperties类型才能获得类型提示
      declare module "vue" {
        export interface ComponentCustomProperties {
          $Bus: typeof Mit;
        }
      }
      
      const app = createApp(App);
      // Vue3挂载全局API
      app.config.globalProperties.$Bus = Mit;
      
      app.use(store).use(router).mount("#app");
      
      
    3. 修改文件src\components\A.vue。使用方法通过emit派发。

      <template>
        <div class="a">
          <h3>我是组件A</h3>
          <button @click="emit1">emit1</button>
          <button @click="emit2">emit2</button>
        </div>
      </template>
      
      <script setup lang="ts">
      import { getCurrentInstance } from 'vue';
      
      const instance = getCurrentInstance();
      const emit1 = () => {
        instance?.proxy?.$Bus.emit("on-num1", 100);
      };
      const emit2 = () => {
        instance?.proxy?.$Bus.emit("on-num2", 500);
      };
      </script>
      
      <style scoped lang='less'>
      .a {
        background-color: orange;
        height: 200px;
         300px;
        margin: auto;
      }
      </style>
      
      <script lang="ts">
      export default {
        name: 'A'
      }
      </script>
      
      
    4. 修改文件src\components\B.vue。注册监听事件。

      <template>
          <div class="b">
              <h3>我是组件B</h3>
      
          </div>
      </template>
      
      <script setup lang="ts">
      import { getCurrentInstance } from 'vue';
      
      const instance = getCurrentInstance();
      instance?.proxy?.$Bus.on('on-num1', (num) => {
          console.log("组件B on-num1", num);
      });
      
      instance?.proxy?.$Bus.on('on-num2', (num) => {
          console.log("组件B on-num2", num);
      });
      
      </script>
      
      <style scoped lang='less'>
      .b {
          background-color: green;
          height: 200px;
           300px;
          margin: auto;
      }
      </style>
      
      <script lang="ts">
      export default {
          name: 'B'
      }
      </script>
      
      

    拓展

    • 监听所有事件。

      <script>
          ...
          // 监听所有事件
          instance?.proxy?.$Bus.on('*', (type, num) => {
              console.log("组件B", type, num);
          });
      </script>
      
    • 移除监听事件。

      <script>
          ...
          const Fn = (num: any) => {
          	console.log(num, '===========>B')
          }
          instance?.proxy?.$Bus.on('on-num',Fn); // listen
          instance?.proxy?.$Bus.off('on-num',Fn); // unListen
      </script>
      
    • 清空所有监听。

      <script>
          ...
          instance?.proxy?.$Bus.all.clear();
      </script>
      

    TSX

    unplugin-auto-import/vite无须import

    Vite项目使用。

    1. 安装依赖包。

      npm i -D unplugin-auto-import
      
    2. 在main.ts中导入。

      import AutoImport from 'unplugin-auto-import/vite'
      

    自定义指令directive

    Vue3指令的钩子函数

    • created:元素初始化的时候
    • beforeMount:指令绑定到元素后调用,只调用一次
    • mounted:元素插入父级dom调用
    • beforeUpdate:元素被更新之前调用
    • update:这个周期方法被移除 改用updated
    • beforeUnmount:在元素被移除前调用
    • unmounted:指令被移除后调用 只调用一次

    setup中定义局部指令

    这里有一个需要注意的限制:必须以 vNameOfDirective 的形式来命名本地自定义指令,以使得它们可以直接在模板中使用。

    1. 修改文件src\App.vue。

      <template>
        <h3>我是App组件</h3>
        <button @click="show = !show">开关{{ show }} ----- {{ title }}</button>
        <!-- 可以自定义参数aaa和增加修饰符bcd -->
        <HelloWorld v-if="show" v-move:aaa.bcd="{ background: 'green', flag: show }"></HelloWorld>
      </template>
      
      <script setup lang="ts">
      import { Directive, DirectiveBinding, ref } from "vue";
      import HelloWorld from "./components/HelloWorld.vue";
      
      let show = ref<boolean>(true);
      let title = ref<string>("Hello Vue+TS");
      
      type Dir = {
        background: string,
      }
      
      const vMove: Directive = {
        created: () => {
          console.log("生命周期created:初始化");
        },
        beforeMount(...args: Array<any>) {
          // 在元素上做些操作
          console.log("生命周期beforeMount:初始化一次", args);
        },
        // 可以增加一个类型推导(可选)<Dir>
        mounted(el: any, dir: DirectiveBinding<Dir>) {
          el.style.background = dir.value.background;
          console.log("生命周期mounted:初始化");
        },
        beforeUpdate() {
          console.log("生命周期beforeUpdate:更新之前");
        },
        updated() {
          console.log("生命周期updated:更新结束");
        },
        beforeUnmount(...args: Array<any>) {
          console.log("生命周期beforeUnmount:卸载之前", args);
        },
        unmounted(...args: Array<any>) {
          console.log("生命周期unmounted:卸载完成", args);
        },
      };
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    2. 修改文件src\components\HelloWorld.vue。

      <template>
        <div class="hello">
          <h3>我是A组件</h3>
        </div>
      
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style scoped>
      .hello {
        background-color: aqua;
         200px;
        height: 200px;
      }
      </style>
      
      

    函数简写

    修改文件src\App.vue。

    <template>
      <div>
        <h3>我是App组件</h3>
        <input type="text" v-model="val">
        <HelloWorld v-move="{ background: val }"></HelloWorld>
      </div>
    </template>
    
    <script setup lang="ts">
    import { Directive, DirectiveBinding, ref } from "vue";
    import HelloWorld from "./components/HelloWorld.vue";
    
    let val = ref<string>("");
    
    type Dir = {
      background: string,
    }
    
    const vMove: Directive = (el: any, dir: DirectiveBinding<Dir>) => {
      el.style.background = dir.value.background;
    };
    </script>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
    

    自定义指令案例(拖拽窗口)

    参见参考内容。

    自定义Hooks(VueUse开源库)

    自定义全局函数和变量

    操作步骤

    1. 修改文件src\main.ts。

      import { createApp } from 'vue'
      import App from './App.vue'
      
      let app = createApp(App);
      
      // 自定义了一个全局函数$filter
      app.config.globalProperties.$filters = {
          format<T>(str: T): string {
              return `真-${str}`;
          }
      }
      
      // 自定义了一个全局变量$env
      app.config.globalProperties.$env = "dev";
      
      // 声明定义的全局函数和变量
      // 声明要扩充@vue/runtime-core包的声明.
      // 这里扩充"ComponentCustomProperties"接口, 因为他是Vue3中实例的属性的类型
      type Filter = {
          format: <T extends any>(str: T) => T
      };
      
      declare module '@vue/runtime-core' {
          export interface ComponentCustomProperties {
              $filters: any,
              $env: string
          }
      }
      
      app.mount('#app')
      
      
    2. 修改文件src\App.vue。

      <template>
        <div>
          <h3>我是App组件</h3>
          <div>{{ $filters.format("张三") }}</div>
          <div>{{ $env }}</div>
        </div>
      </template>
      
      <script setup lang="ts"></script>
      
      <style lang="less"></style>
      
      

    自定义Vue3插件

    UI库ElementUI【模板】

    开箱即用。

    以表格举例说明。

    1. 安装依赖库。

      npm i -D element-plus
      
    2. 修改文件src\main.ts。

      import { createApp } from 'vue'
      import App from './App.vue'
      import ElementPlus from 'element-plus'
      import 'element-plus/dist/index.css'
      
      const app = createApp(App);
      
      app.use(ElementPlus);
      app.mount('#app')
      
      
    3. 复制代码使用即可。

    UI库AntDesign【模板】

    操作步骤

    1. 安装依赖库。

      npm i -D ant-design-vue
      
    2. 修改文件src\main.ts。

      import { createApp } from 'vue'
      import App from './App.vue'
      import DatePicker from 'ant-design-vue';
      import 'ant-design-vue/dist/antd.css';
      
      const app = createApp(App);
      
      app.use(DatePicker);
      app.mount('#app')
      
      
    3. 复制代码使用即可。

      <template>
        <a-timeline>
          <a-timeline-item>Create a services site 2015-09-01</a-timeline-item>
          <a-timeline-item>Solve initial network problems 2015-09-01</a-timeline-item>
          <a-timeline-item>Technical testing 2015-09-01</a-timeline-item>
          <a-timeline-item>Network problems being solved 2015-09-01</a-timeline-item>
        </a-timeline>
      </template>
      
      

    详解Scoped和样式穿透

    修改文件App.vue。

    <template>
      <div style="margin: 200px">
        <el-input class="ipt"></el-input>
      </div>
    </template>
    
    <script setup lang="ts"></script>
    
    <style scoped lang="less">
    .ipt {
      :deep(input) {
        background-color: red;
      }
    }
    </style>
    
    

    CSS完整新特性

    插槽选择器

    1. 修改文件src\App.vue。

      <template lang="">
      <div>
        <h3>我是App组件</h3>
        <HelloWorld>
          <p class="helloSlot">我是父组件给子组件的内容</p>
        </HelloWorld>
      </div>
      </template>
      
      <script setup lang="ts">
      import HelloWorld from './components/HelloWorld.vue'
      
      </script>
      
      <style lang="less">
      
      </style>
      
      
    2. 修改文件src\components\HelloWorld.vue。
      在子组件中添加插槽选择器即可控制修改父组件的插槽内容的样式。

      <template>
        <div class="hello">
          <h3>我是HelloWorld组件</h3>
          <slot></slot>
        </div>
      
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style scoped>
      .hello {
        background-color: aqua;
         200px;
        height: 200px;
      }
      
      :slotted(.helloSlot) {
        color: red;
      }
      </style>
      
      

    全局选择器

    之前我们想加入全局样式,通常都是新建一个style标签,不加scoped。
    现在有更优雅的解决方案。

    <style scoped lang="less">
    .hello {
      background-color: aqua;
       200px;
      height: 200px;
    }
    
    :global(h3) {
      color: red;
    }
    </style>
    

    动态CSS

    • 单文件组件的<style>标签可以通过v-bind这一CSS函数将CSS的值关联到动态的组件状态上。
    • 如果是对象参数,v-bind参数中需要加上引号。

    修改文件src\App.vue。

    <template lang="">
    <div>
      <h3>我是App组件</h3>
      <p><input v-model.lazy="fontColor" /></p>
      <p><input v-model.lazy="fontBackground.color" /></p>
      <p class="change">动态变化</p>
    </div>
    </template>
    
    <script setup lang="ts">
    import { reactive, ref } from "@vue/reactivity";
    
    type BG = {
      color: string
    }
    
    let fontColor = ref<string>('red');
    let fontBackground = reactive<BG>({
      color: "blue",
    })
    
    </script>
    
    <style scoped lang="less">
    .change {
      color: v-bind('fontColor');
      background-color: v-bind('fontBackground.color');
    } 
    </style>
    
    

    CSS Module

    • <style module>标签会编译为CSS Modules,并且将生成的CSS类作为$style对象的键暴露给组件。

      <template lang="">
      <div>
        <h3>我是App组件</h3>
        <p :class="$style.aaa">我是测试样式数据</p>
      </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style module>
      .aaa {
        color: red;
        font-size: 20px;
      }
      </style>
      
      
    • 自定义注入名称(多个情况下可以用数组)。
      示例1:

      <template lang="">
      <div>
        <h3>我是App组件</h3>
        <p :class="zs.aaa">我是测试样式数据</p>
      </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style module="zs">
      .aaa {
        color: red;
        font-size: 20px;
      }
      </style>
      
      

      示例2:

      <template lang="">
      <div>
        <h3>我是App组件</h3>
        <p :class="[zs.aaa, zs.bbb]">我是测试样式数据</p>
      </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style module="zs">
      .aaa {
        color: red;
        font-size: 20px;
      }
      
      .bbb {
        border: 1px solid #ccc;
      }
      </style>
      
      

    Vue3集成Tailwind CSS *

    操作步骤

    1. 安装依赖包。

      npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
      
    2. 创建配置文件。
      生成 tailwind.config.jspostcss.config.js 文件。

      npx tailwindcss init -p
      
    3. 修改配置文件tailwind.config.js

      • V2.6版本。

        module.exports = {
          purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
          theme: {
            extend: {},
          },
          plugins: [],
        }
        
      • V3.0版本。

        module.exports = {
          content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
          theme: {
            extend: {},
          },
          plugins: [],
        }
        
    4. 创建文件src\assets\tailwind.css。

      @tailwind base;
      @tailwind components;
      @tailwind utilities;
      
    5. 在文件src\main.ts中引入CSS文件。

      import { createApp } from 'vue'
      import App from './App.vue'
      import './assets/tailwind.css'
      
      createApp(App).mount('#app')
      
      
    6. 在XX.vue组件中直接使用该样式即可。

    7. 运行项目。

      npm run dev
      

    Pinia(适用Vue2.x和Vue3.x)

    安装Pinia(Vue3.x为例)

    1. 创建Vue3.x项目工程。

    2. 安装依赖包。

      npm i -D pinia
      
    3. 修改文件src\main.ts。引入注册。

      import { createApp } from 'vue'
      import App from './App.vue'
      import { createPinia } from 'pinia'
      const pinia = createPinia();
      
      const app = createApp(App);
      
      app.use(pinia);
      
      app.mount('#app')
      
      

    初始化仓库Store

    1. 新建文件夹src\store。

    2. 新建文件src\store\store-name.ts。

      export const enum Names {
          TEST = 'TEST',
      }
      
    3. 修建文件src\store\index.ts。

      import { defineStore } from "pinia";
      import { Names } from "./store-name";
      
      // 当前仓库名称为Names.TEST
      export const userStore = defineStore(Names.TEST, {
          state: () => {
              return {
                  count: 10,
                  name: '张三',
              }
          },
      
          // computed计算属性
          getters: {
          },
      
          // methods,可以使用同步/异步方法,提交state
          actions: {
          },
      });
      
      
    4. 修改文件src\App.vue。

      <template>
        <div>
          pinia -- {{ TEST.count }} -- {{ TEST.name }}
        </div>
      </template>
      
      <script setup lang="ts">
      // 可以根据需要导入需要的数据仓库
      import { userStore } from './store'
      
      const TEST = userStore();
      </script>
      
      <style scoped lang="">
      </style>
      
      
    5. State的修改方式。修改文件src\App.vue。

      • 方式一:获取后直接修改。

        <template>
          <div>
            pinia -- {{ TEST.count }} -- {{ TEST.name }}
          </div>
          <button @click="countAddOne">Count+1</button>
        </template>
        
        <script setup lang="ts">
        // 可以根据需要导入需要的数据仓库
        import { userStore } from './store'
        
        const TEST = userStore();
        
        function countAddOne() {
          TEST.count++;
        }
        
        </script>
        
        <style scoped lang="">
        </style>
        
        
      • 方式二:数据结构后结合响应式的声明进行修改。

        <template>
          <div>
            pinia -- {{ TEST.count }} -- {{ TEST.name }}
          </div>
          <button @click="countAddOne">Count+1</button>
        </template>
        
        <script setup lang="ts">
        // 可以根据需要导入需要的数据仓库
        import { storeToRefs } from 'pinia';
        import { userStore } from './store'
        
        const TEST = userStore();
        let { count } = storeToRefs(TEST);
        
        function countAddOne() {
          // TEST.count++;
          count.value++;
        }
        
        </script>
        
        <style scoped lang="">
        </style>
        
        
      • 方式三:使用$patch进行修改。
        举例1:

        <template>
          <div>
            pinia -- {{ TEST.count }} -- {{ TEST.name }}
          </div>
          <button @click="countAddOne">Count+1</button>
          <button @click="patchClick">Count+5</button>
        </template>
        
        <script setup lang="ts">
        // 可以根据需要导入需要的数据仓库
        import { storeToRefs } from 'pinia';
        import { userStore } from './store'
        
        const TEST = userStore();
        let { count } = storeToRefs(TEST);
        
        function countAddOne() {
          // TEST.count++;
          count.value++;
        }
        
        // $patch的方式修改
        function patchClick() {
          TEST.$patch({
            count: TEST.count + 5,
          })
        }
        
        </script>
        
        <style scoped lang="">
        </style>
        
        

        举例2:

        修改文件src\store\index.ts。

        import { defineStore } from "pinia";
        import { Names } from "./store-name";
        
        // 当前仓库名称为Names.TEST
        export const userStore = defineStore(Names.TEST, {
            state: () => {
                return {
                    count: 10,
                    name: '张三',
                    list: [{
                        name: "iPhone",
                        price: 5888,
                    },
                    {
                        name: "Mate40 Pro",
                        price: 6888,
                    },]
                }
            },
        
            // computed计算属性
            getters: {
            },
        
            // methods,可以使用同步/异步方法,提交state
            actions: {
            },
        });
        
        

        修改文件src\App.vue。

        <template>
          <div>
            pinia -- {{ TEST.count }} -- {{ TEST.name }}
          </div>
          <button @click="countAddOne">Count+1</button>
          <button @click="patchClick">Count+5</button>
        
          <br>
          <h3>商品列表:</h3>
          <ul>
            <li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li>
          </ul>
          <button @click="listAddOne">ListAddOne</button>
        </template>
        
        <script setup lang="ts">
        // 可以根据需要导入需要的数据仓库
        import { storeToRefs } from 'pinia';
        import { userStore } from './store'
        
        const TEST = userStore();
        let { count } = storeToRefs(TEST);
        
        function countAddOne() {
          // TEST.count++;
          count.value++;
        }
        
        // $patch的方式修改
        function patchClick() {
          TEST.$patch({
            count: TEST.count + 5,
          })
        }
        
        // $patch的方式修改
        function listAddOne() {
          TEST.list.push({
            name: "Oppo",
            price: 2000
          })
          TEST.$patch({
            list: TEST.list
          })
        }
        </script>
        
        <style scoped lang="">
        </style>
        
        
      • 方式四(推荐):使用$patch进行修改。

        <template>
          <div>
            pinia -- {{ TEST.count }} -- {{ TEST.name }}
          </div>
          <button @click="countAddOne">Count+1</button>
          <button @click="patchClick">Count+5</button>
        
          <br>
          <h3>商品列表:</h3>
          <ul>
            <li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li>
          </ul>
          <button @click="listAddOne">ListAddOne</button>
          <button @click="recommendPatch">RecommendPatch</button>
        </template>
        
        <script setup lang="ts">
        // 可以根据需要导入需要的数据仓库
        import { storeToRefs } from 'pinia';
        import { userStore } from './store'
        
        const TEST = userStore();
        let { count } = storeToRefs(TEST);
        
        function countAddOne() {
          // TEST.count++;
          count.value++;
        }
        
        // $patch的方式修改
        function patchClick() {
          TEST.$patch({
            count: TEST.count + 5,
          })
        }
        
        // $patch的方式修改
        function listAddOne() {
          TEST.list.push({
            name: "Oppo",
            price: 2000
          })
          TEST.$patch({
            list: TEST.list
          })
        }
        
        // $patch推荐的修改方式
        function recommendPatch() {
          TEST.$patch((state) => {
            state.list.push({
              name: "Xiaomi",
              price: 2001,
            });
            state.count += 10;
          }
        }
        </script>
        
        <style scoped lang="">
        </style>
        重置State。
        

    替换State值

    修改文件src\App.vue。

    <template>
      <div>
        pinia -- {{ TEST.count }} -- {{ TEST.name }}
      </div>
      <button @click="countAddOne">Count+1</button>
      <button @click="patchClick">Count+5</button>
    
      <br>
      <h3>商品列表:</h3>
      <ul>
        <li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li>
      </ul>
      <button @click="listAddOne">ListAddOne</button>
      <button @click="recommendPatch">RecommendPatch</button>
      <button @click="toggleState">重置State状态</button>
    </template>
    
    <script setup lang="ts">
    // 可以根据需要导入需要的数据仓库
    import { storeToRefs } from 'pinia';
    import { userStore } from './store'
    
    const TEST = userStore();
    let { count } = storeToRefs(TEST);
    
    function countAddOne() {
      // TEST.count++;
      count.value++;
    }
    
    // $patch的方式修改
    function patchClick() {
      TEST.$patch({
        count: TEST.count + 5,
      })
    }
    
    // $patch的方式修改
    function listAddOne() {
      TEST.list.push({
        name: "Oppo",
        price: 2000
      })
      TEST.$patch({
        list: TEST.list
      })
    }
    
    // $patch推荐的修改方式
    function recommendPatch() {
      TEST.$patch((state) => {
        state.list.push({
          name: "Xiaomi",
          price: 2001,
        });
        state.count += 10;
      })
    }
    
    // 替换State值
    function toggleState() {
      TEST.$state = {
        count: 100,
        name: "李四",
        list: [
          {
            name: "Oppo",
            price: 2000
          },
          {
            name: "Xiaomi",
            price: 2001,
          }
        ],
      };
    }
    </script>
    
    <style scoped lang="">
    </style>
    
    

    重置State状态

    修改文件src\App.vue。

    <template>
      <div>
        pinia -- {{ TEST.count }} -- {{ TEST.name }}
      </div>
      <button @click="countAddOne">Count+1</button>
      <button @click="patchClick">Count+5</button>
    
      <br>
      <h3>商品列表:</h3>
      <ul>
        <li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li>
      </ul>
      <button @click="listAddOne">ListAddOne</button>
      <button @click="recommendPatch">RecommendPatch</button>
      <button @click="toggleState">重置State状态</button>
      <button @click="resetState">重置State状态</button>
    </template>
    
    <script setup lang="ts">
    // 可以根据需要导入需要的数据仓库
    import { storeToRefs } from 'pinia';
    import { userStore } from './store'
    
    const TEST = userStore();
    let { count } = storeToRefs(TEST);
    
    function countAddOne() {
      // TEST.count++;
      count.value++;
    }
    
    // $patch的方式修改
    function patchClick() {
      TEST.$patch({
        count: TEST.count + 5,
      })
    }
    
    // $patch的方式修改
    function listAddOne() {
      TEST.list.push({
        name: "Oppo",
        price: 2000
      })
      TEST.$patch({
        list: TEST.list
      })
    }
    
    // $patch推荐的修改方式
    function recommendPatch() {
      TEST.$patch((state) => {
        state.list.push({
          name: "Xiaomi",
          price: 2001,
        });
        state.count += 10;
      })
    }
    
    // 替换State值
    function toggleState() {
      TEST.$state = {
        count: 100,
        name: "李四",
        list: [
          {
            name: "Oppo",
            price: 2000
          },
          {
            name: "Xiaomi",
            price: 2001,
          }
        ],
      };
    }
    
    // 重置State状态
    function resetState() {
      TEST.$reset();
    }
    </script>
    
    <style scoped lang="">
    </style>
    
    

    监听整个仓库变化

    修改文件src\App.vue。

    <template>
      <div>
        pinia -- {{ TEST.count }} -- {{ TEST.name }}
      </div>
      <button @click="countAddOne">Count+1</button>
      <button @click="patchClick">Count+5</button>
    
      <br>
      <h3>商品列表:</h3>
      <ul>
        <li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li>
      </ul>
      <button @click="listAddOne">ListAddOne</button>
      <button @click="recommendPatch">RecommendPatch</button>
      <button @click="toggleState">替换State值</button>
      <button @click="resetState">重置State状态</button>
    </template>
    
    <script setup lang="ts">
    // 可以根据需要导入需要的数据仓库
    import { storeToRefs } from 'pinia';
    import { userStore } from './store'
    
    const TEST = userStore();
    let { count } = storeToRefs(TEST);
    
    function countAddOne() {
      // TEST.count++;
      count.value++;
    }
    
    // $patch的方式修改
    function patchClick() {
      TEST.$patch({
        count: TEST.count + 5,
      })
    }
    
    // $patch的方式修改
    function listAddOne() {
      TEST.list.push({
        name: "Oppo",
        price: 2000
      })
      TEST.$patch({
        list: TEST.list
      })
    }
    
    // $patch推荐的修改方式
    function recommendPatch() {
      TEST.$patch((state) => {
        state.list.push({
          name: "Xiaomi",
          price: 2001,
        });
        state.count += 10;
      })
    }
    
    // 替换State值
    function toggleState() {
      TEST.$state = {
        count: 100,
        name: "李四",
        list: [
          {
            name: "Oppo",
            price: 2000
          },
          {
            name: "Xiaomi",
            price: 2001,
          }
        ],
      };
    }
    
    // 重置State状态
    function resetState() {
      TEST.$reset();
    }
    
    // 监听整个仓库变化
    TEST.$subscribe((mutations, state) => {
      console.log("mutations", mutations);
      console.log("state", state);
    })
    </script>
    
    <style scoped lang="">
    </style>
    
    

    计算属性

    例如需要计算总的价格。

    修改文件src\App.vue。

    1. 修改文件src\store\index.ts。

      import { defineStore } from "pinia";
      import { Names } from "./store-name";
      
      // 当前仓库名称为Names.TEST
      export const userStore = defineStore(Names.TEST, {
          state: () => {
              return {
                  count: 10,
                  name: '张三',
                  list: [{
                      name: "iPhone",
                      price: 5888,
                      num: 1,
                  },
                  {
                      name: "Mate40 Pro",
                      price: 6888,
                      num: 1,
                  },]
              }
          },
      
          // computed计算属性
          getters: {
              sumPrice: (state) => {
                  return state.list.reduce((pre, item) => {
                      return pre + (item.price * item.num);
                  }, 0);
              }
          },
      
          // methods,可以使用同步/异步方法,提交state
          actions: {
          },
      });
      
      
    2. 修改文件src\App.vue。

      <template>
        <div>
          pinia -- {{ TEST.count }} -- {{ TEST.name }}
        </div>
        <button @click="countAddOne">Count+1</button>
        <button @click="patchClick">Count+5</button>
      
        <br>
        <h3>商品列表:</h3>
        <ul>
          <li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li>
        </ul>
        <button @click="listAddOne">ListAddOne</button>
        <button @click="recommendPatch">RecommendPatch</button>
        <button @click="toggleState">替换State值</button>
        <button @click="resetState">重置State状态</button>
      
        <br>
        <p>方式一:</p>
        <p>总的价格为:{{ TEST.sumPrice }}</p>
        <p>方式二:结构后直接使用</p>
        <p>总的价格为:{{ sumPrice }}</p>
      </template>
      
      <script setup lang="ts">
      // 可以根据需要导入需要的数据仓库
      import { storeToRefs } from 'pinia';
      import { userStore } from './store'
      
      const TEST = userStore();
      // 计算属性sumPrice也可以直接结构后使用
      let { count, sumPrice } = storeToRefs(TEST);
      
      function countAddOne() {
        // TEST.count++;
        count.value++;
      }
      
      // $patch的方式修改
      function patchClick() {
        TEST.$patch({
          count: TEST.count + 5,
        })
      }
      
      // $patch的方式修改
      function listAddOne() {
        TEST.list.push({
          name: "Oppo",
          price: 2000,
          num: 1,
        })
        TEST.$patch({
          list: TEST.list
        })
      }
      
      // $patch推荐的修改方式
      function recommendPatch() {
        TEST.$patch((state) => {
          state.list.push({
            name: "Xiaomi",
            price: 2001,
            num: 1,
          });
          state.count += 10;
        })
      }
      
      // 替换State值
      function toggleState() {
        TEST.$state = {
          count: 100,
          name: "李四",
          list: [
            {
              name: "Oppo",
              price: 2000,
              num: 1,
            },
            {
              name: "Xiaomi",
              price: 2001,
              num: 1,
            }
          ],
        };
      }
      
      // 重置State状态
      function resetState() {
        TEST.$reset();
      }
      
      // 监听整个仓库变化
      TEST.$subscribe((mutations, state) => {
        console.log("mutations", mutations);
        console.log("state", state);
      })
      </script>
      
      <style scoped lang="">
      </style>
      
      

    Actions

    同步和异步皆可以。

    以XX网络请求为例说明。

    1. 修改文件src\store\index.ts。

      import { defineStore } from "pinia";
      import { Names } from "./store-name";
      import axios from 'axios';
      
      // 当前仓库名称为Names.TEST
      export const userStore = defineStore(Names.TEST, {
          state: () => {
              return {
                  count: 10,
                  name: '张三',
                  list: [{
                      name: "iPhone",
                      price: 5888,
                      num: 1,
                  },
                  {
                      name: "Mate40 Pro",
                      price: 6888,
                      num: 1,
                  },]
              }
          },
      
          // computed计算属性
          getters: {
              sumPrice: (state) => {
                  return state.list.reduce((pre, item) => {
                      return pre + (item.price * item.num);
                  }, 0);
              }
          },
      
          // methods,可以使用同步/异步方法,提交state
          actions: {
              countAddN(n: number) {
                  this.count += n;
              },
              async getExams() {
                  let res = await axios.post(
                      "http://www.liulongbin.top:3006/api/post",
                      { name: "zs", gender: "女" }
                  );
                  console.log(res);
              },
          },
      });
      
      
    2. 修改文件src\App.vue。

      <template>
        <div>
          pinia -- {{ TEST.count }} -- {{ TEST.name }}
        </div>
        <button @click="countAddOne">Count+1</button>
        <button @click="patchClick">Count+5</button>
      
        <br>
        <h3>商品列表:</h3>
        <ul>
          <li v-for="(item, index) in TEST.list" :key="index">{{ item }}</li>
        </ul>
        <button @click="listAddOne">ListAddOne</button>
        <button @click="recommendPatch">RecommendPatch</button>
        <button @click="toggleState">替换State值</button>
        <button @click="resetState">重置State状态</button>
      
        <br>
        <p>方式一:</p>
        <p>总的价格为:{{ TEST.sumPrice }}</p>
        <p>方式二:结构后直接使用</p>
        <p>总的价格为:{{ sumPrice }}</p>
      
        <hr>
        <button @click="TEST.countAddN(20)">Actions countAddN</button>
        <button @click="TEST.getExams">获取请求</button>
      </template>
      
      <script setup lang="ts">
      // 可以根据需要导入需要的数据仓库
      import { storeToRefs } from 'pinia';
      import { userStore } from './store'
      
      const TEST = userStore();
      // 计算属性sumPrice也可以直接结构后使用
      let { count, sumPrice } = storeToRefs(TEST);
      
      function countAddOne() {
        // TEST.count++;
        count.value++;
      }
      
      // $patch的方式修改
      function patchClick() {
        TEST.$patch({
          count: TEST.count + 5,
        })
      }
      
      // $patch的方式修改
      function listAddOne() {
        TEST.list.push({
          name: "Oppo",
          price: 2000,
          num: 1,
        })
        TEST.$patch({
          list: TEST.list
        })
      }
      
      // $patch推荐的修改方式
      function recommendPatch() {
        TEST.$patch((state) => {
          state.list.push({
            name: "Xiaomi",
            price: 2001,
            num: 1,
          });
          state.count += 10;
        })
      }
      
      // 替换State值
      function toggleState() {
        TEST.$state = {
          count: 100,
          name: "李四",
          list: [
            {
              name: "Oppo",
              price: 2000,
              num: 1,
            },
            {
              name: "Xiaomi",
              price: 2001,
              num: 1,
            }
          ],
        };
      }
      
      // 重置State状态
      function resetState() {
        TEST.$reset();
      }
      
      // 监听整个仓库变化
      TEST.$subscribe((mutations, state) => {
        console.log("mutations", mutations);
        console.log("state", state);
      })
      </script>
      
      <style scoped lang="">
      </style>
      
      

    Pinia插件

    Pinia和Vuex都有一个通病,页面刷新状态会丢失。

    Router

    参考:CSDN 小满的博客 Router

    路由模式

    • vue2 mode hash vue3 createWebHashHistory
      hash是URL中hash (#) 及后面的那部分,常用作锚点在页面内进行导航,改变URL中的hash部分,不会引起页面刷新。

      • 基于H5的location.hash = 'xx'实现原理。

      • 通过以下方式监听hash值的变化。

        window.addEventListener('hashchange', (e) => {
            console.log('hashchange', e);
        })
        
    • vue2 mode abstract vue3 createMemoryHistory
      history提供pushState和replaceState两个方法,这两个方法改变URL的path部分,不会引起页面刷新。

      • 基于H5的history实现原理。

      • 通过以下方法监听值得变化。

        window.addEventListener('popstate', (e) => {
            console.log('popstate', e);
        })
        
      • 通过以下方式实现跳转。

        history.pushState({ state: 1 }, '', '/ccc')
        

    Vite安装和配置Router【模板】

    1. 使用Vite搭建Vue3开发环境,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。

    2. 安装和配置less。参见[Vite安装和配置less【模板】](# Vite安装和配置less【模板】)。

    3. 安装和配置Router。参见[Vite安装和配置Router【模板】](# Vite安装和配置Router【模板】)。

    4. 修改文件src\router\index.ts。

      //引入路由对象
      import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router'
      
      // vue2 mode history vue3 createWebHistory
      // vue2 mode hash vue3 createWebHashHistory
      // vue2 mode abstract vue3 createMemoryHistory
      
      // 路由数组的类型 RouteRecordRaw
      // 定义一些路由
      // 每个路由都需要映射到一个组件。
      const routes: Array<RouteRecordRaw> = [{
          path: '/',
          component: () => import('../views/Home.vue')
      }, {
          path: '/about',
          component: () => import('../views/About.vue')
      }]
      
      const router = createRouter({
          history: createWebHistory(),
          routes
      })
      
      // 导出router
      export default router;
      
      
    5. 新建文件夹src\views和修建文件src\views\Home.vue。

      <template>
          <div class="content-home">
              <h3>我是Home组件</h3>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style lang="less" scoped>
      .content-home {
           200px;
          height: 200px;
          background-color: aqua;
      }
      </style>
      
      
    6. 新建文件src\views\About.vue。

      <template>
          <div class="content-about">
              <h3>我是About组件</h3>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style lang="less" scoped>
      .content-about {
           200px;
          height: 200px;
          background-color: green;
      }
      </style>
      
      
    7. 新建文件夹src\router和新建文件src\router\index.ts。

      //引入路由对象
      import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router'
      
      // vue2 mode history vue3 createWebHistory
      // vue2 mode hash vue3 createWebHashHistory
      // vue2 mode abstract vue3 createMemoryHistory
      
      // 路由数组的类型 RouteRecordRaw
      // 定义一些路由
      // 每个路由都需要映射到一个组件。
      const routes: Array<RouteRecordRaw> = [{
          path: '/',
          component: () => import('../views/Home.vue')
      }, {
          path: '/about',
          component: () => import('../views/About.vue')
      }]
      
      const router = createRouter({
          history: createWebHistory(),
          routes
      })
      
      // 导出router
      export default router;
      
      
    8. 修改文件src\App.vue。声明路由链接和占位符。

      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      </script>
      
      <template>
        <div>
          <!-- <router-link to="/home">首页</router-link> -->
          <router-link to="/">首页</router-link> |
          <router-link to="/about">关于</router-link>
          <hr />
      
          <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 -->
          <!-- 它的作用很单纯,就是占位符 -->
          <router-view></router-view>
        </div>
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      

    命名路由

    1. 修改文件src\router\index.ts。增加name属性。

      //引入路由对象
      import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router'
      
      // vue2 mode history vue3 createWebHistory
      // vue2 mode hash vue3 createWebHashHistory
      // vue2 mode abstract vue3 createMemoryHistory
      
      // 路由数组的类型 RouteRecordRaw
      // 定义一些路由
      // 每个路由都需要映射到一个组件。
      const routes: Array<RouteRecordRaw> = [{
          path: '/',
          name: 'Home',
          component: () => import('../views/Home.vue')
      }, {
          path: '/about',
          name: 'About',
          component: () => import('../views/About.vue')
      }]
      
      const router = createRouter({
          history: createWebHistory(),
          routes
      })
      
      // 导出router
      export default router;
      
      
    2. 修改文件src\App.vue。

      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      </script>
      
      <template>
        <div>
          <!-- <router-link to="/home">首页</router-link> -->
          <router-link :to="{ name: 'Home' }">首页</router-link> |
          <router-link :to="{ name: 'About' }">关于</router-link>
          <hr />
      
          <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 -->
          <!-- 它的作用很单纯,就是占位符 -->
          <router-view></router-view>
        </div>
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      

      使用a标签也可以实现跳转,会存在页面刷新的现象,不推荐使用。

      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      </script>
      
      <template>
        <div>
          <!-- <router-link to="/home">首页</router-link> -->
          <!-- <router-link :to="{ name: 'Home' }">首页</router-link> | -->
          <!-- <router-link :to="{ name: 'About' }">关于</router-link> -->
          <a href="/">首页</a> |
          <a href="/about">关于</a>
          <hr />
      
          <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 -->
          <!-- 它的作用很单纯,就是占位符 -->
          <router-view></router-view>
        </div>
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      

    编程式导航

    修改文件src\App.vue。

    • 字符串模式

      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      import { useRouter } from 'vue-router'
      const router = useRouter();
      
      const gotoPage = (url: string) => {
        router.push(url);
      }
      </script>
      
      <template>
        <div>
          <button @click="gotoPage('/')">首页</button> |
          <button @click="gotoPage('/about')">关于</button>
        </div>
      
        <hr>
        <router-view></router-view>
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    • 对象模式

      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      import { useRouter } from 'vue-router'
      const router = useRouter();
      
      const gotoPage = (url: string) => {
        router.push({
          path: url,
        });
      }
      </script>
      
      <template>
        <div>
          <button @click="gotoPage('/')">首页</button> |
          <button @click="gotoPage('/about')">关于</button>
        </div>
      
        <hr>
        <router-view></router-view>
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    • 命名式路由模式

      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      import { useRouter } from 'vue-router'
      const router = useRouter();
      
      const gotoPage = (urlName: string) => {
        router.push({
          name: urlName,
        });
      }
      </script>
      
      <template>
        <div>
          <button @click="gotoPage('Home')">首页</button> |
          <button @click="gotoPage('About')">关于</button>
        </div>
      
        <hr>
        <router-view></router-view>
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      

    历史记录

    屏蔽历史记录

    修改文件src\App.vue。

    • 场景1

      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      </script>
      
      <template>
        <div>
          <!-- <router-link to="/home">首页</router-link> -->
          <router-link replace :to="{ name: 'Home' }">首页</router-link> |
          <router-link replace :to="{ name: 'About' }">关于</router-link>
          <hr />
      
          <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 -->
          <!-- 它的作用很单纯,就是占位符 -->
          <router-view></router-view>
        </div>
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    • 场景2

      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      import { useRouter } from 'vue-router'
      const router = useRouter();
      
      const gotoPage = (urlName: string) => {
        router.replace({
          name: urlName,
        });
      }
      </script>
      
      <template>
        <div>
          <button @click="gotoPage('Home')">首页</button> |
          <button @click="gotoPage('About')">关于</button>
        </div>
      
        <hr>
        <router-view></router-view>
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      

    主动触发前进和后退

    <script setup lang="ts">
    // This starter template is using Vue 3 <script setup> SFCs
    // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
    import { useRouter } from 'vue-router'
    const router = useRouter();
    
    const gotoPage = (urlName: string) => {
      router.push({
        name: urlName,
      });
    }
    
    const next = () => {
      router.go(1); // router.go(2);
    }
    
    const back = () => {
      // 方式一
      router.back();
      // 方式二(不推荐)
      // router.go(-1);
    }
    </script>
    
    <template>
      <div>
        <!-- <router-link to="/home">首页</router-link> -->
        <router-link :to="{ name: 'Home' }">首页</router-link> |
        <router-link :to="{ name: 'About' }">关于</router-link> |
    
        <button @click="gotoPage('Home')">首页</button> |
        <button @click="gotoPage('About')">关于</button> |
    
        <button @click="next">Next</button> |
        <button @click="back">Back</button>
        <hr />
    
        <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 -->
        <!-- 它的作用很单纯,就是占位符 -->
        <router-view></router-view>
      </div>
    </template>
    
    <style>
    #app {
      font-family: Avenir, Helvetica, Arial, sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-align: center;
      color: #2c3e50;
      margin-top: 60px;
    }
    </style>
    
    

    路由传参

    query方式传参

    说明:query方式传参,会直接展示在URL的地址上。

    1. 修改文件src\router\index.ts。

      //引入路由对象
      import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router'
      
      // vue2 mode history vue3 createWebHistory
      // vue2 mode hash vue3 createWebHashHistory
      // vue2 mode abstract vue3 createMemoryHistory
      
      // 路由数组的类型 RouteRecordRaw
      // 定义一些路由
      // 每个路由都需要映射到一个组件。
      const routes: Array<RouteRecordRaw> = [{
          path: '/',
          name: 'Home',
          component: () => import('../views/Home.vue')
      }, {
          path: '/about',
          name: 'About',
          component: () => import('../views/About.vue')
      }]
      
      const router = createRouter({
          history: createWebHistory(),
          routes
      })
      
      // 导出router
      export default router;
      
      
    2. 修改文件src\App.vue。

      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      import { useRouter } from 'vue-router'
      const router = useRouter();
      
      type RootObject = {
        data: Datum[];
      }
      
      type Datum = {
        name: string;
        price: number;
        id: number;
      }
      
      let list: RootObject = {
        "data": [
          {
            name: "红烧牛肉面",
            price: 50,
            id: 1,
          },
          {
            name: "火腿肠",
            price: 35,
            id: 2,
          },
          {
            name: "牛肉干",
            price: 24,
            id: 3,
          },
        ]
      }
      
      const gotoPage = (url: string, dataNo: number) => {
        router.push({
          path: url,
          query: list.data[dataNo],
        });
      }
      </script>
      
      <template>
        <div>
          <button @click="gotoPage('/', 0)">首页</button> |
          <button @click="gotoPage('/about', 1)">关于</button>
          <hr />
      
          <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 -->
          <!-- 它的作用很单纯,就是占位符 -->
          <router-view></router-view>
        </div>
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    3. 修改文件src\views\Home.vue。

      <template>
          <div class="content-home">
              <h3>我是Home组件</h3>
              <p>品牌:{{ route.query.name }}</p>
              <p>价格:{{ route.query.price }}</p>
              <p>ID:{{ route.query.id }}</p>
          </div>
      </template>
      
      <script setup lang="ts">
      import { useRoute } from 'vue-router'; // 接收传参数据
      import { useRouter } from 'vue-router';
      
      const route = useRoute();
      const router = useRouter();
      </script>
      
      <style lang="less" scoped>
      .content-home {
           200px;
          height: 200px;
          background-color: aqua;
      }
      </style>
      
      
    4. 修改文件src\views\About.vue。

      <template>
          <div class="content-about">
              <h3>我是About组件</h3>
              <p>品牌:{{ route.query.name }}</p>
              <p>价格:{{ route.query.price }}</p>
              <p>ID:{{ route.query.id }}</p>
          </div>
      </template>
      
      <script setup lang="ts">
      import { useRoute } from 'vue-router'; // 接收传参数据
      import { useRouter } from 'vue-router';
      
      const route = useRoute();
      const router = useRouter();
      </script>
      
      <style lang="less" scoped>
      .content-about {
           200px;
          height: 200px;
          background-color: green;
      }
      </style>
      
      

    params方式传参

    说明:

    1. params需要结合name的方式进行使用。
    2. 存储在内存中,刷新页面,参数会丢失。
    1. 修改文件src\router\index.ts。

      //引入路由对象
      import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router'
      
      // vue2 mode history vue3 createWebHistory
      // vue2 mode hash vue3 createWebHashHistory
      // vue2 mode abstract vue3 createMemoryHistory
      
      // 路由数组的类型 RouteRecordRaw
      // 定义一些路由
      // 每个路由都需要映射到一个组件。
      const routes: Array<RouteRecordRaw> = [{
          path: '/',
          name: 'Home',
          component: () => import('../views/Home.vue')
      }, {
          path: '/about',
          name: 'About',
          component: () => import('../views/About.vue')
      }]
      
      const router = createRouter({
          history: createWebHistory(),
          routes
      })
      
      // 导出router
      export default router;
      
      
    2. 修改文件src\App.vue。

      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      import { useRouter } from 'vue-router'
      const router = useRouter();
      
      type RootObject = {
        data: Datum[];
      }
      
      type Datum = {
        name: string;
        price: number;
        id: number;
      }
      
      let list: RootObject = {
        "data": [
          {
            name: "红烧牛肉面",
            price: 50,
            id: 1,
          },
          {
            name: "火腿肠",
            price: 35,
            id: 2,
          },
          {
            name: "牛肉干",
            price: 24,
            id: 3,
          },
        ]
      }
      
      const gotoPage = (urlName: string, dataNo: number) => {
        router.push({
          name: urlName,
          params: list.data[dataNo],
        });
      }
      </script>
      
      <template>
        <div>
          <button @click="gotoPage('Home', 0)">首页</button> |
          <button @click="gotoPage('About', 1)">关于</button>
          <hr />
      
          <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 -->
          <!-- 它的作用很单纯,就是占位符 -->
          <router-view></router-view>
        </div>
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    3. 修改文件src\views\Home.vue。

      <template>
          <div class="content-home">
              <h3>我是Home组件</h3>
              <p>品牌:{{ route.params.name }}</p>
              <p>价格:{{ route.params.price }}</p>
              <p>ID:{{ route.params.id }}</p>
          </div>
      </template>
      
      <script setup lang="ts">
      import { useRoute } from 'vue-router'; // 接收传参数据
      import { useRouter } from 'vue-router';
      
      const route = useRoute();
      const router = useRouter();
      </script>
      
      <style lang="less" scoped>
      .content-home {
           200px;
          height: 200px;
          background-color: aqua;
      }
      </style>
      
      
    4. 修改文件src\views\About.vue。

      <template>
          <div class="content-about">
              <h3>我是About组件</h3>
              <p>品牌:{{ route.params.name }}</p>
              <p>价格:{{ route.params.price }}</p>
              <p>ID:{{ route.params.id }}</p>
          </div>
      </template>
      
      <script setup lang="ts">
      import { useRoute } from 'vue-router'; // 接收传参数据
      import { useRouter } from 'vue-router';
      
      const route = useRoute();
      const router = useRouter();
      </script>
      
      <style lang="less" scoped>
      .content-about {
           200px;
          height: 200px;
          background-color: green;
      }
      </style>
      
      

    params方式结合动态路由参数

    1. 修改文件src\router\index.ts。

      //引入路由对象
      import { createRouter, createWebHistory, createWebHashHistory, createMemoryHistory, RouteRecordRaw } from 'vue-router'
      
      // vue2 mode history vue3 createWebHistory
      // vue2 mode hash vue3 createWebHashHistory
      // vue2 mode abstract vue3 createMemoryHistory
      
      // 路由数组的类型 RouteRecordRaw
      // 定义一些路由
      // 定义一些路由
      // 每个路由都需要映射到一个组件。
      const routes: Array<RouteRecordRaw> = [{
          path: '/',
          name: 'Home',
          component: () => import('../views/Home.vue')
      }, {
          // 动态路由传参
          path: '/about/:id',
          name: 'About',
          component: () => import('../views/About.vue')
      }]
      
      const router = createRouter({
          history: createWebHistory(),
          routes
      })
      
      // 导出router
      export default router;
      
      
    2. 修改文件src\App.vue。

      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      import { useRouter } from 'vue-router'
      const router = useRouter();
      
      type RootObject = {
        data: Datum[];
      }
      
      type Datum = {
        name: string;
        price: number;
        id: number;
      }
      
      let list: RootObject = {
        "data": [
          {
            name: "红烧牛肉面",
            price: 50,
            id: 1,
          },
          {
            name: "火腿肠",
            price: 35,
            id: 2,
          },
          {
            name: "牛肉干",
            price: 24,
            id: 3,
          },
        ]
      }
      
      const gotoPage = (urlName: string, dataNo: number) => {
        router.push({
          name: urlName,
          params: {
            id: list.data[dataNo].id
          },
        });
      }
      </script>
      
      <template>
        <div>
          <button @click="gotoPage('Home', 0)">首页</button> |
          <button @click="gotoPage('About', 1)">关于</button>
          <hr />
      
          <!-- 只要在项目中安装和配置了vue-router,就可以使用router-view这个组件了 -->
          <!-- 它的作用很单纯,就是占位符 -->
          <router-view></router-view>
        </div>
      </template>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      
    3. 修改文件src\views\Home.vue。

      <template>
          <div class="content-home">
              <h3>我是Home组件</h3>
              <p>品牌:{{ route.params.name }}</p>
              <p>价格:{{ route.params.price }}</p>
              <p>ID:{{ route.params.id }}</p>
          </div>
      </template>
      
      <script setup lang="ts">
      import { useRoute } from 'vue-router'; // 接收传参数据
      import { useRouter } from 'vue-router';
      
      const route = useRoute();
      const router = useRouter();
      </script>
      
      <style lang="less" scoped>
      .content-home {
           200px;
          height: 200px;
          background-color: aqua;
      }
      </style>
      
      
    4. 修改文件src\views\About.vue。

      <template>
          <div class="content-about">
              <h3>我是About组件</h3>
              <p>品牌:{{ route.params.name }}</p>
              <p>价格:{{ route.params.price }}</p>
              <p>ID:{{ route.params.id }}</p>
          </div>
      </template>
      
      <script setup lang="ts">
      import { useRoute } from 'vue-router'; // 接收传参数据
      import { useRouter } from 'vue-router';
      
      const route = useRoute();
      const router = useRouter();
      </script>
      
      <style lang="less" scoped>
      .content-about {
           200px;
          height: 200px;
          background-color: green;
      }
      </style>
      
      

    路由嵌套

    1. 使用Vite搭建Vue3开发环境,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。

    2. 安装和配置less。参见[Vite安装和配置less【模板】](# Vite安装和配置less【模板】)。

    3. 安装和配置Router。参见[Vite安装和配置Router【模板】](# Vite安装和配置Router【模板】)。

    4. 修改文件src\router\index.ts。

      import { createRouter, createWebHistory } from 'vue-router'
      import Users from '../views/Users.vue'
      import Home from '../views/Home.vue'
      import Tab1 from '../views/tabs/Tab1.vue'
      import Tab2 from '../views/tabs/Tab2.vue'
      
      const router = createRouter({
          history: createWebHistory(),
          routes: [
              {
                  path: '/users/:username',
                  component: Users,
                  children: [
                      // UserHome will be rendered inside User's <router-view>
                      // when /users/:username is matched
                      { path: '', component: Home },
      
                      // UserProfile will be rendered inside User's <router-view>
                      // when /users/:username/profile is matched
                      { path: 'tab1', component: Tab1 },
      
                      // UserPosts will be rendered inside User's <router-view>
                      // when /users/:username/posts is matched
                      { path: 'tab2', component: Tab2 },
                  ],
              },
          ],
      })
      
      export default router;
      
      
    5. 新建文件src\views\Users.vue。

      <template>
          <div class="content-users">
              <h3>Top -- 我是Users组件</h3>
              <router-view></router-view>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style lang="less" scoped>
      .content-users {
           400px;
          height: 400px;
          background-color: aqua;
      }
      </style>
      
      
    6. 新建文件src\views\Home.vue。

      <template>
          <div class="content-home">
              <h3>Second -- 我是Home组件</h3>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style lang="less" scoped>
      .content-home {
           200px;
          height: 200px;
          background-color: green;
      }
      </style>
      
      
    7. 新建文件src\views\tabs\Tab1.vue。

      <template>
          <div class="content-tab1">
              <h3>Second -- 我是嵌套子组件Tab1组件</h3>
          </div>
      </template>
      
      <script setup lang="ts
      
      </script>
      
      <style lang="less" scoped>
      .content-tab1 {
           200px;
          height: 200px;
          background-color: bisque;
      }
      </style>
      
      
    8. 新建文件src\views\tabs\Tab2.vue。

      <template>
          <div class="content-tab2">
              <h3>Second -- 我是嵌套子组件Tab2组件</h3>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style lang="less" scoped>
      .content-tab2 {
           200px;
          height: 200px;
          background-color: orangered;
      }
      </style>
      
      
    9. 修改文件src\App.vue。

      <template>
        <h1>Nested Views</h1>
        <p>
          <!-- 名称"/eduardo"为可选 -->
          <router-link to="/users/eduardo">/users/eduardo</router-link>
          <br />
          <router-link to="/users/eduardo/tab1">/users/eduardo/tab1</router-link>
          <br />
          <router-link to="/users/eduardo/tab2">/users/eduardo/tab2</router-link>
          <br />
        </p>
        <router-view></router-view>
      </template>
      
      <script setup lang="ts">
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        color: #2c3e50;
      }
      
      .router-link-active {
        color: orange;
      }
      
      .router-link-exact-active {
        color: crimson;
      }
      </style>
      
      <style scoped>
      ul {
        display: flex;
        list-style: none;
        padding: 0;
        margin: 0;
      }
      
      li:not(:last-of-type) {
        margin-right: 1rem;
      }
      </style><template>
        <h1>Nested Views</h1>
        <p>
          <!-- 名称"/eduardo"为可选 -->
          <router-link to="/users/eduardo">/users/eduardo</router-link>
          <br />
          <router-link to="/users/eduardo/tab1">/users/eduardo/tab1</router-link>
          <br />
          <router-link to="/users/eduardo/tab2">/users/eduardo/tab2</router-link>
          <br />
        </p>
        <router-view></router-view>
      </template>
      
      <script setup lang="ts">
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        color: #2c3e50;
      }
      
      .router-link-active {
        color: orange;
      }
      
      .router-link-exact-active {
        color: crimson;
      }
      </style>
      
      <style scoped>
      ul {
        display: flex;
        list-style: none;
        padding: 0;
        margin: 0;
      }
      
      li:not(:last-of-type) {
        margin-right: 1rem;
      }
      </style>
      
      

    命名视图

    命名视图可以在同一级(同一个组件)中展示更多的路由视图,而不是嵌套显示。 命名视图可以让一个组件中具有多个路由渲染出口,这对于一些特定的布局组件非常有用。 命名视图的概念非常类似于“具名插槽”,并且视图的默认名称也是default。

    命名视图

    1. 使用Vite搭建Vue3开发环境,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。

    2. 安装对应的Router版本。
      使用Vue3安装对应的Router4版本。
      使用Vue2安装对应的Router3版本。

      npm i -D vue-router@4
      
    3. 安装和配置less。参见[Vite安装和配置less【模板】](# Vite安装和配置less【模板】)。

    4. 修改文件src\main.ts。

      import { createApp } from 'vue'
      import App from './App.vue'
      import router from './router';
      
      const app = createApp(App);
      app.use(router);
      
      app.mount('#app')
      
      
    5. 新建文件夹src\views,新建文件src\views\Menu.vue。

      <template>
          <div class="content-menu">
              <h3>我是Menu组件</h3>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style lang="less" scoped>
      .content-menu {
           200px;
          height: 200px;
          background-color: aqua;
      }
      </style>
      
      
    6. 新建文件src\views\Header.vue。

      <template>
          <div class="content-header">
              <h3>我是Header组件</h3>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style lang="less" scoped>
      .content-header {
           200px;
          height: 200px;
          background-color: orangered;
      }
      </style>
      
      
    7. 新建文件src\views\Content.vue。

      <template>
          <div class="content-content">
              <h3>我是Content组件</h3>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style lang="less" scoped>
      .content-content {
           200px;
          height: 200px;
          background-color: orange;
      }
      </style>
      
      
    8. 新建文件夹src\router和新建文件src\router\index.ts。

      import { createRouter, createWebHistory } from 'vue-router'
      import Menu from '../views/Menu.vue'
      import Header from '../views/Header.vue'
      import Content from '../views/Content.vue'
      
      const router = createRouter({
          history: createWebHistory(),
          routes: [
              {
                  path: '/',
                  components: {
                      default: Menu,
                      aa: Header,
                      bb: Content,
                  },
              },
              {
                  path: '/header',
                  components: {
                      default: Header,
                      aa: Menu,
                      bb: Content,
                  },
              },
              {
                  path: '/content',
                  components: {
                      default: Content,
                      aa: Menu,
                      bb: Header,
                  },
              },
          ],
      })
      
      export default router;
      
      
    9. 修改文件src\App.vue。

      <template>
        <h1>我是App组件</h1>
        <p>
          <!-- 单击不同的路由链接,展示几个路由视图 -->
          <router-link to="/">Menu</router-link> |
          <router-link to="/header">Header</router-link> |
          <router-link to="/content">Content</router-link>
        </p>
        <hr>
        <router-view></router-view>
        <router-view name="aa"></router-view>
        <router-view name="bb"></router-view>
      </template>
      
      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      

    嵌套命名视图

    1. 基于[命名视图](# 命名视图)的基础上进行修改使用。

    2. 新建文件src\views\Root.vue。

      <template>
          <div class="content-menu">
              <h3>Top -- 我是Root组件</h3>
              <p>
                  <!-- 名称"/eduardo"为可选 -->
                  <router-link to="/">Menu</router-link> |
                  <router-link to="/header">Header</router-link> |
                  <router-link to="/content">Content</router-link>
              </p>
              <hr>
              <router-view></router-view>
              <router-view name="aa"></router-view>
              <router-view name="bb"></router-view>
          </div>
      </template>
      
      <script setup lang="ts">
      
      </script>
      
      <style lang="less" scoped>
      .content-menu {
           400px;
          height: 400px;
          background-color: yellow;
      }
      </style>
      
      
    3. 修改文件src\router\index.ts。

      import { createRouter, createWebHistory } from 'vue-router'
      import Root from '../views/Root.vue'
      import Menu from '../views/Menu.vue'
      import Header from '../views/Header.vue'
      import Content from '../views/Content.vue'
      
      const router = createRouter({
          history: createWebHistory(),
          routes: [
              {
                  path: '/',
                  component: Root,
                  children: [
                      {
                          // path: '',子路由中如果路径填写为空,则默认展示的是此路径中的内容
                          path: '/header',
                          components: {
                              default: Header,
                              aa: Menu,
                              bb: Content,
                          },
                      },
                      {
                          path: '/content',
                          components: {
                              default: Content,
                              aa: Menu,
                              bb: Header,
                          },
                      },
                  ]
              },
          ],
      })
      
      export default router;
      
      
    4. 修改文件src\App.vue。

      <template>
        <h1>我是App组件</h1>
        <router-view></router-view>
      </template>
      
      <script setup lang="ts">
      // This starter template is using Vue 3 <script setup> SFCs
      // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
      </script>
      
      <style>
      #app {
        font-family: Avenir, Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        text-align: center;
        color: #2c3e50;
        margin-top: 60px;
      }
      </style>
      
      

    重定向-别名

    说明:基于上述嵌套命名视图的示例说明。修改文件src\router\index.ts内容。

    重定向-redirect

    • 字符串形式配置,访问/重定向到/header

      import { createRouter, createWebHistory } from 'vue-router'
      import Root from '../views/Root.vue'
      import Menu from '../views/Menu.vue'
      import Header from '../views/Header.vue'
      import Content from '../views/Content.vue'
      
      const router = createRouter({
          history: createWebHistory(),
          routes: [
              {
                  path: '/',
                  component: Root,
                  // 默认展示/header子组件中的内容
                  redirect: '/header',
                  children: [
                      {
                          path: '/header',
                          components: {
                              default: Header,
                              aa: Menu,
                              bb: Content,
                          },
                      },
                      {
                          path: '/content',
                          components: {
                              default: Content,
                              aa: Menu,
                              bb: Header,
                          },
                      },
                  ]
              },
          ],
      })
      
      export default router;
      
      
    • 对象形式配置。

      import { createRouter, createWebHistory } from 'vue-router'
      import Root from '../views/Root.vue'
      import Menu from '../views/Menu.vue'
      import Header from '../views/Header.vue'
      import Content from '../views/Content.vue'
      
      const router = createRouter({
          history: createWebHistory(),
          routes: [
              {
                  path: '/',
                  component: Root,
                  redirect: {
                      path: '/header',
                  },
                  children: [
                      {
                          path: '/header',
                          components: {
                              default: Header,
                              aa: Menu,
                              bb: Content,
                          },
                      },
                      {
                          path: '/content',
                          components: {
                              default: Content,
                              aa: Menu,
                              bb: Header,
                          },
                      },
                  ]
              },
          ],
      })
      
      export default router;
      
      
    • 函数形式(可以传参)。

      import { createRouter, createWebHistory } from 'vue-router'
      import Root from '../views/Root.vue'
      import Menu from '../views/Menu.vue'
      import Header from '../views/Header.vue'
      import Content from '../views/Content.vue'
      
      const router = createRouter({
          history: createWebHistory(),
          routes: [
              {
                  path: '/',
                  component: Root,
                  redirect(to) {
                      // 模板
                      // return {
                      //     path: '/header',
                      //     query: to.query, // 可选的传参
                      // }
                      // 示例
                      return {
                          path: '/header',
                          query: { // 可选的传参
                              name: '张三',
                          },
                      }
                  },
                  children: [
                      {
                          path: '/header',
                          components: {
                              default: Header,
                              aa: Menu,
                              bb: Content,
                          },
                      },
                      {
                          path: '/content',
                          components: {
                              default: Content,
                              aa: Menu,
                              bb: Header,
                          },
                      },
                  ]
              },
          ],
      })
      
      export default router;
      
      

    别名-alias

    /别名为/root/root1/root2,意味着当用户访问/root/root1/root2时候,会被匹配为用户正在访问/

    import { createRouter, createWebHistory } from 'vue-router'
    import Root from '../views/Root.vue'
    import Menu from '../views/Menu.vue'
    import Header from '../views/Header.vue'
    import Content from '../views/Content.vue'
    
    const router = createRouter({
        history: createWebHistory(),
        routes: [
            {
                path: '/',
                component: Root,
                alias: ['/root', '/root1', '/root2'],
                children: [
                    {
                        path: '/header',
                        components: {
                            default: Header,
                            aa: Menu,
                            bb: Content,
                        },
                    },
                    {
                        path: '/content',
                        components: {
                            default: Content,
                            aa: Menu,
                            bb: Header,
                        },
                    },
                ]
            },
        ],
    })
    
    export default router;
    
    

    导航守卫

    前置守卫(结合ElementUI使用)

    1. 使用Vite搭建Vue3开发环境,参见[Vite创建Vue3.x项目【模板】](# Vite创建Vue3.x项目【模板】)。
    2. 安装和配置less。参见[Vite安装和配置less【模板】](# Vite安装和配置less【模板】)。
    3. 安装和配置Router。参见[Vite安装和配置Router【模板】](# Vite安装和配置Router【模板】)。
    4. 安装和配置ElementUI。参见[UI库ElementUI【模板】](# UI库ElementUI【模板】)。

    后置守卫

  • 相关阅读:
    Python实现常用的数据结构
    Python实现一些常用排序算法
    python实现简单排序算法
    Django学习-25-图片验证码实例
    Django学习-24-Ajax
    Django学习-23-ModelForm
    Django学习-22-Form
    Django学习-21-表关系参数
    Django学习-20-信号
    用"再生龙"Clonezilla 来克隆Linux系统
  • 原文地址:https://www.cnblogs.com/zyjhandsome/p/16274334.html
Copyright © 2020-2023  润新知