我是靠谱客的博主 含蓄乌冬面,这篇文章主要介绍vue3 学习笔记简介环境搭建计数器案例命令式和声明式编程MVVM模型template属性data属性methods属性this的指向VSCode代码片段Musache语法基本指令重要指令v-ifv-showv-for数组更新检测VNode计算属性-computedWatch综合案例_书籍购物车v-model组件化开发组件通信插槽动态组件Webpack的代码分包&异步组件$refs的使用生命周期组件的v-modelVue3过渡&动画实现animate.css动画gsap库Mixinextends,现在分享给大家,希望可以做个参考。

文章目录

  • 简介
    • 组件化
    • 类型检测
    • 技术栈
    • 学习方法
    • 什么是渐进式框架
    • vue的本质
    • 基本思路
      • 链式调用
    • 调试工具
  • 环境搭建
    • CDN方式引入
      • 什么是CDN?
      • 引入
    • 通过vue.js文件引入
      • 下载
      • 引入
  • 计数器案例
    • 原生JS实现
    • VUE实现计数器
  • 命令式和声明式编程
    • 声明式编程
      • 理解
    • 命令式编程和声明式编程的区别
  • MVVM模型
    • MVC模型
    • MVVM模型
  • template属性
    • 挂载
    • 分离式写法_script标签
    • 分离式写法_template标签
    • template标签的特点
    • template中的根元素
  • data属性
    • vue2和vue3中的区别
  • methods属性
  • this的指向
    • 普通函数
    • 箭头函数
    • 为什么vue的methods不要使用箭头函数?
    • 总结
  • VSCode代码片段
  • Musache语法
    • 正确用法
    • 错误用法
  • 基本指令
    • v-once
    • v-text
    • v-html
    • v-pre
    • v-cloak
      • cloak单词的意思
  • 重要指令
    • v-bind
      • 属性绑定
        • 属性绑定_对象形式
          • 多值情况
          • 对象放到一个单独的属性中
          • 将对象放到methods中返回
      • 属性绑定_数组形式
      • 样式绑定_对象形式
      • 样式绑定_数组形式
      • 属性名称绑定
      • 属性名和属性值绑定
    • v-bind(same)
    • v-on
      • 基本使用
      • 绑定一个对象
      • 传递参数
      • 修饰符
        • stop修饰符
        • [按键]修饰符
  • v-if
    • 渲染原理
    • 基本使用
    • 多个条件
    • template和v-if的结合使用
      • 为什么需要和v-if结合使用
      • v-if结合使用例子
  • v-show
    • 基本使用
    • v-show和v-if的区别
    • v-show和v-if如何选择
  • v-for
    • 基本使用
    • v-for和template搭配使用
    • 搭配绑定key
  • 数组更新检测
    • push()方法
  • VNode
  • 计算属性-computed
    • 为什么会有计算属性?
    • 基本使用
    • 使用methods实现的数据二次封装和计算属性的区别
    • 修改计算属性
  • Watch
    • 基本使用
    • 深度监听
      • 反例_语法糖形式
      • 反例_全写形式
      • 正例
      • 立即执行
    • 针对对象中某个属性的监听
    • create()创建监听器
  • 综合案例_书籍购物车
    • 按钮禁用
    • 问题_多个tbody标签
      • 问题
      • 解决
    • 问题_有时能完全删除,有时不能
      • 问题
      • 答案
    • 最终
  • v-model
    • 原始方法实现v-model
    • v-model语法糖实现
    • 绑定基本组件
      • 绑定textarea
      • 绑定单选框
      • 绑定多选框
      • 绑定单选按钮
      • 绑定下拉框
    • 修饰符
      • lazy修饰符
      • number修饰符
      • trim修饰符
  • 组件化开发
    • 全局组件
      • 多个组件
      • 组件的命名
    • 局部组件
    • 基于Vue CLI组件化开发
      • 使用Vue CLI创建项目
      • 创建一个总的组件
      • 组件拆分
      • 进一步组件拆分
    • 组件的CSS作用域
      • 总结
  • 组件通信
    • 父传子
      • 属性形式_字符串数组
      • 属性形式_对象
        • required属性
        • default属性
        • 其他写法
          • 为什么对象的默认值必须通过一个工厂函数获取?
    • 非Prop的Attribute
      • 单根结点
        • 传递一个没有定义的属性怎么样
        • 如何把属性绑定到目标的标签上面
        • 如何去掉这个子组件根元素的属性
      • 多根结点
    • 子传父
      • 注册事件_数组形式
        • 计数器案例_无参数传递
        • 计数器案例_传递参数
      • 注册事件_对象形式
    • 组件通信案例
    • 非父子组件之间的通信
      • Provide/Inject
        • 背景
        • 通信
        • 问题_长辈组件在自己中是否使用provide中的数据
        • 问题_长辈组件如何拿到自己data中的数据信息然后传给子孙组件
          • 问题背景
          • 原因分析
          • 解决方案
        • 问题_长辈组件传给子孙组件的是动态数据吗
            • 解决方案_动态传递数据
      • Mitt全局事件总线
        • 安装mitt库
        • 背景
        • 多事件监听
        • 事件取消
  • 插槽
    • 插槽的基本使用
    • 多个插槽
    • 具名插槽
    • 动态具名插槽
    • 具名插槽的缩写
    • 渲染作用域
    • 作用域插槽
      • 总结
  • 动态组件
    • 按钮切换案例
    • 按钮切换案例-动态组件
    • 动态组件传递参数&发送事件
      • 参数传递
        • 如何传递的参数由默认的字符串类型转换成数字类型
      • 监听事件
    • keep-alive(缓存组件)
      • 背景案例
      • 基本使用案例
      • keep-alive的属性
        • include
  • Webpack的代码分包&异步组件
    • 为什么需要分包
    • 如何分包
      • JS中的代码分包
      • Vue组件中实现异步组件(代码分包)
        • 工厂函数形式
        • 对象形式
          • 对象形式中常用的属性
    • 异步组件和suspense
  • $refs的使用
    • vue中的DOM操作
      • 获取元素
      • 获取组件
      • `$parent`和`$root`
  • 生命周期
    • 生命周期流程
    • 缓存组件的生命周期
  • 组件的v-model
    • 基本使用
    • 优化
    • 绑定多个v-model
  • Vue3过渡&动画实现
    • 认识动画
    • 案例_hello world的显示和隐藏
      • 没有动画效果
      • 加入动画效果
    • transition组件的原理
    • 过渡动画class
      • class的添加或删除的时机
      • class的name命名规则
    • animation动画
    • transition的属性
      • type属性
      • duration属性
      • mode属性(常用多个元素切换)
      • appear属性
  • animate.css动画
    • 介绍
    • 如何使用
    • 安装animate.css
    • 导入animate.css
    • 基本使用
    • 自定义过渡class_使用animate中的类
      • 小修改
    • 自定义过渡class_使用自己定义的类
  • gsap库
    • 介绍
    • 如何使用
    • 安装gsap库
    • JavaScript钩子
    • 基本使用gsap
    • gsap实现数字增长效果
    • 列表的过渡
    • 列表的交错过渡
    • 数字洗牌
  • Mixin
    • 认识Mixin
    • Mixin的基本使用
    • Mixin的合并规则
    • 全局混入Mixin
  • extends
  • Options API的弊端
  • Composition API
    • setup函数的参数
      • props
        • 通过props拿到父组件传递过来的数据
      • context
        • 非prop的attribute
    • setup函数的返回值
      • Reactive API_动态绑定数据
      • Ref API
        • 自动解包的层数
    • readonly
    • Reactive判断的API
    • toRefs
      • 使用结构赋值存在的问题
      • 使用toRefs解决解构赋值的问题
    • toRef
    • ref其他的API
      • shallowRef&triggerRef
        • 问题背景
        • 目标需求
          • 使用shallowRef后没有反应的代码
          • shallowRef需要搭配triggerRef
      • customRef
  • computed
    • 基本使用_传入一个getter函数
    • 传入一个对象,包含getter和setter
  • 侦听数据的变化_watchEffect
    • watchEffect
    • watchEffect的停止侦听
    • watchEffect清除副作用
    • setup中使用ref
  • 侦听数据的变化_watch
    • 侦听reactive对象
      • 第一种传入方式'![image-20210907151337906](https://file2.kaopuke.com:8081/files_image/2023061023/2c18c2475cc220a0b1a3e206bb4f321c.png)'
      • 第二种传入方式'![image-20210907151510505](https://file2.kaopuke.com:8081/files_image/2023061023/76a8a889e8ac5020dca324a8d0f891d2.png)'
        • 结构赋值
    • 侦听ref对象
    • 侦听多个数据
    • 侦听数组
    • 深层侦听
  • 生命周期钩子
  • Provide和Inject
    • 基本使用_父组件向子组件传递数据
    • 基本使用_动态数据传递
      • 改进_数据单向流
  • Composition API练习
    • 计数器案例
    • useTitle案例
    • 综合案例
      • 代码整合优化
  • jsx
    • 基本使用
    • 计数器案例
    • 引入子组件
      • 基本使用
      • 使用插槽
  • VueRouter
    • 概念
      • 路由器
      • 前后端分离阶段
    • URL的hash
    • HTML5的History
      • e.preventDefault()
      • history.pushState({}, "", href);
      • history.replaceState({},'',href)
    • 认识vue-router
    • 安装vue-router
    • 路由的使用步骤
    • 路由的默认路径
    • router-linker
      • active-class&exact-active-class属性
    • 路由懒加载
      • 分包_魔法注释
    • 路由的其他属性
    • 动态路由_参数传递
      • 如何在setup()中的拿到动态路由传递的参数
      • 多参数传递
    • NotFound
      • $route.params.pathMatch
      • 匹配规则加*
    • 路由的嵌套
    • 编程式控制路由
    • router-link的v-slot
      • v-slot的基本使用
      • custom的作用和如何通过插槽给内部传值
      • navigate导航函数
      • isActive
    • router-view的v-slot
    • 动态添加路由
      • 添加一级路由
      • 添加二级路由
    • 动态删除路由
    • 路由导航守卫
      • 登录守卫功能
  • Vuex的状态管理
    • 批量引入Vuex中的数据_mapState
    • setup中如何使用mapState
    • setup中使用mapState的封装
    • Vuex的计算属性_getters
      • 使用state中的数据
      • 使用getters中的数据
      • getters返回一个函数
      • 批量拿到getters中的数据
      • 封装批量拿到getters中的数据
        • hooks>useGetters.js
    • mapState 和mapGetters的综合封装
      • 图解
      • hooks>useMapper.js
      • hooks>useState.js
      • hooks>useGetters.js
      • hooks>index.js
      • 引用useGetters的文件:Home.vue
    • Mutation
      • 无参数传递_计数器
        • App.vue
      • 有参数传递_计数器
        • 图解
          • 传递普通参数
          • 传递对象类型参数
          • 两种提交风格
        • App.vue
        • store>index.js
      • 常量类型
        • 图解
        • store>index.js
        • store>mutation-types.js
        • App.vue
    • mapMutation
      • 图解
      • App.vue
    • actions
      • 基本使用
      • 基本使用-异步请求-一秒后加一的计数器
        • 图解
        • App.vue
        • store>index.js

简介

组件化

组件化开发最最重要的一点,就是复用.

image-20210720190301214

类型检测

为什么一定要有类型检测呢?

简而言之,就是错误发现越早越好.

image-20210720190611546


  • JavaScript的类型错误只有在运行阶段才能发现

image-20210720190813935

技术栈

vue项目需要掌握的技术栈

image-20210720191004043

学习方法

image-20210720191046562

什么是渐进式框架

一点点引入和使用

image-20210720191150539

vue的本质

本质就是一个JavaScript库,就当做一个JS文件引入就好了

image-20210720191258814

基本思路

image-20210720192510061


传入一个对象,返回一个对象,将返回的对象挂在到dom元素上面

image-20210720192833514

链式调用

链式调用更简单,更常用.

image-20210720193019418

调试工具

[shell-chrome.rar - 快捷方式.lnk](…ae_文本文件shell-chrome.rar - 快捷方式.lnk)

环境搭建

image-20210720191429242

CDN方式引入

什么是CDN?

个人理解,有点像P2P的下载模式,有点就近转发的意思.

image-20210720191546762

引入

复制代码
1
2
<script src="https://unpkg.com/vue@next"></script>

image-20210720191811252

通过vue.js文件引入

下载

登录网址, https://unpkg.com/browse/vue@3.1.5/dist/ ,如下图所示下载vue.global.js文件,这个文件并不是源码文件,而是经过打包之后的文件.

image-20210720193418728

引入

如下图引入:

image-20210720194006466

计数器案例

原生JS实现

原生JS实现计数器-增加事件监听.html

==增加事件监听=

image-20210720202214407

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!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> <div id="counter"></div> <button id="increase" >+</button> <button id="decrease">-</button> <script> //aa-get the counter and button elements let counter = document.querySelector('#counter') let increase = document.querySelector('#increase') let decrease = document.querySelector('#decrease') //bb-display number in the counter div let num = 100 counter.innerHTML = num //cc-bind the button click event increase.addEventListener('click',()=>{ num++ counter.innerHTML = num }) decrease.addEventListener('click',()=>{ num-- counter.innerHTML = num }) </script> </body> </html>

原生JS实现计数器-在元素中绑定onclick属性和script标签中增加onclick属性.html

方式一

方式二

image-20210720203035899

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!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> <div id="counter"></div> <button id="increase" onclick="increase()" >+</button> <button id="decrease">-</button> <script> //aa-get the counter and button elements let counter = document.querySelector('#counter') let increase = document.querySelector('#increase') let decrease = document.querySelector('#decrease') //bb-display number in the counter div let num = 100 counter.innerHTML = num //cc-bind the button click event //ca-bind onclick in the button div function increase(){ num++ counter.innerHTML = num } //cb-bind onclick in the script query decrease.onclick = function(){ num -- counter.innerHTML = num } </script> </body> </html>

VUE实现计数器

vue实现计数器.html

基本结构

image-20210720224607723

数据绑定mustache语句

image-20210720224659089

事件绑定

image-20210720224738318

data的value为啥是个函数

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html> <html lang="zh"> <head> </head> <body> <div id="app"></div> <script src="./vue/vue.js"></script> <script> Vue.createApp({ template:` <h2>{{counter}}</h2> <button @click='increase'>+1</button> <button @click='decrease'>-1</button> `, data:function(){ return{ counter: 10 } }, methods: { increase(){ this.counter++ }, decrease(){ this.counter-- } }, }).mount('#app') </script> </body> </html>

vue实现计数器 and es5的写法 and 箭头函数的写法.html

es5和es6的写法的对比

image-20210720225907205

箭头函数的写法

