Vue2.0全面教程

news2024/11/18 11:43:09

Vue2.0

学习视频地址

文章目录

  • Vue2.0
    • 1.vue 简介
      • 1.1.什么是vue
      • 1.2.vue的两个特性
        • 1.2.1.数据驱动视图
        • 1.2.2.双向数据绑定
      • 1.3.MVVM概述
      • 1.4.MVVM工作原理
    • 2.vue的基本使用
      • 2.1.基本使用步骤
      • 2.2.基本代码与MVVM的对应关系
    • 3.vue的调试工具
      • 3.1.安装vue-devtool调试工具
      • 3.2.配置Chrome浏览器中的vue-devtools
      • 3.3.使用vue-devtools调试vue页面
    • 4.vue的指令与过滤器
      • 4.1.指令的概念
      • 4.2.内容渲染指令
        • 4.2.1.v-text
        • 4.2.2.{{ }}
        • 4.2.3.v-html
      • 4.3.属性绑定指令 v-bind
      • 4.4.使用 Javascript 表达式
      • 4.5.事件绑定指令
        • 4.5.1.事件绑定的简写形式
        • 4.5.2.绑定事件并传参
        • 4.5.3.事件参数对象
        • 4.5.4.$event
        • 4.5.5.事件修饰符
        • 4.5.6.按键修饰符
      • 4.6.双向绑定指令
        • 4.6.1.v-model 指令的修饰符
      • 4.7.条件渲染指令
        • 4.7.1.v-if
        • 4.7.2.v-show
        • 4.7.3.v-if 和 v-show 的区别
        • 4.7.4.v-else
        • 4.7.5.v-else-if
      • 4.8.列表渲染指令
        • 4.8.1.v-for 中的索引
        • 4.8.2.使用 key 维护列表的状态
        • 4.8.3.key 的注意事项
      • 4.9.过滤器
        • 4.9.1.概述
        • 4.9.2.定义过滤器
        • 4.9.3.私有过滤器和全局过滤器
        • 4.9.4.连续调用多个过滤器
        • 4.9.5.过滤器传参
    • 5.品牌列表案例
    • 6.侦听器
      • 6.1.什么是 watch 侦听器
      • 6.2.使用 watch 检测用户名是否可用
      • 6.3.immediate 选项
      • 6.4.deep 选项
      • 6.5.监听对象单个属性的变化
    • 7.计算属性
      • 7.1.什么是计算属性
      • 7.2.计算属性的特点
    • 8.axios
      • 8.1.axios 的基本使用
        • 8.1.1.发起 GET 请求:
        • 8.1.2.发起 POST 请求:
        • 8.1.3.基于axios.get和axios.post发起请求
    • 9.vue-cli
      • 9.1.什么是 vue-cli
      • 9.2.安装和使用
      • 9.3.vue 项目的运行流程
      • 9.4.vue 项目中 src 目录的构成
    • 10.vue 组件
      • 10.1.什么是组件化开发
      • 10.2.vue 中的组件化开发
      • 10.3.vue 组件的三个组成部分
        • 10.3.1.template
        • 10.3.2.script
        • 10.3.3.style
      • 10.4.组件之间的父子关系
        • 10.4.1.使用组件的三个步骤
        • 10.4.2.通过 components 注册的是私有子组件
        • 10.4.3.注册全局组件
      • 10.5.组件的 props
        • 10.5.1.props 是只读的
        • 10.5.2.props 的 default 默认值
        • 10.5.3.props 的 type 值类型
        • 10.5.4.props 的 required 必填项
      • 10.6.组件之间的样式冲突问题
        • 10.6.1.解决组件样式冲突的问题
        • 10.6.2.style 节点的 scoped 属性
        • 10.6.3./deep/ 样式穿透
    • 11.组件的生命周期
      • 11.1.生命周期 & 生命周期函数
      • 11.2.组件生命周期函数的分类
      • 11.3.生命周期图示
    • 12.组件之间的数据共享
      • 12.1.组件之间的关系
      • 12.2.父子组件之间的数据共享
        • 12.2.1.父组件向子组件共享数据
        • 12.2.2.子组件向父组件共享数据
        • 12.2.3.兄弟组件之间的数据共享
          • 12.2.1.1.EventBus 的使用步骤
    • 13.ref 引用
      • 13.1.什么是 ref 引用
      • 13.2.使用 ref 引用 DOM 元素
      • 13.3.使用 ref 引用组件实例
      • 13.4.控制文本框和按钮的按需切换
        • 13.4.1.this.$nextTick(cb) 方法
    • 14.购物车案例
    • 15.动态组件
      • 15.1.什么是动态组件
      • 15.2.如何实现动态组件渲染
      • 15.3.动态切换组件的展示与隐藏
      • 15.4.keep-alive
        • 15.4.1.使用 keep-alive 保持状态
        • 15.4.2.keep-alive 对应的生命周期函数
        • 15.4.3.keep-alive 的 include 和 exclude 属性
      • 15.5.了解组件注册名称和组件声明时name的区别
    • 16.插槽
      • 16.1.什么是插槽
      • 16.2.体验插槽的基础用法
      • 16.3.v-slot
      • 16.4.后备内容
      • 16.5.具名插槽
      • 16.6.为具名插槽提供内容
      • 16.7.作用域插槽的解构赋值
    • 17.自定义指令
      • 17.1.什么是自定义指令
      • 17.2.自定义指令的分类
        • 17.2.1.私有自定义指令
          • 17.2.1.1.通过 binding 获取指令的参数值
          • 17.2.1.2.update 函数
          • 17.2.1.3.函数简写
        • 17.2.2.全局自定义指令
    • 18.路由
      • 18.1.前端路由的概念与原理
        • 18.1.1.什么是路由
        • 18.1.2.SPA 与前端路由
        • 18.1.3.什么是前端路由
        • 18.1.4.前端路由的工作方式
        • 18.1.4.实现简易的前端路由
      • 18.2.vue-router 的基本使用
        • 18.2.1.什么是 vue-router
        • 18.2.2.vue-router 安装和配置的步骤
          • 18.2.2.1. 在项目中安装 vue-router
          • 18.2.2.2.创建路由模块
          • 18.2.2.3.导入并挂载路由模块
          • 18.2.2.4.声明路由链接和占位符
          • 18.2.2.5.声明路由的匹配规则
          • 18.2.2.6.路由重定向 redirect
      • 18.3.vue-router 的常见用法
        • 18.3.1.嵌套路由
          • 18.3.1.1.声明子路由链接和子路由占位符
          • 18.3.1.2.通过 children 属性声明子路由规则
          • 18.3.1.3.默认子路由
        • 18.3.2.动态路由
          • 18.3.2.1. 动态路由匹配
          • 18.3.2.2.动态路由的概念
          • 18.3.2.3.$route.params 参数对象
          • 18.3.2.4.使用 props 接收路由参数
        • 18.3.3.声明式导航 & 编程式导航
          • 18.3.3.1.vue-router 中的编程式导航 API
        • 18.3.4.导航守卫
          • 18.3.4.1.全局前置守卫
          • 18.3.4.2.守卫方法的 3 个形参
          • 18.3.4.3.next 函数的 3 种调用方式
          • 18.3.4.*.控制后台主页的访问权限
      • 18.4.后台管理案例
      • 18.3.vue-router 的常见用法
        • 18.3.1.嵌套路由
          • 18.3.1.1.声明子路由链接和子路由占位符
          • 18.3.1.2.通过 children 属性声明子路由规则
          • 18.3.1.3.默认子路由
        • 18.3.2.动态路由
          • 18.3.2.1. 动态路由匹配
          • 18.3.2.2.动态路由的概念
          • 18.3.2.3.$route.params 参数对象
          • 18.3.2.4.使用 props 接收路由参数
        • 18.3.3.声明式导航 & 编程式导航
          • 18.3.3.1.vue-router 中的编程式导航 API
        • 18.3.4.导航守卫
          • 18.3.4.1.全局前置守卫
          • 18.3.4.2.守卫方法的 3 个形参
          • 18.3.4.3.next 函数的 3 种调用方式
          • 18.3.4.*.控制后台主页的访问权限
      • 18.4.后台管理案例

1.vue 简介

1.1.什么是vue

官方给出的概念:Vue (读音 /vju:/,类似于 view)是一套用于构建用户界面的前端框架

  1. 构建用户界面
    • 用 vue 往 html 页面中填充数据,非常的方便
  2. 框架
    • 框架是一套现成的解决方案,程序员只能遵守框架的规范,去编写自己的业务功能!
    • 要学习 vue,就是在学习 vue 框架中规定的用法!
    • vue 的指令、组件(是对 UI 结构的复用)、路由、Vuex、vue 组件库
    • 只有把上面老师罗列的内容掌握以后,才有开发 vue 项目的能力!

1.2.vue的两个特性

1.2.1.数据驱动视图

  • 数据的变化会驱动视图自动更新
  • 好处:程序员只管把数据维护好,那么页面结构会被 vue 自动渲染出来!

在使用了 vue 的页面中,vue 会监听数据的变化,从而自动重新渲染页面的结构。示意图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ntnNEJCg-1671884043142)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221216162127137.png)]

好处:当页面数据发生变化时,页面会自动重新渲染!

注意:数据驱动视图是单向的数据绑定。

1.2.2.双向数据绑定

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

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

​ 在填写表单时,双向数据绑定可以辅助开发者在不操作 DOM 的前提下,自动把用户填写的内容同步到数据源

中。示意图如下:

在这里插入图片描述

好处:开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值!

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

1.3.MVVM概述

​ MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。MVVM 指的是 Model、View 和 ViewModel,

它把每个 HTML 页面都拆分成了这三个部分,如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5lal7Ftl-1671884043144)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221216163252808.png)]

1.4.MVVM工作原理

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

在这里插入图片描述

当数据源发生变化时,会被 ViewModel 监听到,VM 会根据最新的数据源自动更新页面的结构

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

2.vue的基本使用

2.1.基本使用步骤

1.导入 vue.js 的 script 脚本文件

2.在页面中声明一个将要被 vue 所控制的 DOM 区域

3.创建 vm 实例对象(vue 实例对象)

代码示例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>


    <!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">
        {{ username }}
    </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 : 'k'
            }
        })
    </script>
</body>
</html>

2.2.基本代码与MVVM的对应关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FY37H3wf-1671884043145)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221216171008763.png)]

3.vue的调试工具

3.1.安装vue-devtool调试工具

vue 官方提供的 vue-devtools 调试工具,能够方便开发者对 vue 项目进行调试与开发。

Chrome 浏览器在线安装 vue-devtools :

https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd

FireFox 浏览器在线安装 vue-devtools :

https://addons.mozilla.org/zh-CN/firefox/addon/vue-js-devtools/

3.2.配置Chrome浏览器中的vue-devtools

​ 点击 Chrome 浏览器右上角的 按钮,选择更多工具 -> 扩展程序 -> Vue.js devtools 详细信息,并勾选如下

的两个选项:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7kOWKh99-1671884043145)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221216171218514.png)]

注意:修改完配置项,须重启浏览器才能生效!

