Vue笔记-4-Vue3.0

目录

1.简介

1.1简介

1.2vue-cli创建工程

1.3vite创建工程

1.4分析vue-cli创建的Vue3工程结构

2.常用Composition API

2.1初识setup()

2.2ref函数--响应式

2.2.1处理基本类型--底层Object.defineProperty(,)

2.2.2处理对象类型--底层reactive()、proxy()

2.2.3处理数组类型--底层reactive()、proxy()

2.3reactive函数--响应式

2.3.1处理对象类型--底层proxy()

2.3.2处理数组类型--底层proxy()

2.3.3多说一嘴

2.4Vue3中ref/reactive响应式原理--window.Proxy+window.Reflect

2.4.1window.Proxy()

2.4.2window.Reflect

2.5reactive()对比ref()--和--替换整个对象/嵌套对象时的响应式问题

2.5.1reactive()对比ref()

2.5.2替换整个对象/嵌套对象时的响应式问题

2.6setup()的两个注意点

2.6.1Vue2中的vc.$attrs属性--无props配置项时用于接收props参数

2.6.2Vue2中的vc.$slots属性--存放插槽虚拟节点

2.6.3setup()的两个注意点

2.7computed()计算属性函数

2.7.1简写形式(只读)

2.7.2全写形式(可读可写)

2.8watch()监视函数

2.8.1watch()监视ref()定义的数据

2.8.2watch()监视reactive()定义的数据

2.8.3watch时value的问题

2.9watchEffect()函数

2.10Vue3生命周期

2.10.1配置项写法--8个钩子

2.10.2组合式Api写法--6个钩子

2.10.3扯淡の两种写法都写上

2.11自定义hook()函数--类似vue2中mixin混入

2.11.1获取鼠标坐标

2.11.2自定义hook()函数

2.12toRef()和toRefs()

2.12.1toRef()和toRefs()要解决的问题--和2个错误解法

2.12.2toRef()

2.12.3toRefs()

2.12.4补充

3.其它Composition API

3.1shallowReactive()与shallowRef()

3.1.1shallowReactive()--联系本文2.5.2

3.1.2shallowRef()--联系本文2.5.2

3.2readOnly()与shallowReadonly()

3.3toRaw()与markRaw()

3.3.1toRaw()

3.3.2markRaw() 

3.4customRef()

3.4.1customRef()

3.4.2防抖、节流

3.5provide()与inject()实现组件间通信

3.6isRef()/isReactive()/isReadonly()/isProxy()--响应式数据判断

4.Composition API相比传统API的优势(hook)

5.新的组件

5.1Fragment组件

5.2Teleport组件

5.3Suspense组件

6.Vue3中其它的改变

6.1全局API的转移--将Vue.xxx调整到应用实例对象app.xxx

6.2其它改变


1.简介

1.1简介

2020.9.18发布vue3.0,代号one piece。

性能提升:打包大小减少、内存占用减少、初次渲染和更新渲染更快。

源码升级:使用Proxy代替Object.defineProperty()实现响应式,优化虚拟Dom对比算法和Tree-shaking(术语,剪除无用代码包括依赖库中的代码,webpack已支持)。

拥抱TypeScript:更好的支持ts(ts是微软开发的强类型的js的替代品)。

新特性:

1.2vue-cli创建工程

确保@vue/cli版本在4.5.0以上,从3.12.1版本升级到5.0.8后,vue -V查看版本报错:

升级node版本:

linux环境:

# 查看当前node版本
$ node -v
 
# 【清除npm缓存--有时npm下载资源出错,再次下载可能因为之前错误缓存下载不成功】
$ npm cache clean -f
 
# 全局安装n
$ npm install -g n
 
# 升级到最新稳定版
$ n stable
 
# 升级到最新版
$ n latest
 
# 升级到指定版本
$ n v14.6.0
 
# 切换使用版本
$ n 13.10.0 (ENTER)
 
# 删除制定版本
$ n rm 13.10.0
 
# 用指定的版本执行脚本
$ n use 13.10.0 some.js
 
# 升级完成查看 node版本
$ node -v

windows环境:

下载gnvm.exehttps://raw.githubusercontent.com/Kenshin/gnvm-bin/master/64-bit/gnvm.exe

放到nodejs安装目录下,执行gnvm version查看版本

win7支持的node.js版本最高应该是V13.14,这里安装V12.0.0

gnvm install 12.0.0命令更新node.js版本到V12.0.0

gnvm update latest命令更新node.js版本为最新版

gnvm ls                  -- 查看已经下载的nodejs版本号
gnvm install  下载的nodejs版本号  -- 按照指定需求版本进行下载
gnvm uninstall latest 1.0.0-x86 1.0.0-x64 5.0.0    --卸载本地任意 Node.js 版本
gnvm use 版本号               切换当前要使用的node.js版本
gnvm npm global              下载与当前使用node.js版本相匹配的npm版本
gnvm npm latest              下载最新的npm版本
gnvm config                  创建或更新.gnvmrc文件

