Vue3 实现
Published on December 8, 2023Updated on March 23, 2024
Loading content...
编译时
运行时
JavaScriptimport vue from "rollup-plugin-vue"; import styles from "rollup-plugin-styles"; export default { plugins: [ vue({ preprocessStyles: true, }), styles( { mode: "extract", // ... or with relative to output dir/output file's basedir (but not outside of it) mode: ["extract", "awesome-bundle.css"], } ), ], input: "index.vue", output: { format: "esm", file: "dist/index.js", }, };
JavaScriptit("Proxy & Reflect ", () => { const target = { message1: "hello", message2: "everyone", get message3() { return this.message1 + this.message2; }, set xx(x) { this.message1 = x; }, }; const handler = { get(target, key, receiver) { console.log("read", key, target === receiver); // Reflect 处理调用对象的基本方法 return Reflect.get(target, key, receiver); }, set(target, property, value, receiver) { return Reflect.set(target, property, value, receiver); }, }; const proxy = new Proxy(target, handler); proxy.message3; proxy.message1 = "2"; expect(target.message1).toBe("2"); });
https://vitest.dev/guide/debugging
CODE{ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "node", "request": "launch", "name": "Debug Current Test File", "autoAttachChildProcesses": true, "skipFiles": ["<node_internals>/**", "**/node_modules/**"], "program": "${workspaceRoot}/node_modules/vitest/vitest.mjs", "args": ["run", "${relativeFile}"], "smartStep": true, "console": "integratedTerminal" } ] }
JavaScriptit("should observe basic properties", () => { let dummy; // init start const counter = reactive({ num: 0 }); effect(() => (dummy = counter.num)); // init end expect(dummy).toBe(0); counter.num = 7; expect(dummy).toBe(7); });
初始化
effect(() => (dummy = counter.num));
-> const _effect = new ReactiveEffect(fn);
-> _effect.run();
-> activeEffect = this as any; // # 很重要!!
-> const result = this.fn();
-> () => (dummy = counter.num)
-> counter.num
-> return function get(target, key, receiver) {
-> track(target, "get", key);
-> trackEffects(dep);
-> dep.add(activeEffect);
(activeEffect as any).deps.push(dep);
初始化的目的是形成 targetMap 全局的 WeekMap
CODEMap<Target extends object, Map<string | symbol, Set<ReactiveEffect>>> { [{num: 0}]: { "num": [[new ReactiveEffect(() => (dummy = counter.num))]] } }
TypeScriptexport function effect(fn, options = {}) { const _effect = new ReactiveEffect(fn); // 把用户传过来的值合并到 _effect 对象上去 // 缺点就是不是显式的,看代码的时候并不知道有什么值 extend(_effect, options); _effect.run(); // 把 _effect.run 这个方法返回 // 让用户可以自行选择调用的时机(调用 fn) const runner: any = _effect.run.bind(_effect); runner.effect = _effect; return runner; } export class ReactiveEffect { active = true; deps = []; public onStop?: () => void; constructor(public fn, public scheduler?) { console.log("创建 ReactiveEffect 对象"); } run() { console.log("run"); // 运行 run 的时候,可以控制 要不要执行后续收集依赖的一步 // 目前来看的话,只要执行了 fn 那么就默认执行了收集依赖 // 这里就需要控制了 // 是不是收集依赖的变量 // 执行 fn 但是不收集依赖 if (!this.active) { return this.fn(); } // 执行 fn 收集依赖 // 可以开始收集依赖了 shouldTrack = true; // 执行的时候给全局的 activeEffect 赋值 // 利用全局属性来获取当前的 effect activeEffect = this as any; // # 很重要!! // 执行用户传入的 fn console.log("执行用户传入的 fn"); const result = this.fn();
触发变化
counter.num = 7;
-> return function set(target, key, value, receiver) {
-> trigger(target, "set", key);
-> triggerEffects(createDep(effects));
-> effect.run();
-> const result = this.fn();