3.3.使用vue-devtools调试vue页面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q412e4om-1671884043145)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221216171430081.png)]

4.vue的指令与过滤器

4.1.指令的概念

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

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

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

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

4.2.内容渲染指令

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

  1. v-text
  2. {{ }}
  3. v-html

4.2.1.v-text

代码示例:

<!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>
    </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: 'k',
                gender : '男'
            }
        })
    </script>
</body>

</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-geQJPvBU-1671884043146)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221216173115892.png)]

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

4.2.2.{{ }}

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

代码示例:

<!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>
    </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: 'k',
                gender : '男'
            }
        })
    </script>
</body>

</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JmdOnMBr-1671884043146)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221216173414055.png)]

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

4.2.3.v-html

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

则需要用到 v-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: 'k',
                gender : '男',
                info : '<h1 style="color:red; font-weight:bold;">hello Vue.js</h1>'
            }
        })
    </script>
</body>

</html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T1pEL5yl-1671884043146)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221216174017690.png)]

4.3.属性绑定指令 v-bind

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

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

属性绑定指令的简写形式

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

代码示例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>


    <!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">
   
        <input type="text" v-bind:placeholder="tips">

        <hr>

        <img v-bind:src="photo" alt="" style="width:150px;">

        <hr>
        
        <!-- vue 规定 v-bind 执行可以简写为 : -->
        <img :src="photo" alt="" style="width:150px;">
    </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: {
                tips : '请输入用户名',
                photo : 'https://img1.baidu.com/it/u=1161435337,124885559&fm=253&fmt=auto&app=138&f=JPEG?w=550&h=309.png'
           
            }
        })
    </script>
</body>

</html>

4.4.使用 Javascript 表达式

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

代码示例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>


    <!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">

        <!-- 使用 Javascript 表达式 -->
        <hr>

        <div>1+2的结果是:{{ 1+ 2 }}</div>
        <div>{{ tips }} 反转的结果是: {{ tips.split('').reverse().join('')}}</div>
        <div :title="'box' + index">这是一个 div </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: {
                tips : '请输入用户名',
                index : 3
           
            }
        })
    </script>
</body>

</html>

4.5.事件绑定指令

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

        <p>count 的值是: {{ count }}</p>
        <button v-on:click="addCount">+1</button>
        <button v-on:click="subCount">-1</button>

注意:原生 DOM 对象有 onclick、oninput、onkeyup 等原生事件,替换为 vue 的事件绑定形式后,

分别为:v-on:click、v-on:input、v-on:keyup

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

  <!-- 2.创建 vue 的实例对象 -->
    <script>
        // 创建 vue 的实例对象
        const vm = new Vue({
            // el 属性是固定的写法,表示当前的 vm 实例要控制页面上的哪个区域,接收的值是一个选择器
            el: "#app",

            // data 对象就是要渲染到页面上的数据
            data: {
                count : 0
           
            },
            
            // methods 的作用就是定义事件的处理函数
            methods: {
                addCount () {
                    // this 表示当前 new 出来的 vm 实例对象
                    // 通过 this 可以访问到 data 中数据
                    this.count++;
                },

                subCount(){
                    this.count--;
                }

            },
        })
    </script>

4.5.1.事件绑定的简写形式

代码示例:

 <!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">

        <p>count 的值是: {{ count }}</p>
        <button v-on:click="addCount">+1</button>
        <button v-on:click="subCount">-1</button>

        <hr>
		<!-- v-on: 指令可以被简写为 @ -->
        <button @click="addCount">+1</button>
        <button @click="subCount">-1</button>
    </div>

完整代码:

<!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>count 的值是: {{ count }}</p>
        <button v-on:click="addCount">+1</button>
        <button v-on:click="subCount">-1</button>

        <hr>
		<!-- v-on: 指令可以被简写为 @ -->
        <button @click="addCount">+1</button>
        <button @click="subCount">-1</button>
    </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: {
                count : 0
           
            },
            
            // methods 的作用就是定义事件的处理函数
            methods: {
                addCount () {
                    // this 表示当前 new 出来的 vm 实例对象
                    // 通过 this 可以访问到 data 中数据
                    this.count++;
                },

                subCount(){
                    this.count--;
                }

            },
        })
    </script>
</body>

</html>

4.5.2.绑定事件并传参

<!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>count 的值是: {{ count }}</p>
        <!-- 在绑定事件处理函数的时候,可以使用()传递参数 -->
        <button v-on:click="addCount(10)">+10</button>
        <button v-on:click="subCount">-1</button>

        <hr>
		<!-- v-on: 指令可以被简写为 @ -->
        <button @click="addCount(10)">+1</button>
        <button @click="subCount">-1</button>
    </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: {
                count : 0
           
            },
            
            // methods 的作用就是定义事件的处理函数
            methods: {
                addCount (n) {
                    // this 表示当前 new 出来的 vm 实例对象
                    // 通过 this 可以访问到 data 中数据
                    this.count+=n;
                },

                subCount(){
                    this.count--;
                }

            },
        })
    </script>
</body>

</html>

4.5.3.事件参数对象

​ 在原生的 DOM 事件绑定中,可以在事件处理函数的形参处,接收事件参数对象 event。同理,在 v-on 指令

(简写为 @ )所绑定的事件处理函数中,同样可以接收到事件参数对象 event,示例代码如下:


    <!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">

        <p>count 的值是: {{ count }}</p>
    
        <!-- 如果 count 是偶数,则背景变成红色,否则去掉背景颜色 -->
        <button @click="addCount">+n</button>
       
    </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: {
                count: 0

            },

            // methods 的作用就是定义事件的处理函数
            methods: {
                addCount(e) {
                    this.count += 1;
                    // 判断 this.count 的值是否为偶数
                    if(this.count%2==0){
                        e.target.style.backgroundColor = 'red';
                    }else{
                        e.target.style.backgroundColor = '';
                    }
                },

            },
        })
    </script>


4.5.4.$event

e v e n t 是 v u e 提供的特殊变量,用来表示原生的事件参数对象 e v e n t 。 event 是 vue 提供的特殊变量,用来表示原生的事件参数对象 event。 eventvue提供的特殊变量,用来表示原生的事件参数对象eventevent 可以解决事件参数对象 event 被覆盖的问题。示例用法如下:

<!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">

        <p>count 的值是: {{ count }}</p>
    
        <!-- 如果 count 是偶数,则背景变成红色,否则去掉背景颜色 -->
        <!-- vue 提供了内置变量,$event ,它就是原生 DOM 的事件对象  e-->
        <button @click="addCount(1,$event)">+n</button>
       
    </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: {
                count: 0

            },

            // methods 的作用就是定义事件的处理函数
            methods: {
                addCount(n, e) {
                    this.count += n;
                    
                    // 判断 this.count 的值是否为偶数
                    if(this.count%2==0){
                        e.target.style.backgroundColor = 'red';
                    }else{
                        e.target.style.backgroundColor = '';
                    }
                },

            },
        })
    </script>
</body>

4.5.5.事件修饰符

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y8PTvcfs-1671884043147)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221216214910608.png)]

代码示例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>


    <!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">
        <a href=" http://www.baidu.com" @click.prevent="show">跳转到百度首页</a>
    </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: {},

            methods: {
                show(){
                 console.log('被点击了');   
                }
            },
          
        })
    </script>
</body>

</html>

阻止事件冒泡代码示例:

 <!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">
        <!-- 阻止事件冒泡 -->
        <div style="height:150px;background-color:orange;padding-left:100px;line-height:150px" @click="divHandler">
            <button @click.stop="btnHandler">按钮</button>
        </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: {},

            methods: {

                btnHandler(){
                    console.log('btnHandler');
                },
                
                divHandler(){
                    console.log('divHandler');
                }
            },
          
        })
    </script>

4.5.6.按键修饰符

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

代码示例:

    <!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">
        <input type="text" @keyup.esc="clearInput" @keyup.enter="commitAjax">
    </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: {},

            methods: {
                clearInput(e){
                    e.target.value = '';
                },

                commitAjax(){
                    console.log('commitAjax');
                }
            },
          
        })
    </script>

4.6.双向绑定指令

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

代码示例:

	<!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">
        <p>用户名: {{ username }}</p>
        <input type="text" v-model="username">

        <hr>

        <!-- : 是 v-bind 的简写 -->
        <input type="text" :value="username">

        <hr>

        <select v-model="city">
            <option value="">请选择城市...</option>
            <option value="1">广州</option>
            <option value="2">深圳</option>
            <option value="3">汕尾</option>
        </select>
    </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 : 'k',
                city : ''
            }
        })
    </script>

4.6.1.v-model 指令的修饰符

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6SXsaj3I-1671884043147)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221217202259933.png)]

代码示例:

  <!-- 希望 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="showUserName">获取用户名</button>

       <hr>

       <input type="text" v-model.lazy="username">
    </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 : 'k',
                n1 : 1,
                n2 : 2
            },

            methods: {
                showUserName(){
                    console.log(`用户名是:"${this.username}"`);
                }
            },
        })
    </script>

4.7.条件渲染指令

条件渲染指令用来辅助开发者按需控制 DOM 的显示与隐藏。条件渲染指令有如下两个,分别是:

v-if、v-show。

4.7.1.v-if

4.7.2.v-show

代码示例:

 <!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">
        {{ username }}
    </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 : 'k'
            }
        })
    </script>

4.7.3.v-if 和 v-show 的区别

实现原理不同:

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

性能消耗不同:

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

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

4.7.4.v-else

4.7.5.v-else-if

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

代码示例:

  <!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">

        <input type="text" v-model="type">请输入成绩等级(A/B/C/D)</input>
        <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>



    <!-- 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: {
                type : 'A'
            },

        })
    </script>

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

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

4.8.列表渲染指令

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

用 item in items 形式的特殊语法,其中:

  1. items 是待循环的数组
  2. item 是被循环的每一项

代码示例:

 <!-- 希望 vue 能够控制下面的这个 div ,帮我们能够把数据填充到 div 内部 -->
    <div id="app">

        <table class="table table-bordered table-hover table-striped">
            <thead>
                <th>id</th>
                <th>姓名</th>
            </thead>
            <tbody>
                <tr v-for="item in list">
                    <td>{{ item.id }}</td>
                    <td>{{ item.name }}</td>
                </tr>
            </tbody>
        </table>

    </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: {
               list : [
                { id: 1, name: 'k' },
                { id: 2, name: 'kk' },
                { id: 3, name: 'kkk' },
               ]
            }
        })
    </script>

4.8.1.v-for 中的索引

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

代码示例:

 <!-- 希望 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>
                <tr v-for="(item,index) in list">
                    <td>{{ index }}</td>
                    <td>{{ item.id }}</td>
                    <td>{{ item.name }}</td>
                </tr>
            </tbody>
        </table>

    </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: {
               list : [
                { id: 1, name: 'k' },
                { id: 2, name: 'kk' },
                { id: 3, name: 'kkk' },
               ]
            }
        })
    </script>

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

4.8.2.使用 key 维护列表的状态

​ 当列表的数据变化时,默认情况下,vue 会尽可能的复用已存在的 DOM 元素,从而提升渲染的性能。但这种