【config 配置 .gnvmrc】
use          使用某个本地已存在的 Node.js 版本
ls           输出 [local] [remote] Node.js 版本
install      下载/安装任意已知版本的 Node.js
uninstall    删除任意本地已存在的 Node.js
update       下载 Node.js latest 版本并更新到 .gnvmrc 里面
npm          NPM 下载/安装/删除 管理
session      临时设定本地某个已存在的 Node.js 为 全局 Node.js
search       查询并且输出符合查询条件的 Node.js 版本详细信息的列表
node-version 输出 [global] [latest] Node.js 版本
reg          设定 .gnvmrc 属性值 [noderoot] 为 环境变量 [NODE_HOME],并加入到 Path 中version  

安装完成,在nodejs原安装目录自动创建对应版本号的文件夹,存放对应版本的node.exe,在各个文件夹下执行node -v,输出的版本号各不同。

但注意,你在其他地方打开命令行窗口,查看node版本还是之前的版本。所以可以将12.0.0目录中的node.exe文件复制到原来的node.exe所在目录,覆盖掉它。旧的node.exe备份防止意外,新的node.exe再放进去。然后在任意位置打开命令行窗口查看node版本,发现已经是安装的最新版本了。

再次执行vue -V:没有问题了

新建对应10.23.1版本号的文件夹把旧版本node.exe的放进去

在桌面上创建

创建过程中选择vue3.0

创建完成后,cd vue3_testnpm run serve运行即可

运行报错

继续更换node版本为12.1.0即可解决。

1.3vite创建工程

vite是vue团队开发的前端工程构建工具

其它前端构建工具:webpack、grunt、gulp。

  • 无须打包(webpack是npm run serve  -->  打包  -->运行)。
  • HMR(hot module replace)热模替代--热重载,修改代码无须重新运行。
  • 按需编译,动态引入和代码分割,无需等待整个应用编译完成,现用现编译,速度更快。

一定要进入工程目录再npm i

vite创建的工程默认启动端口是3000

1.4分析vue-cli创建的Vue3工程结构

main.js

console.log('@@@@', app);          //app比vm更轻量化

Vue3组件中的模板结构可以没有根标签

2.常用Composition API

setup()是所composition api--组合式api的基础

2.1初识setup()

setup不能是一个async函数,否则报错:setup function returned a promise, but no <Suspense> boundary was found in the parent component tree.除非使用了Suspense组件异步引入组件配合,如本文标题5.3中所述

//此处只是测试一下setup,暂时不考虑响应式的问题。

//setup()中的自定义函数,需要加上function关键字。或者采用箭头函数也可以,如下图

<template>
	<h1>一个人的信息</h1>
	<h2>姓名:{{name}}</h2>
	<h2>年龄:{{age}}</h2>
	<h2>性别:{{sex}}</h2>
    <!--如果有重名,setup()中优先-->
	<h2>a的值是:{{a}}</h2>
	<button @click="sayHello">说话(Vue3所配置的——sayHello)</button>
	<br>
	<br>
	<button @click="sayWelcome">说话(Vue2所配置的——sayWelcome)</button>
	<br>
	<br>
	<button @click="test1">测试一下在Vue2的配置中去读取Vue3中的数据、方法</button>
	<br>
	<br>
	<button @click="test2">测试一下在Vue3的setup配置中去读取Vue2中的数据、方法</button>
</template>

<script>
  import {h} from 'vue' //【引入渲染函数(名字是vue规定的h):h】
	export default {
		name: 'App',
        // 【【【vue2】】】
        // 极不建议写vue2中的data/methods等配置项,这里是为了测试
		data() {
			return {
				sex:'男',
				a:100 //如果有重名,setup()中优先
			}
		},
		methods: {
			sayWelcome(){
				alert('欢迎来到尚硅谷学习')
			},
			test1(){
				console.log(this.sex)
				console.log(this.name)
				console.log(this.age)
				console.log(this.sayHello)
			}
		},
        // 【【【vue3】】】
		// 此处只是测试一下setup,暂时不考虑响应式的问题。
		// async setup(){ //不能被[async]关键字修饰,否则空白页面且会警告:setup function returned a promise, but no <Suspense> boundary was found in the parent component tree.
        setup(){
			//数据
			let name = '张三'
			let age = 18
			let a = 200
			//方法
			function sayHello(){
				alert(`我叫${name},我${age}岁了,你好啊!`)
			}
			function test2(){
				console.log(name)
				console.log(age)
				console.log(sayHello)
				console.log(this.sex)
				console.log(this.sayWelcome)
			}
      
			// 返回一个对象(常用)
			return {
				name,
				age,
				sayHello,
				test2,
				a //如果有重名,setup()中优先
			}
			// 返回一个函数(渲染函数)创建内容为'尚硅谷'的h1标签
			// return () => h('h1','尚硅谷')
		}
	}
</script>

2.2ref函数--响应式

2.2.1处理基本类型--底层Object.defineProperty(,)

ref()函数的返回值是对象:

2.2.2处理对象类型--底层reactive()、proxy()

     

console.log(job):

console.log(job.value):

2.2.3处理数组类型--底层reactive()、proxy()

