vue3响应式实现原理(3)

简介: vue3响应式实现原理(3)

theme: fancy

highlight: a11y-light

纠正两个问题

function trigger(target, key) {
   
  let effects = bucket?.get(target)?.get(key);
  const effectsToRun = new Set(effects);
  effects &&
    effectsToRun.forEach((effectFn) => {
   
    //开始
      let deps = effectFn.deps;
      deps.forEach((item) => {
   
        item.delete(effectFn);
      });
      effectFn.deps.length = 0;
      //结尾
      //这一段代码应放到执行effectFn函数之前,如果放在这,后面的代码不需要执行,effectFn函数也不会触发,所以需要把这段代码换个位置
      if (activeEffect !== effectFn) {
   
        effectFn.options.scheduler
          ? effectFn.options.scheduler(effectFn)
          : effectFn();
      }
    });
}
//放到effect函数内
function effect(fn, options = {
   }) {
   
  const effectFn = () => {
   
    let deps = effectFn.deps;
    deps.forEach((item) => {
   
      item.delete(effectFn);
    });
    effectFn.deps.length = 0;
    activeEffect = effectFn;
    effectStack.push(activeEffect);
    res=fn();//也就是以这个函数执行为分界线,在这之前清除副作用,赋值入栈,之后出栈赋值
    effectStack.pop();
    activeEffect = effectStack[effectStack.length - 1];
    //return fn();不能直接返回fn()会导致fn的立即执行
    return res//用res存下来
  };
  effectFn.deps = [];
  effectFn.options = options;
  if (options.lazy) {
   
    return effectFn;
  } else {
   
    effectFn();
  }
}

实现watch

首先说明watch的实现也需要effect加调度器,先实现一个基础的watch

function watch(obj, cb) {
   
  effect(() => obj.age, {
   
    scheduler() {
   
      cb();
    },
  });
}
watch(proxyData, () => {
   
  console.log("changed");
});
proxyData.age++;
proxyData.age++;

上面只能监听一个属性,现在需要监听传入对象的任意属性都能触发

function watch(obj, cb) {
   
  effect(
    () => {
   
      travel(obj)
    },
    {
   
      scheduler() {
   
        cb();
      },
    }
  );
}
function travel(obj){
   
  for (const key in obj) {
   
    if (typeof obj[key] === 'object' && obj[key]!=null) {
   
      travel(obj[key]);
    }
  }
}

watch的参数也能传入一个函数,实现也很简单,只需要简单判断执行

function watch(getter, cb) {
   
  effect(
    () => {
   
      if (typeof getter === "function") {
   
        getter();
      } else {
   
        travel(getter);
      }
    },
    {
   
      scheduler() {
   
        cb();
      },
    }
  );
}

那么怎么拿到watch的新值和旧值,可以借助之前的lazy,他保存着上次副作用函数执行的结果,也就是我们需要的旧值,并且watch不需要再执行副作用函数,所以使用lazy没有任何影响。

function watch(getter, cb) {
   
  let newVal, oldVal;//新增
  const effectFn = effect(
    () => {
   
      if (typeof getter === "function") {
   
        return getter();//新增,这里要加个return,把值返回回来才能用lazy拿到
      } else {
   
        travel(getter);
      }
    },
    {
   
      lazy: true,//新增
      scheduler() {
   
        newVal = effectFn();//新增
        cb(newVal, oldVal);
        oldVal = newVal;//新增
      },
    }
  );
  oldVal = effectFn();//新增
}

watch还有一个参数immediate,在注册的时候就应该监听一次,之前在注册watch函数后等待数据改变触发副作用,现在直接在注册watch函数手动执行一次函数。实现:给watch添加第三个参数

watch(
  () => proxyData.age,
  (newVal, oldVal) => {
   
    console.log(newVal, oldVal);
  },
  {
    immediate: true }
);

function watch(getter, cb, options = {
   }) {
   
  let newVal, oldVal;
  const job = () => {
   //将调度函数抽离
    newVal = effectFn();
    cb(newVal, oldVal);
    oldVal = newVal;
  };
  const effectFn = effect(
    () => {
   
      if (typeof getter === "function") {
   
        return getter();
      } else {
   
        travel(getter);
      }
    },
    {
   
      lazy: true,
      scheduler: job,//修改
    }
  );
  if (options.immediate) {
   //新增
    job();
  } else {
   
    newVal = effectFn();
  }
}

flush用于控制调度器的执行时机

在调度器函数内检测 options.flush 的值是否为post,如果是,则将 job 函数放到微任务队列中,从而实现异步延迟执行;否则直接执行 job 函数

watch(
  () => proxyData.age,
  (newVal, oldVal) => {
   
    console.log(newVal, oldVal);
  },
  {
    immediate: true, flush: "post" }
);

function watch(getter, cb, options = {
   }) {
   
  let newVal, oldVal;
  const job = () => {
   
    newVal = effectFn();
    cb(newVal, oldVal);
    oldVal = newVal;
  };
  const effectFn = effect(
    () => {
   
      if (typeof getter === "function") {
   
        return getter();
      } else {
   
        travel(getter);
      }
    },
    {
   
      lazy: true,
      scheduler() {
   //修改
        if (options.flush === "post") {
   
          const p = Promise.resolve();
          p.then(job);
        } else {
   
          job();
        }
      },
    }
  );
  if (options.immediate) {
   
    job();
  } else {
   
    newVal = effectFn();
  }
}
相关文章
|
5天前
|
JavaScript API
Vue3 基础语法
该内容介绍了Vue项目的创建和Vue3的语法、响应式API、生命周期、组件通信及跨组件通信方法。包括使用`npm init vue@latest`创建项目,`npm install`初始化,Vue3的`setup`语法,`reactive`、`ref`、`computed`和`watch`的用法,生命周期图解,以及父子组件间的数据传递。此外,还提到了Vue3中使用`provide`和`inject`进行跨层数据传递,以及通过Pinia库进行状态管理。
29 0
Vue3 基础语法
|
8天前
|
JavaScript 定位技术 API
在 vue3 中使用高德地图
在 vue3 中使用高德地图
18 0
|
9天前
vue3 键盘事件 回车发送消息,ctrl+回车 内容换行
const textarea = textInput.value.textarea; //获取输入框元素
24 3
|
11天前
|
JavaScript 前端开发 CDN
vue3速览
vue3速览
26 0
|
11天前
|
设计模式 JavaScript 前端开发
Vue3报错Property “xxx“ was accessed during render but is not defined on instance
Vue3报错Property “xxx“ was accessed during render but is not defined on instance
|
11天前
|
JavaScript 前端开发 安全
Vue3官方文档速通(下)
Vue3官方文档速通(下)
23 0
|
11天前
|
JavaScript API
Vue3 官方文档速通(中)
Vue3 官方文档速通(中)
29 0
|
11天前
|
缓存 JavaScript 前端开发
Vue3 官方文档速通(上)
Vue3 官方文档速通(上)
42 0
|
11天前
Vue3+Vite+Pinia+Naive后台管理系统搭建之五:Pinia 状态管理
Vue3+Vite+Pinia+Naive后台管理系统搭建之五:Pinia 状态管理
15 1
|
11天前
Vue3+Vite+Pinia+Naive后台管理系统搭建之三:vue-router 的安装和使用
Vue3+Vite+Pinia+Naive后台管理系统搭建之三:vue-router 的安装和使用
20 0