默认的性能优化策略,会导致有状态的列表无法被正确更新。

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

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

4.8.3.key 的注意事项

  1. key 的值只能是字符串或数字类型
  2. key 的值必须具有唯一性(即:key 的值不能重复)
  3. 建议把数据项 id 属性的值作为 key 的值(因为 id 属性的值具有唯一性)
  4. 使用 index 的值当作 key 的值没有任何意义(因为 index 的值不具有唯一性)
  5. 建议使用 v-for 指令时一定要指定 key 的值(既提升性能、又防止列表状态紊乱)
  <!-- 希望 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">
                    <td>{{ index }}</td>
                    <td>{{ item.id }}</td>
                    <td>{{ item.name }}</td>
                </tr>
            </tbody>
        </table>

    </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: {
               list : [
                { id: 1, name: 'k' },
                { id: 2, name: 'kk' },
                { id: 3, name: 'kkk' },
               ]
            }
        })
    </script>

4.9.过滤器

4.9.1.概述

​ 过滤器(Filters)是 vue 为开发者提供的功能,常用于文本的格式化。过滤器可以用在两个地方:插值表达式

和 v-bind 属性绑定。

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

4.9.2.定义过滤器

在创建 vue 实例期间,可以在 filters 节点中定义过滤器。

代码示例:

 <div id="app">
        <p>message 的值是:{{ message | capi }}</p>
    </div>


    <script src="./lib/vue-2.6.12.js"></script>
    <script>
        const vm = new  Vue({
            el: '#app',
            data: {
                message: 'hello Vue.js'
            },

            // 过滤器函数必须被顶到 filters 节点下
            // 过滤器本质是函数
            filters: {
                // 注意:过滤器函数形参中的 val 永远都是管道符前边的那个值
                capi(val){
                    const first = val.charAt(0).toUpperCase();
                    // 字符串的 slice 方法,可以截取字符串从指定索引往后截取
                    const other = val.slice(1)

                    // 强调:过滤器中,一定要有一个返回值
                    return first+other;
                } 
            }
        })
    </script>

4.9.3.私有过滤器和全局过滤器

在 filters 节点下定义的过滤器,称为“私有过滤器”,因为它只能在当前 vm 实例所控制的 el 区域内使用。

如果希望在多个 vue 实例之间共享过滤器,则可以按照如下的格式定义全局过滤器

代码示例:

 <div id="app">
        <p>message 的值是:{{ message | capi }}</p>
    </div>

    <div id="app2">
        <p>message 的值是:{{ message | capi }}</p>
    </div>


    <script src="./lib/vue-2.6.12.js"></script>
    <script>
        // 使用 Vue.filter() 定义全局过滤器
        Vue.filter('capi', function (str) {
            const first = str.charAt(0).toUpperCase();
            const other = str.slice(1);
            return first + other+'~~~';
        })


        const vm = new  Vue({
            el: '#app',
            data: {
                message: 'hello Vue.js'
            },

            // 过滤器函数必须被顶到 filters 节点下
            // 过滤器本质是函数
            filters: {
                // 注意:过滤器函数形参中的 val 永远都是管道符前边的那个值
                capi(val) {
                    const first = val.charAt(0).toUpperCase();
                    // 字符串的 slice 方法,可以截取字符串从指定索引往后截取
                    const other = val.slice(1)

                    // 强调:过滤器中,一定要有一个返回值
                    return first + other;
                }
            }
        })

        // --------------------------------

        const vm2 = new  Vue({
            el: '#app2',
            data: {
                message: 'cmk'
            },
        })

    </script>

4.9.4.连续调用多个过滤器

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7MkRAXPJ-1671884043148)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221218201551245.png)]

4.9.5.过滤器传参

过滤器的本质是 JavaScript 函数,因此可以接收参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hxf6LUhC-1671884043148)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221218201617158.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AcJoRVLg-1671884043148)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221218201641075.png)]

5.品牌列表案例




6.侦听器

6.1.什么是 watch 侦听器

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

代码示例:

 <div id="app">
        <input type="text" v-model="username">
    </div>


    <script src="./lib/vue-2.6.12.js"></script>
    <script>
        const vm = new Vue({
            el: "#app",

            data: {
                username: ''
            },

            // 所有的侦听器都应该被定义到 watch 节点下
            watch: {
                // 侦听器本质上是一个函数,要监听哪个数据的变化,就把数据名作为方法即可
                // 新值在前,旧值在后
                username(newVal, oldVal) {
                    console.log('监听到了username 值的变化!', newVal, oldVal);
                }
            }
        })
    </script>

6.2.使用 watch 检测用户名是否可用

监听 username 值的变化,并使用 axios 发起 Ajax 请求,检测当前输入的用户名是否可用

代码示例:

 <div id="app">
        <input type="text" v-model="username">
    </div>


    <script src="./lib/vue-2.6.12.js"></script>
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

    <script>
        const vm = new Vue({
            el: "#app",

            data: {
                username: ''
            },

            // 所有的侦听器都应该被定义到 watch 节点下
            watch: {
                // 侦听器本质上是一个函数,要监听哪个数据的变化,就把数据名作为方法即可
                // 新值在前,旧值在后
                username(newVal) {
                    if(newVal===''){
                        return ;
                    }
                    // 1.调用 JQ 中的 ajax 发起请求,判断 newVal 是否被占用
                   $.get('http://localhost:9090/user/EixtUserName/'+newVal,function(result){
                        console.log(result);
                    })
                }
            }
        })
    </script>

6.3.immediate 选项

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

immediate 选项。

代码示例:

<div id="app">
        <input type="text" v-model="username">
    </div>


    <script src="./lib/vue-2.6.12.js"></script>
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

    <script>
        const vm = new Vue({
            el: "#app",

            data: {
                username: 'admin'
            },

            // 所有的侦听器都应该被定义到 watch 节点下
            watch: {
                // 定义对象格式的侦听器
                username: {
                    handler(newVal) {
                         if (newVal === '') {
                            return;
                        }
                        // 1.调用 JQ 中的 ajax 发起请求,判断 newVal 是否被占用
                        $.get('http://localhost:9090/user/EixtUserName/' + newVal, function (result) {
                            console.log(result);
                        })
                    },

                    // immediate 选项的默认值是 false
                    // immediate 的作用是:控制侦听器是否自动触发一次
                    immediate: true
                }
            }
        })
    </script>

6.4.deep 选项

​ 如果 watch 侦听的是一个对象,如果对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep 选

项。

代码示例:

 <div id="app">
        <input type="text" v-model="info.username">
    </div>


    <script src="./lib/vue-2.6.12.js"></script>
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

    <script>
        const vm = new Vue({
            el: "#app",

            data: {
                // 用户信息对象
                info: {
                    username: 'admin'
                }
            },

            // 所有的侦听器都应该被定义到 watch 节点下
            watch: {
                info: {
                    handler(newVal) {
                        const userName = newVal.username
                        if (userName === '') {
                            return;
                        }
                        // 1.调用 JQ 中的 ajax 发起请求,判断 newVal 是否被占用
                        $.get('http://localhost:9090/user/EixtUserName/' + userName, function (result) {
                            console.log(result);
                        })
                    },

                    // 开启深度监听,只要对象中任何一个属性变化了,都会触发对象的侦听器
                    deep: true,

                    immediate: true
                }
            }
        })
    </script>

6.5.监听对象单个属性的变化

如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器。

代码示例:

   <div id="app">
        <input type="text" v-model="info.username">
    </div>


    <script src="./lib/vue-2.6.12.js"></script>
    <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>

    <script>
        const vm = new Vue({
            el: "#app",

            data: {
                // 用户信息对象
                info: {
                    username: 'admin'
                }
            },

            // 所有的侦听器都应该被定义到 watch 节点下
            watch: {
                // 如果要监听的是对象的子属性的变化,则必须包裹一层单引号
                'info.username'(newVal) {
                    if (newVal === '') {
                        return;
                    }
                    // 1.调用 JQ 中的 ajax 发起请求,判断 newVal 是否被占用
                    $.get('http://localhost:9090/user/EixtUserName/' + newVal, function (result) {
                        console.log(result);
                    })
                },

                immediate: true
            }
        })
    </script>

7.计算属性

7.1.什么是计算属性

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

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

7.2.计算属性的特点

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

代码示例:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="./lib/vue-2.6.12.js"></script>
    <style>
        .box {
            width: 200px;
            height: 200px;
            border: 1px solid #ccc;
        }
    </style>
</head>

<body>
    <div id="app">
        <div>
            <span>R:</span>
            <input type="text" v-model.number="r">
        </div>
        <div>
            <span>G:</span>
            <input type="text" v-model.number="g">
        </div>
        <div>
            <span>B:</span>
            <input type="text" v-model.number="b">
        </div>
        <hr>

        <!-- 专门用户呈现颜色的 div 盒子 -->
        <!-- 在属性身上,: 代表  v-bind: 属性绑定 -->
        <!-- :style 代表动态绑定一个样式对象,它的值是一个 {  } 样式对象 -->
        <!-- 当前的样式对象中,只包含 backgroundColor 背景颜色 -->
        <div class="box" :style="{ backgroundColor: rgb }">
            {{ rgb }}
        </div>
        <button @click="show">按钮</button>
    </div>

    <script>
        // 创建 Vue 实例,得到 ViewModel
        var vm = new Vue({
            el: '#app',
            data: {
                // 红色
                r: 0,
                // 绿色
                g: 0,
                // 蓝色
                b: 0
            },
            methods: {
                // 点击按钮,在终端显示最新的颜色
                show() {
                    console.log(this.rgb)
                }

            },

            // 所有的计算属性都要定义到 computed 节点下
            // 计算属性在定义的是时候,要定义成 方法格式
            computed: {

                // rgb 作为一个计算属性,被定义成了方法格式
                // 最终,在这个方法中要返回一个生成好的 rgb(x,y,z) 的字符串
                rgb() {
                    return `rgb(${this.r},${this.g},${this.b})`;
                }
            }
        });
    </script>
</body>

</html>

8.axios

axios 是一个专注于网络请求的库!

8.1.axios 的基本使用

8.1.1.发起 GET 请求:

代码示例:

<script src="./lib/axios.js"></script>
    <script>

        // 1.调用 axios 方法得到的返回值是 Promise 对象
        axios({
            // 请求方式
            method:'GET',
            // 请求地址
            url:'http://localhost:9090/user/AllUser'
        }).then(function(result){
            console.log(result.data);
        })
    </script>

代码示例(传参):

 <script src="./lib/axios.js"></script>
    <script>

        // 1.调用 axios 方法得到的返回值是 Promise 对象
        axios({
            // 请求方式
            method: 'GET',
            // 请求地址   http://localhost:9090/user/selectUserById?id=1
            url: 'http://localhost:9090/user/selectUserById',
            // URL 中的查询参数
            params: {
                id : 1
            },
            // 请求体参数
            data: {}
        }).then(function (result) {
            console.log(result.data);
        })
    </script>

简化1:

 <button id="btnGet">发起GET请求</button>

    

    <script src="./lib/axios.js"></script>
    <script>

        document.querySelector('#btnGet').addEventListener('click', async function () {
            // 如果调用某个方法的返回值是 Promise 示例,则前面可以添加 await  
            // await 只能用在被 async 修饰的方法中  
            const { data } = await axios({
                // 请求方式
                method: 'GET',
                // 请求地址
                url: 'http://localhost:9090/user/AllUser',
            })

            console.log(data);
        })
    </script>

简化2:

<button id="btnGet">发起GET请求</button>



    <script src="./lib/axios.js"></script>
    <script>

        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://localhost:9090/user/AllUser',
            })

            console.log(res.data);
        })
    </script>

8.1.2.发起 POST 请求:

<button id="btnPost">发起POST请求</button>



    <script src="./lib/axios.js"></script>
    <script>

        document.querySelector('#btnPost').addEventListener('click', function () {
            axios({
                // 请求方式
                method: 'POST',
                // 请求地址
                url: 'http://localhost:9090/user/register',

                // 请求体参数
                data: {
                    userName:'post测试',
                    nickName:'post测试',
                    password:'123456',
                    email:'123456@qq.com'
                }
            }).then(function (result) {
                console.log(result);
            })
        })
    </script>

简化:

<button id="btnPost">发起POST请求</button>



    <script src="./lib/axios.js"></script>
    <script>

        document.querySelector('#btnPost').addEventListener('click', async function () {
            // 如果调用某个方法的返回值是 Promise 示例,则前面可以添加 await  
            // await 只能用在被 async 修饰的方法中  
            const { data } = await axios({
                // 请求方式
                method: 'POST',
                // 请求地址
                url: 'http://localhost:9090/user/register',

                // 请求体参数
                data: {
                    userName: 'post测试111',
                    nickName: 'post测试111',
                    password: '123456',
                    email: '123456111@qq.com'
                }
            })

            console.log(data);
        })
    </script>

8.1.3.基于axios.get和axios.post发起请求

代码示例:

 	<button id="btnGet">GET</button>

    <button id="btnPost">POST</button>

    <script src="./lib/axios.js"></script>

    <script>
        document.querySelector('#btnGet').addEventListener('click', async function () {

            const { data: res } = await axios.get('http://localhost:9090/user/selectUserById', {
                // get参数
                params: {
                    id: 1
                }
            })
            console.log(res);
        })

        // ---------------------------------

        document.querySelector('#btnPost').addEventListener('click', async function () {

            const { data: res } = await axios.post('http://localhost:9090/user/register', 
            {userName: 'post测试222',nickName: 'post测试222',password: '123456',email: '123456222@qq.com'})
            console.log(res);
        })
    </script>

9.vue-cli

9.1.什么是 vue-cli

vue-cli 是 Vue.js 开发的标准工具。它简化了程序员基于 webpack 创建工程化的 Vue 项目的过程。

引用自 vue-cli 官网上的一句话:

程序员可以专注在撰写应用上,而不必花好几天去纠结 webpack 配置的问题。

中文官网:https://cli.vuejs.org/zh/

9.2.安装和使用

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

npm install -g @vue/cli

基于 vue-cli 快速生成工程化的 Vue 项目:

vue create 项目的名称

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WStiaBRC-1671884043149)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219153759222.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UB2eMXi2-1671884043149)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219154337721.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XycJq9xB-1671884043149)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219154412743.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oxwWpHJI-1671884043149)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219154458700.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v47Ivlfp-1671884043150)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219154614244.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-joMgoMwp-1671884043150)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219154934029.png)]

9.3.vue 项目的运行流程

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

其中:

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

9.4.vue 项目中 src 目录的构成

assets 文件夹:存放项目中用到的静态资源文件,例如:css 样式表、图片资源
components 文件夹:程序员封装的、可复用的组件,都要放到 components 目录下
main.js 是项目的入口文件。整个项目的运行,要先执行 main.js
App.vue 是项目的根组件。

10.vue 组件

10.1.什么是组件化开发

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

10.2.vue 中的组件化开发

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

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

10.3.vue 组件的三个组成部分

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

  1. template -> 组件的模板结构
  2. script -> 组件的 JavaScript 行为
  3. style -> 组件的样式

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

10.3.1.template

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

注意:

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

10.3.2.script

vue 规定:开发者可以在

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

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

10.3.3.style

vue 规定:组件内的

让 style 中支持 less 语法

代码示例:

<template>
    <div>
        <div class="box">
            <h1>这是用户自定义的 Test.vue --- {{ username }}</h1>
            <button @click="changeName">修改用户名</button>
        </div>

        <div>111</div>
    </div>
</template>

<script>
// 默认导出,这是固定写法
export default {

    // data 数据源
    // 注意:.vue 组件中的 data 不能像之前一样指向对象
    // 组件中的 data 必须是一个函数
    data() {
        // 这个 return 出去的 {} 中可以定义数据
        return {
            username: 'k'
        }
    },

    methods: {
        changeName() {
            // 在组件中,this 就表示当前组件的实例对象
            this.username = 'kk'
        }
    },

    // 侦听器
    watch: {
        // 当前组件中的计算属性
        computed: {},
        // 当前组件中的过滤器
        filters: {}
    }
}
</script>

<style lang="less">
.box {
    background-color: aqua;

    h1 {
        color: red;
    }
}
</style>

10.4.组件之间的父子关系

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m3KZy5tH-1671884043150)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219173217217.png)]

组件在被封装好之后,彼此之间是相互独立的,不存在父子关系

在使用组件的时候,根据彼此的嵌套关系,形成了父子关系、兄弟关系

10.4.1.使用组件的三个步骤

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tAQJdoNK-1671884043151)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219173657680.png)]

代码示例:

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

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

<script>

// 导入需要使用的 .vue 组件
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'

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

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

.box {
  display: flex;
}
</style>

具体代码查看 day4/code/demo-1 文件

在VScode中配置@路径提示的插件

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

例如:

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

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

10.4.3.注册全局组件

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

代码示例:

// 导入需要全局注册的组件
import Count from '@/components/Count.vue'

// 参数1:字符串格式,表示组件的 注册名称
// 参数2:需要被全局注册的那个组件
Vue.component('MyCount', Count)

10.5.组件的 props

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

可以用于存放从其他组件传递过来的数据

10.5.1.props 是只读的

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zuMB3ht4-1671884043151)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219200846462.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-110njx4Q-1671884043151)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219200854222.png)]

10.5.2.props 的 default 默认值

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u52aTUkT-1671884043151)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219200924472.png)]

10.5.3.props 的 type 值类型

在声明自定义属性时,可以通过 type 来定义属性的值类型。示例代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jOcOcfnO-1671884043152)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219200952077.png)]

10.5.4.props 的 required 必填项

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

码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XZZWX3o2-1671884043152)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219201020541.png)]

代码示例:

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

        <button @click="show">打印 this</button>
    </div>
</template>

<script>
export default {
    // props 是"自定义属性",允许使用者通过自定义属性,为当前组件指定初始值
    // 自定义属性的名字,是封装者自定义的(只要名称合法即可)
    // props 中的数据,可以直接在模板结构中被使用
    // 注意:props 是只读的,不要直接修改 props 的值,否则终端会报错!
    // props: ['init'],
    props: {    
        // 自定义属性A : { /* 配置选项 */ },
        // 自定义属性B : { /* 配置选项 */ },
        // 自定义属性C : { /* 配置选项 */ },
        init: {
            // 如果外界使用 Count 组件的时候,没有传递 init 属性,则默认值生效
            default: 10,
            // init 的值类型必须是 Number 数字
            type: Number,
            // 必填项校验
            required: true
        }
    },

    data() {
        return {
            // 把 props 中的 init 值,转存到 count 上
            count: this.init
        }
    },
    methods: {
        show() {
            console.log(this)
        }
    }
}
</script>

<style lang="less">

</style>

10.6.组件之间的样式冲突问题

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

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

  1. 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
  2. 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素

10.6.1.解决组件样式冲突的问题

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5RWVKxiD-1671884043152)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219231928868.png)]

10.6.2.style 节点的 scoped 属性

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

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


h3{
  color: red;
}
</style>

10.6.3./deep/ 样式穿透

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5Two5yoK-1671884043153)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221219232507746.png)]

代码示例:

<style lang="less" scoped>

h3 {
  color: red;
}


/deep/ h5 {
  color: green;
}
</style>

11.组件的生命周期

11.1.生命周期 & 生命周期函数

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

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

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

11.2.组件生命周期函数的分类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mhKEamkX-1671884043153)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221220212013958.png)]

11.3.生命周期图示

可以参考 vue 官方文档给出的“生命周期图示”,进一步理解组件生命周期执行的过程:

https://cn.vuejs.org/v2/guide/instance.html#生命周期图示

代码示例:

<template>
    <div class="test-container">
        <h3 id="myh3">Test.vue 组件 --- {{ users.length }} 个用户</h3>

        <p id="ppp">message 的值是: {{ message }}</p>
        <button @click="message += '~'">修改 message 的值</button>
    </div>
</template>

<script>
export default {
    props: ['info'],
    data() {
        return {
            message: 'Hello Test.vue',
            // 定义 users 数组,存储的是所有用户的数据,默认为空数组
            users: []
        }
    },
    methods: {
        show() {
            console.log('调用了 Test 组件的 show 方法');
        },

        // 使用 Ajax 请求用户列表的数据
        initUserList() {
            const xhr = new XMLHttpRequest();
            xhr.addEventListener('load', () => {
                const result = JSON.parse(xhr.responseText);
                this.users = result.data;
                console.log(result);
            });
            xhr.open('GET', 'http://localhost:9090/user/AllUser');
            xhr.send()
        }
    },

    // 创建阶段的第一个生命周期函数
    beforeCreate() {
        // undefined
        // console.log('beforeCreate---this.info'+this.info);
        // console.log('beforeCreate---this.message'+this.message);
        // this.show();
    },

    // 创建阶段的第二个生命周期函数
    // 在这个阶段经常使用 ajax 拿取数据
    created() {

        // created 生命周期函数,非常常用
        // 经常在它里边调用 methods 中的方法,请求服务器的数据
        // 并且,把请求到的数据转存到 data 中,供 templete 模板渲染使用
        this.initUserList();

        console.log('created---this.info' + this.info);
        console.log('created---this.message' + this.message);
        this.show();


        // 在 cteate 阶段拿不到这个方法
        //const myh3 = document.querySelector('#myh3')
        //console.log(myh3);


    },

    // 创建阶段的第三个生命周期函数
    // 用处不大
    beforeMount() {
        // 在 beforeMount 阶段拿不到这个方法
        const myh3 = document.querySelector('#myh3')
        console.log('beforeMount---myh3:' + myh3);
    },


    // 创建阶段的第四个生命周期函数
    // 如果要操作当前组件的 DOM ,最早只能在 mounted 阶段执行
    // 主要用来操作 DOM 元素
    mounted() {
        const myh3 = document.querySelector('#myh3')
        console.log('mounted---myh3:' + myh3);
    },


    // 创建阶段的第五个生命周期
    beforeUpdate() {
        console.log('beforeUpdate');

        console.log('beforeUpdate---this.message:' + this.message);
        const ppp = document.querySelector('#ppp')
        console.log('beforeUpdate:' + ppp.innerHTML);
    },

    // 创建阶段的第六个生命周期
    // 当数据变化之后,为了能够操作最新的 DOM 结构,必须把代码写到 updated 生命周期函数
    updated() {
        console.log('updated---this.message:' + this.message);
        const ppp = document.querySelector('#ppp')
        console.log('updated:' + ppp.innerHTML);
    },

    // 创建阶段的第七个生命周期
    beforeDestroy() {
        console.log('beforeDestroy');
    },

    // 创建阶段的第八个生命周期
    destroyed() {
        console.log('destroyed');
    },

}
</script>


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

    h3 {
        color: red;
    }
}
</style>

