vue中watch属性,vue的watch函数
本文讨论了如何用副作用函数和选项封装watch函数,并通过调度函数控制回调函数的立即执行和定时,也可以解决race问题。有兴趣的可以看看。
:
目录
1.写在前面2。手表3的实现原理。立即监视和回调的执行时间。回调函数4的执行时间。过期的副作用函数和清除5。写到最后。
1.写在前面
在上一篇文章中,我们讨论了compted的实现原理,即带效果和选项参数的封装。同样,watch也是基于此进行封装的。当然,watch也可以通过第三个参数来清理过期的副作用函数。我们不仅可以利用副作用函数的调度来实现回调函数的立即执行,还可以控制回调函数的执行时机。
2.watch的实现原理
watch的本质是观察一个响应的数据,当数据发生变化时通知并执行相应的回调函数。watch的实现本质上类似于computed,基于effect函数和options.scheduler选项。
常量数据={
姓名:平平,
年龄:18,
标志:正确
};
const state=新代理(数据,{
/*.*/
})
手表(状态,()={
Console.log(数据已更改.);
});
//当响应数据的年龄值被修改时,回调函数将被执行。
state.age
watch函数的实现代码如下所示,副作用函数存在于调度器选项中。当响应数据发生变化时,会触发调度器调度函数,而不是直接触发副作用函数。
//watch函数接收源响应数据和回调函数cb
功能表(信号源、cb){
效果(
//触发读操作,建立连接。
()=source.age,
{
scheduler(){
//当数据发生变化时,调用回调函数cb
CB();
}
}
)
}
在上面的代码片段中,我们使用watch监控数据时,只能观察soure.age的变化,不具有普适性,需要进一步封装。
功能表(信号源、cb){
效果(
//调用遍历函数递归读取并建立联系。
()=导线(源),
{
scheduler(){
//当数据发生变化时,调用回调函数cb
CB();
}
}
)
}
const is object=(value:any)=type of value=== object value!==null
函数遍历(value,seen=new Set()){
//如果读取的数据是原始值,或者已经读取了响应数据,则什么都不做。
如果(!is object(value) seen . has(value))return;
//将数据添加到seen,也就是遍历读取的数据,避免循环引用导致的死循环。
seen.add(值);
//递归读取依赖项集合的数据对象。
for(const k in value){
遍历(值[k],见);
}
返回值;
}
在上面的代码中,单独封装了一个递归函数traverse,可以遍历并递归读取响应数据,这样就可以读取对象的所有属性,并且在任何属性值发生变化时,都可以触发回调函数。
事实上,在使用watch进行数据观察时,不仅可以观察响应数据,还可以观察getter函数。然后,我们只需要先判断输入的观察数据的数据类型是否为函数,如果是,就赋给getter,否则,监听响应数据。
功能表(信号源、cb){
让getter
If(type of source=== function ){//如果是函数,就是getter的意思,可以直接赋值。
getter=source
}否则{
Getter=()=traverse(source)//包装成Effect对应的effectfn,在函数内部遍历,达到依赖集合的目的。
}
让oldValue,newValue
const effectFn=效果(
()=getter(),
{
//打开lazy选项,将返回值存储在effectFn中,供以后手动调用。
懒:真的,
scheduler(){
new value=effect fn();//当值改变时再次运行效果函数以获得新值
cb(新值,旧值);
//更新旧值,否则下次会得到错误的旧值。
oldValue=newValue
}
}
)
//手动调用副作用函数,得到的值是旧值
old value=effectFn();
}
实际上,上面的代码充分利用了lazy选项来创建一个lazy效果。通过手动执行effectFn函数获得的返回值是旧值。当数据发生变化,调度程序被触发执行时,effectFn函数将被重新执行,并获得一个新值。
这样我们就得到数据变化前后的新值和旧值,可以作为参数传递给回调函数cb。副作用函数改变后,我们需要将新值赋给旧值,方便后续计算,否则后续改变会得到错误的旧值。
编写演示以供使用:
手表(
()=state.age,
(新值,旧值)={
console.log(newValue,old value);
}
)
州.年龄
3.立即执行的watch与回调执行时机
watch本质上是effect的二次封装,它有两个特点:立即执行的回调函数,回调函数的执行时机。
立即执行的回调函数
即时回调函数。默认情况下,只有当响应数据改变时,才会执行手表的回调函数。但是,在Vue.js中,您可以通过options.immediate指定是否立即执行回调
当options.immediate存在且为true时,回调函数在创建监视时执行一次。其实回调函数的立即执行和后续执行本质上差别不大。所以它的scheduler调度器可以封装成一个通用函数,可以通过options.immediate的存在来判断是在初始化时执行还是改变
函数手表(source,cb,options={}){
让getter
if(type===function){
getter=source
}否则{
getter=()=traverse(source);
}
让oldValue,newValue
//将调度函数提取为独立函数。
const scheduler=()={
new value=effect fn();//当值改变时再次运行效果函数以获得新值
cb(新值,旧值);
//更新旧值,否则下次会得到错误的旧值。
oldValue=newValue
}
const effectFn=效果(
()=getter(),
{
//打开lazy选项,将返回值存储在effectFn中,供以后手动调用。
懒:真的,
调度程序:调度程序
}
)
if(options.immediate){
//当immediate为true时,立即执行调度器函数,触发回调执行。
调度程序()
}否则{
//手动调用副作用函数,得到的值是旧值
old value=effectFn();
}
}
在上面的代码中,回调函数是立即执行的,第一次执行回调函数时没有所谓的oldValue。此时,回调函数的旧值是未定义的。
回调函数的执行时机
当然,除了上面的,回调函数可以指定立即执行,回调函数的执行时间也可以通过options参数指定。在Vue.js3中,可以通过flush选项指定调度函数的执行时间。当flush的值为 post 时,意味着调度函数需要将副作用函数放入微任务队列中,等待DOM更新完成后再执行。
函数手表(source,cb,options={}){
让getter
if(type===function){
getter=source
}否则{
getter=()=traverse(source);
}
让oldValue,newValue
//将调度函数提取为独立函数。
const obj=()={
new value=effect fn();//当值改变时再次运行效果函数以获得新值
cb(新值,旧值);
//更新旧值,否则下次会得到错误的旧值。
oldValue=newValue
}
const effectFn=效果(
()=getter(),
{
//打开lazy选项,将返回值存储在effectFn中,供以后手动调用。
懒:真的,
scheduler(){
if(options.flush===post){
const p=promise . resolve();
p . then(obj);
}否则{
obj();
}
}
}
)
if(options.immediate){
//当immediate为true时,立即执行调度器函数,触发回调执行。
调度程序()
}否则{
//手动调用副作用函数,得到的值是旧值
old value=effectFn();
}
}
实际上是基于options.flush是否等于 post 来实现obj函数是否需要异步处理。
4.过期的副作用函数和cleanup
说到手表过期的副作用功能,就不得不提到多进程或者多线程编程中经常提到的race问题。在下面的代码片段中,watch用于观察状态对象的变化,每次状态对象发生变化时都会发送一个网络请求。
let finalData
watch(state,async ()={
//发送等待网络请求。
const RES=await fetch(/user/info );
finalData=res
})
在上面的代码中,看似没有问题,但实际上会有种族问题。第一次修改状态对象的字段值后,执行回调,同时发送第一个请求A;在A请求返回结果之前,我们继续修改state的字段值,同时发送第二个请求B。但是我们不知道先返回谁的结果,请求A还是请求B?
用A的请求结果覆盖b的请求结果。
在理论分析下,我们先后发送了A和B请求,应该是先返回A再返回B请求的结果。这是因为请求A是副作用函数第一次执行的副作用,而请求B是副作用函数第二次执行的副作用。请求B发生在请求A之后,请求A应该早就过期了,返回的结果应该是无效的。
但是如果之前没有安排watch的执行定时,就会出现请求A的值,然后返回覆盖B的请求的返回值的错误。
要解决这个问题,我们只需要提供一个让副作用失效的手段。实际上,watch函数的回调函数可以传入onInvalidate函数的第三个参数来注册一个在当前副作用函数到期时要执行的回调:
函数手表(source,cb,options={}){
让getter
if(type===function){
getter=source
}否则{
getter=()=traverse(source);
}
让oldValue,newValue
让cleanupFn//用于存储用户注册的过期回调
//定义onInvalidate函数
const onInvalidate=(fn)={
//将过期的回调函数存储在cleanupFn中
fn();
}
//将调度函数提取为独立函数。
const obj=()={
new value=effect fn();//当值改变时再次运行效果函数以获得新值
//在调用回调函数cb之前调用过期的回调函数
if(cleanupFn){
clean upfn();
}
cb(newValue,oldValue,on invalidate);
//更新旧值,否则下次会得到错误的旧值。
oldValue=newValue
}
const effectFn=效果(
()=getter(),
{
//打开lazy选项,将返回值存储在effectFn中,供以后手动调用。
懒:真的,
scheduler(){
if(options.flush===post){
const p=promise . resolve();
p . then(obj);
}否则{
obj();
}
}
}
)
if(options.immediate){
//当immediate为true时,立即执行调度器函数触发回调执行。
调度程序()
}否则{
//手动调用副作用函数,得到的值是旧值
old value=effectFn();
}
}
在上面的代码片段中,定义了一个变量来存储用户通过onInvalidate函数注册的回调函数,过期的回调被赋给cleanupFn。在作业函数中,每次执行回调函数cb时,都会检查是否有过期的回调。如果有过期的回调,将执行cleanupFn函数来清理它,然后将onInvalidate返回给用户。
编写演示练习:
watch(state,async (newValue,oldValue,onInvalidate)={
let expired=false
onInvalidate(()={
过期=真;
})
const RES=await fetch(/user/info );
如果(!过期){
finaleData=res
}
});
//第一次修改
state.age
setTimeout(()={
state.age
},200)
示意图如下:
请求已过期
5.写在最后
在本文中,我们讨论了如何用副作用函数和选项封装手表函数。我们还可以通过调度函数来控制回调函数的立即执行和定时,也解决了争用问题。
以上是Vue.js实现watch属性的例子的详细说明的详细内容。更多关于Vue.js watch属性的信息,请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。