image-20210720230007375

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html> <html lang="zh"> <head> </head> <body> <div id="app"></div> <script src="./vue/vue.js"></script> <script> Vue.createApp({ template:` <h2>{{counter}}</h2> <button @click='increase'>+1</button> <button @click='decrease'>-1</button> `, data:function(){ return{ counter: 10 } }, methods: { //es6的写法 //increase(){ // this.counter++ //}, //es5的写法--OK increase:function(){ this.counter++ } , //箭头函数--Not OK!!! decrease:()=>{ this.counter-- } }, }).mount('#app') </script> </body> </html>

命令式和声明式编程

声明式编程

理解

image-20210720230637295

命令式编程和声明式编程的区别

不恰当的比喻:一个是手把手的教,一个是发个命令就好了.

image-20210721064900640

MVVM模型

MVC模型

image-20210721065538134

image-20210721065144627

MVVM模型

vue的模式类似MVVM模型

image-20210721065457700

template属性

挂载

template中的内容会被挂载到对应的元素下面,并且其中的内容会被覆盖掉

image-20210721065749579

分离式写法_script标签

image-20210721070213332

分离式写法.html

关键代码

image-20210721070838288

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html> <html lang="zh"> <head> </head> <body> <div id="app"></div> <script type='x-template' id='main'> <h2>{{counter}}</h2> <button @click='increase'>+1</button> <button @click='decrease'>-1</button> </script> <script src="./vue/vue.js"></script> <script> Vue.createApp({ template:`#main`, data:function(){ return{ counter: 10 } }, methods: { increase(){ this.counter++ }, decrease(){ this.counter-- } }, }).mount('#app') </script> </body> </html>

分离式写法_template标签

template中的内容分离式写法_使用template标签.html

核心代码

image-20210721071236294

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html> <html lang="zh"> <head> </head> <body> <div id="app"></div> <template id='main'> <div> <h2>{{counter}}</h2> <button @click='increase'>+1</button> <button @click='decrease'>-1</button> </div> </template> <script src="./vue/vue.js"></script> <script> Vue.createApp({ template:`#main`, data:function(){ return{ counter: 10 } }, methods: { increase(){ this.counter++ }, decrease(){ this.counter-- } }, }).mount('#app') </script> </body> </html>

template标签的特点

https://developer.mozilla.org/zh-CN/docs/Web/HTML/Element/template

image-20210721071624380

普通div其实也是可以实现挂载的,vue也会将其挂载上去,只不过div会被浏览器解析器渲染上去,显示出来,从而导致内容重复多出来.

image-20210721072239210

template中的根元素

vue2和vue3中template中根元素个数的区别.html

vue3中可以有多个根元素

image-20210722092351744

vue2中只能有一个根元素

image-20210722092424104

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- vue3 是允许template中有多个根元素 --> <template id="my-app"> <a v-bind:href="link">百度一下</a> <a :href="link">百度一下</a> </template> <!-- vue2 template模板中只能有一个根元素 --> <template id="my-app"> <div> <a v-bind:href="link">百度一下</a> <a :href="link">百度一下</a> </div> </template>

data属性

vue2和vue3中的区别

image-20210721072402349

methods属性

image-20210721072653548

this的指向

普通函数

普通函数的this指向.html

this的指向

image-20210721075050193

this永远指向的是调用他的那个对象

fun7()其实是window.fun7()的省略写法

image-20210721075459603

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html> <html lang="zh"> <head> </head> <body> <script> /** * this的指向 * 1-函数调用的时候,this就是指向这个window对象 * 2-对象进行调用的时候,就是指向这个对象。 */ function fun7(){ console.log(this); console.log(this.name); console.log("I'm fun7"); } fun7(); console.log('----------------'); var obj2 = { name:"Bruce", age:12, say:function(){ console.log(this); console.log(this.name); console.log("I'm fun8"); } } obj2.say(); </script> </body> </html>

强制改变普通函数函数定义时候的this.html

强制改变普通函数定义时候的this

image-20210721100954533

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html> <html lang="zh"> <head> </head> <body> <script> function Timer() { this.s1 = 0; this.s2 = 0; // 箭头函数 setInterval(() => this.s1++, 1000); // 普通函数 _this = this setInterval(function () { _this.s2++; }, 1000); } var timer = new Timer(); setTimeout(() => console.log('s1: ', timer.s1), 3100);//=>3 setTimeout(() => console.log('s2: ', timer.s2), 3100);//=>3 </script> </body> </html>

箭头函数

箭头函数中this的指向.html

普通函数this永远指向调用他的对象

箭头函数this的指向定义时候的this

箭头函数没有自己的this,他会从里头向外头寻找,直到找到有this为止,这里的this是windows对象.

image-20210721093417251

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html> <html lang="zh"> <head> </head> <body> <script> var obj2 = { name:"Bruce", age:12, say:function(){ console.log(this); //=>{name: "Bruce", age: 12, say: ƒ, run: ƒ} }, run:()=>{ console.log(this); //=>Window {window: Window, self: Window, document: document, name: "", location: Location, …} }, } obj2.say(); obj2.run() </script> </body> </html>

为什么vue的methods不要使用箭头函数?

计数器.html

我们想要操作的对象是什么?

我想要操作的对象是什么,我们想要通过这个this.counter对象拿到这个data中的counter,但是如果传入的this是Windows对象,我们通过这个windows对象是拿不到这个counter的.所以不推荐使用箭头函数.

箭头函数没有自己this,一般在定义的时候,会到自己的上级作用域寻找this,这里的上级作用域就是windows所在的作用域,一般来说都是这个windows作用域.

image-20210721170554857

普通函数和箭头函数中的this

image-20210721170903992

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html> <html lang="zh"> <head> </head> <body> <div id="app"></div> <script src="./vue/vue.js"></script> <script> Vue.createApp({ template:` <h2>{{counter}}</h2> <button @click='increase'>+1</button> <button @click='decrease'>-1</button> `, data:function(){ return{ counter: 10 } }, methods: { increase(){ console.log(this); //=>Proxy {increase: ƒ, decrease: ƒ, …} this.counter++ }, decrease:()=>{ console.log(this); //=>Window {window: Window, self: Window, document: document, name: "", location: Location, …} this.counter-- } } }).mount('#app') </script> </body> </html>

总结

使用function定义的函数,this的指向随着调用环境的变化而变化的,而箭头函数中的this指向是固定不变的,一直指向的是定义函数的环境。

使用function定义的函数中this指向是随着调用环境的变化而变化的

复制代码
1
2
3
4
5
6
7
8
//使用function定义的函数 function foo(){ console.log(this); } var obj = { aa: foo }; foo(); //Window obj.aa() //obj { aa: foo }

明显使用箭头函数的时候,this的指向是没有发生变化的。

复制代码
1
2
3
4
5
6
//使用箭头函数定义函数 var foo = () => { console.log(this) }; var obj = { aa:foo }; foo(); //Window obj.aa(); //Window

VSCode代码片段

  1. 赋值自己需要的代码
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html> <html lang="zh"> <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> <div id="app"> {{msg}} </div> <script src="vue/vue.js"></script> <script> Vue.createApp({ data:function(){ return{ msg: 'hello vue' } } }).mount('#app') </script> </body> </html>
  1. 登录这个网站

https://snippet-generator.app/

image-20210721184734917

  1. 将生成的代码片段拷贝下来
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"create vue app": { "prefix": "vueapp", "body": [ "<!DOCTYPE html>", "<html lang="zh">", "<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>", " <div id="app">", " {{msg}}", " </div>", " <script src="vue/vue.js"></script>", " <script>", " Vue.createApp({", " data:function(){", " return{", " msg: 'hello vue'", " }", " }", " }).mount('#app')", " </script>", "</body>", "</html>" ], "description": "create vue app" }
  1. 打开vscode

image-20210721185000847

image-20210721185012387

image-20210721185149459

image-20210721185215489

Honeycam 2021-07-21 18-54-06

Musache语法

正确用法

mustache语法.html

基本使用

image-20210721191047349

简单的表达式

image-20210721191130009

image-20210721191255879

也可以是methods中的函数

image-20210721191709464

既然可以是表达式,当然也可以是三元表达式了

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <!-- 1.mustache的基本使用 --> <h2>{{message}}</h2> <!-- 2.是一个表达式 --> <h2>{{counter * 10}}</h2> <h2>{{ message.split(" ").reverse().join(" ") }}</h2> <!-- 3.也可以调用函数 --> <!-- 可以使用computed(计算属性) --> <h2>{{getReverseMessage()}}</h2> <!-- 4.三元运算符 --> <h2>{{ isShow ? "哈哈哈": "" }}</h2> <button @click="toggle">切换</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message: "Hello World", counter: 100, isShow: true } }, methods: { getReverseMessage() { return this.message.split(" ").reverse().join(" "); }, toggle() { this.isShow = !this.isShow; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

错误用法

musache错误用法.html

两者都是赋值语句,不是表达式

image-20210722074511774

复制代码
1
2
3
4
5
6
<!-- 错误用法 --> <!-- var name = "abc" -> 赋值语句 --> <h2>{{var name = "abc"}}</h2> <h2>{{ if(isShow) { return "哈哈哈" } }}</h2>

基本指令

v-once

v-once修饰的html元素,只渲染一次,以后都是不变,相当于一个原始的参照系.

v-once.html

代码理解

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html> <html lang="en"> <head> </head> <body> <div id="app"></div> <template id="my-app"> <div v-once> <h2>原始数值:{{counter}}</h2> </div> <h2>当前数值{{counter}}</h2> <button @click="increment">+1</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { counter: 100, } }, methods: { increment() { this.counter++; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出

Honeycam 2021-07-22 08-11-24

v-text

v-text就是其修饰的html元素中添加内容,作用和mustache类似,不过没有mustache语法灵活.

v-text.html

关键代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <h2 v-text="message"></h2> <h2>{{message}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message: "Hello World" } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

v-html

v-html.html

关键代码

image-20210722081414361

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <div>{{msg}}</div> <div v-html="msg"></div> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { msg: '<span style="color:red; background: blue;">哈哈哈</span>' } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出

image-20210722081422869

v-pre

v-pre.html

关键代码

image-20210722085858915

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2 v-pre>{{message}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message: "Hello World" } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出

image-20210722085943860

v-cloak

cloak就是斗篷,遮盖的的意思,这个指令的作用是什么,主要为了显示效果,比如说网络很卡,浏览器很卡,这个{{message}}中的内容还没有渲染进来,网页页面会显示mustache语法的原始内容,我们加上这个遮盖之后,就是什么都不显示,然后等到这个里面的message内容渲染完成之后,才将其显示,目的是为了更好的用户体验.

v-cloak.html

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <h2 v-cloak>{{message}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message: "Hello World" } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

cloak单词的意思

重要指令

v-bind

属性绑定

v-bind是用来绑定属性,实现动态属性.mustache是用来绑定内容,实现动态内容.

v-bind的基本使用.html

v-bind的基本使用

image-20210722091926655

v-bind的语法糖

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <!-- 1.v-bind的基本使用 --> <a v-bind:href="link">百度一下</a> <!-- 2.v-bind提供一个语法糖 : --> <a :href="link">百度一下</a> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { link: "https://www.baidu.com" } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

属性绑定_对象形式

属性绑定_对象形式.html

key-value结构

image-20210722105528267

错误写法

key可以加上引号,也可以不加引号.但是value一定不能加引号,因为value加上引号,就变成了字符串.

image-20210722105708876

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html> <html lang="en"> <head> <style> .active { color: red; } </style> </head> <body> <div id="app"></div> <template id="my-app"> <!-- 正确写法:key-value结构 --> <!-- 对象语法: {key: value} --> <h2 :class="{'active': isActive}">呵呵呵呵</h2> <h2 :class="{active: isActive}">呵呵呵呵</h2> <!-- 错误写法 --> <h2 :class="{active: 'isActive'}">呵呵呵呵</h2> <button @click="toggle">切换</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: "#my-app", data() { return { isActive: true, }; }, methods: { toggle() { this.isActive = !this.isActive; }, }, }; Vue.createApp(App).mount("#app"); </script> </body> </html>

输出

Honeycam 2021-07-22 10-58-26

多值情况

属性绑定_多值情况.html

多个键值对

image-20210722110501400

默认class和动态的class结合

image-20210722110706098

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<!DOCTYPE html> <html lang="en"> <head> <style> .active { color: red; } .title { background-color: yellowgreen; } </style> </head> <body> <div id="app"></div> <template id="my-app"> <button @click="toggle">切换</button> <!-- 也可以有多个键值对 --> <div :class="{active: isActive, title: true}">多个键值对</div> <!-- 默认的class和动态的class结合 --> <div class="abc cba" :class="{active: isActive, title: true}"> 默认的class和动态的class结合 </div> </template> <script src="../js/vue.js"></script> <script> const App = { template: "#my-app", data() { return { isActive: true, }; }, methods: { toggle() { this.isActive = !this.isActive; } }, }; Vue.createApp(App).mount("#app"); </script> </body> </html>
对象放到一个单独的属性中

将对象放到一个单独的属性中.html

将对象放到一个单独的属性中

image-20210722111658561

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html> <html lang="zh"> <head> <style> .active { color: red; } </style> </head> <body> <div id="app"></div> <template id="my-app"> <!-- 将对象放到一个单独的属性中 --> <h2 class="abc cba" :class="classObj">将对象放到一个单独的属性中</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: "#my-app", data() { return { classObj: { active: true, title: true } }; } }; Vue.createApp(App).mount("#app"); </script> </body> </html>
将对象放到methods中返回

将返回的对象放到一个methods(computed)方法中.html

将对象放到一个methods返回

image-20210722112014022

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html> <html lang="zh"> <head> <style> .active { color: red; } </style> </head> <body> <div id="app"></div> <template id="my-app"> <!-- 将返回的对象放到一个methods(computed)方法中 --> <h2 class="abc cba" :class="getClassObj()">将对象放到一个methods(computed)方法中返回</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: "#my-app", methods: { getClassObj() { return { active: true, title: true } } }, }; Vue.createApp(App).mount("#app"); </script> </body> </html>

属性绑定_数组形式

数组形式绑定属性.html

基本用法

image-20210722114741177

image-20210722114916250

嵌入三元表达式

image-20210722114752033

嵌入对象

image-20210722114801586

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <!-- 基本用法 --> <div :class="['abc', title]">哈哈哈哈</div> <!-- 数组中可以嵌入三元表达式 --> <div :class="['abc', title, isActive ? 'active': '']">哈哈哈哈</div> <!-- 数组中可以嵌入对象 --> <div :class="['abc', title, {active: isActive}]">哈哈哈哈</div> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message: "Hello World", title: "cba", isActive: true } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

样式绑定_对象形式

v-bind绑定样式.html

基本使用

image-20210722115639670

基本使用_简单拼接

image-20210722115805707

绑定data属性中的object对象

image-20210722115908808

方法中返回的一个对象

image-20210722120030176

短横线需要加引号,驼峰不需要加引号

image-20210722121914360

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<!DOCTYPE html> <html lang="zh"> <head> </head> <body> <div id="app"></div> <template id="my-app"> <!-- :style="{cssPropertyName: cssPropertyValue}" --> <div :style="{color: finalColor, 'font-size': '30px'}">'font-size'加了引号</div> <div :style="{color: finalColor, fontSize: '30px'}">fontSize不加引号</div> <div :style="{color: finalColor, fontSize: finalFontSize + 'px'}">finalFontSize是data中的值,和后面的'px'拼起来</div> <!-- 绑定一个data中的属性值, 并且是一个对象 --> <div :style="finalStyleObj">绑定一个data中的属性</div> <!-- 方法中返回的一个对象 --> <div :style="getFinalStyleObj()">methods中返回的一个对象</div> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message: "Hello World", finalColor: 'red', finalFontSize: 50, finalStyleObj: { 'font-size': '50px', fontWeight: 700, backgroundColor: 'red' } } }, methods: { getFinalStyleObj() { return { 'font-size': '50px', fontWeight: 700, backgroundColor: 'red' } } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

样式绑定_数组形式

样式绑定_数组形式.html

数组中嵌套对象

image-20210722120457109

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <div :style="[style1Obj, style2Obj]">哈哈哈</div> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { style1Obj: { color: 'red', fontSize: '30px' }, style2Obj: { textDecoration: "underline" } } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

属性名称绑定

属性名称绑定.html

属性名称的绑定

image-20210722122405532

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <div :[name]="value">哈哈哈</div> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { name: "classs", value: "content" } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

属性名和属性值绑定

属性名和属性值绑定.html

和属性值绑定的区别

没有冒号:

image-20210722123405766

多个属性名和属性值的键值对的绑定

image-20210722123153573

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2 v-bind="info">同时绑定多个属性名和属性值</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { info: { name: "zhuo", age: 18, height: 1.88 } } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

v-bind(same)

  • 缩写:

  • 预期any (with argument) | Object (without argument)

  • 参数attrOrProp (optional)

  • 修饰符

    • .camel - 将 kebab-case attribute 名转换为 camelCase。
  • 用法

    动态地绑定一个或多个 attribute,或一个组件 prop 到表达式。

    在绑定 classstyle attribute 时,支持其它类型的值,如数组或对象。可以通过下面的教程链接查看详情。

    在绑定 prop 时,prop 必须在子组件中声明。可以用修饰符指定不同的绑定类型。

    没有参数时,可以绑定到一个包含键值对的对象。注意此时 classstyle 绑定不支持数组和对象。

  • 示例

    复制代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <!-- 绑定 attribute --> <img v-bind:src="imageSrc" /> <!-- 动态 attribute 名 --> <button v-bind:[key]="value"></button> <!-- 缩写 --> <img :src="imageSrc" /> <!-- 动态 attribute 名缩写 --> <button :[key]="value"></button> <!-- 内联字符串拼接 --> <img :src="'/path/to/images/' + fileName" /> <!-- class 绑定 --> <div :class="{ red: isRed }"></div> <div :class="[classA, classB]"></div> <div :class="[classA, { classB: isB, classC: isC }]"> <!-- style 绑定 --> <div :style="{ fontSize: size + 'px' }"></div> <div :style="[styleObjectA, styleObjectB]"></div> <!-- 绑定一个全是 attribute 的对象 --> <div v-bind="{ id: someProp, 'other-attr': otherProp }"></div> <!-- prop 绑定。"prop" 必须在 my-component 声明 --> <my-component :prop="someThing"></my-component> <!-- 通过 $props 将父组件的 props 一起传给子组件 --> <child-component v-bind="$props"></child-component> <!-- XLink --> <svg><a :xlink:special="foo"></a></svg> </div>

    .camel 修饰符允许在使用 DOM 模板时将 v-bind property 名称驼峰化,

    例如 SVG 的 viewBox property:

    复制代码
    1
    2
    <svg :view-box.camel="viewBox"></svg>

    在使用字符串模板或通过 vue-loader / vueify 编译时,无需使用 .camel

v-on

基本使用

v-on的基本使用.html

click事件

mousemove事件

image-20210722124014612

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html> <html lang="zh"> <head> <style> .area { width: 200px; height: 200px; background: red; } </style> </head> <body> <div id="app"></div> <template id="my-app"> <!-- 完整写法: v-on:监听的事件="methods中方法" --> <button v-on:click="btn1Click">按钮1</button> <div class="area" v-on:mousemove="mouseMove">div</div> <!-- 语法糖 --> <button @click="btn1Click">按钮1</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', methods: { btn1Click() { console.log("按钮1发生了点击"); }, mouseMove() { console.log("鼠标移动"); } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

绑定一个对象

v-on通过绑定一个对象从而实现绑定多个事件.html

v-on绑定一个对象实现绑定多个事件

image-20210722124432040

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html> <html lang="en"> <head> <style> .area { width: 200px; height: 200px; background: red; } </style> </head> <body> <div id="app"></div> <template id="my-app"> <!-- 绑定一个对象 --> <div class="area" v-on="{click: btn1Click, mousemove: mouseMove}"></div> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', methods: { btn1Click() { console.log("按钮1发生了点击"); }, mouseMove() { console.log("鼠标移动"); } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

传递参数

v-on如何向vue的methods中传递参数.html

默认传入event事件

传入其他参数的同时传入event参数

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <!-- 默认传入event对象, 可以在方法中获取 --> <button @click="btn1Click">按钮1</button> <!-- $event可以获取到事件发生时的事件对象 --> <button @click="btn2Click($event, 'zhuo', 18)">按钮2</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message: "Hello World" } }, methods: { btn1Click(event) { console.log(event); }, btn2Click(event, name, age) { console.log(name, age, event); } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

修饰符

image-20210722130338774

stop修饰符

阻止事件冒泡的按钮.html

关键代码

image-20210722125641019

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <div @click="divClick"> <button @click='btnClick'>没有阻止冒泡的按钮</button><br> <button @click.stop="btnClick">阻止冒泡按钮</button> </div> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', methods: { divClick() { console.log("divClick"); }, btnClick() { console.log('btnClick'); }, } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

Honeycam 2021-07-22 12-57-11

[按键]修饰符

输入框实现enter键上屏的效果.html

关键代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <input type="text" @keyup.enter="enterKeyup"> <h2>{{content}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { content:'' } }, methods: { enterKeyup(event) { this.content = event.target.value } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

Honeycam 2021-07-22 13-02-02

v-if

渲染原理

怎么理解这个惰性?当条件为false时,在dom的元素就会完全的删除掉,而不是display:none.

image-20210722200738970

基本使用

v-if的基本使用.html

基本使用

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html> <html lang="zn"> <body> <div id="app"></div> <template id="my-app"> <h2 v-if="isShow">哈哈哈哈</h2> <button @click="toggle">切换</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { isShow: true } }, methods: { toggle() { this.isShow = !this.isShow; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出

Honeycam 2021-07-22 20-11-57

多个条件

v-if多个条件的使用.html

关键代码

image-20210722201319872

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <input type="text" v-model="score"> <h2 v-if="score > 90">优秀</h2> <h2 v-else-if="score > 60">良好</h2> <h2 v-else>不及格</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { score: 95 } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出

Honeycam 2021-07-22 20-14-40

template和v-if的结合使用

为什么需要和v-if结合使用

v-if不结合使用.html

关键代码和输出效果

多了一个外层的div,现在就是不想要这个div

image-20210722202534782

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <!-- 和div结合使用 --> <div v-if="isShowHa"> <h2>哈哈哈哈</h2> <h2>哈哈哈哈</h2> <h2>哈哈哈哈</h2> </div> <!-- 和template结合使用 --> <template v-else> <h2>呵呵呵呵</h2> <h2>呵呵呵呵</h2> <h2>呵呵呵呵</h2> </template> <button @click='toggle'>toggle</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { isShowHa: true } }, methods: { toggle(){ this.isShowHa = !this.isShowHa } }, } Vue.createApp(App).mount('#app'); </script> </body> </html>

v-if结合使用例子

template和v-if的结合使用.html

关键代码

image-20210722202011296

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <template v-if="isShowHa"> <h2>哈哈哈哈</h2> <h2>哈哈哈哈</h2> <h2>哈哈哈哈</h2> </template> <template v-else> <h2>呵呵呵呵</h2> <h2>呵呵呵呵</h2> <h2>呵呵呵呵</h2> </template> <button @click='toggle'>toggle</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { isShowHa: true } }, methods: { toggle(){ this.isShowHa = !this.isShowHa } }, } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

Honeycam 2021-07-22 20-18-42

v-show

基本使用

v-show的基本使用.html

关键代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <h2 v-show="isShow">哈哈哈哈</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { isShow: true } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

v-show和v-if的区别

2,3,4都是很好理解,第1个怎么理解?首先要理解一点就是v-show是通过display:none来控制显示不显示的,而template这个标签一旦被渲染,这个标签就是不存在了,我在对这个标签使用css修饰已经没有任何意义了.

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

关键代码

image-20210722203016325

重要区别

v-show通过修改css属性来实现隐藏显示,而v-if直接就是干掉整个元素.

Honeycam 2021-07-22 20-32-44

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <h2 v-if="isShow">哈哈哈哈</h2> <h2 v-show="isShow">呵呵呵呵</h2> <button @click='toggle'>toggle</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { isShow: true } },methods: { toggle(){ this.isShow = !this.isShow } }, } Vue.createApp(App).mount('#app'); </script> </body> </html>

v-show和v-if如何选择

image-20210722233700515

v-for

基本使用

v-for的使用.html

遍历数组

括号的中两个参数分别是value和index

image-20210722222732217

image-20210722223051804

v-for中传递参数的括号可以不加,但是建议加上去

image-20210723223851209

v-for的in也可以使用of

image-20210723224004367

遍历对象

遍历对象的时候,括号中的参数分别是value,key和index

image-20210722222743792

image-20210722223058417

遍历数字

遍历数字的时候,括号中的参数分别是num和index

image-20210722222756507

image-20210722223104508

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <h2>遍历数组</h2> <ul> <!-- 遍历数组 --> <li v-for="(movie, index) in movies">{{index+1}}.{{movie}}</li> </ul> <h2>遍历对象</h2> <ul> <!-- 遍历对象 --> <li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li> </ul> <h2>遍历数字</h2> <ul> <!-- 遍历数字 --> <li v-for="(num, index) in 10">{{num}}-{{index}}</li> </ul> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { movies: [ "星际穿越", "盗梦空间", "大话西游", "教父", "少年派" ], info: { name: "zhuo", age: 18, height: 1.88 } } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

v-for和template搭配使用

v-for和template搭配使用.html

v-for和template搭配使用

image-20210722223903277

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <ul> <template v-for="(value, key) in info"> <li>{{key}}</li> <li>{{value}}</li> <li class="divider"></li> </template> </ul> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { info: { name: "why", age: 18, height: 1.88 } } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

搭配绑定key

image-20210722234232631

数组更新检测

vue已经自动帮我们侦听了数组,所以数组的改变就会触发新的视图.实际上可以这样理解,vue已经自动帮我们把这个数组进行了数据的双向绑定.

image-20210722230657490

push()方法

数组的更新检测.html

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2>电影列表</h2> <ul> <li v-for="(movie, index) in movies">{{index+1}}.{{movie}}</li> </ul> <input type="text" v-model="newMovie"> <button @click="addMovie">添加电影</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { newMovie: "", movies: [ "星际穿越", "盗梦空间", "大话西游", "教父", "少年派" ] } }, methods: { addMovie() { this.movies.push(this.newMovie); this.newMovie = ""; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

Honeycam 2021-07-22 23-12-19

VNode

image-20210722233935367

计算属性-computed

为什么会有计算属性?

有些数据通过mustache语法显示在界面上面,但是在mustache中又进行了简单的表达式计算.这个用来写单独的项目是可以的.但是现在我们想要写出通用的组件,就必须让这个mustache中的数据更加纯粹,于是就必须对这些数据进行解耦,那么感觉我就是对原来的数据进行一下二次封装,然后就这些二次封装后的数据在渲染到界面上面.这个二次封装的数据就是计算属性.

基本使用

计算属性.html

计算属性的来源和目的地

  • 来源:计算属性数据的来源肯定是来自data中的元数据,
  • 目的地:mustache展示界面.

image-20210723164251032

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html> <html lang="en"> <body> <div id="app"></div> <template id="my-app"> <h2>{{fullName}}</h2> <h2>{{result}}</h2> <h2>{{reverseMessage}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { firstName: "Kobe", lastName: "Bryant", score: 80, message: "Hello World" } }, computed: { fullName() { return this.firstName + " " + this.lastName; }, result() { return this.score >= 60 ? "及格": "不及格"; }, reverseMessage() { return this.message.split(" ").reverse().join(" "); } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

使用methods实现的数据二次封装和计算属性的区别

methods和计算属性的区别.html

计算属性

image-20210723194828601

methods实现类似计算属性的功能

image-20210723195005203

区别

image-20210723194653255

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h1>计算属性</h1> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> <h1>使用methods实现</h1> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { firstName: "Kobe", lastName: "Bryant" } }, computed: { // 计算属性是有缓存的, 当我们多次使用计算属性时, 计算属性中的运算只会执行一次. // 计算属性会随着依赖的数据(firstName)的改变, 而进行重新计算. fullName() { console.log("computed的fullName中的计算"); return this.firstName + " " + this.lastName; } }, methods: { getFullName() { console.log("methods的getFullName中的计算"); return this.firstName + " " + this.lastName; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

methods和计算属性的区别_修改元数据.html

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <button @click="changeFirstName">修改firstName</button> <h2>{{fullName}}</h2> <h2>{{fullName}}</h2> <h2>{{getFullName()}}</h2> <h2>{{getFullName()}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { firstName: "Kobe", lastName: "Bryant" } }, computed: { fullName() { console.log("computed的fullName中的计算"); return this.firstName + " " + this.lastName; } }, methods: { getFullName() { console.log("methods的getFullName中的计算"); return this.firstName + " " + this.lastName; }, changeFirstName() { this.firstName = "Coder" } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

描述

methods修改了三次,计算属性只是修改了一次.所以计算属性的更省.

Honeycam 2021-07-23 19-58-29

修改计算属性

修改计算属性_没有效果的例子.html

修改流程

按钮绑定click事件去修改,通过methods方法去修改计算属性中的计算属性.

image-20210723201311169

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <button @click="changeFullName">修改fullName</button> <h2>{{fullName}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { firstName: "Kobe", lastName: "Bryant" } }, computed: { // fullName 的 getter方法 fullName() { return this.firstName + " " + this.lastName; }, }, methods: { changeFullName() { this.fullName = "Coder Why"; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

描述

没有丝毫效果

Honeycam 2021-07-23 20-14-51

修改计算属性_成功例子.html

计算属性的全写形式

image-20210723203810598

语法糖和全写形式

image-20210723204016900

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <button @click="changeFullName">修改fullName</button> <h2>{{fullName}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { firstName: "Kobe", lastName: "Bryant" } }, computed: { // fullName的getter和setter方法 fullName: { get: function() { console.log('计算属性被__获取了'); return this.firstName + " " + this.lastName; }, set: function(newValue) { console.log('计算属性被__修改了'); let names = newValue.split(" "); this.firstName = names[0]; this.lastName = names[1]; } } }, methods: { changeFullName() { this.fullName = "Coder Why"; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

描述

getter方法用上展示,没有getter方法,无法在mustache中渲染显示出来.

setter方法用于修改,没有setter方法,无法修改计算属性

Honeycam 2021-07-23 20-36-17

Watch

基本使用

监听输入的内容.html

监听器的内容

image-20210723225252213

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2>监听输入的内容</h2> <input type="text" v-model="question"> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { // 侦听question的变化时, 去进行一些逻辑的处理(JavaScript, 网络请求) question: "Hello World", } }, watch: { // question侦听的data中的属性的名称 // newValue变化后的新值 // oldValue变化前的旧值 question: function(newValue, oldValue) { console.log("新值: ", newValue, "旧值", oldValue); this.displayInput(); } }, methods: { displayInput() { console.log(`你输入的内容是:${this.question}`); } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

Honeycam 2021-07-23 22-55-28

深度监听

反例_语法糖形式

无法深度监听的一个例子.html

info对象

image-20210724065031093

监听的部分代码

image-20210724070458977

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2>{{info.name}}</h2> <button @click="changeInfo">改变info</button> <button @click="changeInfoName">改变info.name</button> <button @click="changeInfoNbaName">改变info.nba.name</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { info: { name: "why", age: 18, nba: {name: 'kobe'} } } }, watch: { // 默认情况下我们的侦听器只会针对监听的数据本身的改变(内部发生的改变是不能侦听) info(newInfo, oldInfo) { console.log("newValue=", newInfo); console.log("oldValue=", oldInfo); } }, methods: { changeInfo() { this.info = {name: "kobe"}; }, changeInfoName() { this.info.name = "kobe"; }, changeInfoNbaName() { this.info.nba.name = "james"; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

描述

改变整个info对象是能够监听到的,

改变info.name无法监听到,

改变info.nba.name无法监听到.

Honeycam 2021-07-24 06-54-22

反例_全写形式

无法深度监听的例子2.html

监听部分的代码

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2>{{info.name}}</h2> <button @click="changeInfo">改变info</button> <button @click="changeInfoName">改变info.name</button> <button @click="changeInfoNbaName">改变info.nba.name</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { info: { name: "why", age: 18, nba: {age: 18} } } }, watch: { info: { handler: function(newInfo, oldInfo) { console.log(newInfo); console.log(oldInfo); }, } }, methods: { changeInfo() { this.info = {name: "kobe"}; }, changeInfoName() { this.info.name = "kobe"; }, changeInfoNbaName() { this.info.nba.age = 20; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

正例

能够深度监听的例子.html

关键代码:开启深度监听

image-20210724071049053

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2>{{info.name}}</h2> <button @click="changeInfo">改变info</button> <button @click="changeInfoName">改变info.name</button> <button @click="changeInfoNbaName">改变info.nba.name</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { info: { name: "why", age: 18, nba: {age: 18} } } }, watch: { info: { handler: function(newInfo, oldInfo) { console.log('newInfo VVV'); console.log(newInfo); console.log('oldInfor VVV'); console.log(oldInfo); }, deep:true } }, methods: { changeInfo() { this.info = {name: "kobe"}; }, changeInfoName() { this.info.name = "kobe"; }, changeInfoNbaName() { this.info.nba.age = 20; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

改变info.nba.Age的效果

现在的情况是可以监听到这个info里面对象的变化,但是监听不到里面具体内容的变化,只能检测到改变之后的内容.

Honeycam 2021-07-24 09-12-32

改变info.name

虽然info.name被改变了,mustache中的内容也改变了,但是这个watch只能检测到info.name的变化,无法检测到内容的变化,无法知道以前的info.name的值

Honeycam 2021-07-24 09-17-12

改吗info

改变info可以检测到变化,也可以检测其新旧的内容,只有当整个对象发生变化的时候,才能够检测到其内容.

立即执行

有时候我们希望这个watch在启动的时候就是自动监听一次.

立即监听的例子.html

核心代码

image-20210724093622584

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2>{{info.name}}</h2> <button @click="changeInfo">改变info</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { info: { name: "why", age: 18, nba: {name: 'kobe'} } } }, watch: { // 深度侦听/立即执行(一定会执行一次) info: { handler: function(newInfo, oldInfo) { console.log('newInfo VVV'); console.log(newInfo); console.log('oldInfo VVV'); console.log(oldInfo); }, deep: true, // 深度侦听 immediate: true // 立即执行 } }, methods: { changeInfo() { this.info = {name: "kobe"}; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

描述

刷新一下立马执行一次,也就是相当于有个初始化执行.

Honeycam 2021-07-24 09-35-14

针对对象中某个属性的监听

针对info.name的监听.html

针对info.name的监听

image-20210724203123785

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2>{{info}}</h2> <button @click="changeInfo">改变info</button> <button @click="changeInfoName">改变info.name</button> <button @click="changeInfoNbaAge">改变info.age</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { info: { name: "why", age: 18 } } }, watch: { "info.name": function(newName, oldName) { console.log('newName='+newName,' oldName='+oldName); }, }, methods: { changeInfo() { this.info = {name: "kobe"}; }, changeInfoName() { this.info.name = "kobe"; }, changeInfoNbaAge() { this.info.age = 100 }, } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

描述

  • 改变info: 有效果,因为整个对象都发生了变化,所以里面的name属性当然也发生了变化,所以能够检测到
  • 改变info.name:很显然有效果
  • 改变info.age: 发现虽然年龄由18–>100发生了变化,但是没有检测到

Honeycam 2021-07-24 20-34-41

create()创建监听器

在create声明周期函数里面创建监听器.html

关键代码

image-20210724204926369

this.$watch()的三个参数

image-20210724205113255

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2>{{info.name}}</h2> <button @click="changeInfo">改变info</button> <button @click="changeInfoName">改变info.name</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { info: { name: "why", age: 18 }, } }, methods: { changeInfo() { this.info = {name: "kobe" , age:100}; }, changeInfoName() { this.info.name = "james"; }, }, created() { this.$watch("info", function(newInfo, oldInfo) { console.log('newInfo VVV'); console.log(newInfo); console.log('oldInfo VVV'); console.log(oldInfo); }, { deep: true, immediate: true }) } } Vue.createApp(App).mount('#app'); </script> </body> </html>

综合案例_书籍购物车

image-20210727080433004

按钮禁用

按钮禁用

复制代码
1
2
<button v-bind:disabled='book.count<=1'>-</button>

问题_多个tbody标签

问题

多个tbody标签

image-20210727095014904

没有tr标签

image-20210727095119407

v-for直接写在tbody里面了

image-20210727095201051

解决

增加tr标签,v-for写在tr标签里面

image-20210727095337065

问题_有时能完全删除,有时不能

问题

问题_有时能够完全删除,有时不能删除

Honeycam 2021-07-27 10-19-00

问题_代码部分问题

__因为我传递的是数组中的book的id,而当我删除这个id的时候,这个id是固定不变的,比如说当我删除最后一个的时候,id=4,然后现在数组中只有一个元素,我传递过来的id等于4,即使id-1=3,也没有下标从三开始的元素,所以无法删除.

image-20210727102401670

答案

解决_传递变化的index来解决问题

image-20210727102703509

输出效果

Honeycam 2021-07-27 10-28-26

最终

index.html

增加数量

image-20210727120924874

减少数量

image-20210727121000685

移出书籍

image-20210727120952984

总价格

image-20210727121020786

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<!DOCTYPE html> <html lang="zh"> <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"> <link rel="stylesheet" href="style.css"> <title>书籍购物车</title> </head> <body> <div id="app"></div> <template id="my-app"> <table> <thead> <th></th> <th>书籍名称</th> <th>出版日期</th> <th>价格</th> <th>购买数量</th> <th>操作</th> </thead> <tbody> <tr v-for="(book, index) in books" :key="book.id"> <td>{{index+1}}</td> <td>{{book.name}}</td> <td>{{book.date}}</td> <td>{{formatPrice(book.price)}}</td> <td> <button @click='decrease($event,book.id)' v-bind:disabled='book.count<=1'>-</button> {{book.count}} <button @click='increase($event,book.id)'>+</button> </td> <td> <button @click='remove($event,index)'>移除</button> </td> </tr> </tbody> </table> <h2>总价为:{{formatPrice(totalPrice)}}</h2> </template> <script src="../js/vue.js"></script> <script src="./index.js"></script> </body> </html>

index.js

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
Vue.createApp({ template:'#my-app', data() { return { books: [ { id: 1, name: '《算法导论》', date: '2006-9', price: 85.00, count: 1 }, { id: 2, name: '《UNIX编程艺术》', date: '2006-2', price: 59.00, count: 1 }, { id: 3, name: '《编程珠玑》', date: '2008-10', price: 39.00, count: 1 }, { id: 4, name: '《代码大全》', date: '2006-3', price: 128.00, count: 1 } ] } }, computed:{ //总价格使用计算属性 totalPrice(){ let totalPrice = 0 for(let book of this.books){ totalPrice += book.count*book.price } return totalPrice } }, methods: { //增加数量 increase(event,id){ this.books[id-1].count++ }, //减少数量 decrease(event,id){ this.books[id-1].count-- }, //移出书籍 remove(event,id){ console.log(id); this.books.splice(id,1) }, //用来给代码加上rmb符号 formatPrice(price) { return "¥" + price; } }, }).mount('#app')

index.css

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
table { border: 1px solid #e9e9e9; border-collapse: collapse; border-spacing: 0; } th, td { padding: 8px 16px; border: 1px solid #e9e9e9; text-align: left; } th { background-color: #f7f7f7; color: #5c6b77; font-weight: 600; } .counter { margin: 0 5px; }

输出效果

Honeycam 2021-07-27 12-05-16

v-model

原始方法实现v-model

原始方法实现v-model.html

h2中的数据和input显示的数据绑定

image-20210727190956938

监听input输入事件

image-20210727191053331

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <!-- 1.v-bind value的绑定 2.监听input事件, 更新message的值 --> <input type="text" :value="message" @input="inputChange"> <h2>{{message}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message: "Hello World" } }, methods: { inputChange(event) { this.message = event.target.value; } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

Honeycam 2021-07-27 19-11-44

v-model语法糖实现

v-model本质上是上面方法的语法糖.

v-model语法糖实现数据双向绑定.html

message数据的双向绑定

image-20210727191804574

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <input type="text" v-model="message"> <h2>{{message}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message: "Hello World" } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

绑定基本组件

绑定textarea

v-model绑定textarea.html

关键代码

image-20210728162431843

复制代码

intro: {{intro}}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <!-- 1.绑定textarea --> <label for="intro"> 自我介绍<br> <textarea name="intro" id="intro" cols="30" rows="10" v-model="intro"></textarea> </label> <h2>intro: {{intro}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { intro: "Hello World", } }, } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

image-20210728162625923

绑定单选框

绑定单选框.html

关键代码

image-20210728165508968

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <!-- 2.checkbox --> <!-- 2.1.单选框 --> <label for="agree"> <input id="agree" type="checkbox" v-model="isAgree"> 同意协议 </label> <h2>isAgree: {{isAgree}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { isAgree: false, } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

image-20210728165616426

绑定多选框

绑定多选框.html

多选框

image-20210728165935194

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <!-- 多选框 --> <span>你的爱好: </span> <label for="basketball"> <input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球 </label> <label for="football"> <input id="football" type="checkbox" v-model="hobbies" value="football"> 足球 </label> <label for="tennis"> <input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网球 </label> <h2>hobbies: {{hobbies}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { hobbies: ["basketball"], } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

image-20210728170035510

绑定单选按钮

绑定单选按钮.html

绑定单选按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <!-- radio --> <span>你的爱好: </span> <label for="male"> <input id="male" type="radio" v-model="gender" value="male"></label> <label for="female"> <input id="female" type="radio" v-model="gender" value="female"></label> <h2>gender: {{gender}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { gender: "", } }, } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

image-20210728170440417

绑定下拉框

绑定下拉框.html

下拉框

image-20210728170717286

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <!-- select --> <span>喜欢的水果: </span> <select v-model="fruit" multiple size="2"> <option value="apple">苹果</option> <option value="orange">橘子</option> <option value="banana">香蕉</option> </select> <h2>fruit: {{fruit}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { fruit: "orange" } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

image-20210728170808607

修饰符

lazy修饰符

lazy修饰符修饰的输入框.html

lazy模式

image-20210728172107593

普通模式和lazy模式的对比

image-20210728172143464

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> 普通模式<input type="text" v-model="message1"> <h2>{{message1}}</h2> <hr> lazy模式<input type="text" v-model.lazy="message2"> <h2>{{message2}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message1: "Hello World", message2: "Hello World" } }, } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

普通模式

实时输出.

lazy模式

先输入,只有按下enter键后,输入的内容才会显示其上.

Honeycam 2021-07-28 17-22-42

number修饰符

number修饰符.html

对比

image-20210728173641910

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2>普通的没有number修饰符</h2> <input type="text" v-model="message1"> <h2>{{message1}}</h2> <button @click="showType1">查看类型</button> <hr> <h2>有number修饰符</h2> <input type="text" v-model.number="message2"> <h2>{{message2}}</h2> <button @click="showType2">查看类型</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message1: "", message2: "" } }, methods: { showType1() { console.log(this.message1, typeof this.message1); }, showType2() { console.log(this.message2, typeof this.message2); }, } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出效果

Honeycam 2021-07-28 17-35-25

trim修饰符

trim修饰符能够去掉输入字符串前面和后面的空格.

trim修饰符.html

有trim和没有trim的对比

image-20210728175335627

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <h2>普通模式,没有trim修饰符</h2> <input type="text" v-model="message1"> <button @click="showResult1">查看结果</button> <hr> <h2>trim模式,有trim修饰符</h2> <input type="text" v-model.trim="message2"> <button @click="showResult2">查看结果</button> </template> <script src="../js/vue.js"></script> <script> const App = { template: '#my-app', data() { return { message1: "", message2: "" } }, methods: { showResult1() { console.log(this.message1); }, showResult2() { console.log(this.message2); } } } Vue.createApp(App).mount('#app'); </script> </body> </html>

输出

Honeycam 2021-07-28 17-51-47

组件化开发

全局组件

注册一个全局组件.html

组件的注册逻辑

从template中拿到id=component-a的组件内容,然后注册名为component-a的组件

image-20210728180523214

自定义组件的使用

image-20210728180952783

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <component-a></component-a> </template> <template id="component-a"> <h2>{{title}}</h2> <button @click="btnClick">按钮点击</button> </template> <script src="../js/vue.js"></script> <script> const app = Vue.createApp({ template: "#my-app", }); // 使用app注册一个全局组件app.component() // 全局组件: 意味着注册的这个组件可以在任何的组件模板中使用 app.component("component-a", { template: "#component-a", data() { return { title: "我是标题", } }, methods: { btnClick() { alert('clicked!') }, }, }); app.mount("#app"); </script> </body> </html>

多个组件

多个全局组件注册.html

两个组件的注册逻辑

image-20210729080010613

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <component-a></component-a> <hr> <component-b></component-b> </template> <template id="component-a"> <h1>组件一号</h1> <h2>{{title}}</h2> <p>{{desc}}</p> <button @click="btnClick">按钮点击</button> </template> <template id="component-b"> <h1>组件二号</h1> <input type="text" v-model="message"/> <h2>{{message}}</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: "#my-app", }; const app = Vue.createApp(App); // 使用app注册一个全局组件app.component() app.component("component-a", { template: "#component-a", data() { return { title: "我是标题", desc: "我是内容, 哈哈哈哈哈", }; }, methods: { btnClick() { console.log("按钮的点击"); }, }, }); app.component("component-b", { template: "#component-b", data() { return { message: "Hello World", }; }, }); app.mount("#app"); </script> </body> </html>

输出

image-20210729080240755

组件的命名

组件命名方法.html

注册组件大驼峰,引用组件下划线

image-20210729074806222

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <component-name></component-name> </template> <template id="component-c"> <h2>ComponentC</h2> </template> <script src="../js/vue.js"></script> <script> const App = { template: "#my-app", }; const app = Vue.createApp(App); // 使用app注册一个全局组件app.component() app.component('ComponentName', { template: "#component-c" }) app.mount("#app"); </script> </body> </html>

局部组件

局部组件.html

局部组件的注册逻辑

image-20210729081121846

全局组件和局部组件对比

image-20210729081307849

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<!DOCTYPE html> <html lang="zh"> <body> <div id="app"></div> <template id="my-app"> <component-a></component-a> </template> <template id="component-a"> <h2>我是组件A</h2> </template> <script src="../js/vue.js"></script> <script> //组件A的对象内容引入 const ComponentA = { template: "#component-a" } const App = { template: '#my-app', components: { // key: 组件名称 value: 组件对象 ComponentA: ComponentA }, data() { return { message: "Hello World" } } } const app = Vue.createApp(App); app.mount('#app'); </script> </body> </html>

基于Vue CLI组件化开发

关于Vue CLI的使用,在webpack的学习笔记中有详细的创建过程.

使用Vue CLI创建项目

命令行

1
2
vue create 03_learn_component_2

输出

自此一个使用vue脚手架的创建的就是已经创建好了

image-20210831160117984

image-20210831160535588

image-20210831160607981

创建一个总的组件

为了便于学习,不需要每次都是创建一个新的项目,我们现在把这个src文件夹的文件除了main.js,其他的文件都是删除,然后新建一个文件夹,用来存放第一个组件.

image-20210831161305804

安装下面这款插件:

image-20210831161820422

在App.vue中输入vbase之后,会自动生成如下代码:

image-20210831162124359

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template> <div id="app"> <div class="myheader"> <h2>Header</h2> <h2>Navebar</h2> </div> <div class="main"> <h2>Banner</h2> <ul> <li>product info 1</li> <li>product info 2</li> <li>product info 3</li> <li>product info 4</li> <li>product info 5</li> </ul> </div> <div class="footer"> <h2>Footer</h2> </div> </div> </template> <script> export default { } </script> <style scoped> </style>

main.js

1
2
3
4
5
6
import { createApp } from 'vue' import App from './01_组件的拆分和嵌套/App.vue' createApp(App).mount('#app')

输出

image-20210831163800019

组件拆分

在分别创建另外三个组件MyHeader.vue MyMain.vue MyFooter.vue ,然后将App.vue中组件分别拆分到三个组件当中.

image-20210831164320101

拆分过后需要在App.vue中引入其他组件:

App.vue中引入其他组件

三个步骤

导入,注册和使用

image-20210831164711924

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template> <div id="app"> <my-header></my-header> <my-main></my-main> <my-footer></my-footer> </div> </template> <script> import MyHeader from './MyHeader.vue' import MyMain from './MyMain.vue' import MyFooter from './MyFooter.vue' export default { components:{ MyHeader, MyMain, MyFooter } } </script> <style scoped> </style>

输出

image-20210831164753155

进一步组件拆分

MyMain.vue进一步拆分成两个组件

image-20210831165738577

MyMain.vue

组件注册错误

image-20210831165915903

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template> <div class="main"> <my-main-banner></my-main-banner> <my-main-product-list></my-main-product-list> </div> </template> <script> import MyMainBanner from "./MyMainBanner.vue"; import MyMainProductList from "./MyMainProductList.vue"; export default { components: { MyMainBanner, MyMainProductList, }, }; </script> <style lang="scss" scoped> </style>

输出OK了

image-20210831170005248

组件的CSS作用域

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template> <div> <h2>this is App.vue</h2> <hello-vue></hello-vue> </div> </template> <script> import HelloVue from "./HelloVue.vue"; export default { components: { HelloVue, }, }; </script> <style scoped> h2{ color: green; } </style>

HelloVue.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template> <h2>this HelloVue.vue</h2> </template> <script> export default { } </script> <style scoped> /* h2{ color:red } */ </style>

输出

image-20210831172113241

去掉HelloVue.vue的注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template> <h2>this HelloVue.vue</h2> </template> <script> export default { } </script> <style scoped> h2{ color:red } </style>

输出

紫色被注释掉了

image-20210831172516980

去掉去掉App.vue的style标签的scoped

image-20210831172753191

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template> <div> <h2>this is App.vue</h2> <hello-vue></hello-vue> </div> </template> <script> import HelloVue from "./HelloVue.vue"; export default { components: { HelloVue, }, }; </script> <style > h2{ color: green; } </style>

输出

达到了期望的效果

image-20210831172815880

总结

上面的实验说命令,这个当这个子组件没有自己的样式时候,父组件的样式会作用于子组件.我们希望这个父组件的样式就是仅仅作用域父组件,而作用域子组件,一般就是:尽量不适用html标签来作用样式,使用类名,实际开发就是使用的类

组件通信

组件通信中使用最为广泛的就是父子组件间的通信:

image-20210831173838262

父传子

image-20210831173929528

简而言之

子组件注册属性,然后父组件使用属性,也就是给这些属性赋值.

image-20210831174027406

属性形式_字符串数组

逻辑图

image-20210831175335122

子组件注册属性:ShowMsg.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template> <div> <h2>{{name}} : {{age}}</h2> </div> </template> <script> export default { props:['name','age'] } </script> <style lang="scss" scoped> </style>

父组件给属性赋值:App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template> <div> <show-msg name="zhuo" age=10></show-msg> </div> </template> <script> import ShowMsg from './ShowMsg.vue' export default { components:{ ShowMsg } } </script> <style scoped> </style>

输出

image-20210831174715945


除了直接赋值的方式也可以使用v-bind动态绑定属性:

image-20210831175924024

属性形式_对象

ShowMsg.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template> <div> <h2>{{name}} : {{age}}</h2> </div> </template> <script> export default { props:{ name:String, age:Number } } </script> <style lang="scss" scoped> </style>

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template> <div> <show-msg name="zhuo" age=10></show-msg> <show-msg :name="name" :age=age></show-msg> </div> </template> <script> import ShowMsg from './ShowMsg.vue' export default { components:{ ShowMsg }, data() { return { name:'bing', age:12 } }, } </script> <style scoped> </style>

输出

image-20210831215627449


required属性

image-20210831220155283

default属性

发现没传给这个age属性赋值,于是就使用默认值.

image-20210831220555287

其他写法

image-20210831221007262

image-20210831221131682

为什么对象的默认值必须通过一个工厂函数获取?

image-20210831221618979

非Prop的Attribute

image-20210831221938712

单根结点

传递一个没有定义的属性怎么样

image-20210831222629174

如何把属性绑定到目标的标签上面

image-20210831223536314

image-20210831222930778

如何去掉这个子组件根元素的属性

image-20210831223600634

image-20210831223428096

多根结点

image-20210831223745491

子传父

image-20210901103646154

注册事件_数组形式

计数器案例_无参数传递

代码逻辑

image-20210901105621090

父组件App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template> <div> <h2>current number:{{counter}}</h2> <counter-operation @add="addOne" @sub="subOne"></counter-operation> </div> </template> <script> import CounterOperation from './CounterOperation.vue' export default { components:{ CounterOperation }, data() { return { counter:0 } }, methods: { addOne(){ this.counter++ }, subOne(){ this.counter-- } }, } </script> <style scoped> </style>

子组件:CounterOperation.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template> <div> <button @click="addOne">+1</button> <button @click="subOne">-1</button> </div> </template> <script> export default { emits:['add','sub'], methods: { addOne(){ console.log("+1"); this.$emit('add') }, subOne(){ console.log("-1"); this.$emit('sub') } } } </script> <style lang="scss" scoped> </style>

输出

image-20210901105129239

计数器案例_传递参数

代码逻辑

image-20210901113015891

父组件App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template> <div> <h2>current number:{{counter}}</h2> <counter-operation @add="addOne" @sub="subOne" @addN="addN"> </counter-operation> </div> </template> <script> import CounterOperation from './CounterOperation.vue' export default { components:{ CounterOperation }, data() { return { counter:0 } }, methods: { addOne(){ this.counter++ }, subOne(){ this.counter-- }, addN(num){ console.log(num); this.counter += num } }, } </script> <style scoped> </style>

子组件CounterOperation.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<template> <div> <h2>current number:{{counter}}</h2> <counter-operation @add="addOne" @sub="subOne" @addN="addN"> </counter-operation> </div> </template> <script> import CounterOperation from './CounterOperation.vue' export default { components:{ CounterOperation }, data() { return { counter:0 } }, methods: { addOne(){ this.counter++ }, subOne(){ this.counter-- }, addN(num){ console.log(num); this.counter += num } }, } </script> <style scoped> </style>

输出效果

image-20210901113112689

注册事件_对象形式

上面的例子中都是使用数组形式>image-20210901113653680,对象的形式常用来进行参数检查.

子组件CounterOperation.vue

对象形式注册事件

主要是为了检查参数

image-20210901114756881

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template> <div> <button @click="addOne">+1</button> <button @click="subOne">-1</button> <input type="number" v-model.number="num"> <button @click="emitAddN">+N</button> </div> </template> <script> export default { // emits:['add','sub','addN'], emits:{ add:null, sum:null, addN:num =>{ console.log(num); if(num > 10){ return true }else{ return false } } }, data() { return { num:0 } }, methods: { addOne(){ console.log("+1"); this.$emit('add') }, subOne(){ console.log("-1"); this.$emit('sub') }, emitAddN(){ console.log("+n"); this.$emit('addN',this.num) } } } </script> <style lang="scss" scoped> </style>

输出效果

当我们传递过去的参数小于10的时候就会出现警告,只有大于10的时候,才能够正常运行

Honeycam 2021-09-01 11-43-26

组件通信案例

输出效果

Honeycam 2021-09-01 13-09-10

代码逻辑

image-20210901131807945

父组件:App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template> <div> <tab-control :titles="titles"></tab-control> </div> </template> <script> import TabControl from './TabControl.vue' export default { components:{ TabControl }, data() { return { titles:['衣服','鞋子','帽子'] } } } </script> <style scoped> </style>

子组件:TabControl.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<template> <div class="tab-control"> <div class="tab-control-item" :class="{active:currentIndex === index}" v-for='(title,index) in titles' :key="title" @click="itemClick(index)"> <span>{{title}}</span> </div> </div> </template> <script> export default { data(){ return{ currentIndex:0 } }, props:{ titles:{ type:Array, default(){ return ['title1','title2','title3'] } } }, methods: { itemClick(index){ this.currentIndex = index } }, } </script> <style scoped> .tab-control { display: flex; } .tab-control-item { flex: 1; text-align: center; } .tab-control-item.active { color: red; } .tab-control-item.active span { border-bottom:5px red solid; padding: 5px 10px; } </style>

非父子组件之间的通信

Provide/Inject

image-20210901151152804

背景

代码逻辑:爷爷和孙子

image-20210901152434689

通信

父组件:App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template> <div> <home></home> </div> </template> <script> import Home from './Home.vue' export default { components:{ Home }, provide:{ name:'bing', age:100 } } </script> <style lang="scss" scoped> </style>

儿子组件:Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template> <div> <home-content></home-content> </div> </template> <script> import HomeContent from "./HomeContent.vue" export default { components:{ HomeContent } } </script> <style lang="scss" scoped> </style>

孙子组件:HomeContent.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template> <div> this is homeContent--{{name}}--{{age}} </div> </template> <script> export default { inject:['name','age'] } </script> <style lang="scss" scoped> </style>

长辈组件和子孙组件间的通信逻辑图

image-20210901153236655

App.vue

image-20210901153305880

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template> <div> <home></home> </div> </template> <script> import Home from './Home.vue' export default { components:{ Home }, provide:{ name:'bing', age:100 } } </script> <style lang="scss" scoped> </style>

HomeContent.vue

image-20210901153337898

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template> <div> this is homeContent--{{name}}--{{age}} </div> </template> <script> export default { inject:['name','age'] } </script> <style lang="scss" scoped> </style>

问题_长辈组件在自己中是否使用provide中的数据

很显然是不能使用

image-20210901154018148

问题_长辈组件如何拿到自己data中的数据信息然后传给子孙组件

问题背景

image-20210901154728762

很显然,没有拿到.

原因分析

this的指向很是关键,我们知道在一个函数中,this的指向就是这个函数作用域,image-20210901154906107作用域里面的this.

image-20210901155113416,所以报错

解决方案

把provide写成函数形式,就是把这个作用域现在这个函数里面.

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template> <div> <home></home> </div> </template> <script> import Home from './Home.vue' console.log(this); export default { components:{ Home }, // provide:{ // name:'bing', // age:100, // namesLength:this.names.length // }, provide(){ return { name:'bing', age:100, namesLength:this.names.length } }, data(){ return{ names:['Alice','Bruce','Celina'] } } } </script> <style lang="scss" scoped> </style>

子孙组件:HomeContent.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template> <div> this is homeContent--{{name}}--{{age}}--{{namesLength}} </div> </template> <script> export default { inject:['name','age','namesLength'] } </script> <style lang="scss" scoped> </style>

输出

image-20210901160151645

问题_长辈组件传给子孙组件的是动态数据吗

上面的例子中,尽管我们给这个数组增加了内容,数组的长度发生了变化,但是这个传递被子孙组件的数据依然没有改变,其实很好理解,这个就是在第一次就是当成一个普通的值赋了过去.

image-20210901160748129

解决方案_动态传递数据

使用计算属性.

输出效果

数组的长度增加,这个传递给子孙组件的数据长度也是增加

Honeycam 2021-09-01 16-14-40

长辈组件:App.vue

关键代码

image-20210901161605116

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<template> <div> <home></home> <button @click="addName">array push</button> </div> </template> <script> import Home from './Home.vue' import {computed} from 'vue' export default { components:{ Home }, // provide:{ // name:'bing', // age:100, // namesLength:this.names.length // }, provide(){ return { name:'bing', age:100, namesLength:computed(()=>this.names.length) } }, data(){ return{ names:['Alice','Bruce','Celina'] } }, methods: { addName(){ console.log(this.names); this.names.push('bing') } }, } </script> <style lang="scss" scoped> </style>

警告

1
2
3
4
5
6
injected property "namesLength" is a ref and will be auto-unwrapped and no longer needs `.value` in the next minor release. To opt-in to the new behavior now, set `app.config.unwrapInjectedRef = true` (this config is temporary and will not be needed in the future.)

image-20210901161652837

警告的原因:

image-20210901162037542

子孙组件:HomeContent.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template> <div> this is homeContent--{{name}}--{{age}}--{{namesLength}} //警告出现,提示会自动解包 this is homeContent--{{name}}--{{age}}--{{namesLength.value}}//手动解包 </div> </template> <script> export default { inject:['name','age','namesLength'] } </script> <style lang="scss" scoped> </style>

Mitt全局事件总线

image-20210901193721449

https://github.com/developit/mitt

https://github.com/scottcorgan/tiny-emitter

安装mitt库

1
2
npm install mitt -D

背景

现在我想要在[image-20210901195441833]的事件

image-20210901195401987

代码逻辑

image-20210901200813961

输出效果

image-20210901200840207

About.vue

image-20210901200936233

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template> <div> this is about.vue <button @click="btnClick">about click</button> </div> </template> <script> import emitter from './utils/eventBus' export default { methods: { btnClick(){ console.log('About.vue is clicked') emitter.emit('aboutClicked',{name:'bing',age:20}) } }, } </script> <style lang="scss" scoped> </style>

HomeContent.vue

image-20210901200949971

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template> <div> this is home-content <hr> </div> </template> <script> import emitter from './utils/eventBus' export default { created() { emitter.on('aboutClicked',(info)=>{ console.log(info); }) }, } </script> <style lang="scss" scoped> </style>

多事件监听

About.vue

发送两个事件

image-20210901201744299

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template> <div> this is about.vue <button @click="btnClick">about click</button> <button @click="btnClick1">about click</button> </div> </template> <script> import emitter from './utils/eventBus' export default { methods: { btnClick(){ emitter.emit('aboutClicked',{name:'bing',age:20}) }, btnClick1(){ emitter.emit('aboutClicked1',{name:'bing1',age:201}) } }, } </script> <style lang="scss" scoped> </style>

HomeContent.vue

监听所有事件

打印事件名称和传递过来的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template> <div> this is home-content <hr> </div> </template> <script> import emitter from './utils/eventBus' export default { created() { // emitter.on('aboutClicked',(info)=>{ // console.log(info); // }) emitter.on('*',(eventName,info) => { console.log(eventName); console.log(info); }) }, } </script> <style lang="scss" scoped> </style>

输出效果

Honeycam 2021-09-01 20-16-40

事件取消

image-20210901202032989

插槽

image-20210902080325326

image-20210902080334501

比如说上面的NavBar的共性就是都是具有三个部分,左中右,不同就是左中右三个区域可以显示三个不同的内容.

image-20210902080400107

image-20210902080406632

插槽的基本使用

代码逻辑

有插入的内容显示插入的内容,没有插入的内容显示默认的内容

image-20210902083354620

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template> <div> <my-slot-cpn> <h4>我是插入插槽中的内容</h4> </my-slot-cpn> <my-slot-cpn> <button>我是插入插槽中的按钮</button> </my-slot-cpn> <my-slot-cpn></my-slot-cpn> </div> </template> <script> import MySlotCpn from './MySlotCpn.vue' export default { components:{ MySlotCpn } } </script> <style lang="scss" scoped> </style>

MySlotCpn.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template> <div> <h3>组件开始VVVVVV</h3> <slot>我是插槽中的默认内容</slot> <h3>组件结束YYYYYY</h3> <hr> </div> </template> <script> export default { } </script> <style lang="scss" scoped> </style>

多个插槽

一个内容插入多个插槽

我把一个元素插入多个插槽,发现是一个元素每个插槽都会被插入一次.

image-20210902084125039

三个元素插入三个插槽

我们三个元素插入三个插槽,是这个三个元素作为一个整体插入三个插槽,因此显示了9个,我们其实有点希望三个分别插入三个插槽,这个需要下面的具名插槽来实现.

image-20210902084723531

具名插槽

代码逻辑

通过名字来达到分别填入的效果

image-20210902090232359

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template> <div> <my-slot-cpn> <template v-slot:slot1> <h6>我是插入 第一个 插槽中的内容</h6> </template> <template v-slot:slot2> <button>我是想插入 第二个 插槽中的按钮</button> </template> <template v-slot:slot3> <h5>我是想插入 第三个 插槽的内容</h5> </template> </my-slot-cpn> <my-slot-cpn></my-slot-cpn> </div> </template> <script> import MySlotCpn from './MySlotCpn.vue' export default { components:{ MySlotCpn } } </script> <style lang="scss" scoped> </style>

MySlotCpn.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template> <div> <h3>组件开始VVVVVV</h3> <slot name="slot1"><h5>我是插槽中的默认内容1</h5></slot> <slot name="slot2"><h5>我是插槽中的默认内容2</h5></slot> <slot name="slot3"><h5>我是插槽中的默认内容3</h5></slot> <h3>组件结束YYYYYY</h3> <hr> </div> </template> <script> export default { } </script> <style lang="scss" scoped> </style>

动态具名插槽

代码逻辑

先把三个插槽的名字通过父传子的形式传递过去,然后再使用时候使用其对应的名字就是可以了.

image-20210902093931596

具名插槽的缩写

渲染作用域

image-20210902103947213

image-20210902103958919

作用域插槽

image-20210902104216458

代码逻辑

image-20210902113727678

App.vue

image-20210902113935569

image-20210902114006724

image-20210902114023685

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<template> <div> <show-name :names="names"></show-name> <hr> **v-slot="slotProps" and 默认插槽的省略写法,省略template** <show-name-slot :names="names" v-slot="slotProps"> <button>{{slotProps.index}}-{{slotProps.item}}</button> </show-name-slot> <hr> **v-slot="slotProps 默认插槽的省略写法"** <show-name-slot :names="names" > <template v-slot="slotProps"> <button>{{slotProps.index}}-{{slotProps.item}}</button> </template> </show-name-slot> <hr> **v-slot:default="slotProps" 默认插槽的使用** <show-name-slot :names="names" > <template v-slot:default="slotProps"> <button>{{slotProps.index}}-{{slotProps.item}}</button> </template> </show-name-slot> <hr> **v-slot:juming="slotProps" 具名插槽的使用** <show-name-slot :names="names" > <template v-slot:juming="slotProps"> <button>{{slotProps.index}}-{{slotProps.item}}</button> </template> </show-name-slot> </div> </template> <script> import ShowName from './ShowName.vue' import ShowNameSlot from './ShowNameSlot.vue' export default { components:{ ShowName, ShowNameSlot }, data() { return { names:['Alice','Bruce','Celina','Dora'] } }, } </script> <style scoped> </style>

ShowName.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template> <div> <ul> <li v-for="(name,index) in names" :key="index"> {{index}}--{{name}}</li> </ul> </div> </template> <script> export default { props:{ names:{ type:Array, default:()=>[] } } } </script> <style scoped> </style>

ShowNameSlot.vue

image-20210902114053256

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template> <div> <template v-for="(item,index) in names"> <slot :item='item' :index='index'> </slot> <slot name='juming' :item='item' :index='index'></slot> </template> </div> </template> <script> export default { props:{ names:{ type:Array, default:()=>[] }, } } </script> <style scoped> </style>

总结

image-20210902114310638

image-20210902114323633

动态组件

按钮切换案例

输出效果

Honeycam 2021-09-02 17-53-01

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active:currentTab === tab}" @click="btnClick(tab)"> {{tab}} </button> </div> </template> <script> export default { data() { return { tabs:['home','about','category'], currentTab:'home' } }, methods: { btnClick(tab){ this.currentTab = tab } }, } </script> <style scoped> .active { color: red; } </style>

按钮切换案例-动态组件

输出效果

Honeycam 2021-09-02 18-02-30

代码逻辑图

image-20210902181613344

App.vue

关键代码

image-20210902181947529

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active:currentTab === tab}" @click="btnClick(tab)"> {{tab}} </button> <component :is="currentTab"></component> </div> </template> <script> import Home from './Home.vue' import About from './About.vue' import Category from './Category.vue' export default { components:{ Home, About, Category }, data() { return { tabs:['home','about','category'], currentTab:'home' } }, methods: { btnClick(tab){ this.currentTab = tab } }, } </script> <style scoped> .active { color: red; } </style>

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template> <div> <h3>Home.vue</h3> </div> </template> <script> export default { name:'home' } </script> <style scoped> </style>

About.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template> <div> <h3>About.vue</h3> </div> </template> <script> export default { name:'about' } </script> <style scoped> </style>

Category.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template> <div> <h3>Category.vue</h3> </div> </template> <script> export default { name:'category' } </script> <style scoped> </style>

动态组件传递参数&发送事件

参数传递

输出效果

Honeycam 2021-09-02 19-17-44

代码逻辑图

image-20210902192346066

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active:currentTab === tab}" @click="btnClick(tab)"> {{tab}} </button> <component :is="currentTab" name='bing' :age='13'> </component> <hr> <home name='bingbing' :age='12'></home> </div> </template> <script> import Home from './Home.vue' import About from './About.vue' import Category from './Category.vue' export default { components:{ Home, About, Category }, data() { return { tabs:['home','about','category'], currentTab:'home' } }, methods: { btnClick(tab){ this.currentTab = tab } }, } </script> <style scoped> .active { color: red; } </style>

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template> <div> <h3>Home.vue</h3> <h4>{{name}}--{{age}}</h4> </div> </template> <script> export default { name:'home', props:{ name:{ type:String, default:'' }, age:{ type:Number, default:0 } } } </script> <style scoped> </style>

如何传递的参数由默认的字符串类型转换成数字类型

image-20210902191643082

监听事件

和上面的参数传递一样,都是写在component的属性里面.

App.vue

image-20210902192937747

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active:currentTab === tab}" @click="btnClick(tab)"> {{tab}} </button> <component :is="currentTab" name='bing' :age='13' @homeClick='homeClick'> </component> <hr> <home name='bingbing' :age='12'></home> </div> </template> <script> import Home from './Home.vue' import About from './About.vue' import Category from './Category.vue' export default { components:{ Home, About, Category }, data() { return { tabs:['home','about','category'], currentTab:'home' } }, methods: { btnClick(tab){ this.currentTab = tab }, homeClick(){ console.log('home.vue is clicked'); } }, } </script> <style scoped> .active { color: red; } </style>

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template> <div @click="divClick"> <h3>Home.vue</h3> <h4>{{name}}--{{age}}</h4> </div> </template> <script> export default { name:'home', props:{ name:{ type:String, default:'' }, age:{ type:Number, default:0 } }, emits:['homeClick'], methods: { divClick(){ this.$emit('homeClick') } }, } </script> <style scoped> </style>

keep-alive(缓存组件)

背景案例

在About页面增加一个按钮计数器.

输出效果

现在我们在About页面添加按钮计数器,然后切换页面我们发现这个计数的数值就是清零了.清零的原因是这个当我们切换界面的时候,这个界面实际上被销毁了,也就是生命周期结束了.

我们其实有点希望这个数值就是保存下来.

Honeycam 2021-09-02 19-36-06

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active:currentTab === tab}" @click="btnClick(tab)"> {{tab}} </button> <component :is="currentTab" name='bing' :age='13' @homeClick='homeClick'> </component> </div> </template> <script> import Home from './Home.vue' import About from './About.vue' import Category from './Category.vue' export default { components:{ Home, About, Category }, data() { return { tabs:['home','about','category'], currentTab:'home' } }, methods: { btnClick(tab){ this.currentTab = tab }, homeClick(){ console.log('home.vue is clicked'); } }, } </script> <style scoped> .active { color: red; } </style>

About.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template> <div> <h3>About.vue</h3> <button @click="increase">{{counter}}</button> </div> </template> <script> export default { name:'about', data() { return { counter:0 } }, methods: { increase(){ this.counter ++ } }, } </script> <style scoped> </style>

基本使用案例

输出效果

现在即使是切换界面,这个计数器按钮的数值仍然能够保持不变

Honeycam 2021-09-02 19-42-05

仅仅是增加了一行代码

image-20210902194412552

App.vue

image-20210902194306147

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active:currentTab === tab}" @click="btnClick(tab)"> {{tab}} </button> <keep-alive> <component :is="currentTab" name='bing' :age='13' @homeClick='homeClick'> </component> </keep-alive> </div> </template> <script> import Home from './Home.vue' import About from './About.vue' import Category from './Category.vue' export default { components:{ Home, About, Category }, data() { return { tabs:['home','about','category'], currentTab:'home' } }, methods: { btnClick(tab){ this.currentTab = tab }, homeClick(){ console.log('home.vue is clicked'); } }, } </script> <style scoped> .active { color: red; } </style>

keep-alive的属性

image-20210902194617101

include

输出效果

about界面的计数器是能够保持不变的,category界面的计数器不能

Honeycam 2021-09-02 19-51-57

App.vue

关键代码

image-20210902195336274

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<template> <div> <button v-for="tab in tabs" :key="tab" :class="{active:currentTab === tab}" @click="btnClick(tab)"> {{tab}} </button> <keep-alive include="about"> <component :is="currentTab" name='bing' :age='13' @homeClick='homeClick'> </component> </keep-alive> </div> </template> <script> import Home from './Home.vue' import About from './About.vue' import Category from './Category.vue' export default { components:{ Home, About, Category }, data() { return { tabs:['home','about','category'], currentTab:'home' } }, methods: { btnClick(tab){ this.currentTab = tab }, homeClick(){ console.log('home.vue is clicked'); } }, } </script> <style scoped> .active { color: red; } </style>

Webpack的代码分包&异步组件

为什么需要分包

image-20210902215301849

如何分包

image-20210902215345581

默认的打包情况

image-20210902220448375


现在我们想要把自己写的某些代码也是单独打包,怎么办呢?

JS中的代码分包

代码逻辑图

代码的引用逻辑

image-20210902222212366

image-20210902221725155

main.js

image-20210902222432042

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createApp } from 'vue' import App from './12_异步组件的使用/App.vue' //以前的引入方式和使用方法 // import {sum} from './12_异步组件的使用/utils/math' // console.log(sum(100,200)); //现在的引入方式和使用方法 import('./12_异步组件的使用/utils/math').then(res => { console.log(res.sum(200,300)); }) createApp(App).mount('#app')

Vue组件中实现异步组件(代码分包)

工厂函数形式

新旧导入组件的对比

image-20210902223453793

App.vue

关键代码

image-20210902223542237

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template> <div> <home></home> </div> </template> <script> import Home from './Home.vue' //以前的导入方式 // import AsyncCategory from './AsyncCategory.vue' //现在的导入方式 首先导入一个vue中的函数 import {defineAsyncComponent} from 'vue' const AsyncCategory = defineAsyncComponent(() => import('./AsyncCategory.vue')) export default { components:{ Home, AsyncCategory } } </script> <style scoped> </style>

对象形式

其实使用对象形式主要是为了更多的属性.

导入型的对比

image-20210902224129100

对象形式中常用的属性

image-20210902224236801

异步组件和suspense

image-20210902224537657

输出效果

Honeycam 2021-09-02 23-07-11

App.vue

关键代码

image-20210902230950477

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template> <div> <suspense> <template #default> <async-category></async-category> </template> <template #fallback> <home></home> </template> </suspense> </div> </template> <script> import Home from './Home.vue' //现在的导入方式-工厂函数形式 首先导入一个vue中的函数 import {defineAsyncComponent} from 'vue' const AsyncCategory = defineAsyncComponent(() => import('./AsyncCategory.vue')) export default { components:{ Home, AsyncCategory, } } </script> <style scoped> </style>

$refs的使用

vue中的DOM操作

不推荐使用document.getElement…方法或jQuery等等来操作Vue中的dom元素,因为vue中已经帮我们封装好了操作dom元素的函数,我在使用以前的方法,就是显得很笨拙.

image-20210902231209507

获取元素

输出效果

Honeycam 2021-09-02 23-20-37

App.vue

关键代码

image-20210902232153303

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template> <div> <h2 ref="titleH2">我是被获取的元素</h2> <button @click="getElement">获取自己中的元素</button> </div> </template> <script> export default { methods: { getElement(){ console.log(this.$refs.titleH2); } }, } </script> <style scoped> </style>

获取组件

输出效果

Honeycam 2021-09-02 23-43-49

代码的数据流

也就是说通过$ref不仅仅可以或这个组件,还可以获取这个组件里面的东西.

image-20210902234139722

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template> <div> <h2 ref="titleH2">我是被获取的元素</h2> <button @click="getElement">获取自己中的元素</button> <hr> <home ref="homeCpn"></home> <button @click="getCpn">获取自己中的元素</button> </div> </template> <script> import Home from './Home.vue' export default { components:{ Home }, methods: { getElement(){ console.log(this.$refs.titleH2); }, getCpn(){ console.log(this.$refs.homeCpn); console.log(this.$refs.homeCpn.$el); console.log(this.$refs.homeCpn.name); this.$refs.homeCpn.printName() } }, } </script> <style scoped> </style>

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template> <div> <h2>我是被获取的组件</h2> </div> </template> <script> export default { data(){ return{ name:'我是home组件中的数据' } }, methods: { printName(){ console.log('执行了home组件中的函数') } }, } </script> <style scoped> </style>

$parent$root

image-20210902234757801

生命周期

image-20210902234909428

image-20210902234917264

生命周期流程

img

缓存组件的生命周期

image-20210903083613534

首先看看缓存组件和非缓存组件的区别

这里的About是缓存组件,Category是非缓存组件,

缓存组件的只会一行一次created(),然后就什么都不执行了,

而非非缓存组件created()和unmounted()只要切换就是会执行.

我们现在希望这个非缓存组件且能够频繁执行某些生命周期函数.

Honeycam 2021-09-03 08-18-27

期望的输出效果

Honeycam 2021-09-03 08-33-10

About.vue

关键代码

image-20210903083407953

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template> <div> <h3>About.vue</h3> <button @click="increase">{{counter}}</button> </div> </template> <script> export default { name:'about', data() { return { counter:0 } }, methods: { increase(){ this.counter ++ } }, created() { console.log('about is created') }, unmounted() { console.log('about is unmounted') }, activated() { console.log('about is actived') }, deactivated() { console.log('about is deactived') }, } </script> <style scoped> </style>

组件的v-model

基本使用

输出效果

Honeycam 2021-09-03 09-11-32

代码逻辑图

image-20210903092525155

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template> <div> <h2>普通元素的v-model</h2> <input v-model="msg"> <!-- <input v-bind:value="msg" @input="msg = $event.target.value"> --> <h3>{{msg}}</h3> <!-- ------------------------------------------------------------------------------------------ --> <hr> <h2>组件的v-model</h2> <my-input v-model="msg"></my-input> <!-- 等价于 --> <!-- <my-input :modelValue="msg" @update:modelValue="msg = $event"></my-input> --> </div> </template> <script> import MyInput from './MyInput.vue' export default { components:{ MyInput }, data() { return { msg:'' } }, } </script> <style scoped> </style>

MyInput.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template> <div> <input type="text" :value="modelValue" @input="sendInputEvent"> <h3>{{modelValue}}</h3> </div> </template> <script> export default { props:{ modelValue:String }, emits:['update:modelValue'], methods: { sendInputEvent(event){ this.$emit("update:modelValue", event.target.value); } }, } </script> <style scoped> </style>

优化

上面的例子,虽然实现了组件v-model的基本使用,但是还存在一些小的问题,比如’image-20210903093008466'我们这里的双向绑定,竟然绑定的是属性,这显然是不合逻辑的,因为这里面一般都是存放从父组件传递过来的数据,我们希望这些数据保持原样,子组件最好不要修改.这个就是时候我们就希望把这些数据copy一份,然后再次基础上进行操作,从而也降低了组件间的耦合性,于是就想到了计算属性.

代码逻辑

image-20210903143226532

MyInput.vue

双向绑定

image-20210903143314487

计算属性间接属性的值

image-20210903143345027

给父组件发送触发事件

image-20210903143416246

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template> <div> <input type="text" v-model="valueFromPar" > <h3>{{valueFromPar}}</h3> </div> </template> <script> export default { props:{ modelValue:String }, emits:['update:modelValue'], computed:{ valueFromPar:{ set(value){ this.$emit("update:modelValue", value); }, get(){ return this.modelValue } } } } </script> <style scoped> </style>

绑定多个v-model

代码逻辑图

image-20210903180040823

image-20210903181007534

普通标签上面不支持绑定多个v-model

Honeycam 2021-09-03 18-01-57

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template> <div> <span>msg: </span><input v-model="msg" > <h3>{{msg}}</h3> <span>msg2: </span><input v-model="msg2" > <h3>{{msg2}}</h3> <hr> <h2>组件的v-model</h2> <my-input v-model="msg" v-model:modelValue2="msg2"></my-input> </div> </template> <script> import MyInput from './MyInput.vue' export default { components:{ MyInput }, data() { return { msg:'msg', msg2:'msg2' } }, } </script> <style scoped> </style>

MyInput.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template> <div> <span>msg: </span><input type="text" v-model="valueFromPar" > <h3>{{valueFromPar}}</h3> <span>msg2: </span><input type="text" v-model="valueFromPar2" > <h5>{{valueFromPar2}}</h5> </div> </template> <script> export default { props:{ modelValue:String, modelValue2:String }, emits:['update:modelValue','update:modelValue2'], computed:{ valueFromPar:{ set(value){ this.$emit("update:modelValue", value); }, get(){ return this.modelValue } }, valueFromPar2:{ set(value){ this.$emit("update:modelValue2", value); }, get(){ return this.modelValue2 } } } } </script> <style scoped> </style>

Vue3过渡&动画实现

认识动画

image-20210903205400418

案例_hello world的显示和隐藏

没有动画效果

输出效果

Honeycam 2021-09-03 21-09-26

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template> <div> <button @click="isShow = !isShow">toggle</button> <h2 v-if="isShow">hello world</h2> </div> </template> <script> export default { data() { return { isShow:true } }, } </script> <style scoped> </style>

加入动画效果

输出效果

Honeycam 2021-09-03 21-14-40

App.vue

transition标签将要显示或隐藏的内容包裹起来

image-20210903211638053

根据其的名字来设置css样式

image-20210903211729110

可以省略的代码

将这个元素完全显示出来他的opacity默认就是1,所以可以省略不写,也是可以的.

image-20210903212458998

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template> <div> <button @click="isShow = !isShow">toggle</button> <transition name="hw"> <h2 v-if="isShow">hello world</h2> </transition> </div> </template> <script> export default { data() { return { isShow:true } }, } </script> <style scoped> .hw-enter-from, .hw-leave-to{ opacity:0 } .hw-enter-to, .hw-leave-from{ opacity:1 } .hw-enter-active, .hw-leave-active{ transition:opacity 2s ease; } </style>

transition组件的原理

过渡动画class

image-20210903212012433

class的添加或删除的时机

class的name命名规则

image-20210903212157266

animation动画

输出效果

Honeycam 2021-09-03 21-31-16

App.vue

image-20210903213249418

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<template> <div> <button @click="isShow = !isShow">toggle</button> <hr> <transition name="hw"> <h2 class='hello' v-if="isShow">hello world</h2> </transition> </div> </template> <script> export default { data() { return { isShow:true } }, } </script> <style scoped> .hello { display: inline-block; } .hw-enter-active { animation:bounce 2s ease } .hw-leave-active { animation:bounce 2s ease reverse } @keyframes bounce { 0% { transform: scale(0) } 50% { transform: scale(1.2); } 100% { transform: scale(1); } } </style>

transition的属性

type属性

image-20210903214128610

duration属性

image-20210903214201456

mode属性(常用多个元素切换)

image-20210903214259553

存在的问题

两个元素进行切换的时候,发现前面一个元素还没有离开,后面的元素就是进来了,然后前面的元素完全离开,后面的元素才会过来占位,有种一卡一卡的感觉.

Honeycam 2021-09-03 21-45-06

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<template> <div> <button @click="isShow = !isShow">toggle</button> <hr> <transition name="hw"> <h2 class='hello' v-if="isShow">hello world</h2> <h2 class='hello' v-else>I'am computer</h2> </transition> </div> </template> <script> export default { data() { return { isShow:true } }, } </script> <style scoped> .hello { display: inline-block; } .hw-enter-from, .hw-leave-to{ opacity:0 } .hw-enter-to, .hw-leave-from{ opacity:1 } .hw-enter-active, .hw-leave-active{ transition:opacity 1s ease; } </style>

改进后的输出效果

Honeycam 2021-09-03 21-47-28

App.vue

mode的模式

先让前面的元素离开,然后再进来.就是这种丝滑的感觉.

我们由此可知前面的卡卡的感觉,先让后面的元素进来,等待前面的元素离去,就是in-out了

image-20210903214809239

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<template> <div> <button @click="isShow = !isShow">toggle</button> <hr> <transition name="hw" mode="out-in"> <h2 class='hello' v-if="isShow">hello world</h2> <h2 class='hello' v-else>I'am computer</h2> </transition> </div> </template> <script> export default { data() { return { isShow:true } }, } </script> <style scoped> .hello { display: inline-block; } .hw-enter-from, .hw-leave-to{ opacity:0 } .hw-enter-to, .hw-leave-from{ opacity:1 } .hw-enter-active, .hw-leave-active{ transition:opacity 1s ease; } </style>

appear属性

image-20210903215839918

下面的例子是一个组件的例子和普通元素差不多.

输出效果

Honeycam 2021-09-03 21-57-11

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<template> <div> <button @click="isShow = !isShow">toggle</button> <hr> <transition name="hw" mode="out-in" appear="true"> <component :is="isShow?'home':'about'"></component> </transition> </div> </template> <script> import About from './pages/About.vue' import Home from './pages/Home.vue' export default { components:{ About, Home }, data() { return { isShow:true } }, } </script> <style scoped> .hello { display: inline-block; } .hw-enter-from, .hw-leave-to{ opacity:0 } .hw-enter-to, .hw-leave-from{ opacity:1 } .hw-enter-active, .hw-leave-active{ transition:opacity 1s ease; } </style>

animate.css动画

https://animate.style/

http://www.animate.net.cn/

https://www.dowebok.com/demo/2014/98/

介绍

image-20210904101921222

如何使用

image-20210904101931910

安装animate.css

命令行输入

1
2
npm install animate.css

输出效果

image-20210904102853520

导入animate.css

image-20210904103021235

基本使用

输出效果

Honeycam 2021-09-04 10-54-42

App.vue

关键代码

image-20210904110110664

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<template> <div> <button @click="isShow = !isShow">toggle</button> <hr> <transition name="hw"> <h2 class='title' v-if="isShow">hello world</h2> </transition> </div> </template> <script> export default { data() { return { isShow:true } }, } </script> <style scoped> .title { text-align: center; } .hw-enter-active { animation: bounceInDown 2s ease } .hw-leave-active { animation: bounceOutDown 2s ease } </style>

自定义过渡class_使用animate中的类

image-20210904110356246

输出效果

和上面的例子中输出效果一样,只不过这里使用了不同的方式

Honeycam 2021-09-04 10-54-42

代码逻辑

image-20210904111251676

App.vue

关键代码

image-20210904111441998

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template> <div> <button @click="isShow = !isShow">toggle</button> <hr> <transition name="hw" enter-active-class="animate__animated animate__bounceInDown" leave-active-class="animate__animated animate__bounceOutDown"> <h2 class='title' v-if="isShow">hello world</h2> </transition> </div> </template> <script> export default { data() { return { isShow:true } }, } </script> <style scoped> .title { text-align: center; } </style>

小修改

有时候我们对第三方库的动画效果进行小小的修改,比如说添加个reverse.

输出效果

Honeycam 2021-09-04 11-51-29

App.vue

关键代码

image-20210904115644050

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template> <div> <button @click="isShow = !isShow">toggle</button> <hr> <transition name="hw" enter-active-class="animate__animated animate__bounceInDown" leave-active-class="animate__animated animate__bounceOutDown"> <h2 class='title' v-if="isShow">hello world</h2> </transition> </div> </template> <script> export default { data() { return { isShow:true } }, } </script> <style scoped> .title { text-align: center; } .animate__bounceOutDown { animation-direction: reverse; } </style>

自定义过渡class_使用自己定义的类

输出效果

Honeycam 2021-09-04 11-31-27

关键代码

image-20210904113304476

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template> <div> <button @click="isShow = !isShow">toggle</button> <hr> <transition name="hw" enter-active-class="animate__animated animate__bounceInDown" leave-active-class="animate__animated my__animate__drop"> <h2 class='title' v-if="isShow">hello world</h2> </transition> </div> </template> <script> export default { data() { return { isShow:true } }, } </script> <style scoped> .title { text-align: center; } .my__animate__drop { animation:hinge 2s ease; } </style>

gsap库

https://www.npmjs.com/package/gsap

https://greensock.com/

介绍

image-20210904115745446

如何使用

image-20210904115759948

安装gsap库

命令行

1
2
npm install gsap

JavaScript钩子

image-20210904120648513

输出效果

Honeycam 2021-09-04 12-09-38

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<template> <div> <button @click="isShow = !isShow">toggle</button> <hr /> <transition @before-enter="beforeEnter" @enter="enter" @after-enter="afterEnter" @before-leave="beforeLeave" @leave="leave" @afterLeave="afterLeave" > <h2 class="title" v-if="isShow">hello world</h2> </transition> </div> </template> <script> export default { data() { return { isShow: true, }; }, methods: { beforeEnter() { console.log("beforeEnter"); }, enter() { console.log("enter"); }, afterEnter() { console.log("afterEnter"); }, beforeLeave() { console.log("beforeLeave"); }, leave() { console.log("leave"); }, afterLeave() { console.log("afterLeave"); }, }, }; </script> <style scoped> .title { text-align: center; } </style>

基本使用gsap

参考网址: https://greensock.com/get-started/

输出

Honeycam 2021-09-04 12-30-31

代码逻辑

image-20210904122931969

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<template> <div> <button @click="isShow = !isShow">toggle</button> <hr /> <transition @enter="enter" @leave="leave" > <h2 class="title" v-if="isShow">hello world</h2> </transition> </div> </template> <script> import gsap from 'gsap' export default { data() { return { isShow: true, }; }, methods: { enter(el,done) { console.log("enter"); gsap.from(el,{ scale:0, x:200, onComplete:done }) }, leave(el,done) { console.log("leave"); gsap.to(el,{ scale:0, x:200, onComplete:done }) }, }, }; </script> <style scoped> .title { text-align: center; } </style>

gsap实现数字增长效果

输出

Honeycam 2021-09-04 12-43-58

App.vue

关键代码

image-20210904124615598

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template> <div> <input type="number" step=100 v-model="counter"> <hr> <h2>{{showCounter.toFixed(0)}}</h2> </div> </template> <script> import gsap from 'gsap' export default { data() { return { counter:0, showCounter:0 }; }, watch:{ counter(newValue){ gsap.to(this,{ duration:1, showCounter:newValue }) } } }; </script> <style scoped> .title { text-align: center; } </style>

列表的过渡

image-20210904140347108

image-20210904140409165

App.vue

image-20210904143325296

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<template> <div> <button @click="addNum">添加数字</button> <button @click="removeNum">删除数字</button> <transition-group tag="p" name="why"> <span v-for="item in numbers" :key="item" class="item"> {{item}} </span> </transition-group> </div> </template> <script> export default { data() { return { numbers:[0,1,2,3,4,5,6,7,8,9], } }, methods: { addNum() { this.numbers.splice(this.randomIndex(), 0, this.numCounter) }, removeNum() { this.numbers.splice(this.randomIndex(), 1) }, randomIndex() { return Math.floor(Math.random() * this.numbers.length) }, }, computed:{ numCounter(){ return this.numbers.length } } } </script> <style scoped> .item { margin-right: 10px; display: inline-block; } .why-enter-from, .why-leave-to { opacity: 0; transform: translateY(30px); } .why-enter-active, .why-leave-active { transition: all 1s ease; } </style>

输出效果与不足

现在的情况是添加数字和删除数字都是具有动画的,而剩余在数组中的其他数字占位却显得很生硬,

Honeycam 2021-09-04 14-20-39

尝试在App.vue添加如下代码:

App.vue添加的代码

1
2
3
4
5
6
<style scoped> .why-move { transition: transform 1s ease; } </style>

输出效果

现在的情况是添加数字时,无论是添加的数字还是移动的数字都是具有动画的,而删除数字时,删除的数字有动画,但是移动的数字很生硬.原因是因为当一个数字被删除时,这个动画还没有执行完成,这个元素仍然是标准流中的元素,所以会占位.我们现在希望这个删除的时候就是不要占位,因此可以将其删除时候的属性设置为absolute,脱离标准流.

Honeycam 2021-09-04 14-26-39

在App.vue继续增加如下代码

1
2
3
4
5
6
<style scoped> .why-leave-active { position: absolute; } </style>

输出效果

输出效果还可以,无论删除或增加数字,以及由此引起的数字的移动,都是具有动画的.

Honeycam 2021-09-04 14-31-32

完整App.vue代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<template> <div> <button @click="addNum">添加数字</button> <button @click="removeNum">删除数字</button> <transition-group tag="p" name="why"> <span v-for="item in numbers" :key="item" class="item"> {{item}} </span> </transition-group> </div> </template> <script> export default { data() { return { numbers:[0,1,2,3,4,5,6,7,8,9], } }, methods: { addNum() { this.numbers.splice(this.randomIndex(), 0, this.numCounter) }, removeNum() { this.numbers.splice(this.randomIndex(), 1) }, randomIndex() { return Math.floor(Math.random() * this.numbers.length) }, }, computed:{ numCounter(){ return this.numbers.length } } } </script> <style scoped> .item { margin-right: 10px; display: inline-block; } .why-enter-from, .why-leave-to { opacity: 0; transform: translateY(30px); } .why-enter-active, .why-leave-active { transition: all 1s ease; } .why-leave-active { position: absolute; } .why-move { transition: transform 1s ease; } </style>

列表的交错过渡

代码逻辑图

image-20210904152410102

App.vue

HTML5中数据传递的方式

image-20210904152535698

数组的过滤

image-20210904152619618

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<template> <div> <input v-model="keyword"> <transition-group tag="ul" name="why" :css="false" @before-enter="beforeEnter" @enter="enter" @leave="leave"> <li v-for="(item, index) in showNames" :key="item" :data-index="index"> {{item}} </li> </transition-group> </div> </template> <script> import gsap from 'gsap'; export default { data() { return { names: ["abc", "cba", "nba", "why", "lilei", "hmm", "kobe", "james"], keyword: "" } }, computed: { showNames() { return this.names.filter(item => item.indexOf(this.keyword) !== -1) } }, methods: { beforeEnter(el) { el.style.opacity = 0; el.style.height = 0; }, enter(el, done) { gsap.to(el, { opacity: 1, height: "1.5em", delay: el.dataset.index * 0.5, onComplete: done }) }, leave(el, done) { gsap.to(el, { opacity: 0, height: 0, delay: el.dataset.index * 0.5, onComplete: done }) } } } </script> <style scoped> </style>

输出

Honeycam 2021-09-04 15-27-32

数字洗牌

用到了了一个第三方库,首先安装这个第三方库,在命令行输入:

1
2
npm install lodash

然后再App.vue中使用:

App.vue

image-20210904153155982

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
<template> <div> <button @click="addNum">添加数字</button> <button @click="removeNum">删除数字</button> <button @click="shuffleNum">数字洗牌</button> <transition-group tag="p" name="why"> <span v-for="item in numbers" :key="item" class="item"> {{item}} </span> </transition-group> </div> </template> <script> import _ from 'lodash'; export default { data() { return { numbers: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], numCounter: 10 } }, methods: { addNum() { this.numbers.splice(this.randomIndex(), 0, this.numCounter++) }, removeNum() { this.numbers.splice(this.randomIndex(), 1) }, shuffleNum() { this.numbers = _.shuffle(this.numbers); }, randomIndex() { return Math.floor(Math.random() * this.numbers.length) } }, } </script> <style scoped> .item { margin-right: 10px; display: inline-block; } .why-enter-from, .why-leave-to { opacity: 0; transform: translateY(30px); } .why-enter-active, .why-leave-active { transition: all 1s ease; } .why-leave-active { position: absolute; } .why-move { transition: transform 1s ease; } </style>

输出效果

Honeycam 2021-09-04 15-32-27

Mixin

认识Mixin

关键就是把组件中相同的代码逻辑进行抽取.

image-20210905090253135

image-20210905090308874

Mixin的基本使用

export default和export两种不同的导出方式

代码逻辑图

image-20210905121512469

mixins>demoMixin.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
export const demoMixin = { data() { return { msg:'this is mixin message', msg2:'this is mixin message2' } }, methods: { mixinFunc(){ console.log('this is mixin function') }, mixinAndApp(){ console.log('mixinAndApp function in the demoMixin.js'); } }, created() { console.log('mixin created') }, }

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template> <div> <h4>demoMixin中的msg--{{msg}}</h4> <button @click="mixinFunc">mixFunc</button> <hr> <h4>demoMixin和App.vue中都有的msg2--{{msg2}}</h4> <button @click="mixinAndApp">mixinAndApp</button> <hr> <h4>App.vue中的appData--{{appData}}</h4> <button @click="appFunc">appFunc</button> </div> </template> <script> import {demoMixin} from './mixins/demoMixin' export default { mixins:[demoMixin], data() { return { msg2:'this is App.vue message2', appData:"this is App.vue appData" } }, methods: { mixinAndApp(){ console.log('mixinAndApp function but in the App.vue'); }, appFunc(){ console.log('appFunc function in the App.vue'); } }, created() { console.log('App.vue is created'); }, } </script> <style scoped> </style>

输出效果

Mixin的合并规则

image-20210905121749751

全局混入Mixin

image-20210905121949721

代码逻辑

image-20210905125848529

main.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { createApp } from 'vue' import App from './02_mixin的全局混入/App.vue' const app = createApp(App) app.mixin({ data() { return { msgInMain:'msg data in main.js' } }, }) app.mount('#app')

extends

image-20210905144751658

Options API的弊端

image-20210905144954599

image-20210905145005765

Composition API

image-20210905145115972

setup函数的参数

image-20210905145521157

props

image-20210905145534732

通过props拿到父组件传递过来的数据

父组件:App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template> <div> App <hr> <home paraFromParent="hello my son"></home> </div> </template> <script> import Home from './Home.vue' export default { components:{ Home } } </script> <style scoped> </style>

子组件:Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template> <div> Home </div> </template> <script> export default { props:{ paraFromParent:{ type:String, required:true } }, setup(props) { console.log(props.paraFromParent); //=>hello my son console.log(this); //=>undefined // console.log(this.paraFromParent);//=>报错 } } </script> <style scoped> </style>

输出

  1. setup()不能使用this
  2. 父组件传递过来的数据通过props拿到.

image-20210905150812015

context

image-20210905145546333

非prop的attribute

代码的逻辑

image-20210905152107245

父组件:App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template> <div> App <hr> <home paraFromParent="hello my son" class="helloHome" id="helloHome_1" ></home> </div> </template> <script> import Home from './Home.vue' export default { components:{ Home } } </script> <style scoped> </style>

子组件:Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template> <div> Home </div> </template> <script> export default { props:{ paraFromParent:{ type:String, required:true } }, setup(props,context) { console.log(props.paraFromParent); //=>hello my son console.log(context.attrs)Proxy //=>{class: "helloHome", id: "helloHome_1", __vInternal: 1} console.log(context.attrs.class)//=>helloHome console.log(context.attrs.id)//=>helloHome_1 } } </script> <style scoped> </style>

子组件:Home.vue

解构赋值

image-20210905163010312

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template> <div> Home </div> </template> <script> export default { props:{ paraFromParent:{ type:String, required:true } }, setup(props,{attrs,slots,emit}) { console.log(props.paraFromParent) console.log(attrs) console.log(slots) console.log(emit) } } </script> <style scoped> </style>

setup函数的返回值

代码逻辑图

setup的返回值

setup的返回值可以在template中使用.

存在的问题

虽然数字能够绑定到mustache语法中,但是数据不是动态绑定的.

image-20210905165224971

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template> <div> Home <h3>msg: {{msg}}</h3> <h4>counter: {{counter}}</h4> <button @click="increment">+</button> </div> </template> <script> export default { props:{ paraFromParent:{ type:String, required:true } }, setup(props,{attrs,slots,emit}) { let counter = 100 const increment = () => { counter++; console.log(counter); }; return { msg:'I am Home data msg', counter, increment } } } </script> <style scoped> </style>

补充

在setup中的函数也要返回

Honeycam 2021-09-05 17-00-34

Reactive API_动态绑定数据

代码逻辑图

image-20210905171757726

Home.vue

关键代码

image-20210905171852390

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template> <div> Home <h4>counter: {{dyCounter.counter}}</h4> <button @click="increment">+</button> </div> </template> <script> import {reactive} from 'vue' export default { props:{ paraFromParent:{ type:String, required:true } }, setup(props,{attrs,slots,emit}) { const dyCounter = reactive({ counter:100 }) const increment = () => { dyCounter.counter++; }; return { dyCounter, increment } } } </script> <style scoped> </style>

输出

Honeycam 2021-09-05 17-20-06

Ref API

image-20210905172258235

代码逻辑图

image-20210905173353290

Home.vue

关键代码

image-20210905173553886

自动解包功能

自动解包功能,本来这里应该填写counter.value,vue帮我们做了自动解析,所以这里填写counter也是可以的.

image-20210905173709717

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template> <div> Home <h4>counter: {{counter}}</h4> <button @click="increment">+</button> </div> </template> <script> import {ref} from 'vue' export default { props:{ paraFromParent:{ type:String, required:true } }, setup(props,{attrs,slots,emit}) { let counter = ref(100) const increment = () => { counter.value ++ } return { counter, increment } } } </script> <style scoped> </style>

自动解包的层数

image-20210905172329836

代码的逻辑图

image-20210905174821639

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template> <div> Home <h4>counter: {{info}}</h4> <h4>info.counter: {{info.counter}}</h4> <h4>info.counter.value: {{info.counter.value}}</h4> <button @click="increment">+</button> </div> </template> <script> import {ref} from 'vue' export default { props:{ paraFromParent:{ type:String, required:true } }, setup(props,{attrs,slots,emit}) { let counter = ref(100) const info = { counter } const increment = () => { info.counter.value ++ } return { info, increment } } } </script> <style scoped> </style>

readonly

image-20210905175141055

image-20210905175154899

关键代码

image-20210905175953303

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template> <div> Home <h4>rCounter: {{rCounter}}</h4> <button @click="increment">+</button> </div> </template> <script> import {ref, readonly} from 'vue' export default { props:{ paraFromParent:{ type:String, required:true } }, setup(props,{attrs,slots,emit}) { let counter = ref(100) let rCounter = readonly(counter) const increment = () => { //正确使用方式 //counter.value ++ rCounter.value ++//=>Set operation on key "value" failed: target is readonly. } return { rCounter, increment } } } </script> <style scoped> </style>

输出

image-20210905175703453

Reactive判断的API

image-20210906212647226

toRefs

image-20210906212808568

使用结构赋值存在的问题

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template> <div> <h3>{{name}}--{{age}}</h3> <button @click="changAge">changName</button> </div> </template> <script> import {reactive} from 'vue' export default { setup() { const {name,age} = reactive({name:'Alice',age:18}) const changAge = ()=>{ age ++ } return { name, age, changAge } } } </script> <style scoped> </style>

输出

  1. 发现使用解构赋值之后,这个age就不是动态数据了.

image-20210906214522484

使用toRefs解决解构赋值的问题

App.vue

关键代码

image-20210906215622322

toRefs一般和reactive搭配使用

toRefs()里面传入的一定是reactive类型的对象.

image-20210906215722537

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<template> <div> <h3>{{name}}--{{age}}</h3> <button @click="changAge">changName</button> </div> </template> <script> import {reactive,toRefs} from 'vue' export default { setup() { const info = reactive({name:'Alice',age:18}) let {name,age} = toRefs(info) const changAge = ()=>{ // info.age ++ //有作用 // age ++ //没有作用 age.value ++ //有作用 console.log(info.age); } return { name, age, changAge } } } </script> <style scoped> </style>

输出

  1. toRefs在image-20210906220135287这两者之间建立了联系,两者之中只要有一个改变,另外一个也会改变.

Honeycam 2021-09-06 21-54-49

toRef

App.vue

解构赋值的单独使用

image-20210906221751425

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template> <div> <h3>{{name}}--{{age}}</h3> <button @click="changAge">changName</button> </div> </template> <script> import {reactive,toRef} from 'vue' export default { setup() { const info = reactive({name:'Alice',age:18}) let {name} = info // let {age} = toRef(info,age) //没有作用 // let age = toRef(info,age)//没有作用 // let {age} = toRef(info,'age')//没有作用 let age = toRef(info,'age')//有作用 const changAge = ()=>{ info.age ++ console.log(info.age); } return { name, age, changAge } } } </script> <style scoped> </style>

输出

image-20210906221657079

ref其他的API

image-20210906222307383

shallowRef&triggerRef

问题背景

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template> <div> <h3>{{info.name}}--{{info.age}}</h3> <button @click="changAge">changAge</button> <hr> <h4>{{info.friend.name}}--{{info.friend.age}}</h4> <button @click="changFriendAge">changFriendAge</button> </div> </template> <script> import {ref} from 'vue' export default { setup() { const info = ref({name:'Alice',age:18,friend:{name:'Celina',age:20}}) const changAge = ()=>{ info.value.age ++ console.log('changAge--'+info.value.age); } const changFriendAge = ()=>{ info.value.friend.age ++ console.log('changFriendAge--'+info.value.friend.age); } return { info, changAge, changFriendAge } } } </script> <style scoped> </style>

输出

  1. 无论是第一层’image-20210907092209688'的age都是能够被修改的.

image-20210907092038192

目标需求

  1. 我们现在希望这个第一层’image-20210907092209688'的age是不能够被修改的.

  2. 这个时候我们就可以使用shallowRef’image-20210907092452285'了.

  3. shallRef一般搭配image-20210907094627829使用

使用shallowRef后没有反应的代码

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template> <div> <h3>{{info.name}}--{{info.age}}</h3> <button @click="changAge">changAge</button> <hr> <h4>{{info.friend.name}}--{{info.friend.age}}</h4> <button @click="changFriendAge">changFriendAge</button> </div> </template> <script> import {ref,shallowRef} from 'vue' export default { setup() { const info = shallowRef({name:'Alice',age:18,friend:{name:'Celina',age:20}}) const changAge = ()=>{ info.value.age ++ console.log('changAge--'+info.value.age); } const changFriendAge = ()=>{ info.value.friend.age ++ console.log('changFriendAge--'+info.value.friend.age); } return { info, changAge, changFriendAge } } } </script> <style scoped> </style>

输出

  1. 虽然info.value.age的值和info.value.friend.age的值都是发生改变了,但是在mustache语法中的值没有改变.

image-20210907093518738

shallowRef需要搭配triggerRef

搭配triggerRef的代码:App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template> <div> <h3>{{info.name}}--{{info.age}}</h3> <button @click="changAge">changAge</button> <hr> <h4>{{info.friend.name}}--{{info.friend.age}}</h4> <button @click="changFriendAge">changFriendAge</button> </div> </template> <script> import {ref,shallowRef, triggerRef} from 'vue' export default { setup() { const info = shallowRef({name:'Alice',age:18,friend:{name:'Celina',age:20}}) const changAge = ()=>{ info.value.age ++ triggerRef(info) console.log('changAge--'+info.value.age); } const changFriendAge = ()=>{ info.value.friend.age ++ // triggerRef(info) console.log('changFriendAge--'+info.value.friend.age); } return { info, changAge, changFriendAge } } } </script> <style scoped> </style>

输出

  1. 在第一层’image-20210907094417260’,所以数据是动态绑定的
  2. 在第二层’image-20210907094424817’,所以数据无法动态更新
  3. 当我们再次点击第一个changeAge时,我们又再一次触发这个trigger,所以不仅第一层的age更新了,第二层的age也更新了.

Honeycam 2021-09-07 09-39-31

customRef

image-20210907094737237

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template> <div> <input type="text" v-model="msg"> <h2>{{msg}}</h2> </div> </template> <script> import debounceRef from './hook/useDebounceRef' export default { setup(props) { const msg = debounceRef('hello world') return{ msg } } } </script> <style scoped> </style>

hook>useDebounceRef.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { customRef } from 'vue'; // 自定义ref export default function(value, delay = 300) { let timer = null; return customRef((track, trigger) => { return { get() { track(); return value; }, set(newValue) { clearTimeout(timer); timer = setTimeout(() => { value = newValue; trigger(); }, delay); } } }) }

输出

  1. 输入延时的效果
  2. 防抖

Honeycam 2021-09-07 12-33-48

computed

基本使用_传入一个getter函数

App.vue

image-20210907124254648

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template> <div> <h2>{{fullName}}</h2> </div> </template> <script> import { ref, computed } from 'vue'; export default { setup() { const firstName = ref("Kobe"); const lastName = ref("Bryant"); // 1.用法一: 传入一个getter函数 // computed的返回值是一个ref对象 const fullName = computed(() => firstName.value + " " + lastName.value); return { fullName, } } } </script> <style scoped> </style>

输出

image-20210907124306066

传入一个对象,包含getter和setter

image-20210907131612578

代码逻辑

image-20210907130157644

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<template> <div> <h2>{{fullName}}</h2> <button @click="changName">changName</button> </div> </template> <script> import { ref, computed } from 'vue'; export default { setup() { const firstName = ref("Kobe"); const lastName = ref("Bryant"); const fullName = computed({ get:() => firstName.value + " " + lastName.value, set(newValue){ let names = newValue.split(' ') firstName.value = names[0] lastName.value = names[1] } }) const changName = ()=>{ fullName.value = 'haha hehe' } return { fullName, changName } } } </script> <style scoped> </style>

输出

Honeycam 2021-09-07 12-56-06

侦听数据的变化_watchEffect

image-20210907133015242

watchEffect

image-20210907133049527

App.vue

image-20210907133536390

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<template> <div> <h2>{{info.name}}--{{info.age}}</h2> <button @click="changeName">changName</button> <button @click="changeAge">changAge</button> </div> </template> <script> import {ref,watchEffect} from 'vue' export default { setup() { const info = ref({name:'Alice',age:20}) const changeName = ()=>{ info.value.name = 'Celina' } const changeAge = () => { info.value.age ++ } watchEffect( () => { console.log('name: ',info.value.name+' age: ',info.value.age); } ) return { info, changeName, changeAge } } } </script> <style scoped> </style>

输出

  1. watchEffect会监听所有的数据,当其中只要有一个数据发生变化时,就是输出
  2. 刚开始的时候会立即执行一次.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KSpdfiF1-1634743836658)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231150360-2081718128.png)]

watchEffect的停止侦听

image-20210907134942652

代码逻辑图

image-20210907135123997

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template> <div> <h2>{{info.name}}--{{info.age}}</h2> <button @click="changeName">changName</button> <button @click="changeAge">changAge</button> </div> </template> <script> import {ref,watchEffect} from 'vue' export default { setup() { const info = ref({name:'Alice',age:20}) const stop = watchEffect(() => { console.log('name: ',info.value.name+' age: ',info.value.age); }) const changeName = ()=>{ info.value.name = 'Celina' } const changeAge = () => { info.value.age ++ if(info.value.age > 25){ stop() } } return { info, changeName, changeAge } } } </script> <style scoped> </style>

输出

Honeycam 2021-09-07 13-46-34

watchEffect清除副作用

image-20210907135253007

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<template> <div> <h2>{{info.name}}--{{info.age}}</h2> <button @click="changeName">changName</button> <button @click="changeAge">changAge</button> </div> </template> <script> import {ref,watchEffect} from 'vue' export default { setup() { const info = ref({name:'Alice',age:20}) const stop = watchEffect((onInvalidate) => { onInvalidate(() => { //在这里面清除额外的副作用 console.log('onInvaliddata'); }) console.log('name: ',info.value.name+' age: ',info.value.age); }) const changeName = ()=>{ info.value.name = 'Celina' } const changeAge = () => { info.value.age ++ } return { info, changeName, changeAge } } } </script> <style scoped> </style>

输出

  1. 每次输出都会执行onInvaliddata中的程序,
  2. 相当于清除的作用

Honeycam 2021-09-07 13-58-52

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<template> <div> <h2>{{info.name}}--{{info.age}}</h2> <button @click="changeName">changName</button> <button @click="changeAge">changAge</button> </div> </template> <script> import {ref,watchEffect} from 'vue' export default { setup() { const info = ref({name:'Alice',age:20}) const timer = setTimeout(() =>{ console.log('网络请求成功'); },2000) const stop = watchEffect((onInvalidate) => { onInvalidate(() => { //在这里面清除额外的副作用 clearTimeout(timer) console.log('清除上次的网络请求'); }) console.log('name: ',info.value.name+' age: ',info.value.age); }) const changeName = ()=>{ info.value.name = 'Celina' } const changeAge = () => { info.value.age ++ } return { info, changeName, changeAge } } } </script> <style scoped> </style>

输出

Honeycam 2021-09-07 14-04-21


代码逻辑图

image-20210907141127795

模拟网络请求的例子:App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<template> <div> <h2>{{info.name}}--{{info.age}}</h2> <button @click="changeName">changName</button> <button @click="changeAge">changAge</button> </div> </template> <script> import {ref,watchEffect} from 'vue' export default { setup() { const info = ref({name:'Alice',age:20}) const timer = setTimeout(() =>{ console.log('网络请求成功'); },3000) const stop = watchEffect((onInvalidate) => { onInvalidate(() => { //在这里面清除额外的副作用 clearTimeout(timer) console.log('清除上次的网络请求'); console.log('准备下次的网络请求...'); setTimeout(() =>{ console.log('网络再次请求成功'); },3000) console.log('----'); }) console.log('name: ',info.value.name+' age: ',info.value.age); }) const changeName = ()=>{ info.value.name = 'Celina' } const changeAge = () => { info.value.age ++ } return { info, changeName, changeAge } } } </script> <style scoped> </style>

输出:模拟网络请求

Honeycam 2021-09-07 14-08-50

setup中使用ref

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template> <div> <h2 ref="titleRef">hello world</h2> </div> </template> <script> import {ref,watchEffect} from 'vue' export default { setup() { const titleRef = ref(null) watchEffect(() => { console.log(titleRef.value); },{ flush:'post' }) return { titleRef } } } </script> <style scoped> </style>

输出

image-20210907145216546


几种输出比较

  1. 为什么打印了两次
  2. image-20210907145733258
  3. 其他补充:image-20210907145800653

image-20210907144956924

侦听数据的变化_watch

侦听reactive对象

第一种传入方式’image-20210907151337906

App.vue

image-20210907151154741

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template> <div> <h2>{{info.name}}--{{info.age}}</h2> <button @click="changeName">changeName</button> </div> </template> <script> import {reactive,watch} from 'vue' export default { setup() { const info = reactive({name:'Alice',age:19}) const changeName = () => { info.name = 'Celina' } watch(() => info.name,(newValue,oldValue) =>{ console.log(oldValue,'--',newValue); }) return { info, changeName } } } </script> <style scoped> </style>

输出

image-20210907150959744

第二种传入方式’image-20210907151510505

App.vue

image-20210907151458978

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template> <div> <h2>{{info.name}}--{{info.age}}</h2> <button @click="changeName">changeName</button> </div> </template> <script> import {reactive,watch} from 'vue' export default { setup() { const info = reactive({name:'Alice',age:19}) const changeName = () => { info.name = 'Celina' } watch(info,(newValue,oldValue) =>{ console.log(oldValue,'--',newValue); }) return { info, changeName } } } </script> <style scoped> </style>

输出

image-20210907151350130

结构赋值

App.vue

关键代码

image-20210907154629406

和上面的对比

image-20210907154822192

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template> <div> <h2>{{info.name}}--{{info.age}}</h2> <button @click="changeName">changeName</button> </div> </template> <script> import {reactive,watch} from 'vue' export default { setup() { const info = reactive({name:'Alice',age:19}) const changeName = () => { info.name = 'Celina' } watch(() => { return {...info} } ,(newValue,oldValue) =>{ console.log(oldValue,newValue); }) return { info, changeName } } } </script> <style scoped> </style>

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vI3Iy6Wh-1634743836726)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231154893-1501892486.png)]

侦听ref对象

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template> <div> <h2>{{name}}</h2> <button @click="changeName">changeName</button> </div> </template> <script> import {ref,watch} from 'vue' export default { setup() { const name = ref('Alice') const changeName = () => { name.value = 'Celina' } watch(name,(newValue,oldValue) =>{ console.log(oldValue,'--',newValue); }) return { name, changeName } } } </script> <style scoped> </style>

输出

image-20210907152347122

侦听多个数据

image-20210907161029892

App.vue

传入的值是一个数组

传入的值是一个数组,返回的新旧的值也是一对数组.

image-20210907155618873

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template> <div> <h2 ref="title">{{info.name}}</h2> <button @click="changeData">修改数据</button> </div> </template> <script> import { ref, reactive, watch } from 'vue'; export default { setup() { // 1.定义可响应式的对象 const info = reactive({name: "why", age: 18}); const name = ref("why"); // 2.侦听器watch watch([() => ({...info}), name], ([newInfo, newName], [oldInfo, oldName]) => { console.log(newInfo, newName,'---', oldInfo, oldName); }) const changeData = () => { info.name = "kobe"; } return { changeData, info } } } </script> <style scoped> </style>

侦听数组

image-20210907161057463

深层侦听

image-20210907161332201

生命周期钩子

image-20210907211323627

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template> <div> <h2>{{name}}</h2> <button @click="changeName">changName</button> </div> </template> <script> import {ref,onMounted, onUpdated} from 'vue' export default { setup(props) { onMounted(() => { console.log('App onMounted_1'); }) onMounted(() => { console.log('App onMounted_2'); }) onUpdated(() =>{ console.log('App onUpdated'); }) const name = ref("Alice") const changeName = () => { name.value = 'Celina' } return { name, changeName } } } </script> <style scoped> </style>

输出

  1. 生命周期函数可以重复定义,其中执行的代码会叠加.
  2. 改变用户名称时,会执行onUpdated()

Honeycam 2021-09-07 21-21-18

Provide和Inject

基本使用_父组件向子组件传递数据

数据传递过程图

image-20210907214226137

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template> <div> App <hr> <home></home> </div> </template> <script> import {ref,provide} from 'vue' import Home from './Home.vue' export default { components:{ Home }, setup() { const msg = ref('I am msg form Father') provide('msg',msg) } } </script> <style scoped> </style>

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template> <div> Home <h2>{{msg}}</h2> </div> </template> <script> import {inject} from 'vue' export default { setup() { const msg = inject('msg') return{ msg } } } </script> <style scoped> </style>

基本使用_动态数据传递

父组件:App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template> <div> <h1>App.vue</h1> <input type="text" v-model="msg"> <hr> <home></home> </div> </template> <script> import {ref,provide} from 'vue' import Home from './Home.vue' export default { components:{ Home }, setup() { const msg = ref('I am msg form Father') provide('msg',msg) return{ msg } } } </script> <style scoped> </style>

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template> <div> <h1>Home.vue</h1> <h2>{{msg}}</h2> <input type="text" v-model="msg"> </div> </template> <script> import {inject} from 'vue' export default { setup() { const msg = inject('msg') return{ msg } } } </script> <style scoped> </style>

输出

  1. 父组件能够传递数据到子组件
  2. 父组件的数据能够动态绑定到子组件中,也就修改父组件中的数据,子组件中的数据能够动态变化.
  3. 存在的问题,就是子组件修改数据,父组件中的数据也遭受到修改,违反了数据的单向流原则,不太友好

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I82HpT98-1634743836742)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231157127-2055290198.gif)]

改进_数据单向流

针对上面出现的问题,修改子组件中的数据,父组件中的数据也会遭到修改.

父组件:App.vue

和上面代码对比

image-20210907215849692

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<template> <div> <h1>App.vue</h1> <input type="text" v-model="msg"> <hr> <home></home> </div> </template> <script> import {ref,provide,readonly} from 'vue' import Home from './Home.vue' export default { components:{ Home }, setup() { const msg = ref('I am msg form Father') provide('msg',readonly(msg)) return{ msg } } } </script> <style scoped> </style>

输出效果

Honeycam 2021-09-07 21-57-11

Composition API练习

计数器案例

代码提取图

image-20210907222108728

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template> <div> <h2>{{counter}}</h2> <button @click="increment">+1</button> <button @click="decrement">-1</button> </div> </template> <script> import useCounter from './hooks/useCounter' export default { setup(){ const {counter,increment,decrement} = useCounter() return { counter, increment, decrement } } } </script> <style scoped> </style>

hooks>useCounter.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {ref} from 'vue' export default function(){ const counter = ref(0) const increment = () => { counter.value ++ } const decrement = () => { counter.value -- } return { counter, increment, decrement } }

useTitle案例

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template> <div> </div> </template> <script> import useTitle from './hooks/useTitle' export default { setup(){ const titleRef = useTitle('myTitle') setTimeout(() => { titleRef.value = 'youTitle' },2000) } } </script> <style scoped> </style>

hooks>useTitle

1
2
3
4
5
6
7
8
9
10
11
import {ref,watch} from 'vue' export default function(title = 'default title'){ const titleRef = ref(title) document.title = title watch(titleRef,(newTitle) => { document.title = newTitle }) return titleRef }

输出

  1. 刷新的时候显示默认的title,即default title
  2. 然后迅速变换成myTitle
  3. 最后经过两秒过后,这个变成youTitle

Honeycam 2021-09-07 22-36-31

综合案例

关键代码逻辑

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<template> <div> <h2>{{ counter }}</h2> <button @click="increment">+1</button> <button @click="decrement">-1</button> <hr /> <p class="content"></p> <div class="scroll"> <div class="scroll-x">scrollX: {{ scrollX }}</div> <div class="scroll-y">scrollY: {{ scrollY }}</div> </div> <div class="mouse"> <div class="mouse-x">mouseX: {{ mouseX }}</div> <div class="mouse-y">mouseY: {{ mouseY }}</div> </div> </div> </template> <script> import useCounter from "./hooks/useCounter"; import useTitle from "./hooks/useTitle"; import useScrollPosition from './hooks/useScrollPosition' import useMousePosition from './hooks/useMousePosition' export default { setup() { const { counter, increment, decrement } = useCounter(); useTitle("myTitle"); const {scrollX, scrollY} = useScrollPosition() const {mouseX,mouseY} = useMousePosition() return { counter, increment, decrement, scrollX, scrollY, mouseX, mouseY }; }, }; </script> <style scoped> .content { width: 3000px; height: 5000px; } .scroll { position: fixed; right: 30px; bottom: 30px; } .mouse { position: fixed; right: 30px; bottom: 80px; } </style>

hooks>useScrollPosition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { ref } from 'vue'; export default function() { const scrollX = ref(0); const scrollY = ref(0); document.addEventListener("scroll", () => { scrollX.value = window.scrollX; scrollY.value = window.scrollY; }); return { scrollX, scrollY } }

hooks>useMousePosition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { ref } from 'vue'; export default function() { const mouseX = ref(0); const mouseY = ref(0); window.addEventListener("mousemove", (event) => { mouseX.value = event.pageX; mouseY.value = event.pageY; }); return { mouseX, mouseY } }

输出

Honeycam 2021-09-07 22-49-14

代码整合优化

所有导入合并成一个单独的文件

image-20210907231129915

jsx

image-20210908130110379

基本使用

App.vue

  1. 非常简单,就是把template标签扔掉了,然后直接 在render()的返回值里面写html代码
1
2
3
4
5
6
7
8
9
10
11
12
<script> export default { render() { return <h2>hello, I am render</h2> } } </script> <style scoped> </style

输出

image-20210908131126066

计数器案例

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<script> export default { data() { return { counter:0 } }, render() { const increment = () =>this.counter ++ const decrement = () =>this.counter -- return ( <div> <h2>当前计数:{this.counter}</h2> <button onClick={increment}>+1</button> <button onClick={decrement}>-1</button> </div> ) } } </script> <style scoped> </style>

输出

image-20210908131941895

引入子组件

基本使用

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script> import Home from './Home.vue' export default { render() { return ( <div> <h3>App.vue</h3> <hr></hr> <Home></Home> </div> ) } } </script> <style scoped> </style>

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template> <div> <h3>Home</h3> </div> </template> <script> export default { } </script> <style scoped> </style>

输出

image-20210908132320174

使用插槽

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script> import Home from './Home.vue' export default { render() { return ( <div> <h3>App.vue</h3> <hr></hr> <Home> {{default:props => <button>Button</button>}} </Home> </div> ) } } </script> <style scoped> </style>

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script> export default { render(h) { return( <div> <h3>Home.vue</h3> {this.$slots.default ? this.$slots.default():<span>hahaha</span>} </div> ) }, } </script> <style scoped> </style>

输出

  1. 当父组件传递插槽过来时,就是使用父组件的插槽
  2. 当父组件没有传递插槽过来时,就是使用默认的组件.

Honeycam 2021-09-08 13-29-32

VueRouter

概念

路由器

image-20210909085056766

前后端分离阶段

image-20210909085544380

URL的hash

image-20210909085718494

代码理解1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wVtirRO6-1634743836769)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231201803-1048287985.png)]

代码理解2

image-20210909092325937

代码理解3

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HuzP4HYG-1634743836772)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231202432-1233714614.gif)]

HTML5的History

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x0lbEiWE-1634743836776)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231202730-48274783.png)]

代码理解1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sz5AuURU-1634743836781)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231202897-2108420382.png)]

e.preventDefault()

代码理解:history.html

image-20210909110725784

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!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> <div id="app"> <a href="/home">home</a> <a href="/about">about</a> <div class="content">Default</div> </div> <script> const aEls = document.getElementsByTagName("a"); for (let aEl of aEls) { aEl.addEventListener("click", e => { e.preventDefault(); console.log("clicked"); }) } </script> </body> </html>

输出

  1. 没有用e.preventDefault();时,浏览器地址会进行跳转
  2. 使用了e.preventDefault();,浏览器地址不会跳转,不会变化,
  3. 问-为什么要阻止浏览器地址的跳转?答-因为浏览器地址跳转会引发网络请求,而每次引发网络请求,频繁的网络请求也是不好的.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qJBrrruj-1634743836789)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231203356-1125157459.gif)]


history.pushState({}, “”, href);

使用history方式跳转页面:history.html

阻止网络请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7xEH9ULK-1634743836793)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231203679-356167998.gif)]

使用新的路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R4WGTLeI-1634743836797)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231204044-1028348893.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<!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> <div id="app"> <a href="/home">home</a> <a href="/about">about</a> <div class="content">Default</div> </div> <script> const contentEl = document.querySelector('.content'); const changeContent = () => { switch(location.pathname) { case "/home": contentEl.innerHTML = "Home"; break; case "/about": contentEl.innerHTML = "About"; break; default: contentEl.innerHTML = "Default"; } } const aEls = document.getElementsByTagName("a"); for (let aEl of aEls) { aEl.addEventListener("click", e => { e.preventDefault(); const href = aEl.getAttribute("href"); history.pushState({}, "", href); changeContent(); }) } </script> </body> </html>

代码理解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9NeTx4JJ-1634743836800)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231204262-1828601027.png)]

输出

  1. 实现了即使改变url的部分内容,不用网络请求

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DixPvLes-1634743836801)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231204532-633789497.gif)]

  1. history.pushState({}, "", href);这个过程类似入栈,出栈的过程,所以可以前进后退.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-axYCAlPN-1634743836803)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231204827-206735112.png)]


history.replaceState({},’’,href)

使用history中的history.replaceState({},'',href):history.html

关键代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n2Y2YZoH-1634743836816)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231205053-1150201436.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!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> <div id="app"> <a href="/home">home</a> <a href="/about">about</a> <div class="content">Default</div> </div> <script> const contentEl = document.querySelector('.content'); const changeContent = () => { switch(location.pathname) { case "/home": contentEl.innerHTML = "Home"; break; case "/about": contentEl.innerHTML = "About"; break; default: contentEl.innerHTML = "Default"; } } const aEls = document.getElementsByTagName("a"); for (let aEl of aEls) { aEl.addEventListener("click", e => { e.preventDefault(); const href = aEl.getAttribute("href"); // history.pushState({}, "", href); history.replaceState({},'',href) changeContent(); }) } </script> </body> </html>

输出

  1. 这个history.replaceState({},'',href)就不是history.pushState({}, "", href);的出栈入栈了,而是直接替换,所以无法前进和后退’[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8JoOU8Wr-1634743836817)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231205397-815595052.png)]’.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GokMqecY-1634743836820)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231205692-961565742.png)]