12.组件之间的数据共享

12.1.组件之间的关系

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MkJ6vABP-1671884043153)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221220221145129.png)]

12.2.父子组件之间的数据共享

父子组件之间的数据共享又分为:

  1. 父 -> 子共享数据
  2. 子 -> 父共享数据

12.2.1.父组件向子组件共享数据

父组件向子组件共享数据需要使用自定义属性。示例代码如下:

父组件:App.vue

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

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

    </div>
  </div>
</template>

<script>

import Left from '@/components/Left.vue'

export default {
  data() {
    return {
      message: 'Hello Vue',
      userinfo: { name: 'k', age: 18 },
    }
  },
  components:{
    Left
  }
}
</script>

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

.box {
  display: flex;
}
</style>

子组件:Left.vue

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

<script>


export default {
  props: ['msg', 'user']
}
</script>

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

12.2.2.子组件向父组件共享数据

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

子组件:Right.vue

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

<script>

export default {
  data() {
    return {
      // 子组件自己的数据,将来希望把 count 的值传递给父组件
      count: 0,
    }
  },
  methods: {
    add() {
      this.count += 1;
      // 把自增结果传递给 父组件
      this.$emit('numchange', this.count);
    },
  },
}
</script>

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

父组件:App.vue

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

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

<script>
import Right from '@/components/Right.vue'

export default {
  data() {
    return {
      // 定义 countFromSon 来接收子组件传递过来的值
      countFromSon:0
    }
  },
  methods: {
    // 获取子组件传递过来的数据
    getNewCount(val){
      this.countFromSon = val;
    }
  },
  components:{
    Right
  }
}
</script>

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

.box {
  display: flex;
}
</style>

12.2.3.兄弟组件之间的数据共享

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjZa6vJZ-1671884043153)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221220221322704.png)]

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

代码示例:

Left.vue

<template>
  <div class="left-container">
    <h3>Left 组件</h3>
    <button @click="send">把 str 发送给 Right.vue</button>
  </div>
</template>

<script>

import bus from '@/components/eventBus.js'

export default {

  data() {
    return {
      str: '我是来自 Left 的数据'
    }
  },
  methods: {
    send() {
      // 通过 eventBus 来发送数据
      bus.$emit('share', this.str)
    }
  },
}
</script>

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

eventBus.js

import Vue from 'vue'

export default new Vue()

Right.vue

<template>
  <div class="right-container">
    <h3>Right 组件</h3>
    <p>{{ msgFromLeft }}</p>
  </div>
</template>

<script>

import bus from '@/components/eventBus.js'


export default {
  data() {
    return {
      msgFromLeft: ''
    }
  },
  created() {
    // 为 bus 绑定自定义事件
    bus.$on('share', (val) => {
      this.msgFromLeft = val;
    })
  },
}
</script>

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

13.ref 引用

13.1.什么是 ref 引用

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

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

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

13.2.使用 ref 引用 DOM 元素

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

代码示例:

<template>
  <div class="app-container">
    <h1 ref="myh1">App 根组件</h1>
    <button @click="showThis">打印 this</button>
    <hr />

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

    </div>
  </div>
</template>

<script>

import Left from '@/components/Left.vue'

export default {
  methods: {
    showThis() {
      // 当前 App 组件的实例对象
      // console.log(this.$refs.myh1);
      this.$refs.myh1.style.color = 'red'
    },

}
</script>

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

.box {
  display: flex;
}
</style>

13.3.使用 ref 引用组件实例

如果想要使用 ref 引用页面上的组件实例,则可以按照如下的方式进行操作:

代码示例:

Left.vue

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

    <button @click="count += 1">+1</button>
    <button @click="resetCount">重置</button>
  </div>
</template>

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

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

App.vue

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

    <button @click="onReset">重置 Left 的 count 值为 0</button>
    <hr />

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 使用 ref 拿到 Left.vue 组件的实例对象,依次来操作 Left.vue 组件里边的内容 -->
      <Left ref="comLeft"></Left>
    </div>
  </div>
</template>

<script>

import Left from '@/components/Left.vue'

export default {
  methods: {
    // 点击按钮重置 Left 的 count 值
    onReset(){
      // this.$refs.comLeft.resetCount();
      this.$refs.comLeft.count = 0;
    }
  },
  components: {
    Left
  }
}
</script>

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

.box {
  display: flex;
}
</style>

13.4.控制文本框和按钮的按需切换

通过布尔值 inputVisible 来控制组件中的文本框与按钮的按需切换。

代码示例:

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

    <hr />

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

    <hr>

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

<script>


export default {
  data() {
    return {
      // 控制输入框和按钮的按需切换
      // 默认值为 false ,表示默认展示按钮,隐藏输入框
      inputVisible: false
    }
  },
  methods: {
    showInput() {
      this.inputVisible = true;
    },
    showButton() {
      this.inputVisible = false;
    }
  },
}
</script>

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

.box {
  display: flex;
}
</style>

13.4.1.this.$nextTick(cb) 方法

​ 组件的 $nextTick(cb) 方法,会把 cb 回调推迟到下一个 DOM 更新周期之后执行。通俗的理解是:等组件的

DOM 更新完成之后,再执行 cb 回调函数。从而能保证 cb 回调函数可以操作到最新的 DOM 元素。

代码示例:

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

    <hr />

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

    <hr>

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

    </div>
  </div>
</template>

<script>

export default {
  data() {
    return {
      // 控制输入框和按钮的按需切换
      // 默认值为 false ,表示默认展示按钮,隐藏输入框
      inputVisible: false
    }
  },
  methods: {

    showInput() {
      // 1.切换布尔值,把文本框展示出来
      this.inputVisible = true;
      // 2.让展示出来的文本框自动获得焦点
      this.$nextTick(() => this.$refs.iptRef.focus())
    },
    showButton() {
      this.inputVisible = false;
    }
  },

}
</script>

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

.box {
  display: flex;
}
</style>

14.购物车案例

15.动态组件

15.1.什么是动态组件

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

15.2.如何实现动态组件渲染

vue 提供了一个内置的 组件,专门用来实现动态组件的渲染。

代码示例:

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

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 1.component 标签是 vue 内置的,作用:组件的占位符 -->
      <!-- 2.is 属性的值表示要渲染的组件的名字 -->
      <component :is="comName"></component>
    </div>
  </div>
</template>

<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {

  data() {
    return {
      // comName 表示要展示组件的名称
      comName:'Left'
    }
  },

  components: {
    Left,
    Right
  }
}
</script>

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

.box {
  display: flex;
}
</style>

15.3.动态切换组件的展示与隐藏

代码示例:

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

    <button @click="comName='Left'">展示 Left</button>
    <button @click="comName='Right'">展示 Right</button>


    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 1.component 标签是 vue 内置的,作用:组件的占位符 -->
      <!-- 2.is 属性的值表示要渲染的组件的名字 -->
      <component :is="comName"></component>
    </div>
  </div>
</template>

<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {

  data() {
    return {
      // comName 表示要展示组件的名称
      comName:'Left'
    }
  },

  components: {
    Left,
    Right
  }
}
</script>

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

.box {
  display: flex;
}
</style>

15.4.keep-alive

15.4.1.使用 keep-alive 保持状态

​ 默认情况下,切换动态组件时无法保持组件的状态。此时可以使用 vue 内置的 组件保持动态组

件的状态。

代码示例:

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

    <button @click="comName = 'Left'">展示 Left</button>
    <button @click="comName = 'Right'">展示 Right</button>


    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 1.component 标签是 vue 内置的,作用:组件的占位符 -->
      <!-- 2.is 属性的值表示要渲染的组件的名字 -->

      <!-- keep-alive 可以把内部的组件进行缓存,而不是销毁组件 -->
      <keep-alive>
        <component :is="comName"></component>
      </keep-alive>
    </div>
  </div>
</template>

<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {

  data() {
    return {
      // comName 表示要展示组件的名称
      comName: 'Left'
    }
  },

  components: {
    Left,
    Right
  }
}
</script>

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

.box {
  display: flex;
}
</style>

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

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

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

代码示例:

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

<script>
export default {
  data() {
    return {
      count: 0
    }
  },

  // 当组件第一次被创建的时候,既会执行 created 生命周期,也会执行 activated 生命周期
  // 但是,当组件被激活的时候,只会触发 activated 生命周期,不再触发 created 生命周期
  activated() {
    console.log('组件被激活了,activated');
  },

  deactivated() {
    console.log('组件被缓存了,deactivated');
  },
}
</script>

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

15.4.3.keep-alive 的 include 和 exclude 属性

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

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

注意:include 和 exclude 不能同时使用

代码示例:

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

    <button @click="comName = 'Left'">展示 Left</button>
    <button @click="comName = 'Right'">展示 Right</button>


    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 1.component 标签是 vue 内置的,作用:组件的占位符 -->
      <!-- 2.is 属性的值表示要渲染的组件的名字 -->

      <!-- keep-alive 可以把内部的组件进行缓存,而不是销毁组件 -->
      <!-- include 表示只缓存字符串的组件 多个组件用英文逗号分隔 -->
      <!-- exclude 排除项, 注意 include 和 exclude 不能同时使用 -->
      <keep-alive include="Left">
        <component :is="comName"></component>
      </keep-alive>
    </div>
  </div>
</template>

<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {

  data() {
    return {
      // comName 表示要展示组件的名称
      comName: 'Left'
    }
  },

  components: {
    Left,
    Right
  }
}
</script>

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

.box {
  display: flex;
}
</style>

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

代码示例:

Right.vue

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

<script>
export default {

  // 当提供了 name 属性之后,组件的名称就是 name 的值
  // 对比:
  // 1.组件的注册名称的主要场景是 以标签的形式,把注册好的组件渲染和使用到页面结构之中
  // 2.组件声明时候的 name 名称主要应用场景:结合 <keep-alive> 标签实现组件缓存功能,以及在调试工具中看到组件的名称
  name:'MyRight'
}
</script>

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

App.vue

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

    <button @click="comName = 'Left'">展示 Left</button>
    <button @click="comName = 'Right'">展示 Right</button>


    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <!-- 1.component 标签是 vue 内置的,作用:组件的占位符 -->
      <!-- 2.is 属性的值表示要渲染的组件的名字 -->

      <!-- keep-alive 可以把内部的组件进行缓存,而不是销毁组件 -->
      <!-- include 表示只缓存字符串的组件 多个组件用英文逗号分隔 -->
      <!-- exclude 排除项, 注意 include 和 exclude 不能同时使用 -->
      <keep-alive exclude="MyRight">
        <component :is="comName"></component>
      </keep-alive>
    </div>
  </div>
</template>

<script>
import Left from '@/components/Left.vue'
import Right from '@/components/Right.vue'
export default {

  data() {
    return {
      // comName 表示要展示组件的名称
      comName: 'Left'
    }
  },

  components: { 
    // 如果在声明组件的时候,没有为组件指定 name 名称,则组件的名称默认就是注册时候的名称
    Left,
    Right
  }
}
</script>

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

.box {
  display: flex;
}
</style>

16.插槽

16.1.什么是插槽

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

部分定义为插槽。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XkJF6gjn-1671884043154)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221222162515056.png)]

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

