├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── README.md ├── babel.config.js ├── docs └── README.md ├── example ├── apiInject │ ├── App.js │ └── index.html ├── compiler-base │ ├── App.js │ ├── index.html │ └── main.js ├── componentEmit │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── componentSlots │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── mian.js ├── componentUpdate │ ├── App.js │ ├── Child.js │ ├── index.html │ └── main.js ├── getCurrentInstance │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── helloworld │ ├── App.js │ ├── Foo.js │ ├── index.html │ └── main.js ├── nextTicker │ ├── App.js │ ├── index.html │ └── main.js ├── text.js │ └── index.html ├── update │ ├── App.js │ ├── index.html │ └── main.js └── updateChildren │ ├── App.js │ ├── ArrayToArray.js │ ├── ArrayToText.js │ ├── TextToArray.js │ ├── TextToText.js │ ├── index.html │ └── main.js ├── lib ├── mini-vue.cjs.js └── mini-vue.esm.js ├── mark.md ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── rollup.config.js ├── src ├── compiler-core │ ├── src │ │ ├── ast.ts │ │ ├── codegen.ts │ │ ├── compile.ts │ │ ├── index.ts │ │ ├── parse.ts │ │ ├── runtimeHelpers.ts │ │ ├── transform.ts │ │ ├── transforms │ │ │ ├── tarnsformElement.ts │ │ │ ├── transformExpression.ts │ │ │ └── transfromText.ts │ │ └── utils.ts │ └── tests │ │ ├── __snapshots__ │ │ └── codegen.spec.ts.snap │ │ ├── codegen.spec.ts │ │ ├── parse.spec.ts │ │ └── transform.spec.ts ├── index.ts ├── reactivity │ ├── baseHandler.ts │ ├── computed.ts │ ├── effect.ts │ ├── index.ts │ ├── reactive.ts │ ├── ref.ts │ └── tests │ │ ├── computed.spec.ts │ │ ├── effect.spec.ts │ │ ├── index.spec.ts │ │ ├── reactive.spec.ts │ │ ├── readonly.spec.ts │ │ ├── ref.spec.ts │ │ ├── shallowReactive.spec.ts │ │ └── shallowReadonly.spec.ts ├── runtime-core │ ├── apiInject.ts │ ├── component.ts │ ├── componentEmit.ts │ ├── componentProps.ts │ ├── componentPublicInstance.ts │ ├── componentSlots.ts │ ├── componentUpdateUtils.ts │ ├── createApp.ts │ ├── h.ts │ ├── helpers │ │ └── renderSlots.ts │ ├── index.ts │ ├── renderer.ts │ ├── scheduler.ts │ └── vnode.ts ├── runtime-dom │ └── index.ts └── shared │ ├── index.ts │ ├── shapeFlags.ts │ ├── text.ts │ └── toDisplayString.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:8080", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5502 3 | } 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mini-vue3.0 学习 2 | 3 | 通过 mini-vue 的学习 来理解 vue3 的 reactivity、compiler-core、 runtime-core 4 | 5 | ## 已经实现过的模块 6 | 7 | ## reactivity 8 | 9 | - [x] reactive 10 | - [x] readonly 11 | - [x] shallowReadonly 12 | - [x] shallowReactive 13 | - [x] isReactive 14 | - [x] isReadonly 15 | - [x] isProxy 16 | - [x] ref 17 | - [x] isRef 18 | - [x] unRef 19 | - [x] proxyRef 20 | - [x] computed 21 | - [x] effect 22 | 23 | ## runtime-core 24 | 25 | - [x] 初始化 Component 主流程 26 | - [x] 初始化 Element 主流程 27 | - [x] shapeFlags 28 | - [x] 组件道理对象 Proxy 29 | - [x] 注册事件 30 | - [x] 组件 props 31 | - [x] 组件 emit 32 | - [x] 组件 slots 33 | - [ ] getCurrentInstance 34 | - [x] 更新 element 35 | 36 | ## runtime-dom 37 | 38 | - [ ] custom renderer 39 | 40 | ## compiler 41 | 42 | - [ ] 解析插值 43 | - [ ] 解析 element 44 | - [ ] 解析 text 功能 45 | - [ ] 解析 三种联合类型功能 46 | - [ ] parse 的实现 47 | 未完待续 48 | 49 | ## 掘金文章 50 | 51 | 配合《Vue.js 设计和实现》的总结输出 52 | 53 | - [Vue3 源码学习(1)--框架设计概览 ](https://juejin.cn/post/7074111898894991390/) 54 | - [Vue3 源码学习(2)--响应式系统(1)](https://juejin.cn/post/7074496267061035038/) 55 | - [Vue3 源码学习(3)--响应式系统(2)](https://juejin.cn/post/7074847535621210126/) 56 | - [Vue3 源码学习(4)--响应式系统(3)](https://juejin.cn/post/7075139625592815624/) 57 | - [Vue3 源码学习(4)--响应式系统(3)](https://juejin.cn/post/7075139625592815624) 58 | - [Vue3 源码学习(5)--runtime-core(1)--初始化(1)](https://juejin.cn/post/7079687116841549855) 59 | - [Vue3 源码学习(6)--runtime-core(2)--初始化(2)](https://juejin.cn/post/7082212664067227679) 60 | - [vue3 源码学习(6) -- runtime-core(3):更新 element(1)](https://juejin.cn/post/7083065686150348836/) 61 | - [vue3 源码学习(6) -- runtime-core(3):更新 element(2):双端 diff 算法](https://juejin.cn/post/7083459283458719757) 62 | - [Vue3 源码学习(5)--响应式系统(3)](https://juejin.cn/post/7075139625592815624) 63 | - [Vue3 源码学习(6)--runtime-core(1)--初始化(1)](https://juejin.cn/post/7079687116841549855) 64 | - [Vue3 源码学习(7)--runtime-core(2)--初始化(2)](https://juejin.cn/post/7082212664067227679) 65 | - [vue3 源码学习(8) -- runtime-core(3):更新 element(1)](https://juejin.cn/post/7083065686150348836/) 66 | - [vue3 源码学习(9) -- runtime-core(4):更新 element(2):双端 diff 算法](https://juejin.cn/post/7083459283458719757) 67 | 未完待续 68 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [["@babel/preset-env", { targets: { node: "current" } }], "@babel/preset-typescript"], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # 一些关于 vue3 源码方面的文章输出 2 | 3 | 源码初学者,请多关照!!!!! 4 | -------------------------------------------------------------------------------- /example/apiInject/App.js: -------------------------------------------------------------------------------- 1 | // 组件 provide 和 inject 功能 2 | import { h, provide, inject } from "../../lib/mini-vue.esm.js"; 3 | 4 | const Provider = { 5 | name: "Provider", 6 | setup() { 7 | provide("foo", "fooVal"); 8 | provide("bar", "barVal"); 9 | }, 10 | render() { 11 | return h("div", {}, [h("p", {}, "Provider"), h(ProviderTwo)]); 12 | }, 13 | }; 14 | 15 | const ProviderTwo = { 16 | name: "ProviderTwo", 17 | setup() { 18 | provide("foo", "fooTwo"); 19 | const foo = inject("foo"); 20 | 21 | return { 22 | foo, 23 | }; 24 | }, 25 | render() { 26 | return h("div", {}, [h("p", {}, `ProviderTwo foo:${this.foo}`), h(Consumer)]); 27 | }, 28 | }; 29 | 30 | const Consumer = { 31 | name: "Consumer", 32 | setup() { 33 | const foo = inject("foo"); 34 | const bar = inject("bar"); 35 | // const baz = inject("baz", "bazDefault"); //默认值 36 | const baz = inject("baz", () => "bazDefault"); 37 | 38 | return { 39 | foo, 40 | bar, 41 | baz, 42 | }; 43 | }, 44 | 45 | render() { 46 | return h("div", {}, `Consumer: - ${this.foo} - ${this.bar}-${this.baz}`); 47 | }, 48 | }; 49 | 50 | export default { 51 | name: "App", 52 | setup() {}, 53 | render() { 54 | return h("div", {}, [h("p", {}, "apiInject"), h(Provider)]); 55 | }, 56 | }; -------------------------------------------------------------------------------- /example/apiInject/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/compiler-base/App.js: -------------------------------------------------------------------------------- 1 | import { ref } from "../../lib/mini-vue.esm.js"; 2 | 3 | export const App = { 4 | name: "App", 5 | template: `
hi,{{count}}
`, 6 | setup() { 7 | const count = (window.count = ref(1)); 8 | return { 9 | count, 10 | }; 11 | }, 12 | }; -------------------------------------------------------------------------------- /example/compiler-base/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/compiler-base/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#app"); 5 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/componentEmit/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | export const App = { 4 | name: "App", 5 | 6 | render() { 7 | //emit 8 | 9 | return h("div", {}, [ 10 | h("div", {}, "App"), 11 | h(Foo, { 12 | //props 13 | //emit 14 | onAdd(num1, num2) { 15 | console.log("onAdd", num1, num2); 16 | }, 17 | onAddFoo() { 18 | console.log("onAddFoo"); 19 | }, 20 | }), 21 | ]); 22 | }, 23 | 24 | setup() { 25 | return {}; 26 | }, 27 | }; -------------------------------------------------------------------------------- /example/componentEmit/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | setup(props, { emit }) { 5 | const emitAdd = () => { 6 | console.log("emit add"); 7 | emit("add", 1, 2); 8 | emit("add-foo"); 9 | }; 10 | return { 11 | emitAdd, 12 | }; 13 | }, 14 | 15 | render() { 16 | const btn = h("button", { onClick: this.emitAdd }, "emitAdd"); 17 | const foo = h("p", {}, "foo"); 18 | return h("div", {}, [foo, btn]); 19 | }, 20 | }; -------------------------------------------------------------------------------- /example/componentEmit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /example/componentEmit/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.getElementById("app"); 5 | // console.log(rootContainer); 6 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/componentSlots/App.js: -------------------------------------------------------------------------------- 1 | import { h, createTextVnode } from "../../lib/mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | export const App = { 4 | name: "App", 5 | 6 | render() { 7 | const app = h("div", {}, "App"); //vnode 8 | 9 | //怎么使用插槽 ---- 把对应的内容放到对应的组件的children中 10 | //希望children里面的p标签能够在foo组件中渲染出来 11 | 12 | //传入单值 13 | // const foo = h(Foo, {}, h("p", {}, "123")); 14 | 15 | 16 | // 传入数组 17 | // const foo = h(Foo, {}, [h("p", {}, "123"), h("p", {}, '456')]); 18 | 19 | //具名插槽 需要知道key value 所以用到obj 20 | // const foo = h( 21 | // Foo, {}, { 22 | // header: h("p", {}, "header"), 23 | // footer: h("p", {}, "footer"), 24 | // } 25 | // ); 26 | 27 | //作用域插槽 28 | const foo = h( 29 | Foo, {}, { 30 | // header: ({ age }) => h("p", {}, "header" + age), 31 | header: ({ age }) => h("p", {}, "header" + age), 32 | 33 | footer: () => [h("p", {}, "footer"), createTextVnode("你好呀!")] 34 | // footer: () => h("p", {}, "footer") 35 | } 36 | ); //vnode 37 | return h("div", {}, [app, foo]); 38 | }, 39 | setup() { 40 | return {}; 41 | }, 42 | }; -------------------------------------------------------------------------------- /example/componentSlots/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, renderSlots } from "../../lib/mini-vue.esm.js"; 2 | 3 | 4 | //插槽本质 就是将App.js中 Foo组件的children const foo = h(Foo, {}, h("p", {}, "123")); 5 | // 插入到 Foo组件中 element vnode subtree中children 6 | export const Foo = { 7 | name: "Foo", 8 | render() { 9 | const foo = h("p", {}, "foo"); 10 | 11 | //Foo .vnode .children 12 | // console.log(this); 13 | // console.log("+++++++++:" + 14 | // JSON.stringify(this.$slots)); 15 | // children -> vnode 16 | // 内部到处renderSlots 17 | console.log('slots:', this.$slots); 18 | // return h("div", {}, [foo, h("div", {}, this.$slots)]); 19 | // return h("div", {}, [foo, h("div", {}, renderSlots(this.$slots))]); 20 | 21 | 22 | const age = 18 23 | // renderSlots 24 | //1、获取到要渲染的元素 25 | //2、获取到渲染的位置 26 | 27 | // return h("div", {}, [renderSlots(this.$slots, "header", { age }), foo, renderSlots(this.$slots, "footer")]); 28 | 29 | return h("div", {}, [renderSlots(this.$slots, "header", { age }), foo, renderSlots(this.$slots, "footer")]); 30 | }, 31 | 32 | setup() { 33 | return {}; 34 | }, 35 | }; -------------------------------------------------------------------------------- /example/componentSlots/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /example/componentSlots/mian.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.getElementById("app"); 5 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/componentUpdate/App.js: -------------------------------------------------------------------------------- 1 | //组件更新: 2 | // 首先更新组建的数据 props等 3 | // 其次调用组件的render函数 利用effect 的返回值是runner函数 4 | // 更新时 检测组件是否需要更新 5 | 6 | import { h, ref } from '../../lib/mini-vue.esm.js' 7 | import Child from './Child.js' 8 | export const App = { 9 | name: 'App', 10 | 11 | setup() { 12 | const msg = ref('123') 13 | const count = ref(1) 14 | window.msg = msg 15 | 16 | const changeChildProps = () => { 17 | msg.value = '456' 18 | } 19 | 20 | const changeCount = () => { 21 | count.value++ 22 | } 23 | 24 | return { 25 | msg, 26 | count, 27 | changeChildProps, 28 | changeCount, 29 | } 30 | }, 31 | render() { 32 | return h('div', {}, [ 33 | h('div', {}, '你好'), 34 | h('button', { onClick: this.changeChildProps }, 'change child props'), 35 | h(Child, { msg: this.msg }), //重点在这 36 | h('button', { onClick: this.changeCount }, 'change self count'), 37 | h('p', {}, 'count: ' + this.count), 38 | ]) 39 | }, 40 | } 41 | 42 | /** 43 | * 组建的更新 44 | * 45 | * 组件的渲染是一个开箱的过程,在props发生变化的时候就再次执行render函数 46 | * 执行render函数就会创建新的subTree(vnode) 然后在进行patch(prevSubTree,subTree) 47 | * 所以 在进行processComponent中 就要进行判断 是否有prevSubTree 48 | * 无 ---> mountComponent 49 | * 有 ---> patchComponent 50 | * 51 | * 更新component 需要先更新数据(组件的props) 52 | * 53 | * 54 | */ 55 | -------------------------------------------------------------------------------- /example/componentUpdate/Child.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/mini-vue.esm.js" 2 | export default { 3 | name: "Child", 4 | setup(props, { emit }) {}, 5 | render(proxy) { 6 | return h("div", {}, [ 7 | h("div", {}, "child - props - msg: " + this.$props.msg) 8 | ]); 9 | }, 10 | }; -------------------------------------------------------------------------------- /example/componentUpdate/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/componentUpdate/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from '../../lib/mini-vue.esm.js' 2 | import { App } from './app.js' 3 | 4 | const rootContainer = document.getElementById("app") 5 | 6 | console.log(rootContainer); 7 | createApp(App).mount(rootContainer) -------------------------------------------------------------------------------- /example/getCurrentInstance/App.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | export const App = { 5 | name: "App", 6 | 7 | render() { 8 | return h("div", {}, [h("p", {}, "currentInstance demo"), h(Foo)]); 9 | }, 10 | 11 | setup() { 12 | const instance = getCurrentInstance(); 13 | console.log("App:", instance); 14 | }, 15 | }; -------------------------------------------------------------------------------- /example/getCurrentInstance/Foo.js: -------------------------------------------------------------------------------- 1 | import { h, getCurrentInstance } from "../../lib/mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | name: "Foo", 5 | 6 | render() { 7 | return h("div", {}, "foo"); 8 | }, 9 | 10 | setup() { 11 | const instance = getCurrentInstance(); 12 | console.log("Foo:", instance); 13 | return {}; 14 | }, 15 | }; -------------------------------------------------------------------------------- /example/getCurrentInstance/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/getCurrentInstance/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.getElementById("app"); 5 | 6 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/helloworld/App.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/mini-vue.esm.js"; 2 | import { Foo } from "./Foo.js"; 3 | 4 | window.self = null; 5 | export const App = { 6 | name: "APP", 7 | // .vue 8 | // ->render 9 | 10 | //render 11 | 12 | render() { 13 | window.self = this; 14 | return h( 15 | "div", 16 | { 17 | id: "root", 18 | class: ["red", "hard"], 19 | onClick: () => { 20 | console.log("嘿嘿嘿"); 21 | }, 22 | onMousedown: (e) => { 23 | console.log(e.target); 24 | }, 25 | }, 26 | // "hi" + this.msg 27 | //this.$el -> get 当前component的 root element 28 | //string 29 | // "hi mini-vue" 30 | //array 31 | // [ 32 | // h("p", { class: "red" }, "hi"), 33 | // h("p", { class: "hard" }, [h("span", { id: "aaa" }, "hihaha")]), 34 | // ] 35 | 36 | [h("div", {}, "hi" + this.msg), h(Foo, { count: 1 })] 37 | ); 38 | }, 39 | 40 | setup() { 41 | //composition api 42 | return { 43 | msg: "mini-vue 哈哈哈", 44 | }; 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /example/helloworld/Foo.js: -------------------------------------------------------------------------------- 1 | import { h } from "../../lib/mini-vue.esm.js"; 2 | 3 | export const Foo = { 4 | name: "Foo", 5 | setup(props) { 6 | //props.count 7 | console.log(props); 8 | // props.count++; 9 | //props readonly 10 | }, 11 | render() { 12 | return h("div", {}, "foo : " + this.count); 13 | }, 14 | }; -------------------------------------------------------------------------------- /example/helloworld/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /example/helloworld/main.js: -------------------------------------------------------------------------------- 1 | // vue3 2 | 3 | import { createApp } from "../../lib/mini-vue.esm.js"; 4 | import { App } from "./App.js"; 5 | 6 | const rootContainer = document.getElementById("app"); 7 | console.log(rootContainer); 8 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/nextTicker/App.js: -------------------------------------------------------------------------------- 1 | import { 2 | h, 3 | ref, 4 | getCurrentInstance, 5 | nextTick, 6 | } from "../../lib/mini-vue.esm.js"; 7 | 8 | export default { 9 | name: "App", 10 | setup() { 11 | const count = ref(1); 12 | const instance = getCurrentInstance(); 13 | 14 | function onClick() { 15 | for (let i = 0; i < 100; i++) { 16 | console.log("update"); 17 | count.value = i; 18 | } 19 | 20 | debugger; 21 | console.log(instance); 22 | nextTick(() => { 23 | console.log(instance); 24 | }); 25 | 26 | // await nextTick() 27 | // console.log(instance) 28 | } 29 | 30 | return { 31 | onClick, 32 | count, 33 | }; 34 | }, 35 | render() { 36 | const button = h("button", { onClick: this.onClick }, "update"); 37 | const p = h("p", {}, "count:" + this.count); 38 | 39 | return h("div", {}, [button, p]); 40 | }, 41 | }; -------------------------------------------------------------------------------- /example/nextTicker/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/nextTicker/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/mini-vue.esm.js"; 2 | import App from "./App.js"; 3 | 4 | const rootContainer = document.querySelector("#root"); 5 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/text.js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
34 | 35 | 36 | -------------------------------------------------------------------------------- /example/update/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from "../../lib/mini-vue.esm.js"; 2 | 3 | export const App = { 4 | name: "App", 5 | 6 | render() { 7 | // window.self = this; 8 | // console.log(this.count); // count refImpl对象 count 9 | return h("div", { id: "root", ...this.props }, [ 10 | h("button", { onClick: this.onClick }, "click"), 11 | h("div", {}, "count" + this.count), // render函数当作依赖,用effect包裹 ==》触发render==》 依赖收集 12 | 13 | //props 14 | 15 | h("button", { onClick: this.onChangePropsDemo1 }, "changeProps - foo的值改变了"), 16 | h("button", { onClick: this.onChangePropsDemo2 }, "changeProps - foo的值改变了为undefined"), 17 | h("button", { onClick: this.onChangePropsDemo3 }, "changeProps - bar没有了"), 18 | ]); 19 | }, 20 | setup() { 21 | const count = ref(0); 22 | 23 | const onClick = () => { 24 | count.value++; 25 | }; 26 | 27 | //props 28 | const props = ref({ 29 | foo: "foo", 30 | bar: "bar", 31 | }); 32 | 33 | const onChangePropsDemo1 = () => { 34 | props.value.foo = "new-foo"; 35 | }; 36 | const onChangePropsDemo2 = () => { 37 | props.value.foo = "undefined"; 38 | }; 39 | const onChangePropsDemo3 = () => { 40 | props.value = { 41 | foo: "foo", 42 | }; 43 | }; 44 | return { 45 | count, 46 | onClick, 47 | props, 48 | onChangePropsDemo1, 49 | onChangePropsDemo2, 50 | onChangePropsDemo3, 51 | }; 52 | }, 53 | }; 54 | 55 | 56 | 57 | /** 58 | * 思考1: 什么时候进行更新 59 | * 响应式对象发生改变,render函数结果就会变化 导致vnode对象发生变化 60 | * 所以 我们要怎么获取到之前和之后两个vnode呢? 61 | * 62 | * setupRenderEffect函数中的 subTree就是组件render函数的结果 即当前组件的vnode n1 63 | * 64 | * 65 | * 那么 怎么获取响应式对象改变后的subTree结果呢? 66 | * 通过之前实现的effect函数包裹setupRenderEffect中的逻辑, 67 | * 当响应式对象发生变化的时候,就会重新执行包裹的逻辑,从而重新执行组件的render函数,生成新的vnode n2 68 | * 69 | *然后进行n1 和 n2 之间的比较 patch(n1,n2,container,parentInstance)-> processElement ->patchElement 70 | * 71 | * 72 | * 更新逻辑 : 可以看作是两个vnode对象之间的对比 n1 n2 73 | * 74 | * 三个方面进行对比 75 | * type 76 | * 但是普通修改render函数 只会修改内部的props和 children 所以一般type不会改变,如果type改变了 会直接当作新的element直接挂在 77 | * 78 | * props patchProps 79 | * 1、之前的值和现在的值不一样了 修改了值 80 | * 遍历新的props,通过key获取到 newProps[key] 和 oldProps[key] 81 | * newProps[key] != oldProps[key] 修改props的值(通过patchProp函数) 82 | * hostPatchProp(el, key, oldProps[key], newProps[key]) 83 | * 84 | * 85 | * 2、新的值null || undefined 删除 86 | * 同上面方法,因为新的props的key值没有发生变化 87 | * 88 | * 89 | * 3、之前的值在新的里面没有了 删除 90 | * 新的props值少了,证明新的props中的key与原来props的key不一样了,新的props的key值少了 91 | * 这时候我们需要遍历老的props中key,查找当前key在新的props中是否存在 92 | * newProps[key] === 'undefined' 删除当前key hostPatchProp(el, key, oldProps[key], null) 93 | * 94 | * 95 | * 96 | * children 97 | * children 的类型 包括 string 和 array 两种类型 98 | * 所以在进行children对比的时候就会产生四种情况 99 | * oldChildren newChildren 100 | * string string 101 | * string array 102 | * array string 103 | * array array 104 | * 在updateChildren 的app.js中详细总结 105 | */ -------------------------------------------------------------------------------- /example/update/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/update/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | 4 | const rootContainer = document.getElementById("app"); 5 | 6 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /example/updateChildren/App.js: -------------------------------------------------------------------------------- 1 | import { h, ref } from '../../lib/mini-vue.esm.js' 2 | 3 | import ArrayToText from './ArrayToText.js' 4 | import TextToArray from './TextToArray.js' 5 | import TextToText from './TextToText.js' 6 | import ArrayToArray from './ArrayToArray.js' 7 | export const App = { 8 | name: 'App', 9 | 10 | render() { 11 | return h('div', { tId: 1 }, [ 12 | h('p', {}, '主页'), 13 | // h('button', { onClick: this.changeCount }, 'change self count'), 14 | 15 | //老的节点是array,新的节点是text 16 | // h(ArrayToText), 17 | //老的节点是text,新的节点是text 18 | // h(TextToText), 19 | //老的节点是text,新的节点是array 20 | // h(TextToArray), 21 | //老的节点是array,新的节点是array 22 | h(ArrayToArray), 23 | ]) 24 | }, 25 | 26 | setup() { 27 | // const count = ref('false') 28 | // window.count = count 29 | // const changeCount = () => { 30 | // count = !this.count.value 31 | // } 32 | // return { 33 | // changeCount, 34 | // count, 35 | // } 36 | }, 37 | } 38 | 39 | /** 40 | * children的更新逻辑 patchChildren(n1,n2,container,parentComponent) 41 | * 42 | * 新节点children类型 shapeFlag = n2.shapeFlag 43 | * 老节点children类型 prevShapeFlag = n1.shapeFlag 44 | * 老节点children c1 = n1.children 45 | * 新节点children c2 = n2.children 46 | * 47 | * 48 | * 1、 array to text 49 | * 首先我们需要判断新老children的类型是text 还是 array类型 50 | * 通过vnode.shapeFlag来判断 51 | * 52 | * 通过 shapeFlag & ShapeFlags.text_children 判断为text类型 53 | * 再判断 prevShapeFlag & ShapeFlags.array_children 判断老节点是否为array类型 54 | * 55 | * 删除老节点children 56 | * 添加新节点children 57 | * 58 | * 59 | * 2 text to text 60 | * 通过shapeFlag 和 prevShapeFlag 判断都为text类型 61 | * 62 | * 然后判断 n1.children != n2.children 63 | * 直接修改el.textContent = n2.children 64 | * 65 | * 66 | * 3、 text to array 67 | * shapeFlag & ShapeFlags.array_children 判断为array的类型 68 | * prevShapeFlag & ShapeFlags.text_children 判断为text类型 69 | * 70 | * 清空老节点 setElementText(container, ""); 71 | * 添加新节点 遍历c2 然后patch(null,v,container,parentComponent) 72 | * 73 | * 74 | * 4、 array to array 最复杂滴 75 | * 分为 无key 和 有key 两种情况 76 | * 4.1无key 77 | * 4.2 有key 78 | * 79 | * 详细见array to array 80 | */ 81 | -------------------------------------------------------------------------------- /example/updateChildren/ArrayToArray.js: -------------------------------------------------------------------------------- 1 | //TODO 2 | //老的是 array 3 | //新的是 array 4 | 5 | import { h, ref } from '../../lib/mini-vue.esm.js' 6 | 7 | // 1、左侧的对比 8 | // (a b) c 9 | //(a,b) d e 10 | 11 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")]; 12 | 13 | // const nextChildren = [ 14 | // h("p", { key: "A" }, "A"), 15 | // h("p", { key: "B" }, "B"), 16 | // h("p", { key: "D" }, "D"), 17 | // h("p", { key: "E" }, "E"), 18 | // ]; 19 | 20 | //2、右侧对比 21 | // a (b c) 22 | //d e (b c) 23 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")]; 24 | // const nextChildren = [ 25 | // h("p", { key: "D" }, "D"), 26 | // h("p", { key: "E" }, "E"), 27 | // h("p", { key: "B" }, "B"), 28 | // h("p", { key: "C" }, "C"), 29 | // ]; 30 | 31 | //3、新的比老的长 32 | //(a b) 33 | //(a b) c 34 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 35 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")]; 36 | 37 | //(a b) 38 | //c (a b) 39 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 40 | // const nextChildren = [h("p", { key: "C" }, "C"), h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 41 | 42 | //4、老的比新的长 43 | //(a b) c 44 | //(a b) 45 | // const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")]; 46 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 47 | 48 | //c (a b) 49 | //(a b) 50 | // const prevChildren = [h("p", { key: "C" }, "C"), h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 51 | // const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")]; 52 | 53 | //5、对比中间部分 54 | //删除老的(在老的里面存在,新的里面不存在) 55 | //5.1 56 | //a b (c d) f g 57 | //a b (e c) f g 58 | //D节点在新的里面没有的 -需要删除 59 | //C节点props也发生了变化 60 | // const prevChildren = [ 61 | // h("p", { key: "A" }, "A"), 62 | // h("p", { key: "B" }, "B"), 63 | // h("p", { key: "C", id: "c-prev" }, "C"), 64 | // h("p", { key: "D" }, "D"), 65 | // h("p", { key: "F" }, "F"), 66 | // h("p", { key: "G" }, "G"), 67 | // ]; 68 | // const nextChildren = [ 69 | // h("p", { key: "A" }, "A"), 70 | // h("p", { key: "B" }, "B"), 71 | // h("p", { key: "E" }, "E"), 72 | // h("p", { key: "C", id: "c-next" }, "C"), 73 | // h("p", { key: "F" }, "F"), 74 | // h("p", { key: "G" }, "G"), 75 | // ]; 76 | // 5.1.1 77 | // a,b,(c,e,d),f,g 78 | // a,b,(e,c),f,g 79 | // 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑) 80 | // const prevChildren = [ 81 | // h("p", { key: "A" }, "A"), 82 | // h("p", { key: "B" }, "B"), 83 | // h("p", { key: "C", id: "c-prev" }, "C"), 84 | // h("p", { key: "E" }, "E"), 85 | // h("p", { key: "D" }, "D"), 86 | // h("p", { key: "F" }, "F"), 87 | // h("p", { key: "G" }, "G"), 88 | // ]; 89 | 90 | // const nextChildren = [ 91 | // h("p", { key: "A" }, "A"), 92 | // h("p", { key: "B" }, "B"), 93 | // h("p", { key: "E" }, "E"), 94 | // h("p", { key: "C", id: "c-next" }, "C"), 95 | // h("p", { key: "F" }, "F"), 96 | // h("p", { key: "G" }, "G"), 97 | // ]; 98 | 99 | // 5.2 移动 (节点存在于新的和老的里面,但是位置变了) 100 | // 2.1 101 | // a,b,(c,d,e),f,g 102 | // a,b,(e,c,d),f,g 103 | // 最长子序列: [1,2] 104 | 105 | // const prevChildren = [ 106 | // h("p", { key: "A" }, "A"), 107 | // h("p", { key: "B" }, "B"), 108 | // h("p", { key: "C" }, "C"), 109 | // h("p", { key: "D" }, "D"), 110 | // h("p", { key: "E" }, "E"), 111 | // h("p", { key: "F" }, "F"), 112 | // h("p", { key: "G" }, "G"), 113 | // ]; 114 | 115 | // const nextChildren = [ 116 | // h("p", { key: "A" }, "A"), 117 | // h("p", { key: "B" }, "B"), 118 | // h("p", { key: "E" }, "E"), 119 | // h("p", { key: "C" }, "C"), 120 | // h("p", { key: "D" }, "D"), 121 | // h("p", { key: "F" }, "F"), 122 | // h("p", { key: "G" }, "G"), 123 | // ]; 124 | // 3. 创建新的节点 125 | // a,b,(c,e),f,g 126 | // a,b,(e,c,d),f,g 127 | // d 节点在老的节点中不存在,新的里面存在,所以需要创建 128 | // const prevChildren = [ 129 | // h("p", { key: "A" }, "A"), 130 | // h("p", { key: "B" }, "B"), 131 | // h("p", { key: "C" }, "C"), 132 | // h("p", { key: "E" }, "E"), 133 | // h("p", { key: "F" }, "F"), 134 | // h("p", { key: "G" }, "G"), 135 | // ]; 136 | 137 | // const nextChildren = [ 138 | // h("p", { key: "A" }, "A"), 139 | // h("p", { key: "B" }, "B"), 140 | // h("p", { key: "E" }, "E"), 141 | // h("p", { key: "C" }, "C"), 142 | // h("p", { key: "D" }, "D"), 143 | // h("p", { key: "F" }, "F"), 144 | // h("p", { key: "G" }, "G"), 145 | // ]; 146 | 147 | // 综合例子 148 | // a,b,(c,d,e,z),f,g 149 | // a,b,(d,c,aky,y,e),f,g 150 | 151 | // const prevChildren = [ 152 | // h('p', { key: 'A' }, 'A'), 153 | // h('p', { key: 'B' }, 'B'), 154 | // h('p', { key: 'C' }, 'C'), 155 | // h('p', { key: 'D' }, 'D'), 156 | // h('p', { key: 'E' }, 'E'), 157 | // h('p', { key: 'Z' }, 'Z'), 158 | // h('p', { key: 'F' }, 'F'), 159 | // h('p', { key: 'G' }, 'G'), 160 | // ] 161 | 162 | // const nextChildren = [ 163 | // h('p', { key: 'A' }, 'A'), 164 | // h('p', { key: 'B' }, 'B'), 165 | // h('p', { key: 'D' }, 'D'), 166 | // h('p', { key: 'C' }, 'C'), 167 | // h('p', { key: 'aky' }, 'aky'), 168 | // h('p', { key: 'Y' }, 'Y'), 169 | // h('p', { key: 'E' }, 'E'), 170 | // h('p', { key: 'F' }, 'F'), 171 | // h('p', { key: 'G' }, 'G'), 172 | // ] 173 | 174 | const prevChildren = [ 175 | h('p', { key: 'A' }, 'A'), 176 | h('p', { key: 'B' }, 'B'), 177 | h('p', { key: 'C' }, 'C'), 178 | h('p', { key: 'D' }, 'D'), 179 | h('p', { key: 'E' }, 'E'), 180 | h('p', { key: 'F' }, 'F'), 181 | h('p', { key: 'G' }, 'G'), 182 | ] 183 | 184 | const nextChildren = [ 185 | h('p', { key: 'A' }, 'A'), 186 | h('p', { key: 'B' }, 'B'), 187 | h('p', { key: 'Z' }, 'Z'), 188 | h('p', { key: 'C' }, 'C'), 189 | h('p', { key: 'E' }, 'E'), 190 | h('p', { key: 'aky' }, 'aky'), 191 | h('p', { key: 'F' }, 'F'), 192 | h('p', { key: 'G' }, 'G'), 193 | ] 194 | 195 | //fix c节点应该是move 而不是移除后创建 196 | 197 | // const prevChildren = [ 198 | // h("p", { key: "A" }, "A"), 199 | // h("p", {}, "C"), 200 | 201 | // h("p", { key: "B" }, "B"), 202 | 203 | // h("p", { key: "D" }, "D"), 204 | 205 | // ]; 206 | 207 | // const nextChildren = [ 208 | // h("p", { key: "A" }, "A"), 209 | 210 | // h("p", { key: "B" }, "B"), 211 | // h("p", {}, "C"), 212 | 213 | // h("p", { key: "D" }, "D"), 214 | 215 | // ]; 216 | export default { 217 | name: 'ArrayToArray', 218 | 219 | setup() { 220 | const isChange = ref(false) 221 | window.isChange = isChange 222 | return { 223 | isChange, 224 | } 225 | }, 226 | 227 | render() { 228 | const self = this 229 | return self.isChange === true ? h('div', {}, nextChildren) : h('div', {}, prevChildren) 230 | }, 231 | } 232 | 233 | /** 234 | * 4.1 无key情况 235 | * 236 | * 获取新节点数组长度,获取就节点数组的长度 237 | * 取两个长度中较小的长度值 238 | * 从0位置开始一次进行比较 for(i=0;i<=commonLength; i++) 然后patch 239 | * 240 | * 逻辑本质就是: 241 | * 如果新节点个数 > 新节点个数,移除多余的节点 242 | * 如果旧节点个数 < 新节点个数,增加节点 243 | * 4.2 有key情况 (复杂的嘞) 244 | * 245 | * a,b(c,d,e,,z,y)f,g 246 | * a,b(d,c,y,e)f,g 247 | * 248 | * 新节点children c2 249 | * 旧节点children c1 250 | * 就节点children长度 l1 = c1.length 251 | * 新节点children长度 l2 = c2.length 252 | * 253 | * e1 = c1.length -1 254 | * e2 = c2.length -1 255 | * 三指针对比 256 | * 257 | * 1、左侧对比 258 | * 条件是:while( i<=e1 && i<=e2 ) 259 | * n1 = c1[i] 是否与 n2 = c2[i] 相同 isSameVnode(n1,n2) (type和key相同 判断段isSame) 260 | * if(isSameVnode === true) patch(n1,n2,container,parentComponent) 261 | * else break 退出循环 262 | * i++ 263 | * 264 | * 综合案例 此时 i=2时 退出循环 e1 = 9 ; e2 = 8 265 | * 266 | * 267 | * 2、右侧对比 268 | * 此时 先进行过左侧的对比了 此时左侧已经对比出不同的节点了,i的值确定了 269 | * 例如综合案例 i=确定了 270 | * 在进行右侧对比 e1=9 e2 =8 271 | * 条件依然是: while( i <=e1 && i<=e2) 272 | * n1 = c1[e1] n2 = c2[e2] 273 | * if(isSameVndoe(n1,n2)===true) patch(n1,n2,container,parentComponent) 274 | * else break 退出循环 275 | * e1 -- 276 | * e2 -- 277 | * 278 | * 综合案例 此时 i= 2 e1=7 e2 = 6 279 | * 280 | * 281 | * 3、新的比老的多 (两种情况) 282 | * eg a b a b 283 | * a b (c d) ( c d) a b 284 | * 285 | * 在左侧对比和右侧对比完成后 出现的一种情况 286 | * 如果 e1< i <= e2 证明新节点多与老节点 287 | * if( i > e1){ 288 | * if( i <=e2) { 289 | * 新的比老的多,直接创建新的节点(多个一个甚至多个), 290 | * 并且把新节点创建在它指定的位置 291 | * } 292 | * } 293 | * 294 | * 295 | * 4、老的比新的多(同上两种情况) 296 | * 在左侧对比和右侧对比完成后 出现的一种情况 297 | * 如果 i >e2 i<=e1 298 | * else if(i>e2){ 299 | * while(ie1 同时 i<=e2,证明 新节点比老节点长 343 | * 新建新的节点 344 | * if(i>e1){ 345 | * if(i <=e2){ 346 | * while(1<=e2){ 347 | * 创建新节点 348 | * i++ 349 | * } 350 | * } 351 | * } 352 | * }else if( i >e2){ 如果此时 i >e2 同时 i<=e1,证明 老节点比新节点长 353 | * 删除老节点 354 | * while( i <=e1){ 355 | * 删除老节点 356 | * i++ 357 | * } 358 | * 中间对比 359 | * }else { 360 | * s1 = i 361 | * s2 = i 362 | * patched = 0 363 | * toBePatch = e2-s2 +1 364 | * keyToNewIndexMap映射表建立 365 | * 366 | * for(let i=o;i<=e2;i++) keyToNewIndexMap.set(c2[i].key,i) 填充数据 367 | * 368 | * for (let i = s1; i <= e1; i++) { 369 | * prevChild = c1[i] 370 | * 371 | * if(patched >= toBePatched) remove(prevChild.el) 372 | * if(prevChid.ket !== null){ 373 | * prevChild元素在新节点数组中的新索引 newIndex = keyToNewIndexMap.get(prevChid.key) 374 | * }else{ 375 | * 遍历新节点数组 for(let j = s2; i <= e2; i++){ 376 | * if(isSameVnode(prevChild,c2[j])) newIndex = j break 377 | * } 378 | * } 379 | * 更新和删除 380 | * if(newIndex) patch(prevChild,c2[newIndex],container,parentComponent,null) patched ++ 381 | * else remove(prevChid.el) 删除 382 | * 383 | * } 384 | * 385 | * 386 | * } 387 | */ 388 | -------------------------------------------------------------------------------- /example/updateChildren/ArrayToText.js: -------------------------------------------------------------------------------- 1 | //老的是array 2 | //新的是text 3 | 4 | import { h, ref } from "../../lib/mini-vue.esm.js"; 5 | 6 | const nextChildren = "newChildren"; 7 | const prevChildren = [h("div", {}, "A"), h("div", {}, "B")]; 8 | 9 | export default { 10 | name: "ArrayToText", 11 | 12 | setup() { 13 | const isChange = ref(false); 14 | window.isChange = isChange; 15 | 16 | return { 17 | isChange, 18 | }; 19 | }, 20 | render() { 21 | const self = this; 22 | return self.isChange === true ? 23 | h("div", {}, nextChildren) : 24 | h("div", {}, prevChildren); 25 | }, 26 | }; -------------------------------------------------------------------------------- /example/updateChildren/TextToArray.js: -------------------------------------------------------------------------------- 1 | // 新的是 array 2 | // 老的是 text 3 | import { h, ref } from "../../lib/mini-vue.esm.js"; 4 | 5 | const prevChildren = "oldChild"; 6 | const nextChildren = [h("div", {}, "A"), h("div", {}, "B")]; 7 | 8 | export default { 9 | name: "TextToArray", 10 | setup() { 11 | const isChange = ref(false); 12 | window.isChange = isChange; 13 | 14 | return { 15 | isChange, 16 | }; 17 | }, 18 | render() { 19 | const self = this; 20 | 21 | return self.isChange === true ? h("div", {}, nextChildren) : h("div", {}, prevChildren); 22 | }, 23 | }; -------------------------------------------------------------------------------- /example/updateChildren/TextToText.js: -------------------------------------------------------------------------------- 1 | // 新的是 text 2 | // 老的是 text 3 | import { h, ref } from "../../lib/mini-vue.esm.js"; 4 | 5 | const prevChildren = "oldChild"; 6 | const nextChildren = "newChild"; 7 | 8 | export default { 9 | name: "TextToText", 10 | setup() { 11 | const isChange = ref(false); 12 | window.isChange = isChange; 13 | 14 | return { 15 | isChange, 16 | }; 17 | }, 18 | render() { 19 | const self = this; 20 | 21 | return self.isChange === true ? h("div", {}, nextChildren) : h("div", {}, prevChildren); 22 | }, 23 | }; -------------------------------------------------------------------------------- /example/updateChildren/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /example/updateChildren/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from "../../lib/mini-vue.esm.js"; 2 | import { App } from "./App.js"; 3 | const rootContainer = document.getElementById("app"); 4 | createApp(App).mount(rootContainer); -------------------------------------------------------------------------------- /lib/mini-vue.esm.js: -------------------------------------------------------------------------------- 1 | function toDisplayString(value) { 2 | return String(value); 3 | } 4 | 5 | const extend = Object.assign; 6 | const EMPTY_OBJ = {}; 7 | function isObject(val) { 8 | return val !== null && typeof val === "object"; 9 | } 10 | const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key); 11 | //add-foo ->addFoo 12 | const camelize = (str) => { 13 | return str.replace(/-(\w)/g, (_, c) => { 14 | return c ? c.toUpperCase() : ""; 15 | }); 16 | }; 17 | //addFoo ->AddFoo 18 | const capitalize = (str) => { 19 | return str.charAt(0).toUpperCase() + str.slice(1); 20 | }; 21 | // console.log(capitalize(event)); 22 | // AddFoo -> toAddFoo 23 | const toHandlerKey = (str) => { 24 | return str ? "on" + str : ""; 25 | }; 26 | const isString = (value) => typeof value === "string"; 27 | 28 | const Fragment = Symbol('Fragment'); 29 | const Text = Symbol('Text'); 30 | function createVNode(type, props, children) { 31 | const vnode = { 32 | type, 33 | props, 34 | children, 35 | key: props && props.key, 36 | shapeFlag: getShapeFlag(type), 37 | el: null, 38 | component: null, 39 | }; 40 | //children 41 | if (typeof children === 'string') { 42 | vnode.shapeFlag = vnode.shapeFlag | 4 /* shapeFlags.text_children */; 43 | } 44 | else if (Array.isArray(children)) { 45 | vnode.shapeFlag = vnode.shapeFlag | 8 /* shapeFlags.array_children */; 46 | } 47 | // 组件 + children 为object类型 48 | if (vnode.shapeFlag & 2 /* shapeFlags.stateful_component */) { 49 | if (isObject(children)) { 50 | vnode.shapeFlag = vnode.shapeFlag | 16 /* shapeFlags.slot_children */; 51 | } 52 | } 53 | return vnode; 54 | } 55 | function createTextVnode(text) { 56 | return createVNode(Text, {}, text); 57 | } 58 | function getShapeFlag(type) { 59 | return typeof type === 'string' ? 1 /* shapeFlags.element */ : 2 /* shapeFlags.stateful_component */; 60 | } 61 | 62 | function h(type, props, children) { 63 | return createVNode(type, props, children); 64 | } 65 | 66 | function renderSlots(slots, name, props) { 67 | const slot = slots[name]; 68 | if (slot) { 69 | if (typeof slot === "function") { 70 | return createVNode(Fragment, {}, slot(props)); 71 | } 72 | } 73 | } 74 | 75 | //effect 第一个参数接受一个函数 76 | /** 77 | * 不给activeEffect添加类型, 单测会报错 78 | * 所以进行了代码优化 79 | */ 80 | // let activeEffect: () => void; 81 | // export function effect(fn: () => void) { 82 | // activeEffect = fn; 83 | // fn(); //执行函数 ->触发了响应式对象的getter ->track 84 | // activeEffect = function () {}; 85 | // } 86 | //工具函数 87 | function cleanupEffect(effect) { 88 | effect.deps.forEach((dep) => { 89 | dep.delete(effect); 90 | }); 91 | //把 effect.deps清空 92 | effect.deps.length = 0; 93 | } 94 | //代码优化 面向对象思想 95 | let activeEffect; 96 | let shouldTrack = false; //用于记录是否应该收集依赖,防止调用stop后触发响应式对象的property的get的依赖收集 obj.foo ++ 97 | class ReactiveEffect { 98 | constructor(fn, option) { 99 | this.deps = []; //用于保存与当前实例相关的响应式对象的 property 对应的 Set 实例 用于stop操作 100 | this.active = true; //用于记录当前实例状态,为 true 时未调用 stop 方法,否则已调用,防止重复调用 stop 方法 101 | this._fn = fn; 102 | this.scheduler = option === null || option === void 0 ? void 0 : option.scheduler; 103 | this.onStop = option === null || option === void 0 ? void 0 : option.onStop; 104 | // this.deps = []; 105 | // this.active = true; 106 | } 107 | //用于执行传入的函数 108 | run() { 109 | //stop的状态下(active =false) 直接执行fn 不收集依赖 110 | if (!this.active) { 111 | this.active = true; 112 | return this._fn(); 113 | } 114 | //应该收集依赖 115 | shouldTrack = true; 116 | activeEffect = this; 117 | const res = this._fn(); 118 | //重置 119 | shouldTrack = false; 120 | // 返回传入的函数执行的结果 121 | return res; 122 | } 123 | stop() { 124 | //删除effect active用于优化 多次调用stop也只清空一次 125 | if (this.active) { 126 | cleanupEffect(this); 127 | if (this.onStop) { 128 | this.onStop(); 129 | } 130 | this.active = false; 131 | } 132 | } 133 | } 134 | //effect函数 135 | /** 136 | * @param fn 参数函数 137 | */ 138 | function effect(fn, option = {}) { 139 | const _effect = new ReactiveEffect(fn, option); 140 | // Object.assign(_effect, option); 141 | if (option) { 142 | extend(_effect, option); //what this? 143 | } 144 | if (!option || !option.lazy) { 145 | _effect.run(); 146 | } 147 | // _effect.run(); //实际上是调用执行了fn函数 148 | const runner = _effect.run.bind(_effect); //直接调用runnner 149 | runner.effect = _effect; 150 | return runner; 151 | } 152 | const targetMap = new WeakMap(); 153 | // 进行依赖收集track 154 | function track(target, key) { 155 | // 若不应该收集依赖则直接返回 156 | // if (!shouldTrack || activeEffect === undefined) { 157 | // return; 158 | // } 159 | if (!isTracking()) 160 | return; 161 | //1、先获取到key的依赖集合dep 162 | //所有对象的的以来集合targetMap -> 当前对象的依赖集合objMap -> 当前key的依赖集合 163 | let objMap = targetMap.get(target); 164 | // 如果没有初始化过 需要先初始化 165 | if (!objMap) { 166 | objMap = new Map(); 167 | targetMap.set(target, objMap); 168 | } 169 | //同理 如果没有初始化过 需要先初始化 170 | let dep = objMap.get(key); 171 | if (!dep) { 172 | dep = new Set(); //依赖不会重复 173 | objMap.set(key, dep); 174 | } 175 | //d将依赖函数添加给dep 176 | // if (!activeEffect) return; 177 | // if(dep.has(activeEffect)) return 178 | // dep.add(activeEffect); ? 怎么获取到fn? 添加一个全局变量activeEffect 179 | // activeEffect?.deps.push(dep); ? 180 | trackEffect(dep); 181 | } 182 | //重构 183 | function trackEffect(dep) { 184 | //看看dep之前有没有添加过,添加过的话 就不添加了 185 | if (dep.has(activeEffect)) 186 | return; 187 | dep.add(activeEffect); 188 | activeEffect.deps.push(dep); 189 | } 190 | //activeEffect可能为undefined 原因: 访问一个单纯的reactive对象,没有任何依赖的时候 activeEffect可能为undefined 191 | function isTracking() { 192 | return shouldTrack && activeEffect !== undefined; 193 | } 194 | console.log(targetMap.has(effect)); 195 | //触发依赖trigger 196 | function trigger(target, key) { 197 | // console.log("触发依赖了"); 198 | //1、先获取到key的依赖集合dep 199 | let objMap = targetMap.get(target); 200 | // console.log(objMap); 201 | let dep = objMap.get(key); 202 | console.log(objMap); 203 | //去执行dep里面的函数 204 | // dep.forEach((effect) => { 205 | // if (effect.scheduler) { 206 | // effect.scheduler(); 207 | // } else { 208 | // effect.run(); 209 | // } 210 | // }); 211 | triggerEffect(dep); 212 | } 213 | // 重构 214 | function triggerEffect(dep) { 215 | dep.forEach((effect) => { 216 | if (effect.scheduler) { 217 | effect.scheduler(); 218 | } 219 | else { 220 | effect.run(); 221 | } 222 | }); 223 | } 224 | 225 | // reactive和readonly对象复用代码的重构 226 | function createGetter(isReadonly = false, isShallow = false) { 227 | //true 228 | return function get(target, key) { 229 | //专门判断isReactive 230 | if (key === "__v_isReactive" /* ReactiveFlags.IS_REACTIVE */) { 231 | return !isReadonly; 232 | } 233 | //专门判断isReadonly 234 | else if (key === "__v_isReadonly" /* ReactiveFlags.IS_READONLY */) { 235 | return isReadonly; 236 | } 237 | const res = Reflect.get(target, key); 238 | //reactive对象的getter 进行依赖收集 //readonly对象不用进行依赖收集 239 | if (!isReadonly) { 240 | track(target, key); 241 | } 242 | //如果是shallowReadonly类型,就不用执行内部嵌套的响应式转换。也不用执行依赖收集 243 | if (isShallow) { 244 | return res; 245 | } 246 | //reactive、readonly对象嵌套的响应式转换 247 | if (isObject(res)) { 248 | //递归调用 249 | // isReadonly == true -> 表明是readonly对象 :是reactive对象 250 | return isReadonly ? readonly(res) : reactive(res); 251 | } 252 | return res; 253 | }; 254 | } 255 | function createSetter() { 256 | //只有reactive对象能够调用setter 257 | return function set(target, key, newVal) { 258 | const res = Reflect.set(target, key, newVal); 259 | //触发依赖 260 | trigger(target, key); 261 | return res; 262 | }; 263 | } 264 | // reactive对象getter和setter 265 | const get = createGetter(); 266 | const set = createSetter(); 267 | const mutableHandlers = { 268 | get, 269 | set, 270 | }; 271 | // readonly对象的getter和setter 272 | const readonlyGetter = createGetter(true); 273 | const readonlyHandlers = { 274 | get: readonlyGetter, 275 | set: function (target, key, newVal) { 276 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal); 277 | return true; 278 | }, 279 | }; 280 | const shallowReadonlyGetter = createGetter(true, true); 281 | const shallowReadonlyHandlers = { 282 | get: shallowReadonlyGetter, 283 | set: function (target, key, newVal) { 284 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal); 285 | return true; 286 | }, 287 | }; 288 | 289 | // import { track, trigger } from "./effect"; 290 | // import { track, trigger } from "./effect"; 291 | /** 292 | * reative 和 readonly 的get和set重复代码较多,进行代码抽取重构 293 | * 294 | */ 295 | // 工具函数 296 | function createReactiveObject(target, baseHandler) { 297 | if (!isObject(target)) { 298 | console.warn(`target${target}必须是一个对象`); 299 | return target; 300 | } 301 | return new Proxy(target, baseHandler); 302 | } 303 | //reactive函数 304 | // export function reactive(obj) { 305 | // return new Proxy(obj, { 306 | // // get(target, key) { 307 | // // //ToDo 收集依赖 308 | // // track(target, key); 309 | // // const res = Reflect.get(target, key); //返回属性的值 310 | // // return res; 311 | // // }, 312 | // // set(target, key, newVal) { 313 | // // //返回Boolean 返回 true 代表属性设置成功。 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。 314 | // // const res = Reflect.set(target, key, newVal); //返回一个 Boolean 值表明是否成功设置属性。 315 | // // //ToDo 触发依赖依赖 316 | // // trigger(target, key); 317 | // // return res; 318 | // // }, 319 | // get, 320 | // set, 321 | // }); 322 | // } 323 | function reactive(obj) { 324 | return createReactiveObject(obj, mutableHandlers); 325 | } 326 | //readonly函数 只读不能修改 327 | // export function readonly(obj) { 328 | // return new Proxy(obj, { 329 | // // get(target, key) { 330 | // // return Reflect.get(target, key); 331 | // // }, 332 | // // set(target, key, newValue) { 333 | // // console.warn( 334 | // // `target:${target} 对象是readonly对象,不能修改的属性 `, 335 | // // key, 336 | // // newValue 337 | // // ); 338 | // // return true; 339 | // // }, 340 | // get, 341 | // set, 342 | // }); 343 | // } 344 | function readonly(obj) { 345 | return createReactiveObject(obj, readonlyHandlers); 346 | } 347 | //shallowReadonly 348 | //创建一个 proxy,使其自身的 property为只读,但不执行嵌套对象的深度只读转换 (暴露原始值) 349 | // 自身property为readonly 内部嵌套不是readonly 350 | function shallowReadonly(obj) { 351 | return createReactiveObject(obj, shallowReadonlyHandlers); 352 | } 353 | 354 | function emit(instance, event, ...args) { 355 | console.log("emit" + event); 356 | // instance.props 有没有对应event的回调 357 | const { props } = instance; 358 | //tpp -> 359 | //先去写一个特定行为 -》 重构通用行为 360 | // const handler = props["onAdd"]; 361 | //add-foo ->addFoo 362 | // const camelize = (str: string) => { 363 | // return str.replace(/-(\w)/g, (_, c: string) => { 364 | // return c ? c.toUpperCase() : ""; 365 | // }); 366 | // }; 367 | // //addFoo ->AddFoo 368 | // const capitalize = (str: string) => { 369 | // return str.charAt(0).toUpperCase() + str.slice(1); 370 | // }; 371 | // console.log(capitalize(event)); 372 | // const str = capitalize(camelize(event)); 373 | // AddFoo -> noAddFoo 374 | // const toHandlerKey = (str: string) => { 375 | // return str ? "on" + str : ""; 376 | // }; 377 | //add-foo -> addFoo 378 | let str = camelize(event); 379 | //addFoo -> AddFoo 380 | str = capitalize(str); 381 | const handlerName = toHandlerKey(str); 382 | const handler = props[handlerName]; 383 | handler && handler(...args); 384 | } 385 | 386 | function initProps(instance, rawProps) { 387 | instance.props = rawProps || {}; 388 | } 389 | 390 | const PublicPropertiesMap = { 391 | $el: (i) => i.vnode.el, 392 | $slots: (i) => i.slots, 393 | $props: (i) => i.props, 394 | }; 395 | // * proxy的getter方法 get(target,key) 396 | const PublicInstanceProxyHandlers = { 397 | get({ _: instance }, key) { 398 | //这里的_:instance是解构 399 | // console.log("instance:", instance); 400 | //setupState 401 | const { setupState, props } = instance; 402 | // if (key in setupState) { 403 | // return setupState[key]; 404 | // } 405 | if (hasOwn(setupState, key)) { 406 | return setupState[key]; 407 | } 408 | else if (hasOwn(props, key)) { 409 | return props[key]; 410 | } 411 | //key ->$el 412 | const publicGetter = PublicPropertiesMap[key]; 413 | if (publicGetter) { 414 | return publicGetter(instance); 415 | } 416 | }, 417 | }; 418 | 419 | function initSlot(instance, children) { 420 | // instance.slots = Array.isArray(children) ? children : [children]; 421 | //children Object 422 | // const slots = {}; 423 | // for (const key in children) { 424 | // const value = children[key]; 425 | // slots[key] = normalizeSlotsValue(value); 426 | // } 427 | // 428 | // const { vnode } = instance; 429 | // if (vnode.shapeFlag & shapeFlags.slot_children) { 430 | // console.log("isntance.slots:" + instance.slots); 431 | normalizeObjectSlots(children, instance.slots); 432 | // } 433 | // instance.slots = slots; 434 | } 435 | function normalizeObjectSlots(children, slots) { 436 | // console.log("isntance.slots:" + JSON.stringify(slots));\ 437 | console.log("children", children); 438 | for (const key in children) { 439 | const value = children[key]; 440 | slots[key] = (props) => normalizeSlotsValue(value(props)); 441 | // slots[key] = (props) => normalizeSlotsValue(value); 442 | console.log("isntance.slots:" + slots); 443 | } 444 | } 445 | function normalizeSlotsValue(value) { 446 | return Array.isArray(value) ? value : [value]; 447 | } 448 | /** 449 | * 父组件通过children 传递 slot 类型为对象类型 450 | * 451 | * { 452 | * key:(props)=>{h()} 453 | * } 454 | * 通过遍历将所有插槽通过 key value的形式 保存在instance.slot对象中 使用this.$slots可以访问得到 455 | * 在子组件中进行插槽渲染的时候 通过renderSlots(this.$slots,name,props)函数 456 | * 每一个slot 都是一个函数, renderSlots函数中会执行插槽函数 生成vnode对象或者vnode对象组成的数组result 457 | * 然后将生成的结果通过createVnode(Fragment,{},result)包裹成vnode对象 458 | * 459 | * 又因为 result是vnode对象或者vnode对象组成的数组, 460 | * createVnode 传入的children 只能是数组或者string 461 | * 所有如果是vnode对象则需要先包裹一个[]数组,normalizeSlotsValue就是这个职责 462 | * 463 | */ 464 | 465 | // 创建当前组件实例对象 466 | function createComponentInstance(vnode, parent) { 467 | const component = { 468 | //初始化 469 | vnode, 470 | type: vnode.type, 471 | next: null, 472 | setupState: {}, 473 | props: {}, 474 | slots: {}, 475 | provides: parent ? parent.provides : {}, 476 | parent, 477 | emit: () => { }, 478 | isMounted: false, 479 | subTree: {}, //! elemnt 树 480 | }; 481 | component.emit = emit.bind(null, component); //绑定instance为this 并返回函数 这里emit是从外部引入的emit 482 | return component; 483 | } 484 | function setupComponent(instance) { 485 | // TODO 486 | // initProps() 487 | console.log(instance); 488 | // * 初始化props 489 | initProps(instance, instance.vnode.props); 490 | //! 初始化slot 491 | initSlot(instance, instance.vnode.children); 492 | setupStatefulComponent(instance); 493 | } 494 | function setupStatefulComponent(instance) { 495 | const Component = instance.type; 496 | //* 增加了代理对象 497 | //cxt 498 | console.log({ _: 123 }); 499 | console.log({ _: instance }); 500 | instance.proxy = new Proxy(//增加了代理对象 501 | { _: instance }, 502 | // get(target, key) { 503 | // //setupState 504 | // const { setupState } = instance; 505 | // if (key in setupState) { 506 | // return setupState[key]; 507 | // } 508 | // //key ->$el 509 | // if (key === "$el") { 510 | // return instance.vnode.el; 511 | // } 512 | // }, 513 | PublicInstanceProxyHandlers); 514 | const { setup } = Component; 515 | if (setup) { 516 | // currentInstance = instance; 517 | setCurrentInstance(instance); 518 | const setupResult = setup(shallowReadonly(instance.props), { 519 | emit: instance.emit, 520 | }); 521 | setCurrentInstance(null); 522 | handleSetupResult(instance, setupResult); 523 | } 524 | } 525 | function handleSetupResult(instance, setupResult) { 526 | // function Object 527 | // TODO function 528 | if (typeof setupResult === 'object') { 529 | instance.setupState = proxyRefs(setupResult); //setup返回值的ref对象 直接key访问,不用key.value 530 | } 531 | finishComponentSetup(instance); 532 | } 533 | function finishComponentSetup(instance) { 534 | const Component = instance.type; 535 | //如果用户不提供render函数 而是用的template 536 | if (compiler && !Component.render) { 537 | if (Component.template) { 538 | Component.render = compiler(Component.template); 539 | } 540 | } 541 | instance.render = Component.render; 542 | } 543 | //借助全局变量获取instccne 544 | let currentInstance = null; 545 | function getCurrentInstance() { 546 | return currentInstance; 547 | } 548 | function setCurrentInstance(instance) { 549 | currentInstance = instance; 550 | } 551 | let compiler; 552 | function registerRuntimeCompiler(_compiler) { 553 | compiler = _compiler; 554 | } 555 | 556 | function provide(key, value) { 557 | //存 558 | //key value 存在哪里???? 存在当前实例对象上 559 | //获取当前组件实例对象 560 | const currentInstance = getCurrentInstance(); 561 | if (currentInstance) { 562 | let { provides } = currentInstance; 563 | const parentProvides = currentInstance.parent.provides; 564 | //init 不能每次都初始化,只有第一次初始化 565 | //判断初始化状态 当前组件的provides = parentProvides 566 | if (provides === parentProvides) { 567 | provides = currentInstance.provides = Object.create(parentProvides); //利用原型原型链的机制 来进行多层inject provides 568 | } 569 | provides[key] = value; 570 | } 571 | } 572 | function inject(key, defaultValue) { 573 | //取 574 | const currentInstance = getCurrentInstance(); 575 | if (currentInstance) { 576 | const parentProvides = currentInstance.parent.provides; 577 | if (key in parentProvides) { 578 | return parentProvides[key]; 579 | } 580 | else if (defaultValue) { 581 | if (typeof defaultValue === 'function') { 582 | return defaultValue(); 583 | } 584 | return defaultValue; 585 | } 586 | } 587 | } 588 | /** 589 | * 590 | * 思路: provide提供数据 inject获取数据 591 | * 592 | * 593 | * 1、基础作用 父子间的provide和inject 594 | * provide(key,value) 595 | * provide在setup中使用, 我们通过getCurrentInstance 获取到当前组件实例,并将传入的参数通过key-value的形式保存到instance.provide上 596 | * 597 | * 598 | * inject(key) 599 | * 600 | * 在setup中使用, 通过getCurrentInstance 获取到组件实例,,通过instance.parent.provide来获取父组件的provide 601 | * 通过key 老获取到所需要的值 602 | * 603 | * 604 | * 2、跨层级的provide和inject 605 | * 606 | * 前面的实现只支持 父子间的provide和inject传递,更深层次的传递利用了原型和原型链的机制 607 | * 608 | * 父组件 parentProvide 609 | * 子组件 provide 610 | * 孙组件 inject 611 | * 612 | * provide currentInstance.provide = Object.create(parentProvide) 613 | * 614 | * 在孙组件中 使用inject(key) 查找key的value 615 | * 先找子组件提供的provide,找到就直接返回value, 616 | * 找不到就会通过原型链查找parentProvide上的key值,逐级向上直到找到值 617 | * 618 | * 3、init provide 619 | * 620 | * 在创建instance实例的时候, 我们设置 parent和 provide 的默认值、 621 | * parent = parentInstance 622 | * provide = parent ? parent.provide :{} 623 | * 所以 provide中我们获取的provide 和 parentProvide 在最初是一致的 624 | * 625 | * 在进行object.create()后 provide会变成一个空对象,其prototype 指向 parentProvide 626 | * 627 | * 然后在空对象上进行新值的添加,我们不能每次进行provide都进行一次object.create 这样,我们在同一个setup中 之前的多次的provide只会生效最后一次 628 | * 629 | * 所以我们只需要进行一次原型链的继承 630 | * 631 | * 而这一次就要在最初的时候进行, 就是provide = parentProvide 632 | */ 633 | 634 | function shouldUpdateComponent(preVnode, nextVnode) { 635 | const { props: prevProps } = preVnode; 636 | const { props: nextProps } = nextVnode; 637 | for (const key in nextProps) { 638 | if (nextProps[key] !== prevProps[key]) { 639 | return true; 640 | } 641 | } 642 | return false; 643 | } 644 | 645 | // import { render } from "./renderer"; 646 | function createAppAPI(render) { 647 | return function createApp(rootComponent) { 648 | return { 649 | mount(rootContainer) { 650 | const vnode = createVNode(rootComponent); 651 | render(vnode, rootContainer); 652 | }, 653 | }; 654 | }; 655 | } 656 | // export function createApp(rootComponent) { 657 | // return { 658 | // mount(rootContainer) { 659 | // const vnode = createVNode(rootComponent); 660 | // render(vnode, rootContainer); 661 | // }, 662 | // }; 663 | // } 664 | 665 | const queue = []; 666 | let isFlushPending = false; 667 | function nextTick(fn) { 668 | return fn ? Promise.resolve().then(fn) : Promise.resolve(); 669 | } 670 | function queueJobs(job) { 671 | if (!queue.includes(job)) { 672 | queue.push(job); 673 | } 674 | queueFlush(); 675 | } 676 | function queueFlush() { 677 | if (isFlushPending) 678 | return; 679 | isFlushPending = true; 680 | // Promise.resolve().then(() => { 681 | // isFlushPending = false; 682 | // let job; 683 | // while ((job = queue.shift())) { 684 | // job && job(); 685 | // } 686 | // }); 687 | nextTick(flushJobs); 688 | } 689 | function flushJobs() { 690 | isFlushPending = false; 691 | let job; 692 | while ((job = queue.shift())) { 693 | job && job(); 694 | } 695 | } 696 | 697 | function createRenderer(options) { 698 | const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options; 699 | function render(vnode, container) { 700 | // debugger; 701 | patch(null, vnode, container, null, null); 702 | } 703 | /** 704 | * 705 | * @param n1 老的vnode n1存在 更新逻辑 n1不存在 初始化逻辑 706 | * @param n2 新的vnode 707 | * @param container 容器 708 | * @param parentComponent 父组件 createInstance创建组件实例的时候 用得到 709 | */ 710 | function patch(n1, n2, container, parentComponent, anchor) { 711 | //TODO 判断vnode是不是一个element 712 | //是element 就应该处理element 713 | //如何去区分是element类型和component类型 :vnode.type 来判断 714 | // console.log(vnode.type); 715 | // const { type, shapeFlag } = vnode; 716 | const { type, shapeFlag } = n2; 717 | //Fragment -> 只渲染children 718 | switch (type) { 719 | case Fragment: 720 | processFragment(n1, n2, container, parentComponent, anchor); 721 | break; 722 | case Text: 723 | processText(n1, n2, container); 724 | break; 725 | default: 726 | if (shapeFlag & 1 /* shapeFlags.element */) { 727 | // if (typeof vnode.type === "string") { 728 | // element类型 729 | processElement(n1, n2, container, parentComponent, anchor); 730 | // } else if (isObject(vnode.type)) { 731 | } 732 | else if (shapeFlag & 2 /* shapeFlags.stateful_component */) { 733 | // component类型 734 | processComponent(n1, n2, container, parentComponent, anchor); 735 | } 736 | } 737 | } 738 | function processFragment(n1, n2, container, parentComponent, anchor) { 739 | mountChildren(n2, container, parentComponent, anchor); 740 | } 741 | function processText(n1, n2, container) { 742 | const { children } = n2; 743 | const textNode = (n2.el = document.createTextNode(children)); 744 | container.appendChild(textNode); 745 | } 746 | //element vnode.type为element类型 747 | function processElement(n1, n2, container, parentComponent, anchor) { 748 | //init 初始化 749 | if (!n1) { 750 | mountElement(n2, container, parentComponent, anchor); 751 | } 752 | else { 753 | //TODO UPDATE 754 | console.log('patchElement'); 755 | patchElement(n1, n2, container, parentComponent, anchor); 756 | } 757 | } 758 | //挂载element 759 | function mountElement(vnode, container, parentComponent, anchor) { 760 | //跨平台渲染 761 | //canvas 762 | // new Element() 763 | //Dom平台 764 | // const el = document.createElement("div") 765 | // const el = (n2.el = document.createElement(n2.type)); 766 | const el = (vnode.el = hostCreateElement(vnode.type)); 767 | //props 768 | // el.setttribute("id", "root"); 769 | const { props } = vnode; 770 | for (const key in props) { 771 | const val = props[key]; 772 | // console.log(key); 773 | // // const isOn = (key) => /^on[A-Z]/.test(key); 774 | // // if(isOn(key)){ 775 | // if (key.startsWith("on")) { 776 | // // console.log(key.split("on")[1]); 777 | // const event = key.slice(2).toLowerCase(); 778 | // el.addEventListener(event, val); 779 | // } else { 780 | // el.setAttribute(key, val); 781 | // } 782 | hostPatchProp(el, key, null, val); 783 | } 784 | //children 785 | // el.textContent = "hi mini-vue"; 786 | const { children, shapeFlag } = vnode; 787 | if (shapeFlag & 4 /* shapeFlags.text_children */) { 788 | // if (typeof children === "string") { 789 | //children为srting类型 790 | el.textContent = children; 791 | // } else if (Array.isArray(children)) { 792 | } 793 | else if (shapeFlag & 8 /* shapeFlags.array_children */) { 794 | //children 是数组类型 795 | // children.forEach((v) => { 796 | // patch(v, el); 797 | // }); 798 | mountChildren(vnode, el, parentComponent, anchor); 799 | } 800 | //挂载要渲染的el 801 | // document.appendChild(el) 802 | // container.appendChild(el); 803 | // container.append(el); 804 | hostInsert(el, container, anchor); 805 | } 806 | function mountChildren(childrenVnode, container, parentComponent, anchor) { 807 | childrenVnode.children.forEach((v) => { 808 | patch(null, v, container, parentComponent, anchor); 809 | }); 810 | } 811 | //更新element 812 | function patchElement(n1, n2, container, parentComponent, anchor) { 813 | console.log('n1:', n1); 814 | console.log('n2:', n2); 815 | //type 816 | //props 817 | const oldProps = n1.props || EMPTY_OBJ; 818 | const newProps = n2.props || EMPTY_OBJ; 819 | const el = (n2.el = n1.el); 820 | //1、key不变 value 改变 821 | //2、 value= undefined 、null ==> 删除key 822 | //3、 老的vnode 里的key 在新的element vnode不存在了 ==> 删除 823 | patchProps(el, oldProps, newProps); 824 | // children 825 | patchChildren(n1, n2, el, parentComponent, anchor); 826 | } 827 | // const EMPTY_OBJ = {} 828 | function patchProps(el, oldProps, newProps) { 829 | // debugger; 830 | if (oldProps !== newProps) { 831 | for (const key in newProps) { 832 | const prevProp = oldProps[key]; 833 | const nextProp = newProps[key]; 834 | if (prevProp !== nextProp) { 835 | hostPatchProp(el, key, prevProp, nextProp); 836 | } 837 | } 838 | //第三个场景 839 | if (oldProps !== EMPTY_OBJ) { 840 | for (const key in oldProps) { 841 | if (!(key in newProps)) { 842 | hostPatchProp(el, key, oldProps[key], null); 843 | } 844 | } 845 | } 846 | } 847 | } 848 | function patchChildren(n1, n2, container, parentComponent, anchor) { 849 | // ArrayToText 850 | //判断新节点的shapeFlag 判断 children是text还是array 851 | // const prevShapeFlag = n1.shapeFlag; 852 | const { shapeFlag: prevShapeFlag } = n1.shapeFlag; 853 | const { shapeFlag } = n2; 854 | const c1 = n1.children; 855 | const c2 = n2.children; 856 | if (shapeFlag & 4 /* shapeFlags.text_children */) { 857 | //新节点children为 text类型 858 | if (prevShapeFlag & 8 /* shapeFlags.array_children */) { 859 | //老节点children 为array类型 860 | //1、把老节点清空 861 | unmountedChildren(n1.children); 862 | //2、设置text 863 | hostSetElementText(container, c2); 864 | } 865 | else { 866 | //老节点children为 text类型 867 | if (c1 !== c2) { 868 | //设置text 869 | hostSetElementText(container, c2); 870 | } 871 | } 872 | } 873 | else { 874 | //新节点children为 array类型 875 | if (prevShapeFlag & 4 /* shapeFlags.text_children */) { 876 | //老节点children为text 877 | // 1、清空老节点 878 | hostSetElementText(container, ''); 879 | // 2、设置新节点 880 | mountChildren(n2, container, parentComponent, anchor); 881 | } 882 | else { 883 | //老节点children 为array类型 884 | patchKeyChildren(c1, c2, container, parentComponent, anchor); 885 | } 886 | } 887 | } 888 | function unmountedChildren(children) { 889 | for (let i = 0; i < children.length; i++) { 890 | const el = children[i].el; 891 | //remove 892 | hostRemove(el); 893 | } 894 | } 895 | function patchKeyChildren(c1, c2, container, parentComponent, anchor) { 896 | // debugger; 897 | let i = 0; 898 | let e1 = c1.length - 1; 899 | let e2 = c2.length - 1; 900 | //1.左侧对比 901 | while (i <= e1 && i <= e2) { 902 | const n1 = c1[i]; 903 | const n2 = c2[i]; 904 | if (isSameVnodeType(n1, n2)) { 905 | patch(n1, n2, container, parentComponent, anchor); 906 | } 907 | else { 908 | break; 909 | } 910 | i++; 911 | } 912 | //2.右侧对比 913 | while (i <= e1 && i <= e2) { 914 | const n1 = c1[e1]; 915 | const n2 = c2[e2]; 916 | if (isSameVnodeType(n1, n2)) { 917 | patch(n1, n2, container, parentComponent, anchor); 918 | } 919 | else { 920 | break; 921 | } 922 | e1--; 923 | e2--; 924 | } 925 | //3.新的比老的多 添加到指定的位置 926 | if (i > e1) { 927 | if (i <= e2) { 928 | const nextPos = e2 + 1; 929 | // const anchor = i + 1 < c2.length ? c2[nextPos].el : null; 有bug 930 | const anchor = nextPos < c2.length ? c2[nextPos].el : null; 931 | while (i <= e2) { 932 | patch(null, c2[i], container, parentComponent, anchor); 933 | i++; 934 | } 935 | } 936 | } 937 | else if (i > e2) { 938 | //4、新的比老少 939 | while (i <= e1) { 940 | hostRemove(c1[i].el); 941 | i++; 942 | } 943 | } 944 | else { 945 | //中间对比 946 | let s1 = i; 947 | let s2 = i; 948 | let patched = 0; 949 | const toBePatch = e2 - s2 + 1; //记录新节点的数量 用于老节点多余新节点时 删除逻辑的优化 950 | console.log(s2, e2); 951 | console.log('toBePatch', toBePatch); 952 | //建立新child.key的映射表 953 | const keyToNewIndexMap = new Map(); 954 | //简历 最长递归子序列的映射表 最终结果是中间老节点新节点都有的节点在老节点数组的索引+1 排序 955 | const newIndexToOldIndexMap = new Array(toBePatch); 956 | let moved = false; 957 | let maxNewIndexSoFar = 0; 958 | //初始化 最长递归子序列的映射表 959 | for (let i = 0; i < toBePatch; i++) { 960 | newIndexToOldIndexMap[i] = 0; 961 | } 962 | //将新的需要更新的(key:索引) 添加到映射表中 963 | for (let i = s2; i <= e2; i++) { 964 | let nextChild = c2[i]; 965 | keyToNewIndexMap.set(nextChild.key, i); 966 | } 967 | for (let i = s1; i <= e1; i++) { 968 | const prevChild = c1[i]; 969 | if (patched >= toBePatch) { 970 | //patch的数量大于需要更新的数量时 表示新的需要更新的已经更新完毕,剩下多的可以直接删除 971 | hostRemove(prevChild.el); 972 | continue; 973 | } 974 | let newIndex; 975 | if (prevChild.key != null) { 976 | //有key情况 977 | newIndex = keyToNewIndexMap.get(prevChild.key); 978 | } 979 | else { 980 | //无key 981 | for (let j = s2; j <= e2; j++) { 982 | if (isSameVnodeType(prevChild, c2[j])) { 983 | newIndex = j; 984 | break; 985 | } 986 | } 987 | } 988 | //删除逻辑 989 | if (newIndex === undefined) { 990 | console.log('删除'); 991 | hostRemove(prevChild.el); 992 | } 993 | else { 994 | if (newIndex >= maxNewIndexSoFar) { 995 | maxNewIndexSoFar = newIndex; 996 | } 997 | else { 998 | moved = true; 999 | } 1000 | //新老节点建立映射关系 1001 | newIndexToOldIndexMap[newIndex - s2] = i + 1; //i+1 避免i=0的情况 1002 | //更新逻辑 1003 | patch(prevChild, c2[newIndex], container, parentComponent, null); 1004 | patched++; //更新一次 数量+1 1005 | } 1006 | } 1007 | //得到最长递增子序列 1008 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : []; 1009 | console.log('newIndexToOldIndexMap', newIndexToOldIndexMap); 1010 | console.log('最长递增子序列', increasingNewIndexSequence); 1011 | let j = increasingNewIndexSequence.length - 1; 1012 | for (let i = toBePatch - 1; i >= 0; i--) { 1013 | const nextIndex = i + s2; 1014 | const nextChild = c2[nextIndex]; 1015 | const anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null; 1016 | if (newIndexToOldIndexMap[i] === 0) { 1017 | console.log('新增'); 1018 | patch(null, nextChild, container, parentComponent, anchor); 1019 | } 1020 | else if (moved) { 1021 | if (j < 0 || i !== increasingNewIndexSequence[j]) { 1022 | console.log('移动位置'); 1023 | hostInsert(nextChild.el, container, anchor); 1024 | } 1025 | else { 1026 | j--; 1027 | } 1028 | } 1029 | } 1030 | } 1031 | } 1032 | function isSameVnodeType(n1, n2) { 1033 | return n1.type === n2.type && n1.key === n2.key; 1034 | } 1035 | //componentvnode.type为component类型 1036 | function processComponent(n1, n2, container, parentComponent, anchor) { 1037 | if (!n1) { 1038 | mountComponent(n1, n2, container, parentComponent, anchor); 1039 | } 1040 | else { 1041 | updateComponent(n1, n2); 1042 | } 1043 | } 1044 | //组件初始化 1045 | function mountComponent(n1, initialVNode, container, parentComponent, anchor) { 1046 | // * 创建当前组件实例 方便后续对当前组件的操作 1047 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent)); 1048 | // * 处理组件setup函数 1049 | setupComponent(instance); 1050 | setupRenderEffect(instance, initialVNode, container, anchor); 1051 | } 1052 | function setupRenderEffect(instance, initialVNode, container, anchor) { 1053 | //响应式 1054 | instance.update = effect(() => { 1055 | // 区分式初始化还是更新 1056 | if (!instance.isMounted) { 1057 | //init 1058 | console.log('init'); 1059 | const { proxy } = instance; 1060 | // instance.subtree 用于存储当前组件的render函数生成 vnode? 是否可以称为vnode? 1061 | const subTree = (instance.subTree = instance.render.call(proxy, proxy)); //subTree 虚拟节点树 vnode树 1062 | console.log(subTree); 1063 | patch(null, subTree, container, instance, anchor); 1064 | //element ->mount 1065 | // ! 将render生成的vnode的el 存储到组件的el 1066 | initialVNode.el = subTree.el; 1067 | instance.isMounted = true; 1068 | } 1069 | else { 1070 | //update 1071 | console.log('update'); 1072 | const { proxy, next, vnode } = instance; 1073 | if (next) { 1074 | next.el = vnode.el; 1075 | updateComponentPreRender(instance, next); 1076 | } 1077 | const subTree = instance.render.call(proxy, proxy); //subTree 虚拟节点树 vnode树 1078 | console.log(subTree); 1079 | const preSubTree = instance.subTree; 1080 | console.log(preSubTree); 1081 | console.log(subTree); 1082 | instance.subTree = subTree; //将现在的subTree更新到instance.subTree 方便后续再次更新 1083 | patch(preSubTree, subTree, container, instance, anchor); 1084 | } 1085 | }, { 1086 | scheduler() { 1087 | console.log('scheduler执行啦'); 1088 | queueJobs(instance.update); 1089 | }, 1090 | }); 1091 | } 1092 | function updateComponent(n1, n2) { 1093 | //获取到挂载到vnode上的component 1094 | const instance = (n2.component = n1.component); 1095 | if (shouldUpdateComponent(n1, n2)) { 1096 | instance.next = n2; 1097 | instance.update(); // 利用effect返回值runner 调用runner()会再次执行fn 1098 | } 1099 | else { 1100 | n2.el = n1.el; 1101 | instance.vnode = n2; 1102 | } 1103 | } 1104 | return { 1105 | createApp: createAppAPI(render), 1106 | }; 1107 | } 1108 | //最长递增子序列 return 递归子序列的索引数组 1109 | function getSequence(arr) { 1110 | const p = arr.slice(); 1111 | const result = [0]; 1112 | let i, j, u, v, c; 1113 | const len = arr.length; 1114 | for (i = 0; i < len; i++) { 1115 | const arrI = arr[i]; 1116 | if (arrI !== 0) { 1117 | j = result[result.length - 1]; 1118 | if (arr[j] < arrI) { 1119 | p[i] = j; 1120 | result.push(i); 1121 | continue; 1122 | } 1123 | u = 0; 1124 | v = result.length - 1; 1125 | while (u < v) { 1126 | c = (u + v) >> 1; 1127 | if (arr[result[c]] < arrI) { 1128 | u = c + 1; 1129 | } 1130 | else { 1131 | v = c; 1132 | } 1133 | } 1134 | if (arrI < arr[result[u]]) { 1135 | if (u > 0) { 1136 | p[i] = result[u - 1]; 1137 | } 1138 | result[u] = i; 1139 | } 1140 | } 1141 | } 1142 | u = result.length; 1143 | v = result[u - 1]; 1144 | while (u-- > 0) { 1145 | result[u] = v; 1146 | v = p[v]; 1147 | } 1148 | return result; 1149 | } 1150 | //组件更新之前 需要将组件中的props 等数据先更新 1151 | function updateComponentPreRender(instance, nextVndoe) { 1152 | instance.vnode = nextVndoe; 1153 | instance.next = null; 1154 | instance.props = nextVndoe.props; 1155 | } 1156 | 1157 | // export function ref(val) { 1158 | // ref接口的实现类 对操作进行封装 1159 | class RefImpl { 1160 | constructor(value) { 1161 | this.__v_isRef = true; // 判断是ref的标识 1162 | // 将传入的值赋值给实例的私有属性property_value 1163 | this._rawValue = value; 1164 | //value 为对象的话 需要转换为reactive包裹value 1165 | // this._value = isObject(value) ? reactive(value) : value; 1166 | this._value = convert(value); 1167 | this.dep = new Set(); 1168 | } 1169 | get value() { 1170 | if (isTracking()) { 1171 | // 进行依赖收集 1172 | trackEffect(this.dep); 1173 | } 1174 | return this._value; 1175 | } 1176 | set value(val) { 1177 | //如果value是reactive对象的时候 this._value 为Proxy 1178 | // 提前声明一个this._rawValue 来存储并进行比较 1179 | if (Object.is(val, this._rawValue)) 1180 | return; // ref.value = 2 ref.value = 2 两次赋值相同值的操作 不会执行effect 1181 | this._rawValue = val; 1182 | // this._value = isObject(val) ? reactive(val) : val; 1183 | this._value = convert(val); //处理值 如果是对象 ->转为reactive对象 不是对象 返回原值 1184 | triggerEffect(this.dep); 1185 | } 1186 | } 1187 | function convert(val) { 1188 | return isObject(val) ? reactive(val) : val; 1189 | } 1190 | //ref 1191 | function ref(val) { 1192 | return new RefImpl(val); 1193 | } 1194 | //isRef 1195 | function isRef(val) { 1196 | return !!(val.__v_isRef === true); 1197 | } 1198 | //unRef 1199 | function unRef(val) { 1200 | return isRef(val) ? val.value : val; 1201 | } 1202 | //proxyRef 应用场景: template中使用setup中return的ref 不需要使用ref.value 1203 | function proxyRefs(objectWithRefs) { 1204 | //怎么知道调用getter 和setter ? ->proxy 1205 | return new Proxy(objectWithRefs, { 1206 | get(target, key) { 1207 | //get -> age(ref) 那么就给他返回 .value 1208 | // not ref -> return value 1209 | return unRef(Reflect.get(target, key)); 1210 | }, 1211 | set(target, key, newVal) { 1212 | //当前需要修改的值是ref对象,同时修改值不是ref 1213 | if (isRef(target[key]) && !isRef(newVal)) { 1214 | target[key].value = newVal; 1215 | return true; 1216 | } 1217 | else { 1218 | return Reflect.set(target, key, newVal); 1219 | } 1220 | }, 1221 | }); 1222 | } 1223 | 1224 | function add(a, b) { 1225 | return a + b; 1226 | } 1227 | 1228 | //创建element元素 1229 | function createElement(type) { 1230 | return document.createElement(type); 1231 | } 1232 | // function patchProp(el, key, preVal, nextVal) { 1233 | // // const isOn = (key) => /^on[A-Z]/.test(key); 1234 | // // if(isOn(key)){ 1235 | // if (key.startsWith("on")) { 1236 | // // console.log(key.split("on")[1]); 1237 | // const event = key.slice(2).toLowerCase(); 1238 | // el.addEventListener(event, nextVal); 1239 | // } else { 1240 | // if (nextVal === undefined || nextVal === null) { 1241 | // el.removeAttribute(key); 1242 | // } else { 1243 | // el.setAttribute(key, nextVal); 1244 | // } 1245 | // } 1246 | // } 1247 | //创建、更新props 1248 | function patchProp(el, key, prevVal, nextVal) { 1249 | const isOn = (key) => /^on[A-Z]/.test(key); 1250 | if (isOn(key)) { 1251 | const event = key.slice(2).toLowerCase(); 1252 | el.addEventListener(event, nextVal); 1253 | } 1254 | else { 1255 | if (nextVal === "undefined" || nextVal === null) { 1256 | el.removeAttribute(key); 1257 | } 1258 | else { 1259 | el.setAttribute(key, nextVal); 1260 | } 1261 | } 1262 | } 1263 | //指定位置插入 1264 | function insert(child, parent, anchor) { 1265 | //只是添加到后面 1266 | // parent.append(child); 1267 | // 添加到指定位置 1268 | parent.insertBefore(child, anchor || null); 1269 | } 1270 | //删除children 1271 | function remove(child) { 1272 | const parent = child.parentNode; 1273 | if (parent) { 1274 | parent.removeChild(child); 1275 | } 1276 | } 1277 | function setElementText(el, text) { 1278 | el.textContent = text; 1279 | } 1280 | const renderer = createRenderer({ 1281 | createElement, 1282 | patchProp, 1283 | insert, 1284 | remove, 1285 | setElementText, 1286 | }); 1287 | function createApp(...args) { 1288 | return renderer.createApp(...args); 1289 | } 1290 | 1291 | var runtimeDom = /*#__PURE__*/Object.freeze({ 1292 | __proto__: null, 1293 | createApp: createApp, 1294 | h: h, 1295 | renderSlots: renderSlots, 1296 | createTextVnode: createTextVnode, 1297 | createElementVnode: createVNode, 1298 | getCurrentInstance: getCurrentInstance, 1299 | registerRuntimeCompiler: registerRuntimeCompiler, 1300 | provide: provide, 1301 | inject: inject, 1302 | createRenderer: createRenderer, 1303 | nextTick: nextTick, 1304 | toDisplayString: toDisplayString, 1305 | ref: ref, 1306 | proxyRefs: proxyRefs, 1307 | add: add 1308 | }); 1309 | 1310 | const To_Display_String = Symbol("toDisplayString"); 1311 | const Create_ELEMENT_Vnode = Symbol("createElementVnode"); 1312 | const helperMapName = { 1313 | [To_Display_String]: "toDisplayString ", 1314 | [Create_ELEMENT_Vnode]: "createElementVnode ", 1315 | }; 1316 | 1317 | // import { isString } from "../../shared"; 1318 | function generate(ast) { 1319 | const context = createCodegenContext(); 1320 | const { push } = context; 1321 | genFunctionPreamble(ast, context); 1322 | const functionName = "render"; 1323 | const args = ["_ctx", "_cache"]; 1324 | const signature = args.join(", "); 1325 | push(`function ${functionName}(${signature}){`); 1326 | push("return "); 1327 | genNode(ast.codegenNode, context); 1328 | push("}"); 1329 | return { 1330 | code: context.code, 1331 | }; 1332 | } 1333 | function genFunctionPreamble(ast, context) { 1334 | const { push } = context; 1335 | const VueBinging = "Vue"; 1336 | const aliasHelper = (s) => `${helperMapName[s]}:_${helperMapName[s]}`; 1337 | if (ast.helpers.length > 0) { 1338 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`); 1339 | } 1340 | push("\n"); 1341 | push("return "); 1342 | } 1343 | function createCodegenContext() { 1344 | const context = { 1345 | code: "", 1346 | push(source) { 1347 | context.code += source; 1348 | }, 1349 | helper(key) { 1350 | return `_${helperMapName[key]}`; 1351 | }, 1352 | }; 1353 | return context; 1354 | } 1355 | function genNode(node, context) { 1356 | switch (node.type) { 1357 | case 3 /* NodeTypes.TEXT */: 1358 | genText(node, context); 1359 | break; 1360 | case 0 /* NodeTypes.INTERPOLATION */: 1361 | genInterpolation(node, context); 1362 | break; 1363 | case 1 /* NodeTypes.SIMPLE_EXPRESSION */: 1364 | genExpression(node, context); 1365 | break; 1366 | case 2 /* NodeTypes.ELEMENT */: 1367 | genElement(node, context); 1368 | break; 1369 | case 5 /* NodeTypes.COMPOUND_EXPRESSION */: 1370 | genCompoundExpression(node, context); 1371 | break; 1372 | } 1373 | } 1374 | function genCompoundExpression(node, context) { 1375 | const { push } = context; 1376 | const children = node.children; 1377 | for (let i = 0; i < children.length; i++) { 1378 | const child = children[i]; 1379 | if (isString(child)) { 1380 | push(child); 1381 | } 1382 | else { 1383 | genNode(child, context); 1384 | } 1385 | } 1386 | } 1387 | function genElement(node, context) { 1388 | const { push, helper } = context; 1389 | const { tag, children, props } = node; 1390 | push(`${helper(Create_ELEMENT_Vnode)}(`); 1391 | genNodeList(genNullable([tag, props, children]), context); 1392 | push(")"); 1393 | } 1394 | function genNodeList(nodes, context) { 1395 | const { push } = context; 1396 | for (let i = 0; i < nodes.length; i++) { 1397 | const node = nodes[i]; 1398 | if (isString(node)) { 1399 | push(node); 1400 | } 1401 | else { 1402 | genNode(node, context); 1403 | } 1404 | if (i < nodes.length - 1) { 1405 | push(", "); 1406 | } 1407 | } 1408 | } 1409 | function genNullable(args) { 1410 | return args.map((arg) => arg || "null"); 1411 | } 1412 | function genExpression(node, context) { 1413 | const { push } = context; 1414 | push(`${node.content}`); 1415 | } 1416 | function genInterpolation(node, context) { 1417 | const { push, helper } = context; 1418 | push(`${helper(To_Display_String)}(`); 1419 | genNode(node.content, context); 1420 | push(")"); 1421 | } 1422 | function genText(node, context) { 1423 | const { push } = context; 1424 | push(`'${node.content}'`); 1425 | } 1426 | 1427 | function baseParse(content) { 1428 | //message 1429 | const context = createParseContext(content); //{source:message} 1430 | //重构 1431 | return createRoot(parseChildren(context, [])); // 1432 | // return { 1433 | // children: [ 1434 | // { 1435 | // type: "interpolation", 1436 | // content: { type: "simple_expression", content: "message" }, 1437 | // }, 1438 | // ], 1439 | // }; 1440 | // return createRoot([ 1441 | // { 1442 | // type: "interpolation", 1443 | // content: { type: "simple_expression", content: "message" }, 1444 | // }, 1445 | // ]); 1446 | } 1447 | function createParseContext(content) { 1448 | return { 1449 | source: content, 1450 | }; 1451 | } 1452 | function createRoot(children) { 1453 | return { 1454 | children, 1455 | type: 4 /* NodeTypes.ROOT */, 1456 | }; 1457 | } 1458 | function parseChildren(context, ancestors) { 1459 | // ancestors 祖先 1460 | const nodes = []; 1461 | while (!isEnd(context, ancestors)) { 1462 | let node; 1463 | //解析插值{{}} 1464 | if (context.source.startsWith("{{")) { 1465 | node = parseInterpolation(context); 1466 | } 1467 | else if (context.source[0] === "<") { 1468 | //element类型 1469 | if (/[a-z]/.test(context.source[1])) { 1470 | console.log("parse.element"); 1471 | node = parseElement(context, ancestors); 1472 | } 1473 | } 1474 | // } else { 1475 | // //text类型 1476 | // node = parseText(context); 1477 | // } 1478 | if (!node) { 1479 | node = parseText(context); 1480 | } 1481 | nodes.push(node); 1482 | } 1483 | return nodes; 1484 | // return [ 1485 | // { 1486 | // type: "interpolation", 1487 | // content: { type: "simple_expression", content: "message" }, 1488 | // }, 1489 | // ]; 1490 | } 1491 | function isEnd(context, ancestors) { 1492 | //
1493 | //结束的条件 1494 | //1、context.source 没有值的时候 1495 | //2、当遇到结束标签时候 1496 | //2 1497 | const s = context.source; 1498 | // 1499 | if (s.startsWith("= 0; i--) { 1501 | const tag = ancestors[i].tag; 1502 | if (startsWithEndTagOpen(s, tag)) { 1503 | return true; 1504 | } 1505 | } 1506 | } 1507 | // if (parentTag && s.startsWith(``)) { 1508 | // return true; 1509 | // } 1510 | //1 1511 | return !context.source; 1512 | } 1513 | //element类型parse 1514 | function parseElement(context, ancestors) { 1515 | //implement
1516 | // 1、解析tag 1517 | // 2、删除处理完成的代码 1518 | //1 1519 | // const match: any = /^<([a-z]*)/i.exec(context.source); // 1520 | // console.log(match); // [ '', groups: undefined ] 1521 | // const tag = match[1]; 1522 | // console.log(tag); //div 1523 | // //2 删除处理完成的代码 1524 | // advanceBy(context, match[0].length); 1525 | // console.log(context.source); //> 1526 | // advanceBy(context, 1); 1527 | // console.log(context.source); // 1528 | // return { 1529 | // type: NodeTypes.ELEMENT, 1530 | // tag, 1531 | // }; 1532 | // 重构 1533 | const element = parseTag(context, 0 /* TagType.Start */); 1534 | ancestors.push(element); 1535 | element.children = parseChildren(context, ancestors); 1536 | ancestors.pop(); 1537 | // if (context.source.slice(2, 2 + element.tag.length) === element.tag) { 1538 | //重构 1539 | if (startsWithEndTagOpen(context.source, element.tag)) { 1540 | parseTag(context, 1 /* TagType.End */); 1541 | } 1542 | else { 1543 | throw new Error(`缺少结束标签:${element.tag}`); 1544 | } 1545 | console.log("-------", context.source); 1546 | return element; 1547 | } 1548 | function parseTag(context, TagType) { 1549 | //implement
1550 | // 1、解析tag 1551 | // 2、删除处理完成的代码 1552 | //1 1553 | const match = /^<\/?([a-z]*)/i.exec(context.source); 1554 | console.log(match); // [ '', groups: undefined ] 1555 | const tag = match[1]; 1556 | console.log(tag); //div 1557 | // 2 1558 | advanceBy(context, match[0].length); //> 1559 | advanceBy(context, 1); // 1560 | if (tag === TagType.End) 1561 | return; 1562 | return { 1563 | type: 2 /* NodeTypes.ELEMENT */, 1564 | tag, 1565 | }; 1566 | } 1567 | //{{}}插值类型parse 1568 | function parseInterpolation(context) { 1569 | console.log(context); 1570 | const openDelimiter = "{{"; 1571 | const closeDelimiter = "}}"; 1572 | //{{message}}解析 1573 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length); 1574 | advanceBy(context, openDelimiter.length); 1575 | // context.source = context.source.slice(openDelimiter.length); // message}} 1576 | console.log(context.source); 1577 | const rawContentLength = closeIndex - openDelimiter.length; 1578 | // const rawContent = context.source.slice(0, rawContentLength); // message 1579 | const rawContent = parseTextData(context, rawContentLength); 1580 | //边缘处理 利于 {{ message}} 双括号中有空格 1581 | const content = rawContent.trim(); 1582 | console.log(content); 1583 | // 例如{{message}} {{message}}处理完毕的 删除掉 剩下 1584 | // context.source = context.source.slice(closeDelimiter.length); 1585 | advanceBy(context, closeDelimiter.length); 1586 | console.log("context.source", context.source); 1587 | return { 1588 | type: 0 /* NodeTypes.INTERPOLATION */, 1589 | content: { type: 1 /* NodeTypes.SIMPLE_EXPRESSION */, content: content }, //"simple_expression" 1590 | }; 1591 | } 1592 | //TEXT类型parse 1593 | function parseText(context) { 1594 | //1、获取content 1595 | //2、推进代码 : 删除已经处理的代码 1596 | //1 1597 | // const content = context.source.slice(0, context.source.length); 1598 | // console.log(content); 1599 | //2 1600 | // advanceBy(context, content.length); 1601 | // console.log(context.source); 1602 | let endIndex = context.source.length; 1603 | let endTokens = ["<", "{{"]; 1604 | for (let i = 0; i < endTokens.length; i++) { 1605 | const index = context.source.indexOf(endTokens[i]); 1606 | if (index !== -1 && endIndex > index) { 1607 | //!==-1 证明没有插值 1608 | endIndex = index; 1609 | } 1610 | } 1611 | //重构 1612 | const content = parseTextData(context, endIndex); 1613 | console.log("content _______", content); 1614 | return { 1615 | type: 3 /* NodeTypes.TEXT */, 1616 | content, 1617 | }; 1618 | } 1619 | //推进工具函数 1620 | function advanceBy(context, length) { 1621 | context.source = context.source.slice(length); 1622 | } 1623 | //裁剪工具函数 1624 | function parseTextData(context, length) { 1625 | const content = context.source.slice(0, length); 1626 | advanceBy(context, length); 1627 | return content; 1628 | } 1629 | function startsWithEndTagOpen(source, tag) { 1630 | return source.startsWith(" { 1708 | context.helper(Create_ELEMENT_Vnode); 1709 | // 中间处理层 1710 | const vnodeTag = `"${node.tag}"`; 1711 | //props 1712 | let vnodeProps; 1713 | // children 1714 | const children = node.children; 1715 | let vnodeChildren = children[0]; 1716 | // const vnodeElement = { 1717 | // type: NodeTypes.ELEMENT, 1718 | // tag: vnodeTag, 1719 | // prpos: vnodeProps, 1720 | // children: vnodeChildren, 1721 | // }; 1722 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren); 1723 | }; 1724 | } 1725 | } 1726 | 1727 | function transformExpression(node) { 1728 | if (node.type === 0 /* NodeTypes.INTERPOLATION */) { 1729 | node.content = processExpression(node.content); 1730 | } 1731 | } 1732 | function processExpression(node) { 1733 | node.content = `_ctx.${node.content}`; 1734 | return node; 1735 | } 1736 | 1737 | function isText(node) { 1738 | return node.type === 3 /* NodeTypes.TEXT */ || node.type === 0 /* NodeTypes.INTERPOLATION */; 1739 | } 1740 | 1741 | function transformText(node) { 1742 | if (node.type === 2 /* NodeTypes.ELEMENT */) { 1743 | return () => { 1744 | const { children } = node; 1745 | let currentContainer; 1746 | for (let i = 0; i < children.length; i++) { 1747 | const child = children[i]; 1748 | if (isText(child)) { 1749 | for (let j = i + 1; j < children.length; j++) { 1750 | const next = children[j]; 1751 | if (isText(next)) { 1752 | if (!currentContainer) { 1753 | currentContainer = children[i] = { 1754 | type: 5 /* NodeTypes.COMPOUND_EXPRESSION */, 1755 | children: [child], 1756 | }; 1757 | } 1758 | currentContainer.children.push(" + "); 1759 | currentContainer.children.push(next); 1760 | children.splice(j, 1); 1761 | j--; 1762 | } 1763 | else { 1764 | currentContainer = undefined; 1765 | break; 1766 | } 1767 | } 1768 | } 1769 | } 1770 | }; 1771 | } 1772 | } 1773 | 1774 | function baseCompile(template) { 1775 | const ast = baseParse(template); 1776 | transform(ast, { 1777 | nodeTransforms: [transformExpression, transformElement, transformText], 1778 | }); 1779 | console.log("ast_______", ast); 1780 | return generate(ast); 1781 | } 1782 | 1783 | // mini-vue 的出口 1784 | // render 1785 | function compoileToFunction(template) { 1786 | const { code } = baseCompile(template); 1787 | const render = new Function("Vue", code)(runtimeDom); 1788 | return render; 1789 | } 1790 | registerRuntimeCompiler(compoileToFunction); 1791 | 1792 | export { add, createApp, createVNode as createElementVnode, createRenderer, createTextVnode, getCurrentInstance, h, inject, nextTick, provide, proxyRefs, ref, registerRuntimeCompiler, renderSlots, toDisplayString }; 1793 | -------------------------------------------------------------------------------- /mark.md: -------------------------------------------------------------------------------- 1 | √ :掌握 2 | × :没掌握 3 | √×:可惜可惜 4 | 5 | ## reactivty模块掌握复习情况 6 | 7 | 8 | * 基础的reactive对象 √ 9 | * 基础的effect函数 √ 10 | * 完善effect——返回runner函数 √ 11 | * 完善effect——接收scheduler √ 12 | * 完善effect——stop及其完善 × 13 | * 完善effect——接收onStop √× -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-vue", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "./lib/mini-vue.cjs.js", 6 | "module": "./lib/mini-vue.esm.js", 7 | "scripts": { 8 | "test": "jest", 9 | "build": "rollup -c rollup.config.js" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "jest": "^27.4.5" 16 | }, 17 | "devDependencies": { 18 | "@babel/core": "^7.16.5", 19 | "@babel/preset-env": "^7.16.5", 20 | "@babel/preset-typescript": "^7.16.5", 21 | "@rollup/plugin-typescript": "^8.3.0", 22 | "@types/jest": "^27.5.2", 23 | "babel-jest": "^27.4.5", 24 | "commitizen": "^4.2.4", 25 | "cz-conventional-changelog": "^3.3.0", 26 | "rollup": "^2.62.0", 27 | "tslib": "^2.3.1", 28 | "typescript": "^4.5.4" 29 | }, 30 | "config": { 31 | "commitizen": { 32 | "path": "./node_modules/cz-conventional-changelog" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import pkg from './package.json' 2 | 3 | import typescript from '@rollup/plugin-typescript' 4 | export default { 5 | 6 | input: "./src/index.ts", 7 | output: [ 8 | 9 | // 1.cjs commonjs 10 | // 2.esm esmodule 11 | { 12 | format: "cjs", 13 | // file: "lib/mini-vue.cjs.js" 14 | file: pkg.main 15 | }, 16 | { 17 | format: "es", 18 | // file: "lib/mini-vue.esm.js" 19 | file: pkg.module 20 | } 21 | ], 22 | 23 | plugins: [ 24 | typescript() 25 | ] 26 | 27 | } -------------------------------------------------------------------------------- /src/compiler-core/src/ast.ts: -------------------------------------------------------------------------------- 1 | import { Create_ELEMENT_Vnode } from "./runtimeHelpers"; 2 | 3 | export const enum NodeTypes { 4 | INTERPOLATION, 5 | SIMPLE_EXPRESSION, 6 | ELEMENT, 7 | TEXT, 8 | ROOT, 9 | COMPOUND_EXPRESSION, 10 | } 11 | 12 | export function createVNodeCall(context, tag, props, children) { 13 | context.helper(Create_ELEMENT_Vnode); 14 | 15 | return { 16 | type: NodeTypes.ELEMENT, 17 | tag, 18 | props, 19 | children, 20 | }; 21 | } 22 | -------------------------------------------------------------------------------- /src/compiler-core/src/codegen.ts: -------------------------------------------------------------------------------- 1 | // import { isString } from "../../shared"; 2 | // import { NodeTypes } from "./ast"; 3 | // import { Create_ELEMENT_Vnode, helperMapName, To_Display_String } from "./runtimeHelpers"; 4 | 5 | // export function generate(ast) { 6 | // // let code = ""; 7 | // const context: any = createCodegenContext(); 8 | // const { push } = context; 9 | 10 | // // const VueBinging = "Vue"; 11 | // // // const helpers = ["toDisplayString"]; 12 | // // const aliasHelper = (s) => `${s}:_${s}`; 13 | // // push(`const ${ast.helpers.map(aliasHelper).join(",")}= ${VueBinging} `); 14 | // // push("\n"); 15 | // // // code += "return"; 16 | // // push("return "); 17 | 18 | // //重构 19 | // genFunctionPreamble(context, ast); 20 | 21 | // const functionName = "render"; 22 | // const args = ["_cxt", "_cache"]; 23 | // const signature = args.join(","); 24 | 25 | // push(`function ${functionName}(${signature}){`); 26 | // // code += ` function ${functionName}(${signature}){`; 27 | 28 | // console.log(ast); 29 | 30 | // // code += `return`; 31 | // push(`return `); 32 | // genNode(ast.codegenNode, context); 33 | // // code += "}"; 34 | // push("}"); 35 | 36 | // return { 37 | // // code: `return function render(_cxt,_cache,$props,$setup,$data,,$options){ 38 | // // return "hi" 39 | // // }`, 40 | // code: context.code, 41 | // }; 42 | // } 43 | // function genNode(node: any, context: any) { 44 | // switch (node.type) { 45 | // case NodeTypes.TEXT: 46 | // genText(context, node); 47 | // break; 48 | // case NodeTypes.INTERPOLATION: 49 | // genInterpolation(node, context); 50 | // break; 51 | // case NodeTypes.SIMPLE_EXPRESSION: 52 | // genExpression(node, context); 53 | // break; 54 | // case NodeTypes.ELEMENT: 55 | // genElement(node, context); 56 | // break; 57 | // case NodeTypes.COMPOUND_EXPRESSION: 58 | // genCompoundExpression(node, context); 59 | // break; 60 | // default: 61 | // break; 62 | // } 63 | // } 64 | // function createCodegenContext() { 65 | // const context = { 66 | // code: "", 67 | // push(source) { 68 | // context.code += source; 69 | // }, 70 | // helper(key) { 71 | // return `_${helperMapName[key]}`; 72 | // }, 73 | // }; 74 | // return context; 75 | // } 76 | 77 | // function genFunctionPreamble(context: any, ast: any) { 78 | // const VueBinging = "Vue"; 79 | // // const helpers = ["toDisplayString"]; 80 | // const { push } = context; 81 | // const aliasHelper = (s) => `${helperMapName[s]}:_${helperMapName[s]}`; 82 | 83 | // if (ast.helpers.length > 0) { 84 | // push(`const {${ast.helpers.map(aliasHelper).join(",")}}= ${VueBinging} `); 85 | // } 86 | 87 | // push("\n"); 88 | // // // code += "return"; 89 | // push("return "); 90 | // } 91 | // function genText(context, node) { 92 | // const { push } = context; 93 | // // code += ` "${node.content}"`; 94 | // push(`'${node.content}'`); 95 | // // return code; 96 | // } 97 | 98 | // function genInterpolation(node: any, context: any) { 99 | // const { push, helper } = context; 100 | // console.log(node); 101 | 102 | // push(`_${helper(To_Display_String)}(`); 103 | // genNode(node.content, context); 104 | // push(")"); 105 | // } 106 | 107 | // function genExpression(node: any, context: any): any { 108 | // const { push } = context; 109 | 110 | // push(`${node.content}`); 111 | // } 112 | 113 | // function genElement(node: any, context: any) { 114 | // const { push, helper } = context; 115 | 116 | // const { tag, children, props } = node; 117 | 118 | // push(`${helper(Create_ELEMENT_Vnode)}(`); 119 | // genNodeList(genNullable([tag, props, children]), context); 120 | // // genNode(children, context); 121 | // // for (let i = 0; i < children.length; i++) { 122 | // // const child = children[i]; 123 | 124 | // // genNode(child, context); 125 | // // } 126 | 127 | // push(")"); 128 | // } 129 | // function genCompoundExpression(node: any, context: any) { 130 | // const { children } = node; 131 | // const { push } = context; 132 | // for (let i = 0; i < children.length; i++) { 133 | // const child = children[i]; 134 | // if (isString(child)) { 135 | // push(child); 136 | // } else { 137 | // genNode(child, context); 138 | // } 139 | // } 140 | // } 141 | // function genNullable(args: any[]) { 142 | // return args.map((arg) => { 143 | // arg || "null"; 144 | // }); 145 | // } 146 | 147 | // function genNodeList(nodes, context) { 148 | // const { push } = context; 149 | // for (let i = 0; i < nodes.length; i++) { 150 | // const node = nodes[i]; 151 | // if (isString(node)) { 152 | // push(node); 153 | // } else { 154 | // genNode(node, context); 155 | // } 156 | // if (i < nodes.length - 1) { 157 | // push(","); 158 | // } 159 | // } 160 | // } 161 | 162 | import { isString } from "../../shared"; 163 | import { NodeTypes } from "./ast"; 164 | import { Create_ELEMENT_Vnode, helperMapName, To_Display_String } from "./runtimeHelpers"; 165 | 166 | export function generate(ast) { 167 | const context = createCodegenContext(); 168 | const { push } = context; 169 | 170 | genFunctionPreamble(ast, context); 171 | 172 | const functionName = "render"; 173 | const args = ["_ctx", "_cache"]; 174 | const signature = args.join(", "); 175 | 176 | push(`function ${functionName}(${signature}){`); 177 | push("return "); 178 | genNode(ast.codegenNode, context); 179 | push("}"); 180 | 181 | return { 182 | code: context.code, 183 | }; 184 | } 185 | 186 | function genFunctionPreamble(ast, context) { 187 | const { push } = context; 188 | const VueBinging = "Vue"; 189 | const aliasHelper = (s) => `${helperMapName[s]}:_${helperMapName[s]}`; 190 | if (ast.helpers.length > 0) { 191 | push(`const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}`); 192 | } 193 | push("\n"); 194 | push("return "); 195 | } 196 | 197 | function createCodegenContext(): any { 198 | const context = { 199 | code: "", 200 | push(source) { 201 | context.code += source; 202 | }, 203 | helper(key) { 204 | return `_${helperMapName[key]}`; 205 | }, 206 | }; 207 | 208 | return context; 209 | } 210 | 211 | function genNode(node: any, context) { 212 | switch (node.type) { 213 | case NodeTypes.TEXT: 214 | genText(node, context); 215 | break; 216 | case NodeTypes.INTERPOLATION: 217 | genInterpolation(node, context); 218 | break; 219 | case NodeTypes.SIMPLE_EXPRESSION: 220 | genExpression(node, context); 221 | break; 222 | case NodeTypes.ELEMENT: 223 | genElement(node, context); 224 | break; 225 | case NodeTypes.COMPOUND_EXPRESSION: 226 | genCompoundExpression(node, context); 227 | break; 228 | 229 | default: 230 | break; 231 | } 232 | } 233 | 234 | function genCompoundExpression(node: any, context: any) { 235 | const { push } = context; 236 | const children = node.children; 237 | for (let i = 0; i < children.length; i++) { 238 | const child = children[i]; 239 | if (isString(child)) { 240 | push(child); 241 | } else { 242 | genNode(child, context); 243 | } 244 | } 245 | } 246 | 247 | function genElement(node: any, context: any) { 248 | const { push, helper } = context; 249 | const { tag, children, props } = node; 250 | push(`${helper(Create_ELEMENT_Vnode)}(`); 251 | genNodeList(genNullable([tag, props, children]), context); 252 | push(")"); 253 | } 254 | 255 | function genNodeList(nodes, context) { 256 | const { push } = context; 257 | 258 | for (let i = 0; i < nodes.length; i++) { 259 | const node = nodes[i]; 260 | if (isString(node)) { 261 | push(node); 262 | } else { 263 | genNode(node, context); 264 | } 265 | 266 | if (i < nodes.length - 1) { 267 | push(", "); 268 | } 269 | } 270 | } 271 | 272 | function genNullable(args: any) { 273 | return args.map((arg) => arg || "null"); 274 | } 275 | 276 | function genExpression(node: any, context: any) { 277 | const { push } = context; 278 | push(`${node.content}`); 279 | } 280 | 281 | function genInterpolation(node: any, context: any) { 282 | const { push, helper } = context; 283 | push(`${helper(To_Display_String)}(`); 284 | genNode(node.content, context); 285 | push(")"); 286 | } 287 | 288 | function genText(node: any, context: any) { 289 | const { push } = context; 290 | push(`'${node.content}'`); 291 | } 292 | -------------------------------------------------------------------------------- /src/compiler-core/src/compile.ts: -------------------------------------------------------------------------------- 1 | import { generate } from "./codegen"; 2 | import { baseParse } from "./parse"; 3 | import { transform } from "./transform"; 4 | import { transformElement } from "./transforms/tarnsformElement"; 5 | import { transformExpression } from "./transforms/transformExpression"; 6 | import { transformText } from "./transforms/transfromText"; 7 | 8 | export function baseCompile(template) { 9 | const ast: any = baseParse(template); 10 | transform(ast, { 11 | nodeTransforms: [transformExpression, transformElement, transformText], 12 | }); 13 | console.log("ast_______", ast); 14 | 15 | return generate(ast); 16 | } 17 | -------------------------------------------------------------------------------- /src/compiler-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./compile"; 2 | -------------------------------------------------------------------------------- /src/compiler-core/src/parse.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast"; 2 | 3 | const enum TagType { 4 | Start, 5 | End, 6 | } 7 | 8 | export function baseParse(content: string) { 9 | //message 10 | const context = createParseContext(content); //{source:message} 11 | //重构 12 | return createRoot(parseChildren(context, [])); // 13 | // return { 14 | // children: [ 15 | // { 16 | // type: "interpolation", 17 | // content: { type: "simple_expression", content: "message" }, 18 | // }, 19 | // ], 20 | // }; 21 | 22 | // return createRoot([ 23 | // { 24 | // type: "interpolation", 25 | // content: { type: "simple_expression", content: "message" }, 26 | // }, 27 | // ]); 28 | } 29 | 30 | function createParseContext(content): any { 31 | return { 32 | source: content, 33 | }; 34 | } 35 | function createRoot(children) { 36 | return { 37 | children, 38 | type: NodeTypes.ROOT, 39 | }; 40 | } 41 | 42 | function parseChildren(context, ancestors) { 43 | // ancestors 祖先 44 | const nodes: any = []; 45 | 46 | while (!isEnd(context, ancestors)) { 47 | let node; 48 | //解析插值{{}} 49 | if (context.source.startsWith("{{")) { 50 | node = parseInterpolation(context); 51 | } else if (context.source[0] === "<") { 52 | //element类型 53 | if (/[a-z]/.test(context.source[1])) { 54 | console.log("parse.element"); 55 | node = parseElement(context, ancestors); 56 | } 57 | } 58 | // } else { 59 | // //text类型 60 | // node = parseText(context); 61 | // } 62 | if (!node) { 63 | node = parseText(context); 64 | } 65 | nodes.push(node); 66 | } 67 | return nodes; 68 | // return [ 69 | // { 70 | // type: "interpolation", 71 | // content: { type: "simple_expression", content: "message" }, 72 | // }, 73 | // ]; 74 | } 75 | 76 | function isEnd(context, ancestors) { 77 | //
78 | 79 | //结束的条件 80 | //1、context.source 没有值的时候 81 | //2、当遇到结束标签时候 82 | 83 | //2 84 | 85 | const s = context.source; 86 | // 87 | if (s.startsWith("= 0; i--) { 89 | const tag = ancestors[i].tag; 90 | if (startsWithEndTagOpen(s, tag)) { 91 | return true; 92 | } 93 | } 94 | } 95 | 96 | // if (parentTag && s.startsWith(``)) { 97 | // return true; 98 | // } 99 | //1 100 | return !context.source; 101 | } 102 | 103 | //element类型parse 104 | function parseElement(context: any, ancestors) { 105 | //implement
106 | // 1、解析tag 107 | // 2、删除处理完成的代码 108 | 109 | //1 110 | // const match: any = /^<([a-z]*)/i.exec(context.source); // 111 | // console.log(match); // [ '', groups: undefined ] 112 | // const tag = match[1]; 113 | // console.log(tag); //div 114 | 115 | // //2 删除处理完成的代码 116 | // advanceBy(context, match[0].length); 117 | // console.log(context.source); //> 118 | // advanceBy(context, 1); 119 | // console.log(context.source); // 120 | 121 | // return { 122 | // type: NodeTypes.ELEMENT, 123 | // tag, 124 | // }; 125 | 126 | // 重构 127 | const element: any = parseTag(context, TagType.Start); 128 | ancestors.push(element); 129 | element.children = parseChildren(context, ancestors); 130 | ancestors.pop(); 131 | 132 | // if (context.source.slice(2, 2 + element.tag.length) === element.tag) { 133 | 134 | //重构 135 | if (startsWithEndTagOpen(context.source, element.tag)) { 136 | parseTag(context, TagType.End); 137 | } else { 138 | throw new Error(`缺少结束标签:${element.tag}`); 139 | } 140 | 141 | console.log("-------", context.source); 142 | 143 | return element; 144 | } 145 | 146 | function parseTag(context: any, TagType) { 147 | //implement
148 | // 1、解析tag 149 | // 2、删除处理完成的代码 150 | 151 | //1 152 | const match: any = /^<\/?([a-z]*)/i.exec(context.source); 153 | console.log(match); // [ '', groups: undefined ] 154 | const tag = match[1]; 155 | console.log(tag); //div 156 | 157 | // 2 158 | advanceBy(context, match[0].length); //> 159 | 160 | advanceBy(context, 1); // 161 | 162 | if (tag === TagType.End) return; 163 | 164 | return { 165 | type: NodeTypes.ELEMENT, 166 | tag, 167 | }; 168 | } 169 | 170 | //{{}}插值类型parse 171 | function parseInterpolation(context) { 172 | console.log(context); 173 | 174 | const openDelimiter = "{{"; 175 | const closeDelimiter = "}}"; 176 | 177 | //{{message}}解析 178 | const closeIndex = context.source.indexOf(closeDelimiter, openDelimiter.length); 179 | 180 | advanceBy(context, openDelimiter.length); 181 | 182 | // context.source = context.source.slice(openDelimiter.length); // message}} 183 | console.log(context.source); 184 | 185 | const rawContentLength = closeIndex - openDelimiter.length; 186 | // const rawContent = context.source.slice(0, rawContentLength); // message 187 | const rawContent = parseTextData(context, rawContentLength); 188 | 189 | //边缘处理 利于 {{ message}} 双括号中有空格 190 | const content = rawContent.trim(); 191 | console.log(content); 192 | 193 | // 例如{{message}} {{message}}处理完毕的 删除掉 剩下 194 | // context.source = context.source.slice(closeDelimiter.length); 195 | advanceBy(context, closeDelimiter.length); 196 | console.log("context.source", context.source); 197 | 198 | return { 199 | type: NodeTypes.INTERPOLATION, //"interpolation" 200 | content: { type: NodeTypes.SIMPLE_EXPRESSION, content: content }, //"simple_expression" 201 | }; 202 | } 203 | 204 | //TEXT类型parse 205 | function parseText(context) { 206 | //1、获取content 207 | //2、推进代码 : 删除已经处理的代码 208 | 209 | //1 210 | // const content = context.source.slice(0, context.source.length); 211 | // console.log(content); 212 | 213 | //2 214 | // advanceBy(context, content.length); 215 | // console.log(context.source); 216 | 217 | let endIndex = context.source.length; 218 | let endTokens = ["<", "{{"]; 219 | 220 | for (let i = 0; i < endTokens.length; i++) { 221 | const index = context.source.indexOf(endTokens[i]); 222 | if (index !== -1 && endIndex > index) { 223 | //!==-1 证明没有插值 224 | endIndex = index; 225 | } 226 | } 227 | 228 | //重构 229 | const content = parseTextData(context, endIndex); 230 | console.log("content _______", content); 231 | 232 | return { 233 | type: NodeTypes.TEXT, 234 | content, 235 | }; 236 | } 237 | 238 | //推进工具函数 239 | function advanceBy(context: any, length: number) { 240 | context.source = context.source.slice(length); 241 | } 242 | 243 | //裁剪工具函数 244 | function parseTextData(context, length) { 245 | const content = context.source.slice(0, length); 246 | 247 | advanceBy(context, length); 248 | return content; 249 | } 250 | 251 | function startsWithEndTagOpen(source, tag) { 252 | return source.startsWith(" { 7 | context.helper(Create_ELEMENT_Vnode); 8 | // 中间处理层 9 | const vnodeTag = `"${node.tag}"`; 10 | //props 11 | let vnodeProps; 12 | 13 | // children 14 | const children = node.children; 15 | let vnodeChildren = children[0]; 16 | 17 | // const vnodeElement = { 18 | // type: NodeTypes.ELEMENT, 19 | // tag: vnodeTag, 20 | // prpos: vnodeProps, 21 | // children: vnodeChildren, 22 | // }; 23 | node.codegenNode = createVNodeCall(context, vnodeTag, vnodeProps, vnodeChildren); 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transformExpression.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../ast"; 2 | 3 | export function transformExpression(node) { 4 | if (node.type === NodeTypes.INTERPOLATION) { 5 | node.content = processExpression(node.content); 6 | } 7 | } 8 | function processExpression(node: any) { 9 | node.content = `_ctx.${node.content}`; 10 | return node; 11 | } 12 | -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transfromText.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../ast"; 2 | import { isText } from "../utils"; 3 | 4 | export function transformText(node) { 5 | if (node.type === NodeTypes.ELEMENT) { 6 | return () => { 7 | const { children } = node; 8 | 9 | let currentContainer; 10 | for (let i = 0; i < children.length; i++) { 11 | const child = children[i]; 12 | 13 | if (isText(child)) { 14 | for (let j = i + 1; j < children.length; j++) { 15 | const next = children[j]; 16 | if (isText(next)) { 17 | if (!currentContainer) { 18 | currentContainer = children[i] = { 19 | type: NodeTypes.COMPOUND_EXPRESSION, 20 | children: [child], 21 | }; 22 | } 23 | currentContainer.children.push(" + "); 24 | currentContainer.children.push(next); 25 | children.splice(j, 1); 26 | j--; 27 | } else { 28 | currentContainer = undefined; 29 | break; 30 | } 31 | } 32 | } 33 | } 34 | }; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/compiler-core/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast"; 2 | 3 | export function isText(node) { 4 | return node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION; 5 | } 6 | -------------------------------------------------------------------------------- /src/compiler-core/tests/__snapshots__/codegen.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`codegen element 1`] = ` 4 | "const { toDisplayString :_toDisplayString , createElementVnode :_createElementVnode } = Vue 5 | return function render(_ctx, _cache){return _createElementVnode (\\"div\\", null, 'hi,' + _toDisplayString (_ctx.message))}" 6 | `; 7 | 8 | exports[`codegen interpolation 1`] = ` 9 | "const { toDisplayString :_toDisplayString } = Vue 10 | return function render(_ctx, _cache){return _toDisplayString (_ctx.message)}" 11 | `; 12 | 13 | exports[`codegen string 1`] = ` 14 | " 15 | return function render(_ctx, _cache){return 'hi'}" 16 | `; 17 | -------------------------------------------------------------------------------- /src/compiler-core/tests/codegen.spec.ts: -------------------------------------------------------------------------------- 1 | import { generate } from "../src/codegen"; 2 | import { baseParse } from "../src/parse"; 3 | import { transform } from "../src/transform"; 4 | import { transformElement } from "../src/transforms/tarnsformElement"; 5 | import { transformExpression } from "../src/transforms/transformExpression"; 6 | import { transformText } from "../src/transforms/transfromText"; 7 | 8 | describe("codegen", () => { 9 | it("string", () => { 10 | const ast = baseParse("hi"); 11 | transform(ast); 12 | const { code } = generate(ast); 13 | 14 | //快照 作用 15 | //1、抓bug 16 | // 2、有意的改变 17 | expect(code).toMatchSnapshot(); 18 | }); 19 | 20 | it("interpolation", () => { 21 | const ast = baseParse("{{message}}"); 22 | 23 | transform(ast, { 24 | nodeTransforms: [transformExpression], 25 | }); 26 | 27 | const { code } = generate(ast); 28 | expect(code).toMatchSnapshot(); 29 | }); 30 | 31 | it("element", () => { 32 | const ast: any = baseParse("
hi,{{message}}
"); 33 | 34 | transform(ast, { 35 | nodeTransforms: [transformExpression, transformElement, transformText], 36 | }); 37 | console.log("ast_______", ast); 38 | 39 | const { code } = generate(ast); 40 | expect(code).toMatchSnapshot(); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/compiler-core/tests/parse.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../src/ast"; 2 | import { baseParse } from "../src/parse"; 3 | 4 | describe("parse", () => { 5 | describe("interpolation", () => { 6 | test("simple interpolation", () => { 7 | const ast = baseParse("{{message}}"); 8 | 9 | expect(ast.children[0]).toStrictEqual({ 10 | type: NodeTypes.INTERPOLATION, 11 | content: { 12 | type: NodeTypes.SIMPLE_EXPRESSION, 13 | content: "message", 14 | }, 15 | 16 | // type: "interpolation", 17 | // content: { type: "simple_expression", content: "message" }, 18 | }); 19 | }); 20 | }); 21 | 22 | describe("element", () => { 23 | it("simple element div", () => { 24 | const ast = baseParse("
"); 25 | expect(ast.children[0]).toStrictEqual({ 26 | type: NodeTypes.ELEMENT, 27 | tag: "div", 28 | children: [], 29 | }); 30 | }); 31 | }); 32 | 33 | describe("text", () => { 34 | it("simple text", () => { 35 | const ast = baseParse("some text"); 36 | 37 | expect(ast.children[0]).toStrictEqual({ 38 | type: NodeTypes.TEXT, 39 | content: "some text", 40 | }); 41 | }); 42 | }); 43 | 44 | test("hello world", () => { 45 | const ast = baseParse("
hi,{{message}}
"); 46 | 47 | expect(ast.children[0]).toStrictEqual({ 48 | type: NodeTypes.ELEMENT, 49 | tag: "div", 50 | children: [ 51 | { 52 | type: NodeTypes.TEXT, 53 | content: "hi,", 54 | }, 55 | { 56 | type: NodeTypes.INTERPOLATION, 57 | content: { 58 | type: NodeTypes.SIMPLE_EXPRESSION, 59 | content: "message", 60 | }, 61 | }, 62 | ], 63 | }); 64 | }); 65 | 66 | test("Nested element", () => { 67 | const ast = baseParse("

hi

{{message}}
"); 68 | 69 | expect(ast.children[0]).toStrictEqual({ 70 | type: NodeTypes.ELEMENT, 71 | tag: "div", 72 | children: [ 73 | { 74 | type: NodeTypes.ELEMENT, 75 | tag: "p", 76 | children: [ 77 | { 78 | type: NodeTypes.TEXT, 79 | content: "hi", 80 | }, 81 | ], 82 | }, 83 | 84 | { 85 | type: NodeTypes.INTERPOLATION, 86 | content: { 87 | type: NodeTypes.SIMPLE_EXPRESSION, 88 | content: "message", 89 | }, 90 | }, 91 | ], 92 | }); 93 | }); 94 | 95 | test("should throw error when lack end tag", () => { 96 | expect(() => { 97 | baseParse("
"); 98 | }).toThrow(`缺少结束标签:span`); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /src/compiler-core/tests/transform.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../src/ast"; 2 | import { baseParse } from "../src/parse"; 3 | import { transform } from "../src/transform"; 4 | 5 | describe("transfrom", () => { 6 | it("happy path", () => { 7 | const ast = baseParse("
hi,{{message}}
"); 8 | 9 | const plugin = (node) => { 10 | if (node.type === NodeTypes.TEXT) { 11 | node.content = node.content + "mini-vue"; 12 | } 13 | }; 14 | 15 | transform(ast, { 16 | nodeTransforms: [plugin], 17 | }); 18 | const nodeText = ast.children[0].children[0]; 19 | expect(nodeText.content).toBe("hi,mini-vue"); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // mini-vue 的出口 2 | 3 | export * from "./runtime-dom/index"; 4 | 5 | import { baseCompile } from "./compiler-core/src"; 6 | import * as runtimeDom from "./runtime-dom"; 7 | import { registerRuntimeCompiler } from "./runtime-dom"; 8 | 9 | // render 10 | 11 | function compoileToFunction(template) { 12 | const { code } = baseCompile(template); 13 | const render = new Function("Vue", code)(runtimeDom); 14 | return render; 15 | } 16 | registerRuntimeCompiler(compoileToFunction); 17 | -------------------------------------------------------------------------------- /src/reactivity/baseHandler.ts: -------------------------------------------------------------------------------- 1 | // reactive和readonly对象复用代码的重构 2 | 3 | import { isObject } from "../shared/index"; 4 | import { track, trigger } from "./effect"; 5 | import { reactive, ReactiveFlags, readonly } from "./reactive"; 6 | 7 | function createGetter(isReadonly = false, isShallow = false) { 8 | //true 9 | return function get(target, key) { 10 | //专门判断isReactive 11 | if (key === ReactiveFlags.IS_REACTIVE) { 12 | return !isReadonly; 13 | } 14 | //专门判断isReadonly 15 | else if (key === ReactiveFlags.IS_READONLY) { 16 | return isReadonly; 17 | } 18 | const res = Reflect.get(target, key); 19 | 20 | //reactive对象的getter 进行依赖收集 //readonly对象不用进行依赖收集 21 | if (!isReadonly) { 22 | track(target, key); 23 | } 24 | 25 | //如果是shallowReadonly类型,就不用执行内部嵌套的响应式转换。也不用执行依赖收集 26 | if (isShallow) { 27 | return res; 28 | } 29 | //reactive、readonly对象嵌套的响应式转换 30 | if (isObject(res)) { 31 | //递归调用 32 | // isReadonly == true -> 表明是readonly对象 :是reactive对象 33 | return isReadonly ? readonly(res) : reactive(res); 34 | } 35 | return res; 36 | }; 37 | } 38 | 39 | function createSetter() { 40 | //只有reactive对象能够调用setter 41 | return function set(target, key, newVal) { 42 | const res = Reflect.set(target, key, newVal); 43 | //触发依赖 44 | trigger(target, key); 45 | return res; 46 | }; 47 | } 48 | 49 | // reactive对象getter和setter 50 | const get = createGetter(); 51 | const set = createSetter(); 52 | 53 | export const mutableHandlers = { 54 | get, 55 | set, 56 | }; 57 | 58 | // readonly对象的getter和setter 59 | const readonlyGetter = createGetter(true); 60 | 61 | export const readonlyHandlers = { 62 | get: readonlyGetter, 63 | set: function (target, key, newVal) { 64 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal); 65 | return true; 66 | }, 67 | }; 68 | 69 | const shallowReactiveGetter = createGetter(false, true); 70 | // const shallowReactiveSetter = createSetter(); 71 | 72 | // export const shallowReactiveHandlers = Object.assign({}, mutableHandlers, { 73 | // get: shallowReactiveGetter, 74 | // }); 75 | export const shallowReactiveHandlers = { 76 | get: shallowReactiveGetter, 77 | set, 78 | }; 79 | 80 | const shallowReadonlyGetter = createGetter(true, true); 81 | 82 | export const shallowReadonlyHandlers = { 83 | get: shallowReadonlyGetter, 84 | set: function (target, key, newVal) { 85 | console.warn(`key :"${String(key)}" set 失败,因为 target 是 readonly 类型`, target, newVal); 86 | return true; 87 | }, 88 | }; 89 | -------------------------------------------------------------------------------- /src/reactivity/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "./effect"; 2 | 3 | //computed 的类实现 4 | class computedRefIpml { 5 | private _fn: any; 6 | private _dirty: boolean = true; 7 | private _value: any; 8 | private _effect: ReactiveEffect; 9 | 10 | constructor(fn) { 11 | this._fn = fn; 12 | 13 | this._effect = new ReactiveEffect(fn, { 14 | scheduler: () => { 15 | if (!this._dirty) { 16 | this._dirty = true; 17 | } 18 | }, 19 | }); 20 | } 21 | 22 | get value() { 23 | if (this._dirty) { 24 | this._dirty = false; 25 | // this._value = this._fn(); 26 | this._value = this._effect.run(); 27 | } 28 | return this._value; 29 | } 30 | } 31 | 32 | export function computed(fn) { 33 | return new computedRefIpml(fn); 34 | } 35 | -------------------------------------------------------------------------------- /src/reactivity/effect.ts: -------------------------------------------------------------------------------- 1 | //effect 第一个参数接受一个函数 2 | //ReactiveEffect类 (抽离的一个概念) ------>面向对象思维 3 | 4 | import { extend } from "../shared"; 5 | 6 | /** 7 | * 不给activeEffect添加类型, 单测会报错 8 | * 所以进行了代码优化 9 | */ 10 | 11 | // let activeEffect: () => void; 12 | // export function effect(fn: () => void) { 13 | // activeEffect = fn; 14 | // fn(); //执行函数 ->触发了响应式对象的getter ->track 15 | // activeEffect = function () {}; 16 | // } 17 | 18 | //工具函数 19 | function cleanupEffect(effect) { 20 | effect.deps.forEach((dep: any) => { 21 | dep.delete(effect); 22 | }); 23 | //把 effect.deps清空 24 | effect.deps.length = 0; 25 | } 26 | 27 | //代码优化 面向对象思想 28 | let activeEffect: any; 29 | let shouldTrack: boolean = false; //用于记录是否应该收集依赖,防止调用stop后触发响应式对象的property的get的依赖收集 obj.foo ++ 30 | 31 | export class ReactiveEffect { 32 | private _fn: any; 33 | deps = []; //用于保存与当前实例相关的响应式对象的 property 对应的 Set 实例 用于stop操作 34 | active = true; //用于记录当前实例状态,为 true 时未调用 stop 方法,否则已调用,防止重复调用 stop 方法 35 | scheduler?: () => void; 36 | onStop?: () => void; 37 | constructor(fn, option) { 38 | this._fn = fn; 39 | this.scheduler = option?.scheduler; 40 | this.onStop = option?.onStop; 41 | // this.deps = []; 42 | // this.active = true; 43 | } 44 | //用于执行传入的函数 45 | run() { 46 | //stop的状态下(active =false) 直接执行fn 不收集依赖 47 | if (!this.active) { 48 | this.active = true; 49 | return this._fn(); 50 | } 51 | //应该收集依赖 52 | shouldTrack = true; 53 | 54 | activeEffect = this; 55 | const res = this._fn(); 56 | 57 | //重置 58 | shouldTrack = false; 59 | // 返回传入的函数执行的结果 60 | return res; 61 | } 62 | stop() { 63 | //删除effect active用于优化 多次调用stop也只清空一次 64 | if (this.active) { 65 | cleanupEffect(this); 66 | if (this.onStop) { 67 | this.onStop(); 68 | } 69 | this.active = false; 70 | } 71 | } 72 | } 73 | 74 | //effect函数 75 | /** 76 | * @param fn 参数函数 77 | */ 78 | export function effect(fn, option: any = {}) { 79 | const _effect: ReactiveEffect = new ReactiveEffect(fn, option); 80 | // Object.assign(_effect, option); 81 | 82 | if (option) { 83 | extend(_effect, option); //what this? 84 | } 85 | if (!option || !option.lazy) { 86 | _effect.run(); 87 | } 88 | 89 | // _effect.run(); //实际上是调用执行了fn函数 90 | 91 | const runner: any = _effect.run.bind(_effect); //直接调用runnner 92 | runner.effect = _effect; 93 | return runner; 94 | } 95 | 96 | export function stop(runner) { 97 | runner.effect.stop(); 98 | } 99 | 100 | const targetMap = new WeakMap(); 101 | // 进行依赖收集track 102 | export function track(target, key) { 103 | // 若不应该收集依赖则直接返回 104 | // if (!shouldTrack || activeEffect === undefined) { 105 | // return; 106 | // } 107 | if (!isTracking()) return; 108 | //1、先获取到key的依赖集合dep 109 | //所有对象的的以来集合targetMap -> 当前对象的依赖集合objMap -> 当前key的依赖集合 110 | let objMap = targetMap.get(target); 111 | // 如果没有初始化过 需要先初始化 112 | if (!objMap) { 113 | objMap = new Map(); 114 | targetMap.set(target, objMap); 115 | } 116 | //同理 如果没有初始化过 需要先初始化 117 | let dep = objMap.get(key); 118 | if (!dep) { 119 | dep = new Set(); //依赖不会重复 120 | objMap.set(key, dep); 121 | } 122 | //d将依赖函数添加给dep 123 | 124 | // if (!activeEffect) return; 125 | // if(dep.has(activeEffect)) return 126 | // dep.add(activeEffect); ? 怎么获取到fn? 添加一个全局变量activeEffect 127 | // activeEffect?.deps.push(dep); ? 128 | 129 | trackEffect(dep); 130 | } 131 | 132 | //重构 133 | export function trackEffect(dep) { 134 | //看看dep之前有没有添加过,添加过的话 就不添加了 135 | if (dep.has(activeEffect)) return; 136 | dep.add(activeEffect); 137 | activeEffect.deps.push(dep); 138 | } 139 | 140 | //activeEffect可能为undefined 原因: 访问一个单纯的reactive对象,没有任何依赖的时候 activeEffect可能为undefined 141 | export function isTracking() { 142 | return shouldTrack && activeEffect !== undefined; 143 | } 144 | console.log(targetMap.has(effect)); 145 | 146 | //触发依赖trigger 147 | export function trigger(target, key) { 148 | // console.log("触发依赖了"); 149 | 150 | //1、先获取到key的依赖集合dep 151 | let objMap = targetMap.get(target); 152 | // console.log(objMap); 153 | 154 | let dep = objMap.get(key); 155 | console.log(objMap); 156 | 157 | //去执行dep里面的函数 158 | // dep.forEach((effect) => { 159 | // if (effect.scheduler) { 160 | // effect.scheduler(); 161 | // } else { 162 | // effect.run(); 163 | // } 164 | // }); 165 | triggerEffect(dep); 166 | } 167 | 168 | // 重构 169 | export function triggerEffect(dep) { 170 | dep.forEach((effect) => { 171 | if (effect.scheduler) { 172 | effect.scheduler(); 173 | } else { 174 | effect.run(); 175 | } 176 | }); 177 | } 178 | -------------------------------------------------------------------------------- /src/reactivity/index.ts: -------------------------------------------------------------------------------- 1 | export function add(a, b) { 2 | return a + b; 3 | } 4 | 5 | export { ref, proxyRefs } from "./ref"; 6 | -------------------------------------------------------------------------------- /src/reactivity/reactive.ts: -------------------------------------------------------------------------------- 1 | // import { track, trigger } from "./effect"; 2 | import { isObject } from "../shared/index"; 3 | import { mutableHandlers, readonlyHandlers, shallowReactiveHandlers, shallowReadonlyHandlers } from "./baseHandler"; 4 | // import { track, trigger } from "./effect"; 5 | 6 | /** 7 | * reative 和 readonly 的get和set重复代码较多,进行代码抽取重构 8 | * 9 | */ 10 | 11 | // 工具函数 12 | function createReactiveObject(target, baseHandler) { 13 | if (!isObject(target)) { 14 | console.warn(`target${target}必须是一个对象`); 15 | return target; 16 | } 17 | return new Proxy(target, baseHandler); 18 | } 19 | 20 | //reactive函数 21 | // export function reactive(obj) { 22 | // return new Proxy(obj, { 23 | // // get(target, key) { 24 | // // //ToDo 收集依赖 25 | // // track(target, key); 26 | // // const res = Reflect.get(target, key); //返回属性的值 27 | // // return res; 28 | // // }, 29 | // // set(target, key, newVal) { 30 | // // //返回Boolean 返回 true 代表属性设置成功。 在严格模式下,如果 set() 方法返回 false,那么会抛出一个 TypeError 异常。 31 | // // const res = Reflect.set(target, key, newVal); //返回一个 Boolean 值表明是否成功设置属性。 32 | // // //ToDo 触发依赖依赖 33 | // // trigger(target, key); 34 | // // return res; 35 | // // }, 36 | // get, 37 | // set, 38 | // }); 39 | // } 40 | export function reactive(obj) { 41 | return createReactiveObject(obj, mutableHandlers); 42 | } 43 | 44 | //readonly函数 只读不能修改 45 | // export function readonly(obj) { 46 | // return new Proxy(obj, { 47 | // // get(target, key) { 48 | // // return Reflect.get(target, key); 49 | // // }, 50 | // // set(target, key, newValue) { 51 | // // console.warn( 52 | // // `target:${target} 对象是readonly对象,不能修改的属性 `, 53 | // // key, 54 | // // newValue 55 | // // ); 56 | // // return true; 57 | // // }, 58 | // get, 59 | // set, 60 | // }); 61 | // } 62 | 63 | export function readonly(obj) { 64 | return createReactiveObject(obj, readonlyHandlers); 65 | } 66 | 67 | export const enum ReactiveFlags { 68 | IS_REACTIVE = "__v_isReactive", 69 | IS_READONLY = "__v_isReadonly", 70 | } 71 | 72 | //isReactive 功能 73 | 74 | export function isReactive(obj) { 75 | //如何判断是一个reactive对象 ? 或者这么问? 什么样的对象是reactive对象 76 | //在设计getter的时候 传入了一个isRaedonly的参数 默认false 77 | // 我们可以用 obj[is_reactive] 来调用getter函数 78 | // 在getter函数中进行判断 如果 key ==="is_reactive" return !isReadonly 79 | 80 | //reactive对象-> getter-> key===ReactiveFlags.IS_RWACTIVE return true -> !!true ->true 81 | //非 reactive独享 ->不执行getter,对象业务ReactiveFlags.IS_RWACTIVE属性值 return undefined -> !!undefined-> false 82 | return !!obj[ReactiveFlags.IS_REACTIVE]; 83 | } 84 | 85 | //isReadonly功能 86 | 87 | export function isReadonly(obj) { 88 | //同理 89 | return !!obj[ReactiveFlags.IS_READONLY]; 90 | } 91 | 92 | //isProxy 93 | export function isProxy(obj) { 94 | // 检查对象是否是由 reactive 或 readonly 创建的 proxy。 95 | //也就是说满足上面isReactive和isReadonly任意一个就是proxy &&(与) ||(或) 96 | return isReadonly(obj) === true || isReactive(obj) === true; 97 | } 98 | 99 | //shallowReactive 100 | //创建一个 proxy,使其自身的 property为只读,但不执行嵌套对象的深度只读转换 (暴露原始值) 101 | // 自身property为reactive 内部嵌套不是reactive 102 | export function shallowReactive(obj) { 103 | return createReactiveObject(obj, shallowReactiveHandlers); 104 | } 105 | 106 | //shallowReadonly 107 | //创建一个 proxy,使其自身的 property为只读,但不执行嵌套对象的深度只读转换 (暴露原始值) 108 | // 自身property为readonly 内部嵌套不是readonly 109 | export function shallowReadonly(obj) { 110 | return createReactiveObject(obj, shallowReadonlyHandlers); 111 | } 112 | -------------------------------------------------------------------------------- /src/reactivity/ref.ts: -------------------------------------------------------------------------------- 1 | // export function ref(val) { 2 | // const refObj = { 3 | // value: val, 4 | // }; 5 | // return refObj; 6 | // } 7 | 8 | import { isObject } from "../shared/index"; 9 | import { isTracking, trackEffect, triggerEffect } from "./effect"; 10 | import { reactive } from "./reactive"; 11 | 12 | // ref接口的实现类 对操作进行封装 13 | class RefImpl { 14 | private _value: any; 15 | public dep; 16 | private _rawValue: any; 17 | public __v_isRef = true; // 判断是ref的标识 18 | constructor(value) { 19 | // 将传入的值赋值给实例的私有属性property_value 20 | this._rawValue = value; 21 | //value 为对象的话 需要转换为reactive包裹value 22 | // this._value = isObject(value) ? reactive(value) : value; 23 | this._value = convert(value); 24 | this.dep = new Set(); 25 | } 26 | get value() { 27 | if (isTracking()) { 28 | // 进行依赖收集 29 | trackEffect(this.dep); 30 | } 31 | 32 | return this._value; 33 | } 34 | set value(val) { 35 | //如果value是reactive对象的时候 this._value 为Proxy 36 | // 提前声明一个this._rawValue 来存储并进行比较 37 | if (Object.is(val, this._rawValue)) return; // ref.value = 2 ref.value = 2 两次赋值相同值的操作 不会执行effect 38 | this._rawValue = val; 39 | // this._value = isObject(val) ? reactive(val) : val; 40 | this._value = convert(val); //处理值 如果是对象 ->转为reactive对象 不是对象 返回原值 41 | triggerEffect(this.dep); 42 | } 43 | } 44 | function convert(val) { 45 | return isObject(val) ? reactive(val) : val; 46 | } 47 | 48 | //ref 49 | export function ref(val) { 50 | return new RefImpl(val); 51 | } 52 | 53 | //isRef 54 | export function isRef(val) { 55 | return !!(val.__v_isRef === true); 56 | } 57 | 58 | //unRef 59 | export function unRef(val) { 60 | return isRef(val) ? val.value : val; 61 | } 62 | 63 | //proxyRef 应用场景: template中使用setup中return的ref 不需要使用ref.value 64 | 65 | export function proxyRefs(objectWithRefs) { 66 | //怎么知道调用getter 和setter ? ->proxy 67 | return new Proxy(objectWithRefs, { 68 | get(target, key) { 69 | //get -> age(ref) 那么就给他返回 .value 70 | // not ref -> return value 71 | return unRef(Reflect.get(target, key)); 72 | }, 73 | set(target, key, newVal) { 74 | //当前需要修改的值是ref对象,同时修改值不是ref 75 | if (isRef(target[key]) && !isRef(newVal)) { 76 | target[key].value = newVal; 77 | return true; 78 | } else { 79 | return Reflect.set(target, key, newVal); 80 | } 81 | }, 82 | }); 83 | } 84 | -------------------------------------------------------------------------------- /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 | //ref 7 | //.value 8 | //1.缓存 9 | const user = reactive({ 10 | age: 1, 11 | }); 12 | const age = computed(() => { 13 | return user.age; 14 | }); 15 | 16 | expect(age.value).toBe(1); 17 | }); 18 | 19 | it("should compute lazily", () => { 20 | const value = reactive({ 21 | foo: 1, 22 | }); 23 | const getter = jest.fn(() => { 24 | return value.foo; 25 | }); 26 | const cValue = computed(getter); 27 | //lazy 28 | //没调用cValue 就不会调用getter 29 | expect(getter).not.toHaveBeenCalled(); 30 | 31 | expect(cValue.value).toBe(1); //cValue.value触发get value -> 执行getter -> 这里才会触发value.foo ->触发reactive数据的getter 收集依赖 32 | expect(getter).toHaveBeenCalledTimes(1); 33 | 34 | // should not compute again 35 | cValue.value; //get 36 | expect(getter).toHaveBeenCalledTimes(1); 37 | 38 | // should not compute until needed 39 | value.foo = 2; // trigger -> effect ->get 重新执行了 40 | expect(getter).toHaveBeenCalledTimes(1); 41 | 42 | // now it should compute 43 | expect(cValue.value).toBe(2); 44 | expect(getter).toHaveBeenCalledTimes(2); 45 | 46 | // should not compute again 47 | cValue.value; 48 | expect(getter).toHaveBeenCalledTimes(2); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /src/reactivity/tests/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { reactive } from "../reactive"; 2 | import { effect, stop } from "../effect"; 3 | 4 | describe("effect", () => { 5 | it("happy path", () => { 6 | const user = reactive({ 7 | age: 10, 8 | }); 9 | 10 | let nextAge; 11 | effect(() => { 12 | nextAge = user.age + 1; 13 | }); 14 | // effect(() => { 15 | // user.age++; 16 | // }); 17 | 18 | expect(nextAge).toBe(11); 19 | // update 20 | user.age++; 21 | expect(nextAge).toBe(12); 22 | }); 23 | it("should return runner when call effect", () => { 24 | // 1、调用effect(fn) 会返回一个function runner函数 25 | //2、调用runner() 会再次调用fn -> 返回fn的返回值 26 | 27 | let foo = 0; 28 | const runner = effect(() => { 29 | foo++; 30 | return foo; 31 | }); 32 | 33 | expect(foo).toBe(1); 34 | runner(); //函数执行,说明了effect返回了一个函数?并且返回的为传入的函数? 35 | expect(foo).toBe(2); 36 | expect(runner()).toBe(3); 37 | }); 38 | 39 | it("scheduler", () => { 40 | //通过effect第二个参数给定一个scheduler的fn 41 | // effect 第一次执行的时候,还会执行第一个fn参数 42 | //当响应式对象 update 不会执行第一个fn参数,而是执行scheduler 43 | // 当执行runner的时候,会执行fn 44 | let dummy; 45 | let run: any; 46 | const scheduler = jest.fn(() => { 47 | run = runner; 48 | }); 49 | const obj = reactive({ foo: 1 }); 50 | const runner = effect( 51 | //effect 传入了两个参数 1回调函数 2 option对象 52 | () => { 53 | dummy = obj.foo; 54 | }, 55 | { scheduler } 56 | ); 57 | expect(scheduler).not.toHaveBeenCalled(); 58 | expect(dummy).toBe(1); 59 | 60 | //should be called on first trigger 61 | obj.foo++; 62 | expect(scheduler).toHaveBeenCalledTimes(1); // scheduler被调用了一次 63 | //should not run yet 64 | expect(dummy).toBe(1); 65 | //manually run 66 | run(); 67 | //should have run 68 | expect(dummy).toBe(2); 69 | // 70 | }); 71 | 72 | it("stop", () => { 73 | // stop接受effect执行返回的函数作为参数。 74 | //用一个变量runner接受effect执行返回的函数 75 | //调用stop并传入runner后,当传入的函数依赖的响应式对象的 property 的值更新时不会再执行该函数, 76 | //只有当调用runner时才会恢复执行该函数。 77 | let dummy; 78 | const obj = reactive({ prop: 1 }); 79 | const runner = effect(() => { 80 | dummy = obj.prop; 81 | }); 82 | obj.prop = 2; 83 | expect(dummy).toBe(2); 84 | stop(runner); 85 | obj.prop = 3; 86 | expect(dummy).toBe(2); // trigger失效 代表依赖清空?怎么清空依赖呢? 87 | obj.prop++; 88 | expect(dummy).toBe(2); 89 | runner(); // runner 执行 effect.run 执行了fn -> obj.prop 触发了get 依赖又收集了 90 | expect(dummy).toBe(4); 91 | }); 92 | 93 | it("onStop", () => { 94 | const obj = reactive({ 95 | foo: 1, 96 | }); 97 | const onStop = jest.fn(); 98 | let dummy; 99 | const runner = effect( 100 | () => { 101 | dummy = obj.foo; 102 | }, 103 | { onStop } 104 | ); 105 | stop(runner); 106 | expect(onStop).toBeCalledTimes(1); 107 | }); 108 | 109 | it("cleanup effect", () => { 110 | let dummy; 111 | let record; 112 | const obj = reactive({ prop: 1, foo: 2 }); 113 | const runner = effect(() => { 114 | dummy = obj.prop; 115 | record = obj.foo; 116 | }); 117 | expect(dummy).toBe(1); 118 | expect(record).toBe(2); 119 | stop(runner); 120 | //should not trigger effect 121 | obj.foo = 3; 122 | expect(dummy).toBe(1); 123 | expect(record).toBe(2); 124 | }); 125 | }); 126 | -------------------------------------------------------------------------------- /src/reactivity/tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | //测试单元测是否ok 2 | 3 | import {add} from "../index" 4 | 5 | it ("init",()=>{ 6 | expect(add(1,1)).toBe(2) 7 | }); -------------------------------------------------------------------------------- /src/reactivity/tests/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { reactive, isReactive, isProxy } from "../reactive"; 2 | describe("reactive", () => { 3 | it("happy path", () => { 4 | const original = { foo: 1 }; 5 | const observed = reactive(original); 6 | expect(observed).not.toBe(original); //检测obseved !== original 7 | expect(observed.foo).toBe(1); 8 | 9 | //isReactive 10 | expect(isReactive(observed)).toBe(true); 11 | expect(isReactive(original)).toBe(false); 12 | 13 | //isProxy 14 | expect(isProxy(observed)).toBe(true); 15 | expect(isProxy(original)).toBe(false); 16 | }); 17 | 18 | it("nested reactive", () => { 19 | const original = { 20 | nested: { 21 | foo: 1, 22 | }, 23 | arr: [{ bar: 2 }], 24 | }; 25 | const observed = reactive(original); 26 | 27 | expect(isReactive(observed.nested)).toBe(true); 28 | expect(isReactive(observed.arr)).toBe(true); 29 | expect(isReactive(observed.arr[0])).toBe(true); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/reactivity/tests/readonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReadonly, readonly } from "../reactive"; 2 | 3 | describe("readonly", () => { 4 | it("should make nested values readonly", () => { 5 | const original = { foo: 1, bar: { baz: 2 } }; 6 | const wrapped = readonly(original); 7 | expect(wrapped).not.toBe(original); 8 | expect(wrapped.foo).toBe(1); 9 | 10 | //isReadonly 11 | expect(isReadonly(wrapped)).toBe(true); 12 | expect(isReadonly(original)).toBe(false); 13 | 14 | //isProxy 15 | expect(isProxy(wrapped)).toBe(true); 16 | expect(isProxy(original)).toBe(false); 17 | 18 | //嵌套 19 | expect(isReadonly(wrapped.bar)).toBe(true); 20 | }); 21 | 22 | it("should call console.warn when set", () => { 23 | console.warn = jest.fn(); 24 | const user = readonly({ 25 | age: 10, 26 | }); 27 | 28 | user.age = 11; 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", () => { 6 | it("happy path", () => { 7 | const a = ref(1); 8 | expect(a.value).toBe(1); 9 | }); 10 | 11 | it("should be reactive", () => { 12 | const a = ref(1); 13 | let dummy; 14 | let calls = 0; 15 | effect(() => { 16 | calls++; 17 | dummy = a.value; 18 | }); 19 | 20 | expect(calls).toBe(1); 21 | expect(dummy).toBe(1); 22 | a.value = 2; 23 | expect(calls).toBe(2); 24 | expect(dummy).toBe(2); 25 | 26 | // same value should not trigger 27 | a.value = 2; 28 | expect(calls).toBe(2); 29 | expect(dummy).toBe(2); 30 | }); 31 | 32 | it("should make nested properties reactive", () => { 33 | const a = ref({ 34 | count: 1, 35 | }); 36 | let dummy; 37 | effect(() => { 38 | dummy = a.value.count; 39 | }); 40 | 41 | expect(dummy).toBe(1); 42 | a.value.count = 2; 43 | expect(dummy).toBe(2); 44 | }); 45 | 46 | it("isRef", () => { 47 | const a = ref(1); 48 | const user = reactive({ 49 | age: 1, 50 | }); 51 | 52 | expect(isRef(a)).toBe(true); 53 | expect(isRef(1)).toBe(false); 54 | expect(isRef(user)).toBe(false); 55 | }); 56 | 57 | it("unRef", () => { 58 | const a = ref(1); 59 | 60 | expect(unRef(a)).toBe(1); 61 | expect(unRef(1)).toBe(1); 62 | }); 63 | 64 | it("proxyRefs", () => { 65 | const user = { 66 | age: ref(10), 67 | name: "aky", 68 | }; 69 | 70 | //get -> age(ref) 那么就给他返回 .value 71 | // not ref -> return value 72 | const proxyUser = proxyRefs(user); 73 | expect(user.age.value).toBe(10); 74 | expect(proxyUser.age).toBe(10); 75 | expect(proxyUser.name).toBe("aky"); 76 | 77 | proxyUser.age = 20; 78 | //set -> 是ref ->修改.val 79 | expect(proxyUser.age).toBe(20); 80 | expect(user.age.value).toBe(20); 81 | 82 | proxyUser.age = ref(10); 83 | //set -> 不是ref -> 直接替换掉 84 | expect(proxyUser.age).toBe(10); 85 | expect(user.age.value).toBe(10); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /src/reactivity/tests/shallowReactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../effect"; 2 | import { isReactive, isReadonly, reactive, shallowReactive } from "../reactive"; 3 | 4 | describe("shallowReactive", () => { 5 | test("should not make non-reactive properties reactive", () => { 6 | const props = shallowReactive({ n: { foo: 1 } }); 7 | expect(isReactive(props)).toBe(true); 8 | // expect(isReadonly(props)).toBe(false); 9 | expect(isReactive(props.n)).toBe(false); 10 | }); 11 | test("happy shallowReactive", () => { 12 | const props = shallowReactive({ 13 | bar: 2, 14 | n: { foo: 1 }, 15 | }); 16 | 17 | let nexProps; 18 | effect(() => { 19 | nexProps = props.bar + 1; 20 | }); 21 | 22 | expect(nexProps).toBe(3); 23 | 24 | // update; 25 | props.bar++; 26 | expect(nexProps).toBe(4); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/reactivity/tests/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isReadonly, shallowReadonly } from "../reactive"; 2 | 3 | describe("shallowReadonly", () => { 4 | test("should not make non-reactive properties reactive", () => { 5 | const props = shallowReadonly({ n: { foo: 1 } }); 6 | expect(isReadonly(props)).toBe(true); 7 | expect(isReadonly(props.n)).toBe(false); 8 | }); 9 | 10 | it("should call console.warn when set", () => { 11 | console.warn = jest.fn(); 12 | const user = shallowReadonly({ 13 | age: 10, 14 | }); 15 | 16 | user.age = 11; 17 | expect(console.warn).toHaveBeenCalled(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/runtime-core/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from './component' 2 | 3 | export function provide(key, value) { 4 | //存 5 | //key value 存在哪里???? 存在当前实例对象上 6 | 7 | //获取当前组件实例对象 8 | const currentInstance: any = getCurrentInstance() 9 | 10 | if (currentInstance) { 11 | let { provides } = currentInstance 12 | const parentProvides = currentInstance.parent.provides 13 | 14 | //init 不能每次都初始化,只有第一次初始化 15 | //判断初始化状态 当前组件的provides = parentProvides 16 | if (provides === parentProvides) { 17 | provides = currentInstance.provides = Object.create(parentProvides) //利用原型原型链的机制 来进行多层inject provides 18 | } 19 | 20 | provides[key] = value 21 | } 22 | } 23 | 24 | export function inject(key, defaultValue) { 25 | //取 26 | 27 | const currentInstance: any = getCurrentInstance() 28 | if (currentInstance) { 29 | const parentProvides = currentInstance.parent.provides 30 | 31 | if (key in parentProvides) { 32 | return parentProvides[key] 33 | } else if (defaultValue) { 34 | if (typeof defaultValue === 'function') { 35 | return defaultValue() 36 | } 37 | return defaultValue 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * 44 | * 思路: provide提供数据 inject获取数据 45 | * 46 | * 47 | * 1、基础作用 父子间的provide和inject 48 | * provide(key,value) 49 | * provide在setup中使用, 我们通过getCurrentInstance 获取到当前组件实例,并将传入的参数通过key-value的形式保存到instance.provide上 50 | * 51 | * 52 | * inject(key) 53 | * 54 | * 在setup中使用, 通过getCurrentInstance 获取到组件实例,,通过instance.parent.provide来获取父组件的provide 55 | * 通过key 老获取到所需要的值 56 | * 57 | * 58 | * 2、跨层级的provide和inject 59 | * 60 | * 前面的实现只支持 父子间的provide和inject传递,更深层次的传递利用了原型和原型链的机制 61 | * 62 | * 父组件 parentProvide 63 | * 子组件 provide 64 | * 孙组件 inject 65 | * 66 | * provide currentInstance.provide = Object.create(parentProvide) 67 | * 68 | * 在孙组件中 使用inject(key) 查找key的value 69 | * 先找子组件提供的provide,找到就直接返回value, 70 | * 找不到就会通过原型链查找parentProvide上的key值,逐级向上直到找到值 71 | * 72 | * 3、init provide 73 | * 74 | * 在创建instance实例的时候, 我们设置 parent和 provide 的默认值、 75 | * parent = parentInstance 76 | * provide = parent ? parent.provide :{} 77 | * 所以 provide中我们获取的provide 和 parentProvide 在最初是一致的 78 | * 79 | * 在进行object.create()后 provide会变成一个空对象,其prototype 指向 parentProvide 80 | * 81 | * 然后在空对象上进行新值的添加,我们不能每次进行provide都进行一次object.create 这样,我们在同一个setup中 之前的多次的provide只会生效最后一次 82 | * 83 | * 所以我们只需要进行一次原型链的继承 84 | * 85 | * 而这一次就要在最初的时候进行, 就是provide = parentProvide 86 | */ 87 | -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | import { proxyRefs } from '../index' 2 | import { shallowReadonly } from '../reactivity/reactive' 3 | import { emit } from './componentEmit' //处理emit 4 | import { initProps } from './componentProps' 5 | import { PublicInstanceProxyHandlers } from './componentPublicInstance' 6 | import { initSlot } from './componentSlots' 7 | 8 | // 创建当前组件实例对象 9 | export function createComponentInstance(vnode, parent) { 10 | const component = { 11 | //初始化 12 | vnode, //* 当前组件的虚拟节点 13 | type: vnode.type, //* 当前虚拟节点的type object | string 14 | next: null, //! 下次更新的vnode节点 15 | setupState: {}, //* 记录setup函数执行后返回的结果 16 | props: {}, //* 创建props属性,方便instance实例进行初始化props 17 | slots: {}, //* 创建slots属性,方便instance实例进行初始化slots 18 | provides: parent ? parent.provides : {}, //! 初始化provides 19 | parent, //* 父组件 20 | emit: () => {}, // * 初始化emit 21 | isMounted: false, //! 挂载标识符 用于组件更新 22 | subTree: {}, //! elemnt 树 23 | } 24 | component.emit = emit.bind(null, component) as any //绑定instance为this 并返回函数 这里emit是从外部引入的emit 25 | return component 26 | } 27 | 28 | export function setupComponent(instance) { 29 | // TODO 30 | // initProps() 31 | console.log(instance) 32 | // * 初始化props 33 | initProps(instance, instance.vnode.props) 34 | 35 | //! 初始化slot 36 | initSlot(instance, instance.vnode.children) 37 | 38 | setupStatefulComponent(instance) 39 | } 40 | 41 | function setupStatefulComponent(instance: any) { 42 | const Component = instance.type 43 | 44 | //* 增加了代理对象 45 | //cxt 46 | console.log({ _: 123 }) 47 | 48 | console.log({ _: instance }) 49 | 50 | instance.proxy = new Proxy( //增加了代理对象 51 | { _: instance }, 52 | 53 | // get(target, key) { 54 | // //setupState 55 | // const { setupState } = instance; 56 | // if (key in setupState) { 57 | // return setupState[key]; 58 | // } 59 | // //key ->$el 60 | // if (key === "$el") { 61 | // return instance.vnode.el; 62 | // } 63 | // }, 64 | PublicInstanceProxyHandlers 65 | ) 66 | 67 | const { setup } = Component 68 | 69 | if (setup) { 70 | // currentInstance = instance; 71 | setCurrentInstance(instance) 72 | const setupResult = setup(shallowReadonly(instance.props), { 73 | emit: instance.emit, 74 | }) 75 | 76 | setCurrentInstance(null) 77 | 78 | handleSetupResult(instance, setupResult) 79 | } 80 | } 81 | 82 | function handleSetupResult(instance, setupResult: any) { 83 | // function Object 84 | // TODO function 85 | if (typeof setupResult === 'object') { 86 | instance.setupState = proxyRefs(setupResult) //setup返回值的ref对象 直接key访问,不用key.value 87 | } 88 | 89 | finishComponentSetup(instance) 90 | } 91 | 92 | function finishComponentSetup(instance: any) { 93 | const Component = instance.type 94 | //如果用户不提供render函数 而是用的template 95 | if (compiler && !Component.render) { 96 | if (Component.template) { 97 | Component.render = compiler(Component.template) 98 | } 99 | } 100 | 101 | instance.render = Component.render 102 | } 103 | 104 | //借助全局变量获取instccne 105 | 106 | let currentInstance = null 107 | 108 | export function getCurrentInstance() { 109 | return currentInstance 110 | } 111 | 112 | function setCurrentInstance(instance) { 113 | currentInstance = instance 114 | } 115 | 116 | let compiler 117 | 118 | export function registerRuntimeCompiler(_compiler) { 119 | compiler = _compiler 120 | } 121 | -------------------------------------------------------------------------------- /src/runtime-core/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { camelize, capitalize, toHandlerKey } from "../shared/index"; 2 | 3 | export function emit(instance, event, ...args) { 4 | console.log("emit" + event); 5 | 6 | // instance.props 有没有对应event的回调 7 | const { props } = instance; 8 | 9 | //tpp -> 10 | //先去写一个特定行为 -》 重构通用行为 11 | 12 | // const handler = props["onAdd"]; 13 | 14 | //add-foo ->addFoo 15 | // const camelize = (str: string) => { 16 | // return str.replace(/-(\w)/g, (_, c: string) => { 17 | // return c ? c.toUpperCase() : ""; 18 | // }); 19 | // }; 20 | // //addFoo ->AddFoo 21 | // const capitalize = (str: string) => { 22 | // return str.charAt(0).toUpperCase() + str.slice(1); 23 | // }; 24 | // console.log(capitalize(event)); 25 | 26 | // const str = capitalize(camelize(event)); 27 | // AddFoo -> noAddFoo 28 | // const toHandlerKey = (str: string) => { 29 | // return str ? "on" + str : ""; 30 | // }; 31 | //add-foo -> addFoo 32 | let str = camelize(event); 33 | 34 | //addFoo -> AddFoo 35 | str = capitalize(str); 36 | const handlerName = toHandlerKey(str); 37 | const handler = props[handlerName]; 38 | handler && handler(...args); 39 | } 40 | -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance, rawProps) { 2 | instance.props = rawProps || {}; 3 | } 4 | -------------------------------------------------------------------------------- /src/runtime-core/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | import { hasOwn } from '../shared/index.js' 2 | 3 | const PublicPropertiesMap = { 4 | $el: (i) => i.vnode.el, 5 | $slots: (i) => i.slots, 6 | $props: (i) => i.props, 7 | } 8 | 9 | // * proxy的getter方法 get(target,key) 10 | export const PublicInstanceProxyHandlers = { 11 | get({ _: instance }, key) { 12 | //这里的_:instance是解构 13 | // console.log("instance:", instance); 14 | 15 | //setupState 16 | const { setupState, props } = instance 17 | // if (key in setupState) { 18 | // return setupState[key]; 19 | // } 20 | 21 | if (hasOwn(setupState, key)) { 22 | return setupState[key] 23 | } else if (hasOwn(props, key)) { 24 | return props[key] 25 | } 26 | 27 | //key ->$el 28 | const publicGetter = PublicPropertiesMap[key] 29 | if (publicGetter) { 30 | return publicGetter(instance) 31 | } 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /src/runtime-core/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { shapeFlags } from "../shared/shapeFlags"; 2 | 3 | export function initSlot(instance, children) { 4 | // instance.slots = Array.isArray(children) ? children : [children]; 5 | 6 | //children Object 7 | 8 | // const slots = {}; 9 | // for (const key in children) { 10 | // const value = children[key]; 11 | 12 | // slots[key] = normalizeSlotsValue(value); 13 | // } 14 | 15 | // 16 | // const { vnode } = instance; 17 | // if (vnode.shapeFlag & shapeFlags.slot_children) { 18 | // console.log("isntance.slots:" + instance.slots); 19 | 20 | normalizeObjectSlots(children, instance.slots); 21 | // } 22 | // instance.slots = slots; 23 | } 24 | 25 | function normalizeObjectSlots(children, slots) { 26 | // console.log("isntance.slots:" + JSON.stringify(slots));\ 27 | console.log("children", children); 28 | 29 | for (const key in children) { 30 | const value = children[key]; 31 | slots[key] = (props) => normalizeSlotsValue(value(props)); 32 | // slots[key] = (props) => normalizeSlotsValue(value); 33 | 34 | console.log("isntance.slots:" + slots); 35 | } 36 | } 37 | function normalizeSlotsValue(value) { 38 | return Array.isArray(value) ? value : [value]; 39 | } 40 | 41 | /** 42 | * 父组件通过children 传递 slot 类型为对象类型 43 | * 44 | * { 45 | * key:(props)=>{h()} 46 | * } 47 | * 通过遍历将所有插槽通过 key value的形式 保存在instance.slot对象中 使用this.$slots可以访问得到 48 | * 在子组件中进行插槽渲染的时候 通过renderSlots(this.$slots,name,props)函数 49 | * 每一个slot 都是一个函数, renderSlots函数中会执行插槽函数 生成vnode对象或者vnode对象组成的数组result 50 | * 然后将生成的结果通过createVnode(Fragment,{},result)包裹成vnode对象 51 | * 52 | * 又因为 result是vnode对象或者vnode对象组成的数组, 53 | * createVnode 传入的children 只能是数组或者string 54 | * 所有如果是vnode对象则需要先包裹一个[]数组,normalizeSlotsValue就是这个职责 55 | * 56 | */ 57 | -------------------------------------------------------------------------------- /src/runtime-core/componentUpdateUtils.ts: -------------------------------------------------------------------------------- 1 | export function shouldUpdateComponent(preVnode, nextVnode) { 2 | const { props: prevProps } = preVnode; 3 | const { props: nextProps } = nextVnode; 4 | for (const key in nextProps) { 5 | if (nextProps[key] !== prevProps[key]) { 6 | return true; 7 | } 8 | } 9 | return false; 10 | } 11 | -------------------------------------------------------------------------------- /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 | const vnode = createVNode(rootComponent); 9 | render(vnode, rootContainer); 10 | }, 11 | }; 12 | }; 13 | } 14 | 15 | // export function createApp(rootComponent) { 16 | // return { 17 | // mount(rootContainer) { 18 | // const vnode = createVNode(rootComponent); 19 | 20 | // render(vnode, rootContainer); 21 | // }, 22 | // }; 23 | // } 24 | -------------------------------------------------------------------------------- /src/runtime-core/h.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode"; 2 | export function h(type, props?, children?) { 3 | return createVNode(type, props, children); 4 | } 5 | -------------------------------------------------------------------------------- /src/runtime-core/helpers/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import { createVNode, Fragment } from "../vnode"; 2 | 3 | export function renderSlots(slots, name, props) { 4 | const slot = slots[name]; 5 | 6 | if (slot) { 7 | if (typeof slot === "function") { 8 | return createVNode(Fragment, {}, slot(props)); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | // export { createApp } from "./createApp"; 2 | 3 | export { h } from "./h"; 4 | 5 | export { renderSlots } from "./helpers/renderSlots"; 6 | 7 | export { createTextVnode, createElementVnode } from "./vnode"; 8 | 9 | export { getCurrentInstance, registerRuntimeCompiler } from "./component"; 10 | 11 | export { provide, inject } from "./apiInject"; 12 | 13 | export { createRenderer } from "./renderer"; 14 | 15 | export { nextTick } from "./scheduler"; 16 | export { toDisplayString } from "../shared"; 17 | 18 | export * from "../reactivity/index"; 19 | -------------------------------------------------------------------------------- /src/runtime-core/renderer.ts: -------------------------------------------------------------------------------- 1 | import { effect } from '../reactivity/effect' 2 | import { EMPTY_OBJ, isObject } from '../shared/index' 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 | // debugger; 21 | patch(null, vnode, container, null, null) 22 | } 23 | 24 | /** 25 | * 26 | * @param n1 老的vnode n1存在 更新逻辑 n1不存在 初始化逻辑 27 | * @param n2 新的vnode 28 | * @param container 容器 29 | * @param parentComponent 父组件 createInstance创建组件实例的时候 用得到 30 | */ 31 | 32 | function patch(n1, n2, container, parentComponent, anchor) { 33 | //TODO 判断vnode是不是一个element 34 | //是element 就应该处理element 35 | //如何去区分是element类型和component类型 :vnode.type 来判断 36 | // console.log(vnode.type); 37 | 38 | // const { type, shapeFlag } = vnode; 39 | const { type, shapeFlag } = n2 40 | 41 | //Fragment -> 只渲染children 42 | switch (type) { 43 | case Fragment: 44 | processFragment(n1, n2, container, parentComponent, anchor) 45 | break 46 | case Text: 47 | processText(n1, n2, container) 48 | break 49 | default: 50 | if (shapeFlag & shapeFlags.element) { 51 | // if (typeof vnode.type === "string") { 52 | // element类型 53 | processElement(n1, n2, container, parentComponent, anchor) 54 | // } else if (isObject(vnode.type)) { 55 | } else if (shapeFlag & shapeFlags.stateful_component) { 56 | // component类型 57 | processComponent(n1, n2, container, parentComponent, anchor) 58 | } 59 | } 60 | } 61 | 62 | function processFragment(n1, n2, container: any, parentComponent, anchor) { 63 | mountChildren(n2, container, parentComponent, anchor) 64 | } 65 | 66 | function processText(n1: any, n2: any, container: any) { 67 | const { children } = n2 68 | const textNode = (n2.el = document.createTextNode(children)) 69 | container.appendChild(textNode) 70 | } 71 | //element vnode.type为element类型 72 | function processElement(n1, n2, container, parentComponent, anchor) { 73 | //init 初始化 74 | if (!n1) { 75 | mountElement(n2, container, parentComponent, anchor) 76 | } else { 77 | //TODO UPDATE 78 | console.log('patchElement') 79 | 80 | patchElement(n1, n2, container, parentComponent, anchor) 81 | } 82 | } 83 | 84 | //挂载element 85 | function mountElement(vnode: any, container: any, parentComponent, anchor) { 86 | //跨平台渲染 87 | //canvas 88 | // new Element() 89 | 90 | //Dom平台 91 | // const el = document.createElement("div") 92 | // const el = (n2.el = document.createElement(n2.type)); 93 | const el = (vnode.el = hostCreateElement(vnode.type)) 94 | 95 | //props 96 | // el.setttribute("id", "root"); 97 | const { props } = vnode 98 | for (const key in props) { 99 | const val = props[key] 100 | // console.log(key); 101 | // // const isOn = (key) => /^on[A-Z]/.test(key); 102 | // // if(isOn(key)){ 103 | // if (key.startsWith("on")) { 104 | // // console.log(key.split("on")[1]); 105 | // const event = key.slice(2).toLowerCase(); 106 | // el.addEventListener(event, val); 107 | // } else { 108 | // el.setAttribute(key, val); 109 | // } 110 | 111 | hostPatchProp(el, key, null, val) 112 | } 113 | 114 | //children 115 | 116 | // el.textContent = "hi mini-vue"; 117 | const { children, shapeFlag } = vnode 118 | 119 | if (shapeFlag & shapeFlags.text_children) { 120 | // if (typeof children === "string") { 121 | //children为srting类型 122 | el.textContent = children 123 | // } else if (Array.isArray(children)) { 124 | } else if (shapeFlag & shapeFlags.array_children) { 125 | //children 是数组类型 126 | // children.forEach((v) => { 127 | // patch(v, el); 128 | // }); 129 | mountChildren(vnode, el, parentComponent, anchor) 130 | } 131 | 132 | //挂载要渲染的el 133 | // document.appendChild(el) 134 | // container.appendChild(el); 135 | // container.append(el); 136 | 137 | hostInsert(el, container, anchor) 138 | } 139 | function mountChildren(childrenVnode, container, parentComponent, anchor) { 140 | childrenVnode.children.forEach((v) => { 141 | patch(null, v, container, parentComponent, anchor) 142 | }) 143 | } 144 | 145 | //更新element 146 | function patchElement(n1, n2, container, parentComponent, anchor) { 147 | console.log('n1:', n1) 148 | console.log('n2:', n2) 149 | //type 150 | 151 | //props 152 | const oldProps = n1.props || EMPTY_OBJ 153 | const newProps = n2.props || EMPTY_OBJ 154 | const el = (n2.el = n1.el) 155 | 156 | //1、key不变 value 改变 157 | //2、 value= undefined 、null ==> 删除key 158 | //3、 老的vnode 里的key 在新的element vnode不存在了 ==> 删除 159 | patchProps(el, oldProps, newProps) 160 | 161 | // children 162 | 163 | patchChildren(n1, n2, el, parentComponent, anchor) 164 | } 165 | 166 | // const EMPTY_OBJ = {} 167 | function patchProps(el, oldProps, newProps) { 168 | // debugger; 169 | if (oldProps !== newProps) { 170 | for (const key in newProps) { 171 | const prevProp = oldProps[key] 172 | const nextProp = newProps[key] 173 | if (prevProp !== nextProp) { 174 | hostPatchProp(el, key, prevProp, nextProp) 175 | } 176 | } 177 | 178 | //第三个场景 179 | if (oldProps !== EMPTY_OBJ) { 180 | for (const key in oldProps) { 181 | if (!(key in newProps)) { 182 | hostPatchProp(el, key, oldProps[key], null) 183 | } 184 | } 185 | } 186 | } 187 | } 188 | 189 | function patchChildren(n1, n2, container, parentComponent, anchor) { 190 | // ArrayToText 191 | //判断新节点的shapeFlag 判断 children是text还是array 192 | // const prevShapeFlag = n1.shapeFlag; 193 | const { shapeFlag: prevShapeFlag } = n1.shapeFlag 194 | const { shapeFlag } = n2 195 | const c1 = n1.children 196 | const c2 = n2.children 197 | if (shapeFlag & shapeFlags.text_children) { 198 | //新节点children为 text类型 199 | if (prevShapeFlag & shapeFlags.array_children) { 200 | //老节点children 为array类型 201 | //1、把老节点清空 202 | unmountedChildren(n1.children) 203 | //2、设置text 204 | hostSetElementText(container, c2) 205 | } else { 206 | //老节点children为 text类型 207 | if (c1 !== c2) { 208 | //设置text 209 | hostSetElementText(container, c2) 210 | } 211 | } 212 | } else { 213 | //新节点children为 array类型 214 | if (prevShapeFlag & shapeFlags.text_children) { 215 | //老节点children为text 216 | // 1、清空老节点 217 | hostSetElementText(container, '') 218 | // 2、设置新节点 219 | mountChildren(n2, container, parentComponent, anchor) 220 | } else { 221 | //老节点children 为array类型 222 | patchKeyChildren(c1, c2, container, parentComponent, anchor) 223 | } 224 | } 225 | } 226 | 227 | function unmountedChildren(children) { 228 | for (let i = 0; i < children.length; i++) { 229 | const el = children[i].el 230 | //remove 231 | hostRemove(el) 232 | } 233 | } 234 | 235 | function patchKeyChildren(c1, c2, container, parentComponent, anchor) { 236 | // debugger; 237 | let i = 0 238 | let e1 = c1.length - 1 239 | let e2 = c2.length - 1 240 | //1.左侧对比 241 | while (i <= e1 && i <= e2) { 242 | const n1 = c1[i] 243 | const n2 = c2[i] 244 | 245 | if (isSameVnodeType(n1, n2)) { 246 | patch(n1, n2, container, parentComponent, anchor) 247 | } else { 248 | break 249 | } 250 | i++ 251 | } 252 | 253 | //2.右侧对比 254 | while (i <= e1 && i <= e2) { 255 | const n1 = c1[e1] 256 | const n2 = c2[e2] 257 | 258 | if (isSameVnodeType(n1, n2)) { 259 | patch(n1, n2, container, parentComponent, anchor) 260 | } else { 261 | break 262 | } 263 | e1-- 264 | e2-- 265 | } 266 | 267 | //3.新的比老的多 添加到指定的位置 268 | if (i > e1) { 269 | if (i <= e2) { 270 | const nextPos = e2 + 1 271 | // const anchor = i + 1 < c2.length ? c2[nextPos].el : null; 有bug 272 | const anchor = nextPos < c2.length ? c2[nextPos].el : null 273 | 274 | while (i <= e2) { 275 | patch(null, c2[i], container, parentComponent, anchor) 276 | 277 | i++ 278 | } 279 | } 280 | } else if (i > e2) { 281 | //4、新的比老少 282 | while (i <= e1) { 283 | hostRemove(c1[i].el) 284 | i++ 285 | } 286 | } else { 287 | //中间对比 288 | let s1 = i 289 | let s2 = i 290 | let patched = 0 291 | const toBePatch = e2 - s2 + 1 //记录新节点的数量 用于老节点多余新节点时 删除逻辑的优化 292 | console.log(s2, e2) 293 | 294 | console.log('toBePatch', toBePatch) 295 | 296 | //建立新child.key的映射表 297 | const keyToNewIndexMap = new Map() 298 | 299 | //简历 最长递归子序列的映射表 最终结果是中间老节点新节点都有的节点在老节点数组的索引+1 排序 300 | const newIndexToOldIndexMap = new Array(toBePatch) 301 | 302 | let moved = false 303 | let maxNewIndexSoFar = 0 304 | //初始化 最长递归子序列的映射表 305 | for (let i = 0; i < toBePatch; i++) { 306 | newIndexToOldIndexMap[i] = 0 307 | } 308 | 309 | //将新的需要更新的(key:索引) 添加到映射表中 310 | for (let i = s2; i <= e2; i++) { 311 | let nextChild = c2[i] 312 | keyToNewIndexMap.set(nextChild.key, i) 313 | } 314 | 315 | for (let i = s1; i <= e1; i++) { 316 | const prevChild = c1[i] 317 | 318 | if (patched >= toBePatch) { 319 | //patch的数量大于需要更新的数量时 表示新的需要更新的已经更新完毕,剩下多的可以直接删除 320 | hostRemove(prevChild.el) 321 | continue 322 | } 323 | 324 | let newIndex 325 | if (prevChild.key != null) { 326 | //有key情况 327 | newIndex = keyToNewIndexMap.get(prevChild.key) 328 | } else { 329 | //无key 330 | for (let j = s2; j <= e2; j++) { 331 | if (isSameVnodeType(prevChild, c2[j])) { 332 | newIndex = j 333 | break 334 | } 335 | } 336 | } 337 | //删除逻辑 338 | if (newIndex === undefined) { 339 | console.log('删除') 340 | 341 | hostRemove(prevChild.el) 342 | } else { 343 | if (newIndex >= maxNewIndexSoFar) { 344 | maxNewIndexSoFar = newIndex 345 | } else { 346 | moved = true 347 | } 348 | //新老节点建立映射关系 349 | newIndexToOldIndexMap[newIndex - s2] = i + 1 //i+1 避免i=0的情况 350 | //更新逻辑 351 | patch(prevChild, c2[newIndex], container, parentComponent, null) 352 | patched++ //更新一次 数量+1 353 | } 354 | } 355 | 356 | //得到最长递增子序列 357 | const increasingNewIndexSequence = moved ? getSequence(newIndexToOldIndexMap) : [] 358 | console.log('newIndexToOldIndexMap', newIndexToOldIndexMap) 359 | console.log('最长递增子序列', increasingNewIndexSequence) 360 | 361 | let j = increasingNewIndexSequence.length - 1 362 | 363 | for (let i = toBePatch - 1; i >= 0; i--) { 364 | const nextIndex = i + s2 365 | const nextChild = c2[nextIndex] 366 | const anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null 367 | if (newIndexToOldIndexMap[i] === 0) { 368 | console.log('新增') 369 | patch(null, nextChild, container, parentComponent, anchor) 370 | } else if (moved) { 371 | if (j < 0 || i !== increasingNewIndexSequence[j]) { 372 | console.log('移动位置') 373 | hostInsert(nextChild.el, container, anchor) 374 | } else { 375 | j-- 376 | } 377 | } 378 | } 379 | } 380 | } 381 | 382 | function isSameVnodeType(n1, n2) { 383 | return n1.type === n2.type && n1.key === n2.key 384 | } 385 | //componentvnode.type为component类型 386 | function processComponent(n1, n2: any, container: any, parentComponent, anchor) { 387 | if (!n1) { 388 | mountComponent(n1, n2, container, parentComponent, anchor) 389 | } else { 390 | updateComponent(n1, n2) 391 | } 392 | } 393 | 394 | //组件初始化 395 | function mountComponent(n1, initialVNode: any, container, parentComponent, anchor) { 396 | // * 创建当前组件实例 方便后续对当前组件的操作 397 | const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent)) 398 | // * 处理组件setup函数 399 | setupComponent(instance) 400 | setupRenderEffect(instance, initialVNode, container, anchor) 401 | } 402 | 403 | function setupRenderEffect(instance: any, initialVNode, container, anchor) { 404 | //响应式 405 | instance.update = effect( 406 | () => { 407 | // 区分式初始化还是更新 408 | if (!instance.isMounted) { 409 | //init 410 | console.log('init') 411 | const { proxy } = instance 412 | // instance.subtree 用于存储当前组件的render函数生成 vnode? 是否可以称为vnode? 413 | const subTree = (instance.subTree = instance.render.call(proxy, proxy)) //subTree 虚拟节点树 vnode树 414 | console.log(subTree) 415 | 416 | patch(null, subTree, container, instance, anchor) 417 | 418 | //element ->mount 419 | // ! 将render生成的vnode的el 存储到组件的el 420 | initialVNode.el = subTree.el 421 | 422 | instance.isMounted = true 423 | } else { 424 | //update 425 | console.log('update') 426 | const { proxy, next, vnode } = instance 427 | 428 | if (next) { 429 | next.el = vnode.el 430 | 431 | updateComponentPreRender(instance, next) 432 | } 433 | 434 | const subTree = instance.render.call(proxy, proxy) //subTree 虚拟节点树 vnode树 435 | console.log(subTree) 436 | const preSubTree = instance.subTree 437 | 438 | console.log(preSubTree) 439 | console.log(subTree) 440 | instance.subTree = subTree //将现在的subTree更新到instance.subTree 方便后续再次更新 441 | 442 | patch(preSubTree, subTree, container, instance, anchor) 443 | } 444 | }, 445 | { 446 | scheduler() { 447 | console.log('scheduler执行啦') 448 | queueJobs(instance.update) 449 | }, 450 | } 451 | ) 452 | } 453 | 454 | function updateComponent(n1, n2) { 455 | //获取到挂载到vnode上的component 456 | const instance = (n2.component = n1.component) 457 | if (shouldUpdateComponent(n1, n2)) { 458 | instance.next = n2 459 | instance.update() // 利用effect返回值runner 调用runner()会再次执行fn 460 | } else { 461 | n2.el = n1.el 462 | instance.vnode = n2 463 | } 464 | } 465 | 466 | return { 467 | createApp: createAppAPI(render), 468 | } 469 | } 470 | 471 | //最长递增子序列 return 递归子序列的索引数组 472 | function getSequence(arr) { 473 | const p = arr.slice() 474 | const result = [0] 475 | let i, j, u, v, c 476 | const len = arr.length 477 | for (i = 0; i < len; i++) { 478 | const arrI = arr[i] 479 | if (arrI !== 0) { 480 | j = result[result.length - 1] 481 | if (arr[j] < arrI) { 482 | p[i] = j 483 | result.push(i) 484 | continue 485 | } 486 | u = 0 487 | v = result.length - 1 488 | while (u < v) { 489 | c = (u + v) >> 1 490 | if (arr[result[c]] < arrI) { 491 | u = c + 1 492 | } else { 493 | v = c 494 | } 495 | } 496 | if (arrI < arr[result[u]]) { 497 | if (u > 0) { 498 | p[i] = result[u - 1] 499 | } 500 | result[u] = i 501 | } 502 | } 503 | } 504 | u = result.length 505 | v = result[u - 1] 506 | while (u-- > 0) { 507 | result[u] = v 508 | v = p[v] 509 | } 510 | return result 511 | } 512 | 513 | //组件更新之前 需要将组件中的props 等数据先更新 514 | function updateComponentPreRender(instance: any, nextVndoe: any) { 515 | instance.vnode = nextVndoe 516 | instance.next = null 517 | instance.props = nextVndoe.props 518 | } 519 | -------------------------------------------------------------------------------- /src/runtime-core/scheduler.ts: -------------------------------------------------------------------------------- 1 | const queue: any[] = []; 2 | 3 | let isFlushPending = false; 4 | 5 | export function nextTick(fn) { 6 | return fn ? Promise.resolve().then(fn) : Promise.resolve(); 7 | } 8 | 9 | export function queueJobs(job) { 10 | if (!queue.includes(job)) { 11 | queue.push(job); 12 | } 13 | queueFlush(); 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 | nextTick(flushJobs); 27 | } 28 | 29 | function flushJobs() { 30 | isFlushPending = false; 31 | let job; 32 | while ((job = queue.shift())) { 33 | job && job(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from '../shared/index' 2 | import { shapeFlags } from '../shared/shapeFlags' 3 | 4 | export const Fragment = Symbol('Fragment') 5 | export const Text = Symbol('Text') 6 | 7 | export { createVNode as createElementVnode } 8 | 9 | export function createVNode(type, props?, children?) { 10 | const vnode = { 11 | type, 12 | props, 13 | children, 14 | key: props && props.key, 15 | shapeFlag: getShapeFlag(type), 16 | el: null, //$el用的 17 | component: null, 18 | } 19 | //children 20 | if (typeof children === 'string') { 21 | vnode.shapeFlag = vnode.shapeFlag | shapeFlags.text_children 22 | } else if (Array.isArray(children)) { 23 | vnode.shapeFlag = vnode.shapeFlag | shapeFlags.array_children 24 | } 25 | 26 | // 组件 + children 为object类型 27 | if (vnode.shapeFlag & shapeFlags.stateful_component) { 28 | if (isObject(children)) { 29 | vnode.shapeFlag = vnode.shapeFlag | shapeFlags.slot_children 30 | } 31 | } 32 | 33 | return vnode 34 | } 35 | 36 | export function createTextVnode(text: string) { 37 | return createVNode(Text, {}, text) 38 | } 39 | 40 | function getShapeFlag(type) { 41 | return typeof type === 'string' ? shapeFlags.element : shapeFlags.stateful_component 42 | } 43 | -------------------------------------------------------------------------------- /src/runtime-dom/index.ts: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../runtime-core/index"; 2 | 3 | //创建element元素 4 | function createElement(type) { 5 | return document.createElement(type); 6 | } 7 | 8 | // function patchProp(el, key, preVal, nextVal) { 9 | // // const isOn = (key) => /^on[A-Z]/.test(key); 10 | // // if(isOn(key)){ 11 | // if (key.startsWith("on")) { 12 | // // console.log(key.split("on")[1]); 13 | // const event = key.slice(2).toLowerCase(); 14 | // el.addEventListener(event, nextVal); 15 | // } else { 16 | // if (nextVal === undefined || nextVal === null) { 17 | // el.removeAttribute(key); 18 | // } else { 19 | // el.setAttribute(key, nextVal); 20 | // } 21 | // } 22 | // } 23 | 24 | //创建、更新props 25 | function patchProp(el, key, prevVal, nextVal) { 26 | const isOn = (key: string) => /^on[A-Z]/.test(key); 27 | if (isOn(key)) { 28 | const event = key.slice(2).toLowerCase(); 29 | el.addEventListener(event, nextVal); 30 | } else { 31 | if (nextVal === "undefined" || nextVal === null) { 32 | el.removeAttribute(key); 33 | } else { 34 | el.setAttribute(key, nextVal); 35 | } 36 | } 37 | } 38 | 39 | //指定位置插入 40 | function insert(child, parent, anchor) { 41 | //只是添加到后面 42 | // parent.append(child); 43 | 44 | // 添加到指定位置 45 | parent.insertBefore(child, anchor || null); 46 | } 47 | 48 | //删除children 49 | function remove(child) { 50 | const parent = child.parentNode; 51 | if (parent) { 52 | parent.removeChild(child); 53 | } 54 | } 55 | 56 | function setElementText(el, text) { 57 | el.textContent = text; 58 | } 59 | 60 | const renderer: any = createRenderer({ 61 | createElement, 62 | patchProp, 63 | insert, 64 | remove, 65 | setElementText, 66 | }); 67 | 68 | export function createApp(...args) { 69 | return renderer.createApp(...args); 70 | } 71 | 72 | export * from "../runtime-core/index"; 73 | -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./toDisplayString"; 2 | 3 | export const extend = Object.assign; 4 | 5 | export const EMPTY_OBJ = {}; 6 | 7 | export function isObject(val) { 8 | return val !== null && typeof val === "object"; 9 | } 10 | 11 | export const hasOwn = (val, key) => Object.prototype.hasOwnProperty.call(val, key); 12 | 13 | //add-foo ->addFoo 14 | export const camelize = (str: string) => { 15 | return str.replace(/-(\w)/g, (_, c: string) => { 16 | return c ? c.toUpperCase() : ""; 17 | }); 18 | }; 19 | //addFoo ->AddFoo 20 | export const capitalize = (str: string) => { 21 | return str.charAt(0).toUpperCase() + str.slice(1); 22 | }; 23 | // console.log(capitalize(event)); 24 | 25 | // AddFoo -> toAddFoo 26 | export const toHandlerKey = (str: string) => { 27 | return str ? "on" + str : ""; 28 | }; 29 | 30 | export const isString = (value) => typeof value === "string"; 31 | -------------------------------------------------------------------------------- /src/shared/shapeFlags.ts: -------------------------------------------------------------------------------- 1 | export const enum shapeFlags { 2 | element = 1, //0001 3 | stateful_component = 1 << 1, // << 左移1位 0010 4 | text_children = 1 << 2, // 左移2位 0100 5 | array_children = 1 << 3, //左移3位 1000 6 | slot_children = 1 << 4, //左移四位 10000 7 | } 8 | -------------------------------------------------------------------------------- /src/shared/text.ts: -------------------------------------------------------------------------------- 1 | const shapeFlags = { 2 | element: 0, 3 | stateful_component: 0, 4 | text_children: 0, 5 | array_children: 0, 6 | }; 7 | 8 | //可以设置,修改 9 | // v node -> stateful_component -> 10 | //shapeFlags.stateful_component = 1 11 | //shapeFlags.array_children = 1, 12 | 13 | //2. 查找 14 | // if(shapeFlags.element) 15 | // if(shapeFlags.stateful_component) 16 | 17 | // 不够高效 -> 位运算的方式 18 | // 0000; 19 | 20 | // 0001 -> Element 21 | // 0010 -> stateful 22 | // 0100 -> text_children 23 | // 1000 -> array_children 24 | 25 | // | 或运算 两位都为0 才为0 26 | // & 与运算 两位都为1,才为1 27 | 28 | //修改 29 | // 0000 30 | // 0001 31 | // ---- 32 | // 0000 | 0001 = 0001 33 | 34 | //查找 & 35 | // 0001 36 | // 0001 37 | // ---- 38 | // 0001 39 | -------------------------------------------------------------------------------- /src/shared/toDisplayString.ts: -------------------------------------------------------------------------------- 1 | export function toDisplayString(value) { 2 | return String(value); 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es2016" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */, 8 | "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, 9 | "lib": ["DOM", "ES6", "ES2016.Array.Include"] /* Specify library files to be included in the compilation. */, 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | // "outDir": "./", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true /* Enable all strict type-checking options. */, 29 | "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 45 | 46 | /* Module Resolution Options */ 47 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | "types": ["jest"] /* Type declaration files to be included in compilation. */, 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 67 | 68 | /* Advanced Options */ 69 | "skipLibCheck": true /* Skip type checking of declaration files. */, 70 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 71 | } 72 | } 73 | --------------------------------------------------------------------------------