架构的思考(5)

67 阅读3分钟

截至前面的部分,已经是完成了响应式的基本原理。那vue还有refwatch等等,我们就来处理一下。

1. ref函数

//ref.js
/**
 * @description: ref 处理一般数据
 * @param {* string | number} value 接受的值
 * @return {*} 响应式数据
 */
export function ref(value) {}

先把effect里的函数跟ref关联起来哈。

import { reactive } from "./reactive.js";
import { effect } from "./effect.js";
import { ref } from "./ref.js";

const state = ref(1);

effect(() => {
  console.log("effect", state.value);
});

现在来写ref,首先是返回一个对象,对象有个属性是value。当访问这个属性的时候,进行依赖收集;当修改属性值的时候,进行派发更新。

//ref.js
export function ref(value) {
  return {
    get value() {
      track(this, TrackOpTypes.GET, "value");
      return value;
    },
    set(newValue) {
      value = newValue;
      trigger(this, TriggerOpTypes.SET, "value");
    },
  };
}

2.computed函数

//index.js

const state = reactive({
  a: 1,
  b: 2,
});

const sum = computed(() => {
  console.log("computed");
  return state.a + state.b;
});

sum.value;
//computed.js

//参数可能是函数也可能是对象,对参数进行归一化
function normalizeParameter(getterOrOptions) {
  let getter, setter;
  if (typeof getterOrOptions === "function") {
    getter = getterOrOptions;
    setter = () => {
      console.warn(`Computed property was assigned to but it has no setter`);
    };
  } else {
    getter = getterOrOptions.get;
    setter = getterOrOptions.set;
  }
  return { getter, setter };
}


export function computed(getterOrOptions) {
  const { setter, getter } = normalizeParameter(getterOrOptions);
 effect(getter);//把函数交个 effect
}

现在是立即执行的,但vue里的计算属性是当你去访问它的值的时候才执行,那我们就设置下,当访问value的时候才执行。

export function computed(getterOrOptions) {
  const { setter, getter } = normalizeParameter(getterOrOptions);
  const effceFn = effect(getter, {
    lazy: true,
  });

  const obj = {
    get value() {
      return effceFn();
    },
  };
  return obj;
}

这样,当我们访问.value的时候,就会触发get,进而执行effect,进行关联。

但现在如果多次读这个属性,会多次触发。但vue的计算属性是有缓存的。如果发现结果没有变化,就会把结果给你,然后不再运行。

vue有个变量叫做dirty,用来决定要不要进行依赖收集和派发更新。

export function computed(getterOrOptions) {
  const { setter, getter } = normalizeParameter(getterOrOptions);

  let value,
    dirty = true;

  const effceFn = effect(getter, {
    lazy: true,
  });

  const obj = {
    get value() {
    //第一次 赋值
      if (dirty) {
        value = effceFn();
        dirty = false;
      }
      return value;
    },
  };
  return obj;
}

现在只执行一次了,但是还有个问题。

const sum = computed(() => {
  console.log("computed");
  return state.a + state.b;
});

console.log("sum.value", sum.value);
console.log("sum.value", sum.value);
console.log("sum.value", sum.value);

state.a++
state.a++
state.a++
state.a++
console.log("sum.value", sum.value);

image.pngstate.a变化了,依赖发生变化,但是值却还是之前的值,没发生变化。因为dirty变量变为false,那就不再依赖收集了。依赖发生变化后,dirty应该变为true

  const effceFn = effect(getter, {
    lazy: true,
    scheduler: () => {
      dirty = true; // 变为true 进入get 进行依赖收集
      effceFn();
    },
  });

之前的打印还发现一个问题,没有使用computed返回的值,但重新运行了。所以依赖发生变化了,只需要进行标记,说明数据脏了,不需要重新运行。

  const effceFn = effect(getter, {
    lazy: true,
    scheduler: () => {
      dirty = true; 
      // effceFn(); 不用运行
    },
  });

还有个问题,当我们不是在js,或者说是在模板里使用,这种情况呢?

function render() {
  console.log("render", sum.value);
}
effect(render);

vuerender函数就是放在effect里运行的,这里就算是模拟在模板中使用了。 刚才是会执行这个render函数,拿到值。但当我们去修改state.a的值的时候,并没有重新运行,这不扯淡嘛,我修改了值,页面不更新😑😑😑

分析一下,a变化了,要派发更新,但是走着走进那个函数之后,发现有scheduler,那就执行scheduler,而scheduler并没有将数据与函数进行关联,所以手动地加上派发更新就好了。

const effceFn = effect(getter, {
    lazy: true,
    scheduler: () => {
      dirty = true;
      trigger(obj, TriggerOpTypes.SET, "value");
    },
  });
 get value() {
      track(obj, TrackOpTypes.GET, "value");
      if (dirty) {
        value = effceFn();
        dirty = false;
      }
      return value;
    },

最后再把setter加上。

const obj = {
    get value() {
      track(obj, TrackOpTypes.GET, "value");
      if (dirty) {
        value = effceFn();
        dirty = false;
      }
      return value;
    },
    set value(newValue) {
      setter(newValue);
    },
  };

到这里,refcomputed就完成了,至于其他的,懂这些基础应该就等你慢慢摸索出来了。