16.2.体验插槽的基础用法

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

代码示例:

App.vue

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

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <p>这个在 Left 组件的内容区域声明的 p 标签</p>
      </Left>
    </div>
  </div>
</template>

<script>

import Left from '@/components/Left.vue'

export default {
  components:{
    Left
  }
}
</script>

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

Left.vue

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

    <hr>

    <!-- 声明一个插槽区域 -->
    <slot></slot>
  </div>
</template>

<script>
export default {}
</script>

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

没有预留插槽的内容会被丢弃

如果在封装组件时没有预留任何 插槽,则用户提供的任何自定义内容都会被丢弃。

16.3.v-slot

代码示例:

Left.vue

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

    <hr>

    <!-- 声明一个插槽区域 -->
    <!-- vue 官方规定:每一个插槽都要有一个 name 名称 -->
    <!-- 如果省略 slot 的 name 属性,则有个默认名称为 default -->
    <slot name="defualt"></slot>
  </div>
</template>

<script>
export default {}
</script>

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

App.vue

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

    <div class="box">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 默认的情况下,在使用组件的时候,提供的内容都会被填充到名字为 default 的插槽之中 -->
        <!-- 1.如果要把内容填充到指定名称的插槽中,需要使用 v-slot:名称 这个指定-->
        <!-- 2.v-slot: 后面要跟上插槽的名称 -->
        <!-- 3.v-slot: 指定不能直接用在元素身上,必须用到 template 标签-->
        <!-- 4.template 这个标签,它是一个虚拟的标签,只起到包裹性质的作用,不会被渲染为任何实质性的 html 元素 -->
        <!-- 5.v-slot:指定的简写形式是 # -->
        <template #defualt>
          <p>这个在 Left 组件的内容区域声明的 p 标签</p>
        </template>
      </Left>
    </div>
  </div>
</template>

<script>

import Left from '@/components/Left.vue'

export default {
  components:{
    Left
  }
}
</script>

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

16.4.后备内容

​ 封装组件时,可以为预留的 插槽提供后备内容(默认内容)。如果组件的使用者没有为插槽提供任何

内容,则后备内容会生效。

代码示例:

Left.vue

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

    <hr>

    <!-- 声明一个插槽区域 -->
    <!-- vue 官方规定:每一个插槽都要有一个 name 名称 -->
    <!-- 如果省略 slot 的 name 属性,则有个默认名称为 default -->
    <!-- 如果这个插槽被使用,则不会显示插槽中的文字 -->
    <slot name="defualt">这是 default 插槽的默认内容</slot>
  </div>
</template>

<script>
export default {}
</script>

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

16.5.具名插槽

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

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

代码示例:

Article.vue

<template>
    <div class="article-container">


        <!-- 文章的标题 -->
        <div class="header-box">
            <slot name="title"></slot>
        </div>

        <!-- 文章的内容 -->
        <div class="content-box">
            <slot name="content"></slot>
        </div>

        <!-- 文章的作者 -->
        <div class="footer-box">
            <slot name="author"></slot>
        </div>


    </div>
</template>

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

<style lang="less" scoped>
.article-container {

    >div{
        min-height: 150px;
    }

    .header-box {
        background-color:#ffc7c9;
    }

    .content-box {
        background-color: #a9d5e8;
    }

    .footer-box {
        background-color: #ffa776;
    }
}
</style>

App.vue

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

      <Article>
          
        <template #title>
          <h2>震惊!...</h2>
        </template>

        <template #content>
          <div>
            <p>某高校一男子...</p>
            <p>荣获...</p>
            <p>牛逼...</p>
          </div>
        </template>

        <template #author>
          <div>作者:彬锅果</div>
        </template>

      </Article>



  </div>
</template>

<script>

import Article from '@/components/Article.vue'

export default {
  components: {
    Article

  }
}
</script>

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

.box {
  display: flex;
}
</style>

16.6.为具名插槽提供内容

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

形式提供其名称。

代码示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OYrexqjC-1671884043154)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221222171417004.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ncd1z68w-1671884043155)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221222171426579.png)]

16.7.作用域插槽的解构赋值

代码示例:

Article.vue

<template>
    <div class="article-container">


        <!-- 文章的标题 -->
        <div class="header-box">
            <slot name="title"></slot>
        </div>

        <!-- 文章的内容 -->
        <div class="content-box">
            <!-- 在封装组件时,为预留的 <slot> 提供属性对应的值,
                这种用法叫做 作用域插槽 -->
            <slot name="content" msg="Hello Article.vue" :user="userinfo"></slot>
        </div>

        <!-- 文章的作者 -->
        <div class="footer-box">
            <slot name="author"></slot>
        </div>


    </div>
</template>

<script>
export default {
    name: 'Article',
    data() {
        return {
            // 用户信息对象
            userinfo:{
                name:'k',
                age:18
            }
        }
    },
}
</script>

<style lang="less" scoped>
.article-container {

    >div{
        min-height: 150px;
    }

    .header-box {
        background-color:#ffc7c9;
    }

    .content-box {
        background-color: #a9d5e8;
    }

    .footer-box {
        background-color: #ffa776;
    }
}
</style>

App.vue

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

    <Article>
      <template #title>
        <h2>震惊!...</h2>
      </template>

      <template #content="{ msg, user }">
        <div>
          <p>某高校一男子...</p>
          <p>荣获...</p>
          <p>牛逼...</p>
          <p>{{ msg }}</p>

          <p>{{ user.name }}</p>
          <p>{{ user.age }}</p>
        </div>
      </template>

      <template #author>
        <div>作者:彬锅果</div>
      </template>

    </Article>

    <div class="box" style="display:none;">
      <!-- 渲染 Left 组件和 Right 组件 -->
      <Left>
        <!-- 默认的情况下,在使用组件的时候,提供的内容都会被填充到名字为 default 的插槽之中 -->
        <!-- 1.如果要把内容填充到指定名称的插槽中,需要使用 v-slot:名称 这个指定-->
        <!-- 2.v-slot: 后面要跟上插槽的名称 -->
        <!-- 3.v-slot: 指定不能直接用在元素身上,必须用到 template 标签-->
        <!-- 4.template 这个标签,它是一个虚拟的标签,只起到包裹性质的作用,不会被渲染为任何实质性的 html 元素 -->
        <!-- 5.v-slot:指定的简写形式是 # -->
        <template #defualt>
          <p>这个在 Left 组件的内容区域声明的 p 标签</p>
        </template>
      </Left>
    </div>

  </div>
</template>

<script>

import Left from '@/components/Left.vue'
import Article from '@/components/Article.vue'

export default {
  components: {
    Left,
    Article

  }
}
</script>

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

.box {
  display: flex;
}
</style>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LMSGiWhZ-1671884043155)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221222171934551.png)]

17.自定义指令

17.1.什么是自定义指令

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

17.2.自定义指令的分类

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

私有自定义指令

全局自定义指令

17.2.1.私有自定义指令

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

代码示例:

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

<script>

export default {

  // 私有自定义指令的节点
  directives:{
    // 定义名为 color 的指令,指向一个配置对象
    color:{
      // 当指令第一次被绑定到元素上的时候,会立即触发 bind 函数
      // 形参中的 el 表示当前指令所绑定到的那个 DOM 对象
      bind(el){
        el.style.color='red'
      }
    }
  },

}
</script>

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

.box {
  display: flex;
}
</style>

17.2.1.1.通过 binding 获取指令的参数值

在声明自定义指令时,可以通过形参中的第二个参数,来接收指令的参数值

代码示例:

<template>
  <div class="app-container">
    <h1 v-color="color">App 根组件</h1>
    <p v-color="'red'">测试</p>
    <hr />
  </div>
</template>

<script>

export default {

  data() {
    return {
      color: 'blue'
    }
  },

  // 私有自定义指令的节点
  directives: {
    // 定义名为 color 的指令,指向一个配置对象
    color: {
      // 当指令第一次被绑定到元素上的时候,会立即触发 bind 函数
      // 形参中的 el 表示当前指令所绑定到的那个 DOM 对象
      bind(el,binding) {
        el.style.color = binding.value
      }
    }
  },

}
</script>

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

.box {
  display: flex;
}
</style>

17.2.1.2.update 函数

​ bind 函数只调用 1 次:当指令第一次绑定到元素时调用,当 DOM 更新时 bind 函数不会被触发。 update 函

数会在每次 DOM 更新时被调用。

代码示例:

<template>
  <div class="app-container">
    <h1 v-color="color">App 根组件</h1>
    <p v-color="'red'">测试</p>

    <button @click="color='green'">改变 color 的颜色值</button>
    <hr />


  </div>
</template>

<script>

export default {

  data() {
    return {
      color: 'blue'
    }
  },

  // 私有自定义指令的节点
  directives: {
    // 定义名为 color 的指令,指向一个配置对象
    color: {
      // 当指令第一次被绑定到元素上的时候,会立即触发 bind 函数
      // 形参中的 el 表示当前指令所绑定到的那个 DOM 对象
      bind(el,binding) {
        el.style.color = binding.value
      },

      //  每次 DOM 更新的时候,会触发update
      update(el, binding) {
        el.style.color = binding.value
      }

    }
  },

}
</script>

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

.box {
  display: flex;
}
</style>

17.2.1.3.函数简写

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-thKYm7Be-1671884043155)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221222204725907.png)]

17.2.2.全局自定义指令

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

代码示例:

main.js

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

Vue.config.productionTip = false


// 全局自定义指令
/* Vue.directive('color', {
  bind(el, binding) {
    el.style.color = binding.value
  },

  update(el, binding) {
    el.style.color = binding.value
  }
}) */

Vue.directive('color', function (el, binding) {
  el.style.color = binding.value
})

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

Article.vue

<template>
    <div class="article-container">

        <h3 v-color="'red'">Article 组件</h3>

        <!-- 文章的标题 -->
        <div class="header-box">
            <slot name="title"></slot>
        </div>

        <!-- 文章的内容 -->
        <div class="content-box">
            <!-- 在封装组件时,为预留的 <slot> 提供属性对应的值,
                这种用法叫做 作用域插槽 -->
            <slot name="content" msg="Hello Article.vue" :user="userinfo"></slot>
        </div>

        <!-- 文章的作者 -->
        <div class="footer-box">
            <slot name="author"></slot>
        </div>


    </div>