认识vue-router

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uSG3xP3g-1634743836823)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231205996-92500572.png)]

安装vue-router

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GSjAw3Jm-1634743836829)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231206456-1584019000.png)]

路由的使用步骤

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ichgLo0i-1634743836833)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231206686-357628896.gif)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RBrOiYmh-1634743836837)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231206967-1334548514.gif)]


代码结构图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cDTJKiBT-1634743836840)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231207271-599127830.png)]

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template> <div> <h3>App</h3> <hr> <!-- vue-router中的超链接,类似a标签 --> <router-link to="/home">home</router-link>&nbsp;&nbsp;&nbsp; <router-link to="/about">about</router-link> <hr> <!-- 子组件显示的区域 --> <router-view></router-view> </div> </template> <script> export default { } </script> <style scoped> </style>

router>index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {createRouter,createWebHashHistory, createWebHistory} from 'vue-router' import Home from '../pages/Home.vue' import About from '../pages/About.vue' //配置映射关系 const routes = [ {path:'/home',component:Home}, {path:'/about',component:About} ] //创建一个路由对象router const router = createRouter({ routes, history:createWebHashHistory() }) export default router

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template> <div> Home </div> </template> <script> export default { } </script> <style scoped> </style>

About.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template> <div> About </div> </template> <script> export default { } </script> <style scoped> </style>

