├── .gitignore
├── .vscode
├── settings.json
└── launch.json
├── src
├── index.ts
├── compiler-core
│ ├── src
│ │ ├── ast.ts
│ │ ├── runtimeHelpers.ts
│ │ ├── transforms
│ │ │ └── transformExpression.ts
│ │ ├── transform.ts
│ │ ├── codegen.ts
│ │ └── parse.ts
│ └── tests
│ │ ├── __snapshots__
│ │ └── codegen.spec.ts.snap
│ │ ├── transform.spec.ts
│ │ ├── codegen.spec.ts
│ │ └── parse.spec.ts
├── runtime-core
│ ├── componentProps.ts
│ ├── h.ts
│ ├── componentUpdateUtils.ts
│ ├── componentEmit.ts
│ ├── index.ts
│ ├── helpers
│ │ └── renderSlots.ts
│ ├── scheduler.ts
│ ├── componentPublicInstance.ts
│ ├── componentSlots.ts
│ ├── createApp.ts
│ ├── apiInject.ts
│ ├── vnode.ts
│ ├── components.ts
│ └── renderer.ts
├── reactive
│ ├── effectScope.ts
│ ├── index.ts
│ ├── computed.ts
│ ├── test
│ │ ├── effectScope.spec.ts
│ │ ├── computed.spec.ts
│ │ ├── watch.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── effect.spec.ts
│ │ └── ref.spec.ts
│ ├── watch.ts
│ ├── reactive.ts
│ ├── baseHandlers.ts
│ ├── ref.ts
│ └── effect.ts
├── shared
│ ├── ShapeFlags.ts
│ └── index.ts
└── runtime-dom
│ └── index.ts
├── babel.config.js
├── example
├── update_component
│ ├── Child.js
│ └── App.js
├── app.component
│ └── App.js
├── slots
│ ├── Foo.js
│ └── App.js
├── getCurrentInstance
│ ├── Foo.js
│ └── App.js
├── index.html
├── emit
│ ├── App.js
│ └── Foo.js
├── nextTick
│ └── App.js
├── main.js
├── provide_inject
│ └── App.js
├── update_props
│ └── App.js
└── update_children
│ ├── App.js
│ └── Diff.js
├── rollup.config.js
├── filetoc.config.js
├── package.json
├── README.md
├── tsconfig.json
└── lib
├── mini-vue.esm.js
└── mini-vue.cjs.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "liveServer.settings.port": 5502
3 | }
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./runtime-dom/index";
2 | export * from "./reactive/index";
3 |
--------------------------------------------------------------------------------
/src/compiler-core/src/ast.ts:
--------------------------------------------------------------------------------
1 | export const enum NODE_TYPES {
2 | INTERPOLATION,
3 | SIMPLE_EXPRESSION,
4 | ELEMENT,
5 | TEXT,
6 | ROOT
7 | }
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance, rawProps) {
2 | // 将props挂载到实例上
3 | instance.props = rawProps;
4 |
5 | // attrs
6 | }
7 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export function h(type, props?, children?) {
4 | return createVNode(type, props, children);
5 | }
6 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ["@babel/preset-env", { targets: { node: "current" } }],
4 | "@babel/preset-typescript",
5 | ],
6 | };
7 |
8 |
--------------------------------------------------------------------------------
/src/compiler-core/src/runtimeHelpers.ts:
--------------------------------------------------------------------------------
1 | export const TO_DISPLAY_STRING = Symbol("toDisplayString");
2 |
3 | export const helperMapName = {
4 | [TO_DISPLAY_STRING]: "toDisplayString",
5 | };
6 |
--------------------------------------------------------------------------------
/src/reactive/effectScope.ts:
--------------------------------------------------------------------------------
1 | export class EffectScope {
2 | effects: any[] = []
3 | run(fn) {
4 | const res = fn()
5 | return res
6 | }
7 | }
8 |
9 | export function effectScope() {}
10 |
--------------------------------------------------------------------------------
/src/reactive/index.ts:
--------------------------------------------------------------------------------
1 | export { reactive, readonly, shallowReadonly } from "./reactive";
2 | export { computed } from "./computed";
3 | export { ref, proxyRefs } from "./ref";
4 | export { effect } from "./effect";
5 |
--------------------------------------------------------------------------------
/src/shared/ShapeFlags.ts:
--------------------------------------------------------------------------------
1 | export const enum ShapeFlags {
2 | ELEMENT = 1, // 0001
3 | STATEFUL_COMPONENT = 1 << 1, // 0010
4 | TEXT_CHILDREN = 1 << 2, // 0100
5 | ARRAY_CHILDREN = 1 << 3, // 1000
6 | SLOT_CHILDREN = 1 << 4,
7 | }
8 |
--------------------------------------------------------------------------------
/example/update_component/Child.js:
--------------------------------------------------------------------------------
1 | import { h, ref } from "../../../lib/mini-vue.esm.js";
2 |
3 | export const Child = {
4 | name: "Child",
5 | setup(props) {},
6 | render() {
7 | return h("div", {}, this.$props.msg);
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": []
7 | }
--------------------------------------------------------------------------------
/example/app.component/App.js:
--------------------------------------------------------------------------------
1 | import { h, resolveComponent } from "../../../lib/mini-vue.esm.js";
2 |
3 | export default {
4 | setup() {
5 | return {};
6 | },
7 | render() {
8 | return h("div", {}, [h(resolveComponent("my-component")), h("div", {}, "hahaha")]);
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/src/runtime-core/componentUpdateUtils.ts:
--------------------------------------------------------------------------------
1 | export function shouldUpdateComponent(n1, n2) {
2 | const { props: oldProps } = n1;
3 | const { props: newProps } = n2;
4 | for (const key in newProps) {
5 | if (oldProps[key] !== newProps[key]) {
6 | return true;
7 | }
8 | }
9 | return false;
10 | }
11 |
--------------------------------------------------------------------------------
/src/runtime-core/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { camelize, handleEventName } from "../shared/index";
2 |
3 | export function emit(instance, event, ...args) {
4 | const { props } = instance;
5 |
6 | const handlerName = camelize(handleEventName(event));
7 | const handler = props[handlerName];
8 |
9 | handler && handler(...args);
10 | }
11 |
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 | export { h } from "./h";
2 | export { renderSlots } from "./helpers/renderSlots";
3 | export { createTextVNode } from "./vnode";
4 | export { getCurrentInstance } from "./components";
5 | export { provide, inject } from "./apiInject";
6 | export { nextTick } from "./scheduler";
7 | export { createRenderer } from "./renderer";
8 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import typescript from "@rollup/plugin-typescript";
2 |
3 | export default {
4 | input: "./src/index.ts",
5 | output: [
6 | {
7 | format: "cjs",
8 | file: "lib/mini-vue.cjs.js",
9 | },
10 | {
11 | format: "es",
12 | file: "lib/mini-vue.esm.js",
13 | },
14 | ],
15 | plugins: [typescript()],
16 | };
17 |
--------------------------------------------------------------------------------
/example/slots/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from "../../lib/mini-vue.esm.js";
2 |
3 | export default {
4 | setup(props) {},
5 | render() {
6 | const foo = h("p", {}, "Foo");
7 | const age = 18;
8 | return h("div", {}, [
9 | renderSlots(this.$slots, age),
10 | foo,
11 | renderSlots(this.$slots, age, "footer"),
12 | ]);
13 | },
14 | };
15 |
--------------------------------------------------------------------------------
/example/getCurrentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import {
2 | h,
3 | renderSlots,
4 | getCurrentInstance,
5 | } from "../../../lib/mini-vue.esm.js";
6 |
7 | export default {
8 | name: "Foo",
9 | setup() {
10 | const instance = getCurrentInstance();
11 | console.log(instance);
12 | },
13 | render() {
14 | return h("div", {}, [renderSlots(this.$slots)]);
15 | },
16 | };
17 |
--------------------------------------------------------------------------------
/src/compiler-core/src/transforms/transformExpression.ts:
--------------------------------------------------------------------------------
1 | import { NODE_TYPES } from "../ast";
2 |
3 | export function transformExpression(node) {
4 | if (node.type === NODE_TYPES.INTERPOLATION) {
5 | node.content = processExpression(node.content)
6 | }
7 | }
8 |
9 | function processExpression(node) {
10 | node.content = '_ctx.' + node.content
11 | return node
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/src/compiler-core/tests/__snapshots__/codegen.spec.ts.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`codegen interpolation 1`] = `
4 | "const { toDisplayString: _toDisplayString } = Vue
5 | return function render(_ctx, _cache) {return _toDisplayString(_ctx.message)}"
6 | `;
7 |
8 | exports[`codegen string 1`] = `
9 | "
10 | return function render(_ctx, _cache) {return \\"hi\\"}"
11 | `;
12 |
--------------------------------------------------------------------------------
/filetoc.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | remoteUrl: "https://github.com/chenfan0/mini-vue3", // 仓库地址
3 | mainBranch: "main", // 主分支
4 | dirPath: "./src/", // 要生成toc的目录路径
5 | mdPath: "./README.md", // 生成的toc添加到的md文件路径
6 | excludes: ["example"],
7 | };
8 |
9 | // 212 root.helpers.splice(root.helpers.indexOf(name), 1)
10 | // 200
11 | // if (count === 0) {
12 | // context.root.helpers.push(name)
13 | // }
14 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ")).toThrow('div标签没有结束标签');
66 | });
67 |
68 | test("nested element", () => {
69 | const ast = baseParse("
");
70 |
71 | expect(ast.children[0]).toStrictEqual({
72 | type: NODE_TYPES.ELEMENT,
73 | tag: "div",
74 | children: [
75 | {
76 | type: NODE_TYPES.ELEMENT,
77 | tag: "p",
78 | children: [
79 | {
80 | type: NODE_TYPES.TEXT,
81 | content: "hi,",
82 | },
83 | ],
84 | },
85 | {
86 | type: NODE_TYPES.INTERPOLATION,
87 | content: {
88 | type: NODE_TYPES.SIMPLE_EXPRESSION,
89 | content: "message",
90 | },
91 | },
92 | ],
93 | });
94 | });
95 | });
96 |
--------------------------------------------------------------------------------
/src/reactive/test/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive } from "../reactive";
2 | import { effect, stop } from "../effect";
3 |
4 | describe("effect", () => {
5 | it("happy path", () => {
6 | const obj = reactive({
7 | age: 18,
8 | });
9 | let nextAge;
10 | effect(() => {
11 | nextAge = obj.age + 1;
12 | });
13 |
14 | expect(nextAge).toBe(19);
15 |
16 | obj.age++;
17 |
18 | expect(nextAge).toBe(20);
19 | });
20 |
21 | it("runner", () => {
22 | // effect函数会返回一个runner函数,该函数实际上就是传入的fn函数
23 | let foo = 10;
24 | const runner: any = effect(() => {
25 | foo++;
26 | return "foo";
27 | });
28 | expect(foo).toBe(11);
29 |
30 | const r = runner();
31 |
32 | expect(r).toBe("foo");
33 | expect(foo).toBe(12);
34 | });
35 |
36 | it("sheduler", () => {
37 | // 当有传递scheduler参数时,第一次执行还是执行effct的第一个参数fn
38 | // 当数据发生变化时,执行的是scheduler函数
39 | let dummy;
40 | let run: any;
41 | const scheduler = jest.fn(() => {
42 | run = runner;
43 | });
44 | const obj = reactive({ foo: 1 });
45 | const runner = effect(
46 | () => {
47 | dummy = obj.foo;
48 | },
49 | {
50 | scheduler,
51 | }
52 | );
53 | // 一开始scheduler不会被调用
54 | expect(scheduler).not.toHaveBeenCalled();
55 | // effect函数的第一个参数fn会被调用
56 | expect(dummy).toBe(1);
57 |
58 | // 当obj发送变化时
59 | obj.foo++;
60 | // 调用scheduler
61 | expect(scheduler).toHaveBeenCalledTimes(1);
62 | // 不会执行effect的第一个参数
63 | expect(dummy).toBe(1);
64 |
65 | run();
66 |
67 | expect(dummy).toBe(2);
68 | });
69 |
70 | it("stop", () => {
71 | let dummy;
72 | const obj = reactive({ prop: 1 });
73 | const runner = effect(() => {
74 | dummy = obj.prop;
75 | });
76 | obj.prop = 2;
77 | expect(dummy).toBe(2);
78 | stop(runner);
79 | // obj.prop = 3;
80 | obj.prop++;
81 | expect(dummy).toBe(2);
82 | runner();
83 | expect(dummy).toBe(3);
84 | });
85 |
86 | it("onStop", () => {
87 | const obj = reactive({
88 | foo: 1,
89 | });
90 | const onStop = jest.fn();
91 | let dummy;
92 | const runner = effect(
93 | () => {
94 | dummy = obj.foo;
95 | },
96 | {
97 | onStop,
98 | }
99 | );
100 | // 调用stop后,onStop会被执行
101 | stop(runner);
102 | expect(onStop).toBeCalledTimes(1);
103 | });
104 |
105 |
106 | it("nested effect", () => {
107 | let dummy;
108 | let dummy1;
109 | let obj = reactive({
110 | count: 1,
111 | });
112 |
113 | // effect1 effect2
114 | // 执行effect1过程又会
115 | effect(() => {
116 | console.log(111);
117 | effect(() => {
118 | console.log(222);
119 |
120 | dummy = obj.count;
121 | });
122 | dummy1 = obj.count;
123 | });
124 |
125 | expect(dummy).toBe(1);
126 | expect(dummy1).toBe(1);
127 | obj.count = 2;
128 | expect(dummy).toBe(2);
129 | expect(dummy1).toBe(2);
130 | });
131 | });
132 |
--------------------------------------------------------------------------------
/src/reactive/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "../shared/index";
2 |
3 | // 这里使用一个变量保存活跃的effect,
4 | // 但是如果存在effect嵌套的情况,就会有问题,需要把这个变成一个栈。
5 | let activeEffect: ReactiveEffect | undefined;
6 | const effectStack: ReactiveEffect[] = [];
7 | let shouldTrack;
8 | const targetMap = new WeakMap();
9 |
10 | const trackStack: boolean[] = [];
11 |
12 | export function pauseTracking() {
13 | trackStack.push(shouldTrack);
14 | shouldTrack = false;
15 | }
16 |
17 | export function enableTracking() {
18 | trackStack.push(shouldTrack);
19 | shouldTrack = true;
20 | }
21 |
22 | export function resetTracking() {
23 | const last = trackStack.pop();
24 | shouldTrack = last === undefined ? true : last;
25 | }
26 |
27 | export class ReactiveEffect {
28 | // effect传递的第一个参数
29 | private _fn: (...args) => void;
30 | // effect传递的第二个对象的scheduler属性。如果有传该参数,则trigger是会触发该函数
31 | // 调用stop函数会执行该函数
32 | onStop?: () => void;
33 | // 该变量用来记录是否调过stop函数
34 | active = true;
35 | // 收集该effect的dep
36 | deps: any[] = [];
37 |
38 | constructor(fn, public scheduler?) {
39 | this._fn = fn;
40 | this.scheduler = scheduler;
41 | }
42 |
43 | run(...args) {
44 | if (!this.active) {
45 | return this._fn(...args);
46 | }
47 | if (!effectStack.includes(this)) {
48 | effectStack.push((activeEffect = this));
49 |
50 | //
51 | enableTracking()
52 |
53 | const res = this._fn(...args);
54 | resetTracking()
55 | effectStack.pop();
56 | const n = effectStack.length;
57 | activeEffect = n > 0 ? effectStack[n - 1] : undefined;
58 | return res;
59 | }
60 | }
61 |
62 | stop() {
63 | if (this.active) {
64 | cleanupEffect(this);
65 | this.onStop && this.onStop();
66 | this.active = false;
67 | }
68 | }
69 | }
70 |
71 | function cleanupEffect(effect: ReactiveEffect) {
72 | effect.deps.forEach((dep) => {
73 | dep.delete(effect);
74 | });
75 | // 清空effect.deps
76 | effect.deps.length = 0;
77 | }
78 |
79 | export function track(target, key) {
80 | if (!isTracking()) return;
81 |
82 | let depsMap = targetMap.get(target);
83 | if (!depsMap) {
84 | depsMap = new Map();
85 | targetMap.set(target, depsMap);
86 | }
87 | let dep = depsMap.get(key);
88 | if (!dep) {
89 | dep = new Set();
90 | depsMap.set(key, dep);
91 | }
92 | trackEffects(dep);
93 | }
94 |
95 | export function trackEffects(dep) {
96 | dep.add(activeEffect);
97 |
98 | activeEffect!.deps.push(dep);
99 | }
100 |
101 | export function isTracking() {
102 | return shouldTrack && activeEffect !== undefined;
103 | }
104 |
105 | export function trigger(target, key) {
106 | const depsMap = targetMap.get(target);
107 | if (!depsMap) return;
108 | const dep = depsMap.get(key);
109 |
110 | triggerEffects(dep);
111 | }
112 |
113 | export function triggerEffects(dep) {
114 | if (!dep) return;
115 |
116 | for (const effect of dep) {
117 | if (effect.scheduler) {
118 | effect.scheduler();
119 | } else {
120 | effect.run();
121 | }
122 | }
123 | }
124 |
125 | export function effect(fn, options: any = {}) {
126 | const _effect = new ReactiveEffect(fn, options.scheduler);
127 |
128 | extend(_effect, options);
129 | _effect.run();
130 |
131 | const runner: any = _effect.run.bind(_effect);
132 | runner.effect = _effect;
133 |
134 | return runner;
135 | }
136 |
137 | export function stop(runner) {
138 | runner.effect.stop();
139 | }
140 |
--------------------------------------------------------------------------------
/src/compiler-core/src/parse.ts:
--------------------------------------------------------------------------------
1 | import { NODE_TYPES } from "./ast";
2 |
3 | const enum TAG_TYPE {
4 | START,
5 | END,
6 | }
7 |
8 | export function baseParse(content: string) {
9 | const context = createParseContext(content);
10 |
11 | return createRoot(createParseChildren(context, []));
12 | }
13 |
14 | function createParseContext(content: string) {
15 | return {
16 | source: content,
17 | };
18 | }
19 |
20 | function createRoot(children) {
21 | return {
22 | helpers: [],
23 | children,
24 | type: NODE_TYPES.ROOT
25 | };
26 | }
27 |
28 | function createParseChildren(context, ancestors) {
29 | const nodes: any[] = [];
30 | while (!isEnd(context, ancestors)) {
31 | let node;
32 | const s = context.source;
33 | if (s.startsWith("{{")) {
34 | node = parseInterpolation(context);
35 | } else if (s[0] === "<") {
36 | if (/[a-z]/i.test(s[1])) {
37 | node = parseElement(context, ancestors);
38 | }
39 | }
40 |
41 | if (!node) {
42 | node = parseText(context);
43 | }
44 |
45 | nodes.push(node);
46 | }
47 |
48 | return nodes;
49 | }
50 |
51 | function isEnd(context, ancestors) {
52 | const s: string = context.source;
53 | if (s.startsWith("")) {
54 | for (let i = ancestors.length - 1; i >= 0; i--) {
55 | const tag = ancestors[i];
56 | if (startsWithEndOpen(s, tag)) {
57 | return true;
58 | }
59 | }
60 | }
61 | return !s;
62 | }
63 |
64 | function parseInterpolation(context) {
65 | const openDelimiter = "{{";
66 | const closeDelimiter = "}}";
67 | const closeIndex = context.source.indexOf(
68 | closeDelimiter,
69 | openDelimiter.length
70 | );
71 | // {{message}} -> message}}
72 | advanceBy(context, openDelimiter.length);
73 | const rawContentLength = closeIndex - openDelimiter.length;
74 | const rawContent = parseTextData(context, rawContentLength);
75 | advanceBy(context, closeDelimiter.length);
76 | const content = rawContent.trim();
77 |
78 | return {
79 | type: NODE_TYPES.INTERPOLATION,
80 | content: {
81 | type: NODE_TYPES.SIMPLE_EXPRESSION,
82 | content,
83 | },
84 | };
85 | }
86 |
87 | function parseElement(context, ancestors: string[]) {
88 | const element: any = parseTag(context, TAG_TYPE.START);
89 | ancestors.push(element.tag);
90 | element.children = createParseChildren(context, ancestors);
91 | ancestors.pop();
92 |
93 | if (startsWithEndOpen(context.source, element.tag)) {
94 | parseTag(context, TAG_TYPE.END);
95 | } else {
96 | throw new Error(`${element.tag}标签没有结束标签`);
97 | }
98 |
99 | return element;
100 | }
101 |
102 | function startsWithEndOpen(source: string, tag: string) {
103 | return (
104 | source.startsWith("") &&
105 | source.slice(2, tag.length + 2).toLowerCase() === tag.toLowerCase()
106 | );
107 | }
108 |
109 | function parseTag(context, type: TAG_TYPE) {
110 | const match: any = /^<\/?([a-z]*)/i.exec(context.source);
111 | const tag = match[1];
112 |
113 | advanceBy(context, match[0].length + 1);
114 | if (type === TAG_TYPE.END) return;
115 | return {
116 | type: NODE_TYPES.ELEMENT,
117 | tag,
118 | };
119 | }
120 |
121 | function parseText(context) {
122 | const endTokens = ["{{", "<"];
123 | let endIndex = context.source.length;
124 | for (let i = 0; i < endTokens.length; i++) {
125 | const index = context.source.indexOf(endTokens[i]);
126 | if (index > -1 && endIndex > index) {
127 | endIndex = index;
128 | }
129 | }
130 |
131 | const content = parseTextData(context, endIndex);
132 |
133 | return {
134 | type: NODE_TYPES.TEXT,
135 | content,
136 | };
137 | }
138 |
139 | function parseTextData(context, length: number) {
140 | const content = context.source.slice(0, length);
141 |
142 | advanceBy(context, length);
143 |
144 | return content;
145 | }
146 |
147 | function advanceBy(context, length: number) {
148 | context.source = context.source.slice(length);
149 | }
150 |
--------------------------------------------------------------------------------
/src/reactive/test/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../effect";
2 | import { reactive } from "../reactive";
3 | import { isRef, ref, unRef, proxyRefs, toRef, toRefs } from "../ref";
4 |
5 | describe("ref", () => {
6 | it("happy path", () => {
7 | const a = ref(1);
8 | expect(a.value).toBe(1);
9 | });
10 |
11 | it("should be reactive", () => {
12 | const a = ref(1);
13 | let dummy;
14 | let calls = 0;
15 | effect(() => {
16 | calls++;
17 | dummy = a.value;
18 | });
19 | expect(calls).toBe(1);
20 | expect(dummy).toBe(1);
21 | a.value = 2;
22 | expect(calls).toBe(2);
23 | expect(dummy).toBe(2);
24 | // 相同value不会触发trigger
25 | a.value = 2;
26 | expect(calls).toBe(2);
27 | expect(dummy).toBe(2);
28 | });
29 |
30 | it("should make nested properties reactive", () => {
31 | const a = ref({
32 | count: 1,
33 | });
34 | let dummy;
35 | effect(() => {
36 | dummy = a.value.count;
37 | });
38 | expect(dummy).toBe(1);
39 | a.value.count = 2;
40 | expect(dummy).toBe(2);
41 | });
42 |
43 | it("isRef", () => {
44 | const a = ref(1);
45 | expect(isRef(a)).toBe(true);
46 | expect(isRef(1)).toBe(false);
47 | });
48 |
49 | it("unRef", () => {
50 | const a = ref(1);
51 | expect(unRef(a)).toBe(1);
52 | expect(unRef(1)).toBe(1);
53 | });
54 |
55 | it("proxyRefs", () => {
56 | const user = {
57 | age: ref(10),
58 | name: "aaa",
59 | };
60 |
61 | const proxyUser = proxyRefs(user);
62 | expect(user.age.value).toBe(10);
63 | expect(proxyUser.age).toBe(10);
64 | expect(proxyUser.name).toBe("aaa");
65 |
66 | proxyUser.age = 20;
67 | // proxyUser.age改变,user.age.value和proxy.age都需要改变
68 | expect(proxyUser.age).toBe(20);
69 | expect(user.age.value).toBe(20);
70 |
71 | proxyUser.age = ref(30);
72 | expect(proxyUser.age).toBe(30);
73 | expect(user.age.value).toBe(30);
74 | });
75 |
76 | test("toRef", () => {
77 | const a = reactive({
78 | x: 1,
79 | });
80 | const x: any = toRef(a, "x");
81 | expect(isRef(x)).toBe(true);
82 | expect(x.value).toBe(1);
83 |
84 | // source -> proxy
85 | a.x = 2;
86 | expect(x.value).toBe(2);
87 |
88 | // proxy -> source
89 | x.value = 3;
90 | expect(a.x).toBe(3);
91 |
92 | // reactivity
93 | let dummyX;
94 | effect(() => {
95 | dummyX = x.value;
96 | });
97 | expect(dummyX).toBe(x.value);
98 |
99 | // mutating source should trigger effect using the proxy refs
100 | a.x = 4;
101 | expect(dummyX).toBe(4);
102 |
103 | // should keep ref
104 | const r = { x: ref(1) };
105 | expect(toRef(r, "x")).toBe(r.x);
106 | });
107 |
108 | test("toRefs", () => {
109 | const a = reactive({
110 | x: 1,
111 | y: 2,
112 | });
113 |
114 | const { x, y }: any = toRefs(a);
115 |
116 | expect(isRef(x)).toBe(true);
117 | expect(isRef(y)).toBe(true);
118 | expect(x.value).toBe(1);
119 | expect(y.value).toBe(2);
120 |
121 | // source -> proxy
122 | a.x = 2;
123 | a.y = 3;
124 | expect(x.value).toBe(2);
125 | expect(y.value).toBe(3);
126 |
127 | // proxy -> source
128 | x.value = 3;
129 | y.value = 4;
130 | expect(a.x).toBe(3);
131 | expect(a.y).toBe(4);
132 |
133 | // reactivity
134 | let dummyX, dummyY;
135 | effect(() => {
136 | dummyX = x.value;
137 | dummyY = y.value;
138 | });
139 | expect(dummyX).toBe(x.value);
140 | expect(dummyY).toBe(y.value);
141 |
142 | // mutating source should trigger effect using the proxy refs
143 | a.x = 4;
144 | a.y = 5;
145 | expect(dummyX).toBe(4);
146 | expect(dummyY).toBe(5);
147 | });
148 |
149 | test('toRefs should warn on plain object', () => {
150 | toRefs({})
151 | })
152 |
153 |
154 | test('toRefs reactive array', () => {
155 | const arr = reactive(['a', 'b', 'c'])
156 | const refs = toRefs(arr)
157 |
158 | expect(Array.isArray(refs)).toBe(true)
159 |
160 | refs[0].value = '1'
161 | expect(arr[0]).toBe('1')
162 |
163 | arr[1] = '2'
164 | expect(refs[1].value).toBe('2')
165 | })
166 | });
167 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # mini-vue3
2 | - 实现 Vue3 核心逻辑的最简模型,本项目参考 https://github.com/cuixiaorui/mini-vue 实现。
3 | - 与该仓库有关的文章可以访问: https://juejin.cn/column/7065487683673391118
4 |
5 | - [reactive](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive)
6 | - [test](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/test)
7 | - [computed.spec.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/test/computed.spec.ts)
8 | - [effect.spec.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/test/effect.spec.ts)
9 | - [effectScope.spec.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/test/effectScope.spec.ts)
10 | - [reactive.spec.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/test/reactive.spec.ts)
11 | - [readonly.spec.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/test/readonly.spec.ts)
12 | - [ref.spec.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/test/ref.spec.ts)
13 | - [watch.spec.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/test/watch.spec.ts)
14 | - [baseHandlers.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/baseHandlers.ts)
15 | - [computed.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/computed.ts)
16 | - [effect.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/effect.ts)
17 | - [effectScope.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/effectScope.ts)
18 | - [index.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/index.ts)
19 | - [reactive.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/reactive.ts)
20 | - [ref.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/ref.ts)
21 | - [watch.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/reactive/watch.ts)
22 | - [runtime-core](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core)
23 | - [helpers](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/helpers)
24 | - [renderSlots.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/helpers/renderSlots.ts)
25 | - [apiInject.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/apiInject.ts)
26 | - [componentEmit.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/componentEmit.ts)
27 | - [componentProps.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/componentProps.ts)
28 | - [componentPublicInstance.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/componentPublicInstance.ts)
29 | - [components.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/components.ts)
30 | - [componentSlots.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/componentSlots.ts)
31 | - [componentUpdateUtils.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/componentUpdateUtils.ts)
32 | - [createApp.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/createApp.ts)
33 | - [h.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/h.ts)
34 | - [index.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/index.ts)
35 | - [renderer.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/renderer.ts)
36 | - [scheduler.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/scheduler.ts)
37 | - [vnode.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-core/vnode.ts)
38 | - [runtime-dom](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-dom)
39 | - [index.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/runtime-dom/index.ts)
40 | - [shared](https://github.com/chenfan0/mini-vue3/tree/main/src/shared)
41 | - [index.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/shared/index.ts)
42 | - [ShapeFlags.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/shared/ShapeFlags.ts)
43 | - [index.ts](https://github.com/chenfan0/mini-vue3/tree/main/src/index.ts)
44 |
45 | ## 目前实现功能
46 | ### reactivity
47 | - [x] reactive
48 | - [x] shallowReactive
49 | - [x] readonly
50 | - [x] shallowReadonly
51 | - [x] isReactive
52 | - [x] isReadonly
53 | - [x] isProxy
54 | - [x] ref
55 | - [x] isRef
56 | - [x] unRef
57 | - [X] toRaw
58 | - [x] proxyRefs
59 | - [x] computed
60 | - [x] effect
61 | - [x] watch
62 | ### runtime-core
63 | - [x] 初始化Component主流程
64 | - [x] 初始化Element主流程
65 | - [x] shapeFlags
66 | - [x] 组件代理对象
67 | - [x] 注册事件
68 | - [x] 组件props
69 | - [x] 组件emit
70 | - [x] 组件slots
71 | - [x] getCurrentInstance
72 | - [x] provide/inject
73 | - [x] 更新element的基本流程
74 | - [x] 更新Component的基本流程
75 | - [x] nextTick
76 | ### runtime-dom
77 | - [x] custom renderer
78 | ### compiler
79 | 暂未实现
80 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Basic Options */
6 | // "incremental": true, /* Enable incremental compilation */
7 | "target": "es2016", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
8 | "module": "esnext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
9 | "lib": ["DOM", "es6", "ES2016"], /* Specify library files to be included in the compilation. */
10 | // "allowJs": true, /* Allow javascript files to be compiled. */
11 | // "checkJs": true, /* Report errors in .js files. */
12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */
13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
15 | // "sourceMap": true, /* Generates corresponding '.map' file. */
16 | // "outFile": "./", /* Concatenate and emit output to single file. */
17 | // "outDir": "./", /* Redirect output structure to the directory. */
18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
19 | // "composite": true, /* Enable project compilation */
20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
21 | // "removeComments": true, /* Do not emit comments to output. */
22 | // "noEmit": true, /* Do not emit outputs. */
23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
26 |
27 | /* Strict Type-Checking Options */
28 | "strict": true, /* Enable all strict type-checking options. */
29 | "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */
30 | // "strictNullChecks": true, /* Enable strict null checks. */
31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
36 |
37 | /* Additional Checks */
38 | // "noUnusedLocals": true, /* Report errors on unused locals. */
39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
45 |
46 | /* Module Resolution Options */
47 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
51 | // "typeRoots": [], /* List of folders to include type definitions from. */
52 | "types": ["jest"], /* Type declaration files to be included in compilation. */
53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
54 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
57 |
58 | /* Source Map Options */
59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
63 |
64 | /* Experimental Options */
65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
67 |
68 | /* Advanced Options */
69 | "skipLibCheck": true, /* Skip type checking of declaration files. */
70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/runtime-core/renderer.ts:
--------------------------------------------------------------------------------
1 | import { createComponentInstance, setupComponent } from "./components";
2 | import { ShapeFlags } from "../shared/ShapeFlags";
3 | import { Fragment, Text } from "./vnode";
4 | import { createAppAPI } from "./createApp";
5 | import { effect } from "../reactive/index";
6 | import { EMPTY_OBJ } from "../shared/index";
7 | import { shouldUpdateComponent } from "./componentUpdateUtils";
8 | import { queueJobs } from "./scheduler";
9 |
10 | export function createRenderer(options) {
11 | const {
12 | createElement: hostCreateElement,
13 | patchProps: hostPatchProps,
14 | insert: hostInsert,
15 | remove: hostRemove,
16 | setElementText: hostSetElementText,
17 | } = options;
18 |
19 | function render(vnode, container, parent) {
20 | patch(null, vnode, container, parent, null);
21 | }
22 |
23 | function patch(n1, n2, container, parent, anchor) {
24 | const { shapeFlag, type } = n2;
25 |
26 | switch (type) {
27 | case Fragment:
28 | processFragment(n2, container, parent);
29 | break;
30 | case Text:
31 | processText(n2, container);
32 | break;
33 | default:
34 | if (shapeFlag & ShapeFlags.ELEMENT) {
35 | processElement(n1, n2, container, parent, anchor);
36 | } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
37 | processComponent(n1, n2, container, parent, anchor);
38 | }
39 | }
40 | }
41 |
42 | function processFragment(vnode, container, parent) {
43 | mountChildren(vnode, container, parent);
44 | }
45 |
46 | function processText(vnode, container) {
47 | const el = (vnode.el = document.createTextNode(vnode.children));
48 |
49 | container.appendChild(el);
50 | }
51 |
52 | function processComponent(n1, n2, container, parent, anchor) {
53 | if (!n1) {
54 | mountComponent(n2, container, parent, anchor);
55 | } else {
56 | updateComponent(n1, n2);
57 | }
58 | }
59 |
60 | function mountComponent(initialVNode, container, parent, anchor) {
61 | const instance = (initialVNode.component = createComponentInstance(
62 | initialVNode,
63 | parent
64 | ));
65 |
66 | setupComponent(instance);
67 | setupRenderEffect(instance, initialVNode, container, anchor);
68 | }
69 |
70 | function updateComponent(n1, n2) {
71 | // 需要把n1.component赋值给n2.component
72 | // 不然下次更新n2.component会为null
73 | const instance = (n2.component = n1.component);
74 | if (shouldUpdateComponent(n1, n2)) {
75 | instance.next = n2;
76 | instance.update();
77 | }
78 | }
79 |
80 | function setupRenderEffect(instance, vnode, container, anchor) {
81 | instance.update = effect(
82 | () => {
83 | const { proxy, isMounted } = instance;
84 | if (!isMounted) {
85 | const subTree = (instance.subTree = instance.render.call(proxy));
86 |
87 | patch(null, subTree, container, instance, anchor);
88 |
89 | vnode.el = subTree.el;
90 | instance.isMounted = true;
91 | } else {
92 | // next是新的虚拟节点,vnode是旧的虚拟节点
93 | const { next, vnode } = instance;
94 | if (next) {
95 | next.el = vnode.el;
96 | updateComponentPreRender(instance, next);
97 | }
98 | const subTree = instance.render.call(proxy);
99 | const prevTree = instance.subTree;
100 | instance.subTree = subTree;
101 | patch(prevTree, subTree, container, instance, anchor);
102 | }
103 | },
104 | {
105 | scheduler() {
106 | queueJobs(instance.update);
107 | },
108 | }
109 | );
110 | }
111 |
112 | function updateComponentPreRender(instance, next) {
113 | instance.props = next.props;
114 | instance.next = null;
115 | instance.vnode = next;
116 | }
117 |
118 | function processElement(n1, n2, container, parent, anchor) {
119 | if (!n1) {
120 | mountElement(n2, container, parent, anchor);
121 | } else {
122 | patchElement(n1, n2, parent, anchor);
123 | }
124 | }
125 |
126 | function mountElement(vnode, container, parent, anchor) {
127 | const { type, props, children, shapeFlag } = vnode;
128 | // 创建DOM节点
129 | const el = (vnode.el = hostCreateElement(type));
130 |
131 | // 处理props
132 | for (const key in props) {
133 | hostPatchProps(el, key, props[key]);
134 | }
135 |
136 | // 处理children属性
137 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
138 | el.textContent = children;
139 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
140 | mountChildren(vnode, el, parent);
141 | }
142 | hostInsert(el, container, anchor);
143 | }
144 |
145 | function patchElement(n1, n2, parent, anchor) {
146 | // p1 oldProps, p2 newProps
147 | const p1 = n1.props || EMPTY_OBJ;
148 | const p2 = n2.props || EMPTY_OBJ;
149 | // 将el赋值给n2,因为下次patch n2就是n1
150 | const el = (n2.el = n1.el);
151 |
152 | patchProps(p1, p2, el);
153 |
154 | patchChildren(n1, n2, el, parent, anchor);
155 | }
156 |
157 | function patchChildren(n1, n2, container, parent, anchor) {
158 | const c1 = n1.children;
159 | const c2 = n2.children;
160 | const el = n1.el;
161 | const prevShapeFlag = n1.shapeFlag;
162 | const { shapeFlag } = n2;
163 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
164 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
165 | // 新节点children是文本类型, 旧节点children是数组类型
166 | unmountChildren(n1);
167 | hostSetElementText(el, c2);
168 | } else {
169 | patchKeydChildren(c1, c2, container, parent, anchor);
170 | }
171 | } else {
172 | if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
173 | // 新节点children是数组类型,旧节点children是文本类型
174 | hostSetElementText(el, "");
175 | mountChildren(n2, el, parent);
176 | } else {
177 | // 新旧节点children类型都是文本类型
178 | if (c1 !== c2) {
179 | hostSetElementText(el, c2);
180 | }
181 | }
182 | }
183 | }
184 |
185 | function isSameVNodeType(n1, n2) {
186 | return n1.type === n2.type && n1.key === n2.key;
187 | }
188 |
189 | function patchKeydChildren(
190 | c1: any[],
191 | c2: any[],
192 | container,
193 | parent,
194 | parentAnchor
195 | ) {
196 | let i = 0;
197 | let e1 = c1.length - 1;
198 | let e2 = c2.length - 1;
199 | const l2 = c2.length;
200 |
201 | while (i <= e1 && i <= e2) {
202 | if (isSameVNodeType(c1[i], c2[i])) {
203 | patch(c1[i], c2[i], container, parent, parentAnchor);
204 | } else {
205 | break;
206 | }
207 | i++;
208 | }
209 |
210 | while (i <= e1 && i <= e2) {
211 | const n1 = c1[e1];
212 | const n2 = c2[e2];
213 | if (isSameVNodeType(n1, n2)) {
214 | patch(c1[e1], c2[e2], container, parent, parentAnchor);
215 | } else {
216 | break;
217 | }
218 | e1--;
219 | e2--;
220 | }
221 |
222 | if (i > e1) {
223 | if (i <= e2) {
224 | // 找到锚点位置
225 | const nextPos = e2 + 1;
226 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
227 |
228 | while (i <= e2) {
229 | // abc -> abcde
230 | // bcd -> abcd
231 | // 新增节点
232 | patch(null, c2[i], container, parent, anchor);
233 | i++;
234 | }
235 | }
236 | } else if (i > e2) {
237 | while (i <= e1) {
238 | // 删除节点
239 | hostRemove(c1[i].el);
240 | i++;
241 | }
242 | } else {
243 | let s1 = i;
244 | let s2 = i;
245 | const toBePatched = e2 - s2 + 1;
246 | let patched = 0;
247 | const keyToNewIndexMap = new Map();
248 | const newIndexToOldIndexMap = new Array(toBePatched).fill(0);
249 |
250 | let moved = false;
251 | let maxIndexSoFar = 0;
252 |
253 | for (let i = s2; i <= e2; i++) {
254 | const key = c2[i].key;
255 | keyToNewIndexMap.set(key, i);
256 | }
257 |
258 | for (let i = s1; i <= e1; i++) {
259 | if (patched >= toBePatched) {
260 | hostRemove(c2[i]);
261 | continue;
262 | }
263 |
264 | const preVnode = c1[i];
265 | const key = preVnode.key;
266 | let newIndex;
267 |
268 | if (key !== null || key !== undefined) {
269 | newIndex = keyToNewIndexMap.get(key);
270 | } else {
271 | for (let j = s2; j <= e2; j++) {
272 | if (isSameVNodeType(preVnode, c2[j])) {
273 | newIndex = j;
274 | break;
275 | }
276 | }
277 | }
278 | if (newIndex === undefined) {
279 | hostRemove(preVnode.el);
280 | } else {
281 | if (maxIndexSoFar < newIndex) {
282 | maxIndexSoFar = newIndex;
283 | } else {
284 | moved = true;
285 | }
286 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
287 | patch(preVnode, c2[newIndex], container, parent, null);
288 | patched++;
289 | }
290 | }
291 |
292 | // 返回最长递增子序列的下标,也就是新数组的下标。
293 | // 新数组节点的下标在该数组中,则说明不需要移动
294 | const increasingNewIndexSequence = moved
295 | ? getSequence(newIndexToOldIndexMap)
296 | : [];
297 |
298 | let j = increasingNewIndexSequence.length - 1;
299 |
300 | for (let i = toBePatched - 1; i >= 0; i--) {
301 | const nextIndex = i + s2;
302 | const nextChild = c2[nextIndex];
303 | const anchor = nextIndex + 1 >= l2 ? null : c2[nextIndex + 1].el;
304 |
305 | if (newIndexToOldIndexMap[i] === 0) {
306 | // 需要新增节点
307 | patch(null, nextChild, container, parent, anchor);
308 | } else if (moved) {
309 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
310 | hostInsert(nextChild.el, container, anchor);
311 | } else {
312 | j--;
313 | }
314 | }
315 | }
316 | }
317 | }
318 |
319 | function unmountChildren(vnode) {
320 | vnode.children.forEach((v) => {
321 | const el = v.el;
322 | hostRemove(el);
323 | });
324 | }
325 |
326 | function patchProps(oldProps, newProps, el) {
327 | if (oldProps !== newProps) {
328 | // 遍历新属性,增加或者修改属性值
329 | for (const key in newProps) {
330 | const prevProp = oldProps[key];
331 | const currentProp = newProps[key];
332 |
333 | if (prevProp !== currentProp) {
334 | hostPatchProps(el, key, currentProp);
335 | }
336 | }
337 | // 当旧属性不为空对象时
338 | if (oldProps !== EMPTY_OBJ) {
339 | // 遍历旧属性,删除旧属性有而新属性没有的属性
340 | for (const key in oldProps) {
341 | if (!(key in newProps)) {
342 | hostPatchProps(el, key, null);
343 | }
344 | }
345 | }
346 | }
347 | }
348 |
349 | function mountChildren(vnode, container, parent) {
350 | vnode.children.forEach((item) => {
351 | patch(null, item, container, parent, null);
352 | });
353 | }
354 |
355 | return {
356 | createApp: createAppAPI(render),
357 | };
358 | }
359 |
360 | function getSequence(arr: number[]): number[] {
361 | const p = arr.slice();
362 | const result = [0];
363 | let i, j, u, v, c;
364 | const len = arr.length;
365 | for (i = 0; i < len; i++) {
366 | const arrI = arr[i];
367 | if (arrI !== 0) {
368 | j = result[result.length - 1];
369 | if (arr[j] < arrI) {
370 | p[i] = j;
371 | result.push(i);
372 | continue;
373 | }
374 | u = 0;
375 | v = result.length - 1;
376 | while (u < v) {
377 | c = (u + v) >> 1;
378 | if (arr[result[c]] < arrI) {
379 | u = c + 1;
380 | } else {
381 | v = c;
382 | }
383 | }
384 | if (arrI < arr[result[u]]) {
385 | if (u > 0) {
386 | p[i] = result[u - 1];
387 | }
388 | result[u] = i;
389 | }
390 | }
391 | }
392 | u = result.length;
393 | v = result[u - 1];
394 | while (u-- > 0) {
395 | result[u] = v;
396 | v = p[v];
397 | }
398 | return result;
399 | }
400 |
--------------------------------------------------------------------------------
/lib/mini-vue.esm.js:
--------------------------------------------------------------------------------
1 | const extend = Object.assign;
2 | const isObject = (value) => {
3 | return value !== null && typeof value === "object";
4 | };
5 | const hasChange = (value, newValue) => {
6 | return !Object.is(value, newValue);
7 | };
8 | function hasOwn(obj, key) {
9 | return Object.prototype.hasOwnProperty.call(obj, key);
10 | }
11 | function isArray(value) {
12 | return Array.isArray(value);
13 | }
14 | // foo-add => fooAdd
15 | const camelize = (str) => {
16 | return str.replace(/-(\w)/g, (_, p) => {
17 | return p ? p.toUpperCase() : "";
18 | });
19 | };
20 | const capitalize = (str) => {
21 | return str.charAt(0).toUpperCase() + str.slice(1);
22 | };
23 | // fooAdd => onFooAdd
24 | const handleEventName = (name) => {
25 | return name ? "on" + capitalize(name) : "";
26 | };
27 | const EMPTY_OBJ = {};
28 |
29 | const publicPropertiesMap = {
30 | $el: (i) => i.vnode.el,
31 | $slots: (i) => i.slots,
32 | $props: (i) => i.props,
33 | };
34 | const publicInstanceProxyHandlers = {
35 | get({ _: instance }, key) {
36 | if (hasOwn(instance.setupState, key)) {
37 | return instance.setupState[key];
38 | }
39 | else if (hasOwn(instance.props, key)) {
40 | return instance.props[key];
41 | }
42 | const publicGetter = publicPropertiesMap[key];
43 | if (publicGetter) {
44 | return publicGetter(instance);
45 | }
46 | },
47 | };
48 |
49 | function initProps(instance, rawProps) {
50 | // 将props挂载到实例上
51 | instance.props = rawProps;
52 | // attrs
53 | }
54 |
55 | let activeEffect;
56 | let shouldTrack;
57 | const targetMap = new WeakMap();
58 | class ReactiveEffect {
59 | constructor(fn, scheduler) {
60 | this.scheduler = scheduler;
61 | // 该变量用来记录是否调过stop函数
62 | this.active = true;
63 | // 收集该effect的dep
64 | this.deps = [];
65 | this._fn = fn;
66 | this.scheduler = scheduler;
67 | }
68 | run(...args) {
69 | activeEffect = this;
70 | if (!this.active) {
71 | return this._fn(...args);
72 | }
73 | shouldTrack = true;
74 | const res = this._fn(...args);
75 | shouldTrack = false;
76 | return res;
77 | }
78 | stop() {
79 | if (this.active) {
80 | cleanupEffect(this);
81 | this.onStop && this.onStop();
82 | this.active = false;
83 | }
84 | }
85 | }
86 | function cleanupEffect(effect) {
87 | effect.deps.forEach((dep) => {
88 | dep.delete(effect);
89 | });
90 | // 清空effect.deps
91 | effect.deps.length = 0;
92 | }
93 | function track(target, key) {
94 | if (!isTracking())
95 | return;
96 | let depsMap = targetMap.get(target);
97 | if (!depsMap) {
98 | depsMap = new Map();
99 | targetMap.set(target, depsMap);
100 | }
101 | let dep = depsMap.get(key);
102 | if (!dep) {
103 | dep = new Set();
104 | depsMap.set(key, dep);
105 | }
106 | trackEffects(dep);
107 | }
108 | function trackEffects(dep) {
109 | dep.add(activeEffect);
110 | activeEffect.deps.push(dep);
111 | }
112 | function isTracking() {
113 | return shouldTrack && activeEffect !== undefined;
114 | }
115 | function trigger(target, key) {
116 | const depsMap = targetMap.get(target);
117 | if (!depsMap)
118 | return;
119 | const dep = depsMap.get(key);
120 | triggerEffects(dep);
121 | }
122 | function triggerEffects(dep) {
123 | if (!dep)
124 | return;
125 | for (const effect of dep) {
126 | if (effect.scheduler) {
127 | effect.scheduler();
128 | }
129 | else {
130 | effect.run();
131 | }
132 | }
133 | }
134 | function effect(fn, options = {}) {
135 | const _effect = new ReactiveEffect(fn, options.scheduler);
136 | extend(_effect, options);
137 | _effect.run();
138 | const runner = _effect.run.bind(_effect);
139 | runner.effect = _effect;
140 | return runner;
141 | }
142 |
143 | function createGetter(isReadonly = false, isShallow = false) {
144 | return function get(target, key, receiver) {
145 | const res = Reflect.get(target, key, receiver);
146 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
147 | return !isReadonly;
148 | }
149 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
150 | return isReadonly;
151 | }
152 | if (!isReadonly) {
153 | track(target, key);
154 | }
155 | if (isObject(res) && !isShallow) {
156 | return isReadonly ? readonly(res) : reactive(res);
157 | }
158 | return res;
159 | };
160 | }
161 | function createSetter() {
162 | return function set(target, key, newValue, receiver) {
163 | const res = Reflect.set(target, key, newValue, receiver);
164 | // 触发依赖
165 | trigger(target, key);
166 | return res;
167 | };
168 | }
169 | const get = createGetter();
170 | const set = createSetter();
171 | const readonlyGet = createGetter(true);
172 | const shallowReadonlyGet = createGetter(true, true);
173 | const mutableHandlers = {
174 | get,
175 | set,
176 | };
177 | const readonlyHandlers = {
178 | get: readonlyGet,
179 | set(target) {
180 | console.warn(`${target} is a readonly can not be set`);
181 | return true;
182 | },
183 | };
184 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
185 | get: shallowReadonlyGet,
186 | });
187 |
188 | function createReactiveObj(raw, baseHandlers) {
189 | if (!isObject(raw)) {
190 | console.error(`${raw} 必须是一个对象`);
191 | }
192 | return new Proxy(raw, baseHandlers);
193 | }
194 | function reactive(raw) {
195 | return createReactiveObj(raw, mutableHandlers);
196 | }
197 | function readonly(raw) {
198 | return createReactiveObj(raw, readonlyHandlers);
199 | }
200 | function shallowReadonly(raw) {
201 | return createReactiveObj(raw, shallowReadonlyHandlers);
202 | }
203 |
204 | function emit(instance, event, ...args) {
205 | const { props } = instance;
206 | const handlerName = camelize(handleEventName(event));
207 | const handler = props[handlerName];
208 | handler && handler(...args);
209 | }
210 |
211 | function initSlots(instance, children) {
212 | // instance.slots = Array.isArray(children) ? children : [children];
213 | const { vnode } = instance;
214 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
215 | instance.slots = children;
216 | for (const slot in children) {
217 | const value = children[slot];
218 | children[slot] = (props) => normalizeSlotValue(value(props));
219 | }
220 | }
221 | }
222 | function normalizeSlotValue(value) {
223 | return isArray(value) ? value : [value];
224 | }
225 |
226 | let currentInstance = null;
227 | function createComponentInstance(vnode, parent) {
228 | const component = {
229 | vnode,
230 | type: vnode.type,
231 | setupState: {},
232 | props: {},
233 | slots: {},
234 | provides: parent ? parent.provides : {},
235 | isMounted: false,
236 | subTree: {},
237 | next: null,
238 | update: null,
239 | emit: () => { },
240 | };
241 | component.emit = emit.bind(null, component);
242 | return component;
243 | }
244 | function setupComponent(instance) {
245 | const rawProps = instance.vnode.props;
246 | initProps(instance, rawProps);
247 | initSlots(instance, instance.vnode.children);
248 | setupStatefulComponent(instance);
249 | }
250 | function setupStatefulComponent(instance) {
251 | const component = instance.type;
252 | const props = instance.vnode.props;
253 | const emit = instance.emit;
254 | // ctx 代理
255 | instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandlers);
256 | const { setup } = component;
257 | if (setup) {
258 | setCurrentInstance(instance);
259 | const setupResult = setup(shallowReadonly(props), { emit });
260 | setCurrentInstance(null);
261 | handleSetupResult(instance, setupResult);
262 | }
263 | }
264 | function handleSetupResult(instance, setupResult) {
265 | // TODO function
266 | // 如果返回的是一个对象,会将返回的值注入到instance中
267 | if (isObject(setupResult)) {
268 | instance.setupState = proxyRefs(setupResult);
269 | }
270 | finishComponentSetup(instance);
271 | }
272 | function finishComponentSetup(instance) {
273 | const component = instance.type;
274 | if (component.render) {
275 | instance.render = component.render;
276 | }
277 | }
278 | function getCurrentInstance() {
279 | return currentInstance;
280 | }
281 | function setCurrentInstance(instance) {
282 | currentInstance = instance;
283 | }
284 |
285 | const Fragment = Symbol("Fragment");
286 | const Text = Symbol("Text");
287 | function createVNode(type, props = {}, children = []) {
288 | const vnode = {
289 | type,
290 | props,
291 | children,
292 | appContext: null,
293 | component: null,
294 | key: props.key || undefined,
295 | el: null,
296 | shapeFlag: getShapeFlag(type),
297 | };
298 | if (Array.isArray(children)) {
299 | // 子节点为数组
300 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
301 | }
302 | else if (typeof children === "string") {
303 | // 子节点为text类型
304 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
305 | }
306 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
307 | if (isObject(children)) {
308 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */;
309 | }
310 | }
311 | return vnode;
312 | }
313 | function getShapeFlag(type) {
314 | return typeof type === "string"
315 | ? 1 /* ELEMENT */
316 | : 2 /* STATEFUL_COMPONENT */;
317 | }
318 | function createTextVNode(text) {
319 | return createVNode(Text, {}, text);
320 | }
321 |
322 | function createAppContext() {
323 | return {
324 | app: null,
325 | components: {},
326 | };
327 | }
328 | function createAppAPI(render) {
329 | return function createApp(App) {
330 | const context = createAppContext();
331 | const app = (context.app = {
332 | mount(rootComponent) {
333 | const vnode = createVNode(App);
334 | vnode.appContext = context;
335 | render(vnode, rootComponent, null);
336 | },
337 | component(name, component) {
338 | if (!component) {
339 | return context.components[name];
340 | }
341 | context.components[name] = component;
342 | },
343 | });
344 | return app;
345 | };
346 | }
347 |
348 | class ComputedRefImpl {
349 | constructor(getter) {
350 | this._dirty = true;
351 | this._getter = getter;
352 | //
353 | this._effect = new ReactiveEffect(this._getter, () => {
354 | // scheduler函数会在依赖改变时,执行
355 | // 当computed依赖的值发生改变时,将this._dirty设置为true
356 | // 这样当下次获取.value时,this._dirty就为true,就会重新触发getter函数,获取最新的值
357 | this._dirty = true;
358 | });
359 | }
360 | get value() {
361 | if (this._dirty) {
362 | // this._dirty 如果为true,则说明需要重新执行getter函数
363 | this._value = this._effect.run();
364 | // 执行完getter函数将this._dirty设置为false,这样当依赖没有改变时,再次获取.value时,不会重新出发getter函数
365 | this._dirty = false;
366 | }
367 | return this._value;
368 | }
369 | }
370 | function computed(getter) {
371 | return new ComputedRefImpl(getter);
372 | }
373 |
374 | class RefImplement {
375 | constructor(value) {
376 | this.__v_isRef = true;
377 | this._rawValue = value;
378 | this._value = convert(value);
379 | this.deps = new Set();
380 | }
381 | get value() {
382 | if (isTracking()) {
383 | trackEffects(this.deps);
384 | }
385 | return this._value;
386 | }
387 | set value(newValue) {
388 | if (!hasChange(this._rawValue, newValue))
389 | return;
390 | this._rawValue = newValue;
391 | this._value = convert(newValue);
392 | triggerEffects(this.deps);
393 | }
394 | }
395 | function convert(value) {
396 | return isObject(value) ? reactive(value) : value;
397 | }
398 | function ref(value) {
399 | return new RefImplement(value);
400 | }
401 | function isRef(ref) {
402 | if (!ref)
403 | return false;
404 | return !!ref.__v_isRef;
405 | }
406 | function unRef(ref) {
407 | return isRef(ref) ? ref.value : ref;
408 | }
409 | function proxyRefs(objWithRefs) {
410 | return new Proxy(objWithRefs, {
411 | get(target, key, receiver) {
412 | return unRef(Reflect.get(target, key, receiver));
413 | },
414 | set(target, key, newValue, receiver) {
415 | // 当原来值是ref类型,而修改的值不是ref类型
416 | if (isRef(target[key]) && !isRef(newValue)) {
417 | return (target[key].value = newValue);
418 | }
419 | return Reflect.set(target, key, newValue, receiver);
420 | },
421 | });
422 | }
423 |
424 | function shouldUpdateComponent(n1, n2) {
425 | const { props: oldProps } = n1;
426 | const { props: newProps } = n2;
427 | for (const key in newProps) {
428 | if (oldProps[key] !== newProps[key]) {
429 | return true;
430 | }
431 | }
432 | return false;
433 | }
434 |
435 | const queue = [];
436 | let isFlushPending = false;
437 | const p = Promise.resolve();
438 | function nextTick(fn) {
439 | return fn ? p.then(fn) : p;
440 | }
441 | function queueJobs(fn) {
442 | // queue.in;
443 | if (!queue.includes(fn)) {
444 | queue.push(fn);
445 | }
446 | queueFlush();
447 | }
448 | function queueFlush() {
449 | if (isFlushPending)
450 | return;
451 | isFlushPending = true;
452 | nextTick(flushJobs);
453 | }
454 | function flushJobs() {
455 | isFlushPending = false;
456 | let job;
457 | while ((job = queue.shift())) {
458 | job && job();
459 | }
460 | }
461 |
462 | function createRenderer(options) {
463 | const { createElement: hostCreateElement, patchProps: hostPatchProps, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options;
464 | function render(vnode, container, parent) {
465 | patch(null, vnode, container, parent, null);
466 | }
467 | function patch(n1, n2, container, parent, anchor) {
468 | const { shapeFlag, type } = n2;
469 | switch (type) {
470 | case Fragment:
471 | processFragment(n2, container, parent);
472 | break;
473 | case Text:
474 | processText(n2, container);
475 | break;
476 | default:
477 | if (shapeFlag & 1 /* ELEMENT */) {
478 | processElement(n1, n2, container, parent, anchor);
479 | }
480 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
481 | processComponent(n1, n2, container, parent, anchor);
482 | }
483 | }
484 | }
485 | function processFragment(vnode, container, parent) {
486 | mountChildren(vnode, container, parent);
487 | }
488 | function processText(vnode, container) {
489 | const el = (vnode.el = document.createTextNode(vnode.children));
490 | container.appendChild(el);
491 | }
492 | function processComponent(n1, n2, container, parent, anchor) {
493 | if (!n1) {
494 | mountComponent(n2, container, parent, anchor);
495 | }
496 | else {
497 | updateComponent(n1, n2);
498 | }
499 | }
500 | function mountComponent(initialVNode, container, parent, anchor) {
501 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parent));
502 | setupComponent(instance);
503 | setupRenderEffect(instance, initialVNode, container, anchor);
504 | }
505 | function updateComponent(n1, n2) {
506 | // 需要把n1.component赋值给n2.component
507 | // 不然下次更新n2.component会为null
508 | const instance = (n2.component = n1.component);
509 | if (shouldUpdateComponent(n1, n2)) {
510 | instance.next = n2;
511 | instance.update();
512 | }
513 | }
514 | function setupRenderEffect(instance, vnode, container, anchor) {
515 | instance.update = effect(() => {
516 | const { proxy, isMounted } = instance;
517 | if (!isMounted) {
518 | const subTree = (instance.subTree = instance.render.call(proxy));
519 | patch(null, subTree, container, instance, anchor);
520 | vnode.el = subTree.el;
521 | instance.isMounted = true;
522 | }
523 | else {
524 | // next是新的虚拟节点,vnode是旧的虚拟节点
525 | const { next, vnode } = instance;
526 | if (next) {
527 | next.el = vnode.el;
528 | updateComponentPreRender(instance, next);
529 | }
530 | const subTree = instance.render.call(proxy);
531 | const prevTree = instance.subTree;
532 | instance.subTree = subTree;
533 | patch(prevTree, subTree, container, instance, anchor);
534 | }
535 | }, {
536 | scheduler() {
537 | queueJobs(instance.update);
538 | },
539 | });
540 | }
541 | function updateComponentPreRender(instance, next) {
542 | instance.props = next.props;
543 | instance.next = null;
544 | instance.vnode = next;
545 | }
546 | function processElement(n1, n2, container, parent, anchor) {
547 | if (!n1) {
548 | mountElement(n2, container, parent, anchor);
549 | }
550 | else {
551 | patchElement(n1, n2, parent, anchor);
552 | }
553 | }
554 | function mountElement(vnode, container, parent, anchor) {
555 | const { type, props, children, shapeFlag } = vnode;
556 | // 创建DOM节点
557 | const el = (vnode.el = hostCreateElement(type));
558 | // 处理props
559 | for (const key in props) {
560 | hostPatchProps(el, key, props[key]);
561 | }
562 | // 处理children属性
563 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
564 | el.textContent = children;
565 | }
566 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
567 | mountChildren(vnode, el, parent);
568 | }
569 | hostInsert(el, container, anchor);
570 | }
571 | function patchElement(n1, n2, parent, anchor) {
572 | // p1 oldProps, p2 newProps
573 | const p1 = n1.props || EMPTY_OBJ;
574 | const p2 = n2.props || EMPTY_OBJ;
575 | // 将el赋值给n2,因为下次patch n2就是n1
576 | const el = (n2.el = n1.el);
577 | patchProps(p1, p2, el);
578 | patchChildren(n1, n2, el, parent, anchor);
579 | }
580 | function patchChildren(n1, n2, container, parent, anchor) {
581 | const c1 = n1.children;
582 | const c2 = n2.children;
583 | const el = n1.el;
584 | const prevShapeFlag = n1.shapeFlag;
585 | const { shapeFlag } = n2;
586 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
587 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
588 | // 新节点children是文本类型, 旧节点children是数组类型
589 | unmountChildren(n1);
590 | hostSetElementText(el, c2);
591 | }
592 | else {
593 | patchKeydChildren(c1, c2, container, parent, anchor);
594 | }
595 | }
596 | else {
597 | if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
598 | // 新节点children是数组类型,旧节点children是文本类型
599 | hostSetElementText(el, "");
600 | mountChildren(n2, el, parent);
601 | }
602 | else {
603 | // 新旧节点children类型都是文本类型
604 | if (c1 !== c2) {
605 | hostSetElementText(el, c2);
606 | }
607 | }
608 | }
609 | }
610 | function isSameVNodeType(n1, n2) {
611 | return n1.type === n2.type && n1.key === n2.key;
612 | }
613 | function patchKeydChildren(c1, c2, container, parent, parentAnchor) {
614 | let i = 0;
615 | let e1 = c1.length - 1;
616 | let e2 = c2.length - 1;
617 | const l2 = c2.length;
618 | while (i <= e1 && i <= e2) {
619 | if (isSameVNodeType(c1[i], c2[i])) {
620 | patch(c1[i], c2[i], container, parent, parentAnchor);
621 | }
622 | else {
623 | break;
624 | }
625 | i++;
626 | }
627 | while (i <= e1 && i <= e2) {
628 | const n1 = c1[e1];
629 | const n2 = c2[e2];
630 | if (isSameVNodeType(n1, n2)) {
631 | patch(c1[e1], c2[e2], container, parent, parentAnchor);
632 | }
633 | else {
634 | break;
635 | }
636 | e1--;
637 | e2--;
638 | }
639 | if (i > e1) {
640 | if (i <= e2) {
641 | // 找到锚点位置
642 | const nextPos = e2 + 1;
643 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
644 | while (i <= e2) {
645 | // abc -> abcde
646 | // bcd -> abcd
647 | // 新增节点
648 | patch(null, c2[i], container, parent, anchor);
649 | i++;
650 | }
651 | }
652 | }
653 | else if (i > e2) {
654 | while (i <= e1) {
655 | // 删除节点
656 | hostRemove(c1[i].el);
657 | i++;
658 | }
659 | }
660 | else {
661 | let s1 = i;
662 | let s2 = i;
663 | const toBePatched = e2 - s2 + 1;
664 | let patched = 0;
665 | const keyToNewIndexMap = new Map();
666 | const newIndexToOldIndexMap = new Array(toBePatched).fill(0);
667 | let moved = false;
668 | let maxIndexSoFar = 0;
669 | for (let i = s2; i <= e2; i++) {
670 | const key = c2[i].key;
671 | keyToNewIndexMap.set(key, i);
672 | }
673 | for (let i = s1; i <= e1; i++) {
674 | if (patched >= toBePatched) {
675 | hostRemove(c2[i]);
676 | continue;
677 | }
678 | const preVnode = c1[i];
679 | const key = preVnode.key;
680 | let newIndex;
681 | if (key !== null || key !== undefined) {
682 | newIndex = keyToNewIndexMap.get(key);
683 | }
684 | else {
685 | for (let j = s2; j <= e2; j++) {
686 | if (isSameVNodeType(preVnode, c2[j])) {
687 | newIndex = j;
688 | break;
689 | }
690 | }
691 | }
692 | if (newIndex === undefined) {
693 | hostRemove(preVnode.el);
694 | }
695 | else {
696 | if (maxIndexSoFar < newIndex) {
697 | maxIndexSoFar = newIndex;
698 | }
699 | else {
700 | moved = true;
701 | }
702 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
703 | patch(preVnode, c2[newIndex], container, parent, null);
704 | patched++;
705 | }
706 | }
707 | // 返回最长递增子序列的下标,也就是新数组的下标。
708 | // 新数组节点的下标在该数组中,则说明不需要移动
709 | const increasingNewIndexSequence = moved
710 | ? getSequence(newIndexToOldIndexMap)
711 | : [];
712 | let j = increasingNewIndexSequence.length - 1;
713 | for (let i = toBePatched - 1; i >= 0; i--) {
714 | const nextIndex = i + s2;
715 | const nextChild = c2[nextIndex];
716 | const anchor = nextIndex + 1 >= l2 ? null : c2[nextIndex + 1].el;
717 | if (newIndexToOldIndexMap[i] === 0) {
718 | // 需要新增节点
719 | patch(null, nextChild, container, parent, anchor);
720 | }
721 | else if (moved) {
722 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
723 | hostInsert(nextChild.el, container, anchor);
724 | }
725 | else {
726 | j--;
727 | }
728 | }
729 | }
730 | }
731 | }
732 | function unmountChildren(vnode) {
733 | vnode.children.forEach((v) => {
734 | const el = v.el;
735 | hostRemove(el);
736 | });
737 | }
738 | function patchProps(oldProps, newProps, el) {
739 | if (oldProps !== newProps) {
740 | // 遍历新属性,增加或者修改属性值
741 | for (const key in newProps) {
742 | const prevProp = oldProps[key];
743 | const currentProp = newProps[key];
744 | if (prevProp !== currentProp) {
745 | hostPatchProps(el, key, currentProp);
746 | }
747 | }
748 | // 当旧属性不为空对象时
749 | if (oldProps !== EMPTY_OBJ) {
750 | // 遍历旧属性,删除旧属性有而新属性没有的属性
751 | for (const key in oldProps) {
752 | if (!(key in newProps)) {
753 | hostPatchProps(el, key, null);
754 | }
755 | }
756 | }
757 | }
758 | }
759 | function mountChildren(vnode, container, parent) {
760 | vnode.children.forEach((item) => {
761 | patch(null, item, container, parent, null);
762 | });
763 | }
764 | return {
765 | createApp: createAppAPI(render),
766 | };
767 | }
768 | function getSequence(arr) {
769 | const p = arr.slice();
770 | const result = [0];
771 | let i, j, u, v, c;
772 | const len = arr.length;
773 | for (i = 0; i < len; i++) {
774 | const arrI = arr[i];
775 | if (arrI !== 0) {
776 | j = result[result.length - 1];
777 | if (arr[j] < arrI) {
778 | p[i] = j;
779 | result.push(i);
780 | continue;
781 | }
782 | u = 0;
783 | v = result.length - 1;
784 | while (u < v) {
785 | c = (u + v) >> 1;
786 | if (arr[result[c]] < arrI) {
787 | u = c + 1;
788 | }
789 | else {
790 | v = c;
791 | }
792 | }
793 | if (arrI < arr[result[u]]) {
794 | if (u > 0) {
795 | p[i] = result[u - 1];
796 | }
797 | result[u] = i;
798 | }
799 | }
800 | }
801 | u = result.length;
802 | v = result[u - 1];
803 | while (u-- > 0) {
804 | result[u] = v;
805 | v = p[v];
806 | }
807 | return result;
808 | }
809 |
810 | function h(type, props, children) {
811 | return createVNode(type, props, children);
812 | }
813 |
814 | function renderSlots(slots, props, name) {
815 | // 处理具名插槽
816 | if (slots[name]) {
817 | if (typeof slots[name] === "function") {
818 | return createVNode(Fragment, {}, slots[name](props));
819 | }
820 | }
821 | // 处理默认插槽
822 | const totalSlots = [];
823 | for (const slot in slots) {
824 | totalSlots.push(...slots[slot](props));
825 | }
826 | return createVNode(Fragment, {}, totalSlots);
827 | }
828 |
829 | const COMPONENTS = "components";
830 | function resolveComponent(name) {
831 | return resolveAsset(COMPONENTS, name);
832 | }
833 | function resolveAsset(type, name) {
834 | const instance = currentInstance;
835 | if (!instance)
836 | return name;
837 | const res = instance.appContext[type][name];
838 | return res === undefined ? instance.type : res;
839 | }
840 |
841 | function provide(key, value) {
842 | const instance = getCurrentInstance();
843 | const parentProvides = instance.parent ? instance.parent.provides : {};
844 | if (instance) {
845 | let { provides } = instance;
846 | if (parentProvides === provides) {
847 | // parentProvides === provides则表明是第一次进行provide
848 | provides = instance.provides = Object.create(parentProvides);
849 | }
850 | provides[key] = value;
851 | }
852 | }
853 | function inject(key, value) {
854 | const instance = getCurrentInstance();
855 | if (instance) {
856 | const { provides } = instance;
857 | const injectValue = provides[key];
858 | if (!injectValue) {
859 | if (typeof value === "function") {
860 | return value();
861 | }
862 | else {
863 | return value;
864 | }
865 | }
866 | return injectValue;
867 | }
868 | }
869 |
870 | function createElement(type) {
871 | return document.createElement(type);
872 | }
873 | function patchProps(el, key, val) {
874 | // handle props
875 | // 如果key为 on开头并且on后面的第一个字符为大写,则认定为事件监听
876 | const isOn = (key) => /^on[A-Z]/.test(key);
877 | // 处理事件
878 | if (isOn(key)) {
879 | const event = key.slice(2).toLocaleLowerCase();
880 | el.addEventListener(event, val);
881 | }
882 | else if (val !== undefined && val !== null) {
883 | el.setAttribute(key, val);
884 | }
885 | else {
886 | // 当属性值为undefined或者为null时,直接删除该属性即可
887 | el.removeAttribute(key);
888 | }
889 | }
890 | function insert(child, container, anchor) {
891 | container.insertBefore(child, anchor);
892 | }
893 | function remove(el) {
894 | const parent = el.parentNode;
895 | if (parent) {
896 | parent.removeChild(el);
897 | }
898 | }
899 | function setElementText(el, text) {
900 | el.textContent = text;
901 | }
902 | const renderer = createRenderer({
903 | createElement,
904 | patchProps,
905 | insert,
906 | remove,
907 | setElementText,
908 | });
909 | function createApp(...args) {
910 | return renderer.createApp(...args);
911 | }
912 |
913 | export { computed, createApp, createRenderer, createTextVNode, effect, getCurrentInstance, h, inject, nextTick, provide, proxyRefs, reactive, readonly, ref, renderSlots, renderer, resolveComponent, shallowReadonly };
914 |
--------------------------------------------------------------------------------
/lib/mini-vue.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | const extend = Object.assign;
6 | const isObject = (value) => {
7 | return value !== null && typeof value === "object";
8 | };
9 | const hasChange = (value, newValue) => {
10 | return !Object.is(value, newValue);
11 | };
12 | function hasOwn(obj, key) {
13 | return Object.prototype.hasOwnProperty.call(obj, key);
14 | }
15 | function isArray(value) {
16 | return Array.isArray(value);
17 | }
18 | // foo-add => fooAdd
19 | const camelize = (str) => {
20 | return str.replace(/-(\w)/g, (_, p) => {
21 | return p ? p.toUpperCase() : "";
22 | });
23 | };
24 | const capitalize = (str) => {
25 | return str.charAt(0).toUpperCase() + str.slice(1);
26 | };
27 | // fooAdd => onFooAdd
28 | const handleEventName = (name) => {
29 | return name ? "on" + capitalize(name) : "";
30 | };
31 | const EMPTY_OBJ = {};
32 |
33 | const publicPropertiesMap = {
34 | $el: (i) => i.vnode.el,
35 | $slots: (i) => i.slots,
36 | $props: (i) => i.props,
37 | };
38 | const publicInstanceProxyHandlers = {
39 | get({ _: instance }, key) {
40 | if (hasOwn(instance.setupState, key)) {
41 | return instance.setupState[key];
42 | }
43 | else if (hasOwn(instance.props, key)) {
44 | return instance.props[key];
45 | }
46 | const publicGetter = publicPropertiesMap[key];
47 | if (publicGetter) {
48 | return publicGetter(instance);
49 | }
50 | },
51 | };
52 |
53 | function initProps(instance, rawProps) {
54 | // 将props挂载到实例上
55 | instance.props = rawProps;
56 | // attrs
57 | }
58 |
59 | let activeEffect;
60 | let shouldTrack;
61 | const targetMap = new WeakMap();
62 | class ReactiveEffect {
63 | constructor(fn, scheduler) {
64 | this.scheduler = scheduler;
65 | // 该变量用来记录是否调过stop函数
66 | this.active = true;
67 | // 收集该effect的dep
68 | this.deps = [];
69 | this._fn = fn;
70 | this.scheduler = scheduler;
71 | }
72 | run(...args) {
73 | activeEffect = this;
74 | if (!this.active) {
75 | return this._fn(...args);
76 | }
77 | shouldTrack = true;
78 | const res = this._fn(...args);
79 | shouldTrack = false;
80 | return res;
81 | }
82 | stop() {
83 | if (this.active) {
84 | cleanupEffect(this);
85 | this.onStop && this.onStop();
86 | this.active = false;
87 | }
88 | }
89 | }
90 | function cleanupEffect(effect) {
91 | effect.deps.forEach((dep) => {
92 | dep.delete(effect);
93 | });
94 | // 清空effect.deps
95 | effect.deps.length = 0;
96 | }
97 | function track(target, key) {
98 | if (!isTracking())
99 | return;
100 | let depsMap = targetMap.get(target);
101 | if (!depsMap) {
102 | depsMap = new Map();
103 | targetMap.set(target, depsMap);
104 | }
105 | let dep = depsMap.get(key);
106 | if (!dep) {
107 | dep = new Set();
108 | depsMap.set(key, dep);
109 | }
110 | trackEffects(dep);
111 | }
112 | function trackEffects(dep) {
113 | dep.add(activeEffect);
114 | activeEffect.deps.push(dep);
115 | }
116 | function isTracking() {
117 | return shouldTrack && activeEffect !== undefined;
118 | }
119 | function trigger(target, key) {
120 | const depsMap = targetMap.get(target);
121 | if (!depsMap)
122 | return;
123 | const dep = depsMap.get(key);
124 | triggerEffects(dep);
125 | }
126 | function triggerEffects(dep) {
127 | if (!dep)
128 | return;
129 | for (const effect of dep) {
130 | if (effect.scheduler) {
131 | effect.scheduler();
132 | }
133 | else {
134 | effect.run();
135 | }
136 | }
137 | }
138 | function effect(fn, options = {}) {
139 | const _effect = new ReactiveEffect(fn, options.scheduler);
140 | extend(_effect, options);
141 | _effect.run();
142 | const runner = _effect.run.bind(_effect);
143 | runner.effect = _effect;
144 | return runner;
145 | }
146 |
147 | function createGetter(isReadonly = false, isShallow = false) {
148 | return function get(target, key, receiver) {
149 | const res = Reflect.get(target, key, receiver);
150 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
151 | return !isReadonly;
152 | }
153 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
154 | return isReadonly;
155 | }
156 | if (!isReadonly) {
157 | track(target, key);
158 | }
159 | if (isObject(res) && !isShallow) {
160 | return isReadonly ? readonly(res) : reactive(res);
161 | }
162 | return res;
163 | };
164 | }
165 | function createSetter() {
166 | return function set(target, key, newValue, receiver) {
167 | const res = Reflect.set(target, key, newValue, receiver);
168 | // 触发依赖
169 | trigger(target, key);
170 | return res;
171 | };
172 | }
173 | const get = createGetter();
174 | const set = createSetter();
175 | const readonlyGet = createGetter(true);
176 | const shallowReadonlyGet = createGetter(true, true);
177 | const mutableHandlers = {
178 | get,
179 | set,
180 | };
181 | const readonlyHandlers = {
182 | get: readonlyGet,
183 | set(target) {
184 | console.warn(`${target} is a readonly can not be set`);
185 | return true;
186 | },
187 | };
188 | const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
189 | get: shallowReadonlyGet,
190 | });
191 |
192 | function createReactiveObj(raw, baseHandlers) {
193 | if (!isObject(raw)) {
194 | console.error(`${raw} 必须是一个对象`);
195 | }
196 | return new Proxy(raw, baseHandlers);
197 | }
198 | function reactive(raw) {
199 | return createReactiveObj(raw, mutableHandlers);
200 | }
201 | function readonly(raw) {
202 | return createReactiveObj(raw, readonlyHandlers);
203 | }
204 | function shallowReadonly(raw) {
205 | return createReactiveObj(raw, shallowReadonlyHandlers);
206 | }
207 |
208 | function emit(instance, event, ...args) {
209 | const { props } = instance;
210 | const handlerName = camelize(handleEventName(event));
211 | const handler = props[handlerName];
212 | handler && handler(...args);
213 | }
214 |
215 | function initSlots(instance, children) {
216 | // instance.slots = Array.isArray(children) ? children : [children];
217 | const { vnode } = instance;
218 | if (vnode.shapeFlag & 16 /* SLOT_CHILDREN */) {
219 | instance.slots = children;
220 | for (const slot in children) {
221 | const value = children[slot];
222 | children[slot] = (props) => normalizeSlotValue(value(props));
223 | }
224 | }
225 | }
226 | function normalizeSlotValue(value) {
227 | return isArray(value) ? value : [value];
228 | }
229 |
230 | let currentInstance = null;
231 | function createComponentInstance(vnode, parent) {
232 | const component = {
233 | vnode,
234 | type: vnode.type,
235 | setupState: {},
236 | props: {},
237 | slots: {},
238 | provides: parent ? parent.provides : {},
239 | isMounted: false,
240 | subTree: {},
241 | next: null,
242 | update: null,
243 | emit: () => { },
244 | };
245 | component.emit = emit.bind(null, component);
246 | return component;
247 | }
248 | function setupComponent(instance) {
249 | const rawProps = instance.vnode.props;
250 | initProps(instance, rawProps);
251 | initSlots(instance, instance.vnode.children);
252 | setupStatefulComponent(instance);
253 | }
254 | function setupStatefulComponent(instance) {
255 | const component = instance.type;
256 | const props = instance.vnode.props;
257 | const emit = instance.emit;
258 | // ctx 代理
259 | instance.proxy = new Proxy({ _: instance }, publicInstanceProxyHandlers);
260 | const { setup } = component;
261 | if (setup) {
262 | setCurrentInstance(instance);
263 | const setupResult = setup(shallowReadonly(props), { emit });
264 | setCurrentInstance(null);
265 | handleSetupResult(instance, setupResult);
266 | }
267 | }
268 | function handleSetupResult(instance, setupResult) {
269 | // TODO function
270 | // 如果返回的是一个对象,会将返回的值注入到instance中
271 | if (isObject(setupResult)) {
272 | instance.setupState = proxyRefs(setupResult);
273 | }
274 | finishComponentSetup(instance);
275 | }
276 | function finishComponentSetup(instance) {
277 | const component = instance.type;
278 | if (component.render) {
279 | instance.render = component.render;
280 | }
281 | }
282 | function getCurrentInstance() {
283 | return currentInstance;
284 | }
285 | function setCurrentInstance(instance) {
286 | currentInstance = instance;
287 | }
288 |
289 | const Fragment = Symbol("Fragment");
290 | const Text = Symbol("Text");
291 | function createVNode(type, props = {}, children = []) {
292 | const vnode = {
293 | type,
294 | props,
295 | children,
296 | appContext: null,
297 | component: null,
298 | key: props.key || undefined,
299 | el: null,
300 | shapeFlag: getShapeFlag(type),
301 | };
302 | if (Array.isArray(children)) {
303 | // 子节点为数组
304 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
305 | }
306 | else if (typeof children === "string") {
307 | // 子节点为text类型
308 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
309 | }
310 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
311 | if (isObject(children)) {
312 | vnode.shapeFlag |= 16 /* SLOT_CHILDREN */;
313 | }
314 | }
315 | return vnode;
316 | }
317 | function getShapeFlag(type) {
318 | return typeof type === "string"
319 | ? 1 /* ELEMENT */
320 | : 2 /* STATEFUL_COMPONENT */;
321 | }
322 | function createTextVNode(text) {
323 | return createVNode(Text, {}, text);
324 | }
325 |
326 | function createAppContext() {
327 | return {
328 | app: null,
329 | components: {},
330 | };
331 | }
332 | function createAppAPI(render) {
333 | return function createApp(App) {
334 | const context = createAppContext();
335 | const app = (context.app = {
336 | mount(rootComponent) {
337 | const vnode = createVNode(App);
338 | vnode.appContext = context;
339 | render(vnode, rootComponent, null);
340 | },
341 | component(name, component) {
342 | if (!component) {
343 | return context.components[name];
344 | }
345 | context.components[name] = component;
346 | },
347 | });
348 | return app;
349 | };
350 | }
351 |
352 | class ComputedRefImpl {
353 | constructor(getter) {
354 | this._dirty = true;
355 | this._getter = getter;
356 | //
357 | this._effect = new ReactiveEffect(this._getter, () => {
358 | // scheduler函数会在依赖改变时,执行
359 | // 当computed依赖的值发生改变时,将this._dirty设置为true
360 | // 这样当下次获取.value时,this._dirty就为true,就会重新触发getter函数,获取最新的值
361 | this._dirty = true;
362 | });
363 | }
364 | get value() {
365 | if (this._dirty) {
366 | // this._dirty 如果为true,则说明需要重新执行getter函数
367 | this._value = this._effect.run();
368 | // 执行完getter函数将this._dirty设置为false,这样当依赖没有改变时,再次获取.value时,不会重新出发getter函数
369 | this._dirty = false;
370 | }
371 | return this._value;
372 | }
373 | }
374 | function computed(getter) {
375 | return new ComputedRefImpl(getter);
376 | }
377 |
378 | class RefImplement {
379 | constructor(value) {
380 | this.__v_isRef = true;
381 | this._rawValue = value;
382 | this._value = convert(value);
383 | this.deps = new Set();
384 | }
385 | get value() {
386 | if (isTracking()) {
387 | trackEffects(this.deps);
388 | }
389 | return this._value;
390 | }
391 | set value(newValue) {
392 | if (!hasChange(this._rawValue, newValue))
393 | return;
394 | this._rawValue = newValue;
395 | this._value = convert(newValue);
396 | triggerEffects(this.deps);
397 | }
398 | }
399 | function convert(value) {
400 | return isObject(value) ? reactive(value) : value;
401 | }
402 | function ref(value) {
403 | return new RefImplement(value);
404 | }
405 | function isRef(ref) {
406 | if (!ref)
407 | return false;
408 | return !!ref.__v_isRef;
409 | }
410 | function unRef(ref) {
411 | return isRef(ref) ? ref.value : ref;
412 | }
413 | function proxyRefs(objWithRefs) {
414 | return new Proxy(objWithRefs, {
415 | get(target, key, receiver) {
416 | return unRef(Reflect.get(target, key, receiver));
417 | },
418 | set(target, key, newValue, receiver) {
419 | // 当原来值是ref类型,而修改的值不是ref类型
420 | if (isRef(target[key]) && !isRef(newValue)) {
421 | return (target[key].value = newValue);
422 | }
423 | return Reflect.set(target, key, newValue, receiver);
424 | },
425 | });
426 | }
427 |
428 | function shouldUpdateComponent(n1, n2) {
429 | const { props: oldProps } = n1;
430 | const { props: newProps } = n2;
431 | for (const key in newProps) {
432 | if (oldProps[key] !== newProps[key]) {
433 | return true;
434 | }
435 | }
436 | return false;
437 | }
438 |
439 | const queue = [];
440 | let isFlushPending = false;
441 | const p = Promise.resolve();
442 | function nextTick(fn) {
443 | return fn ? p.then(fn) : p;
444 | }
445 | function queueJobs(fn) {
446 | // queue.in;
447 | if (!queue.includes(fn)) {
448 | queue.push(fn);
449 | }
450 | queueFlush();
451 | }
452 | function queueFlush() {
453 | if (isFlushPending)
454 | return;
455 | isFlushPending = true;
456 | nextTick(flushJobs);
457 | }
458 | function flushJobs() {
459 | isFlushPending = false;
460 | let job;
461 | while ((job = queue.shift())) {
462 | job && job();
463 | }
464 | }
465 |
466 | function createRenderer(options) {
467 | const { createElement: hostCreateElement, patchProps: hostPatchProps, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options;
468 | function render(vnode, container, parent) {
469 | patch(null, vnode, container, parent, null);
470 | }
471 | function patch(n1, n2, container, parent, anchor) {
472 | const { shapeFlag, type } = n2;
473 | switch (type) {
474 | case Fragment:
475 | processFragment(n2, container, parent);
476 | break;
477 | case Text:
478 | processText(n2, container);
479 | break;
480 | default:
481 | if (shapeFlag & 1 /* ELEMENT */) {
482 | processElement(n1, n2, container, parent, anchor);
483 | }
484 | else if (shapeFlag & 2 /* STATEFUL_COMPONENT */) {
485 | processComponent(n1, n2, container, parent, anchor);
486 | }
487 | }
488 | }
489 | function processFragment(vnode, container, parent) {
490 | mountChildren(vnode, container, parent);
491 | }
492 | function processText(vnode, container) {
493 | const el = (vnode.el = document.createTextNode(vnode.children));
494 | container.appendChild(el);
495 | }
496 | function processComponent(n1, n2, container, parent, anchor) {
497 | if (!n1) {
498 | mountComponent(n2, container, parent, anchor);
499 | }
500 | else {
501 | updateComponent(n1, n2);
502 | }
503 | }
504 | function mountComponent(initialVNode, container, parent, anchor) {
505 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parent));
506 | setupComponent(instance);
507 | setupRenderEffect(instance, initialVNode, container, anchor);
508 | }
509 | function updateComponent(n1, n2) {
510 | // 需要把n1.component赋值给n2.component
511 | // 不然下次更新n2.component会为null
512 | const instance = (n2.component = n1.component);
513 | if (shouldUpdateComponent(n1, n2)) {
514 | instance.next = n2;
515 | instance.update();
516 | }
517 | }
518 | function setupRenderEffect(instance, vnode, container, anchor) {
519 | instance.update = effect(() => {
520 | const { proxy, isMounted } = instance;
521 | if (!isMounted) {
522 | const subTree = (instance.subTree = instance.render.call(proxy));
523 | patch(null, subTree, container, instance, anchor);
524 | vnode.el = subTree.el;
525 | instance.isMounted = true;
526 | }
527 | else {
528 | // next是新的虚拟节点,vnode是旧的虚拟节点
529 | const { next, vnode } = instance;
530 | if (next) {
531 | next.el = vnode.el;
532 | updateComponentPreRender(instance, next);
533 | }
534 | const subTree = instance.render.call(proxy);
535 | const prevTree = instance.subTree;
536 | instance.subTree = subTree;
537 | patch(prevTree, subTree, container, instance, anchor);
538 | }
539 | }, {
540 | scheduler() {
541 | queueJobs(instance.update);
542 | },
543 | });
544 | }
545 | function updateComponentPreRender(instance, next) {
546 | instance.props = next.props;
547 | instance.next = null;
548 | instance.vnode = next;
549 | }
550 | function processElement(n1, n2, container, parent, anchor) {
551 | if (!n1) {
552 | mountElement(n2, container, parent, anchor);
553 | }
554 | else {
555 | patchElement(n1, n2, parent, anchor);
556 | }
557 | }
558 | function mountElement(vnode, container, parent, anchor) {
559 | const { type, props, children, shapeFlag } = vnode;
560 | // 创建DOM节点
561 | const el = (vnode.el = hostCreateElement(type));
562 | // 处理props
563 | for (const key in props) {
564 | hostPatchProps(el, key, props[key]);
565 | }
566 | // 处理children属性
567 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
568 | el.textContent = children;
569 | }
570 | else if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
571 | mountChildren(vnode, el, parent);
572 | }
573 | hostInsert(el, container, anchor);
574 | }
575 | function patchElement(n1, n2, parent, anchor) {
576 | // p1 oldProps, p2 newProps
577 | const p1 = n1.props || EMPTY_OBJ;
578 | const p2 = n2.props || EMPTY_OBJ;
579 | // 将el赋值给n2,因为下次patch n2就是n1
580 | const el = (n2.el = n1.el);
581 | patchProps(p1, p2, el);
582 | patchChildren(n1, n2, el, parent, anchor);
583 | }
584 | function patchChildren(n1, n2, container, parent, anchor) {
585 | const c1 = n1.children;
586 | const c2 = n2.children;
587 | const el = n1.el;
588 | const prevShapeFlag = n1.shapeFlag;
589 | const { shapeFlag } = n2;
590 | if (prevShapeFlag & 8 /* ARRAY_CHILDREN */) {
591 | if (shapeFlag & 4 /* TEXT_CHILDREN */) {
592 | // 新节点children是文本类型, 旧节点children是数组类型
593 | unmountChildren(n1);
594 | hostSetElementText(el, c2);
595 | }
596 | else {
597 | patchKeydChildren(c1, c2, container, parent, anchor);
598 | }
599 | }
600 | else {
601 | if (shapeFlag & 8 /* ARRAY_CHILDREN */) {
602 | // 新节点children是数组类型,旧节点children是文本类型
603 | hostSetElementText(el, "");
604 | mountChildren(n2, el, parent);
605 | }
606 | else {
607 | // 新旧节点children类型都是文本类型
608 | if (c1 !== c2) {
609 | hostSetElementText(el, c2);
610 | }
611 | }
612 | }
613 | }
614 | function isSameVNodeType(n1, n2) {
615 | return n1.type === n2.type && n1.key === n2.key;
616 | }
617 | function patchKeydChildren(c1, c2, container, parent, parentAnchor) {
618 | let i = 0;
619 | let e1 = c1.length - 1;
620 | let e2 = c2.length - 1;
621 | const l2 = c2.length;
622 | while (i <= e1 && i <= e2) {
623 | if (isSameVNodeType(c1[i], c2[i])) {
624 | patch(c1[i], c2[i], container, parent, parentAnchor);
625 | }
626 | else {
627 | break;
628 | }
629 | i++;
630 | }
631 | while (i <= e1 && i <= e2) {
632 | const n1 = c1[e1];
633 | const n2 = c2[e2];
634 | if (isSameVNodeType(n1, n2)) {
635 | patch(c1[e1], c2[e2], container, parent, parentAnchor);
636 | }
637 | else {
638 | break;
639 | }
640 | e1--;
641 | e2--;
642 | }
643 | if (i > e1) {
644 | if (i <= e2) {
645 | // 找到锚点位置
646 | const nextPos = e2 + 1;
647 | const anchor = nextPos < l2 ? c2[nextPos].el : null;
648 | while (i <= e2) {
649 | // abc -> abcde
650 | // bcd -> abcd
651 | // 新增节点
652 | patch(null, c2[i], container, parent, anchor);
653 | i++;
654 | }
655 | }
656 | }
657 | else if (i > e2) {
658 | while (i <= e1) {
659 | // 删除节点
660 | hostRemove(c1[i].el);
661 | i++;
662 | }
663 | }
664 | else {
665 | let s1 = i;
666 | let s2 = i;
667 | const toBePatched = e2 - s2 + 1;
668 | let patched = 0;
669 | const keyToNewIndexMap = new Map();
670 | const newIndexToOldIndexMap = new Array(toBePatched).fill(0);
671 | let moved = false;
672 | let maxIndexSoFar = 0;
673 | for (let i = s2; i <= e2; i++) {
674 | const key = c2[i].key;
675 | keyToNewIndexMap.set(key, i);
676 | }
677 | for (let i = s1; i <= e1; i++) {
678 | if (patched >= toBePatched) {
679 | hostRemove(c2[i]);
680 | continue;
681 | }
682 | const preVnode = c1[i];
683 | const key = preVnode.key;
684 | let newIndex;
685 | if (key !== null || key !== undefined) {
686 | newIndex = keyToNewIndexMap.get(key);
687 | }
688 | else {
689 | for (let j = s2; j <= e2; j++) {
690 | if (isSameVNodeType(preVnode, c2[j])) {
691 | newIndex = j;
692 | break;
693 | }
694 | }
695 | }
696 | if (newIndex === undefined) {
697 | hostRemove(preVnode.el);
698 | }
699 | else {
700 | if (maxIndexSoFar < newIndex) {
701 | maxIndexSoFar = newIndex;
702 | }
703 | else {
704 | moved = true;
705 | }
706 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
707 | patch(preVnode, c2[newIndex], container, parent, null);
708 | patched++;
709 | }
710 | }
711 | // 返回最长递增子序列的下标,也就是新数组的下标。
712 | // 新数组节点的下标在该数组中,则说明不需要移动
713 | const increasingNewIndexSequence = moved
714 | ? getSequence(newIndexToOldIndexMap)
715 | : [];
716 | let j = increasingNewIndexSequence.length - 1;
717 | for (let i = toBePatched - 1; i >= 0; i--) {
718 | const nextIndex = i + s2;
719 | const nextChild = c2[nextIndex];
720 | const anchor = nextIndex + 1 >= l2 ? null : c2[nextIndex + 1].el;
721 | if (newIndexToOldIndexMap[i] === 0) {
722 | // 需要新增节点
723 | patch(null, nextChild, container, parent, anchor);
724 | }
725 | else if (moved) {
726 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
727 | hostInsert(nextChild.el, container, anchor);
728 | }
729 | else {
730 | j--;
731 | }
732 | }
733 | }
734 | }
735 | }
736 | function unmountChildren(vnode) {
737 | vnode.children.forEach((v) => {
738 | const el = v.el;
739 | hostRemove(el);
740 | });
741 | }
742 | function patchProps(oldProps, newProps, el) {
743 | if (oldProps !== newProps) {
744 | // 遍历新属性,增加或者修改属性值
745 | for (const key in newProps) {
746 | const prevProp = oldProps[key];
747 | const currentProp = newProps[key];
748 | if (prevProp !== currentProp) {
749 | hostPatchProps(el, key, currentProp);
750 | }
751 | }
752 | // 当旧属性不为空对象时
753 | if (oldProps !== EMPTY_OBJ) {
754 | // 遍历旧属性,删除旧属性有而新属性没有的属性
755 | for (const key in oldProps) {
756 | if (!(key in newProps)) {
757 | hostPatchProps(el, key, null);
758 | }
759 | }
760 | }
761 | }
762 | }
763 | function mountChildren(vnode, container, parent) {
764 | vnode.children.forEach((item) => {
765 | patch(null, item, container, parent, null);
766 | });
767 | }
768 | return {
769 | createApp: createAppAPI(render),
770 | };
771 | }
772 | function getSequence(arr) {
773 | const p = arr.slice();
774 | const result = [0];
775 | let i, j, u, v, c;
776 | const len = arr.length;
777 | for (i = 0; i < len; i++) {
778 | const arrI = arr[i];
779 | if (arrI !== 0) {
780 | j = result[result.length - 1];
781 | if (arr[j] < arrI) {
782 | p[i] = j;
783 | result.push(i);
784 | continue;
785 | }
786 | u = 0;
787 | v = result.length - 1;
788 | while (u < v) {
789 | c = (u + v) >> 1;
790 | if (arr[result[c]] < arrI) {
791 | u = c + 1;
792 | }
793 | else {
794 | v = c;
795 | }
796 | }
797 | if (arrI < arr[result[u]]) {
798 | if (u > 0) {
799 | p[i] = result[u - 1];
800 | }
801 | result[u] = i;
802 | }
803 | }
804 | }
805 | u = result.length;
806 | v = result[u - 1];
807 | while (u-- > 0) {
808 | result[u] = v;
809 | v = p[v];
810 | }
811 | return result;
812 | }
813 |
814 | function h(type, props, children) {
815 | return createVNode(type, props, children);
816 | }
817 |
818 | function renderSlots(slots, props, name) {
819 | // 处理具名插槽
820 | if (slots[name]) {
821 | if (typeof slots[name] === "function") {
822 | return createVNode(Fragment, {}, slots[name](props));
823 | }
824 | }
825 | // 处理默认插槽
826 | const totalSlots = [];
827 | for (const slot in slots) {
828 | totalSlots.push(...slots[slot](props));
829 | }
830 | return createVNode(Fragment, {}, totalSlots);
831 | }
832 |
833 | const COMPONENTS = "components";
834 | function resolveComponent(name) {
835 | return resolveAsset(COMPONENTS, name);
836 | }
837 | function resolveAsset(type, name) {
838 | const instance = currentInstance;
839 | if (!instance)
840 | return name;
841 | const res = instance.appContext[type][name];
842 | return res === undefined ? instance.type : res;
843 | }
844 |
845 | function provide(key, value) {
846 | const instance = getCurrentInstance();
847 | const parentProvides = instance.parent ? instance.parent.provides : {};
848 | if (instance) {
849 | let { provides } = instance;
850 | if (parentProvides === provides) {
851 | // parentProvides === provides则表明是第一次进行provide
852 | provides = instance.provides = Object.create(parentProvides);
853 | }
854 | provides[key] = value;
855 | }
856 | }
857 | function inject(key, value) {
858 | const instance = getCurrentInstance();
859 | if (instance) {
860 | const { provides } = instance;
861 | const injectValue = provides[key];
862 | if (!injectValue) {
863 | if (typeof value === "function") {
864 | return value();
865 | }
866 | else {
867 | return value;
868 | }
869 | }
870 | return injectValue;
871 | }
872 | }
873 |
874 | function createElement(type) {
875 | return document.createElement(type);
876 | }
877 | function patchProps(el, key, val) {
878 | // handle props
879 | // 如果key为 on开头并且on后面的第一个字符为大写,则认定为事件监听
880 | const isOn = (key) => /^on[A-Z]/.test(key);
881 | // 处理事件
882 | if (isOn(key)) {
883 | const event = key.slice(2).toLocaleLowerCase();
884 | el.addEventListener(event, val);
885 | }
886 | else if (val !== undefined && val !== null) {
887 | el.setAttribute(key, val);
888 | }
889 | else {
890 | // 当属性值为undefined或者为null时,直接删除该属性即可
891 | el.removeAttribute(key);
892 | }
893 | }
894 | function insert(child, container, anchor) {
895 | container.insertBefore(child, anchor);
896 | }
897 | function remove(el) {
898 | const parent = el.parentNode;
899 | if (parent) {
900 | parent.removeChild(el);
901 | }
902 | }
903 | function setElementText(el, text) {
904 | el.textContent = text;
905 | }
906 | const renderer = createRenderer({
907 | createElement,
908 | patchProps,
909 | insert,
910 | remove,
911 | setElementText,
912 | });
913 | function createApp(...args) {
914 | return renderer.createApp(...args);
915 | }
916 |
917 | exports.computed = computed;
918 | exports.createApp = createApp;
919 | exports.createRenderer = createRenderer;
920 | exports.createTextVNode = createTextVNode;
921 | exports.effect = effect;
922 | exports.getCurrentInstance = getCurrentInstance;
923 | exports.h = h;
924 | exports.inject = inject;
925 | exports.nextTick = nextTick;
926 | exports.provide = provide;
927 | exports.proxyRefs = proxyRefs;
928 | exports.reactive = reactive;
929 | exports.readonly = readonly;
930 | exports.ref = ref;
931 | exports.renderSlots = renderSlots;
932 | exports.renderer = renderer;
933 | exports.resolveComponent = resolveComponent;
934 | exports.shallowReadonly = shallowReadonly;
935 |
--------------------------------------------------------------------------------