</template>

<script>
export default {
    name: 'Article',
    data() {
        return {
            // 用户信息对象
            userinfo:{
                name:'k',
                age:18
            }
        }
    },
}
</script>

<style lang="less" scoped>
.article-container {

    >div{
        min-height: 150px;
    }

    .header-box {
        background-color:#ffc7c9;
    }

    .content-box {
        background-color: #a9d5e8;
    }

    .footer-box {
        background-color: #ffa776;
    }
}
</style>

18.路由

18.1.前端路由的概念与原理

18.1.1.什么是路由

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

18.1.2.SPA 与前端路由

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

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

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

18.1.3.什么是前端路由

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

18.1.4.前端路由的工作方式

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lu7I60Dw-1671884043155)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223161106984.png)]

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

18.1.4.实现简易的前端路由

步骤1:通过 标签,结合 comName 动态渲染组件。示例代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IqzJ4wif-1671884043156)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223162440530.png)]

步骤2:在 App.vue 组件中,为 链接添加对应的 hash 值:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YZzSeGC0-1671884043156)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223162500684.png)]

步骤3:在 created 生命周期函数中,监听浏览器地址栏中 hash 地址的变化,动态切换要展示的组件的名称:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x5d7Jg50-1671884043156)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223162512808.png)]

18.2.vue-router 的基本使用

18.2.1.什么是 vue-router

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

中组件的切换。

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

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

  1. 安装 vue-router 包
  2. 创建路由模块
  3. 导入并挂载路由模块
  4. 声明路由链接和占位符
18.2.2.1. 在项目中安装 vue-router

在 vue2 的项目中,安装 vue-router 的命令如下:

npm install vue-router@3.5.2 -S
18.2.2.2.创建路由模块

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

// src/router/index.js 就是当前项目的路由模块

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

// 2.调用 Vue.use() 函数,把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 用来为 vue项目安装插件
Vue.use(VueRouter)

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

// 4.向外共享路由实例对象
// 只要在外使用 import 导入该路由模块就可以拿到此路由实例对象
export default router
18.2.2.3.导入并挂载路由模块

在 src/main.js 入口文件中,导入并挂载路由模块。示例代码如下:

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

// 导入路由模块,目的:拿到路由的实例对象
// 在进行模块化导入的时候,如果给定的是文件夹,则默认导入这个文件夹下 名字叫做 index.js 的文件
import router from "@/router/index.js"

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


Vue.config.productionTip = false

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kTrrrSc9-1671884043156)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223164249752.png)]

18.2.2.4.声明路由链接和占位符

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

未使用路由链接:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2qYqFNWP-1671884043157)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223165917018.png)]

<template>
  <div class="app-container">
    <h1>App2 组件</h1>

    <a href="#/home">首页</a>
    <a href="#/movie">电影</a>
    <a href="#/about">关于</a>
    <hr/>

    <!-- 只要在项目中安装和配置了 vue-router,就可以使用 router-view 这个组件了 -->
    <!-- 它的作用:占位符 -->
    <!-- 接着就到 src/route/index.js 中定义 "hash地址" 与 "组件" 之间的对应关系 -->
    <router-view></router-view>

  </div>
</template>

使用路由链接

<template>
  <div class="app-container">
    <h1>App2 组件</h1>

  <!--   <a href="#/home">首页</a>
    <a href="#/movie">电影</a>
    <a href="#/about">关于</a> -->

    <!-- 当安装了配置了 vue-router 后,就可以使用 router-link 来代替普通的 a 链接了 -->

    <router-link to="/home">首页</router-link>
    <router-link to="/movie">电影</router-link>
    <router-link to="/about">关于</router-link>
    <hr/>

    <!-- 只要在项目中安装和配置了 vue-router,就可以使用 router-view 这个组件了 -->
    <!-- 它的作用:占位符 -->
    <!-- 接着就到 src/route/index.js 中定义 "hash地址" 与 "组件" 之间的对应关系 -->
    <router-view></router-view>

  </div>
</template>

18.2.2.5.声明路由的匹配规则

在 src/router/index.js 路由模块中,通过 routes 数组声明路由的匹配规则。示例代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rBxEfT5r-1671884043157)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223165819581.png)]

// src/router/index.js 就是当前项目的路由模块

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


// 导入需要的组件
import Home from '@/components/Home.vue'
import Movie from '@/components/Movie.vue'
import About from '@/components/About.vue'

// 2.调用 Vue.use() 函数,把 VueRouter 安装为 Vue 项目的插件
// Vue.use() 用来为 vue项目安装插件
Vue.use(VueRouter)

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

// 4.向外共享路由实例对象
// 只要在外使用 import 导入该路由模块就可以拿到此路由实例对象
export default router
18.2.2.6.路由重定向 redirect

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

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

const router = new VueRouter({
  // routes 是一个数组,作用:定义 "hash地址" 与 "组件" 之间的对应关系
  routes: [


    // redirect 路由重定向,当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
    {
      path: '/',
      redirect: '/home'
    },
    // 路由规则
    {
      path: '/home',
      component: Home
    },
    {
      path: '/movie',
      component: Movie
    },
    {
      path: '/about',
      component: About
    },
  ]
})

18.3.vue-router 的常见用法

18.3.1.嵌套路由

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MwaZ6t3A-1671884043157)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223172127377.png)]

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

在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符。示例代码如下:

<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>
18.3.1.2.通过 children 属性声明子路由规则

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CdwiUgi4-1671884043157)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223172626229.png)]

18.3.1.3.默认子路由

使用 redict 方式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-choigdMk-1671884043157)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223172846308.png)]

使用默认子路由

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-27mkrEAR-1671884043158)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223173244031.png)]

18.3.2.动态路由

18.3.2.1. 动态路由匹配

思考:有如下 3 个路由链接:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QxHlsnQc-1671884043158)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223174529588.png)]

定义如下 3 个路由规则,是否可行?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UxuPZrKJ-1671884043158)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223174544043.png)]

缺点:路由规则的复用性差。

18.3.2.2.动态路由的概念

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

在 vue-router 中使用英文的冒号(:)来定义路由的参数项。示例代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-02wF6n69-1671884043158)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223174620622.png)]

18.3.2.3.$route.params 参数对象

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

<template>
  <div class="movie-container">
    <!-- this.$route 是路由的 “参数对象 ” -->
    <h3>Movie 组件 --- {{ this.$route.params.id }}</h3>

    <button @click="showThis">打印 this</button>
  </div>
</template>

<script>
export default {
  name: 'Movie',
  methods: {
    showThis(){
      console.log(this);
    }
  },
}
</script>
18.3.2.4.使用 props 接收路由参数

为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参。示例代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aJfGfES6-1671884043159)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223175148782.png)]

18.3.3.声明式导航 & 编程式导航

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

  • 普通网页中点击 链接、vue 项目中点击 都属于声明式导航

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

  • 普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
18.3.3.1.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 地址,从而展示对应的组件页面。示例代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cv945lCD-1671884043159)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223202746823.png)]

$router.replace

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

push 和 replace 的区别:

  1. push 会增加一条历史记录
  2. replace 不会增加历史记录,而是替换掉当前的历史记录

$router.go

调用 this.$router.go() 方法,可以在浏览历史中前进和后退。示例代码如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7LZ14E31-1671884043159)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223202836171.png)]

$router.go 的简化用法

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

  • $router.back()

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

    • 在历史记录中,前进到下一个页面

代码示例:

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

    <hr>

    <button @click="pushMovie1">通过 push 跳转到 电影1 页面</button>

    <button @click="replaceMovie1">通过 replace 跳转到 电影1 页面</button>

    <button @click="goBack">通过 go 后退</button>

    <!-- 在行内使用编程式导航跳转的时候,this 必须要省略,否则会报错 -->
    <button @click="$router.back()">back 后退</button>
    <button @click="$router.forward()">forward 前进</button>
  </div>
</template>

<script>
export default {
  name: 'Home',

  methods: {
    pushMovie1(){
      // 通过编程式导航 api ,导航跳转到指定页面
      this.$router.push('/movie/1')
    },

    replaceMovie1(){
      this.$router.replace('/movie/1')
    },

    goBack(){
      // go(-1) 表示后退一层
      // 如果后退层数超过上限,则原地不动
      this.$router.go(-1)
    }

  },
}
</script>

18.3.4.导航守卫

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S5vvF3g0-1671884043159)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223203002589.png)]

18.3.4.1.全局前置守卫

​ 每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行

访问权限的控制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YNRTkS5w-1671884043159)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223204137337.png)]

18.3.4.2.守卫方法的 3 个形参

全局前置守卫的回调函数中接收 3 个形参,格式为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uzkLqvmP-1671884043160)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223204208538.png)]

18.3.4.3.next 函数的 3 种调用方式

参考示意图,分析 next 函数的 3 种调用方式最终导致的结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0waQUXCZ-1671884043160)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223204427569.png)]

  1. 当前用户拥有后台主页的访问权限,直接放行:next()
  2. 当前用户没有后台主页的访问权限,强制其跳转到登录页面:next(‘/login’)
  3. 当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)
18.3.4.*.控制后台主页的访问权限

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vKu3HTxO-1671884043160)(C:\Users\30666\AppData\Roaming\Typora\typora-user-images\image-20221223205913578.png)]

18.4.后台管理案例

ueRouter)

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

// 4.向外共享路由实例对象
// 只要在外使用 import 导入该路由模块就可以拿到此路由实例对象
export default router




##### 18.2.2.6.路由重定向 redirect

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

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