代码逻辑图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y6KA9zdZ-1634743836844)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231207545-657483772.png)]

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QgCldzVO-1634743836846)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231207783-1901021789.png)]

存在的问题

  1. 刚进来的时候没有默认的页面,会有一个警告

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wPDFMpBB-1634743836848)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231207962-1072849973.png)]

路由的默认路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eKtCB1c9-1634743836852)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231208196-1915647600.gif)]

router>index.js

image-20210909131607872

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import {createRouter,createWebHashHistory} from 'vue-router' import Home from '../pages/Home.vue' import About from '../pages/About.vue' //配置映射关系 const routes = [ {path:'/',redirect:'/home'}, {path:'/home',component:Home}, {path:'/about',component:About} ] //创建一个路由对象router const router = createRouter({ routes, history:createWebHashHistory() }) export default router

输出

  1. 以前的默认打开地址栏’image-20210909131711806
  2. 现在的默认打开地址栏[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TQO0poRT-1634743836859)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231209304-1643742547.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hmfUwHvQ-1634743836861)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231209483-129866870.png)]

router-linker

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j2ZqYdcm-1634743836862)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231209765-1081160699.gif)]

active-class&exact-active-class属性

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SS2zZPQi-1634743836864)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231210104-1840733390.png)]

两者的区别

  1. 当点击Home时,两个属性[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xjD9u4M4-1634743836865)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231210407-2048600610.png)]都会被激活
  2. 当点击Home中的消息商品时,只会激活其中一个属性[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ixAmvvvX-1634743836867)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231210640-1582818629.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lwB524C6-1634743836868)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231210913-1137915021.png)]

