完整原文地址见
更多完整Vue笔记目录敬请见《前端 Web 笔记 汇总目录(Updating)》
本文内容提要
- Composition API 的作用
- setup函数
- 例程,打印查看setup内容
- 非响应引用的案例
ref()
概念、原理 与 实战reactive()
概念、原理 与 实战- 使用
readonly
限制对象的访问权限- 使用
toRefs()
对reactive
对象进一步封装- 多个属性进行解构
- 多个属性 配合toRefs() 进行解构
- toRefs()无法处理 undefined的键值
- 使用toRef()应对上述问题
- 关于setup函数的三个参数【attrs、slots、emit】
- 回顾 没有 CompositionAPI时,emit的用法
- 使用setup的 context.emit 替代 this.$emit
- 使用Composition API开发 todoList
- 完善toDoList案例
- 优化上例的逻辑结构!
- setup的 computed 计算属性
- 当然以上是computed 的默认用法,实际上它可以接收一个对象
- 将上例的处理值换成 Object类型,再例
- setup 中的 watch 监听
- setup 中的 watch 监听:监听Object类型
- setup 中的 watch 监听:监听Object类型的 多个属性
- setup 中的 watchEffect监听 以及 与 watch 的异同比较
- 两者都可以用以下的方式,在一个设定的时延之后,停止监听
- 为 watch 配置 immediate属性,可使具备同watchEffect的 即时性
- setup 中的 生命周期
- setup中的provide、inject用法
- 配合上ref实现 响应特性 以及 readonly实现 单向数据流
- setup结合ref指令
Composition API 的作用
使得相同的、相关的功能代码 可以比较 完整地聚合起来,
提高可维护性、可读性,提高开发效率;
规避 同一个功能的代码,
却散落在 组件定义中的data、methods、computed、directives、template、mixin
等各处 的问题;
setup函数
--- Composition API 所有代码编写之前,
都要 建立在setup函数 之上;
--- 在created 组件实例 被完全初始化之前 回调;
(所以注意在setup
函数中,
使用与this
相关的调用是没有用的)
--- setup函数中的内容,
可以在 该组件的 模板template
中直接使用;
(如下例程)
Hello World! heheheheheheda
运行效果:
例程,打印查看setup内容
运行效果:
由于调用时序的关系,setup中 无法调用this等相关 如变量、methods中 等 其他内容,但是其他内容 却可以调用 setup函数!!【setup生时众为生,众生时setup已生】
非响应引用的案例
如下,这样没有使用ref
/reactive
的写法,是不会有响应的:
Hello World! heheheheheheda
如下,运行之后,两秒延时之后,DOM文本展示并不会自动改成zhao
,而是一直展示初始化的guan
:
ref()
概念、原理 与 实战
使用
ref
可以 用于处理基础类型的数据
,赋能响应式
;
原理:通过 proxy 将数据
封装成
类似proxy({value: '【变量值】'})
这样的一个响应式引用
,
当数据
变化时,就会 触发template
等相关UI的更新
【赋予 非data中定义的变量 以响应式
的能力 ——
原先,我们是借助Vue的data函数
,完成响应式变量
的定义的;
有了ref
之后,我们可以不借助data
中的定义,
而直接在普通的函数
中对js变量
做proxy
封装,
就可以对普通的js引用
赋能响应式
了】;
Hello World! heheheheheheda
运行效果:
两秒后自动变化:
reactive()
概念、原理 与 实战
使用
reactive()
用于处理非基础类型的数据(如Object、Array)
,赋能响应式
;
原理类似ref()
,只是处理的数据格式不同而已;
如下,普通的Object类型
是没有响应式
的效果的:
Hello World! heheheheheheda
运行效果,两秒后无反应:
使用reactive()
处理Object类型
后,具备响应式
的能力:
Hello World! heheheheheheda
运行效果:
两秒后自动变化:
使用reactive()
处理Array类型
数据
Hello World! heheheheheheda
运行效果:
两秒后自动变化:
使用readonly
限制对象的访问权限
使用readonly()
封装对象,可以限制对象为只读权限
;
运行两秒之后会有相应的报错:
错误的解构案例
如下的解构是行不通的,
const { name } = nameObj;
只能拿到nameObj
的值,拿不到proxy
对象;
注意解构技巧
赋值时 加上{}
,会 直取 其 赋值右侧 Object的值;
直接赋值,便是直接赋值一份引用;
使用toRefs()
对reactive
对象进一步封装
---
toRefs() expects a reactive object
;
首先,toRefs()
需要接受一个reactive
对象;
即,传给toRefs()
之前,需要先用reactive()
进行封装;
--- 使用toRefs()
对reactive
封装的对象 进一步封装,
便可以顺利解构;
--- 原理:toRefs()
将类似proxy({name:'guan'})
的结构,
转化成类似{ name: proxy( {value:'guan'}) }
的结构,
这里可以看到name
键的值,其实就类似于ref
的处理结果;
然后使用const { name }
对{ name: proxy( {value:'guan'}) }
进行解构赋值,
左侧name
变量 拿到的就是proxy( {value:'guan'})
这一部分的值,
所以放入DOM节点展示时候,
直接使用name
即可;
--- !!!注意:
类似reactive()
的处理结果,
即proxy({name:'guan'})
的结构,
放入DOM节点展示时候,需要使用nameObj.name
的格式;
而类似ref()
的处理结果,
即proxy( {value:'guan'})
的结构,
放入DOM节点展示时候,直接使用nameObj
的格式即可;
运行两秒后,自动更新UI:
多个属性进行解构
注意多个属性解构时的写法 以及
return时的写法;
运行结果(当然不会自动更新):
多个属性 配合toRefs() 进行解构
运行结果:
toRefs()无法处理 undefined的键值
如果意图解构的键,
不存在于toRefs()
封装的对象中,
使用时会报错:
运行结果:
使用toRef()应对上述问题
toRef(data, key)
会尝试从data
中读取key
对应的键值,
如果读得到,就直接取值,
如果读不到,会赋值undefined,后续可以为之赋实值:
运行两秒后自动更新:
关于setup函数的三个参数
setup函数的context参数中的三个属性,即
attrs, slots, emit
;
获取方法(解构context参数
):setup(props, context) { const { attrs, slots, emit } = context; return { }; }
attrs
-- 是一个
Proxy
对象;
-- 子组件的none-props属性
都存进attrs
中;
如下,父组件调用子组件,传递myfield
属性,
子组件没有用props
接收,则myfield
作为none-props属性
被 子组件承接,
这时候会存进setup
函数的attrs
中:
Hello World! heheheheheheda
运行效果:可以看到attrs
也是一个Proxy
对象
(attrs.myfield)
打印取值:
slots
-- 是一个
Proxy
对象;
-- 其中有一个 以为default
为键的函数,
这个函数会以虚拟DOM
的形式,
返回 传给 子组件的slot插槽
的组件
;
打印slots属性:
打印slots属性中的 default函数, 可见其返回的是虚拟DOM:
使用setup中 context参数的 slots属性中的 default方法所返回的 虚拟DOM,通过render函数的形式,在setup函数返回,可以覆盖template
的内容,渲染UI
补充:不使用 Vue3 的这个compositionAPI,子组件只能这样去获取slots
内容
通过在其他函数中,使用this.$slots
的方式调用到slots
内容
运行结果如下,可以看到跟setup
函数的context.slots
是一样的:
回顾 没有 CompositionAPI时,emit的用法
使用setup的 context.emit 替代 this.$emit
运行,点击文本:
使用Composition API开发 todoList
调测input框事件
setup中,
--- const inputValue = ref('6666');
定义到一个proxy
对象,
可以将此对象 用于setup外的 template中,
完成双向绑定
中的一环——数据字段映射到 UI
!!!;
--- handleInputValueChange
定义一个方法;
运行时,将对应触发组件
的 内容
,
赋值给 inputValue
,
完成双向绑定
中的另一环——UI 映射到数据
!!!;
template中,
:value="inputValue"
使得对象的内容 初始化显示inputValue
的内容;
--- @input="handleInputValueChange"
使得输入框被用户输入新的内容时,
会调用对应的方法,这里调用:
运行效果:
完善toDoList案例
--- setup 中定义 数组list,以及 handleSubmit处理函数,
并都在return中返回;
--- template中,
button添加click事件回调;
li 标签,使用v-for,完成列表渲染:
Hello World! heheheheheheda
运行效果:
优化上例的逻辑结构!
如下,将 setup()中,
--- 与 list 相关的定义和操作函数,
封装到listHandleAction
中,然后return;
--- 与 inputValue 相关的定义和操作函数,
封装到inputHandleAction
中,然后return;
--- 最后setup()
调用以上两个业务模块封装函数
,
解构返回的内容
,进行中转调用
;
【分模块 聚合业务逻辑
,只留一个核心方法
进行统筹调度
,有MVP
那味了】
【这样设计,业务分明,方便定位问题,可读性、可维护性高!!
】
运行效果:
setup的 computed 计算属性
运行结果:
当然以上是computed 的默认用法,实际上它可以接收一个对象
这个对象包含两个函数属性,
第一个是get
函数,其内容即取值时候返回的内容,同默认用法;
第二个是set
函数,当试图修改computed
变量的值时,就会回调这个方法,
接收的参数,即试图修改的值
:
如下,试图在3秒后修改computed
变量countAddFive
的值,
这时回调set方法:
运行3s后:
将上例的处理值换成 Object类型,再例
运行效果同上例;
setup 中的 watch 监听
如下,
---watch
一个参数为要监听的引用,
第二个参数为函数类型,当监听的引用发生变化时会回调,
其有两个参数,一个是当前(变化后的)值,一个是变化前的值;
--- input
组件中,v-model
完成双向绑定
!!!
--- input
输入内容时,触发 双向绑定
的特性,
内容映射到name
引用上,
由ref
的响应特性
,name
的内容又映射到{{name}}
这DOM节点
上:
Hello World! heheheheheheda
运行效果:
setup 中的 watch 监听:监听Object类型
注意setup的watch的 可监听数据类型
所以,这里主要是
---将watch的 第一个参数改成 函数;
---使用toRefs,简化传递过程;
(不然template要接收和处理的就是 nameObject.name了,而不是这里的name)
运行效果同上例;
setup 中的 watch 监听:监听Object类型的 多个属性
注意watch的参数写法,
一参写成,以函数类型
为元素
的数组
;
二参,参数列表写成两个数组,
第一个为current值数组,第二个为prev值数组;
Hello World! heheheheheheda
运行,先在Name输入框输入,后再EnglishName框输入,效果:
setup 中的 watchEffect监听 以及 与 watch 的异同比较
函数中,使得纯函数 变成 非纯函数的 异步处理等部分逻辑块,
称之为effect块
;
---
watch
是惰性
的,只有watch
监听的字段发生变化时,
watch
的处理逻辑才会被回调;
---watchEffect
是即时性
的,也就是除了watch
的回调特性
,
watchEffect
的处理逻辑
还会在页面渲染完成时
立马先执行一次,
即watchEffect
监听的字段未曾改变,
watchEffect
就已经执行了一次;
(实例可以见下例运行效果)
watch
需要写明监听字段
,
watchEffect
不需要,直接写处理逻辑
即可,
底层封装
会!自动监听!
所写处理逻辑
中用到的!所有字段!
;
如下例子中,
watchEffect
的处理逻辑——console.log(nameObj.name, nameObj.englishName);
,
仅一行代码,
完成对nameObj.name
和nameObj.englishName
两个字段的监听,
并完成了处理逻辑;
watch
可以直接从参数列表
中获取到之前(变化前)的值
和当前(变化后)的值
,
watchEffect
不行,处理逻辑中拿到的直接就是当前(变化后)的值
;
- 两者都可以用以下的方式,在一个设定的时延之后,停止监听
- 为
watch
配置 immediate属性,可使具备同watchEffect
的 即时性
跟紧console.log(nameObj.name, nameObj.englishName);
,
先在Name输入框输入123,后再EnglishName框输入456,运行效果:
注意第一行打印,第一行是页面渲染完成时立马执行,
用户未曾输入内容,watchEffect
监听的字段未曾改变,
watchEffect
就已经执行了一次,体现watchEffect
的即时性
!!!
两者都可以用以下的方式,在一个设定的时延之后,停止监听
将
watch / watchEffect
的函数返回值
赋给一个字段(如下stopWatch / stopWatchEffect
);
接着在watch / watchEffect
的处理逻辑
中,
编写类似setTimeout
的异步函数,
并在其中调用 与 刚刚定义的字段
同名的 函数(如下stopWatch() / stopWatchEffect()
),
即可停止对应的监听
;
运行,可以看到,
前3s(我们设定的时延)两个输入框是可以响应监听的,
但是3s之后,无论怎么输入内容,console也不会打印log,
因为这时两个监听效果已经取消了:
为 watch
配置 immediate属性,可使具备同watchEffect
的 即时性
如下,使用{ immediate: true}
为 watch
配置 immediate属性,
可使具备同watchEffect
的 即时性:
运行效果如下,undefined
是因第一次执行时,
监听的变量还没有变化,
所以就没有先前值(prevValue)
的说法,只有当前值(currentValue)
:
setup 中的 生命周期
--- Vue3.0提供了一些对应生命周期的,可以写在setup
函数中的回调方法;
(具体请看 下方例程)
--- setup
函数的执行时间点
在于beforeCreate
和Created
之间,
所以CompositionAPI
里边是没有类似onBeforeCreate
和onCreated
的方法的,
要写在这两个周期中的逻辑,
直接写在setup
中即可;
下面是两个Vue3.0引入的新钩子:
--- onRenderTracked
渲染跟踪,
跟踪 收集响应式依赖的时机,
每次准备开始渲染时(onBeforeMount后,onMounted前)回调;
--- onRenderTriggered
渲染触发,
每次触发
页面重新渲染时回调,
回调后,下一轮的 onBeforeMount紧跟其后;
运行效果:
setup中的provide、inject用法
--- 父组件拿出provide
,提供键值对;
--- 子组件拿出inject
,一参为接收键,二参为默认值;
Hello World! heheheheheheda
运行结果:
配合上ref
实现 响应特性 以及 readonly
实现 单向数据流
--- setup中, 借provide
传输的 数据字段 配合上ref
,
使之具备响应
的特性;
--- 父组件提供 更改数据的接口,
在使用provide
传递 数据字段时,加上 readonly
包裹,
使得子组件 需要更改 父组件传递过来的数据字段 时,
无法直接 修改字段,
需调用 父组件的接口方法 更改,
按 单向数据流 规范编程;
运行效果:
setup结合ref指令
前面笔记《Vue3 | Mixin、自定义指令、Teleport传送门、Render函数、插件 详解 及 案例分析》有写到普通场景的ref指定;
--- setup中的ref是前面说的生成响应式字段的意思;
--- template中的ref是获取对应的dom节点;
--- 而当 template中的ref
指定的字段名,
跟setup中的ref生成的响应式字段名一样的时候,两者就会关联起来;
如下,
template中和setup中的字段heheda
关联起来,
在setup的onMounted中,使用这个DOM节点字段,
打印DOM代码:
运行结果: