├── src ├── global.d.ts ├── compiler-core │ ├── src │ │ ├── index.ts │ │ ├── runtimeHelper.ts │ │ ├── transforms │ │ │ ├── transformExpression.ts │ │ │ ├── transformElement.ts │ │ │ └── transformText.ts │ │ ├── ast.ts │ │ ├── compile.ts │ │ ├── transform.ts │ │ ├── parse.ts │ │ └── codegen.ts │ └── __tests__ │ │ ├── transform.spec.ts │ │ ├── __snapshots__ │ │ └── codegen.spec.ts.snap │ │ ├── codegen.spec.ts │ │ └── parse.spec.ts ├── shared │ ├── toDisplayString.ts │ └── index.ts ├── runtime-core │ ├── componentProps.ts │ ├── componentEmit.ts │ ├── helper │ │ └── renderSlots.ts │ ├── componentUpdateUtils.ts │ ├── createApp.ts │ ├── scheduler.ts │ ├── index.ts │ ├── componentSlots.ts │ ├── componentPublicInstance.ts │ ├── apiInject.ts │ ├── shapeFlags.ts │ ├── vnode.ts │ ├── component.ts │ └── renderer.ts ├── reactivity │ ├── src │ │ ├── dep.ts │ │ ├── index.ts │ │ ├── computed.ts │ │ ├── reactive.ts │ │ ├── baseHandlers.ts │ │ ├── ref.ts │ │ └── effect.ts │ └── __tests__ │ │ ├── shallowReadonly.spec.ts │ │ ├── computed.spec.ts │ │ ├── ref.spec.ts │ │ ├── reactive.spec.ts │ │ └── effect.spec.ts ├── other │ ├── __test__ │ │ └── stateMachine.spec.ts │ └── src │ │ └── stateMachine.ts ├── index.ts └── runtime-dom │ └── index.ts ├── .gitignore ├── .vscode └── settings.json ├── .DS_Store ├── docs ├── imgs │ ├── ref.png │ ├── computed.png │ ├── effect.png │ ├── reactive.png │ ├── vue3思维导图.png │ └── moduleResolution.png ├── reactive.md ├── note.md └── think.md ├── jest.config.js ├── rollup.config.js ├── package.json ├── tsconfig.json ├── README.md └── example ├── reactive.html ├── compiler.html ├── patchChildren ├── ArrayToText.html ├── TextToArray.html ├── 中间不同.html └── ArrayToArray.html ├── craeteRender.html ├── componentUpdate ├── nextTick.html └── index.html ├── effect.html ├── updateView.html └── helloworld.html /src/global.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib -------------------------------------------------------------------------------- /src/compiler-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./compile"; 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "liveServer.settings.port": 5501 3 | } 4 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellingfordly/mini-vue/HEAD/.DS_Store -------------------------------------------------------------------------------- /docs/imgs/ref.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellingfordly/mini-vue/HEAD/docs/imgs/ref.png -------------------------------------------------------------------------------- /docs/imgs/computed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellingfordly/mini-vue/HEAD/docs/imgs/computed.png -------------------------------------------------------------------------------- /docs/imgs/effect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellingfordly/mini-vue/HEAD/docs/imgs/effect.png -------------------------------------------------------------------------------- /docs/imgs/reactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellingfordly/mini-vue/HEAD/docs/imgs/reactive.png -------------------------------------------------------------------------------- /docs/imgs/vue3思维导图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellingfordly/mini-vue/HEAD/docs/imgs/vue3思维导图.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "ts-jest", 3 | testEnvironment: "node", 4 | }; 5 | -------------------------------------------------------------------------------- /src/shared/toDisplayString.ts: -------------------------------------------------------------------------------- 1 | export function toDisplayString(value) { 2 | return String(value); 3 | } 4 | -------------------------------------------------------------------------------- /docs/imgs/moduleResolution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shellingfordly/mini-vue/HEAD/docs/imgs/moduleResolution.png -------------------------------------------------------------------------------- /src/runtime-core/componentProps.ts: -------------------------------------------------------------------------------- 1 | export function initProps(instance, rawProps) { 2 | instance.props = rawProps || {}; 3 | } 4 | -------------------------------------------------------------------------------- /src/reactivity/src/dep.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "./effect"; 2 | 3 | export type Dep = Set; 4 | 5 | export function createDep(effects?: ReactiveEffect[]) { 6 | const dep = new Set(effects); 7 | return dep; 8 | } 9 | -------------------------------------------------------------------------------- /src/runtime-core/componentEmit.ts: -------------------------------------------------------------------------------- 1 | import { cameline, toHandlerKey } from "../shared"; 2 | 3 | export function emit(instance, event, ...args) { 4 | const handler = instance.props[toHandlerKey(cameline(event))]; 5 | 6 | handler && handler(...args); 7 | } 8 | -------------------------------------------------------------------------------- /src/compiler-core/src/runtimeHelper.ts: -------------------------------------------------------------------------------- 1 | export const TO_DISPLAY_STRING = Symbol("toDisplayString"); 2 | export const CREATE_ELEMENT_VNODE = Symbol("createElementVnode"); 3 | 4 | export const helperMapName = { 5 | [TO_DISPLAY_STRING]: "toDisplayString", 6 | [CREATE_ELEMENT_VNODE]: "createElementVnode", 7 | }; 8 | -------------------------------------------------------------------------------- /src/reactivity/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | reactive, 3 | readonly, 4 | isProxy, 5 | isReactive, 6 | isReadonly, 7 | } from "./reactive"; 8 | 9 | export { effect, stop } from "./effect"; 10 | 11 | export { computed } from "./computed"; 12 | 13 | export { ref, isRef, unRef, proxyRefs } from "./ref"; 14 | -------------------------------------------------------------------------------- /src/runtime-core/helper/renderSlots.ts: -------------------------------------------------------------------------------- 1 | import { isFunction } from "../../shared"; 2 | import { createVNode, Fragment } from "../vnode"; 3 | 4 | export function renderSlots(slots, name, props?) { 5 | const slot = slots[name]; 6 | 7 | if (slot) { 8 | if (isFunction(slot)) { 9 | return createVNode(Fragment, {}, slot(props)); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /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 | 9 | function processExpression(node: any): any { 10 | node.content = `_ctx.${node.content}`; 11 | return node; 12 | } 13 | -------------------------------------------------------------------------------- /src/runtime-core/componentUpdateUtils.ts: -------------------------------------------------------------------------------- 1 | export function shouldUpdateComponent(n1, n2) { 2 | const { props: prevProps } = n1; 3 | const { props: nextProps } = n2; 4 | 5 | for (const key in prevProps) { 6 | if (Reflect.has(prevProps, key)) { 7 | if (prevProps[key] !== nextProps[key]) { 8 | return true; 9 | } 10 | } 11 | } 12 | 13 | return false; 14 | } 15 | -------------------------------------------------------------------------------- /src/other/__test__/stateMachine.spec.ts: -------------------------------------------------------------------------------- 1 | import { stateMachine } from "../src/stateMachine"; 2 | 3 | describe("stateMachine", () => { 4 | test.only("simple test", () => { 5 | const res = stateMachine("abc"); 6 | expect(res).toStrictEqual([[0, 2]]); 7 | 8 | const res1 = stateMachine("xabcxxabc"); 9 | expect(res1).toStrictEqual([ 10 | [1, 3], 11 | [6, 8], 12 | ]); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/runtime-core/createApp.ts: -------------------------------------------------------------------------------- 1 | import { createVNode } from "./vnode"; 2 | 3 | export function createAppAPI(render) { 4 | 5 | return function createApp(rootComponent) { 6 | return { 7 | mount(rootContainer) { 8 | // 将 跟组件rootComponent(vue组件)转化为虚拟节点 vnode 9 | const vnode = createVNode(rootComponent); 10 | 11 | // 调用渲染函数 12 | render(vnode, rootContainer, null); 13 | }, 14 | }; 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import typescript from "@rollup/plugin-typescript"; 2 | 3 | export default { 4 | input: "./src/index.ts", 5 | output: [ 6 | // 1. cjs --> commonjs 7 | // 2. esm 8 | { 9 | format: "cjs", 10 | file: "lib/mini-vue.cjs.js", 11 | sourcemap: true, 12 | }, 13 | { 14 | format: "es", 15 | file: "lib/mini-vue.esm.js", 16 | sourcemap: true, 17 | }, 18 | ], 19 | plugins: [typescript()], 20 | }; 21 | -------------------------------------------------------------------------------- /src/compiler-core/src/ast.ts: -------------------------------------------------------------------------------- 1 | import { CREATE_ELEMENT_VNODE } from "./runtimeHelper"; 2 | 3 | export const enum NodeTypes { 4 | INTERPOLATION, 5 | SIMPLE_EXPRESSION, 6 | ELEMENT, 7 | TEXT, 8 | ROOT, 9 | COMPUND_EXPORESS, 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/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./runtime-dom"; 2 | export * from "./reactivity/src"; 3 | 4 | import { baseCompile } from "./compiler-core/src/compile"; 5 | import * as runtimeDom from "./runtime-dom"; 6 | import { registerRuntiomCompiler } from "./runtime-dom"; 7 | 8 | export function compileToFunction(template) { 9 | const code = baseCompile(template); 10 | const render = new Function("Vue", code)(runtimeDom); 11 | return render; 12 | } 13 | 14 | registerRuntiomCompiler(compileToFunction); 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mini-vue", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "test": "jest", 8 | "build": "rollup -c rollup.config.js" 9 | }, 10 | "devDependencies": { 11 | "@rollup/plugin-typescript": "^8.3.1", 12 | "@types/jest": "^27.4.1", 13 | "jest": "^27.5.1", 14 | "node-sass": "^7.0.1", 15 | "rollup": "^2.70.1", 16 | "ts-jest": "^27.1.3", 17 | "tslib": "^2.3.1", 18 | "typescript": "^4.6.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/runtime-core/scheduler.ts: -------------------------------------------------------------------------------- 1 | const queue: any[] = []; 2 | const pResolve = Promise.resolve(); 3 | 4 | export function nextTick(callback) { 5 | return callback ? pResolve.then(callback) : pResolve; 6 | } 7 | 8 | export function queueJobs(job) { 9 | if (!queue.includes(job)) { 10 | queue.push(job); 11 | } 12 | 13 | queueFlush(); 14 | } 15 | 16 | function queueFlush() { 17 | nextTick(() => { 18 | let job; 19 | while ((job = queue.shift())) { 20 | job && job(); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transformElement.ts: -------------------------------------------------------------------------------- 1 | import { createVNodeCall, NodeTypes } from "../ast"; 2 | 3 | export function transformElement(node, context) { 4 | if (node.type === NodeTypes.ELEMENT) { 5 | return () => { 6 | const vnodeTag = `"${node.tag}"`; 7 | const vnodeProps = node.props; 8 | const vnodeChildren = node.children; 9 | 10 | node.codegenNode = createVNodeCall( 11 | context, 12 | vnodeTag, 13 | vnodeProps, 14 | vnodeChildren 15 | ); 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/runtime-core/index.ts: -------------------------------------------------------------------------------- 1 | export { getCurrentInstance, registerRuntiomCompiler } from "./component"; 2 | 3 | export { createAppAPI } from "./createApp"; 4 | 5 | export { 6 | h, 7 | Fragment, 8 | Text, 9 | createTextVNode, 10 | createVNode, 11 | createVNode as createElementVnode, 12 | } from "./vnode"; 13 | 14 | export { renderSlots } from "./helper/renderSlots"; 15 | 16 | export { provide, inject } from "./apiInject"; 17 | 18 | export { createRenderer } from "./renderer"; 19 | 20 | export { nextTick } from "./scheduler"; 21 | 22 | export { toDisplayString } from "../shared"; 23 | -------------------------------------------------------------------------------- /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/transformElement"; 5 | import { transformExpression } from "./transforms/transformExpression"; 6 | import { transformText } from "./transforms/transformText"; 7 | 8 | export function baseCompile(template) { 9 | const ast = baseParse(template); 10 | 11 | transform(ast, { 12 | nodeTransform: [transformExpression, transformElement, transformText], 13 | }); 14 | 15 | return generate(ast); 16 | } 17 | -------------------------------------------------------------------------------- /src/runtime-core/componentSlots.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from "../shared"; 2 | import { ShapeFlag } from "./shapeFlags"; 3 | 4 | export function initSlots(instance, children) { 5 | if (instance.vnode.shapeFlag & ShapeFlag.SLOT_CHILDREN) { 6 | normalizeObjectSlots(children, instance.slots); 7 | } 8 | } 9 | 10 | function normalizeObjectSlots(children, slots) { 11 | for (const key in children) { 12 | const value = children[key]; 13 | slots[key] = (props) => normalizeSlotValue(value(props)); 14 | } 15 | } 16 | 17 | export function normalizeSlotValue(slot) { 18 | return isArray(slot) ? slot : [slot]; 19 | } 20 | -------------------------------------------------------------------------------- /src/runtime-core/componentPublicInstance.ts: -------------------------------------------------------------------------------- 1 | const publicPropertiesMap = { 2 | $el: (instance) => instance.vnode.el, 3 | $slots: (instance) => instance.slots, 4 | $props: (instance) => instance.props, 5 | }; 6 | 7 | export const PublicInstanceProxyHandlers = { 8 | get({ _instance }, key) { 9 | const { setupState, props } = _instance; 10 | 11 | if (key in setupState) { 12 | return setupState[key]; 13 | } 14 | 15 | if (key in props) { 16 | return props[key]; 17 | } 18 | 19 | const publicGetter = publicPropertiesMap[key]; 20 | if (publicGetter) { 21 | return publicGetter(_instance); 22 | } 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /src/reactivity/src/computed.ts: -------------------------------------------------------------------------------- 1 | import { ReactiveEffect } from "./effect"; 2 | 3 | class ComputedRef { 4 | private _getter; 5 | private _dirty = true; 6 | private _value: any; 7 | private _effect: ReactiveEffect 8 | 9 | constructor(getter) { 10 | this._getter = getter; 11 | 12 | this._effect = new ReactiveEffect(getter, () => { 13 | this._dirty = true; 14 | }); 15 | } 16 | 17 | get value() { 18 | if (this._dirty) { 19 | this._dirty = false; 20 | this._value = this._effect.run(); 21 | } 22 | 23 | return this._value; 24 | } 25 | } 26 | 27 | export function computed(getter) { 28 | return new ComputedRef(getter); 29 | } 30 | -------------------------------------------------------------------------------- /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("transform", () => { 6 | it("happy path", () => { 7 | const ast = baseParse("
hi, {{message}}
"); 8 | const plugin = (node) => { 9 | if (node.type === NodeTypes.TEXT) { 10 | node.content = node.content + "mini-vue"; 11 | } 12 | }; 13 | 14 | transform(ast, { 15 | nodeTransform: [plugin], 16 | }); 17 | 18 | const nodeText = ast.children[0].children[0]; 19 | expect(nodeText.content).toBe("hi, mini-vue"); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/runtime-core/apiInject.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentInstance } from "."; 2 | 3 | export function provide(key, value) { 4 | const currentInstance: any = getCurrentInstance(); 5 | 6 | if (currentInstance) { 7 | if ( 8 | currentInstance.parent && 9 | // 解决多次调用 provide 时数据被覆盖问题 10 | currentInstance.provides === currentInstance.parent.provides 11 | ) { 12 | // 解决获取祖先组件数据问题 13 | currentInstance.provides = Object.create(currentInstance.parent.provides); 14 | } 15 | currentInstance.provides[key] = value; 16 | } 17 | } 18 | 19 | export function inject(key) { 20 | const currentInstance: any = getCurrentInstance(); 21 | if (currentInstance) { 22 | return currentInstance.parent.provides[key]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, // 开启所有严格的类型检查 4 | // 模块解析策略,ts默认用node的解析策略,即相对的方式导入 5 | "moduleResolution": "node", 6 | "target": "es2016", 7 | "module": "esnext", 8 | "esModuleInterop": true, 9 | "noImplicitAny": false, // 不允许隐式的 any 类型 10 | "removeComments": true, // 删除注释 11 | // 保留 const 和 enum 声明 12 | "preserveConstEnums": true, 13 | // 生成目标文件的sourceMap文件 14 | "sourceMap": true, 15 | // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现 16 | "downlevelIteration": true, 17 | // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost, 18 | // 如需要使用es的高级版本特性,通常都需要配置, 19 | // 如es8的数组新特性需要引入"ES2019.Array", 20 | // "lib": ["es6", "DOM"] 21 | }, 22 | "include": ["src/index.ts", "src/global.d.ts"] 23 | } 24 | -------------------------------------------------------------------------------- /src/reactivity/__tests__/shallowReadonly.spec.ts: -------------------------------------------------------------------------------- 1 | import { isProxy, isReadonly, shallowReadonly } from "../src/reactive"; 2 | 3 | describe("shallowReadonly", () => { 4 | it("should not make non-reactive properties reactive", () => { 5 | const shallowObj = shallowReadonly({ a: { b: 1 } }); 6 | expect(isReadonly(shallowObj)).toBe(true); 7 | expect(isReadonly(shallowObj.a)).toBe(false); 8 | expect(isProxy(shallowObj)).toBe(true); 9 | expect(isProxy(shallowObj.a)).toBe(false); 10 | }); 11 | 12 | it("create readonly", () => { 13 | const shallowObj = shallowReadonly({ a: { b: 1 } }); 14 | 15 | console.warn = jest.fn(); 16 | 17 | shallowObj.c = 2; 18 | expect(console.warn).toBeCalled(); 19 | // 由于深层不是 reactive,对深层设置不会触发 console.warn 20 | shallowObj.a.b = 2; 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mini-vue 2 | 3 | [mini-vue](https://github.com/cuixiaorui/mini-vue)是由[cuixiaorui](https://github.com/cuixiaorui)编写的开源 vue3 学习项目,在[阮一峰周刊第 144 期](https://www.ruanyifeng.com/blog/2021/01/weekly-issue-144.html)中推荐过这个库,它实现最简 vue3 模型,用于深入学习 vue3,能够更轻松的理解 vue3 的核心逻辑。 4 | 5 | 此项目主要是为了学习 mini-vue,理解 vue3 核心逻辑,以及学习中的一些[思考与疑问](https://github.com/shellingfordly/mini-vue/blob/main/docs/think.md)记录,最后自己能够从头到尾的实现一遍 mini-vue。 6 | 7 | ## 思维导图 8 | 9 | ![vue3思维导图](./docs/imgs/vue3思维导图.png) 10 | 11 | ## 学习笔记 12 | 13 | ### reactivity 14 | 15 | 1. [reactive](https://github.com/shellingfordly/mini-vue/blob/main/docs/reactive.md) 16 | 17 | ![reactive 思维导图](./docs/imgs/reactive.png) 18 | 19 | 2. effect 20 | 21 | ![effect 思维导图](./docs/imgs/effect.png) 22 | 23 | 3. ref 24 | 25 | ![ref 思维导图](./docs/imgs/ref.png) 26 | 27 | 4. computed 28 | 29 | ![computed 思维导图](./docs/imgs/computed.png) 30 | 31 | ### runtime-core 32 | 33 | ### runtime-dom 34 | -------------------------------------------------------------------------------- /example/reactive.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 9 | 45 | -------------------------------------------------------------------------------- /example/compiler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Complier 8 | 9 | 10 |
11 | 12 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/runtime-core/shapeFlags.ts: -------------------------------------------------------------------------------- 1 | export enum ShapeFlag { 2 | NUll = 0, 3 | ELEMENT = 1, 4 | STATEFUL_COMPONENT = 1 << 1, // 0010 5 | TEXT_CHILDREN = 1 << 2, // 0100 6 | ARRAY_CHILDREN = 1 << 3, // 1000 7 | SLOT_CHILDREN = 1 << 4, //10000 8 | } 9 | 10 | /** 11 | * @description 判断 vnode 类型 12 | * @param type 被判断的类型 13 | * @param shapeFlag 期望是的类型 14 | * @returns ShapeFlag 15 | */ 16 | export function is(type, shapeFlag): ShapeFlag { 17 | return type & shapeFlag; 18 | } 19 | 20 | export function isElement(shapeFlag): ShapeFlag { 21 | return shapeFlag & ShapeFlag.ELEMENT; 22 | } 23 | 24 | export function isCompoent(shapeFlag): ShapeFlag { 25 | return shapeFlag & ShapeFlag.STATEFUL_COMPONENT; 26 | } 27 | 28 | export function isTextChildren(shapeFlag): ShapeFlag { 29 | return shapeFlag & ShapeFlag.TEXT_CHILDREN; 30 | } 31 | 32 | export function isArrayChildren(shapeFlag): ShapeFlag { 33 | return shapeFlag & ShapeFlag.ARRAY_CHILDREN; 34 | } 35 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToText.html: -------------------------------------------------------------------------------- 1 | xx 2 |
3 | 4 | 40 | -------------------------------------------------------------------------------- /example/patchChildren/TextToArray.html: -------------------------------------------------------------------------------- 1 | xx 2 |
3 | 4 | 40 | -------------------------------------------------------------------------------- /example/craeteRender.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41 | -------------------------------------------------------------------------------- /example/componentUpdate/nextTick.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 48 | -------------------------------------------------------------------------------- /src/other/src/stateMachine.ts: -------------------------------------------------------------------------------- 1 | export function stateMachine(str: string) { 2 | let i = 0; 3 | let startIndex: number = 0; 4 | let endIndex: number = 0; 5 | let result: number[][] = []; 6 | 7 | const waitForA = (char) => { 8 | if (char === "a") { 9 | startIndex = i; 10 | return waitForB; 11 | } 12 | return waitForA; 13 | }; 14 | 15 | const waitForB = (char) => { 16 | if (char === "b") { 17 | return waitForC; 18 | } 19 | return waitForB; 20 | }; 21 | 22 | const waitForC = (char) => { 23 | if (char === "c") { 24 | endIndex = i; 25 | return end; 26 | } 27 | return waitForC; 28 | }; 29 | 30 | const end = () => { 31 | return end; 32 | }; 33 | 34 | let currentState = waitForA; 35 | for (i = 0; i < str.length; i++) { 36 | const nextState = currentState(str[i]); 37 | currentState = nextState; 38 | 39 | if (nextState === end) { 40 | result.push([startIndex, endIndex]); 41 | currentState = waitForA; 42 | // return true; 43 | } 44 | } 45 | 46 | return result; 47 | // return false; 48 | } 49 | -------------------------------------------------------------------------------- /example/effect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | effect 8 | 9 | 10 |
11 | 12 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/compiler-core/src/transforms/transformText.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "../ast"; 2 | 3 | export function transformText(node) { 4 | const isText = (node) => 5 | node.type === NodeTypes.TEXT || node.type === NodeTypes.INTERPOLATION; 6 | 7 | if (node.type === NodeTypes.ELEMENT) { 8 | return () => { 9 | const { children } = node; 10 | let currentContainer; 11 | for (let i = 0; i < children.length; i++) { 12 | const child = children[i]; 13 | 14 | if (isText(child)) { 15 | for (let j = i + 1; j < children.length; j++) { 16 | const nextChild = children[j]; 17 | 18 | if (isText(nextChild)) { 19 | if (!currentContainer) { 20 | currentContainer = children[i] = { 21 | type: NodeTypes.COMPUND_EXPORESS, 22 | children: [child], 23 | }; 24 | } 25 | 26 | currentContainer.children.push(" + "); 27 | currentContainer.children.push(nextChild); 28 | children.splice(j, 1); 29 | j--; 30 | } else { 31 | currentContainer = undefined; 32 | break; 33 | } 34 | } 35 | } 36 | } 37 | }; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/reactivity/__tests__/computed.spec.ts: -------------------------------------------------------------------------------- 1 | import { computed } from "../src/computed"; 2 | import { reactive } from "../src/reactive"; 3 | 4 | describe("computed", () => { 5 | it("create computed", () => { 6 | const count = reactive({ value: 1 }); 7 | 8 | const doubleCount = computed(() => count.value * 2); 9 | 10 | expect(doubleCount.value).toBe(2); 11 | }); 12 | 13 | it("should computed lazy", () => { 14 | 15 | const count = reactive({ value: 1 }); 16 | 17 | const getter = jest.fn(() => count.value * 2); 18 | const doubleCount = computed(getter); 19 | 20 | // lazy 21 | expect(getter).not.toHaveBeenCalled(); 22 | 23 | // get 24 | expect(doubleCount.value).toBe(2); 25 | expect(getter).toHaveBeenCalledTimes(1); 26 | 27 | // get again 28 | expect(doubleCount.value).toBe(2); 29 | expect(getter).toHaveBeenCalledTimes(1); 30 | 31 | // set count ---> update doubleCount 32 | count.value = 2; 33 | expect(getter).toHaveBeenCalledTimes(1); 34 | 35 | // update doubleCount 36 | expect(doubleCount.value).toBe(4); 37 | expect(getter).toHaveBeenCalledTimes(2); 38 | 39 | // get again after update doubleCount 40 | expect(doubleCount.value).toBe(4); 41 | expect(getter).toHaveBeenCalledTimes(2); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /src/runtime-dom/index.ts: -------------------------------------------------------------------------------- 1 | import { createRenderer } from "../runtime-core"; 2 | import { isNullOrUndefined } from "../shared"; 3 | 4 | export function createElement(type) { 5 | return document.createElement(type); 6 | } 7 | 8 | export function patchProp(el, key, preValue, nextValue) { 9 | if (isOnEvent(key)) { 10 | const event = key.slice(2).toLowerCase(); 11 | el.addEventListener(event, nextValue); 12 | } else { 13 | if (isNullOrUndefined(nextValue)) { 14 | el.removeAttribute(key); 15 | } else { 16 | el.setAttribute(key, nextValue); 17 | } 18 | } 19 | } 20 | 21 | export function insert(el, parent, anchor) { 22 | parent.insertBefore(el, anchor || null); 23 | } 24 | 25 | function setElementText(el, text) { 26 | el.textContent = text; 27 | } 28 | 29 | function remove(el) { 30 | const parent = el.parentNode; 31 | parent && parent.removeChild(el); 32 | } 33 | 34 | /** 35 | * @description 判断是否为 事件 key 36 | * @param key string 37 | * @returns 38 | */ 39 | function isOnEvent(key): boolean { 40 | return /^on[A-Z]/.test(key); 41 | } 42 | 43 | const renderer: any = createRenderer({ 44 | createElement, 45 | patchProp, 46 | insert, 47 | setElementText, 48 | remove, 49 | }); 50 | 51 | export const createApp = function (...args) { 52 | return renderer.createApp(...args); 53 | }; 54 | 55 | export * from "../runtime-core"; 56 | -------------------------------------------------------------------------------- /src/reactivity/src/reactive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | mutableHandlers, 3 | readonlyHandlers, 4 | shallowReadonlyHandlers, 5 | } from "./baseHandlers"; 6 | 7 | const reactiveMap = new WeakMap(); 8 | const readonlyMap = new WeakMap(); 9 | const shallowReadonlyMap = new WeakMap(); 10 | 11 | export enum ReactiveFlags { 12 | IS_REACTIVE = "__v_is_Reactive", 13 | IS_READONLY = "__v_is_Readonly", 14 | } 15 | 16 | export function reactive(target): typeof target { 17 | return createReactiveObject(target, reactiveMap, mutableHandlers); 18 | } 19 | 20 | export function readonly(target) { 21 | return createReactiveObject(target, readonlyMap, readonlyHandlers); 22 | } 23 | 24 | export function shallowReadonly(target) { 25 | return createReactiveObject( 26 | target, 27 | shallowReadonlyMap, 28 | shallowReadonlyHandlers 29 | ); 30 | } 31 | 32 | export function isProxy(target) { 33 | return isReactive(target) || isReadonly(target); 34 | } 35 | 36 | export function isReactive(target) { 37 | return !!target[ReactiveFlags.IS_REACTIVE]; 38 | } 39 | 40 | export function isReadonly(target) { 41 | return !!target[ReactiveFlags.IS_READONLY]; 42 | } 43 | 44 | function createReactiveObject(taget, proxyMap, baseHandlers) { 45 | const existingProxy = proxyMap.get(taget); 46 | 47 | // 使用缓存优化 48 | if (existingProxy) { 49 | return existingProxy; 50 | } 51 | 52 | const proxy = new Proxy(taget, baseHandlers); 53 | proxyMap.set(taget, proxy); 54 | 55 | return proxy; 56 | } 57 | -------------------------------------------------------------------------------- /example/patchChildren/中间不同.html: -------------------------------------------------------------------------------- 1 | xx 2 |
3 | 4 | 58 | -------------------------------------------------------------------------------- /example/updateView.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 | 13 | 72 | -------------------------------------------------------------------------------- /src/shared/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./toDisplayString"; 2 | 3 | enum Type { 4 | Object = "Object", 5 | Array = "Array", 6 | String = "String", 7 | Function = "Function", 8 | } 9 | 10 | export const EMPTY_OBJ = {}; 11 | 12 | const toString = Object.prototype.toString; 13 | 14 | function is(val, type) { 15 | return toString.call(val) === `[object ${type}]`; 16 | } 17 | 18 | export const isObjectOrArray = (val) => isObject(val) || isArray(val); 19 | 20 | export const isObject = (val) => is(val, Type.Object); 21 | 22 | export const isArray = (val) => is(val, Type.Array); 23 | 24 | export const isString = (val) => is(val, Type.String); 25 | 26 | export const isFunction = (val) => is(val, Type.Function); 27 | 28 | export const isUndefined = (val) => val === undefined; 29 | 30 | export const isNull = (val) => val === null; 31 | 32 | export const isNullOrUndefined = (val) => isUndefined(val) || isNull(val); 33 | 34 | export const hasChange = (obj, newObj) => !Object.is(obj, newObj); 35 | 36 | /** 37 | * @description 短横线命名法 转 驼峰命名法 38 | * @param str 39 | * @returns 40 | */ 41 | export const cameline = (str: string) => { 42 | return str.replace(/-(\w)/g, (_, c: string) => c.toUpperCase()); 43 | }; 44 | 45 | /** 46 | * @description 首字母大写 47 | * @param str 48 | * @returns 49 | */ 50 | export const capitalize = (str: string) => { 51 | return str.charAt(0).toUpperCase() + str.slice(1); 52 | }; 53 | 54 | /** 55 | * @description 处理 on 事件名 56 | * @param str 57 | * @returns 58 | */ 59 | export const toHandlerKey = (str: string) => { 60 | return `on${capitalize(str)}`; 61 | }; 62 | -------------------------------------------------------------------------------- /src/compiler-core/__tests__/__snapshots__/codegen.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`codegen element > element > element 1`] = ` 4 | "const { createElementVnode: _createElementVnode, toDisplayString: _toDisplayString } = Vue 5 | return function render(_ctx, _cache){return _createElementVnode(\\"div\\", null, [ _createElementVnode(\\"p\\", null, [ _createElementVnode(\\"span\\", null, \\"Hi, Tom.\\"), \\" My name is \\" + _toDisplayString(_ctx.username)] )] )}" 6 | `; 7 | 8 | exports[`codegen element > element > interpolation/string 1`] = ` 9 | "const { toDisplayString: _toDisplayString, createElementVnode: _createElementVnode } = Vue 10 | return function render(_ctx, _cache){return _createElementVnode(\\"div\\", null, [ \\"hi, \\", _createElementVnode(\\"span\\", null, \\"I'm \\" + _toDisplayString(_ctx.message))] )}" 11 | `; 12 | 13 | exports[`codegen element > interpolation/string 1`] = ` 14 | "const { toDisplayString: _toDisplayString, createElementVnode: _createElementVnode } = Vue 15 | return function render(_ctx, _cache){return _createElementVnode(\\"div\\", null, \\"hi, \\" + _toDisplayString(_ctx.message))}" 16 | `; 17 | 18 | exports[`codegen element 1`] = ` 19 | "const { createElementVnode: _createElementVnode } = Vue 20 | return function render(_ctx, _cache){return _createElementVnode(\\"div\\", null)}" 21 | `; 22 | 23 | exports[`codegen interpolation 1`] = ` 24 | "const { toDisplayString: _toDisplayString } = Vue 25 | return function render(_ctx, _cache){return _toDisplayString(_ctx.message)}" 26 | `; 27 | 28 | exports[`codegen string 1`] = `"return function render(_ctx, _cache){return \\"hi\\"}"`; 29 | -------------------------------------------------------------------------------- /src/reactivity/src/baseHandlers.ts: -------------------------------------------------------------------------------- 1 | import { isObjectOrArray } from "../../shared/index"; 2 | import { track, trigger } from "./effect"; 3 | import { reactive, ReactiveFlags, readonly } from "./reactive"; 4 | 5 | const get = createGetter(); 6 | const set = createSetter(); 7 | const readonlyGet = createGetter(true); 8 | const shallowReadonlyGet = createGetter(true, true); 9 | 10 | function createGetter(isReadonly = false, shallow = false) { 11 | return (target, key, receiver) => { 12 | if (key === ReactiveFlags.IS_REACTIVE) { 13 | return !isReadonly; 14 | } else if (key === ReactiveFlags.IS_READONLY) { 15 | return isReadonly; 16 | } 17 | 18 | // 如果不是只读对象,收集依赖 19 | if (!isReadonly) { 20 | track(target, "get", key); 21 | } 22 | 23 | const res = Reflect.get(target, key, receiver); 24 | 25 | if (shallow) { 26 | return res; 27 | } 28 | 29 | if (isObjectOrArray(res)) { 30 | return isReadonly ? readonly(res) : reactive(res); 31 | } 32 | return res; 33 | }; 34 | } 35 | 36 | function createSetter() { 37 | return (target, key, value, receiver) => { 38 | const res = Reflect.set(target, key, value, receiver); 39 | 40 | trigger(target, "set", key); 41 | return res; 42 | }; 43 | } 44 | 45 | export const mutableHandlers = { 46 | get, 47 | set, 48 | }; 49 | 50 | export const readonlyHandlers = { 51 | get: readonlyGet, 52 | set(target, key) { 53 | console.warn(`${target} of ${key} can't be setted, it's a readonly object`); 54 | return true; 55 | }, 56 | }; 57 | 58 | export const shallowReadonlyHandlers = Object.assign({}, readonlyHandlers, { 59 | get: shallowReadonlyGet, 60 | }); 61 | -------------------------------------------------------------------------------- /example/componentUpdate/index.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 9 | 76 | -------------------------------------------------------------------------------- /src/reactivity/src/ref.ts: -------------------------------------------------------------------------------- 1 | import { hasChange, isObjectOrArray } from "../../shared/index"; 2 | import { createDep, Dep } from "./dep"; 3 | import { isTracking, trackEffects, triggerEffects } from "./effect"; 4 | import { reactive } from "./reactive"; 5 | 6 | class RefImpl { 7 | private _value: T; 8 | private _rawValue: T; 9 | private dep: Dep; 10 | 11 | public __v_isRef = true; 12 | 13 | constructor(value) { 14 | this._value = convert(value); 15 | this._rawValue = value; 16 | this.dep = createDep(); 17 | } 18 | 19 | get value() { 20 | if (isTracking()) { 21 | trackEffects(this.dep); 22 | } 23 | return this._value; 24 | } 25 | 26 | set value(newValue) { 27 | if (hasChange(newValue, this._rawValue)) { 28 | this._value = convert(newValue); 29 | this._rawValue = newValue; 30 | triggerEffects(this.dep); 31 | } 32 | } 33 | } 34 | 35 | function convert(value) { 36 | return isObjectOrArray(value) ? reactive(value) : value; 37 | } 38 | 39 | export function ref(value): RefImpl { 40 | const ref = new RefImpl(value); 41 | 42 | return ref; 43 | } 44 | 45 | export function isRef(value) { 46 | return !!value.__v_isRef; 47 | } 48 | 49 | export function unRef(ref) { 50 | return isRef(ref) ? ref.value : ref; 51 | } 52 | 53 | export function proxyRefs(objectWithRefs) { 54 | return new Proxy(objectWithRefs, { 55 | get(target, key, receiver) { 56 | return unRef(Reflect.get(target, key, receiver)); 57 | }, 58 | set(target, key, value, receiver) { 59 | const oldValue = target[key]; 60 | if (isRef(oldValue) && !isRef(value)) { 61 | return (target[key].value = value); 62 | } else { 63 | return Reflect.set(target, key, value, receiver); 64 | } 65 | }, 66 | }); 67 | } 68 | -------------------------------------------------------------------------------- /src/reactivity/__tests__/ref.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect } from "../src/effect"; 2 | import { reactive } from "../src/reactive"; 3 | import { ref, isRef, unRef, proxyRefs } from "../src/ref"; 4 | 5 | describe("ref", () => { 6 | it("create ref object", () => { 7 | const count = ref(0); 8 | expect(count.value).toBe(0); 9 | }); 10 | 11 | it("effect ref", () => { 12 | let doubleCount; 13 | const count = ref(1); 14 | expect(count.value).toBe(1); 15 | 16 | effect(() => { 17 | doubleCount = count.value * 2; 18 | }); 19 | 20 | expect(doubleCount).toBe(2); 21 | 22 | count.value = 2; 23 | expect(doubleCount).toBe(4); 24 | }); 25 | 26 | it("object ref", () => { 27 | let name = ""; 28 | const info = ref({ 29 | lastName: "xxx", 30 | firstName: "sss", 31 | }); 32 | 33 | effect(() => { 34 | name = info.value.lastName + info.value.firstName; 35 | }); 36 | 37 | expect(name).toBe("xxxsss"); 38 | 39 | info.value.lastName = "sss"; 40 | expect(name).toBe("ssssss"); 41 | }); 42 | 43 | it("isRef", () => { 44 | const count = ref(1); 45 | 46 | expect(isRef(count)).toBe(true); 47 | expect(isRef(1)).toBe(false); 48 | }); 49 | 50 | it("unRef", () => { 51 | const count = ref(1); 52 | 53 | expect(unRef(count)).toBe(1); 54 | expect(unRef(1)).toBe(1); 55 | }); 56 | 57 | it("proxyRefs", () => { 58 | const info = reactive({ age: ref(18) }); 59 | 60 | const proxyRefInfo = proxyRefs(info); 61 | expect(proxyRefInfo.age).toBe(18); 62 | expect(info.age.value).toBe(18); 63 | expect(isRef(info.age)).toBe(true); 64 | 65 | // proxyRefInfo.age = 20; 66 | 67 | // expect(proxyRefInfo.age).toBe(20); 68 | // expect(isRef(info.age)).toBe(true); 69 | // expect(info.age.value).toBe(20); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /src/runtime-core/vnode.ts: -------------------------------------------------------------------------------- 1 | import { ShapeFlag } from "./shapeFlags"; 2 | import { isArray, isObject, isString } from "../shared"; 3 | 4 | export const Fragment = Symbol("Fragment"); 5 | export const Text = Symbol("Text"); 6 | 7 | /** 8 | * @description 创建一个虚拟节点对象 9 | * @param type 虚拟节点 10 | * 1. dom元素类型, nodeType 11 | * 2. vue 组件对象 12 | * @param props 节点属性 13 | * @param children 子(虚拟)节点 14 | * @returns vnode 虚拟节点 15 | */ 16 | export function createVNode(type, props?, children?) { 17 | const shapeFlag = getShapeFlag(type, children); 18 | 19 | const vnode = { 20 | type, // vue 组件对象 21 | props, 22 | children, 23 | el: null, 24 | component: null, 25 | key: props && props.key, 26 | shapeFlag, 27 | }; 28 | 29 | return vnode; 30 | } 31 | 32 | export function createTextVNode(text: string, props = {}) { 33 | return createVNode(Text, props, text); 34 | } 35 | 36 | /** 37 | * @description 创建一个虚拟节点对象 38 | * @param type 39 | * @param props 40 | * @param children 41 | * @returns vnode 虚拟节点 42 | */ 43 | export function h(type, props?, children?) { 44 | return createVNode(type, props, children); 45 | } 46 | 47 | /** 48 | * @description 设置 vnode 的 shapeFlag 类型 49 | * 方便后面判断 vnode 属于什么类型的 节点, 组件/element/文本/数组 50 | * @param type vnode type 51 | * @param children vnode children 52 | * @returns shapeFlag number 53 | */ 54 | function getShapeFlag(type, children): ShapeFlag { 55 | let shapeFlag = ShapeFlag.NUll; 56 | 57 | if (isString(type)) { 58 | shapeFlag |= ShapeFlag.ELEMENT; 59 | } else if (isObject(type)) { 60 | shapeFlag |= ShapeFlag.STATEFUL_COMPONENT; 61 | } 62 | 63 | if (isString(children)) { 64 | shapeFlag |= ShapeFlag.TEXT_CHILDREN; 65 | } else if (isArray(children)) { 66 | shapeFlag |= ShapeFlag.ARRAY_CHILDREN; 67 | } 68 | 69 | if (shapeFlag & ShapeFlag.STATEFUL_COMPONENT) { 70 | if (isObject(children)) { 71 | shapeFlag |= ShapeFlag.SLOT_CHILDREN; 72 | } 73 | } 74 | 75 | return shapeFlag; 76 | } 77 | -------------------------------------------------------------------------------- /src/compiler-core/src/transform.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast"; 2 | import { TO_DISPLAY_STRING } from "./runtimeHelper"; 3 | 4 | /** 5 | * @description transform 6 | * 将 basePardse 生成的ast树转换为方便 generate 方便去操作的节点树 7 | * 方便 generate 生成 render 函数 8 | * @param root 9 | * @param options 10 | */ 11 | export function transform(root, options = {}) { 12 | const context = createTransfromContext(root, options); 13 | 14 | traverseNode(root, context); 15 | 16 | createRootCodegen(root); 17 | 18 | root.helpers = [...context.helpers.keys()]; 19 | } 20 | 21 | function traverseNode(node, context) { 22 | const exitFns: any[] = []; 23 | for (let i = 0; i < context.nodeTransform.length; i++) { 24 | const transform = context.nodeTransform[i]; 25 | const onExit = transform(node, context); 26 | if (onExit) exitFns.push(onExit); 27 | } 28 | 29 | switch (node.type) { 30 | case NodeTypes.INTERPOLATION: 31 | context.helper(TO_DISPLAY_STRING); 32 | break; 33 | case NodeTypes.ROOT: 34 | case NodeTypes.ELEMENT: 35 | traverseChildren(node, context); 36 | break; 37 | default: 38 | break; 39 | } 40 | 41 | let i = exitFns.length; 42 | while (i--) { 43 | exitFns[i](); 44 | } 45 | } 46 | 47 | function traverseChildren(node: any, context: any) { 48 | const children = node.children; 49 | if (children) { 50 | for (let i = 0; i < children.length; i++) { 51 | const child = children[i]; 52 | traverseNode(child, context); 53 | } 54 | } 55 | } 56 | 57 | function createTransfromContext(root: any, options: any) { 58 | const context = { 59 | root, 60 | nodeTransform: options.nodeTransform || [], 61 | helpers: new Map(), 62 | helper(key) { 63 | context.helpers.set(key, 1); 64 | }, 65 | }; 66 | return context; 67 | } 68 | 69 | function createRootCodegen(root: any) { 70 | const child = root.children[0]; 71 | if (child.type === NodeTypes.ELEMENT) { 72 | root.codegenNode = child.codegenNode; 73 | } else { 74 | root.codegenNode = child; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /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 { transformExpression } from "../src/transforms/transformExpression"; 5 | import { transformElement } from "../src/transforms/transformElement"; 6 | import { transformText } from "../src/transforms/transformText"; 7 | 8 | describe("codegen", () => { 9 | it("string", () => { 10 | const ast = baseParse("hi"); 11 | 12 | transform(ast); 13 | 14 | const code = generate(ast); 15 | 16 | expect(code).toMatchSnapshot(); 17 | }); 18 | 19 | it("interpolation", () => { 20 | const ast = baseParse("{{message}}"); 21 | 22 | transform(ast, { 23 | nodeTransform: [transformExpression], 24 | }); 25 | 26 | const code = generate(ast); 27 | 28 | expect(code).toMatchSnapshot(); 29 | }); 30 | 31 | it("element", () => { 32 | const ast = baseParse("
"); 33 | 34 | transform(ast, { 35 | nodeTransform: [transformElement], 36 | }); 37 | 38 | const code = generate(ast); 39 | 40 | expect(code).toMatchSnapshot(); 41 | }); 42 | 43 | it("element > interpolation/string", () => { 44 | const ast = baseParse("
hi, {{message}}
"); 45 | 46 | transform(ast, { 47 | nodeTransform: [transformExpression, transformElement, transformText], 48 | }); 49 | 50 | const code = generate(ast); 51 | 52 | expect(code).toMatchSnapshot(); 53 | }); 54 | 55 | it("element > element > interpolation/string", () => { 56 | const ast = baseParse("
hi, I'm {{message}}
"); 57 | 58 | transform(ast, { 59 | nodeTransform: [transformExpression, transformElement, transformText], 60 | }); 61 | 62 | const code = generate(ast); 63 | 64 | expect(code).toMatchSnapshot(); 65 | }); 66 | 67 | it("element > element > element", () => { 68 | const ast = baseParse( 69 | "

Hi, Tom. My name is {{username}}

" 70 | ); 71 | 72 | transform(ast, { 73 | nodeTransform: [transformExpression, transformElement, transformText], 74 | }); 75 | 76 | const code = generate(ast); 77 | 78 | expect(code).toMatchSnapshot(); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /example/patchChildren/ArrayToArray.html: -------------------------------------------------------------------------------- 1 | xx 2 |
3 | 4 | 91 | -------------------------------------------------------------------------------- /src/reactivity/__tests__/reactive.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isProxy, 3 | isReactive, 4 | isReadonly, 5 | reactive, 6 | readonly, 7 | } from "../src/reactive"; 8 | 9 | describe("reactive", () => { 10 | it("create reactive", () => { 11 | const oriObj = { a: 1 }; 12 | const newObj = reactive(oriObj); 13 | expect(newObj).not.toBe(oriObj); 14 | expect(newObj.a).toBe(1); 15 | expect("a" in newObj).toBe(true); 16 | expect(Object.keys(newObj)).toEqual(["a"]); 17 | expect(isProxy(newObj)).toBe(true); 18 | }); 19 | 20 | it("create readonly", () => { 21 | const oriObj = { a: 1 }; 22 | const newObj = readonly(oriObj); 23 | 24 | console.warn = jest.fn(); 25 | 26 | expect(newObj).not.toBe(oriObj); 27 | expect(newObj.a).toBe(1); 28 | expect("a" in newObj).toBe(true); 29 | expect(Object.keys(newObj)).toEqual(["a"]); 30 | expect(isProxy(newObj)).toBe(true); 31 | 32 | newObj.a = 2; 33 | expect(console.warn).toBeCalled(); 34 | }); 35 | 36 | it("isReactive", () => { 37 | const oriObj = { a: 1 }; 38 | const newObj = reactive(oriObj); 39 | 40 | expect(isReactive(newObj)).toBe(true); 41 | expect(isReactive(oriObj)).toBe(false); 42 | }); 43 | 44 | it("isReadonly", () => { 45 | const oriObj = { a: 1 }; 46 | const newObj = readonly(oriObj); 47 | 48 | expect(isReadonly(newObj)).toBe(true); 49 | expect(isReadonly(oriObj)).toBe(false); 50 | }); 51 | 52 | it("nested reactive object", () => { 53 | const reactObj = reactive({ 54 | info: { 55 | name: "Tom", 56 | age: 18, 57 | }, 58 | arr: [{ id: 1 }, { id: 2 }], 59 | }); 60 | 61 | expect(isReactive(reactObj)).toBe(true); 62 | expect(isReactive(reactObj.info)).toBe(true); 63 | expect(isReactive(reactObj.arr)).toBe(true); 64 | expect(isReactive(reactObj.arr[0])).toBe(true); 65 | }); 66 | 67 | it("nested readonly object", () => { 68 | const reactObj = readonly({ 69 | info: { 70 | name: "Tom", 71 | age: 18, 72 | }, 73 | arr: [{ id: 1 }, { id: 2 }], 74 | }); 75 | 76 | expect(isReadonly(reactObj)).toBe(true); 77 | expect(isReadonly(reactObj.info)).toBe(true); 78 | expect(isReadonly(reactObj.arr)).toBe(true); 79 | expect(isReadonly(reactObj.arr[0])).toBe(true); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /src/reactivity/__tests__/effect.spec.ts: -------------------------------------------------------------------------------- 1 | import { effect, stop } from "../src/effect"; 2 | import { reactive } from "../src/reactive"; 3 | 4 | describe("effect", () => { 5 | it("should observe basic properties", () => { 6 | let doubleCount; 7 | const count = reactive({ value: 1 }); 8 | 9 | effect(() => (doubleCount = count.value * 2)); 10 | 11 | expect(doubleCount).toBe(2); 12 | 13 | // update 14 | count.value = 2; 15 | expect(doubleCount).toBe(4); 16 | }); 17 | 18 | it("should observe runner", () => { 19 | let count = 10; 20 | 21 | const runner = effect(() => { 22 | count++; 23 | return "count"; 24 | }); 25 | // console.log(runner); 26 | expect(count).toBe(11); 27 | 28 | const r = runner(); 29 | expect(count).toBe(12); 30 | expect(r).toBe("count"); 31 | }); 32 | 33 | it("should observe scheduler", () => { 34 | // 1. effect 的第二个参数 35 | // 2. 第一次执行时执行 fn 36 | // 3. update时执行 scheduler 37 | // 4. 执行 runner 时, 执行 fn 38 | 39 | let doubleCount; 40 | let run; 41 | const scheduler = jest.fn(() => { 42 | run = runner; 43 | }); 44 | 45 | const count = reactive({ value: 1 }); 46 | const runner = effect( 47 | () => { 48 | doubleCount = count.value * 2; 49 | }, 50 | { scheduler } 51 | ); 52 | expect(scheduler).not.toHaveBeenCalled(); 53 | expect(doubleCount).toBe(2); 54 | 55 | count.value = 2; 56 | expect(scheduler).toHaveBeenCalledTimes(1); 57 | expect(doubleCount).toBe(2); 58 | run(); 59 | expect(doubleCount).toBe(4); 60 | }); 61 | 62 | it.only("should observe stop", () => { 63 | let doubleCount; 64 | const count = reactive({ value: 1 }); 65 | const runner = effect(() => { 66 | doubleCount = count.value * 2; 67 | }); 68 | 69 | count.value = 2; 70 | expect(count.value).toBe(2); 71 | expect(doubleCount).toBe(4); 72 | stop(runner); 73 | 74 | // count.value++ 实际上是 count.value = count.value + 1 75 | // 会触发 count get 函数,收集依赖(effect) 76 | count.value++; 77 | 78 | expect(count.value).toBe(3); 79 | expect(doubleCount).toBe(4); 80 | 81 | // 重新触发 effect.run 82 | runner(); 83 | expect(doubleCount).toBe(6); 84 | 85 | count.value++; 86 | expect(count.value).toBe(4); 87 | expect(doubleCount).toBe(6); 88 | 89 | runner(); 90 | expect(doubleCount).toBe(8); 91 | }); 92 | }); 93 | 1; 94 | -------------------------------------------------------------------------------- /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 | }); 17 | }); 18 | 19 | describe("element", () => { 20 | it("simple element div", () => { 21 | const ast = baseParse("
"); 22 | 23 | expect(ast.children[0]).toStrictEqual({ 24 | type: NodeTypes.ELEMENT, 25 | tag: "div", 26 | children: [], 27 | }); 28 | }); 29 | }); 30 | 31 | describe("text", () => { 32 | it("simple text", () => { 33 | const ast = baseParse("this is a test"); 34 | 35 | expect(ast.children[0]).toStrictEqual({ 36 | type: NodeTypes.TEXT, 37 | content: "this is a test", 38 | }); 39 | }); 40 | }); 41 | 42 | test("interpolation/element/text", () => { 43 | const ast = baseParse("
hi, {{ message }}
"); 44 | 45 | expect(ast.children[0]).toStrictEqual({ 46 | type: NodeTypes.ELEMENT, 47 | tag: "div", 48 | children: [ 49 | { 50 | type: NodeTypes.TEXT, 51 | content: "hi, ", 52 | }, 53 | { 54 | type: NodeTypes.INTERPOLATION, 55 | content: { 56 | type: NodeTypes.SIMPLE_EXPRESSION, 57 | content: "message", 58 | }, 59 | }, 60 | ], 61 | }); 62 | }); 63 | 64 | test("nested element", () => { 65 | const ast = baseParse("

hi,

{{ message }}
"); 66 | 67 | expect(ast.children[0]).toStrictEqual({ 68 | type: NodeTypes.ELEMENT, 69 | tag: "div", 70 | children: [ 71 | { 72 | type: NodeTypes.ELEMENT, 73 | tag: "p", 74 | children: [ 75 | { 76 | type: NodeTypes.TEXT, 77 | content: "hi, ", 78 | }, 79 | ], 80 | }, 81 | { 82 | type: NodeTypes.INTERPOLATION, 83 | content: { 84 | type: NodeTypes.SIMPLE_EXPRESSION, 85 | content: "message", 86 | }, 87 | }, 88 | ], 89 | }); 90 | }); 91 | 92 | test("should throw error when lack end tag", () => { 93 | expect(() => { 94 | baseParse("
"); 95 | }).toThrow("span"); 96 | }); 97 | }); 98 | -------------------------------------------------------------------------------- /src/reactivity/src/effect.ts: -------------------------------------------------------------------------------- 1 | import type { Dep } from "./dep"; 2 | import { createDep } from "./dep"; 3 | 4 | let activeEffect: ReactiveEffect; 5 | let shouldTrack = false; 6 | 7 | type KeyToDepMap = Map; 8 | const tagetMap = new WeakMap(); 9 | 10 | export class ReactiveEffect { 11 | deps: Dep[] = []; 12 | active = true; 13 | 14 | constructor(public fn, public scheduler?) {} 15 | 16 | run() { 17 | // 当执行过 stop 之后,清除掉了 effect,就不需要做 activeEffect 赋值操作 18 | // 当外部执行 effect 返回 runner 时,直接调用 fn 即可 19 | // active:effect 状态,失活时不再自动触发,需要用户手动触发 runner 20 | // 失活的 effect 直接调用 fn 21 | if (!this.active) { 22 | return this.fn(); 23 | } 24 | 25 | activeEffect = this; 26 | shouldTrack = true; 27 | 28 | const res = this.fn(); 29 | 30 | activeEffect = undefined!; 31 | shouldTrack = false; 32 | 33 | return res; 34 | } 35 | 36 | stop() { 37 | if (this.active) { 38 | cleanupEffect(this); 39 | 40 | this.active = false; 41 | } 42 | } 43 | } 44 | 45 | // 清楚dep收集的effect 46 | function cleanupEffect(effect) { 47 | effect.deps.forEach((dep) => { 48 | dep.delete(effect); 49 | }); 50 | 51 | // dep 收集的 effect 清空后,对应 effect 绑定的 deps 已经没有意义了,也可以清空了 52 | effect.deps.length = 0; 53 | } 54 | 55 | export function effect(fn, options = {}) { 56 | const _effect = new ReactiveEffect(fn); 57 | 58 | // 将 options 合并到 _effect 上 59 | Object.assign(_effect, options); 60 | _effect.run(); 61 | 62 | const runner: any = _effect.run.bind(_effect); 63 | runner.effect = _effect; 64 | 65 | return runner; 66 | } 67 | 68 | export function stop(runner) { 69 | runner.effect.stop(); 70 | } 71 | 72 | export function track(target, type, key) { 73 | if (!isTracking()) { 74 | return; 75 | } 76 | 77 | let depsMap = tagetMap.get(target); 78 | 79 | if (!depsMap) { 80 | depsMap = new Map(); 81 | tagetMap.set(target, depsMap); 82 | } 83 | 84 | let dep = depsMap.get(key); 85 | 86 | if (!dep) { 87 | dep = createDep(); 88 | 89 | depsMap.set(key, dep); 90 | } 91 | 92 | trackEffects(dep); 93 | } 94 | 95 | export function trackEffects(dep: Dep) { 96 | if (!dep.has(activeEffect!)) { 97 | dep.add(activeEffect!); 98 | activeEffect!.deps.push(dep); 99 | } 100 | } 101 | 102 | export function isTracking() { 103 | return shouldTrack && activeEffect !== null; 104 | } 105 | 106 | export function trigger(target, type, key) { 107 | const depsMap = tagetMap.get(target); 108 | 109 | if (!depsMap) { 110 | return; 111 | } 112 | const dep = depsMap.get(key); 113 | 114 | triggerEffects(dep!); 115 | } 116 | 117 | export function triggerEffects(dep: Set) { 118 | for (const effect of dep) { 119 | if (effect?.scheduler) { 120 | effect?.scheduler(); 121 | } else { 122 | effect?.run(); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /docs/reactive.md: -------------------------------------------------------------------------------- 1 | # reactive 2 | 3 | ![思维导图](./imgs/reactive.png) 4 | 5 | ## createReactiveObject 6 | 7 | > 创建响应式对象 8 | 9 | - 参数 10 | - target 目标对象 11 | - proxyMap 缓存 map 12 | - baseHandlers 处理对象 13 | - 步骤 14 | - 获取已经存在的代理对象,性能优化 15 | - 创建代理对象,设置缓存 16 | 17 | ```ts 18 | function createReactiveObject(target, proxyMap, baseHandlers) { 19 | const existingProxy = proxyMap.get(taget); 20 | 21 | // 使用缓存优化 22 | if (existingProxy) { 23 | return existingProxy; 24 | } 25 | 26 | const proxy = new Proxy(taget, baseHandlers); 27 | proxyMap.set(taget, proxy); 28 | 29 | return proxy; 30 | } 31 | ``` 32 | 33 | ## baseHandlers 34 | 35 | 1. mutableHandlers 36 | 37 | > reactive 的处理对象,非只读 isReadonly = false,非浅响应 shallow = false 38 | 39 | ```ts 40 | const get = createGetter(); 41 | const set = createSetter(); 42 | 43 | export const mutableHandlers = { 44 | get, 45 | set, 46 | }; 47 | ``` 48 | 49 | 2. readonlyHandlers 50 | 51 | > readonly 的处理对象,只读 isReadonly = true,非浅响应 shallow = false 52 | 53 | ```ts 54 | const readonlyGet = createGetter(true); 55 | 56 | export const readonlyHandlers = { 57 | get: readonlyGet, 58 | set(target, key) { 59 | // 不能更改 60 | console.warn(`${target} of ${key} can't be setted, it's a readonly object`); 61 | return true; 62 | }, 63 | }; 64 | ``` 65 | 66 | 3. shallowReadonlyHandlers 67 | 68 | > shallowReadonly 的处理对象,只读 isReadonly = true,浅响应 shallow = true,set 与 readonlyHandlers 一致 69 | 70 | ```ts 71 | const shallowReadonlyGet = createGetter(true, true); 72 | 73 | export const shallowReadonlyHandlers = Object.assign({}, readonlyHandlers, { 74 | get: shallowReadonlyGet, 75 | }); 76 | ``` 77 | 78 | ### createGetter 79 | 80 | > 生成 get 函数 81 | 82 | ```ts 83 | function createGetter(isReadonly = false, shallow = false) { 84 | return (target, key, receiver) => { 85 | if (key === ReactiveFlags.IS_REACTIVE) { 86 | // 判断是否为 readonly 对象 87 | return !isReadonly; 88 | } else if (key === ReactiveFlags.IS_READONLY) { 89 | // 判断是否为 reactive 对象 90 | return isReadonly; 91 | } 92 | 93 | // 如果不是只读对象,收集依赖 94 | // 只读对象不能更改,所以不需要收集 95 | if (!isReadonly) { 96 | track(target, "get", key); 97 | } 98 | 99 | // 获取属性值 100 | const res = Reflect.get(target, key, receiver); 101 | 102 | // 只对最外层最响应式,不执行嵌套对象的响应式转换 103 | if (shallow) { 104 | return res; 105 | } 106 | 107 | // 执行嵌套对象的响应式转换 108 | if (isObjectOrArray(res)) { 109 | return isReadonly ? readonly(res) : reactive(res); 110 | } 111 | return res; 112 | }; 113 | } 114 | ``` 115 | 116 | ### createSetter 117 | 118 | > 生成 set 函数 119 | 120 | ```ts 121 | function createSetter() { 122 | return (target, key, value, receiver) => { 123 | // 设置属性值 124 | const res = Reflect.set(target, key, value, receiver); 125 | 126 | // 触发 依赖 127 | trigger(target, "set", key); 128 | return res; 129 | }; 130 | } 131 | ``` 132 | 133 | ## 工具函数 134 | 135 | - isProxy 判断是否是 reactive 或者 readonly 类型 136 | - isReactive 是否是 reactive 类型 137 | - isReadonly 是否是 readonly 类型 138 | -------------------------------------------------------------------------------- /example/helloworld.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 9 | 121 | 122 | 123 | 126 | 127 | 128 |
129 | 130 |
131 | -------------------------------------------------------------------------------- /src/runtime-core/component.ts: -------------------------------------------------------------------------------- 1 | import { isObject } from "../shared"; 2 | import { PublicInstanceProxyHandlers } from "./componentPublicInstance"; 3 | import { initProps } from "./componentProps"; 4 | import { shallowReadonly } from "../reactivity/src/reactive"; 5 | import { emit } from "./componentEmit"; 6 | import { initSlots } from "./componentSlots"; 7 | import { proxyRefs } from "../reactivity/src"; 8 | 9 | // 创建 10 | export function createComponentInstance(vnode, parent) { 11 | const instance = { 12 | vnode, 13 | type: vnode.type, 14 | next: null, 15 | proxy: {}, 16 | setupState: {}, 17 | props: {}, 18 | provides: parent ? parent.provides : {}, 19 | parent, 20 | update: () => {}, 21 | emit: () => {}, 22 | slots: {}, 23 | isMounted: false, 24 | subTree: {}, 25 | }; 26 | 27 | instance.emit = emit.bind(null, instance) as any; 28 | 29 | return instance; 30 | } 31 | 32 | // 33 | export function setupComponent(instance) { 34 | initProps(instance, instance.vnode.props); 35 | initSlots(instance, instance.vnode.children); 36 | 37 | // 初始化 有状态的 组件 38 | setupStatefulComponent(instance); 39 | } 40 | 41 | function setupStatefulComponent(instance) { 42 | // 将 setup 返回的数据对象设置给 代理对象 43 | // 在后续使用 bind 绑定到 render 函数上,这样 render 内部就可以直接使用 setupState 上的数据 44 | instance.proxy = new Proxy( 45 | { _instance: instance }, 46 | PublicInstanceProxyHandlers 47 | ); 48 | 49 | // 获取 vue 组件, 在组件实例 instance 的 vnode 属性上的 type 50 | // 经常 createVNode 处理后的 type 就是 createApp 传入的 App (vue)组件 51 | const Component = instance.vnode.type; 52 | 53 | const { setup } = Component; 54 | 55 | // 获取 setup 返回值 56 | if (setup) { 57 | setCurrentInstance(instance); 58 | const setupResult = setup(shallowReadonly(instance.props), { 59 | emit: instance.emit, 60 | }); 61 | setCurrentInstance(null); 62 | 63 | handleSetupResult(instance, setupResult); 64 | } 65 | } 66 | 67 | function handleSetupResult(instance, setupResult) { 68 | // setup 的返回值 69 | // 1. function jsx 组件 70 | // 2. object 只是 template 中使用的数据对象 71 | 72 | if (isObject(setupResult)) { 73 | instance.setupState = proxyRefs(setupResult); 74 | } 75 | 76 | // 处理组件 render 77 | finishComponentSetup(instance); 78 | } 79 | 80 | function finishComponentSetup(instance) { 81 | const Component = instance.type; 82 | const proxy = instance.proxy; 83 | 84 | if (compiler && !Component.render) { 85 | if (Component.template) { 86 | Component.render = compiler(Component.template); 87 | } 88 | } 89 | 90 | // 将组件上的 render 函数赋到 组件实例上 91 | if (Component.render) { 92 | instance.render = Component.render.bind(proxy); 93 | } 94 | } 95 | 96 | let currentInstance = null; 97 | 98 | /** 99 | * @description 获取当前组件实例 100 | * 该方法只允许在setup内部调用,因此在调用setup时去给 currentInstance 赋值,结束后清空 101 | * @returns 102 | */ 103 | export function getCurrentInstance() { 104 | return currentInstance; 105 | } 106 | 107 | /** 108 | * @description 设置组件实例对象 109 | * currentInstance = instance 这样一句简单的赋值 抽取为函数的好处 110 | * 方便调试错误,当对 currentInstance 错误赋值时,只需在此处 断点 就可以查询到调用过设置位置 111 | * 如果直接写 currentInstance = instance 的话,查错时很难知道在代码的哪一块设置的 112 | * @param instance 113 | */ 114 | function setCurrentInstance(instance) { 115 | currentInstance = instance; 116 | } 117 | 118 | let compiler; 119 | 120 | export function registerRuntiomCompiler(_compiler) { 121 | compiler = _compiler; 122 | } 123 | -------------------------------------------------------------------------------- /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 | const context = createParserContext(content); 10 | 11 | return createRoot(parseChildren(context, [])); 12 | } 13 | 14 | function parseChildren(context, ancestars) { 15 | const nodes: any[] = []; 16 | 17 | while (!isEnd(context, ancestars)) { 18 | let node; 19 | const s = context.source; 20 | 21 | if (s.startsWith("{{")) { 22 | // 处理 插值 23 | node = parseInterpolation(context); 24 | } else if (s[0] === "<") { 25 | // 处理 标签 26 | if (/[a-z]/i.test(s[1])) { 27 | node = parseElement(context, ancestars); 28 | } 29 | } 30 | 31 | // 处理文本 32 | if (!node) { 33 | node = parseText(context); 34 | } 35 | 36 | nodes.push(node); 37 | } 38 | 39 | return nodes; 40 | } 41 | 42 | function isEnd(context, ancestars) { 43 | const s = context.source; 44 | 45 | if (s.startsWith(" index) { 64 | endIndex = index; 65 | } 66 | } 67 | 68 | const content = parseTextData(context, endIndex); 69 | return { 70 | type: NodeTypes.TEXT, 71 | content, 72 | }; 73 | } 74 | 75 | function parseTextData(context, length) { 76 | const content = context.source.slice(0, length); 77 | 78 | advanceBy(context, length); 79 | 80 | return content; 81 | } 82 | 83 | // 解析 element 84 | // 处理标签
85 | function parseElement(context, ancestars) { 86 | const element: any = parseTag(context, TagType.Start); 87 | ancestars.push(element.tag); 88 | element.children = parseChildren(context, ancestars); 89 | ancestars.pop(); 90 | 91 | if (startsWithEndTagOpen(context.source, element.tag)) { 92 | parseTag(context, TagType.End); 93 | } else { 94 | throw new Error(`${element.tag}`); 95 | } 96 | 97 | return element; 98 | } 99 | 100 | function startsWithEndTagOpen(source, tag) { 101 | return ( 102 | source.startsWith(" 115 | advanceBy(context, 1); 116 | 117 | if (type === TagType.End) { 118 | return; 119 | } 120 | 121 | return { 122 | type: NodeTypes.ELEMENT, 123 | tag, 124 | }; 125 | } 126 | 127 | function parseInterpolation(context) { 128 | // {{ message }} 129 | 130 | const openDelimiter = "{{"; 131 | const closeDelimiter = "}}"; 132 | 133 | const closeIndex = context.source.indexOf( 134 | closeDelimiter, 135 | openDelimiter.length 136 | ); 137 | advanceBy(context, openDelimiter.length); 138 | 139 | const rawContentLength = closeIndex - openDelimiter.length; 140 | 141 | const rawContent = context.source.slice(0, rawContentLength); 142 | const content = rawContent.trim(); 143 | 144 | advanceBy(context, rawContentLength + closeDelimiter.length); 145 | 146 | return { 147 | type: NodeTypes.INTERPOLATION, 148 | content: { 149 | type: NodeTypes.SIMPLE_EXPRESSION, 150 | content, 151 | }, 152 | }; 153 | } 154 | 155 | function advanceBy(context, length: number) { 156 | context.source = context.source.slice(length); 157 | } 158 | 159 | function createRoot(children) { 160 | return { 161 | children, 162 | type: NodeTypes.ROOT, 163 | }; 164 | } 165 | 166 | function createParserContext(content: string) { 167 | return { 168 | source: content, 169 | }; 170 | } 171 | -------------------------------------------------------------------------------- /src/compiler-core/src/codegen.ts: -------------------------------------------------------------------------------- 1 | import { NodeTypes } from "./ast"; 2 | import { 3 | CREATE_ELEMENT_VNODE, 4 | helperMapName, 5 | TO_DISPLAY_STRING, 6 | } from "./runtimeHelper"; 7 | import { isString } from "../../shared"; 8 | 9 | export function generate(ast) { 10 | const context = createGenerateContext(); 11 | const { push } = context; 12 | 13 | genFunctionPreamble(ast, context); 14 | 15 | const functionName = "render"; 16 | const args = ["_ctx", "_cache"]; 17 | const signature = args.join(", "); 18 | 19 | push("return "); 20 | push(`function ${functionName}(${signature}){`); 21 | push("return "); 22 | genNode(ast.codegenNode, context); 23 | push("}"); 24 | 25 | return context.code; 26 | } 27 | 28 | function genNode( 29 | node: any, 30 | context: { code: string; push(source: any): void; helper(key: any): string } 31 | ) { 32 | switch (node.type) { 33 | case NodeTypes.TEXT: 34 | genText(node, context); 35 | break; 36 | case NodeTypes.INTERPOLATION: 37 | genInterpolation(node, context); 38 | break; 39 | case NodeTypes.SIMPLE_EXPRESSION: 40 | genExpression(node, context); 41 | break; 42 | case NodeTypes.ELEMENT: 43 | genElement(node, context); 44 | break; 45 | case NodeTypes.COMPUND_EXPORESS: 46 | genCompundExpression(node, context); 47 | break; 48 | default: 49 | break; 50 | } 51 | } 52 | 53 | function genCompundExpression( 54 | node: any, 55 | context: { code: string; push(source: any): void; helper(key: any): string } 56 | ) { 57 | const { children } = node; 58 | const { push } = context; 59 | 60 | for (let i = 0; i < children.length; i++) { 61 | const child = children[i]; 62 | if (isString(child)) { 63 | push(child); 64 | } else { 65 | genNode(child, context); 66 | } 67 | } 68 | } 69 | 70 | function genText( 71 | node: any, 72 | context: { code: string; push(source: any): void; helper(key: any): string } 73 | ) { 74 | const { push } = context; 75 | push(`"${node.content}"`); 76 | } 77 | 78 | function genInterpolation( 79 | node: any, 80 | context: { code: string; push(source: any): void; helper(key: any): string } 81 | ) { 82 | const { push, helper } = context; 83 | 84 | push(`${helper(TO_DISPLAY_STRING)}(`); 85 | genNode(node.content, context); 86 | push(")"); 87 | } 88 | 89 | function genExpression( 90 | node: any, 91 | context: { code: string; push(source: any): void; helper(key: any): string } 92 | ) { 93 | const { push } = context; 94 | push(`${node.content}`); 95 | } 96 | 97 | function genElement( 98 | node: any, 99 | context: { code: string; push(source: any): void; helper(key: any): string } 100 | ) { 101 | const { push, helper } = context; 102 | const { tag, props, children } = node; 103 | push(`${helper(CREATE_ELEMENT_VNODE)}(`); 104 | genNodeList(genNullable([tag, props]), context); 105 | genChildren(children, context); 106 | push(")"); 107 | } 108 | 109 | function genChildren(children, context) { 110 | const { push } = context; 111 | const isMoreElement = !!children.find( 112 | (child) => child.type === NodeTypes.ELEMENT 113 | ); 114 | children.length && push(", "); 115 | isMoreElement && push("[ "); 116 | genNodeList(children, context); 117 | isMoreElement && push("] "); 118 | } 119 | 120 | function genNodeList(nodes, context) { 121 | const { push } = context; 122 | for (let i = 0; i < nodes.length; i++) { 123 | const node = nodes[i]; 124 | 125 | if (isString(node)) { 126 | push(node); 127 | } else { 128 | let codegenNode = node; 129 | if (node.type === NodeTypes.ELEMENT) { 130 | codegenNode = node.codegenNode; 131 | } 132 | genNode(codegenNode, context); 133 | } 134 | 135 | if (i < nodes.length - 1) { 136 | push(", "); 137 | } 138 | } 139 | } 140 | 141 | function genNullable(args: any[]) { 142 | return args.map((arg) => arg || "null"); 143 | } 144 | 145 | function createGenerateContext() { 146 | const context = { 147 | code: "", 148 | push(source) { 149 | context.code += source; 150 | }, 151 | helper(key) { 152 | return `_${helperMapName[key]}`; 153 | }, 154 | }; 155 | 156 | return context; 157 | } 158 | 159 | function genFunctionPreamble( 160 | ast: any, 161 | context: { code: string; push(source: any): void; helper(key: any): string } 162 | ) { 163 | const { push } = context; 164 | const VueBinging = "Vue"; 165 | 166 | if (ast.helpers.length) { 167 | const aliasHelper = (s) => `${helperMapName[s]}: _${helperMapName[s]}`; 168 | push( 169 | `const { ${ast.helpers.map(aliasHelper).join(", ")} } = ${VueBinging}` 170 | ); 171 | push("\n"); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /docs/note.md: -------------------------------------------------------------------------------- 1 | ### 收集依赖 2 | 3 | 1. 总会有一种感觉在 tarck 的时候 activeEffect 是 null,就直接退出了 tarck 函数,为什么 trigger 里能调用到 effect.run 呢 4 | 5 | 有一个比较关键的点,当一个变量依赖另一个响应式变量的值时,就必然需要读取这个响应式变量,此时就会触发 get 函数内调用的 tarck 函数。 6 | 7 | 不管是 computed 计算属性还是 effect,都是想要在某个响应式变量发生改变时去做某件事,大部分情况下,传入 computed 和 effect 的回调函数内部都会调用这个响应式变量。因此,当 effect.run 执行时,内部执行到 this.fn(传入的回调)时,就会去触发 响应式变量的 get 内调用的 tarck 函数,此时 activeEffect 和 shouldTrack 还没有置空,就收集到了依赖。之后依赖变量改变时就会触发 trigger 去执行这些 effect 上的 run 函数 8 | 9 | 而 computed 不一样的是,在创建 effect 时,传入了第二个参数 scheduler,当有这个参数时,触发 trigger 执行的就不是 effect.run,而是 effect.scheduler。而 ComputedRef 内部利用一个\_dirty 变量去控制要不要执行(包含依赖变量的)回调函数。当依赖变量发生变化时,trigger 执行 effect.scheduler,打开了\_dirty 开关,再次获取计算属性时就会去重新执行回调函数,获取最新依赖值。 10 | 11 | ```ts 12 | class ReactiveEffect { 13 | run() { 14 | activeEffect = this; 15 | shouldTrack = true; 16 | const res = this.fn(); 17 | activeEffect = null; 18 | shouldTrack = false; 19 | return res; 20 | } 21 | } 22 | 23 | class ComputedRef { 24 | private _getter; 25 | private _dirty = true; 26 | private _value: any; 27 | private _effect: ReactiveEffect; 28 | 29 | constructor(getter) { 30 | this._getter = getter; 31 | 32 | this._effect = new ReactiveEffect(getter, () => { 33 | this._dirty = true; 34 | }); 35 | } 36 | 37 | get value() { 38 | if (this._dirty) { 39 | this._dirty = false; 40 | this._value = this._effect.run(); 41 | } 42 | 43 | return this._value; 44 | } 45 | } 46 | 47 | const count = reactive({ value: 1 }); 48 | const doubleCount = computed(() => count.value * 2); 49 | 50 | effect(() => { 51 | count.value; 52 | }); 53 | ``` 54 | 55 | ### 导入报错 56 | 57 | tsconfig 需要配置 moduleResolution 属性为 node,否则导入文件时需要写出 index,而不能自动需要 index 文件 58 | 59 | ![moduleResolution](./imgs/moduleResolution.png) 60 | 61 | ```json 62 | { 63 | "compilerOptions": { 64 | "moduleResolution": "node" 65 | } 66 | } 67 | ``` 68 | 69 | ### 位运算 70 | 71 | > 由于位运算更快,有时候可以使用 2 进制来表示数据类型,在进行判断时使用位运算,可以优化性能 72 | 73 | - | (或运算) 都为 0 则为 0,否则为 1 74 | - & (与运算) 都为 1 则 为 1,否则为 0 75 | 76 | #### 二进制 77 | 78 | 二进制在做左移操作时,其实就等于十进制`乘2`,在做右移时就等于`除2` 79 | 80 | - 1 ---> 0001 81 | - 2 ---> 1 << 1, 1 左移一位 82 | - 4 ---> 1 << 2, 1 左移两位 83 | - 8 ---> 1 << 3, 1 左移三位 84 | 85 | ```ts 86 | export enum ShapeFlag { 87 | ELEMENT = 1, 88 | STATEFUL_COMPONENT = 2, // 0010 89 | TEXT_CHILDREN = 4, // 0100 90 | ARRAY_CHILDREN = 8, // 1000 91 | } 92 | ``` 93 | 94 | ### 有限状态机 95 | 96 | > 读取一组输入然后根据这些速入来更改为不同的状态 97 | 98 | 1. 是否存在 abc 99 | 100 | - 逻辑 101 | - 如果 a 状态存在,返回下一个状态 b,以此类推 102 | - 最后一个状态 c 存在,返回 end 结束状态 103 | - 否则返回当前状态 104 | - 执行过程 105 | - 声明初始状态 currentState, 设置为 waitForA 106 | - 循环字符串,将 currentState 更新为 waitForA 返回的状态 107 | - 更新后的状态可能是 新状态 b, 也可能是老状态 a 108 | - 知道状态为 end,return true 退出循环 109 | 110 | ```ts 111 | export function stateMachine(str: string) { 112 | const waitForA = (char) => { 113 | if (char === "a") { 114 | return waitForB; 115 | } 116 | return waitForA; 117 | }; 118 | 119 | const waitForB = (char) => { 120 | if (char === "b") { 121 | return waitForC; 122 | } 123 | return waitForB; 124 | }; 125 | 126 | const waitForC = (char) => { 127 | if (char === "c") { 128 | return end; 129 | } 130 | return waitForC; 131 | }; 132 | 133 | const end = () => { 134 | return end; 135 | }; 136 | 137 | let currentState = waitForA; 138 | for (let i = 0; i < str.length; i++) { 139 | const nextState = currentState(str[i]); 140 | currentState = nextState; 141 | 142 | if (nextState === end) { 143 | return true; 144 | } 145 | } 146 | return false; 147 | } 148 | ``` 149 | 150 | 2. 记录匹配字符的 index 151 | 152 | - 初始状态 a 成功时记录 startIndex,c 进入结束状态时记录 endIndex 153 | - 循环中状态机为 end 时记录 [startIndex, endIndex] 154 | 155 | ```ts 156 | export function stateMachine(str: string) { 157 | let i = 0; 158 | let startIndex; 159 | let endIndex; 160 | const result = []; 161 | 162 | const waitForA = (char) => { 163 | if (char === "a") { 164 | startIndex = i; 165 | return waitForB; 166 | } 167 | return waitForA; 168 | }; 169 | 170 | const waitForB = (char) => { 171 | if (char === "b") { 172 | return waitForC; 173 | } 174 | return waitForB; 175 | }; 176 | 177 | const waitForC = (char) => { 178 | if (char === "c") { 179 | endIndex = i; 180 | return end; 181 | } 182 | return waitForC; 183 | }; 184 | 185 | const end = () => { 186 | return end; 187 | }; 188 | 189 | let currentState = waitForA; 190 | for (let i = 0; i < str.length; i++) { 191 | const nextState = currentState(str[i]); 192 | currentState = nextState; 193 | 194 | if (nextState === end) { 195 | result.push([startIndex, endIndex]); 196 | } 197 | } 198 | return result; 199 | } 200 | ``` 201 | 202 | ### ts 203 | 204 | - ! 允许 null 和 undefined 赋值给其他类型数据 205 | 206 | ```ts 207 | const obj: { a?: number } = {}; 208 | 209 | const obj1: { a: number } = { a: 0 }; 210 | 211 | obj1.a = obj.a; // 报类型错误 212 | obj1.a = obj.a!; // 不报错 213 | ``` 214 | -------------------------------------------------------------------------------- /docs/think.md: -------------------------------------------------------------------------------- 1 | ## 疑问 2 | 3 | ### effect 4 | 5 | 1. 为什么要将 dep 存到 activeEffect 的 deps 中 6 | 7 | - 猜测 8 | - 后面可能会有某个模块内部需要使用 activeEffect 去触发其他的 dep 执行 run 函数 9 | 10 | ```ts 11 | export function trackEffects(dep) { 12 | if (!dep.has(activeEffect)) { 13 | dep.add(activeEffect); 14 | (activeEffect as any).deps.push(dep); 15 | } 16 | } 17 | ``` 18 | 19 | 2. trigger 函数中为什么要声明新的 deps 变量去收集 dep,声明 effects 去收集 effect,不能直接使用 deps 吗 20 | 21 | - 猜测 22 | - 不去直接使用 targetMap 上的 deps 是为了安全,只拿出 effect 去执行 run 函数即可,不会存在对 targetMap 上的属性做修改的风险 23 | 24 | ```ts 25 | export function trigger(target, type, key) { 26 | let deps: Array = []; 27 | const depsMap = targetMap.get(target); 28 | 29 | if (!depsMap) return; 30 | 31 | const dep = depsMap.get(key); 32 | 33 | deps.push(dep); 34 | 35 | const effects: Array = []; 36 | deps.forEach((dep) => { 37 | effects.push(...dep); 38 | }); 39 | triggerEffects(createDep(effects)); 40 | } 41 | 42 | export function triggerEffects(dep) { 43 | for (const effect of dep) { 44 | if (effect.scheduler) { 45 | effect.scheduler(); 46 | } else { 47 | effect.run(); 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | 2.1. effect.scheduler 是做什么的,暂时没看到后面,先留个疑问 54 | 55 | 3. 读取 reactive 变量会触发 track,track 内部会对 effect 进行收集,dep 收集 effect 是为了 trigger 时去执行 effect.run 函数,为什么 activeEffect 也要去收集 deps?是为了什么优化吗?或者是后面的某个模块会用到? 56 | 57 | ```ts 58 | function trackEffects(dep) { 59 | if (!dep.has(activeEffect)) { 60 | dep.add(activeEffect); 61 | activeEffect.deps.push(dep); 62 | } 63 | } 64 | ``` 65 | 66 | ### ref 67 | 68 | 1. proxyRefs 在设置 proxyRefInfo.age 的时候,设置的 value 按道理是一个普通数字,再做 isRef 判断的时候缺触发了 reactive 的 get 函数,为什么 69 | 70 | ```ts 71 | it("proxyRefs", () => { 72 | const info = reactive({ age: ref(18) }); 73 | 74 | const proxyRefInfo = proxyRefs(info); 75 | expect(proxyRefInfo.age).toBe(18); 76 | expect(info.age.value).toBe(18); 77 | expect(isRef(info.age)).toBe(true); 78 | 79 | proxyRefInfo.age = 20; 80 | 81 | expect(proxyRefInfo.age).toBe(20); 82 | expect(isRef(info.age)).toBe(true); 83 | expect(info.age.value).toBe(20); 84 | }); 85 | ``` 86 | 87 | ```ts 88 | export function isRef(value) { 89 | return !!value.__v_isRef; 90 | } 91 | 92 | export function proxyRefs(objectWithRefs) { 93 | return new Proxy(objectWithRefs, { 94 | get(target, key, receiver) { 95 | return unRef(Reflect.get(target, key, receiver)); 96 | }, 97 | set(target, key, value, receiver) { 98 | const oldValue = target[key]; 99 | if (isRef(oldValue) && !isRef(value)) { 100 | return (target[key].value = value); 101 | } else { 102 | return Reflect.set(target, key, value, receiver); 103 | } 104 | }, 105 | }); 106 | } 107 | ``` 108 | 109 | 2. [error] 在设置 proxyRefInfo.age 的时候 set 触发的 triggerEffects 里遍历 dep 时无法进入循环 110 | 111 | ```ts 112 | export function triggerEffects(dep: Set) { 113 | for (const effect of dep) { 114 | if (effect.scheduler) { 115 | effect.scheduler();**** 116 | } else { 117 | effect.run(); 118 | } 119 | } 120 | } 121 | ``` 122 | 123 | ### slots 124 | 125 | 1. slots 只有在 `vue component` 有效,因此 `initProps` 应该在 setupComponent 中执行 126 | 127 | ```ts 128 | // component.ts 129 | export function setupComponent(instance) { 130 | initSlots(instance, instance.vnode.children); 131 | } 132 | ``` 133 | 134 | 2. 在 initProps 时,将 instance.vnode.children 赋值给 instance.slots 135 | 136 | - 优化: 在 createVNode 时去判断 vnode 是否是 vue componet 以及 children 是否是 object;如果满足,则打上 `SLOT_CHILDREN` 的标记;在初始化时就可以判断此 vnode 是否需要 初始化 slots 137 | - 具名插槽: 传入的 chilren 对象,并给 instance.slots 赋值对应的属性 138 | - 作用域插槽: 将 `slots[key]` 对应的插槽赋值为一个函数,去调用 chilren 对应插入的函数并传入 props。 `slots[key]` 返回的是一个数组 139 | 140 | ```ts 141 | // compoentSlots.ts 142 | export function initSlots(instance, children) { 143 | if (instance.vnode.shapeFlag & ShapeFlag.SLOT_CHILDREN) { 144 | normalizeObjectSlots(children, instance.slots); 145 | } 146 | } 147 | 148 | function normalizeObjectSlots(children, slots) { 149 | for (const key in children) { 150 | const value = children[key]; 151 | slots[key] = (props) => normalizeSlotValue(value(props)); 152 | } 153 | } 154 | 155 | export function normalizeSlotValue(slot) { 156 | return isArray(slot) ? slot : [slot]; 157 | } 158 | ``` 159 | 160 | 3. 调用 renderSlots 获取 slot 161 | 162 | - App 中使用插槽时 chilren 为 object,这是为了实现具名插槽 163 | - App 中插入的 节点 由函数返回,这是为了实现作用域插槽,initSlots 时拿到的 children 内的 slot 函数调用并传入 props 164 | - Foo 中插槽调用 renderSlots 将组件实例上挂载的 $slots 转换为虚拟节点 165 | 166 | ```ts 167 | // index.html 168 | const Foo = { 169 | render() { 170 | return h("div", {}, [ 171 | renderSlots(this.$slots, "header", { data: "header" }), 172 | renderSlots(this.$slots, "footer", { data: "footer" }), 173 | ]); 174 | }, 175 | }; 176 | 177 | const App = { 178 | render() { 179 | return h( 180 | Foo, 181 | {}, 182 | { 183 | header: ({ data }) => h("div", {}, "I am Foo " + data), 184 | footer: ({ data }) => h("div", {}, "I am Foo " + data), 185 | } 186 | ); 187 | }, 188 | }; 189 | ``` 190 | 191 | 1. renderSlots 中调用 createVNode 创建虚拟节点 192 | 193 | - slots 为组件实例上挂载的 $slots,包含了所有插入的返回节点的函数 194 | - 获取对应的 slot 调用并传入 props,此 props 为 Foo 组件中传入的数据 195 | - 获取到的 slot 时在 initSlots 时,设置为函数的 196 | 197 | ```ts 198 | // renderSlots.ts 199 | export function renderSlots(slots, name, props?) { 200 | const slot = slots[name]; 201 | 202 | if (slot) { 203 | if (isFunction(slot)) { 204 | return createVNode("div", {}, slot(props)); 205 | } 206 | } 207 | } 208 | ``` 209 | 210 | #### 思考 211 | 212 | 1. 其实如果在 initSlots 中 `slots[key]` 赋值为 slot 数组,在 renderSlots 中遍历调用也不会报错,但为什么要这样返回函数呢 213 | 214 | - 首先,返回函数的写法看上去确实好看一些 215 | - 暂时还没有想到,也许看到后面,或者看 vue3 源码时能发现这样会有一些优点 216 | 217 | ```ts 218 | function normalizeObjectSlots(children, slots) { 219 | for (const key in children) { 220 | const value = children[key]; 221 | slots[key] = normalizeSlotValue(value); // value 为 函数 222 | } 223 | } 224 | 225 | export function renderSlots(slots, name, props?) { 226 | const slot = slots[name]; 227 | 228 | // slot 是一个 函数数组 229 | if (slot) { 230 | if (isFunction(slot)) { 231 | return createVNode( 232 | "div", 233 | {}, 234 | slot.map((v) => v(props)) 235 | ); 236 | } 237 | } 238 | } 239 | ``` 240 | 241 | ### getCurrentInstance 242 | 243 | > 获取组件实例对象 244 | 245 | 1. 为什么要将一句简单的 `currentInstance = instance` 封装到 `setCurrentInstance` 函数中 246 | 247 | 方便调试错误,当对 currentInstance 被某处代码错误赋值时,只需在 setCurrentInstance 中打断点,就可以查询到调用过位置;如果直接写 currentInstance = instance 的话,查错时很难知道在代码的哪一块设置的。 248 | 249 | ```ts 250 | export function getCurrentInstance() { 251 | return currentInstance; 252 | } 253 | 254 | function setCurrentInstance(instance) { 255 | currentInstance = instance; 256 | } 257 | ``` 258 | 259 | ### provide/inject 260 | 261 | > 跨多个组件传值 262 | 263 | 首先,`provide/inject` 只能在 setup 中使用,因此 provide 内可以获取组件实例;调用 provide 时,将键和值设置到 instance 的 procides 属性上。调用 inject 时在从 `instance.parent.provides` (父组件实例的 provides) 上获取。 264 | 265 | 1. 注意取值的时候是取父组件实例的 provides,而不是自己的,因为是跨组件取数据,要取自己的数据就没有必要调用这个接口 266 | 267 | 2. 怎么获取祖先组件中的 provides? 268 | 269 | 当跨多个层级时,直接从父组件实例中获取数据就获取不到了。 270 | 271 | 第一点,直接将当前组件实例的 provides 设置为 parent.provides。这样会带来一个问题,所有的 provides 使用的都是根节点的那个对象,后面的组件再去设置的时候是直接在根节点的 provides 上增加属性,如果同名就会覆盖,导致中间组件拿到的数据变更。 272 | 273 | 这时就可以利用对象`原型链`的特性,将 `父组件的 provides` 设置为当前组件 procides 的原型。这样获取数据的时候就只会找最近的父组件的 provides 上存在的值;如果父组件的 provides 上没有此属性时,才会继续向上寻找。就解决了无法获取祖先组件设置的数据,以及子组件覆盖父组件数据的情况。 274 | 275 | 1. 存在一个问题,多次调用 provide 276 | 277 | 如果每次调用 provide 都调用`Object.create`将父组件 provides 为原型创建的新对象赋值给当前 instance.provides,那么后面设置属性的对象就会覆盖前面的 provides; 278 | 279 | 由于在创建 instance 时,provides 是直接使用 parent.instance,只需判断 instance.provides 是否等于 instance.parent.provides,就可以知道是否已经使用过 Object.create 280 | 281 | ```ts 282 | export function createComponentInstance(vnode, parent) { 283 | const instance = { 284 | // ... 省略其他属性设置 285 | provides: parent ? parent.provides : {}, 286 | }; 287 | return instance; 288 | } 289 | 290 | export function provide(key, value) { 291 | const currentInstance: any = getCurrentInstance(); 292 | 293 | if (currentInstance) { 294 | if ( 295 | currentInstance.parent && 296 | currentInstance.provides === currentInstance.parent.provides 297 | ) { 298 | currentInstance.provides = Object.create(currentInstance.parent.provides); 299 | } 300 | currentInstance.provides[key] = value; 301 | } 302 | } 303 | 304 | export function inject(key) { 305 | const currentInstance: any = getCurrentInstance(); 306 | if (currentInstance) { 307 | return currentInstance.parent.provides[key]; 308 | } 309 | } 310 | ``` 311 | 312 | ### 更新 element 的 props 313 | 314 | 1. 调用 patchElement 时为什么需要将 n1.el 赋值给 n2.el ,如果不赋值,再次更新获取了 el 将是 null。一直操作的都是一个 el 并没有改变不是吗, 315 | 316 | ```ts 317 | function patchElement(n1, n2, container, parentInstance) { 318 | const oldProps = n1.props || EMPTY_OBJ; 319 | const newProps = n2.props || EMPTY_OBJ; 320 | 321 | // 需要给 n2.el 赋值,否则后面拿不到el 322 | const el = (n2.el = n1.el); 323 | 324 | patchProps(el, oldProps, newProps); 325 | } 326 | ``` 327 | 328 | **解答:** 329 | 330 | 因为在渲染函数里面更新时,调用了组件实例下的 render 函数创建了新的虚拟节点树 currentSubTree。 331 | 332 | 当更新 element 时拿到的就是新的虚拟节点树上的节点(也就是 n2);然后组件实例上保存的旧的 subTree 被更新为新的 currentSubTree。 333 | 334 | 因此当再次发生更新时,又去生成了新的节点树,此时的老 subTree 时上一次更新生成的 currentSubTree,不是初始化那个节点树,因此 n2.el 是 null,所以需要在更新时将初始化的 n1.el 赋值给 n2.el,这样才能再下次更新时拿到初始化生成的 el 335 | 336 | 确实一直都是一个 el,只是生成了新的虚拟节点树,去对比了对象。 337 | 338 | ```ts 339 | function setupRnderEffect(instance, initialVNode, container) { 340 | //更新 341 | const { proxy } = instance; 342 | const currentSubTree = instance.render.call(proxy); 343 | const prevSubTree = instance.subTree; 344 | instance.subTree = currentSubTree; 345 | 346 | patch(prevSubTree, currentSubTree, container, instance); 347 | } 348 | ``` 349 | 350 | ### 更新子节点 351 | 352 | - 指针 i,初始 = 0 353 | - c1 老的节点数组 354 | - c2 新的节点数组 355 | - e1 老节点数组指针,初始 = c1.length - 1 356 | - e2 新节点数组指针,初始 = c2.length - 1 357 | 358 | ```ts 359 | function patchKeyedChaildren(c1, c2, container, parentInstance, anchor) { 360 | let i = 0; 361 | const l2 = c2.length; 362 | let e1 = c1.length - 1; 363 | let e2 = l2 - 1; 364 | const isSomeVNodeType = (n1, n2) => n1.type === n2.type && n1.key === n2.key; 365 | 366 | // ... 后续代码 分开解析 367 | } 368 | ``` 369 | 370 | 1. 从左侧对比 371 | 372 | 从左侧开始对比节点,出现不同节点时退出,此时 i 位置以及之后的节点需要处理 373 | 374 | ```ts 375 | while (i <= e1 && i <= e2) { 376 | // 获取 左侧 节点 377 | const n1 = c1[i]; 378 | const n2 = c2[i]; 379 | if (isSomeVNodeType(n1, n2)) { 380 | // 相同节点去调用patch对比属性以及子节点 381 | patch(n1, n2, container, parentInstance, anchor); 382 | } else { 383 | // 不同则 退出,i 停在 c2 子节点中出现的第一个新节点位置处 384 | break; 385 | } 386 | i++; 387 | } 388 | ``` 389 | 390 | 2. 从右侧对比 391 | 392 | 从右侧开始对比节点,出现不同节点时退出,此时 e1/e2 位置前的节点需要处理 393 | 394 | ```ts 395 | while (i <= e1 && i <= e2) { 396 | // 获取 右侧 节点 397 | const n1 = c1[e1]; 398 | const n2 = c2[e2]; 399 | 400 | if (isSomeVNodeType(n1, n2)) { 401 | patch(n1, n2, container, parentInstance, anchor); 402 | } else { 403 | // 出现不同时,e1和 e2 停在 c1 中出现 第一个 c2 没有的节点位置处 404 | break; 405 | } 406 | 407 | e1--; 408 | e2--; 409 | } 410 | ``` 411 | 412 | #### 例子 413 | 414 | 1. 在原有的基础上增加子节点 415 | 416 | - 在尾部增加新节点 417 | 418 | ![尾部增加](https://user-images.githubusercontent.com/39196952/160577800-00c75440-5002-4349-bc14-efcf23839bbc.png) 419 | 420 | 最初:i = 0, e1 = 1, e2 = 2, 从左侧对比,AB 相等; 421 | 则 i = 2, e1 = 1, e2 = 2; 422 | 满足 i > e1, 且 i <= e2, 添加新节点 C。 423 | 424 | ```ts 425 | if (i > e1) { 426 | if (i <= e2) { 427 | while (i <= e2) { 428 | patch(null, c2[i], container, parentInstance, null); 429 | i++; 430 | } 431 | } 432 | } 433 | ``` 434 | 435 | - 在头部增加节点 436 | 437 | ![头部增加](https://user-images.githubusercontent.com/39196952/160580549-446f0db9-e8de-4f30-87a2-1b92bd72c796.png) 438 | 439 | 最初:i = 0, e1 = 1, e2 = 2, 右侧对比,BC 相等; 440 | 则 i = 0, e1 = -1, e2 = 0; 441 | 满足 i > e1, 且 i <= e2, 在 B 前面添加新节点 A。 442 | 443 | ```ts 444 | if (i > e1) { 445 | if (i <= e2) { 446 | const anchor = e2 + 1 < l2 ? c2[e2 + 1].el : null; 447 | while (i <= e2) { 448 | patch(null, c2[i], container, parentInstance, anchor); 449 | i++; 450 | } 451 | } 452 | } 453 | ``` 454 | 455 | - 在中间增加节点 456 | 457 | ![中间增加](https://user-images.githubusercontent.com/39196952/160581338-e2db7b0c-2975-4a4e-99e0-d7b467f6c043.png) 458 | 459 | 最初:i = 0, e1 = 1, e2 = 2, 左侧对比,右侧对比,AC 相等; 460 | 则 i = 1, e1 = 0, e2 = 1; 461 | 满足 i > e1, 且 i <= e2, 在 C 前面添加新节点 B。 462 | 463 | 中间添加与头部添加没有太大差别,i 的移动并不影响 e1 和 e2 的移动,最终 e2+1 就是与 e1 最后一个相同的右侧节点,因此将从 i 到 e2 的不同节点全部添加到 anchor 前即可。 464 | 465 | 1. 删除多的节点 466 | 467 | ```ts 468 | if (i > e2) { 469 | while (i <= e1) { 470 | hostRemove(c1[i].el); 471 | i++; 472 | } 473 | } 474 | ``` 475 | 476 | - 删除尾部多的节点 477 | 478 | ![删除尾部节点](https://user-images.githubusercontent.com/39196952/160582265-6da1bae1-41fb-4f7d-a97b-97a8fdabc074.png) 479 | 480 | 最初:i = 0, e1 = 2, e2 = 1, 左侧对比,AB 相等; 481 | 退出循环时 i = 2, e1 = 2, e2 = 1; 482 | 此时 i > e2,调用 hostRemove 删除 i 到 e1 位置的所有多的节点。 483 | 484 | - 删除头部多的节点 485 | 486 | ![删除头部节点](https://user-images.githubusercontent.com/39196952/160583340-d6055248-7220-41f6-8e0a-414fdd9969df.png) 487 | 488 | 最初:i = 0, e1 = 2, e2 = 1, 左侧对比,AB 相等; 489 | 退出循环时 i = 0, e1 = 0, e2 = -1; 490 | 491 | - 删除中间多的节点 492 | 493 | ![删除中间节点](https://user-images.githubusercontent.com/39196952/160583800-3b490136-6c1c-45e0-a59f-a7f4f30e7354.png) 494 | 495 | 最初:i = 0, e1 = 2, e2 = 1, 左侧对比,AB 相等; 496 | 退出循环时 i = 1, e1 = 1, e2 = 0; 497 | 498 | 删除逻辑都是一样的,因为删除老节点必然 e2 < e1, 不管怎么对比,最终都满足 i > e2, 将 i - e1 位置的节点删除即可。 499 | 500 | ### 移动位置 501 | 502 | 1. 元素重排的时候是比较耗性能的,当然在 vue 中肯定对重排做了优化,是如何优化的呢?在 table 内元素做正序倒叙的排列时,这种有序的重排是怎么做优化的呢?是否去利用了 vue 的优化做一些不需要重排的事,因为正序倒叙重排时其实元素的相对位置并没有变化。 503 | 504 | - vue 对组件更新的优化 505 | - table 组件排序的优化 506 | - 在做大量组件位置变化时的优化 507 | 508 | 2. 最长递增子序列 509 | 510 | 由于老节点的相对 511 | 512 | ### component 513 | 514 | 1. 组件没有 setup 会报错 515 | -------------------------------------------------------------------------------- /src/runtime-core/renderer.ts: -------------------------------------------------------------------------------- 1 | import { createComponentInstance, setupComponent } from "./component"; 2 | import { 3 | isArrayChildren, 4 | isCompoent, 5 | isElement, 6 | isTextChildren, 7 | ShapeFlag, 8 | } from "./shapeFlags"; 9 | import { Fragment, Text } from "./vnode"; 10 | import { createAppAPI } from "./createApp"; 11 | import { effect } from "../reactivity/src"; 12 | import { EMPTY_OBJ } from "../shared"; 13 | import { shouldUpdateComponent } from "./componentUpdateUtils"; 14 | import { queueJobs } from "./scheduler"; 15 | 16 | export function createRenderer({ 17 | createElement: hostCreateElement, 18 | patchProp: hostPatchProp, 19 | insert: hostInsert, 20 | setElementText: hostSetElementText, 21 | remove: hostRemove, 22 | }) { 23 | function render(vnode, container, parentInstance) { 24 | // 调用 patch 函数递归处理组件 25 | patch(null, vnode, container, parentInstance, null); 26 | } 27 | 28 | // 处理 元素 的函数 (vue组件元素 / element dom元素) 29 | function patch(n1, n2, container, parentInstance, anchor) { 30 | // 处理不同类型的 vnode 元素 31 | // 1. element 32 | // 2. vue compoennt 33 | 34 | const { shapeFlag, type } = n2; 35 | 36 | switch (type) { 37 | case Fragment: 38 | processFragment(n1, n2, container, parentInstance, anchor); 39 | break; 40 | case Text: 41 | proceeText(n1, n2, container); 42 | break; 43 | default: 44 | if (isElement(shapeFlag)) { 45 | // 处理 element 类型元素 46 | processElement(n1, n2, container, parentInstance, anchor); 47 | } else if (isCompoent(shapeFlag)) { 48 | // 处理 vue组件 类型元素 49 | processComponent(n1, n2, container, parentInstance, anchor); 50 | } 51 | break; 52 | } 53 | } 54 | 55 | /** 56 | * @description 处理 Fragment 节点 57 | * @param vnode 58 | * @param container 59 | */ 60 | function processFragment(n1, n2, container, parentInstance, anchor) { 61 | mountChildren(n2.children, container, parentInstance, anchor); 62 | } 63 | 64 | /** 65 | * @description 处理文本节点 66 | * @param vnode 67 | * @param container 68 | */ 69 | function proceeText(n1, n2, container) { 70 | const textNode = (n2.el = document.createTextNode(n2.children)); 71 | container.append(textNode); 72 | } 73 | 74 | /** 75 | * @description 处理 element 类型元素 76 | * 此处的 element,是通过 createVNode 创建的虚拟节点 元素 77 | * 初始化之后才将 element 转化成了 真实dom 78 | * 判断调用 初始化 函数或是调用 更新 函数 79 | * @param vnode 虚拟节点 80 | * @param container 需要实际挂载的 容器 (真实dom元素) 81 | */ 82 | function processElement(n1, n2, container, parentInstance, anchor) { 83 | if (!n1) { 84 | // init 调用 elment 元素初始化函数 85 | mountElement(n2, container, parentInstance, anchor); 86 | } else { 87 | // update 88 | patchElement(n1, n2, container, parentInstance, anchor); 89 | } 90 | } 91 | 92 | /** 93 | * @description 更新 element 94 | * @param n1 95 | * @param n2 96 | * @param container 97 | * @param parentInstance 98 | */ 99 | function patchElement(n1, n2, container, parentInstance, anchor) { 100 | const oldProps = n1.props || EMPTY_OBJ; 101 | const newProps = n2.props || EMPTY_OBJ; 102 | 103 | // 需要给 n2.el 赋值,否则后面拿不到el 104 | const el = (n2.el = n1.el); 105 | 106 | patchChildren(n1, n2, el, parentInstance, anchor); 107 | patchProps(el, oldProps, newProps); 108 | } 109 | 110 | function patchChildren(n1, n2, container, parentInstance, anchor) { 111 | const prevShapeFlag = n1.shapeFlag; 112 | const c1 = n1.children; 113 | const nextShapeFlag = n2.shapeFlag; 114 | const c2 = n2.children; 115 | 116 | if (nextShapeFlag & ShapeFlag.TEXT_CHILDREN) { 117 | if (prevShapeFlag & ShapeFlag.ARRAY_CHILDREN) { 118 | unmountChildren(n1.children); 119 | } 120 | if (c1 !== c2) { 121 | hostSetElementText(container, c2); 122 | } 123 | } else { 124 | if (isTextChildren(prevShapeFlag)) { 125 | hostSetElementText(container, ""); 126 | mountChildren(c2, container, parentInstance, anchor); 127 | } else { 128 | patchKeyedChildren(c1, c2, container, parentInstance, anchor); 129 | } 130 | } 131 | } 132 | 133 | function patchKeyedChildren(c1, c2, container, parentInstance, anchor) { 134 | let i = 0; 135 | const l2 = c2.length; 136 | let e1 = c1.length - 1; 137 | let e2 = l2 - 1; 138 | const isSomeVNodeType = (n1, n2) => 139 | n1.type === n2.type && n1.key === n2.key; 140 | 141 | // 对比左侧虚拟节点 142 | while (i <= e1 && i <= e2) { 143 | // 获取 左侧 节点 144 | const n1 = c1[i]; 145 | const n2 = c2[i]; 146 | if (isSomeVNodeType(n1, n2)) { 147 | // 相同节点去调用patch对比属性以及子节点 148 | patch(n1, n2, container, parentInstance, anchor); 149 | } else { 150 | // 不同则 退出,i 停在 c2 子节点中出现的第一个新节点位置处 151 | break; 152 | } 153 | i++; 154 | } 155 | 156 | // 对比右侧虚拟节点 157 | while (i <= e1 && i <= e2) { 158 | // 获取 右侧 节点 159 | const n1 = c1[e1]; 160 | const n2 = c2[e2]; 161 | 162 | if (isSomeVNodeType(n1, n2)) { 163 | patch(n1, n2, container, parentInstance, anchor); 164 | } else { 165 | // 出现不同时,e1和 e2 停在 c1 中出现 第一个 c2 没有的节点位置处 166 | break; 167 | } 168 | 169 | e1--; 170 | e2--; 171 | } 172 | 173 | if (i > e1) { 174 | if (i <= e2) { 175 | const anchor = e2 + 1 < l2 ? c2[e2 + 1].el : null; 176 | while (i <= e2) { 177 | patch(null, c2[i], container, parentInstance, anchor); 178 | i++; 179 | } 180 | } 181 | } else if (i > e2) { 182 | while (i <= e1) { 183 | hostRemove(c1[i].el); 184 | i++; 185 | } 186 | } else { 187 | const s1 = i; 188 | const s2 = i; 189 | const toBePatched = e2 - s2 + 1; 190 | // 记录 c2 里面, 从 i - e2 的节点位置,方便后面判断 c1 中存在的老节点是 更新还是删除 191 | const keyToNewINdexMap = new Map(); 192 | // c2 中新节点 在 老节点 c1 中的 位置索引 映射 193 | // 这个map是为了去做 老节点的位置更新的 194 | const newIndexToOldIndexMap = new Array(toBePatched).fill(-1); 195 | // 优化更新 196 | let moved = true; 197 | let maxNewIndexSoFar = 0; 198 | 199 | // 将不同的 新节点 的 {key: index} 保存起来 200 | for (let i = s2; i <= e2; i++) { 201 | const nextChild = c2[i]; 202 | nextChild.key && keyToNewINdexMap.set(nextChild.key, i); 203 | } 204 | // 遍历 c1 中不同节点 部分 --- 删除 c1 中不存在的节点, patch 更新相同节点 205 | for (let i = s1; i <= e1; i++) { 206 | const prevChild = c1[i]; 207 | 208 | // 已经做过更新的 节点数 209 | let patched = 0; 210 | 211 | // 当更新 过的节点数 已经 大于等于 新节点数量时,直接删除 c1 中剩余的所有节点 212 | // 前提都是 在 s1 - e1 这个索引范围内 213 | if (patched >= toBePatched) { 214 | hostRemove(prevChild.el); 215 | continue; 216 | } 217 | 218 | // 是否存在 新节点的 索引 219 | let newIndex; 220 | 221 | // 有 key 值从 keyToNewINdexMap 中找 222 | if (prevChild.key !== null) { 223 | newIndex = keyToNewINdexMap.get(prevChild.key); 224 | } else { 225 | // 没有 key, 遍历 c2 找 226 | // 找到与 老节点 prevChild 相同的节点 返回 227 | for (let i = s2; i <= e2; i++) { 228 | if (isSomeVNodeType(prevChild, c2[i])) { 229 | newIndex = i; 230 | break; 231 | } 232 | } 233 | } 234 | 235 | if (newIndex === undefined) { 236 | // newIndex 新节点索引 不存在, 删除老节点 237 | hostRemove(prevChild.el); 238 | } else { 239 | // 新节点 存在 做更新 240 | /* 241 | 0 1 2 3 4 5 242 | a b c d e f c: 2 d: 3 e: 4 243 | s1 e1 244 | a b e c d f e: 4 c: 2 d: 3 245 | s2 e2 newIndexToOldIndexMap[ -1, 2, 3] 246 | 247 | 248 | newIndex 是 老节点 在 新节点数组 中的 下标 249 | c节点 老下标为 2,新下标 3 250 | c: 2 - nexIndex: 3, s2: 2 251 | d: 3 - nexIndex: 4, s2: 2 252 | e: 4 - newIndex: 2, s2: 2 253 | 254 | 在这个循环中,是循环老节点树,获取 此节点 在新节点树中的下标 位置, 255 | 而做位置更新时使用了 最长递增数列 的算法优化,因此如果节点的 绝对位置 不变,则不需要做 移动 256 | 257 | maxNewIndexSoFar 去记录上一个找到的 newIndex 258 | 如果下一个节点的 newIndex 比上一次的大,说明这个节点在新的节点树也是 在上一个节点 后面的,因此不需要移动 259 | 260 | 如果下一个节点的 newIndex 比上一个的小,则说明此节点 在新节点树中发生了位置变化,需要移动,打开 moved开关 261 | 262 | */ 263 | if (newIndex >= maxNewIndexSoFar) { 264 | maxNewIndexSoFar = newIndex; 265 | } else { 266 | moved = true; 267 | } 268 | 269 | // 通过 prevChild.key 找到 此节点在新节点树中的 位置下标(newIndex) 270 | // newIndex - s2 此时为 在不同节点 范围内,此节点在 新节点树中的 绝对位置 271 | // 将这个绝对位置的值设置为,此节点在 老节点树中的 下标 i 272 | // newIndexToOldIndexMap 就映射了 节点的新位置和老位置,下标为新位置,值为老位置 273 | // 因为从 newIndexToOldIndexMap 找到最长的递增数列,就是那些绝对位置没有变的节点,而剩下的就是需要改变的 274 | // 也正因如此,上面 可以同记录上一次的 newIndex 来判断是否需要 移动 275 | newIndexToOldIndexMap[newIndex - s2] = i; 276 | patch(prevChild, c2[newIndex], container, parentInstance, null); 277 | patched++; 278 | } 279 | } 280 | 281 | // 获取 老节点索引 的 最长递增子序列,不需要移动的节点的 相对位置 是不会变化的, 一定是低增的 282 | const increasingNewIndexSequence = moved 283 | ? getSequence(newIndexToOldIndexMap) 284 | : []; 285 | let j = increasingNewIndexSequence.length - 1; 286 | 287 | /* 288 | a b (c d e) f 289 | a b (e c d) f 290 | 为什么这里 只需要 for 改变节点范围的长度 toBePatched 去对比 increasingNewIndexSequence 最长地址数列就可以知道需不需要移动 291 | 292 | 因为 increasingNewIndexSequence 数组代表的是 不需要移动的节点 在新节点树的下标值,因此在更新之后这个 下标的 对应的节点之间的 绝对位置是不变的; 293 | 在 这个 更新节点 的个数范围里面,只需要找到那个在 increasingNewIndexSequence 不存的下标, 就是需要 移动的 节点 位置, 294 | 而这个节点就是 c2中 [数量长度 加上 新节点不同 的起点 s2] 位置的节点 295 | 296 | 而之所以 从最后一个节点位置 toBePatched 开始循环,是因为 在不同节点范围内的节点 的位置不稳定的; 297 | 而 toBePatched + 1 位置的节点是稳定的,因为它要么是 尾部的相同节点, 要么超出了 节点树长度,就直接增加到 最后就行 298 | 299 | */ 300 | 301 | for (let i = toBePatched - 1; i >= 0; i--) { 302 | const nextIndex = i + s2; 303 | const nextChild = c2[nextIndex]; 304 | const anchor = nextIndex + 1 < l2 ? c2[nextIndex + 1].el : null; 305 | 306 | if (moved) { 307 | if (i !== increasingNewIndexSequence[j]) { 308 | hostInsert(nextChild.el, container, anchor); 309 | } else { 310 | j--; 311 | } 312 | } 313 | } 314 | } 315 | } 316 | 317 | function unmountChildren(children) { 318 | for (let i = 0; i < children.length; i++) { 319 | const el = children[i].el; 320 | hostRemove(el); 321 | } 322 | } 323 | 324 | /** 325 | * @description 更新 props 326 | * @param el 327 | * @param oldProps 328 | * @param newProps 329 | */ 330 | function patchProps(el, oldProps, newProps) { 331 | if (oldProps !== newProps) { 332 | for (const key in newProps) { 333 | const oldProp = oldProps[key]; 334 | const newProp = newProps[key]; 335 | 336 | if (oldProp !== newProp) { 337 | hostPatchProp(el, key, oldProp, newProp); 338 | } 339 | } 340 | 341 | if (oldProps !== EMPTY_OBJ) { 342 | for (const key in oldProps) { 343 | if (!Reflect.has(newProps, key)) { 344 | hostPatchProp(el, key, oldProps[key], null); 345 | } 346 | } 347 | } 348 | } 349 | } 350 | 351 | /** 352 | * @description element 类型元素 初始化函数 353 | * @param vnode 354 | * @param container 355 | */ 356 | function mountElement(vnode, container, parentInstance, anchor) { 357 | // 根据 虚拟节点 属性 创建 element (真实dom) 358 | const { type, props } = vnode; 359 | 360 | const el = hostCreateElement(type); 361 | 362 | vnode.el = el; 363 | 364 | // 处理 子节点 (虚拟节点) 365 | const { shapeFlag, children } = vnode; 366 | 367 | if (isTextChildren(shapeFlag)) { 368 | el.textContent = children; 369 | } else if (isArrayChildren(shapeFlag)) { 370 | mountChildren(vnode.children, el, parentInstance, anchor); 371 | } 372 | 373 | // 处理节点属性props 374 | if (props) { 375 | for (const key in props) { 376 | const value = props[key]; 377 | hostPatchProp(el, key, null, value); 378 | } 379 | } 380 | // 添加到容器中 381 | hostInsert(el, container, anchor); 382 | } 383 | 384 | /** 385 | * @description 处理 element 子节点 386 | * 遍历 vnode.children ,调用 patch 生成真实 dom元素 387 | * @param vnode 388 | * @param container 子节点容器,既父节点 vnode 389 | */ 390 | function mountChildren(children, container, parentInstance, anchor) { 391 | children.forEach((child) => { 392 | patch(null, child, container, parentInstance, anchor); 393 | }); 394 | } 395 | 396 | // 397 | /** 398 | * @description 处理 vue组件 类型元素 399 | * @param vnode 400 | * @param container 401 | */ 402 | function processComponent(n1, n2, container, parentInstance, anchor) { 403 | // 调用组件挂载函数 404 | if (!n1) { 405 | mountComponent(n2, container, parentInstance, anchor); 406 | } else { 407 | updateComponent(n1, n2); 408 | } 409 | } 410 | 411 | function updateComponent(n1, n2) { 412 | const instance = (n2.component = n1.component); 413 | if (shouldUpdateComponent(n1, n2)) { 414 | instance.next = n2; 415 | instance.update(); 416 | } else { 417 | n2.el = n1.el; 418 | } 419 | } 420 | 421 | /** 422 | * @name mountComponent 423 | * @description vue组件 处理函数 424 | * 初始化组件 425 | * 处理 render 渲染函数 426 | * mountComponent 最终的归宿还是到了 mountElement 内,将虚拟节点转换成 真实dom 427 | * @param vnode 428 | * @param container 429 | */ 430 | function mountComponent(initialVNode, container, parentInstance, anchor) { 431 | // 创建 组件 实例 432 | const instance = (initialVNode.component = createComponentInstance( 433 | initialVNode, 434 | parentInstance 435 | )); 436 | 437 | // 初始化组件: 初始化 props、slots等等, 挂载 component 到组件实例上 438 | setupComponent(instance); 439 | // 渲染 render 返回值(虚拟节点) 440 | setupRenderEffect(instance, initialVNode, container, anchor); 441 | } 442 | 443 | /** 444 | * @description 渲染 render 返回值 445 | * 调用 patch 函数,将 render 返回的虚拟节点转换成 真实dom 446 | * @param instance 447 | * @param initialVNode 448 | * @param container 449 | */ 450 | function setupRenderEffect(instance, initialVNode, container, anchor) { 451 | instance.update = effect( 452 | () => { 453 | // 初始化 454 | if (!instance.isMounted) { 455 | const { proxy } = instance; 456 | // 执行 组件实例 上的 render 函数,拿到 虚拟节点树 457 | const subTree = instance.render.call(proxy, proxy); 458 | instance.subTree = subTree; 459 | 460 | // 调用 patch 处理 节点树 461 | patch(null, subTree, container, instance, anchor); 462 | 463 | // 在 patch 中真实生成了 真实dom 后,挂载到 vnode 上 464 | 465 | initialVNode.el = subTree.el; 466 | 467 | instance.isMounted = true; 468 | } else { 469 | //更新 470 | console.log("update"); 471 | const { proxy, vnode, next } = instance; 472 | if (next) { 473 | next.el = vnode.el; 474 | updateComponentPreRender(instance, next); 475 | } 476 | const currentSubTree = instance.render.call(proxy, proxy); 477 | const prevSubTree = instance.subTree; 478 | instance.subTree = currentSubTree; 479 | 480 | patch(prevSubTree, currentSubTree, container, instance, anchor); 481 | // initialVNode.el = currentSubTree.el; 482 | } 483 | }, 484 | { 485 | scheduler() { 486 | queueJobs(instance.update); 487 | }, 488 | } 489 | ); 490 | } 491 | 492 | return { 493 | createApp: createAppAPI(render), 494 | }; 495 | } 496 | 497 | function updateComponentPreRender(instance, nextVnode) { 498 | instance.vnode = nextVnode; 499 | instance.next = null; 500 | instance.props = nextVnode.props; 501 | } 502 | 503 | function getSequence(arr) { 504 | const p = arr.slice(); 505 | const result = [0]; 506 | let i, j, u, v, c; 507 | const len = arr.length; 508 | for (i = 0; i < len; i++) { 509 | const arrI = arr[i]; 510 | if (arrI !== 0) { 511 | j = result[result.length - 1]; 512 | if (arr[j] < arrI) { 513 | p[i] = j; 514 | result.push(i); 515 | continue; 516 | } 517 | u = 0; 518 | v = result.length - 1; 519 | while (u < v) { 520 | c = (u + v) >> 1; 521 | if (arr[result[c]] < arrI) { 522 | u = c + 1; 523 | } else { 524 | v = c; 525 | } 526 | } 527 | if (arrI < arr[result[u]]) { 528 | if (u > 0) { 529 | p[i] = result[u - 1]; 530 | } 531 | result[u] = i; 532 | } 533 | } 534 | } 535 | u = result.length; 536 | v = result[u - 1]; 537 | while (u-- > 0) { 538 | result[u] = v; 539 | v = p[v]; 540 | } 541 | return result; 542 | } 543 | --------------------------------------------------------------------------------