路由懒加载

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PT6fQcNa-1634743836869)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231211180-895808863.png)]

分包_魔法注释

懒加载,肯定要分包

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {createRouter,createWebHistory} from 'vue-router' // import Home from '../pages/Home.vue' // import About from '../pages/About.vue' //配置映射关系 const routes = [ {path:'/',redirect:'/home'}, // {path:'/home',component:Home}, // {path:'/about',component:About} {path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')}, {path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')} ] //创建一个路由对象router const router = createRouter({ routes, history:createWebHistory() }) export default router

代码逻辑图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RPxGIkQN-1634743836871)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231211423-1195132143.png)]

路由的其他属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bNQTgeUW-1634743836875)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231211613-1973236825.png)]

动态路由_参数传递

发送参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DZZsCyil-1634743836880)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231211876-120830714.gif)]

接受参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rw2XuR6i-1634743836882)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231212239-449917584.png)]

index.js

传递参数的关键代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FHMgXrIN-1634743836884)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231212444-175274715.gif)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {createRouter,createWebHistory} from 'vue-router' //配置映射关系 const routes = [ {path:'/',redirect:'/home'}, {path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')}, {path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')}, { path:'/user/:userName', component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue')} ] //创建一个路由对象router const router = createRouter({ routes, history:createWebHistory() }) export default router

