日常坑点

83 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

问题发现:

在写一个小demo的时候,如果用户获取一组数据,但是他极快的连发了好几个请求,而异步获取的速度各不相同,此时需要返回最后一次点击获取到的数据,如果任由其进行操作,则可能导致数据混乱。

问题解决:

本文主要介绍:watch函数的第三个参数onInvalidate

这个东东?能干啥:

看一下官腔:让其注册一个回调在当前副作用函数过期时执行

好了,没事了,请看代码:

let FData;
watch(state, async (newValue, oldValue, onInvalidate)=>{
  let expired = false;
  onInvalidate(()=>{
    expired = true;
  })
  
  const res = await fetch("/user/info");
  
  if(!expired){
    FData = res;
  }
});

//第一次修改
state.age++;
//模拟第二次
setTimeout(()=>{
  state.age++;
},200)

代码中,第一次修改后,触发监听,进行请求,我们记为A请求,随机,第二次修改,请求记为B,在watch函数中,会将过期得回调递给一个cleanupFn将其消灭掉,然后返回给用户onInvalidate供使用,即,A请求就会被消灭,留下的是B请求,也是我们需要的最后的请求。

当然,很多朋友可能会说,你这玩意,加不加的,我要是正常来,最后不也是B请求的数据么?

是,按照顺序来说,是这个样子,后面B覆盖前面的A,但是,如果你不专门的对watch执行时机调度的话,结果可能还就是A覆盖B。So,这样一来,这个操作就变得很重要了~

  • 附上一小段供以理解的小代码~
function watch(source, cb, options={}){
  let getter;
  if(type === "function"){
    getter = source;
  }else{
    getter = ()=>traverse(source);
  }
  
  let oldValue, newValue;
  
  let cleanupFn;//用于存储用户注册的过期回调
  //定义onInvalidate函数
  const onInvalidate = (fn)=>{
    //将过期的回调函数存储到cleanupFn中
    fn();
  }
  
  // 提取调度函数为独立的函数
  const obj = ()=>{
    newValue = effectFn(); // 值变化时再次运行effect函数,获取新值
    
    //在调用回调函数cb之前,先调用过期的回调函数
    if(cleanupFn){
      cleanupFn();
    }
    cb(newValue,oldValue,onInvalidate);
    //更新旧值,不然下次得到的是错误的旧值
    oldValue = newValue;
  }
  
  const effectFn = effect(
    ()=>getter(),
    {
      //开启lazy选项,将返回值存储到effectFn中以便于之后手动调用
      lazy: true,
      scheduler(){
        if(options.flush === "post"){
          const p = Promise.resolve();
          p.then(obj);
        }else{
          obj();
        }
      }
    }
  )
  if(options.immediate){
    //当immediate为true时,立即执行scheduler函数从而触发回调执行
    scheduler()
  }else{
    //手动调用副作用函数,拿到的值是旧值
    oldValue = effectFn();
  }
}