├── .gitignore
├── README.md
├── babel.config.js
├── example
├── componentEmit
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── componentSlot
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── currentInstance
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
└── helloworld
│ ├── App.js
│ ├── Foo.js
│ ├── index.html
│ └── main.js
├── lib
├── guide-mini-vue.cjs.js
└── guide-mini-vue.esm.js
├── package.json
├── rollup.config.js
├── src
├── index.ts
├── reactivity
│ ├── baseHandlers.ts
│ ├── computed.ts
│ ├── effect.ts
│ ├── index.ts
│ ├── reactive.ts
│ ├── ref.ts
│ └── tests
│ │ ├── computed.spec.ts
│ │ ├── effect.spec.ts
│ │ ├── reactive.spec.ts
│ │ ├── readonly.spec.ts
│ │ ├── ref.spec.ts
│ │ └── shallowReadonly.spec.ts
├── runtime-core
│ ├── component.ts
│ ├── componentEmit.ts
│ ├── componentProps.ts
│ ├── componentPublicInstance.ts
│ ├── componentSlots.ts
│ ├── createApp.ts
│ ├── h.ts
│ ├── helpers
│ │ └── renderSlots.ts
│ ├── index.ts
│ ├── renderer.ts
│ └── vnode.ts
└── shared
│ ├── ShapeFlags.ts
│ └── index.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | dist/
4 | distTest/
5 | distProd/
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 | package-lock.json
10 | tests/**/coverage/
11 | *.zip
12 | # Editor directories and files
13 | .idea
14 | .vscode
15 | *.suo
16 | *.ntvs*
17 | *.njsproj
18 | *.sln
19 | *.lock
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### 通过学习 vue3 源码 复现一个 mini-vue
2 |
3 | 学习过程基于 Jest 测试
4 |
5 | 1.响应式 (reactivity: reactive effect)
6 |
7 |
8 | ### 依赖
9 | 1. typescript
10 | 2. jest 测试环境
11 | 3. rollup 打包环境
12 | 1. @rollup/plugin-typescript rollup处理ts
13 | 2. tslib @rollup/plugin-typescript依赖包
14 |
15 | ### run run build
16 | 打包mini-vue到 `lib` 文件夹, 支持 esm&cjs 引入
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ["@babel/preset-env", { targets: { node: "current" } }],
4 | "@babel/preset-typescript",
5 | ],
6 | };
7 |
--------------------------------------------------------------------------------
/example/componentEmit/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 |
4 | export const App = {
5 | name: "App",
6 | render() {
7 | const self = this;
8 | return h("div", {}, [
9 | h("div", {}, "App"),
10 | h(Foo, {
11 | // on + Event
12 | onAdd(a, b) {
13 | // 3. 被触发了
14 | self.handleAdd(a, b);
15 | },
16 | // add-foo
17 | onAddFoo() {
18 | console.log("add-foo");
19 | },
20 | }),
21 | ]);
22 | },
23 | setup(props) {
24 | const handleAdd = (a, b) => {
25 | console.log("3. 父组件emit被触发了 , 参数为", a, b);
26 | };
27 | return {
28 | handleAdd,
29 | };
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/example/componentEmit/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | name: "Foo",
5 | // 1. setup内要多传入一个ctx对象 , 里面包含emit
6 | // 2. 调用emit时候 , 父组件会调用组件内的onEmit方法(绑定在父组件的props同位置)
7 | setup(props, ctx) {
8 | const emit = ctx.emit;
9 | const emitAdd = () => {
10 | console.log("1. 子组件准备开始触发emit");
11 | // 2. 触发
12 | emit("add", "a", "b");
13 | emit("add-foo");
14 | };
15 | return { emitAdd };
16 | },
17 | render() {
18 | const btn = h(
19 | "button",
20 | {
21 | onClick: this.emitAdd,
22 | },
23 | "emitAddContent"
24 | );
25 | const foo = h("div", {}, "我是foo");
26 |
27 | return h(
28 | "div",
29 | { onclick: this.emitAdd, style: "border : 1px solid red" },
30 | [btn, foo]
31 | );
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/example/componentEmit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/componentEmit/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | // vue3
5 | const rootContainer = document.getElementById("app");
6 | createApp(App).mount(rootContainer);
7 |
--------------------------------------------------------------------------------
/example/componentSlot/App.js:
--------------------------------------------------------------------------------
1 | import { h, createTextNode } from "../../lib/guide-mini-vue.esm.js";
2 | import Foo from "./Foo.js";
3 |
4 | export default {
5 | name: "App",
6 | render() {
7 | const app = h("div", {}, this.msg);
8 | // 1. 组件createApp 创建组件时候 (第三个参数就是传递给子组件的 slot 参数)
9 | // const foo = h(Foo, {}, [h("p", {}, "app传递给foo的内容") , h("p", {}, "app传递给foo的内容2")]);
10 | // 4. 转对象
11 | const foo = h(
12 | Foo,
13 | {},
14 | {
15 | // header: h("p", {}, "app传递给foo的内容,我是header"),
16 | // footer: h("p", {}, "app传递给foo的内容,我是footer"),
17 |
18 | // 5. 作用域插槽
19 | header: ({ data }) => [
20 | h(
21 | "p",
22 | {},
23 | "app传递给foo的内容,我是header , 接收到子组件给我的信息: " + data
24 | ),
25 | createTextNode("你好呀"),
26 | ],
27 | footer: ({ data }) =>
28 | h(
29 | "p",
30 | {},
31 | "app传递给foo的内容,我是footer , 接收到子组件给我的信息: " + data
32 | ),
33 | }
34 | );
35 | return h("div", {}, [app, foo]);
36 | },
37 | setup() {
38 | return {
39 | msg: "App-container",
40 | };
41 | },
42 | };
43 |
--------------------------------------------------------------------------------
/example/componentSlot/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, renderSlots } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export default {
4 | name: "Foo",
5 | setup() {
6 | return {
7 | fooMsg: "子msg",
8 | };
9 | },
10 | render() {
11 | const fooContainer = h("div", {}, "fooContainer");
12 |
13 | // 2. 拿个app传递的slot , $slots , 放到 Foo组件的children中
14 | console.log(this.$slots);
15 | // 3. this.$slots传递过来的是一个数组 那就要把vnode数组展开或者丢进一个新的h函数中进行处理
16 |
17 | // 4. 需要把$slots转成一个对象传递过来(需要把slot放到对应的位置) (具名插槽)
18 |
19 | // 5. 作用域插槽
20 | return h("div", { style: "border:1px solid red" }, [
21 | renderSlots(this.$slots, "header", {
22 | data: this.fooMsg,
23 | }),
24 | fooContainer,
25 | renderSlots(this.$slots, "footer", {
26 | data: this.fooMsg,
27 | }),
28 | ]);
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/example/componentSlot/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/example/componentSlot/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import App from "./App.js";
3 | createApp(App).mount(document.getElementById("app"));
4 |
--------------------------------------------------------------------------------
/example/currentInstance/App.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js";
2 | import Foo from "./Foo.js";
3 |
4 | export default {
5 | name: "app",
6 | setup(props, ctx) {
7 | const instance = getCurrentInstance();
8 | console.log(instance);
9 | },
10 | render() {
11 | return h("div", {}, [h("div", {}, "app"), h(Foo)]);
12 | },
13 | };
14 |
--------------------------------------------------------------------------------
/example/currentInstance/Foo.js:
--------------------------------------------------------------------------------
1 | import { h, getCurrentInstance } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export default {
4 | name: "foo",
5 | setup(props, ctx) {
6 | const instance = getCurrentInstance();
7 | console.log(instance);
8 | },
9 | render() {
10 | return h("div", {}, "foo");
11 | },
12 | };
13 |
--------------------------------------------------------------------------------
/example/currentInstance/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/example/currentInstance/main.js:
--------------------------------------------------------------------------------
1 | import App from "./App.js";
2 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
3 |
4 | createApp(App).mount(document.getElementById("app"));
5 |
--------------------------------------------------------------------------------
/example/helloworld/App.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 | import { Foo } from "./Foo.js";
3 | window.self = null;
4 | export const App = {
5 | // 先不做 template 模板编译
6 | name: "App",
7 | render() {
8 | window.self = this;
9 |
10 | // ui (tag , props , children / text)
11 | // 通过bind绑定 this 指向当前组件实例的proxy 访问proxy的属性和方法,被代理到instance的setupState\props等上面
12 | return h(
13 | "div",
14 | {
15 | id: "root",
16 | class: ["red", "small"],
17 | onClick: () => {
18 | console.log("click");
19 | },
20 | onMouseenter() {
21 | console.log("mouseenter");
22 | },
23 | },
24 | // 带this this.msg / this.$el / this.$data
25 | [h("div", {}, "hello" + this.msg), h(Foo, { count: this.msg })]
26 | // string
27 | // "hello wihtout this.msg"
28 | // array
29 | // [
30 | // h("p", { class: "red" }, "i am child1 without this.msg"),
31 | // h("p", { class: "blue" }, "i am child2"),
32 | // ]
33 | );
34 | },
35 |
36 | setup(props) {
37 | return {
38 | msg: "mini - vue - lwt",
39 | };
40 | },
41 | };
42 |
--------------------------------------------------------------------------------
/example/helloworld/Foo.js:
--------------------------------------------------------------------------------
1 | import { h } from "../../lib/guide-mini-vue.esm.js";
2 |
3 | export const Foo = {
4 | render() {
5 | // 1.通过 父组件h(Foo, { count: this.msg }) 的方式调用, 初始化Foo的props,绑定到instance.props上
6 | // 2.通过setup传入setup内,让其内部可以使用
7 | // 3.通过this.count可以直接访问this.$props.count
8 | return h(
9 | "div",
10 | {},
11 | `foo: ${this.$props.count} ;访问调用方式2 ${this.count}`
12 | );
13 | },
14 |
15 | setup(props) {
16 | // 4.不可以被修改 shallowReadonly
17 | props.count = '1243'
18 | console.log(props);
19 | },
20 | };
21 |
--------------------------------------------------------------------------------
/example/helloworld/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/example/helloworld/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from "../../lib/guide-mini-vue.esm.js";
2 | import { App } from "./App.js";
3 |
4 | // vue3
5 | const rootContainer = document.getElementById("app");
6 | createApp(App).mount(rootContainer);
7 |
--------------------------------------------------------------------------------
/lib/guide-mini-vue.cjs.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | Object.defineProperty(exports, '__esModule', { value: true });
4 |
5 | // Fragment -> 只渲染children
6 | var Fragment = Symbol("Fragment");
7 | var Text = Symbol("Text");
8 | function createVNode(type, props, children) {
9 | var vnode = {
10 | type: type,
11 | props: props,
12 | children: children,
13 | el: null,
14 | shapeFlag: getShapeFlag(type),
15 | };
16 | // 判断是children是数组还是text
17 | if (Array.isArray(children)) {
18 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
19 | }
20 | else if (typeof children === "string") {
21 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
22 | }
23 | // 判断children是不是slot (obj & 组件)
24 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
25 | if (typeof children === "object") {
26 | vnode.shapeFlag |= 16 /* SLOTS_CHILDREN */;
27 | }
28 | }
29 | return vnode;
30 | }
31 | function createTextNode(str) {
32 | return createVNode(Text, {}, str);
33 | }
34 | function getShapeFlag(type) {
35 | // 判断 vnode.type 是组件还是element元素
36 | return typeof type === "string"
37 | ? 1 /* ELEMENT */
38 | : 2 /* STATEFUL_COMPONENT */;
39 | }
40 |
41 | var extend = Object.assign;
42 | function isObject(val) {
43 | return val !== null && typeof val === "object";
44 | }
45 | var hasOwn = function (raw, key) { return raw.hasOwnProperty(key); };
46 | // add-foo --> addFoo
47 | var camelize = function (str) {
48 | return str.replace(/-(\w)/g, function (match, group1) {
49 | //str--> 'add-foo' , match ---> '-f' , group1 --(\w)--> 'f'
50 | return group1 ? group1.toUpperCase() : "";
51 | });
52 | };
53 | // event('add') --> Add
54 | var capitalize = function (str) {
55 | return str.charAt(0).toUpperCase() + str.slice(1);
56 | };
57 | // Add --> onAdd
58 | var toHandlerOn = function (event) {
59 | return "on" + capitalize(event);
60 | };
61 |
62 | // target --> key --->dep
63 | // 把依赖放到 对应的对象的属性映射表里面去
64 | var targetMap = new Map();
65 | // 通过target - key - 拿到要触发的依赖
66 | function trigger(target, key) {
67 | var depsMap = targetMap.get(target);
68 | if (!depsMap)
69 | return;
70 | var dep = depsMap.get(key);
71 | if (!dep)
72 | return;
73 | // 触发dep的所有依赖
74 | triggerEffect(dep);
75 | }
76 | function triggerEffect(dep) {
77 | for (var _i = 0, dep_1 = dep; _i < dep_1.length; _i++) {
78 | var effect_1 = dep_1[_i];
79 | if (effect_1.scheduler) {
80 | effect_1.scheduler();
81 | }
82 | else
83 | effect_1.run();
84 | }
85 | }
86 |
87 | var get = createGetter();
88 | var set = createSetter();
89 | var readonlyGet = createGetter(true);
90 | var shallowReadonlyGet = createGetter(true, true);
91 | function createGetter(isReadonly, isShallowReadonly) {
92 | if (isReadonly === void 0) { isReadonly = false; }
93 | if (isShallowReadonly === void 0) { isShallowReadonly = false; }
94 | return function get(target, key) {
95 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
96 | return !isReadonly;
97 | }
98 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
99 | return isReadonly;
100 | }
101 | var res = Reflect.get(target, key);
102 | if (isShallowReadonly) {
103 | return res;
104 | }
105 | if (isObject(res)) {
106 | return isReadonly ? readonly(res) : reactive(res);
107 | }
108 | return res;
109 | };
110 | }
111 | function createSetter() {
112 | return function set(target, key, value) {
113 | var res = Reflect.set(target, key, value);
114 | // todo 触发依赖
115 | trigger(target, key);
116 | return res;
117 | };
118 | }
119 | var multableHandlers = {
120 | get: get,
121 | set: set,
122 | };
123 | var readonlyHandlers = {
124 | get: readonlyGet,
125 | set: function (target, key, value) {
126 | console.warn(key + " \u4FEE\u6539set\u5931\u8D25, \u56E0\u4E3A target \u662F\u53EA\u8BFB\u7684readonly\u7C7B\u578B");
127 | return true;
128 | },
129 | };
130 | var shallowReadonlyHandlers = extend({}, readonlyHandlers, {
131 | get: shallowReadonlyGet,
132 | });
133 |
134 | function reactive(raw) {
135 | return createProxyObject(raw, multableHandlers);
136 | }
137 | function readonly(raw) {
138 | return createProxyObject(raw, readonlyHandlers);
139 | }
140 | function shallowReadonly(raw) {
141 | return createProxyObject(raw, shallowReadonlyHandlers);
142 | }
143 | // 生成 proxy
144 | function createProxyObject(raw, baseHandlers) {
145 | // 传入的raw必须是一个对象o
146 | if (!isObject(raw)) {
147 | console.warn("target " + raw + " \u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61\u7684");
148 | return raw;
149 | }
150 | return new Proxy(raw, baseHandlers);
151 | }
152 |
153 | /******************************************************************************
154 | Copyright (c) Microsoft Corporation.
155 |
156 | Permission to use, copy, modify, and/or distribute this software for any
157 | purpose with or without fee is hereby granted.
158 |
159 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
160 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
161 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
162 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
163 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
164 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
165 | PERFORMANCE OF THIS SOFTWARE.
166 | ***************************************************************************** */
167 |
168 | function __spreadArray(to, from, pack) {
169 | if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
170 | if (ar || !(i in from)) {
171 | if (!ar) ar = Array.prototype.slice.call(from, 0, i);
172 | ar[i] = from[i];
173 | }
174 | }
175 | return to.concat(ar || Array.prototype.slice.call(from));
176 | }
177 |
178 | function emit(instance, event) {
179 | var args = [];
180 | for (var _i = 2; _i < arguments.length; _i++) {
181 | args[_i - 2] = arguments[_i];
182 | }
183 | console.log("2. 调用到子组件的emit , 去寻找父组件传递的props里面有没有对应的函数 , 有的话就执行");
184 | // on Event 放在props里面, 所以要去拿的话要拿到instance.props
185 | // 那就要拿到insatnce 实例, 用户调用emit时候不可能传递给组件实例的,所以要在赋值emit给组件的时候
186 | // 就把实例bind到emit函数 上面
187 | var props = instance.props;
188 | // 去掉驼峰 加上on
189 | var handlerName = toHandlerOn(camelize(event));
190 | var handlder = props[handlerName];
191 | handlder && handlder.apply(void 0, __spreadArray([event], args, false));
192 | }
193 |
194 | function initProps(instance, rawProps) {
195 | // 把vnode上面的props传递给instance上面的props
196 | instance.props = rawProps || {};
197 | }
198 |
199 | var publicPropertiesMap = {
200 | // 当用户调用 instance.proxy.$emit 时就会触发这个函数
201 | // i 就是 instance 的缩写 也就是组件实例对象
202 | $el: function (i) { return i.vnode.el; },
203 | $emit: function (i) { return i.emit; },
204 | $slots: function (i) { return i.slots; },
205 | $props: function (i) { return i.props; },
206 | };
207 | var PubilcInstanceHandlers = {
208 | get: function (_a, key) {
209 | var instance = _a._;
210 | var setupState = instance.setupState, props = instance.props;
211 | // if (key in setupState) {
212 | // return setupState[key];
213 | // }
214 | // 判断对象内有没有这个key
215 | if (hasOwn(setupState, key)) {
216 | return setupState[key];
217 | }
218 | else if (hasOwn(props, key)) {
219 | return props[key];
220 | }
221 | // $el 等
222 | if (key in publicPropertiesMap) {
223 | return publicPropertiesMap[key](instance);
224 | }
225 | },
226 | };
227 |
228 | function initSlots(instance, children) {
229 | // 组件&slot children
230 | if (instance.vnode.shapeFlag & 16 /* SLOTS_CHILDREN */) {
231 | normalizeSlots(instance, children);
232 | }
233 | }
234 | function normalizeSlots(instance, children) {
235 | var _loop_1 = function (key) {
236 | if (Object.prototype.hasOwnProperty.call(children, key)) {
237 | var value_1 = children[key];
238 | instance.slots[key] = function (prop) { return normalizeChildren(value_1(prop)); };
239 | }
240 | };
241 | for (var key in children) {
242 | _loop_1(key);
243 | }
244 | }
245 | // 把 h(xx) -> [h(xx)] 转数组
246 | function normalizeChildren(value) {
247 | return Array.isArray(value) ? value : [value];
248 | }
249 |
250 | // 组件instance -> 通过 vnode创建, { vnode, type(vnode.type) , setupState , proxy , 组件的render函数 }
251 | function createComponentInstance(vnode) {
252 | // 初始化组件, 返回一个对象 , 内部包含
253 | var component = {
254 | vnode: vnode,
255 | type: vnode.type,
256 | // setup 返回值
257 | setupState: {},
258 | // props:
259 | props: {},
260 | // emit
261 | emit: function () { },
262 | // slots
263 | slots: {},
264 | };
265 | component.emit = emit.bind(null, component);
266 | return component;
267 | }
268 | function setupComponent(instance) {
269 | // initProps 把h函数(createVNode)传递过来的props 赋值给组件实例的props
270 | initProps(instance, instance.vnode.props);
271 | // initSlots
272 | initSlots(instance, instance.vnode.children);
273 | // initState (添加方法, 让组件实例处理调用setup之后的返回值) , 有状态的组件
274 | setupStatefulComponent(instance);
275 | }
276 | function setupStatefulComponent(instance) {
277 | // 调用setup 拿到setup之后的返回值 ,
278 | // instance 里面包含有type ,也就是当前的组件(App对象)
279 | var Component = instance.type;
280 | // 创建代理组件对象 , 将组件实例的数据挂载到这个代理对象上 , 调用render时候,会调用这个代理对象的方法
281 | instance.proxy = new Proxy({ _: instance }, PubilcInstanceHandlers);
282 | var setup = Component.setup;
283 | if (setup) {
284 | // 储存instance
285 | setCurrentInstance(instance);
286 | // setup可以返回一个 object 或者一个function , 再在setup里面被接收
287 | var setupResult = setup(shallowReadonly(instance.props), {
288 | // 传入emit给子组件, 让子组件可以调用父组件的emit
289 | emit: instance.emit,
290 | });
291 | // 清空instance
292 | setCurrentInstance(null);
293 | handleSetupResult(instance, setupResult);
294 | }
295 | }
296 | function handleSetupResult(instance, setupResult) {
297 | // setupResult 可能是一个object 或者一个function
298 | // 如果是一个function, 则调用这个function skipnow
299 | // setup(){cosnt count =ref(0); return ()=>{'div' , count.value}}
300 | // 如果是一个object, 则直接赋值给instance.state
301 | // setup(){return {msg:'mini-vue'}}
302 | if (typeof setupResult === "object") {
303 | instance.setupState = setupResult;
304 | }
305 | // 组件数据初始化结束,要判断是否有render方法
306 | finishComponentSetup(instance);
307 | }
308 | function finishComponentSetup(instance) {
309 | var Component = instance.type;
310 | if (Component.render) {
311 | instance.render = Component.render;
312 | }
313 | }
314 | // setup内部获取instance
315 | var currentInstance = null;
316 | function getCurrentInstance() {
317 | return currentInstance;
318 | }
319 | function setCurrentInstance(instance) {
320 | currentInstance = instance;
321 | }
322 |
323 | function render(vnode, container) {
324 | // patch
325 | patch(vnode, container);
326 | }
327 | function patch(vnode, container) {
328 | switch (vnode.type) {
329 | // 只处理 vnode.children
330 | case Fragment:
331 | processFragment(vnode, container);
332 | break;
333 | // 只处理 textNode
334 | case Text:
335 | processTextNode(vnode, container);
336 | break;
337 | default:
338 | // 处理vnode
339 | // 1. 判断vnode是否是一个真实的dom节点
340 | // console.log(vnode.type);
341 | if (vnode.shapeFlag & 1 /* ELEMENT */) {
342 | // 是string类型也就是,是普通的element类型的vnode,直接挂载到dom
343 | processElement(vnode, container);
344 | }
345 | else if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
346 | // 2. 判断vnode是否是一个组件, 如果是组件, 则调用processComponent方法
347 | processComponent(vnode, container);
348 | }
349 | break;
350 | }
351 | }
352 | // 处理Fragment
353 | function processFragment(vnode, container) {
354 | mountChildren(vnode.children, container);
355 | }
356 | // 处理存文本节点
357 | function processTextNode(vnode, container) {
358 | var el = (vnode.el = document.createTextNode(vnode.children));
359 | console.log(vnode);
360 | container.append(el);
361 | }
362 | function processElement(vnode, container) {
363 | // 初始化dom
364 | mountElement(vnode, container);
365 | }
366 | function mountElement(vnode, container) {
367 | var el = (vnode.el = document.createElement(vnode.type));
368 | vnode.type; var props = vnode.props, children = vnode.children;
369 | // 处理属性props
370 | for (var key in props) {
371 | if (Object.prototype.hasOwnProperty.call(props, key)) {
372 | var element = props[key];
373 | if (/^on[A-Z]/.test(key)) {
374 | var event_1 = key.slice(2).toLowerCase();
375 | el.addEventListener(event_1, element);
376 | }
377 | else {
378 | el.setAttribute(key, element);
379 | }
380 | }
381 | }
382 | // 处理children / string
383 | if (vnode.shapeFlag & 4 /* TEXT_CHILDREN */) {
384 | el.innerText = children;
385 | }
386 | else if (vnode.shapeFlag & 8 /* ARRAY_CHILDREN */) {
387 | // 同样每个子节点也是一个vnode , 去patch
388 | mountChildren(children, el);
389 | }
390 | container.append(el);
391 | }
392 | function mountChildren(vnode, container) {
393 | vnode.forEach(function (child) {
394 | patch(child, container);
395 | });
396 | }
397 | function processComponent(vnode, container) {
398 | // 分为初始化和更新
399 | // 1. 初始化组件 , 因为vnode的type为组件 , 所以需要对其进行一些数据的初始化 , 让其变成组件实例
400 | mountComponent(vnode, container);
401 | }
402 | function mountComponent(initialVNode, container) {
403 | // 创建组件实例
404 | var instance = createComponentInstance(initialVNode);
405 | // 往组件实例上挂载一些数据
406 | setupComponent(instance);
407 | // 看xmind图可以发现 组件初始化全部结束了
408 | // 应该去调用 组件的render函数了
409 | setupRenderEffect(instance, initialVNode, container);
410 | }
411 | function setupRenderEffect(instance, initialVNode, container) {
412 | //render() { return h("div", "Hello World , hi " + this.msg); }
413 | // 拿到下一个vnode
414 | // 拿到代理对象, this指向它, render里面的this.msg -> 指向proxy上面的msg -> 会去拿setupState.msg
415 | var proxy = instance.proxy;
416 | var subTree = instance.render.call(proxy);
417 | // vnode -> 再次patch
418 | // (如果是不是组件类型 是element类型那就直接挂载到dom了)
419 | patch(subTree, container);
420 | // 把element的el传递给组件实例
421 | initialVNode.el = subTree.el;
422 | }
423 |
424 | function createApp(rootComponent) {
425 | // 返回一个app对象,里面带有mount方法(初始化挂载)
426 | return {
427 | mount: function (rootContainer) {
428 | // 根组件(render) -> vnode -> dom ->挂载到rootContainer
429 | // 1. 根组件 -> vnode(type type可以是vue component也可以是div等标签, props, children)
430 | var vnode = createVNode(rootComponent);
431 | // 2. 内部调用patch方法 ,进行递归的处理
432 | render(vnode, rootContainer);
433 | }
434 | };
435 | }
436 |
437 | // h函数的作用和createVNode函数是一样的,只是h函数的参数不同
438 | function h(type, props, children) {
439 | return createVNode(type, props, children);
440 | }
441 |
442 | function renderSlots(slots, key, data) {
443 | // slots -> Array
444 | // return createVNode('div' , {} , slots)
445 | // slots -> obj
446 | var slot = slots[key];
447 | if (slot) {
448 | if (typeof slot === "function") {
449 | return createVNode(Fragment, {}, slot(data));
450 | }
451 | }
452 | }
453 |
454 | exports.createApp = createApp;
455 | exports.createTextNode = createTextNode;
456 | exports.getCurrentInstance = getCurrentInstance;
457 | exports.h = h;
458 | exports.renderSlots = renderSlots;
459 |
--------------------------------------------------------------------------------
/lib/guide-mini-vue.esm.js:
--------------------------------------------------------------------------------
1 | // Fragment -> 只渲染children
2 | var Fragment = Symbol("Fragment");
3 | var Text = Symbol("Text");
4 | function createVNode(type, props, children) {
5 | var vnode = {
6 | type: type,
7 | props: props,
8 | children: children,
9 | el: null,
10 | shapeFlag: getShapeFlag(type),
11 | };
12 | // 判断是children是数组还是text
13 | if (Array.isArray(children)) {
14 | vnode.shapeFlag |= 8 /* ARRAY_CHILDREN */;
15 | }
16 | else if (typeof children === "string") {
17 | vnode.shapeFlag |= 4 /* TEXT_CHILDREN */;
18 | }
19 | // 判断children是不是slot (obj & 组件)
20 | if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
21 | if (typeof children === "object") {
22 | vnode.shapeFlag |= 16 /* SLOTS_CHILDREN */;
23 | }
24 | }
25 | return vnode;
26 | }
27 | function createTextNode(str) {
28 | return createVNode(Text, {}, str);
29 | }
30 | function getShapeFlag(type) {
31 | // 判断 vnode.type 是组件还是element元素
32 | return typeof type === "string"
33 | ? 1 /* ELEMENT */
34 | : 2 /* STATEFUL_COMPONENT */;
35 | }
36 |
37 | var extend = Object.assign;
38 | function isObject(val) {
39 | return val !== null && typeof val === "object";
40 | }
41 | var hasOwn = function (raw, key) { return raw.hasOwnProperty(key); };
42 | // add-foo --> addFoo
43 | var camelize = function (str) {
44 | return str.replace(/-(\w)/g, function (match, group1) {
45 | //str--> 'add-foo' , match ---> '-f' , group1 --(\w)--> 'f'
46 | return group1 ? group1.toUpperCase() : "";
47 | });
48 | };
49 | // event('add') --> Add
50 | var capitalize = function (str) {
51 | return str.charAt(0).toUpperCase() + str.slice(1);
52 | };
53 | // Add --> onAdd
54 | var toHandlerOn = function (event) {
55 | return "on" + capitalize(event);
56 | };
57 |
58 | // target --> key --->dep
59 | // 把依赖放到 对应的对象的属性映射表里面去
60 | var targetMap = new Map();
61 | // 通过target - key - 拿到要触发的依赖
62 | function trigger(target, key) {
63 | var depsMap = targetMap.get(target);
64 | if (!depsMap)
65 | return;
66 | var dep = depsMap.get(key);
67 | if (!dep)
68 | return;
69 | // 触发dep的所有依赖
70 | triggerEffect(dep);
71 | }
72 | function triggerEffect(dep) {
73 | for (var _i = 0, dep_1 = dep; _i < dep_1.length; _i++) {
74 | var effect_1 = dep_1[_i];
75 | if (effect_1.scheduler) {
76 | effect_1.scheduler();
77 | }
78 | else
79 | effect_1.run();
80 | }
81 | }
82 |
83 | var get = createGetter();
84 | var set = createSetter();
85 | var readonlyGet = createGetter(true);
86 | var shallowReadonlyGet = createGetter(true, true);
87 | function createGetter(isReadonly, isShallowReadonly) {
88 | if (isReadonly === void 0) { isReadonly = false; }
89 | if (isShallowReadonly === void 0) { isShallowReadonly = false; }
90 | return function get(target, key) {
91 | if (key === "__v_isReactive" /* IS_REACTIVE */) {
92 | return !isReadonly;
93 | }
94 | else if (key === "__v_isReadonly" /* IS_READONLY */) {
95 | return isReadonly;
96 | }
97 | var res = Reflect.get(target, key);
98 | if (isShallowReadonly) {
99 | return res;
100 | }
101 | if (isObject(res)) {
102 | return isReadonly ? readonly(res) : reactive(res);
103 | }
104 | return res;
105 | };
106 | }
107 | function createSetter() {
108 | return function set(target, key, value) {
109 | var res = Reflect.set(target, key, value);
110 | // todo 触发依赖
111 | trigger(target, key);
112 | return res;
113 | };
114 | }
115 | var multableHandlers = {
116 | get: get,
117 | set: set,
118 | };
119 | var readonlyHandlers = {
120 | get: readonlyGet,
121 | set: function (target, key, value) {
122 | console.warn(key + " \u4FEE\u6539set\u5931\u8D25, \u56E0\u4E3A target \u662F\u53EA\u8BFB\u7684readonly\u7C7B\u578B");
123 | return true;
124 | },
125 | };
126 | var shallowReadonlyHandlers = extend({}, readonlyHandlers, {
127 | get: shallowReadonlyGet,
128 | });
129 |
130 | function reactive(raw) {
131 | return createProxyObject(raw, multableHandlers);
132 | }
133 | function readonly(raw) {
134 | return createProxyObject(raw, readonlyHandlers);
135 | }
136 | function shallowReadonly(raw) {
137 | return createProxyObject(raw, shallowReadonlyHandlers);
138 | }
139 | // 生成 proxy
140 | function createProxyObject(raw, baseHandlers) {
141 | // 传入的raw必须是一个对象o
142 | if (!isObject(raw)) {
143 | console.warn("target " + raw + " \u5FC5\u987B\u662F\u4E00\u4E2A\u5BF9\u8C61\u7684");
144 | return raw;
145 | }
146 | return new Proxy(raw, baseHandlers);
147 | }
148 |
149 | /******************************************************************************
150 | Copyright (c) Microsoft Corporation.
151 |
152 | Permission to use, copy, modify, and/or distribute this software for any
153 | purpose with or without fee is hereby granted.
154 |
155 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
156 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
157 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
158 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
159 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
160 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
161 | PERFORMANCE OF THIS SOFTWARE.
162 | ***************************************************************************** */
163 |
164 | function __spreadArray(to, from, pack) {
165 | if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
166 | if (ar || !(i in from)) {
167 | if (!ar) ar = Array.prototype.slice.call(from, 0, i);
168 | ar[i] = from[i];
169 | }
170 | }
171 | return to.concat(ar || Array.prototype.slice.call(from));
172 | }
173 |
174 | function emit(instance, event) {
175 | var args = [];
176 | for (var _i = 2; _i < arguments.length; _i++) {
177 | args[_i - 2] = arguments[_i];
178 | }
179 | console.log("2. 调用到子组件的emit , 去寻找父组件传递的props里面有没有对应的函数 , 有的话就执行");
180 | // on Event 放在props里面, 所以要去拿的话要拿到instance.props
181 | // 那就要拿到insatnce 实例, 用户调用emit时候不可能传递给组件实例的,所以要在赋值emit给组件的时候
182 | // 就把实例bind到emit函数 上面
183 | var props = instance.props;
184 | // 去掉驼峰 加上on
185 | var handlerName = toHandlerOn(camelize(event));
186 | var handlder = props[handlerName];
187 | handlder && handlder.apply(void 0, __spreadArray([event], args, false));
188 | }
189 |
190 | function initProps(instance, rawProps) {
191 | // 把vnode上面的props传递给instance上面的props
192 | instance.props = rawProps || {};
193 | }
194 |
195 | var publicPropertiesMap = {
196 | // 当用户调用 instance.proxy.$emit 时就会触发这个函数
197 | // i 就是 instance 的缩写 也就是组件实例对象
198 | $el: function (i) { return i.vnode.el; },
199 | $emit: function (i) { return i.emit; },
200 | $slots: function (i) { return i.slots; },
201 | $props: function (i) { return i.props; },
202 | };
203 | var PubilcInstanceHandlers = {
204 | get: function (_a, key) {
205 | var instance = _a._;
206 | var setupState = instance.setupState, props = instance.props;
207 | // if (key in setupState) {
208 | // return setupState[key];
209 | // }
210 | // 判断对象内有没有这个key
211 | if (hasOwn(setupState, key)) {
212 | return setupState[key];
213 | }
214 | else if (hasOwn(props, key)) {
215 | return props[key];
216 | }
217 | // $el 等
218 | if (key in publicPropertiesMap) {
219 | return publicPropertiesMap[key](instance);
220 | }
221 | },
222 | };
223 |
224 | function initSlots(instance, children) {
225 | // 组件&slot children
226 | if (instance.vnode.shapeFlag & 16 /* SLOTS_CHILDREN */) {
227 | normalizeSlots(instance, children);
228 | }
229 | }
230 | function normalizeSlots(instance, children) {
231 | var _loop_1 = function (key) {
232 | if (Object.prototype.hasOwnProperty.call(children, key)) {
233 | var value_1 = children[key];
234 | instance.slots[key] = function (prop) { return normalizeChildren(value_1(prop)); };
235 | }
236 | };
237 | for (var key in children) {
238 | _loop_1(key);
239 | }
240 | }
241 | // 把 h(xx) -> [h(xx)] 转数组
242 | function normalizeChildren(value) {
243 | return Array.isArray(value) ? value : [value];
244 | }
245 |
246 | // 组件instance -> 通过 vnode创建, { vnode, type(vnode.type) , setupState , proxy , 组件的render函数 }
247 | function createComponentInstance(vnode) {
248 | // 初始化组件, 返回一个对象 , 内部包含
249 | var component = {
250 | vnode: vnode,
251 | type: vnode.type,
252 | // setup 返回值
253 | setupState: {},
254 | // props:
255 | props: {},
256 | // emit
257 | emit: function () { },
258 | // slots
259 | slots: {},
260 | };
261 | component.emit = emit.bind(null, component);
262 | return component;
263 | }
264 | function setupComponent(instance) {
265 | // initProps 把h函数(createVNode)传递过来的props 赋值给组件实例的props
266 | initProps(instance, instance.vnode.props);
267 | // initSlots
268 | initSlots(instance, instance.vnode.children);
269 | // initState (添加方法, 让组件实例处理调用setup之后的返回值) , 有状态的组件
270 | setupStatefulComponent(instance);
271 | }
272 | function setupStatefulComponent(instance) {
273 | // 调用setup 拿到setup之后的返回值 ,
274 | // instance 里面包含有type ,也就是当前的组件(App对象)
275 | var Component = instance.type;
276 | // 创建代理组件对象 , 将组件实例的数据挂载到这个代理对象上 , 调用render时候,会调用这个代理对象的方法
277 | instance.proxy = new Proxy({ _: instance }, PubilcInstanceHandlers);
278 | var setup = Component.setup;
279 | if (setup) {
280 | // 储存instance
281 | setCurrentInstance(instance);
282 | // setup可以返回一个 object 或者一个function , 再在setup里面被接收
283 | var setupResult = setup(shallowReadonly(instance.props), {
284 | // 传入emit给子组件, 让子组件可以调用父组件的emit
285 | emit: instance.emit,
286 | });
287 | // 清空instance
288 | setCurrentInstance(null);
289 | handleSetupResult(instance, setupResult);
290 | }
291 | }
292 | function handleSetupResult(instance, setupResult) {
293 | // setupResult 可能是一个object 或者一个function
294 | // 如果是一个function, 则调用这个function skipnow
295 | // setup(){cosnt count =ref(0); return ()=>{'div' , count.value}}
296 | // 如果是一个object, 则直接赋值给instance.state
297 | // setup(){return {msg:'mini-vue'}}
298 | if (typeof setupResult === "object") {
299 | instance.setupState = setupResult;
300 | }
301 | // 组件数据初始化结束,要判断是否有render方法
302 | finishComponentSetup(instance);
303 | }
304 | function finishComponentSetup(instance) {
305 | var Component = instance.type;
306 | if (Component.render) {
307 | instance.render = Component.render;
308 | }
309 | }
310 | // setup内部获取instance
311 | var currentInstance = null;
312 | function getCurrentInstance() {
313 | return currentInstance;
314 | }
315 | function setCurrentInstance(instance) {
316 | currentInstance = instance;
317 | }
318 |
319 | function render(vnode, container) {
320 | // patch
321 | patch(vnode, container);
322 | }
323 | function patch(vnode, container) {
324 | switch (vnode.type) {
325 | // 只处理 vnode.children
326 | case Fragment:
327 | processFragment(vnode, container);
328 | break;
329 | // 只处理 textNode
330 | case Text:
331 | processTextNode(vnode, container);
332 | break;
333 | default:
334 | // 处理vnode
335 | // 1. 判断vnode是否是一个真实的dom节点
336 | // console.log(vnode.type);
337 | if (vnode.shapeFlag & 1 /* ELEMENT */) {
338 | // 是string类型也就是,是普通的element类型的vnode,直接挂载到dom
339 | processElement(vnode, container);
340 | }
341 | else if (vnode.shapeFlag & 2 /* STATEFUL_COMPONENT */) {
342 | // 2. 判断vnode是否是一个组件, 如果是组件, 则调用processComponent方法
343 | processComponent(vnode, container);
344 | }
345 | break;
346 | }
347 | }
348 | // 处理Fragment
349 | function processFragment(vnode, container) {
350 | mountChildren(vnode.children, container);
351 | }
352 | // 处理存文本节点
353 | function processTextNode(vnode, container) {
354 | var el = (vnode.el = document.createTextNode(vnode.children));
355 | console.log(vnode);
356 | container.append(el);
357 | }
358 | function processElement(vnode, container) {
359 | // 初始化dom
360 | mountElement(vnode, container);
361 | }
362 | function mountElement(vnode, container) {
363 | var el = (vnode.el = document.createElement(vnode.type));
364 | vnode.type; var props = vnode.props, children = vnode.children;
365 | // 处理属性props
366 | for (var key in props) {
367 | if (Object.prototype.hasOwnProperty.call(props, key)) {
368 | var element = props[key];
369 | if (/^on[A-Z]/.test(key)) {
370 | var event_1 = key.slice(2).toLowerCase();
371 | el.addEventListener(event_1, element);
372 | }
373 | else {
374 | el.setAttribute(key, element);
375 | }
376 | }
377 | }
378 | // 处理children / string
379 | if (vnode.shapeFlag & 4 /* TEXT_CHILDREN */) {
380 | el.innerText = children;
381 | }
382 | else if (vnode.shapeFlag & 8 /* ARRAY_CHILDREN */) {
383 | // 同样每个子节点也是一个vnode , 去patch
384 | mountChildren(children, el);
385 | }
386 | container.append(el);
387 | }
388 | function mountChildren(vnode, container) {
389 | vnode.forEach(function (child) {
390 | patch(child, container);
391 | });
392 | }
393 | function processComponent(vnode, container) {
394 | // 分为初始化和更新
395 | // 1. 初始化组件 , 因为vnode的type为组件 , 所以需要对其进行一些数据的初始化 , 让其变成组件实例
396 | mountComponent(vnode, container);
397 | }
398 | function mountComponent(initialVNode, container) {
399 | // 创建组件实例
400 | var instance = createComponentInstance(initialVNode);
401 | // 往组件实例上挂载一些数据
402 | setupComponent(instance);
403 | // 看xmind图可以发现 组件初始化全部结束了
404 | // 应该去调用 组件的render函数了
405 | setupRenderEffect(instance, initialVNode, container);
406 | }
407 | function setupRenderEffect(instance, initialVNode, container) {
408 | //render() { return h("div", "Hello World , hi " + this.msg); }
409 | // 拿到下一个vnode
410 | // 拿到代理对象, this指向它, render里面的this.msg -> 指向proxy上面的msg -> 会去拿setupState.msg
411 | var proxy = instance.proxy;
412 | var subTree = instance.render.call(proxy);
413 | // vnode -> 再次patch
414 | // (如果是不是组件类型 是element类型那就直接挂载到dom了)
415 | patch(subTree, container);
416 | // 把element的el传递给组件实例
417 | initialVNode.el = subTree.el;
418 | }
419 |
420 | function createApp(rootComponent) {
421 | // 返回一个app对象,里面带有mount方法(初始化挂载)
422 | return {
423 | mount: function (rootContainer) {
424 | // 根组件(render) -> vnode -> dom ->挂载到rootContainer
425 | // 1. 根组件 -> vnode(type type可以是vue component也可以是div等标签, props, children)
426 | var vnode = createVNode(rootComponent);
427 | // 2. 内部调用patch方法 ,进行递归的处理
428 | render(vnode, rootContainer);
429 | }
430 | };
431 | }
432 |
433 | // h函数的作用和createVNode函数是一样的,只是h函数的参数不同
434 | function h(type, props, children) {
435 | return createVNode(type, props, children);
436 | }
437 |
438 | function renderSlots(slots, key, data) {
439 | // slots -> Array
440 | // return createVNode('div' , {} , slots)
441 | // slots -> obj
442 | var slot = slots[key];
443 | if (slot) {
444 | if (typeof slot === "function") {
445 | return createVNode(Fragment, {}, slot(data));
446 | }
447 | }
448 | }
449 |
450 | export { createApp, createTextNode, getCurrentInstance, h, renderSlots };
451 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "2.mini-vue",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "./lib/guide-mini-vue.esm.js",
6 | "module": "./lib/guide-mini-vue.cjs.js",
7 | "scripts": {
8 | "test": "jest",
9 | "build": "rollup -c rollup.config.js"
10 | },
11 | "keywords": [],
12 | "author": "",
13 | "license": "ISC",
14 | "devDependencies": {
15 | "@babel/core": "^7.17.8",
16 | "@babel/preset-env": "^7.16.11",
17 | "@babel/preset-typescript": "^7.16.7",
18 | "@rollup/plugin-typescript": "^8.3.2",
19 | "@types/jest": "^27.4.1",
20 | "babel-jest": "^27.5.1",
21 | "jest": "^27.5.1",
22 | "rollup": "^2.70.2",
23 | "tslib": "^2.4.0",
24 | "typescript": "^4.4.3"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from "./package.json";
2 |
3 | import typescript from "@rollup/plugin-typescript";
4 | export default {
5 | input: "./src/index.ts",
6 | output: [
7 | // 1. cjs -> 打包成common js
8 | // 2. esm -> 打包成es module
9 | {
10 | format: "cjs",
11 | file: pkg.module,
12 | },
13 | {
14 | format: "es",
15 | file: pkg.main,
16 | },
17 | ],
18 | plugins: [typescript()],
19 | };
20 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // mini-vue 出口文件
2 |
3 | export * from "./runtime-core/index";
4 |
--------------------------------------------------------------------------------
/src/reactivity/baseHandlers.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "./../shared/index";
2 | import { isObject } from "../shared/index";
3 | import { track, trigger } from "./effect";
4 | import { reactive, ReactiveFlags, readonly } from "./reactive";
5 |
6 | const get = createGetter();
7 | const set = createSetter();
8 | const readonlyGet = createGetter(true);
9 | const shallowReadonlyGet = createGetter(true, true);
10 |
11 | function createGetter(isReadonly = false, isShallowReadonly = false) {
12 | return function get(target, key) {
13 | if (key === ReactiveFlags.IS_REACTIVE) {
14 | return !isReadonly;
15 | } else if (key === ReactiveFlags.IS_READONLY) {
16 | return isReadonly;
17 | }
18 |
19 | const res = Reflect.get(target, key);
20 |
21 | if (isShallowReadonly) {
22 | return res;
23 | }
24 |
25 | if (isObject(res)) {
26 | return isReadonly ? readonly(res) : reactive(res);
27 | }
28 |
29 | // todo 依赖收集
30 | if (!isReadonly) {
31 | track(target, key);
32 | }
33 | return res;
34 | };
35 | }
36 | function createSetter() {
37 | return function set(target, key, value) {
38 | const res = Reflect.set(target, key, value);
39 |
40 | // todo 触发依赖
41 | trigger(target, key);
42 |
43 | return res;
44 | };
45 | }
46 |
47 | export const multableHandlers = {
48 | get,
49 | set,
50 | };
51 | export const readonlyHandlers = {
52 | get: readonlyGet,
53 | set(target, key, value) {
54 | console.warn(`${key} 修改set失败, 因为 target 是只读的readonly类型`);
55 | return true;
56 | },
57 | };
58 | export const shallowReadonlyHandlers = extend({}, readonlyHandlers, {
59 | get: shallowReadonlyGet,
60 | });
61 |
--------------------------------------------------------------------------------
/src/reactivity/computed.ts:
--------------------------------------------------------------------------------
1 | import { ReactiveEffect } from "./effect";
2 |
3 |
4 | class ComputedImpl {
5 | private _getter: any;
6 | private _value: any;
7 | private _dirty: boolean = true;
8 | private _effect: any;
9 |
10 | constructor(getter) {
11 | this._getter = getter;
12 | // 创建 reactive类对应的 effect , 当reactive数据改变时候, 触发scheduler , 让computed不走缓存
13 | this._effect = new ReactiveEffect(getter, () => {
14 | this._dirty = true;
15 | });
16 | }
17 |
18 | get value() {
19 | // 当依赖 改变时候 dirty 为 true , 不走缓存
20 | if (this._dirty) {
21 | // 让 effect 执行 同时把当前的 effect 放到响应式对象的依赖里面
22 | this._value = this._effect.run();
23 | this._dirty = false;
24 | }
25 | return this._value;
26 | }
27 | }
28 |
29 | // 传入一个fn(getter) , 当依赖改变时候再触发fn
30 | // 同时具有缓存机制,访问get时候 当依赖没有变化时候,不会触发fn
31 | export function computed(getter) {
32 | return new ComputedImpl(getter);
33 | }
34 |
--------------------------------------------------------------------------------
/src/reactivity/effect.ts:
--------------------------------------------------------------------------------
1 | import { extend } from "../shared/index";
2 |
3 | export class ReactiveEffect {
4 | private _fn: any;
5 | deps = [];
6 | active = true;
7 | private onStop: any;
8 |
9 | constructor(fn, public scheduler) {
10 | this._fn = fn;
11 | this.scheduler = scheduler;
12 | }
13 |
14 | run() {
15 | // 执行 fn 收集依赖
16 | // 可以开始收集依赖了
17 | shouldTrack = true;
18 |
19 | // 执行的时候给全局的 activeEffect 赋值
20 | // 利用全局属性来获取当前的 effect
21 | activeEffect = this as any;
22 |
23 | let res = this._fn();
24 |
25 | // 重置
26 | shouldTrack = false;
27 | activeEffect = undefined;
28 |
29 | return res;
30 | }
31 | stop() {
32 | if (this.active) {
33 | cleanupEffect(this);
34 | if (this.onStop) {
35 | this.onStop();
36 | }
37 | this.active = false;
38 | }
39 | }
40 | }
41 |
42 | function cleanupEffect(effect: ReactiveEffect) {
43 | effect.deps.forEach((dep: any) => {
44 | dep.delete(effect);
45 | });
46 | effect.deps.length = 0;
47 | }
48 |
49 | // target --> key --->dep
50 | // 把依赖放到 对应的对象的属性映射表里面去
51 | const targetMap = new Map();
52 | export function track(target, key) {
53 | if (!isTracking()) return;
54 |
55 | let depsMap = targetMap.get(target);
56 | if (!depsMap) {
57 | depsMap = new Map();
58 | targetMap.set(target, depsMap);
59 | }
60 |
61 | let dep = depsMap.get(key);
62 | if (!dep) {
63 | dep = new Set();
64 | depsMap.set(key, dep);
65 | }
66 |
67 | // 收集依赖到dep( reavtive是通过 tagetMap.get(target).get(key) 来获取的 , ref 是通过ref.dep来获取的)
68 | trackEffects(dep);
69 | }
70 | export function trackEffects(dep) {
71 | if (!dep.has(activeEffect)) {
72 | dep.add(activeEffect);
73 | activeEffect.deps.push(dep);
74 | }
75 | }
76 | export function isTracking() {
77 | return shouldTrack && activeEffect !== undefined;
78 | }
79 |
80 | // 通过target - key - 拿到要触发的依赖
81 | export function trigger(target, key) {
82 | let depsMap = targetMap.get(target);
83 |
84 | if (!depsMap) return;
85 |
86 | let dep = depsMap.get(key);
87 | if (!dep) return;
88 |
89 | // 触发dep的所有依赖
90 | triggerEffect(dep);
91 | }
92 | export function triggerEffect(dep) {
93 | for (const effect of dep) {
94 | if (effect.scheduler) {
95 | effect.scheduler();
96 | } else effect.run();
97 | }
98 | }
99 |
100 | // 通过runner --> effect实例 --> deps依赖 --> 删除当前 effect
101 | export function stop(runner) {
102 | runner.effect.stop();
103 | }
104 |
105 | let activeEffect;
106 | let shouldTrack = false;
107 | export function effect(fn, options: any = {}) {
108 | let _effect = new ReactiveEffect(fn, options.scheduler);
109 |
110 | extend(_effect, options);
111 |
112 | // activeEffect = _effect;
113 | _effect.run();
114 | // activeEffect = null;
115 |
116 | const runner: any = _effect.run.bind(_effect);
117 | runner.effect = _effect;
118 | return runner;
119 | }
120 |
--------------------------------------------------------------------------------
/src/reactivity/index.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lwt09/mini-vue/3abd6ad90aa2c189764b4a671e9e6c8f815f360d/src/reactivity/index.ts
--------------------------------------------------------------------------------
/src/reactivity/reactive.ts:
--------------------------------------------------------------------------------
1 | import { isObject } from "../shared/index";
2 | import {
3 | multableHandlers,
4 | readonlyHandlers,
5 | shallowReadonlyHandlers,
6 | } from "./baseHandlers";
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 createProxyObject(raw, multableHandlers);
15 | }
16 | export function readonly(raw) {
17 | return createProxyObject(raw, readonlyHandlers);
18 | }
19 | export function shallowReadonly(raw) {
20 | return createProxyObject(raw, shallowReadonlyHandlers);
21 | }
22 |
23 | // 生成 proxy
24 | function createProxyObject(raw, baseHandlers) {
25 | // 传入的raw必须是一个对象o
26 | if (!isObject(raw)) {
27 | console.warn(`target ${raw} 必须是一个对象的`);
28 | return raw;
29 | }
30 |
31 | return new Proxy(raw, baseHandlers);
32 | }
33 |
34 | export function isReactive(value) {
35 | return !!(value && value[ReactiveFlags.IS_REACTIVE]);
36 | }
37 | export function isReadonly(value) {
38 | return !!(value && value[ReactiveFlags.IS_READONLY]);
39 | }
40 | export function isProxy(value) {
41 | return isReactive(value) || isReadonly(value);
42 | }
43 |
--------------------------------------------------------------------------------
/src/reactivity/ref.ts:
--------------------------------------------------------------------------------
1 | import { isChanged, isObject } from "../shared";
2 | import { isTracking, trackEffects, triggerEffect } from "./effect";
3 | import { reactive } from "./reactive";
4 |
5 | // ref和reactive 的区别在于 ref是一个可变的值,reactive是一个可变的对象
6 | // ref接受的参数是一个值,reactive接受的参数是一个对象
7 | // 所以reactive接受的参数可以被proxy监听到get 和 set
8 | // ref接受的参数不可以被proxy监听到get 和 set , 所以定义了一个类 , 来监听 ref.value的变化
9 |
10 | class RefImpl {
11 | private _value;
12 | public dep;
13 | private _rawValue: any;
14 | public __v_isRef = true;
15 |
16 | constructor(value) {
17 | this._rawValue = value;
18 | // value 不是对象的话,直接赋值,如果是对象的话,要用reactive包裹一下
19 | this._value = isObject(value) ? reactive(value) : value;
20 | this.dep = new Set();
21 | }
22 |
23 | get value() {
24 | if (isTracking()) trackEffects(this.dep);
25 |
26 | return this._value;
27 | }
28 | set value(newValue) {
29 | // 比较有没有变化, 有变化的话才trigger
30 | if (isChanged(newValue, this._rawValue)) {
31 | this._rawValue = newValue;
32 | this._value = isObject(newValue) ? reactive(newValue) : newValue;
33 | triggerEffect(this.dep);
34 | }
35 | }
36 | }
37 |
38 | export function ref(value) {
39 | return new RefImpl(value);
40 | }
41 |
42 | export function isRef(ref) {
43 | return !!ref.__v_isRef;
44 | }
45 | export function unRef(ref) {
46 | // 是ref对象的话拆包
47 | return isRef(ref) ? ref.value : ref;
48 | }
49 | export function proxyRefs(objectWithRefs) {
50 | return new Proxy(objectWithRefs, {
51 | get(target, key) {
52 | return unRef(Reflect.get(target, key));
53 | },
54 | set(target, key, value) {
55 | if (isRef(target[key]) && !isRef(value)) {
56 | return (target[key].value = value);
57 | } else {
58 | return Reflect.set(target, key, value);
59 | }
60 | },
61 | });
62 | }
63 |
--------------------------------------------------------------------------------
/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 | const user = reactive({
7 | age: 1,
8 | });
9 | const age = computed(() => user.age);
10 | // expect(age.value).toBe(1);
11 | user.age = 2;
12 | expect(age.value).toBe(2);
13 | });
14 |
15 | it("should compute lazily", () => {
16 | const value = reactive({ foo: 1 });
17 | const getter = jest.fn(() => value.foo);
18 |
19 | const cvalue = computed(getter);
20 |
21 | expect(getter).not.toHaveBeenCalled();
22 |
23 | expect(cvalue.value).toBe(1);
24 | expect(getter).toHaveBeenCalledTimes(1);
25 |
26 | // 计算属性缓存
27 | // 再次访问 不重复执行getter
28 | expect(cvalue.value).toBe(1);
29 | expect(getter).toHaveBeenCalledTimes(1);
30 |
31 | // 变更值 , 当时不调用getter , 再次访问时候才调用
32 | value.foo = 2;
33 | expect(getter).toHaveBeenCalledTimes(1);
34 |
35 | // 再次访问
36 | expect(cvalue.value).toBe(2);
37 | expect(getter).toHaveBeenCalledTimes(2);
38 |
39 | // should not compute again
40 | cvalue.value;
41 | expect(getter).toHaveBeenCalledTimes(2);
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/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 user = reactive({
7 | age: 10,
8 | number: 1,
9 | });
10 | let nextAge;
11 |
12 | // 触发user proxy 的get,同时将fn作为依赖收集到user 的依赖当中去
13 | effect(() => {
14 | nextAge = user.age + 1;
15 | });
16 |
17 | expect(nextAge).toBe(11);
18 |
19 | // update
20 | // user.number++; // bug待解决 activeEffect的影响没有被清除,在别的属性调用get的时候又触发了
21 | user.age++;
22 | expect(nextAge).toBe(12);
23 | });
24 |
25 | it("effect runner", () => {
26 | // effect(fn) --> 返回一个函数 称作runner 调用runner --->返回fn -->调用fn再返回fn的return值
27 | let temp = 1;
28 | let runner = effect(() => {
29 | temp += 10;
30 | return "我是fn的返回值";
31 | });
32 | expect(temp).toBe(11);
33 | let res = runner();
34 | expect(temp).toBe(21);
35 | expect(res).toBe("我是fn的返回值");
36 | });
37 |
38 | it("effect set activeEffect into null", () => {
39 | const obj = reactive({
40 | age: 1,
41 | number: 100,
42 | });
43 | let temp = 0;
44 | effect(() => {
45 | temp += obj.age;
46 | });
47 | expect(temp).toBe(1);
48 |
49 | obj.age++;
50 | expect(temp).toBe(3);
51 | obj.number++;
52 | expect(temp).toBe(3);
53 | effect(() => {
54 | temp += obj.number;
55 | });
56 | expect(temp).toBe(104);
57 | obj.number++;
58 | expect(temp).toBe(206);
59 | });
60 |
61 | it("scheduler", () => {
62 | // 1. 通过 effect 的第二个参数 传入一个 scheduler 的fn
63 | // 2. effect初始化时候 , 还是会执行 effect 的fn
64 | // 3. 当响应式对象的 set 被触发的时候 , 不会再执行 effect 的fn , 而是执行scheduler的fn
65 | // 4. 执行当前effect的runner时候, 会再次执行fn
66 |
67 | let dummy;
68 | let run: any;
69 |
70 | const scheduler = jest.fn(() => {
71 | run = runner;
72 | return "我是scheduler的返回值";
73 | });
74 |
75 | const obj = reactive({
76 | foo: 1,
77 | });
78 |
79 | const runner = effect(
80 | () => {
81 | dummy = obj.foo;
82 | },
83 | { scheduler }
84 | );
85 |
86 | expect(scheduler).not.toHaveBeenCalled();
87 | expect(dummy).toBe(1);
88 |
89 | expect(scheduler).not.toHaveBeenCalled();
90 | expect(dummy).toBe(1);
91 | // should be called on first trigger
92 | obj.foo++;
93 | expect(scheduler).toHaveBeenCalledTimes(1);
94 | // // should not run yet
95 | expect(dummy).toBe(1);
96 | // // manually run
97 | run();
98 | // // should have run
99 | expect(dummy).toBe(2);
100 |
101 | obj.foo++;
102 | runner();
103 | expect(dummy).toBe(3);
104 | });
105 |
106 | it("stop", () => {
107 | let dummy;
108 | const obj = reactive({ foo: 1 });
109 | const runner = effect(() => {
110 | dummy = obj.foo;
111 | });
112 |
113 | obj.foo++;
114 | expect(dummy).toBe(2);
115 |
116 | // stop 之后 , 不会再执行 effect 的fn
117 | // stop内传入 dep , 将dep.deps中的dep移除
118 | stop(runner);
119 | obj.foo++;
120 | expect(dummy).toBe(2);
121 |
122 | runner();
123 | expect(dummy).toBe(3);
124 | });
125 |
126 | it("onStop", () => {
127 | // stop 时候 , onStop 会被调用一次
128 | let dummy;
129 | const obj = reactive({ foo: 1 });
130 | const onStop = jest.fn();
131 | const runner = effect(
132 | () => {
133 | dummy = obj.foo;
134 | },
135 | {
136 | onStop,
137 | }
138 | );
139 |
140 | stop(runner);
141 | expect(onStop).toHaveBeenCalledTimes(1);
142 | });
143 |
144 | it("nested effect", () => {
145 | const user = reactive({
146 | info: {
147 | age: 1,
148 | },
149 | });
150 |
151 | let currentAge;
152 | effect(() => {
153 | currentAge = user.info.age;
154 | });
155 |
156 | expect(currentAge).toBe(1);
157 | user.info.age++;
158 | expect(currentAge).toBe(2);
159 | });
160 | });
161 |
--------------------------------------------------------------------------------
/src/reactivity/tests/reactive.spec.ts:
--------------------------------------------------------------------------------
1 | import { isProxy, isReactive, reactive } from "../reactive";
2 |
3 | describe("reactive", () => {
4 | it("happy path", () => {
5 | const original = { foo: 1 };
6 | const observed = reactive(original);
7 |
8 | expect(original).not.toBe(observed);
9 |
10 | expect(observed.foo).toBe(1);
11 |
12 | // isReactive
13 | expect(isReactive(observed)).toBe(true);
14 | expect(isReactive(original)).toBe(false);
15 |
16 | // isProxy
17 | expect(isProxy(observed)).toBe(true);
18 | });
19 |
20 | it("nested reactive", () => {
21 | // 深层嵌套 reactive
22 | const original = {
23 | foo: {
24 | bar: 1,
25 | },
26 | array: [{ bar: 2 }],
27 | };
28 | const observed = reactive(original);
29 | expect(isReactive(observed.foo)).toBe(true);
30 | expect(isReactive(observed.array)).toBe(true);
31 | expect(isReactive(observed.array[0])).toBe(true);
32 | });
33 | });
34 |
--------------------------------------------------------------------------------
/src/reactivity/tests/readonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { isProxy, isReadonly, reactive, readonly } from "../reactive";
2 |
3 | describe("happy path readonly", () => {
4 | it("happy path", () => {
5 | // 定义一个代理对象
6 | const original = { foo: 1, bar: { baz: 2 } };
7 | const observed = readonly(original);
8 |
9 | expect(original).not.toBe(observed);
10 | expect(isReadonly(observed)).toBe(true);
11 | expect(isReadonly(original)).toBe(false);
12 | expect(isReadonly(observed.bar)).toBe(true);
13 | expect(isReadonly(original.bar)).toBe(false);
14 |
15 | expect(observed.foo).toBe(1);
16 |
17 | expect(isReadonly(observed)).toBe(true);
18 |
19 | expect(isProxy(observed)).toBe(true);
20 | });
21 |
22 | it("warn then call set ", () => {
23 | // not set
24 | console.warn = jest.fn();
25 |
26 | const user = readonly({ age: 10 });
27 | user.age++;
28 |
29 | expect(console.warn).toHaveBeenCalled();
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/reactivity/tests/ref.spec.ts:
--------------------------------------------------------------------------------
1 | import { isRef, proxyRefs, ref, unRef } from "../ref";
2 | import { effect } from "../effect";
3 | import { reactive } from "../reactive";
4 |
5 | describe("ref", function () {
6 | it("happy path ref", function () {
7 | const a = ref(1);
8 | expect(a.value).toBe(1);
9 | });
10 |
11 | it("it should be reactive", function () {
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 |
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 | const dummy = reactive({
46 | age: 1,
47 | });
48 | expect(isRef(a)).toBe(true);
49 | expect(isRef(1)).toBe(false);
50 | expect(isRef(dummy)).toBe(false);
51 | });
52 |
53 | it("unRef", () => {
54 | const a = ref(1);
55 | expect(unRef(a)).toBe(1);
56 | expect(unRef(1)).toBe(1);
57 | });
58 |
59 | it("proxyRefs", () => {
60 | const user = {
61 | age: ref(10),
62 | name: "xiaohong",
63 | };
64 | const proxyUser = proxyRefs(user);
65 | expect(user.age.value).toBe(10);
66 | expect(proxyUser.age).toBe(10);
67 | expect(proxyUser.name).toBe("xiaohong");
68 |
69 | proxyUser.age = 20;
70 | expect(proxyUser.age).toBe(20);
71 | expect(user.age.value).toBe(20);
72 |
73 | proxyUser.age = ref(10);
74 | expect(proxyUser.age).toBe(10);
75 | expect(user.age.value).toBe(10);
76 | });
77 | });
78 |
--------------------------------------------------------------------------------
/src/reactivity/tests/shallowReadonly.spec.ts:
--------------------------------------------------------------------------------
1 | import { effect } from "../effect";
2 | import { isProxy, isReadonly, reactive, readonly, shallowReadonly } from "../reactive";
3 |
4 | describe("shalloReadonly", () => {
5 | it("shalloReadonly", () => {
6 | // 外层响应式对象 内层不响应式对象
7 | const dummy = shallowReadonly({
8 | foo: 1,
9 | bar: { baz: 2 },
10 | });
11 |
12 | expect(dummy.foo).toBe(1);
13 | expect(isReadonly(dummy)).toBe(true);
14 | expect(isReadonly(dummy.bar)).toBe(false);
15 |
16 | expect(isProxy(dummy)).toBe(true);
17 | });
18 |
19 | it("warn then call set ", () => {
20 | // not set
21 | console.warn = jest.fn();
22 |
23 | const user = shallowReadonly({ age: 10 });
24 | user.age++;
25 |
26 | expect(console.warn).toHaveBeenCalled();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/runtime-core/component.ts:
--------------------------------------------------------------------------------
1 | // 组件instance -> 通过 vnode创建, { vnode, type(vnode.type) , setupState , proxy , 组件的render函数 }
2 |
3 | import { shallowReadonly } from "../reactivity/reactive";
4 | import { emit } from "./componentEmit";
5 | import { initProps } from "./componentProps";
6 | import { PubilcInstanceHandlers } from "./componentPublicInstance";
7 | import { initSlots } from "./componentSlots";
8 |
9 | export function createComponentInstance(vnode) {
10 | // 初始化组件, 返回一个对象 , 内部包含
11 | const component = {
12 | vnode,
13 | type: vnode.type,
14 | // setup 返回值
15 | setupState: {},
16 | // props:
17 | props: {},
18 |
19 | // emit
20 | emit: () => {},
21 | // slots
22 | slots: {},
23 | };
24 |
25 | component.emit = emit.bind(null, component) as any;
26 |
27 | return component;
28 | }
29 |
30 | export function setupComponent(instance) {
31 | // initProps 把h函数(createVNode)传递过来的props 赋值给组件实例的props
32 | initProps(instance, instance.vnode.props);
33 | // initSlots
34 | initSlots(instance, instance.vnode.children);
35 | // initState (添加方法, 让组件实例处理调用setup之后的返回值) , 有状态的组件
36 | setupStatefulComponent(instance);
37 | }
38 |
39 | function setupStatefulComponent(instance: any) {
40 | // 调用setup 拿到setup之后的返回值 ,
41 | // instance 里面包含有type ,也就是当前的组件(App对象)
42 | const Component = instance.type;
43 |
44 | // 创建代理组件对象 , 将组件实例的数据挂载到这个代理对象上 , 调用render时候,会调用这个代理对象的方法
45 | instance.proxy = new Proxy({ _: instance }, PubilcInstanceHandlers);
46 |
47 | const { setup } = Component;
48 | if (setup) {
49 | // 储存instance
50 | setCurrentInstance(instance);
51 |
52 | // setup可以返回一个 object 或者一个function , 再在setup里面被接收
53 | const setupResult = setup(shallowReadonly(instance.props), {
54 | // 传入emit给子组件, 让子组件可以调用父组件的emit
55 | emit: instance.emit,
56 | });
57 |
58 | // 清空instance
59 | setCurrentInstance(null);
60 |
61 | handleSetupResult(instance, setupResult);
62 | }
63 | }
64 | function handleSetupResult(instance: any, setupResult: any) {
65 | // setupResult 可能是一个object 或者一个function
66 | // 如果是一个function, 则调用这个function skipnow
67 | // setup(){cosnt count =ref(0); return ()=>{'div' , count.value}}
68 |
69 | // 如果是一个object, 则直接赋值给instance.state
70 | // setup(){return {msg:'mini-vue'}}
71 | if (typeof setupResult === "object") {
72 | instance.setupState = setupResult;
73 | }
74 |
75 | // 组件数据初始化结束,要判断是否有render方法
76 | finishComponentSetup(instance);
77 | }
78 | function finishComponentSetup(instance: any) {
79 | const Component = instance.type;
80 | if (Component.render) {
81 | instance.render = Component.render;
82 | }
83 | }
84 |
85 | // setup内部获取instance
86 | let currentInstance = null;
87 | export function getCurrentInstance() {
88 | return currentInstance;
89 | }
90 | function setCurrentInstance(instance) {
91 | currentInstance = instance;
92 | }
93 |
--------------------------------------------------------------------------------
/src/runtime-core/componentEmit.ts:
--------------------------------------------------------------------------------
1 | import { camelize, toHandlerOn } from "../shared/index";
2 |
3 | export function emit(instance, event, ...args) {
4 | console.log(
5 | "2. 调用到子组件的emit , 去寻找父组件传递的props里面有没有对应的函数 , 有的话就执行"
6 | );
7 |
8 | // on Event 放在props里面, 所以要去拿的话要拿到instance.props
9 | // 那就要拿到insatnce 实例, 用户调用emit时候不可能传递给组件实例的,所以要在赋值emit给组件的时候
10 | // 就把实例bind到emit函数 上面
11 | const { props } = instance;
12 |
13 | // 去掉驼峰 加上on
14 | const handlerName = toHandlerOn(camelize(event));
15 | const handlder = props[handlerName];
16 | handlder && handlder(event, ...args);
17 | }
18 |
--------------------------------------------------------------------------------
/src/runtime-core/componentProps.ts:
--------------------------------------------------------------------------------
1 | export function initProps(instance, rawProps) {
2 | // 把vnode上面的props传递给instance上面的props
3 | instance.props = rawProps || {};
4 | }
5 |
--------------------------------------------------------------------------------
/src/runtime-core/componentPublicInstance.ts:
--------------------------------------------------------------------------------
1 | import { hasOwn } from "../shared/index";
2 |
3 | const publicPropertiesMap = {
4 | // 当用户调用 instance.proxy.$emit 时就会触发这个函数
5 | // i 就是 instance 的缩写 也就是组件实例对象
6 | $el: (i) => i.vnode.el,
7 | $emit: (i) => i.emit,
8 | $slots: (i) => i.slots,
9 | $props: (i) => i.props,
10 | };
11 |
12 | export const PubilcInstanceHandlers = {
13 | get({ _: instance }, key) {
14 | const { setupState, props } = instance;
15 | // if (key in setupState) {
16 | // return setupState[key];
17 | // }
18 |
19 | // 判断对象内有没有这个key
20 | if (hasOwn(setupState, key)) {
21 | return setupState[key];
22 | } else if (hasOwn(props, key)) {
23 | return props[key];
24 | }
25 |
26 | // $el 等
27 | if (key in publicPropertiesMap) {
28 | return publicPropertiesMap[key](instance);
29 | }
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/src/runtime-core/componentSlots.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/ShapeFlags";
2 |
3 | export function initSlots(instance, children) {
4 | // 组件&slot children
5 | if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
6 | normalizeSlots(instance, children);
7 | }
8 | }
9 |
10 | function normalizeSlots(instance, children) {
11 | for (const key in children) {
12 | if (Object.prototype.hasOwnProperty.call(children, key)) {
13 | const value = children[key];
14 | instance.slots[key] = (prop) => normalizeChildren(value(prop));
15 | }
16 | }
17 | }
18 |
19 | // 把 h(xx) -> [h(xx)] 转数组
20 | function normalizeChildren(value) {
21 | return Array.isArray(value) ? value : [value];
22 | }
23 |
--------------------------------------------------------------------------------
/src/runtime-core/createApp.ts:
--------------------------------------------------------------------------------
1 | import { render } from "./renderer"
2 | import { createVNode } from "./vnode"
3 |
4 | export function createApp(rootComponent){
5 | // 返回一个app对象,里面带有mount方法(初始化挂载)
6 | return {
7 | mount(rootContainer){
8 | // 根组件(render) -> vnode -> dom ->挂载到rootContainer
9 |
10 | // 1. 根组件 -> vnode(type type可以是vue component也可以是div等标签, props, children)
11 | const vnode = createVNode(rootComponent)
12 |
13 | // 2. 内部调用patch方法 ,进行递归的处理
14 | render(vnode, rootContainer)
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/runtime-core/h.ts:
--------------------------------------------------------------------------------
1 | import { createVNode } from "./vnode";
2 |
3 | // h函数的作用和createVNode函数是一样的,只是h函数的参数不同
4 | export function h(type, props?, children?) {
5 | return createVNode(type, props, children);
6 | }
7 |
--------------------------------------------------------------------------------
/src/runtime-core/helpers/renderSlots.ts:
--------------------------------------------------------------------------------
1 | import { Fragment } from "./../vnode";
2 | import { createVNode } from "../vnode";
3 |
4 | export function renderSlots(slots, key, data) {
5 | // slots -> Array
6 | // return createVNode('div' , {} , slots)
7 |
8 | // slots -> obj
9 | let slot = slots[key];
10 | if (slot) {
11 | if (typeof slot === "function") {
12 | return createVNode(Fragment, {}, slot(data));
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/runtime-core/index.ts:
--------------------------------------------------------------------------------
1 | export { createApp } from "./createApp";
2 | export { h } from "./h";
3 | export { renderSlots } from "./helpers/renderSlots";
4 | export { createTextNode } from "./vnode";
5 | export { getCurrentInstance } from "./component";
6 |
--------------------------------------------------------------------------------
/src/runtime-core/renderer.ts:
--------------------------------------------------------------------------------
1 | import { Fragment, Text } from "./vnode";
2 | import { ShapeFlags } from "../shared/ShapeFlags";
3 | import { createComponentInstance, setupComponent } from "./component";
4 |
5 | export function render(vnode, container) {
6 | // patch
7 | patch(vnode, container);
8 | }
9 |
10 | function patch(vnode, container) {
11 | switch (vnode.type) {
12 | // 只处理 vnode.children
13 | case Fragment:
14 | processFragment(vnode, container);
15 | break;
16 | // 只处理 textNode
17 | case Text:
18 | processTextNode(vnode, container);
19 | break;
20 |
21 | default:
22 | // 处理vnode
23 | // 1. 判断vnode是否是一个真实的dom节点
24 | // console.log(vnode.type);
25 | if (vnode.shapeFlag & ShapeFlags.ELEMENT) {
26 | // 是string类型也就是,是普通的element类型的vnode,直接挂载到dom
27 | processElement(vnode, container);
28 | } else if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
29 | // 2. 判断vnode是否是一个组件, 如果是组件, 则调用processComponent方法
30 | processComponent(vnode, container);
31 | }
32 | break;
33 | }
34 | }
35 |
36 | // 处理Fragment
37 | function processFragment(vnode, container) {
38 | mountChildren(vnode.children, container);
39 | }
40 | // 处理存文本节点
41 | function processTextNode(vnode, container) {
42 | const el = (vnode.el = document.createTextNode(vnode.children));
43 | console.log(vnode)
44 | container.append(el);
45 | }
46 |
47 | function processElement(vnode: any, container: any) {
48 | // 初始化dom
49 | mountElement(vnode, container);
50 | }
51 | function mountElement(vnode, container) {
52 | const el = (vnode.el = document.createElement(vnode.type));
53 | const { type, props, children } = vnode;
54 |
55 | // 处理属性props
56 | for (const key in props) {
57 | if (Object.prototype.hasOwnProperty.call(props, key)) {
58 | const element = props[key];
59 | if (/^on[A-Z]/.test(key)) {
60 | const event = key.slice(2).toLowerCase();
61 | el.addEventListener(event, element);
62 | } else {
63 | el.setAttribute(key, element);
64 | }
65 | }
66 | }
67 |
68 | // 处理children / string
69 | if (vnode.shapeFlag & ShapeFlags.TEXT_CHILDREN) {
70 | el.innerText = children;
71 | } else if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
72 | // 同样每个子节点也是一个vnode , 去patch
73 | mountChildren(children, el);
74 | }
75 | container.append(el);
76 | }
77 | function mountChildren(vnode, container) {
78 | vnode.forEach((child) => {
79 | patch(child, container);
80 | });
81 | }
82 |
83 | function processComponent(vnode: any, container: any) {
84 | // 分为初始化和更新
85 | // 1. 初始化组件 , 因为vnode的type为组件 , 所以需要对其进行一些数据的初始化 , 让其变成组件实例
86 | mountComponent(vnode, container);
87 | }
88 |
89 | function mountComponent(initialVNode: any, container: any) {
90 | // 创建组件实例
91 | const instance = createComponentInstance(initialVNode);
92 | // 往组件实例上挂载一些数据
93 | setupComponent(instance);
94 |
95 | // 看xmind图可以发现 组件初始化全部结束了
96 | // 应该去调用 组件的render函数了
97 | setupRenderEffect(instance, initialVNode, container);
98 | }
99 | function setupRenderEffect(instance: any, initialVNode, container: any) {
100 | //render() { return h("div", "Hello World , hi " + this.msg); }
101 | // 拿到下一个vnode
102 | // 拿到代理对象, this指向它, render里面的this.msg -> 指向proxy上面的msg -> 会去拿setupState.msg
103 | const proxy = instance.proxy;
104 | const subTree = instance.render.call(proxy);
105 |
106 | // vnode -> 再次patch
107 | // (如果是不是组件类型 是element类型那就直接挂载到dom了)
108 | patch(subTree, container);
109 |
110 | // 把element的el传递给组件实例
111 | initialVNode.el = subTree.el;
112 | }
113 |
--------------------------------------------------------------------------------
/src/runtime-core/vnode.ts:
--------------------------------------------------------------------------------
1 | import { ShapeFlags } from "../shared/ShapeFlags";
2 |
3 | // Fragment -> 只渲染children
4 | export const Fragment = Symbol("Fragment");
5 | export const Text = Symbol("Text");
6 |
7 | export function createVNode(type, props?, children?) {
8 | const vnode = {
9 | type,
10 | props,
11 | children,
12 | el: null,
13 | shapeFlag: getShapeFlag(type),
14 | };
15 |
16 | // 判断是children是数组还是text
17 | if (Array.isArray(children)) {
18 | vnode.shapeFlag |= ShapeFlags.ARRAY_CHILDREN;
19 | } else if (typeof children === "string") {
20 | vnode.shapeFlag |= ShapeFlags.TEXT_CHILDREN;
21 | }
22 |
23 | // 判断children是不是slot (obj & 组件)
24 | if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
25 | if (typeof children === "object") {
26 | vnode.shapeFlag |= ShapeFlags.SLOTS_CHILDREN;
27 | }
28 | }
29 |
30 | return vnode;
31 | }
32 |
33 | export function createTextNode(str: string) {
34 | return createVNode(Text, {}, str);
35 | }
36 |
37 | function getShapeFlag(type: any) {
38 | // 判断 vnode.type 是组件还是element元素
39 | return typeof type === "string"
40 | ? ShapeFlags.ELEMENT
41 | : ShapeFlags.STATEFUL_COMPONENT;
42 | }
43 |
--------------------------------------------------------------------------------
/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 | SLOTS_CHILDREN = 1 << 4, //10000
7 | }
8 |
9 | // 二进制情况下 0100 | 0000 = 0100
10 | // 二进制情况下 0100 & 0011 = 0000
11 | // 通过这个来 设置vnode 的shapeflag 通过获取shapeflag
12 |
--------------------------------------------------------------------------------
/src/shared/index.ts:
--------------------------------------------------------------------------------
1 | export const extend = Object.assign;
2 |
3 | export function isObject(val) {
4 | return val !== null && typeof val === "object";
5 | }
6 |
7 | export function isChanged(oldVal, newVal) {
8 | return Object.is(oldVal, newVal) ? false : true;
9 | }
10 |
11 | export const hasOwn = (raw, key) => raw.hasOwnProperty(key);
12 |
13 | // add-foo --> addFoo
14 | export const camelize = (str: string) => {
15 | return str.replace(/-(\w)/g, (match, group1) => {
16 | //str--> 'add-foo' , match ---> '-f' , group1 --(\w)--> 'f'
17 | return group1 ? group1.toUpperCase() : "";
18 | });
19 | };
20 | // event('add') --> Add
21 | export const capitalize = (str: string) => {
22 | return str.charAt(0).toUpperCase() + str.slice(1);
23 | };
24 | // Add --> onAdd
25 | export const toHandlerOn = (event: string) => {
26 | return `on${capitalize(event)}`;
27 | };
28 |
--------------------------------------------------------------------------------
/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": "es5" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
15 | "lib": [
16 | "dom",
17 | "es6"
18 | ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
19 | // "jsx": "preserve", /* Specify what JSX code is generated. */
20 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
21 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
22 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
23 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
24 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
25 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
26 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
27 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
28 |
29 | /* Modules */
30 | "module": "esnext" /* Specify what module code is generated. */,
31 | // "rootDir": "./", /* Specify the root folder within your source files. */
32 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
33 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
34 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
35 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
36 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
37 | "types": [
38 | "jest"
39 | ] /* Specify type package names to be included without being referenced in a source file. */,
40 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
41 | // "resolveJsonModule": true, /* Enable importing .json files */
42 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */
43 |
44 | /* JavaScript Support */
45 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
46 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
47 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
48 |
49 | /* Emit */
50 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
51 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */
52 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
53 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */
54 | // "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. */
55 | // "outDir": "./", /* Specify an output folder for all emitted files. */
56 | // "removeComments": true, /* Disable emitting comments. */
57 | // "noEmit": true, /* Disable emitting files from a compilation. */
58 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
59 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
60 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
61 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
62 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
63 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
64 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
65 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
66 | // "newLine": "crlf", /* Set the newline character for emitting files. */
67 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
68 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
69 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
70 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
71 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */
72 |
73 | /* Interop Constraints */
74 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
75 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
76 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
77 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
78 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
79 |
80 | /* Type Checking */
81 | "strict": true /* Enable all strict type-checking options. */,
82 | "noImplicitAny": false /* Enable error reporting for expressions and declarations with an implied `any` type.. */,
83 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
84 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
85 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
86 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
87 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
88 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
89 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
90 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
91 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
92 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
93 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
94 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
95 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
96 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
97 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
98 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
99 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
100 |
101 | /* Completeness */
102 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
103 | "skipLibCheck": true /* Skip type checking all .d.ts files. */
104 | }
105 | }
106 |
--------------------------------------------------------------------------------