App.vue

发送参数的关键代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aKRIRJ3D-1634743836885)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231212698-25479102.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template> <div> <h3>App</h3> <hr> <!-- vue-router中的超链接,类似a标签 --> <router-link to="/home" >Home</router-link>&nbsp;&nbsp;&nbsp; <router-link to="/about" >About</router-link>&nbsp;&nbsp;&nbsp; <router-link to="/user/Bruce/Celina" >User</router-link> <hr> <!-- 子组件显示的区域 --> <router-view></router-view> </div> </template> <script> export default { } </script> <style scoped> </style>

输出

  1. 通过在/user后面添加/Alice,可以传递参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zQSbdks3-1634743836887)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231212883-1531769682.png)]

子组件接受参数:User.vue

接受参数的关键代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DGk5l1GY-1634743836888)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231213078-678030523.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template> <div> User </div> </template> <script> export default { created() { console.log(this.$route) }, } </script> <style scoped> </style>

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVHccuEY-1634743836890)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231213241-232851451.png)]

子组件接受指定的参数:User.vue

关键代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZdsAeE0y-1634743836892)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231213427-1621933604.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template> <div> User </div> </template> <script> export default { created() { console.log(this.$route) console.log(this.$route.params.userName) }, } </script> <style scoped> </style>

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EXGFh6Xl-1634743836895)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231213638-781543719.png)]

