Open17

VueのReactiveのコードを少しだけ読む

remonremon

Vueのアップデートをしたらpackages/reactiveの変更で不具合が生じていたので、この際少しだけ調べる。

remonremon

https://github.com/vuejs/core/blob/01172fdb777f9a0b51781ed2015a1ba3824340a3/packages/reactivity/src/reactive.ts#L77-L89

https://github.com/vuejs/core/blob/01172fdb777f9a0b51781ed2015a1ba3824340a3/packages/reactivity/src/reactive.ts#L242-L280

deep-diveで説明されていたように、内部でproxyオブジェクトを生成して返却している。

deep-diveの下の例と比べると、第二引数のオブジェクトが、別メソッドに切り分けられているので、次はそこを読む。

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
    }
  })
}

あとはweakMapでproxyオブジェクトを管理して、既にそのオブジェクトのproxyオブジェクトがあった場合はそちらを返すような処理がある。

remonremon

proxyオブジェクト生成時の第二引数に渡ってるオブジェクトはMutableReactiveHandler のインスタンス。

https://github.com/vuejs/core/blob/01172fdb777f9a0b51781ed2015a1ba3824340a3/packages/reactivity/src/baseHandlers.ts#L265

MutableReactiveHandlerの実装は

https://github.com/vuejs/core/blob/01172fdb777f9a0b51781ed2015a1ba3824340a3/packages/reactivity/src/baseHandlers.ts#L166-L237

ここで、proxyのオブジェクトのhandlerのメソッドを少しだけ参照しておく

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy

remonremon

脇道に逸れたが、

このhandlerオブジェクトがdeep-diveの概要のように、

getメソッドでtrack ( effectの登録)、setメソッドでtrigger(effectの実行)がなされているかを
見てみる。

remonremon

dep

depはeffectを管理するためのmapっぽい

remonremon

effect

effectって結局なんだっけ...

trackする際に登録してるactiveEffectはグローバス変数っぽい...

https://github.com/vuejs/core/blob/01172fdb777f9a0b51781ed2015a1ba3824340a3/packages/reactivity/src/effect.ts#L26

と思ったけど exportされてるやつはreadonlyってことで良いのかな。

このファイルの二箇所でしか書き変わってなさそう?

https://github.com/vuejs/core/blob/01172fdb777f9a0b51781ed2015a1ba3824340a3/packages/reactivity/src/effect.ts#L115

https://github.com/vuejs/core/blob/01172fdb777f9a0b51781ed2015a1ba3824340a3/packages/reactivity/src/effect.ts#L122

ReactiveEffectをrunした時にactiveEffectが更新されるらしい?

remonremon

ここまでのコードリードでReactiveEffectのインスタンスが生成されていないが、

それは当たり前だった。

多分computedとかを使った時にeffectが作成される

remonremon

ちょっと追えなくなってきたが、

deep-diveの例をEffectオブジェクトを使うように拡張してcomputedを作ってみよう

remonremon

完全理解。


class Observable {
  const subscriptions = []

  on = (callback) => {
     subscriptions.push(callback)
  }

  next = () => {
     subscriptions.forEach((callback) => callback())
  }
}

内部でSubscriptionの配列を持って購読をする際に配列に追加する。

というのが基本の処理。

Vueの場合はcomputedwatchがsubscribeみたいなものと考えると良い


let activeEffect = null;

const effect = (fn) => {
    const _effect = (fn) => {
         activeEffect = _effect;
          fn();
          activeEffect = null;
    }
   _effect();
}

const computed = (fn) => {
   let value = fn();

   effect(() => value = fn();)

   return {
        value
    }
}


computedを実行すると、内部でeffectが生成されて実行される。

effect実行前に、activeEffectとして自身を参照できるようにする。

effectを実行し、ref.valueのアクセスしたタイミングでtrackを開始。ref.valueが更新されたときに
effectが実行されるようになる。

remonremon
const ref = (value) => {
  return {
    get value() {
      track(ref)
      return value;
    },
    set value(newVal) {
      value = newVal;
      trigger(ref);
    },
  };
};

let activeEffect = null;

const effect = (fn) => {
  const _effect = () => {
    activeEffect = _effect;
    fn();
    activeEffect = null;
  }
  _effect();
};

const targetMap = new WeakMap();

const track = (target) => {
  let deps = targetMap.get(target);

  if (deps === undefined) {
    deps = new Set();
    targetMap.set(target, deps);
  }

  if (activeEffect && !deps.has(activeEffect)) {
    deps.add(activeEffect);
  }
};

const trigger = (target) => {
  const deps = targetMap.get(target);

  if (deps === undefined) {
    return;
  }

  deps.forEach((effect) => {
    effect();
  });
};


const computed = (getter) => {
  let value;

  effect(() => {
    value = getter()
  })

  return {
    get value() {
      return value
    }
  }
}

const count = ref(0)
const doubled = computed(() => {
  return count.value * 2
})

console.log(count.value, doubled.value)

count.value = 2

effect(() => {
  console.log(count.value, 'changed')
})


console.log(count.value, doubled.value);
remonremon

watchやwatchEffectはreactive packageの外にあった。

内部でReactiveEffectを作成している。