vue3源码解析:2.1 vue3响应式原理-4个Api(reactivity模块)

330 阅读2分钟

reactive、shallowReactive、readonly、shallowReadonly 区别只是在是否仅读、是否深度 --> 柯里化

reactivity 模块的使用

<script src="../node_modules/@vue/reactivity/dist/reactivity.global.js"></script>
<script>
  const { reactive, shallowReactive, readonly, shallowReadonly } =
    VueReactivity;
  const state = reactive({ name: "cherish", age: { age: 10 } });
  state.age = 12;
  console.log(state.age);
</script>

实现 4 个核心 Api

打开 sourceMap

tsconfig.json

{
  "compilerOptions": {
    "target": "ESNEXT",
    "module": "ESNEXT",
    "strict": false,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node",
    "baseUrl": ".",
    "paths": {
      "@vue/*": ["packages/*/src"]
    },
+    "sourceMap": true
  }
}

目录结构

├── example
│   └── 1.reactivity-api.html
├── package.json
├── packages
│   ├── reactivity
│   │   ├── package.json
│   │   └── src
│   │       ├── baseHandlers.ts
│   │       ├── index.ts
│   │       └── reactive.ts
│   ├── runtime-core
│   ├── runtime-dom
│   └── shared
│       ├── package.json
│       └── src
│           └── index.ts
├── rollup.config.js
├── scripts
│   ├── build.js
│   └── dev.js
└── tsconfig.json

入口引入

packages/reactivity/src/index.js

export {
  reactive,
  shallowReactive,
  shallowReadonly,
  readonly,
} from "./reactive";

柯里化拆分

packages/reactivity/src/reactive.js

抽取公共的 createReactiveObject 方法

  1. reactive 只拦截对象类型(ref 可以是普通类型)。
  2. 已被代理过的对象不再代理(可能一个对象被代理是深度,又被仅读代理了)。
  3. 做一个映射表,看对象有没有把被代理过。WeakMap 会自动垃圾回收,不会造成内存泄漏,存储的 key 只能是对象。
import { isObject } from "@vue/shared";
import {
  mutableHandlers,
  shallowReactiveHandlers,
  readonlyHandlers,
  shallowReadonlyHandlers,
} from "./baseHandlers";
export function reactive(target) {
  return createReactiveObject(target, false, mutableHandlers);
}
export function shallowReactive(target) {
  return createReactiveObject(target, false, shallowReactiveHandlers);
}

export function readonly(target) {
  return createReactiveObject(target, true, readonlyHandlers);
}

export function shallowReadonly(target) {
  return createReactiveObject(target, true, shallowReadonlyHandlers);
}

const reactiveMap = new WeakMap();
const readonlyMap = new WeakMap();
export function createReactiveObject(target, isReadonly, baseHandlers) {
  if (!isObject(target)) return target;
  const proxyMap = isReadonly ? readonlyMap : reactiveMap;
  const existProxy = proxyMap.get(target);
  if (existProxy) return existProxy;
  const proxy = new Proxy(target, baseHandlers);
  proxyMap.set(target, proxy); // 将要代理的对象和对应代理结果缓存起来

  return proxy;
}

baseHandlers.ts

import { isObject } from "@vue/shared/src";

import { reactive, readonly } from "./reactive";
debugger;
const createGetter =
  (isReadonly = false, shallow = false) =>
  (target, key, receiver) => {
    const res = Reflect.get(target, key, receiver);
    if (!isReadonly) {
      // 收集依赖,数据变化后更新视图
      console.log("执行effect时会取值", "收集effect");
    }
    if (shallow) return res;
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res);
    }
    return res;
  };
const createSetter =
  (shallow = false) =>
  (target, key, value, receiver) => {
    const result = Reflect.set(target, key, value, receiver);
    return result;
  };

const get = createGetter();
const shallowGet = createGetter(false, true);
const readonlyGet = createGetter(true);
const showllowReadonlyGet = createGetter(true, true);
const set = createSetter();
const shallowSet = createSetter(true);
const readonlySet = {
  set: (_, key) => console.warn(`set on key ${key} falied`),
};
export const mutableHandlers = { get, set };
export const shallowReactiveHandlers = { get: shallowGet, set: shallowSet };
export const readonlyHandlers = { get: readonlyGet, set: readonlySet };
export const shallowReadonlyHandlers = {
  get: showllowReadonlyGet,
  set: readonlySet,
};

reflect 的好处

  • 后续 Object 上的方法会被迁移到 Reflect, 如Reflect.getPropertyOf()
  • 以前 target[key] = value 方式设置值可能会失败,并不会报异常,也没有返回值标识,Reflect 方法有返回值告诉用户是否设置成功。 ps. 一般proxy会配合Reflect,而使用 Reflect 可以不使用 proxy

vue2 & vue3 比较

  • vue2: 一上来就递归
  • vue3: 取值时才会代理--懒代理

设计思想

柯里化,组合优于继承