User.vue

关键代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SI3FK69X-1634743836898)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231213847-238664521.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template> <div> User : {{$route.params.userName}} </div> </template> <script> export default { created() { console.log(this.$route) console.log(this.$route.params.userName) }, } </script> <style scoped> </style>

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DzMz2ulY-1634743836899)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231214036-1907706620.png)]

如何在setup()中的拿到动态路由传递的参数

代码逻辑图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CfTRjFpL-1634743836902)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231214290-87311620.png)]

User.vue

关键代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MLvDDe6g-1634743836903)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231214479-22491041.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template> <div> User : {{$route.params.userName}} </div> </template> <script> import { useRoute } from 'vue-router' export default { created() { console.log(this.$route) }, setup() { const route = useRoute(); console.log(route.params.userName); } } </script> <style scoped> </style>

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xDzidJUQ-1634743836905)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231214674-1252796984.png)]

多参数传递

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ogudETXr-1634743836907)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231214864-308756606.png)]

路由配置文件 : index.js

传递多个参数的关键配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-loJx5lXq-1634743836908)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231215149-1063514329.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {createRouter,createWebHistory} from 'vue-router' //配置映射关系 const routes = [ {path:'/',redirect:'/home'}, {path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')}, {path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')}, { path:'/user/:userName/:age', component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue')} ] //创建一个路由对象router const router = createRouter({ routes, history:createWebHistory() }) export default router