2.3reactive函数--响应式

ref()可以包装基本类型、对象类型、数组类型;reactive()只能包装对象、数组类型。都可以深度包装嵌套属性为响应式的。

2.3.1处理对象类型--底层proxy()

2.3.2处理数组类型--底层proxy()

2.3.3多说一嘴

虽说reactive()不能处理基本类型,但是可以把基本类型数据嵌套进对象里进行包装

2.4Vue3中ref/reactive响应式原理--window.Proxy+window.Reflect

vue2.x中存在的下列问题(前提是不调用Vue.set、数组7个特定方法),vue3.x中不存在了(前提是使用了响应式包装函数)

2.4.1window.Proxy()

1.回顾vue2中实现响应式

p身上的name、age属性是响应式的

新添加的属性可以添加到p但不会添加到person,且不是响应式的--(须借助Vue.set()/vm.$set()

删除属性(即便是响应式的name属性)的动作可以从p删除但不会从person删除,且不是响应式的--(须借助Vue.delete()/vm.$delete(),不会经过setter,不能实现数据劫持,不会响应到页面上

delete关键字删除对象中属性时,返回删除成功/失败

总结:

Object.defineProperty()的getter/setter可以实现读取属性、修改属性并且是响应式的。

Object.defineProperty()的configurable:true配置项配合delete关键字可以实现删除属性,但不是响应式的。

通过对象名.新属性名=属性值可以实现添加属性,但新添加属性不是响应式的。

且Objec.definePropery()实现的数据劫持一次只能定义一个响应式属性。

Object.defineProperty()是Vue2中在读取/修改原有属性时,低层实现数据劫持、数据代理的逻辑——底层通过Observer()构造函数循环调用的Object.defineProperty(),例如使用_data代理data(自己代理自己会死循环)。

对于新添加属性、删除属性的数据劫持,以及数组中单个元素的添加删除等的数据劫持,vue2中是通过 Vue.set()/vm.$set()、Vue.delete()/vm.$delete()、以及数组的7个方法实现的。 

补充:vscode中对块代码注释进行分区,是//#region //#endregion,须加#。

2.模拟vue3中实现响应式--window.Proxy()

全面的(增删改查都可以的)数据代理:

这还只是代理!还不是响应式(不能捕获到数据的修改--不能实现数据劫持--不能呈现在页面)!!

全面の数据代理+在全面数据代理基础上借助Reflect(下一标题讲)实现全面の响应式:

上面描述的“去更新页面”的执行时机好像不太正确,因为Vue笔记-1中的一段笔记是这样的:

,暂时搁置这个问题。

2.4.2window.Reflect

ECMA协会正在尝试将ECMAScript6中的window.Object部分api移植到window.Reflect上,例如Object.defineProperty()、Reflect.defineProperty()。

Reflect操作对象数据

读取:                              修改/增加:                            删除:

全面の数据代理+在全面数据代理基础上借助Reflect实现全面の响应式:

Object.defineProperty()、Reflect.defineProperty()对比

虽然 js中也可以写try-catch捕获Object.defineProperty可能抛出的错误,但是代码臃肿:

2.5reactive()对比ref()--和--替换整个对象/嵌套对象时的响应式问题

2.5.1reactive()对比ref()

2.5.2替换整个对象/嵌套对象时的响应式问题

ref定义的对象类型数据,可以替换整个对象和对象内嵌套的对象,且保持替换后仍是响应式的,但替换时要遵循一定的规则:

reactive定义的对象类型数据,不可以替换整个对象,但可以替换对象内嵌套的对象,且保持替换后仍是响应式的,且模板中如果用到了将要被替换的旧属性,那么新属性名应与旧属性名一致,否则报错。如下图:

2.6setup()的两个注意点

2.6.1Vue2中的vc.$attrs属性--无props配置项时用于接收props参数

 

 

不使用props接收,就不能使用props对接收的参数做一些类型、是否必须、默认值等的限制了。用props接收的参数会直接放在vc身上,不会出现在vc.$attrs身上。

2.6.2Vue2中的vc.$slots属性--存放插槽虚拟节点

=》 =》

      

=》=》

插槽和虚拟节点出现在的是Demo组件的vc.$slots,不是App根组件的vc.$slots

vue2中即便用slot=""而不是v-slot:""test1和test2也会作为插槽名出现在vc.$slots中。这和vue3中的context.slots不同,vue3中只有使用了v-slot:""才会作为插槽名最终出现在context.slots中,若使用slot=""默认插槽名是defalut,所以vue3中尽量用v-slot:""

2.6.3setup()的两个注意点

注意点1:setup()在beforeCreate()钩子执行前执行,在setup()中获取不到this

注意点2:setup()的两个参数。

第1个叫props参数

——对props配置项中接收的参数采用Proxy()进行响应式封装后产生的响应式对象。

打印:

第2个叫context参数

——context.attrs是对未在props配置项中声明接收但发送方确实传递的参数采用Proxy()进行响应式封装后产生的响应式对象,相当于vue2中的vc.$attrs属性。

输出:

——context.emit用来触发自定义事件(下方案例引出了vue3中全新的emits配置项)

——context.slots是记录了插槽名和插槽的虚拟节点的参数。

context.slots打印:

vue2中即便用slot=""而不是v-slot:""test1和test2也会作为插槽名出现在vc.$slots中。这和vue3中的context.slots不同,vue3中只有使用了v-slot:""才会作为插槽名最终出现在context.slots中,若使用slot=""默认插槽名是defalut,所以vue3中尽量用v-slot:""

2.7computed()计算属性函数

vue3中的计算属性变成了组合式API,使用前需要import {computed} from 'vue'

2.7.1简写形式(只读)

2.7.2全写形式(可读可写)

2.8watch()监视函数

vue3中的属性监视变成了组合式API,使用前需要import {watch} from 'vue'

2.8.1watch()监视ref()定义的数据

1.监视单个属性: 

immediate:true一上来就执行监视逻辑,此时oldvalue是undefined

2.监视多个属性:

2.8.2watch()监视reactive()定义的数据

2.8.1中是watch()监视ref()定义的数据——监视单个、多个基本类型数据,

2.8.2中是watch()监视reactive()定义的数据——监视对象类型数据,

2.8.1中不介绍watch()监视ref()定义的对象类型数据——因为ref()定义对象类型数据时低层就是调用reactive()来定义对象类型数据。

2.8.3watch时value的问题

——watch被ref()包装产生的对象类型变量时value的问题

在定义对象类型数据时使用ref()/reactive()哪个皆可,推荐使用reactive(),因为使用ref()会多一个value,虽然在模板中取值时底层已经帮我们加了.value,但是在setup()中想要取到属性值还需要我们手动添加.value。

注意点1:在setup()中写watch()监视函数来监视某个ref()包装/定义的基本类型变量时,是不用加.value的,因为是监视sum--RefImpl对象,而不是监视RefImpl对象的value值,如下图::

注意点2:在setup()中写watch()监视函数来监视某个ref()包装/定义的对象类型变量时,是必须加.value 或者 开启深度监视的。否则如下图::

解决方式1:写成person.value--RefImpl对象中的Proxy对象::

监视Proxy对象时默认开启深度监视,其它略。

解决方式2:开启深度监视::

watch的是RefImpl对象时,默认不开启深度监视,所以要加.value。

watch的是Proxy对象时,默认开启深度监视,并且配置{deep:false}无效。

2.9watchEffect()函数

比如某人工资变了,那么依赖工资的处理逻辑就不必再手动更改了,类似computed计算属性,不过watchEffect更注重过程没有返回值,computed有返回值更关注结果。

2.10Vue3生命周期

2.10.1配置项写法--8个钩子

在Vue3中完全可以像vue2中那样写生命周期钩子[除了beforeDestroy()和destroyed()钩子——已更名为beforeUnmount()和unmounted()],与setup()平级。

2.10.2组合式Api写法--6个钩子

组合式api写法演示:

2.10.3扯淡の两种写法都写上

挂在流程的执行顺序:

2.11自定义hook()函数--类似vue2中mixin混入

2.11.1获取鼠标坐标

event.pageX和event.pageY是鼠标所在的横纵坐标参数。

正确写法:

错误写法:

2.11.2自定义hook()函数

hook()函数不是一个具体的函数,是一类函数的统称。

使用hook函数把上面的获取鼠标横纵坐标的功能代码封装起来。

定义hook():

引入使用hook(): 

当使用hook函数把上面的获取鼠标横纵坐标的功能代码封装起来后,如上例,假如此时在多个组件中多次引入和使用usePoint()虎克函数,会出现bug:多次引入和使用会造成多次地给window添加click事件监听器和回调,导致在界面点击一次鼠标触发多次回调,如下图

2.12toRef()和toRefs()

2.12.1toRef()和toRefs()要解决的问题--和2个错误解法

要解决的问题:

错误解法1:

错误解法2:

效果如下图:数据分家了

2.12.2toRef()

原理图解:使用toRef()可以使得toRef()包装返回的响应式对象toRef()参数1响应式对象中的参数2响应式属性之间建立一条隐式引用关系

正确解法:

实现效果如下:数据没有分家

补充:

2.12.3toRefs()

toRefs()功能类似toRef(),但其可以同时包装传入对象的多个属性(只能包装第一级属性、且包装不了页面渲染后又追加的响应式属性),且只接收1个参数(注意这里的包装不是响应式包装)。

console.log('******',x)打印如下(只包装了person的第一级属性):

2.12.4补充

1.toRef()/toRefs()既可以包装reactive()定义的响应式对象,也可以包装ref()定义的响应式对象,但是包装ref()定义的响应式对象需要加.value,如下图:

2.如下图:toRefs(person)执行完毕return出去后(person是reactive定义的对象),随后再在页面上点击按钮执行addCar()方法给person追加car属性(响应式的--Proxy时增删改查全面代理的)后,页面上的car仍然无法展示!因为:toRefs(person)随着setup()只执行一次,执行完毕后,随后再往person身上追加car属性(是一级属性且是响应式的),toRefs(person)不会再把person身上新的car属性包装返回了

解决方式有二:

一、   

二、

3.其它Composition API

3.1shallowReactive()与shallowRef()

shallowReactive()可以处理对象中第一层数据为响应式(即便是对象类型)”(不能处理替换整个大对象时为响应式)

shallowRef()可以处理替换整个大对象时为响应式”(不能处理对象中第一层数据为响应式)

3.1.1shallowReactive()--联系本文2.5.2

shallowReactive()与reactive()相比

不同点:“不能处理对象中第一层以下层的数据为响应式”

相同点:“可以处理对象中第一层数据为响应式(即便是对象类型)”,“不可以处理替换整个大对象时的响应式”

验证shallowReactive()“可以处理对象中第一层数据为响应式(即便是对象类型)”,“不可以处理替换整个大对象时的响应式”

测试:

1.初始化界面

2.整个替换job的值,是响应式的效果

3.1顺便记录一下,点击[替换整个person值]按钮的非响应式效果,加深印象

3.2接着点击[增长年龄]按钮,会发现在原person数据基础上+1,而非在p数据基础上+1。

3.1.2shallowRef()--联系本文2.5.2

shallowRef()与ref()相比

不同点:“不能处理对象类型数据中任意层数据为响应式”(生成的RefImpl对象的value值不再是Proxy类型而是Object类型)

相同点:“可以处理基本类型为响应式”,“可以处理替换整个大对象时为响应式

验证shallowRef()“可以处理替换整个大对象时为响应式”

           

重要:上图中要求p是被ref()包装过的,这样新的person才具备被ref()包装过的全面响应式对象的特点,才能实现如下界面效果::

1.1初始化界面

1.2.点击绿色按钮

2.如果p不被ref()修饰,通过person.value=p赋值

3.如果p不被ref()修饰,且通过person=p赋值,person也不加.value,那么绿色按钮也失效

4.如果p被reactive()修饰不被ref修饰,通过person.value=p赋值,则测试效果同1.1/1.2,新的person应该具备被ref()包装过的一切响应式特点:被shallowRef()修饰过的person对象(已具备替换大对象时的响应式)、person的value又被赋值一个Proxy类型的响应式对象(又具备了所有属性和嵌套属性的响应式)。

5.如果p被shallowReactive()修饰不被ref修饰,通过person.value=p赋值,则测试效果可照4.中效果类推(事实也确实如此),新的person应该具备以下响应式特点:被shallowRef()修饰过的person对象(已具备替换大对象时的响应式)、person的value又被赋值一个被shallowReactive()修饰的对象(又具备了仅第一层属性的响应式)。

6.如果p也被shallowRef()修饰不被ref修饰,通过person.value=p.value赋值,则新的person应该具备以下响应式特点:被shallowRef()修饰过的person对象(已具备替换整个大对象时的响应式)、person的value又被赋值一个被shallowRef()修饰的对象的value(仍然是替换整个大对象时的响应式)。

7.如果p也被shallowRef()修饰不被ref修饰,通过person.value=p赋值,即相比6.去掉了p的.value,则报错。

8.如果p也被shallowRef()修饰不被ref修饰,通过person=p赋值,即相比7.不仅去掉了p的.value,也去除person的.value,不报错,但连基本的替换大对象的响应式也丢失了

为了以后再测试,附上该部分源码。

<template>
	<h4>{{person}}</h4>
	<h2>姓名:{{person.name}}</h2>
	<h2>年龄:{{person.age}}</h2>
  <h2>性别:{{person.sex}}</h2>
	<h2>薪资:{{person.job.j1.salary}}K</h2>
	<button @click="person.name+='~'">修改姓名</button>
	<button @click="person.age++">增长年龄</button>
  <button @click="person.sex= (person.sex == '男' ? '女': '男')">改变性别</button>
	<button @click="person.job.j1.salary++">涨薪</button>
  <button @click="person.job={
            j1:{
              salary:2023
            }
				  }">整个替换掉job的值</button>
  <button @click="handler">整个替换掉person的值</button>
</template>
<script>
	import {ref,reactive,toRef,toRefs,shallowReactive,shallowRef} from 'vue'
    import {nanoid} from 'nanoid'
	export default {
		name: 'Demo',
		setup(){
			//数据
			let person = shallowRef({
				name:'张三',
				age:18,
        sex:'男',
				job:{
					j1:{
						salary:20
					}
				}
			})
      function handler(){
        let p = shallowRef({
          name:nanoid(),
          age:1233,
          sex:'123',
          job:{
            j1:{
              salary:1234
            }
          }
			  })
        person.value = p;
        console.log(person);
      }
			//返回一个对象(常用)
			return {
				person,
        handler
			}
		}
	}
</script>

3.2readOnly()与shallowReadonly()

readOnly()深只读,所谓深指salary等深层次属性也只读。shallowReadonly()只让第一层属性只读。readOnly()、shallowReadonly()都既可以修饰对象类型的响应式数据为只读也可以修饰基本类型的响应式数据为只读,都既可以修饰ref()定义的响应式数据为只读也可以修饰reactive()定义的响应式数据为只读,且readOnly()、shallowReadonly()不管修饰何种响应式数据,都返回一个Proxy实例对象--isProxy()验证返回true。假如用readonly()修饰非响应式数据,当修饰基本类型数据时会警告且返回值为原样输出当修饰对象类型时不会警告且会返回一个Proxy对象实例,isProxy(这个实例)返回true,但isReactive(这个实例)返回false

假如用readonly()修饰非响应式数据,当修饰基本类型数据时会警告返回值为原样输出当修饰对象类型时不会警告且会返回一个Proxy对象实例,但isReactive(这个实例)返回false

3.3toRaw()与markRaw()

3.3.1toRaw()

toRaw():能把reactive定义的响应式对象转为普通的非响应式对象return出去(作为参数的源对象数据还是响应式的不会被改变)。

toRaw():不能把ref定义的对象类型数据转为普通的非响应式对象,除非加.value

不能把ref定义的基本类型数据转为普通的非响应式数据。

<script>
	import {ref,reactive,toRefs,toRaw,markRaw} from 'vue'
	export default {
		name: 'Demo',
		setup(){
			let sum = ref(0)
            //ref包装的
			let person = ref({
				name:'张三',
				age:18,
				job:{
					j1:{
						salary:20
					}
				}
			})

			function showRawPerson(){
                //【ref包装的基本类型,toRaw()没有工作
				const h = toRaw(sum)
                console.log(h)
                //【ref包装的对象类型,不加.value,toRaw()没有工作
                const p1 = toRaw(person)
                console.log(p1)
                //【ref包装的对象类型,加.value,toRaw()可以工作
                const p2 = toRaw(person.value)
                console.log(p2)
			}

			return {
				sum,
				showRawPerson,
			}
		}
	}
</script>

控制台打印: 

3.3.2markRaw() 

用法示例:

markRaw() return出去的非响应式对象数据可以被修改,但不会响应到页面:

3.4customRef()

3.4.1customRef()

import {ref,customRef} from 'vue'

案例目标:

==》输入停止时下面才展示完(防抖

使用Vue3提供的ref():不能防抖

使用程序员借助Vue3提供的customRef()开发的自定义的myRef():能防抖

在自定义的ref()中使用customRef()也可实现ref()的功能:实现数据(customRef()中getter的返回值/myRef()的返回值/CustomRefImpl实例)的响应式,并且可以在响应式的基础上添加自己的代码(实现输入框防抖等功能),相当于Vue3提供了一个组合式的API让用户可以在Vue3的ref()的源码基础上自定义功能实现:“对其依赖项跟踪和更新触发进显式控制”。

<template>
	<input type="text" v-model="keyWord">
	<h3>{{keyWord}}</h3>
</template>

<script>
	import {ref,customRef} from 'vue'
	export default {
		name: 'App',
		setup() {
			//自定义一个ref——名为:myRef
			function myRef(value,delay){
				let timer //【用于·防抖】
                //调用Vue3提供的——customRef()
				return customRef((track,trigger)=>{
					return {
                        //模板初次加载时调用(此时不写track()也能调用),
                        //set()中的trigger()通知调用并且自己配置了track()时调用
						get(){
							console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
							track() //通知Vue追踪value的变化,
                            //这样接收到set()重新解析模板的通知后才能真正地执行get()
							return value //【这个值也是myRef()的返回值】
						},
                        //myRef/get()的返回值在模板中被修改时调用
						set(newValue){
							console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
							clearTimeout(timer) //【用于·防抖】
							timer = setTimeout(()=>{ //【用于·防抖】
                                //{清除定时器后定时器中代码也会终止执行,但对于此案例的功能实现无影响--
                                //只要拿到最新的输入框内容就可以了--历史输入内容无意义了}
								value = newValue
								trigger() //通知Vue去重新解析模板--重新调用get()-
                                //-但get()调用与否还要看get()中是否写了track()
							},delay)
						},
					}
				})
			}

            //使用程序员自定义的ref(),传入定时器参数
            //(使用定时器--目的--实现防抖)
			let keyWord = myRef('hello',500)
            console.log(keyWord)
			
			return {keyWord}
		}
	}
</script>

console.log(keyWord):CustomRefImpl

3.4.2防抖、节流

1.防抖

如果用户多次频繁操作以最后一次为准,当然也可以以第一次为准,进行数据更新或者网络资源请求,以消除冗余的操作,或者减少一定的请求资源浪费。

vue默认提供的防抖方法:

下面是在Vue2中使用debounce.js实现防抖的案例:

顺便附上上图中的代码: 

<template>
  <div class="main">
    <el-input v-model="inputVal" placeholder="请输入内容" @input="inputChange"></el-input>
    <el-button type="plain" @click="search">搜索</el-button>
  </div>
</template>

<script>
  import debounce from 'lodash/debounce' //导入lodash中的debounce防抖
  export default {
    name:'Test',
    data(){
      return {
        inputVal:''
      }
    },
    methods:{
      search(){
        console.log('---发送请求---')
      },
      inputChange:debounce(function(val){   //lodash中的debounce
        console.log('---输入框内容(lodash)---',val)
        console.log('---发送请求---')
      },200)
    }
  }
</script>

2.节流 

在一定时间范围内,用户触发多次只会执行一次以达到防止用户频繁操作的目的。

哈哈,vue在lodash下面也提供了throttle方法用于实现节流,下面是在Vue2中使用throttle.js实现防抖的案例:

<template>
  <div class="main">
    <el-input v-model="inputVal" placeholder="请输入内容" @input="inputChange"></el-input>
    <el-button type="plain" @click="search">搜索</el-button>
  </div>
</template>

<script>
  import debounce from 'lodash/debounce' //导入lodash中的debounce防抖
  import throttle from 'lodash/throttle' //导入lodash的throttle节流
  export default {
    name:'Test',
    data(){
      return {
        inputVal:''
      }
    },
    methods:{
      search:throttle(function(){   //lodash中的throttle节流
        console.log('---发送请求(lodash)---')
      },3000),
      inputChange:debounce(function(val){   //lodash中的debounce防抖
        console.log('---输入框内容(lodash)---',val)
        console.log('---发送请求---')
      },500)
    }
  }
</script>

3.5provide()与inject()实现组件间通信

父子组件间通信一般采用props,不采用provide()、inject()。

3.6isRef()/isReactive()/isReadonly()/isProxy()--响应式数据判断

且readOnly()、shallowReadonly()不管修饰何种响应式数据,都返回一个Proxy实例对象--isProxy()验证返回true。假如用readonly()修饰非响应式数据,当修饰基本类型数据时会警告且返回值为原样输出,当修饰对象类型时不会警告且会返回一个Proxy对象实例,isProxy(这个实例)返回true,但isReactive(这个实例)返回false。   

                                                                                                           ——引用自标题3.2记述

附上测试代码: 

<template>
	<input type="text" v-model="keyWord">
    <button @click="myfunc" type="button"></button>
	<h3>{{keyWord}}</h3>
</template>

<script>
	import {ref,reactive, readonly,shallowReadonly, customRef, isRef,isReactive,isReadonly,isProxy} from 'vue'
	export default {
		name: 'Demo',
		setup() {
			//自定义一个ref——名为:myRef
			function myRef(value,delay){
				let timer
				return customRef((track,trigger)=>{
					return {
             //模板初次加载时调用(此时不写track()也能调用),set()中的trigger()通知调用并且自己配置了track()时调用
						get(){
							//console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
							track() //通知Vue追踪value的变化,这样接收到set()重新解析模板的通知后才能真正地执行get()
							return value //【这个值也是myRef()的返回值】
						},
            //myRef/get()的返回值在模板中被修改时调用
						set(newValue){
							//console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
							clearTimeout(timer) //【防抖】
							timer = setTimeout(()=>{
								value = newValue
								trigger() //通知Vue去重新解析模板--重新调用get()--但get()调用与否还要看get()中是否写了track()
							}, delay)
						},
					}
				})
			}

	  let keyWord = myRef('hello',500) //使用程序员自定义的ref
      let simpleData = ref(1)
      let car = reactive({name:'奔驰',price:'40W'})
	  let car2 = readonly(car)//readonly/shallowReadonly不管修饰何种响应式数据,都返回Proxy实例对象
      let student = ref({name:'张三',age:'18'})
      let readonlyStudent = readonly(student);
      let readonlyStudentValue = readonly(student.value);
      let readonlySimpleData = readonly(simpleData);
      console.log("readonlySimpleData--被readonly修饰的ref定义的基本类型数据simpleData", readonlySimpleData);//【Proxy对象实例】
      console.log("car2--被readonly修饰的reactive定义的对象car", car2);//Proxy对象实例
      console.log("readonlyStudent--被readonly修饰的ref定义的对象student", readonlyStudent);//【Proxy对象实例】
      console.log("readonlyStudentValue--被readonly修饰的ref定义的对象student的value", readonlyStudentValue);//Proxy对象实例
      let a = 123;
      let b = {name:'zzz',age:'123'};

      let myfunc = (haha) => {
        console.log('[in my function] ', haha)
        return haha;
      }
      console.log("myfunc is", myfunc)
      // myfunc is haha => {
      // console.log('[in my function] ', haha);
      // return haha;
      // }

      let is = isRef(keyWord);console.log(is);//true--【isRef对custoneRef生成的对象的判断结果为true】
      is = isRef(simpleData);console.log(is);//true
	  console.log(isRef(car))//false--isRef对reactive生成的对象的判断结果为false
      console.log(isRef(car2))//false--
      is = isRef(myfunc);console.log(is);//false
      console.log(isRef(student))//true--
	  console.log(isRef(readonlyStudent))//true--
      console.log(isRef(student.value))//false--【isRef对reactive生成的对象的判断结果为false】
      console.log(isRef(readonlyStudentValue))//false--
      console.log('isRef========================================')

      is = isReactive(keyWord);console.log(is);//false--【isReactive对custoneRef生成的对象的判断结果为false】
      is = isReactive(simpleData);console.log(is);//false
	  console.log(isReactive(car))//true
	  console.log(isReactive(car2))//true
      is = isReactive(myfunc);console.log(is);//false
      console.log(isReactive(student))//false--【isReactive对ref生成的对象的判断结果为false】
	  console.log(isReactive(readonlyStudent))//false--
      console.log(isReactive(student.value))//true--isRef对ref生成的对象的value判断结果为true
      console.log(isReactive(readonlyStudentValue))//true--
      console.log(isReactive(readonly(b)))//false--【readonly修饰非响应式对象不警告且返回Proxy实例,但isReactive(这个实例)返回false】
      console.log(isRef(readonly(b)))//false--
      console.log('isReactive========================================')

      is = isReadonly(keyWord);console.log(is);//false
      is = isReadonly(simpleData);console.log(is);//false
	  console.log(isReadonly(car))//false
	  console.log(isReadonly(car2))//true--
      is = isReadonly(myfunc);console.log(is);//false
      console.log(isReadonly(student))//false
	  console.log(isReadonly(readonlyStudent))//true--
      console.log(isReadonly(student.value))//false
      console.log(isReadonly(readonlyStudentValue))//true--
      console.log(isReadonly(readonly(a)))//false--【readonly修饰非响应式基本类型数据会警告且会原样返回】
      console.log(isReadonly(readonly(b)))//true--【readonly修饰非响应式对象不警告且返回Proxy实例,但isReactive(这个实例)返回false】
      console.log('isReadonly========================================')

      is = isProxy(keyWord);console.log(is);//false
      is = isProxy(simpleData);console.log(is);//false
	  console.log(isProxy(car))//true
	  console.log(isProxy(car2))//true
      is = isProxy(myfunc);console.log(is);//false
      console.log(isProxy(student))//false
	  console.log(isProxy(readonlyStudent))//true--【特殊】
      console.log(isProxy(student.value))//true
      console.log(isProxy(readonlyStudentValue))//true
      console.log(isProxy(readonlySimpleData))//true--【特殊】
      console.log(isProxy(readonly(a)))//false--【readonly修饰非响应式基本类型数据会警告且会原样返回】
      console.log(isProxy(readonly(b)))//true--
      console.log('isProxy========================================')

	  return {keyWord, simpleData, myfunc, car, car2, student}
	  }
    }
</script>

4.Composition API相比传统API的优势(hook)

Vue2中OptionsApi        Vue3中CompositionApi

Vue2中OptionsApi的data、methods等配置项中的数据、方法等是把多个功能需求相关的数据、方法整合到一个data、methods配置项中,数据/方法/功能点多了以后,操作分辨起来很难。

Vue3中CompositionApi可以通过自定义hook函数(本文2.11标题)将同一功能点的数据、方法等整合到一个js文件中,在需要的地方引入即可。

5.新的组件

5.1Fragment组件

fragment组件不需要我们写代码,fragment可被翻译为片段。

底层采用的是fragment虚拟元素,不会渲染到页面:

在Vue开发者工具中可以体现出来(相当于官方给一个提示:我底层是用fragment虚拟元素)

5.2Teleport组件

使用<teleport to:""></teleport>标签时不需要import。

Teleport可翻译为传送、瞬间移动、闪现。可以用来做弹窗、遮罩层等,把组件内部自定义的<teleport>元素(内部嵌套弹窗、遮罩层)展示到<teleport>元素的to属性指定的标签/元素中——可以让<teleport>元素中嵌套的元素脱离组件标签所展示到的位置独立展示在指定的其它位置。

第4行to="body",意思是让弹窗遮罩层出现在index.html中的body元素体内的最后面(遮罩设置了样式覆盖整个页面,所以视觉上看不出是追加在body元素体内的最后面):

to属性值也可以写css选择器

假若不写teleport标签,在子组件中就很难实现(定位容易被组件的嵌套结构破坏)子子组件中的弹窗出现在整个屏幕的正中央。

5.3Suspense组件

使用<Suspense></Suspense>标签时不需要import,注意标签名的首字母是大写的。

Suspense底层是插槽/依赖插槽实现,用于让应用在网速差时有更好的用户体验,Suspense可翻译为悬而未决的。

控制台提示Suspense在Vue3中尚处于实验阶段。

异步引入对应静态引入,静态引入的组件如下图,两个组件同时出现在界面。

Suspense()使用案例:

效果(测试时在控制台把网速调慢一点):

==> 

补充:(测试时在控制台把网速调慢一点) ,如果不调网速,可以采用返回Promise对象实例的方式设置延时返回值,来进行测试。如下图:

上图等同下图写法(把setup()定义成异步函数):

6.Vue3中其它的改变

6.1全局API的转移--将Vue.xxx调整到应用实例对象app.xxx

6.2其它改变

vue2中给组件标签绑定一个单击事件会被当作自定义事件,此时可以使用native表示此事件是原生的单击事件:

那么Vue3是怎么处理的呢?

其它改变请参看官方文档。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值