```js
const router = new VueRouter({
  // routes 是一个数组,作用:定义 "hash地址" 与 "组件" 之间的对应关系
  routes: [


    // redirect 路由重定向,当用户访问 / 的时候,通过 redirect 属性跳转到 /home 对应的路由规则
    {
      path: '/',
      redirect: '/home'
    },
    // 路由规则
    {
      path: '/home',
      component: Home
    },
    {
      path: '/movie',
      component: Movie
    },
    {
      path: '/about',
      component: About
    },
  ]
})

18.3.vue-router 的常见用法

18.3.1.嵌套路由

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

[外链图片转存中…(img-MwaZ6t3A-1671884043157)]

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

在 About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符。示例代码如下:

<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>
18.3.1.2.通过 children 属性声明子路由规则

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

[外链图片转存中…(img-CdwiUgi4-1671884043157)]

18.3.1.3.默认子路由

使用 redict 方式

[外链图片转存中…(img-choigdMk-1671884043157)]

使用默认子路由

[外链图片转存中…(img-27mkrEAR-1671884043158)]

18.3.2.动态路由

18.3.2.1. 动态路由匹配

思考:有如下 3 个路由链接:

[外链图片转存中…(img-QxHlsnQc-1671884043158)]

定义如下 3 个路由规则,是否可行?

[外链图片转存中…(img-UxuPZrKJ-1671884043158)]

缺点:路由规则的复用性差。

18.3.2.2.动态路由的概念

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

在 vue-router 中使用英文的冒号(:)来定义路由的参数项。示例代码如下:

[外链图片转存中…(img-02wF6n69-1671884043158)]

18.3.2.3.$route.params 参数对象

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

<template>
  <div class="movie-container">
    <!-- this.$route 是路由的 “参数对象 ” -->
    <h3>Movie 组件 --- {{ this.$route.params.id }}</h3>

    <button @click="showThis">打印 this</button>
  </div>
</template>

<script>
export default {
  name: 'Movie',
  methods: {
    showThis(){
      console.log(this);
    }
  },
}
</script>
18.3.2.4.使用 props 接收路由参数

为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参。示例代码如下:

[外链图片转存中…(img-aJfGfES6-1671884043159)]

18.3.3.声明式导航 & 编程式导航

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

  • 普通网页中点击 链接、vue 项目中点击 都属于声明式导航

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

  • 普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
18.3.3.1.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 地址,从而展示对应的组件页面。示例代码如下:

[外链图片转存中…(img-cv945lCD-1671884043159)]

$router.replace

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

push 和 replace 的区别:

  1. push 会增加一条历史记录
  2. replace 不会增加历史记录,而是替换掉当前的历史记录

$router.go

调用 this.$router.go() 方法,可以在浏览历史中前进和后退。示例代码如下:

[外链图片转存中…(img-7LZ14E31-1671884043159)]

$router.go 的简化用法

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

  • $router.back()

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

    • 在历史记录中,前进到下一个页面

代码示例:

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

    <hr>

    <button @click="pushMovie1">通过 push 跳转到 电影1 页面</button>

    <button @click="replaceMovie1">通过 replace 跳转到 电影1 页面</button>

    <button @click="goBack">通过 go 后退</button>

    <!-- 在行内使用编程式导航跳转的时候,this 必须要省略,否则会报错 -->
    <button @click="$router.back()">back 后退</button>
    <button @click="$router.forward()">forward 前进</button>
  </div>
</template>

<script>
export default {
  name: 'Home',

  methods: {
    pushMovie1(){
      // 通过编程式导航 api ,导航跳转到指定页面
      this.$router.push('/movie/1')
    },

    replaceMovie1(){
      this.$router.replace('/movie/1')
    },

    goBack(){
      // go(-1) 表示后退一层
      // 如果后退层数超过上限,则原地不动
      this.$router.go(-1)
    }

  },
}
</script>

18.3.4.导航守卫

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

[外链图片转存中…(img-S5vvF3g0-1671884043159)]

18.3.4.1.全局前置守卫

​ 每次发生路由的导航跳转时,都会触发全局前置守卫。因此,在全局前置守卫中,程序员可以对每个路由进行

访问权限的控制

[外链图片转存中…(img-YNRTkS5w-1671884043159)]

18.3.4.2.守卫方法的 3 个形参

全局前置守卫的回调函数中接收 3 个形参,格式为:

[外链图片转存中…(img-uzkLqvmP-1671884043160)]

18.3.4.3.next 函数的 3 种调用方式

参考示意图,分析 next 函数的 3 种调用方式最终导致的结果:

[外链图片转存中…(img-0waQUXCZ-1671884043160)]

  1. 当前用户拥有后台主页的访问权限,直接放行:next()
  2. 当前用户没有后台主页的访问权限,强制其跳转到登录页面:next(‘/login’)
  3. 当前用户没有后台主页的访问权限,不允许跳转到后台主页:next(false)
18.3.4.*.控制后台主页的访问权限

[外链图片转存中…(img-vKu3HTxO-1671884043160)]

18.4.后台管理案例

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/112929.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Axios(三)

目录 1.Axios的默认配置 2.Axios创建Ajax实例对象发送请求 3.Axios拦截器 4.Axios取消请求 5.Axios文件结构说明 6.Axios创建过程 7.Axios对象创建过程模拟实现 8.Axios发送请求过程详解 9.模拟实现Axios发送请求 1.Axios的默认配置 <!doctype html> <html …

QT系列第7节 标准对话框使用

QT编程中对话框作为最常用的窗口经常被使用&#xff0c;本节介绍一些QT常用的标准对话框使用。 目录 1.选择单文件 2.选择多文件 3.选择目录 4.文件存储 5.选择颜色 6.选择字体 7.输入字符换/整数/浮点数/条目 8.消息对话框 9.进度对话框 10.向导对话框 1.选择单文件…

【圣诞节限定】今天教你如何用Html+JS+CSS绘制3D动画圣诞树

一、前言 应CSDN的邀请&#xff0c;这次给大家展示一波&#xff0c;如何用H5技术绘制3D圣诞树。 二、创意名 只采用简单的HtmlJSCSS 技术绘制。 三、效果展示 圣诞树修过如下&#xff1a; 四、编码实现 将源码复制保存到html中打开即可。 <!DOCTYPE html> <html lang…

ChatGPT与BimAnt的1小时对话实录【数字孪生】

本文为BimAnt和ChatGPT对数字孪生相关问题的解答&#xff0c;感觉这个AI真的已经“懂”了很多东西&#xff0c;让人恍惚间忘了是在和bot对话。 BimAnt&#xff1a;hello ChatGPT&#xff1a;Hello! How can I help you today? BimAnt&#xff1a;can you speak chinese&am…

鲸鱼优化算法及其在无线网络资源分配中的应用(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 鲸鱼优化算法&#xff08;Whale Optimization Algorithm&#xff09;是一种新兴的智能优化算法&#xff0c;在2016年提出。算法…

JWT渗透与攻防(二)

目录 前言 JWT漏洞演示之CTFhub&#xff08;一&#xff09; JWT漏洞演示之CTFhub&#xff08;二&#xff09; 前言 我们在之前的文章中已经讲解过了JWT漏洞相关的原理和利用&#xff0c;今天我们就通过这篇文章再来了解一下JWT的漏洞。 JWT漏洞演示之CTFhub&#xff08;一&…

Linux-信号

文章目录信号准备知识&#xff1a;信号产生的方式实验验证&#xff1a;9号信号是不可被捕捉&#xff08;自定义的&#xff09;信号处理&#xff1a;信号产生前&#xff1a;信号产生的方式&#xff1a;键盘实验显示&#xff1a;段错误&#xff08;野指针&#xff09;实验验证&am…

SSRF ME XCTF

题目 就是一个验证框和URL框&#xff0c;两个都必须有参数 解法 验证码 做一个粗略的脚本&#xff0c;一般验证码都是数字&#xff0c;所以直接开md5&#xff1a; def cmpcapt(substr):for i in range(1,100001):md5 hashlib.md5(str(i).encode(utf-8))hexmd5 md5.hexd…

机器学习任务功略

目录 机器学习的结构机器学习攻略 训练集loss较大 model bias问题optimization问题 gradient descent的问题解决 如何区分训练集loss大是model bias还是优化器的问题 测试集loss较大 overfitting过拟合 为什么会有overfitting解决过拟合的方法 训练集与测试集的不匹配 如何选择…

Linux内核基础篇——常用调试技巧汇总

文章目录printk动态输出BUG()和BUG_ON()dump_stack()devmemprintk printk共有8个等级&#xff0c;从0-7&#xff0c;等级依次降低。 打印等级可以通过修改/proc/sys/kernel/printk来改变。 查看printk等级&#xff1a; cat /proc/sys/kernel/printk 7 4 1 7打开内核所有打印…

2022圣诞树(C语言摇钱树版本)

逐梦编程&#xff0c;让中华屹立世界之巅。 简单的事情重复做,重复的事情用心做,用心的事情坚持做&#xff1b; 文章目录前言一、个人感悟二、圣诞树由来三、圣诞树发展历史演变四、常见的圣诞树种类五、摇钱圣诞树效果展示六、实现思路七、编码实现总结新壁纸前言 时光飞逝&a…

前端工程师必须掌握—《Webpack教程》

Webpack 学习视频地址 文章目录Webpack1.webpack基础1.1.初始化隔行变色的案例1.2.安装和配置webpack1.2.1.安装webpack1.2.2.配置webpack1.2.3.了解mode可选值的应用场景1.2.4.自定义打包的入口和出口2.插件2.1.安装和配置webpack-dev-server2.1.1.安装webpack-dev-server2.1…

SpringCache+Redis的整合(微服务)

缓存作用&#xff1a; 举个例子&#xff1a;在我们程序中&#xff0c;很多配置数据&#xff08;例如一个商品信息、一个白名单、一个第三方客户的回调接口&#xff09;&#xff0c;这些数据存在我们的DB上&#xff0c;数据量比较少&#xff0c;但是程序访问很频繁&#xff0c;…

Prometheus(十一)Grafana告警

主要概念和特点 关键概念或特征含义Data sources for Alerting 告警的数据源配置从哪里查询到告警信息数据Provisioning for Alerting 告警的配置使用文件等方式配置警报资源&#xff0c;已经管理警报资源Scheduler 调度器评估告警规则&#xff0c;将其视为定期对数据源运行查…

ubuntu虚拟机修改静态ip

我的是&#xff1a;ubuntu 20.04&#xff0c;所以 第一步 sudo vi /etc/netplan/01-network-manager-all.yaml第二步 gateway4已经弃用了&#xff0c;换成下面的&#xff1a; network:version: 2renderer: NetworkManagerethernets:ens33:addresses: [192.168.125.132/24]r…

内核比较: 2.6 内核中改进了内存管理

随着 Linux 内核的发展和成熟&#xff0c;更多的用户期待着 Linux 可以运行非常大的系统来处理科学分析应用程序或者海量数据库。这些企业级的应用程序通常需要大量的内存才能好好运行。2.4 Linux 内核有识别相当大数量的内存的功能&#xff0c;但是 2.5 内核发生了很多改变&am…

docker高级篇第三章-dockerfile案例之制作自己的centos镜像

在上一篇文章中《Dockerfile介绍及常用保留指令》,我们介绍了Dockerfile是什么以及Dockerfile常用的保留字段。熟悉了这些之后,有没有想自己动手写一个Dockerfile呢?本文咱们就实战自己Dockerfile。 案例需求: 我们以远程仓库的centos为模板,制作出代用vim\ifconfig\jav…

Qt实现表格树控件-自绘树节点虚线

一、开心一刻 一程序员第一次上女朋友家她妈板着脸问 &#xff1a;你想娶我女儿&#xff0c;有多少存款&#xff1f; 程序员低了下头&#xff1a;五百&#xff01; 她妈更鄙视了&#xff1a;才五百块&#xff0c;买个厕所都不够&#xff01; 程序员忙说&#xff1a;不是人民币&…

Android混淆技术综述

1. 引入 大量的恶意软件都使用了混淆技术来逃检测。查了下Android混淆技术&#xff0c;看了如下两篇资料&#xff1a; Understanding Android Obfuscation Techniques: A Large-Scale Investigation in the Wildhttps://github.com/ClaudiuGeorgiu/Obfuscapk 对Android的混淆…

logging日志管理

1.日志作用 不管是在项目开发还是测试过程中&#xff0c;项目运行一旦出现问题日志信息就非常重要了。日志是定位问题的重要手段&#xff0c;就像侦探人员要根据现场留下的线索来推断案情。 2.日志级别 脚本运行会有很多的情况&#xff0c;比如调试信息、报错异常信息等。日…