父组件 : App.vue

传递多个参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GOfwLDJA-1634743836909)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231215326-354338892.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template> <div> <h3>App</h3> <hr> <!-- vue-router中的超链接,类似a标签 --> <router-link to="/home" >Home</router-link>&nbsp;&nbsp;&nbsp; <router-link to="/about" >About</router-link>&nbsp;&nbsp;&nbsp; <router-link to="/user/Alice/19" >User</router-link> <hr> <!-- 子组件显示的区域 --> <router-view></router-view> </div> </template> <script> export default { } </script> <style scoped> </style>

子组件 : User.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<template> <div> User : {{$route.params.userName}} &nbsp;&nbsp;&nbsp; Age : {{$route.params.age}} </div> </template> <script> import { useRoute } from 'vue-router' export default { created() { console.log(this.$route) }, // setup() { // const route = useRoute(); // console.log(route.params.userName); // } } </script> <style scoped> </style>

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TN8rK0r1-1634743836912)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231215516-879356976.png)]

NotFound

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5okL3NNK-1634743836913)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231215756-1138079293.png)]

映射表配置文件: route>index.js

关键代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vcEstvrj-1634743836914)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231216026-988957031.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import {createRouter,createWebHistory} from 'vue-router' //配置映射关系 const routes = [ {path:'/',redirect:'/home'}, {path:'/home',component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue')}, {path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')}, { path:'/user/:userName/:age', component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue') }, { path:'/:pathMatch(.*)', component:() => import(/* webpackChunkName:"chunk-notFound" */ '../pages/NotFound.vue') } ] //创建一个路由对象router const router = createRouter({ routes, history:createWebHistory() }) export default router

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtV09L7o-1634743836916)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231216325-1768356826.png)]

$route.params.pathMatch

NotFound.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template> <div> Not Found <h4>{{$route.params.pathMatch}}</h4> </div> </template> <script> export default { } </script> <style scoped> </style>

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZKvNbXmY-1634743836918)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231216505-370869488.png)]

匹配规则加*

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QlbUJP6w-1634743836919)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231216725-2025637599.gif)]

路由的嵌套

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-amCte36g-1634743836921)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231216939-1140140836.png)]

路由配置文件: index.js

关键代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Flz8lKCV-1634743836922)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231217152-2086438639.gif)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import {createRouter,createWebHistory} from 'vue-router' //配置映射关系 const routes = [ {path:'/',redirect:'/home'}, { path:'/home', component:() => import(/* webpackChunkName:"chunk-home" */'../pages/Home.vue'), children:[ { path:'msg', component:() => import('../pages/HomeMsg.vue') }, { path:'pro', component:() => import('../pages/HomeShops.vue') } ] }, {path:'/about',component:() => import(/* webpackChunkName:"chunk-about" */'../pages/About.vue')}, { path:'/user/:userName/:age', component:() => import(/* webpackChunkName:"chunk-user" */'../pages/User.vue') }, { path:'/:pathMatch(.*)', component:() => import(/* webpackChunkName:"chunk-notFound" */ '../pages/NotFound.vue') } ] //创建一个路由对象router const router = createRouter({ routes, history:createWebHistory() }) export default router

Home.vue

关键代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uPOYqsPo-1634743836925)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231217348-297005134.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template> <div> <table border="1"> <div>Home</div> <router-link to="/home/msg">消息</router-link>&nbsp;&nbsp;&nbsp; <router-link to="/home/pro">商品</router-link> <router-view></router-view> </table> </div> </template> <script> export default { } </script> <style scoped> </style>

HomeMsg.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template> <div> <table border="1"> HomeMsg </table> </div> </template> <script> export default { } </script> <style scoped> </style>

HomeShops.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template> <div> <table border="1"> HomeShops </table> </div> </template> <script> export default { } </script> <style scoped> </style>

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4V726BAy-1634743836927)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231217604-777405644.png)]

编程式控制路由

App.vue

关键代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RHSVDSxi-1634743836929)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231217863-1300439155.png)]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<template> <div> <table border="1"> <h3>App</h3> <!-- vue-router中的超链接,类似a标签 --> <router-link to="/home" >Home</router-link>&nbsp;&nbsp;&nbsp; <router-link to="/about" >About</router-link>&nbsp;&nbsp;&nbsp; <router-link to="/user/Alice/19" >User</router-link><br> <button @click="jumpToHome">Home</button>&nbsp;&nbsp;&nbsp; <button @click="jumpToAbout">About</button>&nbsp;&nbsp;&nbsp; <button @click="jumpToUser">User</button> <!-- 子组件显示的区域 --> <router-view></router-view> </table> </div> </template> <script> import {useRouter} from 'vue-router' export default { setup() { const router = useRouter() const jumpToHome = () => { router.push('/home') } const jumpToAbout = () => { router.push('/about') } const jumpToUser = () => { router.push('/user/Alice/19') } return { jumpToHome, jumpToAbout, jumpToUser } } } </script> <style scoped> </style>

输出

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cxVhaM7c-1634743836931)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231218069-684133680.png)]

router-link的v-slot

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uHj8cbjB-1634743836935)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231218366-1785074971.gif)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sQRIlS3R-1634743836940)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231218738-1962418597.gif)]

v-slot的基本使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ONy7ftBg-1634743836945)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231219132-653050866.png)]

custom的作用和如何通过插槽给内部传值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-svkq7OWS-1634743836947)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231219418-1117055798.png)]

navigate导航函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xwoxSFZ2-1634743836952)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231219657-1819683881.png)]

isActive

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9v3FQkhf-1634743836953)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231219855-796732447.png)]

router-view的v-slot

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8WQL1Zlv-1634743836955)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231220143-5353474.png)]

动态添加路由

添加一级路由

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-upLUPdDc-1634743836958)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231220518-1447028218.png)]

添加二级路由

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XajeXrOR-1634743836962)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231220737-1892702388.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SEUL0X6b-1634743836965)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231221020-90472520.png)]

动态删除路由

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W4qTOiY0-1634743836966)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231221315-800547957.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DDVvBQtc-1634743836969)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231221562-591659026.png)]

路由导航守卫

https://next.router.vuejs.org/zh/guide/advanced/navigation-guards.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cBoQuOJq-1634743836970)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231221786-1095513300.png)]

登录守卫功能

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gjwbEiHl-1634743836971)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231221989-889710726.png)]

Vuex的状态管理

https://next.vuex.vuejs.org/

批量引入Vuex中的数据_mapState

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PrtCeYHs-1634743836973)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231222185-1508544011.png)]

setup中如何使用mapState

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K2Y5RQi4-1634743836975)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231222463-160769040.png)]

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<template> <div class="home"> <table border="1"> <h5>{{ name }}</h5> <h5>{{ age }}</h5> <h5>{{ counter }}</h5> </table> </div> </template> <script> import { useStore, mapState } from "vuex"; import { computed } from "vue"; export default { setup() { const store = useStore(); const storeStateFns = mapState(["name", "age", "counter"]); // console.log(storeStateFns);//=>{name: ƒ, age: ƒ, counter: ƒ} const storeState = {}; Object.keys(storeStateFns).forEach((fnKey) => { const fn = storeStateFns[fnKey].bind({ $store: store }); storeState[fnKey] = computed(fn); }); return { ...storeState, }; }, }; </script> <style></style>

setup中使用mapState的封装

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oiZ5s0lt-1634743836977)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231222702-1519398150.png)]

hooks>useState.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { useStore, mapState } from "vuex"; import { computed } from "vue"; export function useState(mapper) { const store = useStore(); const storeStateFns = mapState(mapper); const storeState = {}; Object.keys(storeStateFns).forEach((fnKey) => { const fn = storeStateFns[fnKey].bind({ $store: store }); storeState[fnKey] = computed(fn); }); return storeState; }

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<template> <div class="home"> <table border="1"> <h5>{{ name }}</h5> <h5>{{ age }}</h5> <h5>{{ counter }}</h5> </table> </div> </template> <script> import { useState } from "../hooks/useState"; export default { setup() { const storeState = useState(["name", "age", "counter"]); return { ...storeState, }; }, }; </script> <style></style>

Vuex的计算属性_getters

使用state中的数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vXJ94E8v-1634743836978)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231222925-988745970.png)]

使用getters中的数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RqurF0qB-1634743836980)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231223191-1041303350.png)]

getters返回一个函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S6Gt8q3r-1634743836982)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231223429-952426201.png)]

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { createStore } from "vuex"; export default createStore({ state() { return { counter: 10, name: "Alice", age: 19, height: 180, books: [ { name: "Alice", price: 10, count: 1 }, { name: "Bruce", price: 9, count: 1 }, { name: "Celina", price: 11, count: 1 }, ], }; }, getters: { totalBooksPrice(state) { return function (disCount) { let totalPrice = 0; for (const book of state.books) { totalPrice += book.price * book.count * disCount; } return totalPrice; }; }, }, mutations: { increment(state) { state.counter++; }, decrement(state) { state.counter--; }, }, actions: {}, modules: {}, });

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template> <div class="home"> <table border="1"> <h2>{{ $store.getters.totalBooksPrice(0.8) }}</h2> </table> </div> </template> <script> export default { setup() {}, }; </script> <style></style>

批量拿到getters中的数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0pO3sTl8-1634743836983)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231223665-357050118.png)]

Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template> <div class="home"> <table border="1"> <h2>{{ $store.getters.totalBooksPrice }}</h2> <h2>{{ $store.getters.info }}</h2> <hr /> <h2>{{ totalBooksPrice }}</h2> <h2>{{ info }}</h2> </table> </div> </template> <script> import { mapGetters, useStore } from "vuex"; import { computed } from "vue"; export default { setup() { const store = useStore(); const gettersFns = mapGetters(["totalBooksPrice", "info"]); const getters = {}; Object.keys(gettersFns).forEach((key) => { const fn = gettersFns[key].bind({ $store: store }); getters[key] = computed(fn); }); return { ...getters, }; }, }; </script> <style></style>

封装批量拿到getters中的数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yQ71zbh1-1634743836985)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231223892-1816012496.png)]

hooks>useGetters.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { mapGetters, useStore } from "vuex"; import { computed } from "vue"; export function useGetters(mapper) { const store = useStore(); const gettersFns = mapGetters(mapper); const getters = {}; Object.keys(gettersFns).forEach((key) => { const fn = gettersFns[key].bind({ $store: store }); getters[key] = computed(fn); }); return getters; }

mapState 和mapGetters的综合封装

图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OW4OR7h2-1634743836987)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231224115-1853993190.png)]

hooks>useMapper.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useStore } from "vuex"; import { computed } from "vue"; export function useMapper(mapper, mapFunc) { const store = useStore(); const gettersFns = mapFunc(mapper); const getters = {}; Object.keys(gettersFns).forEach((key) => { const fn = gettersFns[key].bind({ $store: store }); getters[key] = computed(fn); }); return getters; }

hooks>useState.js

1
2
3
4
5
6
7
8
import { mapState } from "vuex"; import { useMapper } from "./useMapper"; export function useState(mapper) { return useMapper(mapper, mapState); }

hooks>useGetters.js

1
2
3
4
5
6
7
8
import { useMapper } from "./useMapper"; import { mapGetters } from "vuex"; export function useGetters(mapper) { return useMapper(mapper, mapGetters); }

hooks>index.js

1
2
3
4
5
6
import { useGetters } from "./useGetters"; import { useState } from "./useState"; export { useGetters, useState };

引用useGetters的文件:Home.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<template> <div class="home"> <table border="1"> <h2>{{ $store.getters.totalBooksPrice }}</h2> <h2>{{ $store.getters.info }}</h2> <hr /> <h2>{{ totalBooksPrice }}</h2> <h2>{{ info }}</h2> </table> </div> </template> <script> // import { useGetters } from "../hooks/useGetters"; import { useGetters } from "../hooks/index"; export default { setup() { return { ...useGetters(["totalBooksPrice", "info"]), }; }, }; </script> <style></style>

Mutation

无参数传递_计数器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tvzzBMmT-1634743836989)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231224321-1943172650.png)]

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template> <div class="app"> <table border="1"> <h2>{{ $store.state.counter }}</h2> <button @click="increment">+1</button> <button @click="decrement">-1</button> </table> </div> </template> <script> export default { name: "App", components: {}, methods: { increment() { this.$store.commit("increment"); }, decrement() { this.$store.commit("decrement"); }, }, }; </script> <style></style>

有参数传递_计数器

图解

传递普通参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aaQoOo10-1634743836991)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231224573-1114998813.png)]

传递对象类型参数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rcwz7NLD-1634743837004)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231224806-624518842.png)]

两种提交风格

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6MB5selb-1634743837008)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231225061-337419833.png)]

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template> <div class="app"> <table border="1"> <h2>{{ $store.state.counter }}</h2> <button @click="increment">+1</button> <button @click="decrement">-1</button> <hr /> <button @click="increment_10">+10</button> </table> </div> </template> <script> export default { name: "App", components: {}, methods: { increment() { this.$store.commit("increment"); }, decrement() { this.$store.commit("decrement"); }, increment_10() { this.$store.commit("incrementN", { step: 10 }); }, }, }; </script> <style></style>

store>index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { createStore } from "vuex"; export default createStore({ state() { return { counter: 10, name: "Alice", age: 19, height: 180, books: [ { name: "Alice", price: 10, count: 1 }, { name: "Bruce", price: 9, count: 1 }, { name: "Celina", price: 11, count: 1 }, ], }; }, getters: { totalBooksPrice(state) { let totalPrice = 0; for (const book of state.books) { totalPrice += book.price * book.count; } return totalPrice; }, info(state) { return state.name + state.age; }, }, mutations: { increment(state) { state.counter++; }, decrement(state) { state.counter--; }, incrementN(state, payload) { state.counter += payload.step; }, }, actions: {}, modules: {}, });

常量类型

图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ToKSJAHJ-1634743837011)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231225269-1771211526.png)]

store>index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import { createStore } from "vuex"; import { INCREMENT_N } from "./mutation-types"; export default createStore({ state() { return { counter: 10, name: "Alice", age: 19, height: 180, books: [ { name: "Alice", price: 10, count: 1 }, { name: "Bruce", price: 9, count: 1 }, { name: "Celina", price: 11, count: 1 }, ], }; }, getters: { totalBooksPrice(state) { let totalPrice = 0; for (const book of state.books) { totalPrice += book.price * book.count; } return totalPrice; }, info(state) { return state.name + state.age; }, }, mutations: { increment(state) { state.counter++; }, decrement(state) { state.counter--; }, // incrementN(state, payload) { // state.counter += payload.step; // }, [INCREMENT_N](state, payload) { state.counter += payload.step; }, }, actions: {}, modules: {}, });

store>mutation-types.js

1
2
3
export const INCREMENT_N = "incrementN";

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<template> <div class="app"> <table border="1"> <h2>{{ $store.state.counter }}</h2> <button @click="increment">+1</button> <button @click="decrement">-1</button> <hr /> <button @click="increment_10">+10</button> </table> </div> </template> <script> import { INCREMENT_N } from "./store/mutation-types"; export default { name: "App", components: {}, methods: { increment() { this.$store.commit("increment"); }, decrement() { this.$store.commit("decrement"); }, // 第一种提交风格 increment_10() { this.$store.commit(INCREMENT_N, { step: 10 }); }, // 第二种提交风格 // increment_10() { // this.$store.commit({ // type: "incrementN", // step: 10, // }); // }, }, }; </script> <style></style>

mapMutation

图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8h38syHH-1634743837012)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231225510-1579645842.png)]

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template> <div class="app"> <table border="1"> <h2>{{ $store.state.counter }}</h2> <button @click="increment">+1</button> <button @click="decrement">-1</button> <hr /> <button @click="incrementN(10)">+10</button> </table> </div> </template> <script> import { INCREMENT_N } from "./store/mutation-types"; import { mapMutations } from "vuex"; export default { name: "App", components: {}, setup() { const mutations = mapMutations(["increment", "decrement", INCREMENT_N]); return { ...mutations, }; }, }; </script> <style></style>

actions

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NwyaJ6mP-1634743837014)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231225763-176180570.png)]

基本使用

  1. 在组件中调用store中actions的函数.
  2. 在actions中调用mutations中的方法.
  3. 真正的数据修改发生在mutations的方法里面.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yCVPwzp3-1634743837016)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231225991-1209323146.png)]

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template> <div class="app"> <table border="1"> <h2>{{ $store.state.counter }}</h2> <button @click="increment">+1</button> </table> </div> </template> <script> export default { name: "App", components: {}, methods: { increment() { console.log("1.在App.vue中调用store中的actions的incrementAction()"); this.$store.dispatch("incrementAction"); }, }, }; </script> <style></style>

index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { createStore } from "vuex"; export default createStore({ state() { return { counter: 10, name: "Alice", age: 19, height: 180, books: [ { name: "Alice", price: 10, count: 1 }, { name: "Bruce", price: 9, count: 1 }, { name: "Celina", price: 11, count: 1 }, ], }; }, getters: {}, mutations: { increment(state) { console.log("3.真正调用mutations中的increment"); state.counter++; }, }, actions: { incrementAction(context) { console.log("2.在actions中调用mutations中的increment"); context.commit("increment"); }, }, modules: {}, });

基本使用-异步请求-一秒后加一的计数器

图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vffey2NE-1634743837019)(https://img2020.cnblogs.com/blog/1222814/202110/1222814-20211020231226197-1528102828.png)]

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template> <div class="app"> <table border="1"> <h2>{{ $store.state.counter }}</h2> <button @click="increment">+1</button> </table> </div> </template> <script> export default { name: "App", components: {}, methods: { increment() { this.$store.dispatch("incrementAction"); }, }, mounted() { this.$store.dispatch("getBannerDataAction"); }, }; </script> <style></style>

store>index.js

1
2
3
4
5
import { createStore } from "vuex"; import axios from "axios"; export default createStore({ sta

最后

以上就是含蓄乌冬面最近收集整理的关于vue3 学习笔记简介环境搭建计数器案例命令式和声明式编程MVVM模型template属性data属性methods属性this的指向VSCode代码片段Musache语法基本指令重要指令v-ifv-showv-for数组更新检测VNode计算属性-computedWatch综合案例_书籍购物车v-model组件化开发组件通信插槽动态组件Webpack的代码分包&异步组件$refs的使用生命周期组件的v-modelVue3过渡&动画实现animate.css动画gsap库Mixinextends的全部内容,更多相关vue3内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(92)

评论列表共有 0 条评论

立即
投稿
返回
顶部