hi, 前后的空格可能导致测试不通过
48 |
49 | expect(ast.children[0]).toStrictEqual({
50 | type: NodeTypes.ELEMENT,
51 | tag: "div",
52 | children: [
53 | {
54 | type: NodeTypes.TEXT,
55 | content: "hi,"
56 | },
57 | {
58 | type: NodeTypes.INTERPOLATION,
59 | content: {
60 | type: NodeTypes.SIMPLE_EXPRESSION,
61 | content: "message",
62 | },
63 | }
64 | ]
65 | })
66 | });
67 |
68 | // 三种联合类型的 edge case 1
69 | test('Nested element', () => {
70 | const ast = baseParse("
");
71 |
72 | expect(ast.children[0]).toStrictEqual({
73 | type: NodeTypes.ELEMENT,
74 | tag: "div",
75 | children: [
76 | {
77 | type: NodeTypes.ELEMENT,
78 | tag: "p",
79 | children: [
80 | {
81 | type: NodeTypes.TEXT,
82 | content: "hi,"
83 | }
84 | ]
85 | },
86 | {
87 | type: NodeTypes.INTERPOLATION,
88 | content: {
89 | type: NodeTypes.SIMPLE_EXPRESSION,
90 | content: "message",
91 | },
92 | }
93 | ]
94 | })
95 | });
96 |
97 | // edge case 2 处理没有结束标签的情况
98 | test('should throw error when lack end tag', () => {
99 | // baseParse("
");
100 | expect(() => {
101 | baseParse("
");
102 | }).toThrow(`缺少结束标签:span`);
103 | });
104 |
105 | });
106 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./runtime-dom";
2 | export * from "./reactivity";
3 |
--------------------------------------------------------------------------------
/src/reactivity/baseHandles.ts:
--------------------------------------------------------------------------------
1 | import { extend, isObject } from "../shared";
2 | import { track, trigger } from "./effect";
3 | import { reactive, ReactiveFlags, readonly } from "./reactive";
4 |
5 | const get = createdGetter();
6 | const set = createdSetter();
7 | const readonlyGet = createdGetter(true);
8 | const shallowReadonlyGet = createdGetter(true, true);
9 |
10 | function createdGetter(isReadonly = false, shallow = false) {
11 | return function get(target, key) {
12 | if (key === ReactiveFlags.IS_REACTIVE) {
13 | return !isReadonly;
14 | } else if (key === ReactiveFlags.IS_READONLY) {
15 | return isReadonly;
16 | }
17 |
18 | const res = Reflect.get(target, key);
19 |
20 | if (shallow) {
21 | return res;
22 | }
23 |
24 | // 对象的嵌套转换 -> 如果是普通对象就转换成reactive或者readonly对象
25 | if (isObject(res)) {
26 | return isReadonly ? readonly(res) : reactive(res);
27 | }
28 |
29 | if (!isReadonly) {
30 | // 依赖收集
31 | track(target, key);
32 | }
33 | return res;
34 | };
35 | }
36 |
37 | function createdSetter() {
38 | return function set(target, key, value) {
39 | const res = Reflect.set(target, key, value);
40 |
41 | // 触发依赖
42 | trigger(target, key);
43 | return res;
44 | };
45 | }
46 |
47 | export const mutableHanders = {
48 | get,
49 | set,
50 | };
51 |
52 | export const readonlyHanders = {
53 | get: readonlyGet,
54 | set(target, key) {
55 | console.warn(
56 | `key :"${String(key)}" set 失败,因为 target 是 readonly 类型`,
57 | target
58 | );
59 | return true;
60 | },
61 | };
62 |
63 | export const shallowReadonlyHanders = extend({}, readonlyHanders, {
64 | get: shallowReadonlyGet,
65 | });
66 |
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { reactiveEffect } from "./effect";
2 |
3 | class ComputedRefImpl {
4 | private _getter: any;
5 | private _dirty: boolean = true;
6 | private _value: any;
7 | private _effect: reactiveEffect;
8 | constructor(getter) {
9 | this._getter = getter;
10 | // effect & scheduler
11 | this._effect = new reactiveEffect(getter, () => {
12 | if (!this._dirty) {
13 | // 当依赖的响应式对象的值发生改变的时候 需要 _dirty = true,才能返回一个新的值
14 | this._dirty = true;
15 | }
16 | });
17 | }
18 |
19 | get value() {
20 | // get
21 | if (this._dirty) {
22 | // 怎么知道变化了,需要使用effect
23 | this._dirty = false;
24 | this._value = this._effect.run();
25 | }
26 | return this._value;
27 | }
28 | }
29 |
30 | export function computed(getter) {
31 | return new ComputedRefImpl(getter);
32 | }
33 |
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "../shared";
2 |
3 | let activeEffect;
4 | let shouldTrack;
5 |
6 | export class reactiveEffect {
7 | private _fn: any;
8 | public scheduler: Function | undefined;
9 | // stop
10 | deps = [];
11 | active = true;
12 | onStop?: () => void;
13 |
14 | constructor(fn, scheduler?: Function) {
15 | this._fn = fn;
16 | this.scheduler = scheduler;
17 | }
18 |
19 | run() {
20 | if (!this.active) {
21 | // runner方法需要得到fn的返回值
22 | return this._fn();
23 | }
24 |
25 | // 应该收集
26 | shouldTrack = true;
27 | activeEffect = this;
28 |
29 | const r = this._fn();
30 | // reset
31 | shouldTrack = false;
32 | return r;
33 | }
34 |
35 | stop() {
36 | if (this.active) {
37 | cleanupEffect(this);
38 | // stop时 调用onStop方法
39 | if (this.onStop) {
40 | this.onStop();
41 | }
42 | this.active = false;
43 | }
44 | }
45 | }
46 |
47 | function cleanupEffect(effect) {
48 | effect.deps.forEach((dep: any) => {
49 | dep.delete(effect);
50 | });
51 | effect.deps.length = 0;
52 | }
53 |
54 | const targetMap = new Map();
55 | export function track(target, key) {
56 | // target -> key -> dep
57 | // targetMap -> { target: depsMap }
58 | // depsMap -> {key: dep}
59 | // dep -> activeEffect
60 |
61 | if (!isTracking()) return; // 抽离-优化
62 |
63 | let depsMap = targetMap.get(target);
64 | if (!depsMap) {
65 | depsMap = new Map();
66 | targetMap.set(target, depsMap);
67 | }
68 |
69 | let dep = depsMap.get(key);
70 | if (!dep) {
71 | dep = new Set();
72 | depsMap.set(key, dep);
73 | }
74 | effectTracks(dep);
75 | }
76 |
77 | export function effectTracks(dep) {
78 | if (dep.has(activeEffect)) return; // 防止重复收集
79 | dep.add(activeEffect);
80 | // track的时候收集dep,stop会用到
81 | activeEffect.deps.push(dep);
82 | }
83 |
84 | export function isTracking() {
85 | // if (!activeEffect) return; //解决只用reactive时,deps undefined的情况
86 | // if (!shouldTrack) return; //解决stop后还会track的问题
87 | return shouldTrack && activeEffect !== undefined;
88 | }
89 |
90 | export function trigger(target, key) {
91 | const depsMap = targetMap.get(target);
92 | const dep = depsMap.get(key);
93 | effectTriggers(dep);
94 | }
95 |
96 | export function effectTriggers(dep) {
97 | for (const effect of dep) {
98 | if (effect.scheduler) {
99 | effect.scheduler();
100 | } else {
101 | effect.run();
102 | }
103 | }
104 | }
105 |
106 | export function effect(fn, options: any = {}) {
107 | const _effect = new reactiveEffect(fn, options.scheduler);
108 |
109 | _effect.run();
110 |
111 | // _effect.onStop = options.onStop; // 抽离-优化
112 | extend(_effect, options);
113 |
114 | // 返回的 runner函数
115 | // run方法用到了this,使用bind处理指针问题
116 | const runner: any = _effect.run.bind(_effect);
117 | // stop 要用到 _effect上面的方法
118 | runner.effect = _effect;
119 | return runner;
120 | }
121 |
122 | export function stop(runner) {
123 | runner.effect.stop();
124 | }
125 |
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
1 | export { ref, proxyRefs } from "./ref";
2 |
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "../shared";
2 | import {
3 | mutableHanders,
4 | readonlyHanders,
5 | shallowReadonlyHanders,
6 | } from "./baseHandles";
7 |
8 | export const enum ReactiveFlags {
9 | IS_REACTIVE = "__v_isReactive",
10 | IS_READONLY = "__v_isReadonly",
11 | }
12 |
13 | export function reactive(raw) {
14 | return createReactiveObject(raw, mutableHanders);
15 | }
16 |
17 | export function readonly(raw) {
18 | return createReactiveObject(raw, readonlyHanders);
19 | }
20 |
21 | export function isReactive(value) {
22 | return !!value[ReactiveFlags.IS_REACTIVE];
23 | }
24 |
25 | export function isReadonly(value) {
26 | return !!value[ReactiveFlags.IS_READONLY];
27 | }
28 |
29 | export function isProxy(value) {
30 | return isReactive(value) || isReadonly(value);
31 | }
32 |
33 | export function shallowReadonly(raw) {
34 | return createReactiveObject(raw, shallowReadonlyHanders);
35 | }
36 |
37 | function createReactiveObject(target, baseHandles) {
38 | if (!isObject(target)) {
39 | console.warn(`target: ${target} 必须是一个对象`);
40 | }
41 | return new Proxy(target, baseHandles);
42 | }
43 |
--------------------------------------------------------------------------------
/src/reactivity/ref.ts:
--------------------------------------------------------------------------------
1 | import { hasChanged, isObject } from "../shared";
2 | import { effectTracks, effectTriggers, isTracking } from "./effect";
3 | import { reactive } from "./reactive";
4 |
5 | // {} -> value -> get set
6 | class RefImpl {
7 | private _value: any;
8 | public dep: any;
9 | private _rawValue: any; // 备份原始值
10 | public __v_isRef = true;
11 | constructor(value) {
12 | this._rawValue = value;
13 | this._value = convert(value);
14 | // value 是对象的话需要转换成 reactive
15 | this.dep = new Set();
16 | }
17 | get value() {
18 | // 只用到 get value时,track里面的activeEffect.deps 为undefined,解决这个问题
19 | trackRefValue(this);
20 | return this._value;
21 | }
22 | set value(newValue) {
23 | // 新旧值不改变,就不执行
24 | // 使用_rawValue,因为这个值是没有被reactive处理过的,是一个普通obj
25 | // 如果传入的是对象,_value就是被处理过的 proxy对象,hasChanged只能传入普通对象做比较
26 | if (hasChanged(newValue, this._rawValue)) {
27 | // 要先修改value,再触发依赖
28 | this._rawValue = newValue;
29 | this._value = convert(newValue);
30 | effectTriggers(this.dep);
31 | }
32 | }
33 | }
34 |
35 | function trackRefValue(ref) {
36 | if (isTracking()) {
37 | effectTracks(ref.dep);
38 | }
39 | }
40 |
41 | // 如果传入 ref 的是一个对象,内部也会调用 reactive 方法进行深层响应转换
42 | function convert(value) {
43 | return isObject(value) ? reactive(value) : value;
44 | }
45 |
46 | export function ref(value) {
47 | return new RefImpl(value);
48 | }
49 |
50 | export function isRef(ref) {
51 | // 如果传入数值 1 这种参数,ref.__v_isRef会是undefined,所以用!!转换成Boolean
52 | return !!ref.__v_isRef;
53 | }
54 |
55 | export function unRef(ref) {
56 | return isRef(ref) ? ref.value : ref;
57 | }
58 |
59 | export function proxyRefs(objectWithRefs) {
60 | return new Proxy(objectWithRefs, {
61 | get(target, key) {
62 | return unRef(Reflect.get(target, key));
63 | },
64 | set(target, key, value) {
65 | if (isRef(target[key]) && !isRef(value)) {
66 | return (target[key].value = value);
67 | } else {
68 | return Reflect.set(target, key, value);
69 | }
70 | },
71 | });
72 | }
73 |
--------------------------------------------------------------------------------
/src/reactivity/tests/computed.spec.ts:
--------------------------------------------------------------------------------
1 | import { computed } from "../computed";
2 | import { reactive } from "../reactive";
3 |
4 | describe("computed", () => {
5 | it("happy path", () => {
6 | // 返回 用.value
7 | // 缓存
8 | const user = reactive({
9 | age: 1,
10 | });
11 | const age = computed(() => {
12 | return user.age;
13 | });
14 | expect(age.value).toBe(1);
15 | });
16 |
17 | it("should compute lazily", () => {
18 | const value = reactive({
19 | foo: 1,
20 | });
21 | const getter = jest.fn(() => {
22 | return value.foo;
23 | });
24 | const cValue = computed(getter);
25 |
26 | // lazy
27 | expect(getter).not.toHaveBeenCalled();
28 |
29 | expect(cValue.value).toBe(1);
30 | expect(getter).toHaveBeenCalledTimes(1);
31 |
32 | // should not compute again
33 | cValue.value;
34 | expect(getter).toHaveBeenCalledTimes(1);
35 | expect(cValue.value).toBe(1);
36 |
37 | // should not compute until needed
38 | value.foo = 2; // 触发trigger -> 此处需要用effect手动收集
39 | expect(getter).toHaveBeenCalledTimes(1); // 期望foo的值成功改变,还不允许getter被执行。所以在computed.ts中,用effect->scheduler完成
40 |
41 | // now should call computed
42 | expect(cValue.value).toBe(2);
43 | expect(getter).toHaveBeenCalledTimes(2);
44 |
45 | // should not compute again
46 | cValue.value;
47 | expect(getter).toHaveBeenCalledTimes(2); // 响应式值不改变,只用到了get,就不会调用函数,还是2次
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/src/reactivity/tests/effect.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect, stop } from "../effect";
2 | import { reactive } from "../reactive";
3 |
4 | describe("effect", () => {
5 | it("happy path", () => {
6 | const obj = reactive({ num: 10 });
7 | let addnum;
8 | effect(() => {
9 | addnum = obj.num + 1;
10 | });
11 | expect(addnum).toBe(11);
12 |
13 | // update
14 | obj.num++;
15 | expect(addnum).toBe(12);
16 | });
17 |
18 | it("runner", () => {
19 | // effect(fn) -> runner -> const r = runner() -> fn() & r
20 | let foo = 10;
21 | const runner = effect(() => {
22 | foo++;
23 | return "foo";
24 | });
25 | expect(foo).toBe(11);
26 | const r = runner();
27 | expect(foo).toBe(12);
28 | expect(r).toBe("foo");
29 | });
30 |
31 | it("scheduler", () => {
32 | // 1.给effect(fn)传入第二个参数 options -> { scheduler: fn2},也就是名为 scheduler 的一个函数
33 | // 2.effect 首次还会执行fn,不会执行fn2
34 | // 3.但如果scheduler存在,则响应式set updata 的时候不再执行fn而是执行fn2
35 | // 4.执行runner的时候会再次执行fn
36 | let dummy = 1;
37 | let run: any;
38 | const obj = reactive({ num: 1 });
39 | const scheduler = jest.fn(() => {
40 | run = runner;
41 | });
42 | const runner = effect(
43 | () => {
44 | dummy = obj.num;
45 | },
46 | { scheduler }
47 | );
48 | // first -> be called fn, not call scheduler
49 | expect(scheduler).not.toHaveBeenCalled();
50 | expect(dummy).toBe(1);
51 | // should be called on first trigger
52 | obj.num++;
53 | expect(scheduler).toHaveBeenCalledTimes(1);
54 | // not call fn
55 | expect(dummy).toBe(1);
56 | // manually run -> fn()
57 | run();
58 | expect(dummy).toBe(2);
59 | });
60 |
61 | it("stop", () => {
62 | let dummy;
63 | const obj = reactive({ num: 1 });
64 | const runner = effect(() => {
65 | dummy = obj.num;
66 | });
67 | expect(dummy).toBe(1);
68 | obj.num = 2;
69 | expect(dummy).toBe(2);
70 | stop(runner);
71 | // obj.num = 3;
72 | obj.num++; // -> obj.num = obj.num + 1;
73 | expect(dummy).toBe(2);
74 |
75 | // stopped effect should still be manually called callable
76 | runner();
77 | expect(dummy).toBe(3);
78 | });
79 |
80 | it("onStop", () => {
81 | // 执行 stop 方法后,会调用onStop函数,和 scheduler 相似
82 | const obj = reactive({ foo: 1 });
83 | const onStop = jest.fn();
84 | let dummy;
85 | const runner = effect(
86 | () => {
87 | dummy = obj.foo;
88 | },
89 | { onStop }
90 | );
91 |
92 | stop(runner);
93 | expect(onStop).toBeCalledTimes(1);
94 | });
95 | });
96 |
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { reactive, isReactive, isProxy } from "../reactive";
2 |
3 | describe("reactive", () => {
4 | it("happy path", () => {
5 | const original = { foo: 1 };
6 | const observed = reactive(original);
7 | expect(observed).not.toBe(original);
8 | expect(observed.foo).toBe(1);
9 |
10 | // isReactive
11 | expect(isReactive(observed)).toBe(true);
12 | expect(isReactive(original)).toBe(false);
13 |
14 | // isProxy
15 | expect(isProxy(observed)).toBe(true);
16 | });
17 | it("nested reactive", () => {
18 | const original = {
19 | obj: { foo: 1 },
20 | arr: [{ bar: 2 }],
21 | };
22 | const observed = reactive(original);
23 |
24 | expect(isReactive(observed.obj)).toBe(true);
25 | expect(isReactive(observed.arr)).toBe(true);
26 | expect(isReactive(observed.arr[0])).toBe(true);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isProxy, isReadonly, readonly } from "../reactive";
2 |
3 | describe("readonly", () => {
4 | it("happy path", () => {
5 | // not allow set
6 | const original = { foo: 1, bar: { baz: 1 } };
7 | const wrapped = readonly(original);
8 | expect(wrapped).not.toBe(original);
9 | expect(wrapped.foo).toBe(1);
10 |
11 | // isReadonly
12 | expect(isReadonly(wrapped)).toBe(true);
13 | expect(isReadonly(original)).toBe(false);
14 |
15 | // isProxy
16 | expect(isProxy(wrapped)).toBe(true);
17 |
18 | // 深层嵌套
19 | expect(isReadonly(wrapped.bar)).toBe(true);
20 | expect(isReadonly(original.bar)).toBe(false);
21 | });
22 |
23 | it("warn when call set", () => {
24 | const user = readonly({ age: 10 });
25 | // mock -> 验证 console.warn 有没有被调用
26 | console.warn = jest.fn();
27 | user.age = 11;
28 | expect(console.warn).toBeCalled();
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../effect";
2 | import { reactive } from "../reactive";
3 | import { isRef, proxyRefs, ref, unRef } from "../ref";
4 |
5 | describe("ref", () => {
6 | it("happy path", () => {
7 | const count = ref(1);
8 | expect(count.value).toBe(1);
9 | });
10 |
11 | it("shoule 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 | // same value should not 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 count = ref(1);
45 | const user = reactive({
46 | age: 1,
47 | });
48 | expect(isRef(count)).toBe(true);
49 | expect(isRef(user)).toBe(false);
50 | expect(isRef(1)).toBe(false);
51 | });
52 |
53 | it("unRef", () => {
54 | // unRef() -> ref.value
55 | const count = ref(1);
56 | expect(unRef(count)).toBe(1);
57 | expect(unRef(1)).toBe(1);
58 | });
59 |
60 | it("proxyRefs", () => {
61 | const user = {
62 | age: ref(10),
63 | name: "tom",
64 | };
65 |
66 | const proxyUser = proxyRefs(user);
67 |
68 | // get
69 | expect(user.age.value).toBe(10);
70 | expect(proxyUser.age).toBe(10);
71 | expect(proxyUser.name).toBe("tom");
72 |
73 | // set
74 | proxyUser.age = 20;
75 | expect(user.age.value).toBe(20);
76 | expect(proxyUser.age).toBe(20);
77 |
78 | // set
79 | proxyUser.age = ref(10);
80 | expect(user.age.value).toBe(10);
81 | expect(proxyUser.age).toBe(10);
82 | });
83 | });
84 |
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isReadonly, shallowReadonly } from "../reactive";
2 |
3 | describe("shallowReadonly", () => {
4 | it("should not make non-reactive properties reactive", () => {
5 | // not allow set
6 | const props = shallowReadonly({ n: { foo: 1 } });
7 | expect(isReadonly(props)).toBe(true);
8 | expect(isReadonly(props.n)).toBe(false);
9 | });
10 |
11 | it("warn when call set", () => {
12 | const user = shallowReadonly({ age: 10 });
13 | // mock -> 验证 console.warn 有没有被调用
14 | console.warn = jest.fn();
15 | user.age = 11;
16 | expect(console.warn).toBeCalled();
17 | });
18 | });
19 |
--------------------------------------------------------------------------------
/src/runtime-core/apiInject.ts:
--------------------------------------------------------------------------------
1 | import { getCurrentInstance } from "./component";
2 |
3 | export function provide(key, value) {
4 | // 存
5 | const currentInstance: any = getCurrentInstance();
6 | if (currentInstance) {
7 | let { provides } = currentInstance;
8 | const parentProvides = currentInstance.parent.provides;
9 | // 判断是初始化的时候才执行
10 | if (provides === parentProvides) {
11 | // 给 provides 指定原型链对象为 父级
12 | provides = currentInstance.provides = Object.create(parentProvides);
13 | }
14 | provides[key] = value;
15 | }
16 | }
17 |
18 | export function inject(key, defaultValue) {
19 | // 取
20 | const currentInstance: any = getCurrentInstance();
21 | if (currentInstance) {
22 | const parentProvides = currentInstance.parent.provides;
23 | if (key in parentProvides) {
24 | return parentProvides[key];
25 | } else if (defaultValue) {
26 | // 处理默认值 可能是字符串或函数
27 | if (typeof defaultValue === "function") {
28 | return defaultValue();
29 | }
30 | return defaultValue;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | import { proxyRefs } from "../reactivity";
2 | import { shallowReadonly } from "../reactivity/reactive";
3 | import { hasOwn } from "../shared";
4 | import { emit } from "./componentEmit";
5 | import { initProps } from "./componentProps";
6 | import { initSlots } from "./componentSlots";
7 |
8 | export function createComponentInstance(vnode, parent) {
9 | // console.log("parent", parent);
10 | const component = {
11 | vnode,
12 | type: vnode.type,
13 | setupState: {},
14 | props: {},
15 | slots: {}, // 就是vnode的children
16 | provides: parent ? parent.provides : {}, // 获取父级。ProviderTwo中再设置一个foo,会导致父级的foo发生变化,用原型链解决
17 | parent,
18 | isMounted: false,
19 | subTree: {},
20 | emit: () => {},
21 | update: null,
22 | next: null,
23 | };
24 |
25 | // 处理emit方法,需要event和instance两个参数,但用户只传一个 add
26 | // 如:emit('add')
27 | // 所以在这里使用bind处理这个问题
28 | component.emit = emit.bind(null, component) as any;
29 |
30 | return component;
31 | }
32 |
33 | export function setupComponent(instance) {
34 | /**
35 | * init
36 | */
37 | initProps(instance, instance.vnode.props);
38 | initSlots(instance, instance.vnode.children);
39 |
40 | setupStatefulComponent(instance);
41 | }
42 |
43 | function setupStatefulComponent(instance: any) {
44 | const Component = instance.type;
45 |
46 | // ctx
47 | instance.proxy = new Proxy(
48 | {},
49 | {
50 | get(target, key) {
51 | const { setupState, props } = instance;
52 |
53 | // if (key in setupState) {
54 | // return setupState[key];
55 | // }
56 | if (hasOwn(setupState, key)) {
57 | return setupState[key];
58 | } else if (hasOwn(props, key)) {
59 | // props
60 | return props[key];
61 | }
62 | // key -> $el
63 | if (key === "$el") {
64 | return instance.vnode.el;
65 | }
66 |
67 | // key -> $slot
68 | if (key === "$slot") {
69 | return instance.slots;
70 | }
71 |
72 | // key -> $props
73 | if (key === "$props") {
74 | return instance.props;
75 | }
76 | },
77 | }
78 | );
79 |
80 | // 拿到 setup 返回值
81 | const { setup } = Component;
82 |
83 | if (setup) {
84 | setCurrentInstance(instance);
85 |
86 | // 用户可能不写setup
87 | // setup() 可能返回 function 或者 object
88 | // 如果是function 就认为组件返回了render函数
89 | // 如果是object 会把object注入到组件上下文中
90 | const setupResult = setup(shallowReadonly(instance.props), {
91 | emit: instance.emit,
92 | });
93 |
94 | setCurrentInstance(null);
95 |
96 | handleSetupResult(instance, setupResult);
97 | }
98 | }
99 |
100 | function handleSetupResult(instance, setupResult: any) {
101 | // TODO function
102 |
103 | if (typeof setupResult === "object") {
104 | instance.setupState = proxyRefs(setupResult);
105 | }
106 |
107 | finishComponentSetup(instance);
108 | }
109 |
110 | function finishComponentSetup(instance: any) {
111 | const Component = instance.type;
112 |
113 | if (Component.render) {
114 | instance.render = Component.render;
115 | }
116 | }
117 |
118 | // getCurrentInstance Api
119 | let currentInstance = null;
120 | function setCurrentInstance(instance) {
121 | currentInstance = instance;
122 | }
123 | export function getCurrentInstance() {
124 | return currentInstance;
125 | }
126 |
--------------------------------------------------------------------------------
/src/runtime-core/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { camelize, toHandlerKey } from "../shared";
2 |
3 | export function emit(instance, event, ...args) {
4 | console.log("event--", event);
5 |
6 | // props里面找 emit 绑定的参数
7 | // emit('add') -> onAdd(){}
8 | const { props } = instance;
9 |
10 | /**
11 | * TPP
12 | * 特定行为 -> 通用行为
13 | */
14 |
15 | const handlerName = toHandlerKey(camelize(event));
16 |
17 | const handler = props[handlerName];
18 | handler && handler(...args);
19 | }
20 |
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance, rawProps) {
2 | instance.props = rawProps || {}; // shallowReadonly 时参数必须为对象,如果没有传props, rawProps === undefined 报错
3 | }
4 |
--------------------------------------------------------------------------------
/src/runtime-core/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | // TODO 重构 setupStatefulComponent() -> proxy
2 | export const PublicInstanceProxyHandlers = {};
3 |
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/ShapeFlags";
2 |
3 | export function initSlots(instance, children) {
4 | // 判断是slot的时候才执行函数,组件 && children 是 object 才是 slots
5 | const { shapeFlag } = instance.vnode;
6 | // if (typeof instance.type === "object" && typeof children === "object") {
7 | if (shapeFlag & ShapeFlags.SLOT_CHILDREN) {
8 | normalizeObjectSlots(instance, children);
9 | }
10 | }
11 |
12 | function normalizeObjectSlots(instance, children) {
13 | // children -> array
14 | // instance.slots = Array.isArray(children) ? children : [children];
15 |
16 | console.log(instance);
17 | // 具名插槽 -> children object
18 |
19 | const slots = {};
20 | for (const key in children) {
21 | const value = children[key];
22 |
23 | // slots[key] = normalizeSlotValue(value);
24 | // 作用域插槽 function
25 | slots[key] = (props) => normalizeSlotValue(value(props));
26 | }
27 |
28 | instance.slots = slots;
29 | }
30 |
31 | function normalizeSlotValue(value) {
32 | return Array.isArray(value) ? value : [value];
33 | }
34 |
--------------------------------------------------------------------------------
/src/runtime-core/componentUpdateUtils.ts:
--------------------------------------------------------------------------------
1 | export function shouldUpdateComponent(prevVNode, nextVNode) {
2 | const { props: prevProps } = prevVNode;
3 | const { props: nextProps } = nextVNode;
4 |
5 | for (const key in nextProps) {
6 | if (nextProps[key] !== prevProps[key]) {
7 | return true;
8 | }
9 | }
10 |
11 | return false;
12 | }
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | // import { render } from "./renderer";
2 | import { createVNode } from "./vnode";
3 |
4 | export function createAppAPI(render) {
5 | return function createApp(rootComponent) {
6 | return {
7 | mount(rootContainer) {
8 | /**
9 | * 先将内容解析成 vnode
10 | * component -> vnode
11 | * 后面的所有逻辑都会基于 vnode 进行操作
12 | */
13 |
14 | // rootContainer -> dom
15 | if (typeof rootContainer === "string") {
16 | rootContainer = document.querySelector(rootContainer);
17 | }
18 |
19 | const vnode = createVNode(rootComponent);
20 |
21 | render(vnode, rootContainer);
22 | },
23 | };
24 | };
25 | }
26 |
27 | // export function createApp(rootComponent) {
28 | // return {
29 | // mount(rootContainer) {
30 | // /**
31 | // * 先将内容解析成 vnode
32 | // * component -> vnode
33 | // * 后面的所有逻辑都会基于 vnode 进行操作
34 | // */
35 |
36 | // // rootContainer -> dom
37 | // if (typeof rootContainer === "string") {
38 | // rootContainer = document.querySelector(rootContainer);
39 | // }
40 |
41 | // const vnode = createVNode(rootComponent);
42 |
43 | // render(vnode, rootContainer);
44 | // },
45 | // };
46 | // }
47 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | export function h(type, props?, children?) {
4 | // childred -> string or array
5 | return createVNode(type, props, children);
6 | }
7 |
--------------------------------------------------------------------------------
/src/runtime-core/helpers/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import { createVNode, Fragment } from "../vnode";
2 |
3 | export function renderSlots(slots, name, props) {
4 | // 非具名 不传name
5 | // if (!name) {
6 | // return createVNode("div", {}, slots);
7 | // }
8 |
9 | // 具名插槽
10 | const slot = slots[name];
11 |
12 | if (slot) {
13 | if (typeof slot === "function") {
14 | // 处理作用域插槽
15 | return createVNode(Fragment, {}, slot(props));
16 | }
17 | // return createVNode("div", {}, slot);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 | // export { createApp } from "./createApp";
2 | export { h } from "./h";
3 | export { renderSlots } from "./helpers/renderSlots";
4 | export { createTextVnode } from "./vnode";
5 | export { getCurrentInstance } from "./component";
6 | export { provide, inject } from "./apiInject";
7 | export { createRenderer } from "./renderer";
8 | export { nextTick } from "./scheduler";
9 |
--------------------------------------------------------------------------------
/src/runtime-core/renderer.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../reactivity/effect";
2 | import { EMPTY_OBJ, isObject, isOn } from "../shared";
3 | import { ShapeFlags } from "../shared/ShapeFlags";
4 | import { createComponentInstance, setupComponent } from "./component";
5 | import { shouldUpdateComponent } from "./componentUpdateUtils";
6 | import { createAppAPI } from "./createApp";
7 | import { queueJobs } from "./scheduler";
8 | import { Fragment, Text } from "./vnode";
9 |
10 | export function createRenderer(options) {
11 | const {
12 | createElement: hostCreateElement,
13 | patchProp: hostPatchProp,
14 | insert: hostInsert,
15 | remove: hostRemove,
16 | setElementText: hostSetElementText,
17 | } = options;
18 |
19 | function render(vnode, container) {
20 | // patch
21 | // console.log("vnode-----", vnode);
22 | patch(null, vnode, container, null, null); // 处理根组件不传 parentComponent 参数
23 | }
24 |
25 | // 优化 patch 架构
26 | // n1 -> 老的,如果不存在则是初始化,存在就是更新逻辑
27 | // n2 -> 新的
28 | // function patch(vnode, container, parentComponent) {
29 | function patch(n1, n2, container, parentComponent, anchor) {
30 | /**
31 | * 区分是 element 还是 component
32 | * 判断两种类型
33 | */
34 | // debugger;
35 | // console.log("patch-------", vnode);
36 |
37 | // 使用 shapeFlag 判断类型
38 | const { type, shapeFlag } = n2;
39 |
40 | switch (type) {
41 | case Fragment:
42 | processFragment(n1, n2, container, parentComponent, anchor);
43 | break;
44 |
45 | case Text:
46 | processText(n1, n2, container);
47 | break;
48 |
49 | default:
50 | // shapeFlag & ShapeFlags.STATEFUL_COMPONENT 等同于 typeof vnode.type === "object"
51 | if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
52 | // 处理component
53 | processComponent(n1, n2, container, parentComponent, anchor);
54 | } else if (shapeFlag & ShapeFlags.ELEMENT) {
55 | // 处理 element
56 | processElement(n1, n2, container, parentComponent, anchor);
57 | }
58 | break;
59 | }
60 | }
61 |
62 | // element 类型
63 | function processElement(n1, n2: any, container: any, parentComponent, anchor) {
64 | if (!n1) {
65 | mountElement(n2, container, parentComponent, anchor);
66 | } else {
67 | patchElement(n1, n2, container, parentComponent, anchor);
68 | }
69 | }
70 |
71 | function patchElement(n1, n2, container, parentComponent, anchor) {
72 | // console.log("n1", n1);
73 | // console.log("n2", n2);
74 | // console.log("container", container);
75 |
76 | // update props
77 | const oldProps = n1.props || EMPTY_OBJ;
78 | const newProps = n2.props || EMPTY_OBJ;
79 | const el = n1.el;
80 | n2.el = el; // 本轮n2就是下一轮的n1,不赋值的话 下轮n1中就没有el
81 |
82 | patchChildren(n1, n2, el, parentComponent, anchor);
83 | patchProps(el, oldProps, newProps);
84 | }
85 |
86 | function patchChildren(n1, n2, container, parentComponent, anchor) {
87 | const prevShapeFlag = n1.shapeFlag;
88 | const nextShapeFlag = n2.shapeFlag;
89 | const c1 = n1.children;
90 | const c2 = n2.children;
91 |
92 | if (nextShapeFlag & ShapeFlags.TEXT_CHILDREN) {
93 | // TODO 优化
94 | if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
95 | // array -> text
96 | // 1. 把老的清空
97 | unmountChildren(n1.children);
98 | // 2. 设置text
99 | hostSetElementText(container, c2);
100 | } else {
101 | // text -> text
102 | // 前后节点不一样才需要改变
103 | if (c1 !== c2) {
104 | hostSetElementText(container, c2);
105 | }
106 | }
107 | } else {
108 | // text -> array
109 | if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
110 | hostSetElementText(container, "");
111 | mountChildren(c2, container, parentComponent, anchor);
112 | } else {
113 | // array diff children
114 | patchKeyedChildren(c1, c2, container, parentComponent, anchor)
115 | }
116 | }
117 | }
118 |
119 | function patchKeyedChildren(c1, c2, container, parentComponent, parentAnchor) {
120 |
121 | // const l2 = c2.length;
122 |
123 | let i = 0;
124 | let e1 = c1.length - 1;
125 | let e2 = c2.length - 1;
126 | // debugger
127 | function isSameVNodeType(n1, n2) {
128 | // type 和 key 判断两种
129 | return n1.type === n2.type && n1.key === n2.key;
130 | }
131 |
132 | // 1. 左侧对比 i指针->向右移动
133 | while (i <= e1 && i <= e2) {
134 | const n1 = c1[i];
135 | const n2 = c2[i];
136 |
137 | if (isSameVNodeType(n1, n2)) {
138 | patch(n1, n2, container, parentComponent, parentAnchor);
139 | } else {
140 | break;
141 | }
142 | i++;
143 | }
144 | // 2. 右侧对比 e1和e2指针->向左移动
145 | while (i <= e1 && i <= e2) {
146 | const n1 = c1[e1];
147 | const n2 = c2[e2];
148 |
149 | if (isSameVNodeType(n1, n2)) {
150 | patch(n1, n2, container, parentComponent, parentAnchor);
151 | } else {
152 | break;
153 | }
154 | e1--;
155 | e2--;
156 | }
157 |
158 | // 3. 新的比老的长 - 左侧和右侧 创建新的
159 | if (i > e1) {
160 | if (i <= e2) {
161 | // debugger;
162 | const nextPos = e2 + 1; // 锚点位置
163 | const anchor = nextPos < c2.length ? c2[nextPos].el : null; // 判断:左侧对比 -> null 还在后面插入节点 右侧对比 -> 找"A"节点传入 在"A"前面插入
164 | // 多个 child 遍历执行 patch
165 | while (i <= e2) {
166 | patch(null, c2[i], container, parentComponent, anchor);
167 | i++;
168 | }
169 | }
170 | }
171 | // 4. 老的比新的长 - 左侧和右侧 删除老的
172 | else if (i > e2) {
173 | while (i <= e1) {
174 | hostRemove(c1[i].el);
175 | i++;
176 | }
177 | }
178 | // 5. 中间对比
179 | else {
180 | let s1 = i;
181 | let s2 = i;
182 |
183 | /**
184 | * 删除的优化:
185 | * 新节点中 对比一次就记录一次;新节点全部对比完成后,如果老节点还有剩余元素的话
186 | * 就可以把这些全部删除
187 | */
188 | const toBePatched = e2 - s2 + 1; // 新的中需要对比的全部数量
189 | let patched = 0; // 已经对比完成的
190 |
191 | /**
192 | * 最长递增子序列
193 | * 移动
194 | * 建立映射表 定长数组
195 | */
196 | const newIndexToOldIndexMap = new Array(toBePatched);
197 | for (let i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0;
198 |
199 | /**
200 | * 移动 优化
201 | * 什么时候需要移动
202 | */
203 | let moved = false;
204 | let maxNewIndexSoFar = 0;
205 |
206 |
207 | // 基于新的 里面的 key 建立 映射表
208 | const keyToNewIndexMap = new Map()
209 | for (let i = s2; i <= e2; i++) {
210 | const nextChild = c2[i];
211 | keyToNewIndexMap.set(nextChild.key, i);
212 | }
213 |
214 | // 遍历老的
215 | for (let i = s1; i <= e1; i++) {
216 | // 拿到当前节点
217 | const prevChild = c1[i];
218 |
219 | if (patched > toBePatched) {
220 | hostRemove(prevChild.el);
221 | continue;
222 | }
223 |
224 | let newIndex;
225 | // 根据 null 和 undefined 判断用户写没写key
226 | if (prevChild.key != null) {
227 | newIndex = keyToNewIndexMap.get(prevChild.key);
228 | } else {
229 | // 如果没有 key 就需要遍历判断 性能低
230 | for (let j = s2; j <= e2; j++) {
231 | if (isSameVNodeType(prevChild, c2[j])) {
232 | newIndex = j;
233 | break;
234 | }
235 | }
236 | }
237 |
238 | // 如果newIndex存在,就说明 新老里面都有该节点
239 | // 不存在 就删除老的
240 | if (newIndex === undefined) {
241 | hostRemove(prevChild.el);
242 | } else {
243 | if (newIndex >= maxNewIndexSoFar) {
244 | maxNewIndexSoFar = newIndex;
245 | } else {
246 | moved = true;
247 | }
248 | /**
249 | * 为什么是i+1,而不是i?
250 | * 因为i 可能是0,而0在这里有特殊含义,因为初始化赋的值就是0,需要用0判断是否需要创建元素
251 | * 所以就赋值为i+1 避免这个问题
252 | */
253 | newIndexToOldIndexMap[newIndex - s2] = i + 1;
254 | patch(prevChild, c2[newIndex], container, parentComponent, null);
255 | patched++;
256 | }
257 | }
258 |
259 | // 生成最长递增子序列
260 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [];
261 | let j = increasingNewIndexSequence.length - 1; // 生成的子序列的下标
262 |
263 | // 从后往前遍历,因为 insertBefore插入元素需要在一个稳定元素的前面插入
264 | for (let i = toBePatched - 1; i >= 0; i--) {
265 | const nextIndex = i + s2;
266 | const nextChild = c2[nextIndex];
267 | const anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null;
268 |
269 | /**
270 | * 创建
271 | */
272 | if (newIndexToOldIndexMap[i] === 0) {
273 | // 在老的里面没有需要创建
274 | patch(null, nextChild, container, parentComponent, anchor);
275 | }
276 | /**
277 | * 移动
278 | */
279 | else if (moved) {
280 | if (j < 0 || i !== increasingNewIndexSequence[j]) {
281 | // 移动位置
282 | hostInsert(nextChild.el, container, anchor);
283 | } else {
284 | j--;
285 | }
286 | }
287 | }
288 | }
289 | }
290 |
291 | function unmountChildren(children) {
292 | for (let i = 0; i < children.length; i++) {
293 | const el = children[i].el;
294 | // remove
295 | hostRemove(el);
296 | }
297 | }
298 |
299 | function patchProps(el, oldProps, newProps) {
300 | if (oldProps !== newProps) {
301 | // 健壮性
302 | for (const key in newProps) {
303 | const prevProp = oldProps[key];
304 | const nextProp = newProps[key];
305 | if (prevProp !== nextProp) {
306 | hostPatchProp(el, key, prevProp, nextProp);
307 | }
308 | }
309 |
310 | if (oldProps !== EMPTY_OBJ) {
311 | // 健壮性
312 | // update props 的第三种情况:属性被删除
313 | for (const key in oldProps) {
314 | if (!(key in newProps)) {
315 | hostPatchProp(el, key, oldProps[key], null);
316 | }
317 | }
318 | }
319 | }
320 | }
321 |
322 | function mountElement(vnode, container, parentComponent, anchor) {
323 | const { type, children, props } = vnode;
324 |
325 | // 编写 api customRenderer,需要抽离document等web平台的相关函数
326 |
327 | // const el = document.createElement(type);
328 | const el = hostCreateElement(type);
329 |
330 | // $el
331 | // vnode -> element -> div
332 | vnode.el = el;
333 |
334 | // children -> string or array
335 | // 使用 shapeFlag 判断类型
336 | const { shapeFlag } = vnode;
337 | if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
338 | // children is string
339 | el.textContent = children;
340 | } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
341 | // children is array
342 | mountChildren(vnode.children, el, parentComponent, anchor); // 抽离-优化
343 | }
344 |
345 | // props
346 | for (const key in props) {
347 | const val = props[key];
348 | // console.log(key);
349 |
350 | // 编写 api customRenderer,需要抽离document等web平台的相关函数
351 |
352 | // if (isOn(key)) {
353 | // const eventName = key.slice(2).toLowerCase(); //onClick等,删除 on 变成小写
354 | // el.addEventListener(eventName, val);
355 | // } else {
356 | // el.setAttribute(key, val);
357 | // }
358 | hostPatchProp(el, key, null, val);
359 | }
360 |
361 | // 编写 api customRenderer,需要抽离document等web平台的相关函数
362 |
363 | // container.append(el);
364 | hostInsert(el, container, anchor);
365 | }
366 |
367 | function mountChildren(children: any, el: any, parentComponent, anchor) {
368 | children.forEach((v) => {
369 | patch(null, v, el, parentComponent, anchor);
370 | });
371 | }
372 |
373 | // component 类型
374 | function processComponent(n1, n2: any, container: any, parentComponent, anchor) {
375 | if (!n1) {
376 | mountComponent(n2, container, parentComponent, anchor);
377 | } else {
378 | updateComponent(n1, n2);
379 | }
380 | }
381 |
382 | function updateComponent(n1, n2) {
383 | const instance = (n2.component = n1.component);
384 | // 判断是否需要更新
385 | if (shouldUpdateComponent(n1, n2)) {
386 | // 新的vnode保存起来 下次要更新的
387 | instance.next = n2;
388 | instance.update();
389 | } else {
390 | n2.el = n1.el;
391 | instance.vnode = n2;
392 | }
393 |
394 | }
395 |
396 | function mountComponent(initialVNode: any, container, parentComponent, anchor) {
397 | // 创建组件实例
398 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent));
399 |
400 | setupComponent(instance);
401 |
402 | setupRenderEffect(instance, container, anchor);
403 | }
404 |
405 | function setupRenderEffect(instance: any, container, anchor) {
406 | // 使用effect包裹原来的逻辑,收集依赖
407 | instance.update = effect(() => {
408 | console.log("setupRenderEffect");
409 | if (!instance.isMounted) {
410 | // 未挂载就是 init 初始化
411 | const { proxy } = instance;
412 | const subTree = instance.render.call(proxy); // 第一次执行App.js根组件中的render函数,这个函数返回由h创建的vnode
413 |
414 | // 保存老的vnode prevSubTree
415 | instance.subTree = subTree;
416 |
417 | // console.log("--subTree", subTree);
418 |
419 | // vnode -> patch
420 | // vnode -> element -> mountElement
421 | patch(null, subTree, container, instance, anchor); // parentComponent -> instance
422 |
423 | // all element -> mount
424 | // $el根节点赋值到当前组件vnode的el上面
425 | instance.vnode.el = subTree.el;
426 |
427 | // init完成
428 | instance.isMounted = true;
429 | } else {
430 | // update
431 | // vnode:更新之前的 next:下次要更新的
432 | const { next, vnode } = instance;
433 | if (next) {
434 | next.el = vnode.el;
435 | updataComponentPreRender(instance, next);
436 | }
437 |
438 | const { proxy } = instance;
439 | const subTree = instance.render.call(proxy);
440 | const prevSubTree = instance.subTree;
441 | // 把老的更新,保证下次进入是正确的
442 | instance.subTree = subTree;
443 |
444 | patch(prevSubTree, subTree, container, instance, anchor);
445 | }
446 | },
447 | {
448 | scheduler() {
449 | console.log("update-scheduler");
450 | queueJobs(instance.update);
451 | }
452 | }
453 | );
454 | }
455 |
456 | // slot 的 Fragment 和 Text
457 | function processFragment(n1, n2: any, container: any, parentComponent, anchor) {
458 | mountChildren(n2.children, container, parentComponent, anchor);
459 | }
460 | function processText(n1, n2: any, container: any) {
461 | const { children } = n2;
462 | const textNode = (n2.el = document.createTextNode(children)); // 需要赋值给vnode的el
463 | container.append(textNode);
464 | }
465 |
466 | // 解决 createRenderer之后,createApp 无法再使用 render 的问题
467 | return {
468 | createApp: createAppAPI(render),
469 | };
470 | }
471 |
472 | function updataComponentPreRender(instance: any, nextVNode: any) {
473 | instance.vnode = nextVNode;
474 | instance.next = null;
475 | instance.props = nextVNode.props;
476 | }
477 |
478 | // 最长递增子序列算法
479 | function getSequence(arr) {
480 | const p = arr.slice();
481 | const result = [0];
482 | let i, j, u, v, c;
483 | const len = arr.length;
484 | for (i = 0; i < len; i++) {
485 | const arrI = arr[i];
486 | if (arrI !== 0) {
487 | j = result[result.length - 1];
488 | if (arr[j] < arrI) {
489 | p[i] = j;
490 | result.push(i);
491 | continue;
492 | }
493 | u = 0;
494 | v = result.length - 1;
495 | while (u < v) {
496 | c = (u + v) >> 1;
497 | if (arr[result[c]] < arrI) {
498 | u = c + 1;
499 | } else {
500 | v = c;
501 | }
502 | }
503 | if (arrI < arr[result[u]]) {
504 | if (u > 0) {
505 | p[i] = result[u - 1];
506 | }
507 | result[u] = i;
508 | }
509 | }
510 | }
511 | u = result.length;
512 | v = result[u - 1];
513 | while (u-- > 0) {
514 | result[u] = v;
515 | v = p[v];
516 | }
517 | return result;
518 | }
519 |
520 |
--------------------------------------------------------------------------------
/src/runtime-core/scheduler.ts:
--------------------------------------------------------------------------------
1 | const queue: any[] = [];
2 | let isFlushPending = false; // 优化每次都会创建promise的问题
3 |
4 | export function queueJobs(job) {
5 | if (!queue.includes(job)) {
6 | queue.push(job);
7 | }
8 |
9 | queueFlush();
10 | }
11 |
12 | export function nextTick(fn) {
13 | return fn ? Promise.resolve().then(fn) : Promise.resolve();
14 | }
15 |
16 | function queueFlush() {
17 | if (isFlushPending) return;
18 | isFlushPending = true;
19 | Promise.resolve().then(() => {
20 | isFlushPending = false;
21 | let job;
22 | while ((job = queue.shift())) {
23 | job && job();
24 | }
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/ShapeFlags";
2 |
3 | export const Fragment = Symbol("Fragment");
4 | export const Text = Symbol("Text");
5 |
6 | export function createVNode(type, props?, children?) {
7 | const vnode = {
8 | type,
9 | props,
10 | children,
11 | component: null,
12 | key: props && props.key,
13 | shapeFlag: getShapeFlag(type),
14 | el: null, // $el
15 | };
16 |
17 | // 重构优化 ShapeFlags
18 | // 判断children类型
19 | if (typeof children === "string") {
20 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
21 | } else if (Array.isArray(children)) {
22 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
23 | }
24 | // 判断 children 是 slot(是slot的条件: 组件 + children是对象)
25 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
26 | if (typeof children === "object") {
27 | vnode.shapeFlag |= ShapeFlags.SLOT_CHILDREN;
28 | }
29 | }
30 |
31 | return vnode;
32 | }
33 |
34 | // 重构优化 ShapeFlags
35 | // 判断type类型
36 | function getShapeFlag(type) {
37 | return typeof type === "string"
38 | ? ShapeFlags.ELEMENT
39 | : ShapeFlags.STATEFUL_COMPONENT;
40 | }
41 |
42 | export function createTextVnode(text) {
43 | return createVNode(Text, {}, text);
44 | }
45 |
--------------------------------------------------------------------------------
/src/runtime-dom/index.ts:
--------------------------------------------------------------------------------
1 | import { createRenderer } from "../runtime-core";
2 | import { isOn } from "../shared";
3 |
4 | function createElement(type) {
5 | console.log("createElement------------");
6 | return document.createElement(type);
7 | }
8 |
9 | /**
10 | * @param el
11 | * @param key
12 | * @param prevVal
13 | * @param val -> nextVal 当前的值
14 | */
15 | function patchProp(el, key, prevVal, val) {
16 | // console.log("patchProp------------");
17 | if (isOn(key)) {
18 | const eventName = key.slice(2).toLowerCase(); //onClick等,删除 on 变成小写
19 | el.addEventListener(eventName, val);
20 | } else {
21 | if (val === undefined || val === null) {
22 | el.removeAttribute(key);
23 | } else {
24 | el.setAttribute(key, val);
25 | }
26 | }
27 | }
28 |
29 | function insert(el, parent, anchor) {
30 | // console.log("insert------------");
31 | // parent.append(el);
32 | parent.insertBefore(el, anchor || null);
33 | }
34 |
35 | function remove(child) {
36 | const parent = child.parentNode;
37 | if (parent) {
38 | parent.removeChild(child);
39 | }
40 | }
41 | function setElementText(el, text) {
42 | el.textContent = text;
43 | }
44 |
45 | const renderer: any = createRenderer({
46 | createElement,
47 | patchProp,
48 | insert,
49 | remove,
50 | setElementText,
51 | });
52 |
53 | export function createApp(...args) {
54 | return renderer.createApp(...args);
55 | }
56 |
57 | export * from "../runtime-core";
58 |
--------------------------------------------------------------------------------
/src/shared/ShapeFlags.ts:
--------------------------------------------------------------------------------
1 | export const enum ShapeFlags {
2 | ELEMENT = 1, // 01 -> 0001 vnode.type->element类型
3 | STATEFUL_COMPONENT = 1 << 1, // 10 -> 0010 vnode.type->component类型
4 | TEXT_CHILDREN = 1 << 2, // 100 -> 0100 vnode.children->string类型
5 | ARRAY_CHILDREN = 1 << 3, // 1000 -> 1000 vnode.children->array类型
6 |
7 | SLOT_CHILDREN = 1 << 4, // 判断children是slot
8 | }
9 |
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | export const EMPTY_OBJ = {};
2 |
3 | export const extend = Object.assign;
4 |
5 | export function isObject(val) {
6 | return val !== null && typeof val === "object";
7 | }
8 |
9 | export const hasChanged = (value, newValue) => {
10 | return !Object.is(value, newValue);
11 | };
12 |
13 | // 事件注册
14 | export const isOn = (key) => /^on[A-Z]/.test(key);
15 |
16 | // props
17 | export const hasOwn = (val, key) =>
18 | Object.prototype.hasOwnProperty.call(val, key);
19 |
20 | // emit
21 | // 首字母大写 前面加on
22 | // add -> onAdd
23 | const capitalize = (str: string) => {
24 | return str.charAt(0).toUpperCase() + str.slice(1);
25 | };
26 |
27 | // kebab-case foo-add -> fooAdd
28 | export const camelize = (str: string) => {
29 | return str.replace(/-(\w)/g, (_, c: string) => {
30 | return c ? c.toUpperCase() : "";
31 | });
32 | };
33 |
34 | export const toHandlerKey = (str: string) => {
35 | return str ? "on" + capitalize(str) : "";
36 | };
37 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */
4 |
5 | /* Projects */
6 | // "incremental": true, /* Enable incremental compilation */
7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
12 |
13 | /* Language and Environment */
14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
15 | "lib": ["DOM", "es6", "ES2016"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
16 | // "jsx": "preserve", /* Specify what JSX code is generated. */
17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
25 |
26 | /* Modules */
27 | "module": "esnext", /* Specify what module code is generated. */
28 | // "rootDir": "./", /* Specify the root folder within your source files. */
29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
34 | "types": ["jest"], /* Specify type package names to be included without being referenced in a source file. */
35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
36 | // "resolveJsonModule": true, /* Enable importing .json files */
37 | // "noResolve": true, /* Disallow `import`s, `require`s or `
`s from expanding the number of files TypeScript should add to a project. */
38 |
39 | /* JavaScript Support */
40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
43 |
44 | /* Emit */
45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
50 | // "outDir": "./", /* Specify an output folder for all emitted files. */
51 | // "removeComments": true, /* Disable emitting comments. */
52 | // "noEmit": true, /* Disable emitting files from a compilation. */
53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
61 | // "newLine": "crlf", /* Set the newline character for emitting files. */
62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
68 |
69 | /* Interop Constraints */
70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
75 |
76 | /* Type Checking */
77 | "strict": true, /* Enable all strict type-checking options. */
78 | "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
96 |
97 | /* Completeness */
98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
100 | }
101 | }
102 |
--------------------------------------------------------------------------------