UNPKG

@small-vue/runtime-core

Version:
652 lines (641 loc) 22.5 kB
'use strict'; var reactivity = require('@small-vue/reactivity'); var shared = require('@small-vue/shared'); const Fragment = Symbol("Fragment"); const Text = Symbol("Text"); function createVNode(type, props, children) { return { type, props: reactivity.shallowReadonly(props || {}), children, }; } function createTextVNode(text) { return createVNode(Text, null, text); } function createFragmentVNode(children) { return createVNode(Fragment, null, children); } function renderSlot(slots, name = "default", props) { const slot = slots[name]; if (slot) { // 创建并返回一个父虚拟节点,执行插槽方法返回虚拟节点数组,作为子节点 return createFragmentVNode(slot(props)); } } function withCtx(fn, _ctx) { return (props) => { let ctx = new Proxy(props || {}, { get(target, key) { if (target[key]) { return target[key]; } else { return _ctx[key]; } }, }); return fn(ctx); }; } let currentInstance; function createComponentInstance(vnode, parent = null) { const instance = { vnode, setupStatus: {}, proxy: null, emit: null, // 以父组件provides为原型,利用原型链,满足provide向祖先组件检索的需求 provides: parent ? Object.create(parent.provides) : {}, parent, subTree: null, update: null, }; instance.proxy = createProxy(instance); instance.emit = emit.bind(null, instance); return instance; } // proxy特殊属性处理 const propertyMap = { $el: (i) => i.subTree.el, $slots: (i) => i.vnode.children, $props: (i) => i.vnode.props, }; function createProxy(instance) { return new Proxy({}, { get(target, key) { // 在setup结果中匹配 const { setupStatus } = instance; if (key in setupStatus) { return setupStatus[key]; } // 在props中匹配 const { props } = instance.vnode; if (key in props) { return props[key]; } // 特殊属性处理 const getter = propertyMap[key]; if (getter) { return getter(instance); } }, }); } function emit(instance, event, ...params) { const { props } = instance.vnode; // 转换事件名,如 add → onAdd const key = "on" + shared.capitalize(event); // 从props中获取事件监听,存在则执行 props[key] && props[key](...params); } function setupComponent(instance) { const { setup } = instance.vnode.type; if (setup) { // 保存当前组件实例 setCurrentInstance(instance); // 执行setup方法 const result = setup(instance.vnode.props, { emit: instance.emit, }); // 重置当前组件实例 setCurrentInstance(null); // 如果是setup结果是对象,挂载到组件对象上 if (shared.isObject(result)) { instance.setupStatus = reactivity.proxyRefs(result); } } // 用户传入的组件对象 const c = instance.vnode.type; // 如果有编译函数,且用户没传 render 函数,且传了 template, // 则将 template 编译成 render 函数 if (compiler && !c.render && c.template) { c.render = compiler(c.template); } // 组件更新时,重新执行了 render 函数,通过 createVNode 改变了 instance.vnode 对象, // 但是 instance.vnode.type 指向的还是同一个对象,因为其是来自 setup 执行后的返回数据 // 因此组件更新时,不需要再走 template 编译 render 函数的逻辑 } function getCurrentInstance() { return currentInstance; } function setCurrentInstance(instance) { currentInstance = instance; } let compiler; function registerRuntimeCompiler(_compiler) { compiler = _compiler; } // 当前组件的provides保存数据 function provide(key, value) { const instance = getCurrentInstance(); if (instance) { instance.provides[key] = value; } } // 向父组件的provides获取数据 function inject(key, defaultValue) { let instance = getCurrentInstance(); if (instance) { // 利用了原型链,父组件没有时,会自动向上检索 const value = instance.parent.provides[key]; if (value) { return value; } else { // 无值时则返回默认值(支持函数) return typeof defaultValue == "function" ? defaultValue() : defaultValue; } } } // 获取最长递增子序列的序号算法 // 比如:[1, 6, 5, 12, 8, 1, 10],最长递增子序列为 1 5 8 10, // 返回其对应序号 [0, 2, 4, 6] function getSequence(arr) { const p = arr.slice(); const result = [0]; let i, j, u, v, c; const len = arr.length; for (i = 0; i < len; i++) { const arrI = arr[i]; if (arrI !== 0) { j = result[result.length - 1]; if (arr[j] < arrI) { p[i] = j; result.push(i); continue; } u = 0; v = result.length - 1; while (u < v) { c = (u + v) >> 1; if (arr[result[c]] < arrI) { u = c + 1; } else { v = c; } } if (arrI < arr[result[u]]) { if (u > 0) { p[i] = result[u - 1]; } result[u] = i; } } } u = result.length; v = result[u - 1]; while (u-- > 0) { result[u] = v; v = p[v]; } return result; } function createAppAPI(render) { return function createApp(rootComponent) { return { mount(rootContainer) { // 创建虚拟节点 const vnode = createVNode(rootComponent); // 字符串转DOM对象 if (typeof rootContainer == "string") { rootContainer = document.querySelector(rootContainer); } // 开启渲染 render(vnode, rootContainer); }, }; }; } const promise = Promise.resolve(); const queue = []; let isFlushPending = false; // 将多个任务收集,放到微任务中统一执行, // 用于在 renderer 中避免重复执行 effect,提升性能 function queueJobs(job) { // 任务不在队列中,添加进来 if (!queue.includes(job)) { queue.push(job); } // 执行队列 queueFlush(); } // 执行队列 function queueFlush() { if (isFlushPending) return; isFlushPending = true; // 开启微任务 promise.then(() => { isFlushPending = false; let job; // 取出队列所有 job 执行 while ((job = queue.shift())) { job && job(); } }); } // 提供给应用代码使用,创建一个微任务, // 如果此时队列的微任务已经存在,则会排在其之后执行 function nextTick(fn) { return fn ? promise.then(fn) : promise; } function createRenderer(options) { const { createElement: hostCreateElement, patchProp: hostPatchProp, insert: hostInsert, remove: hostRemove, setElementText: hostSetElementText, } = options; function render(vnode, container) { patch(null, vnode, container); } // 递归处理虚拟节点 // n1 旧虚拟节点,n2 新虚拟节点 function patch(n1, n2, container, parentComponent = null, anchor = null) { const { type } = n2; switch (type) { case Fragment: // Fragment类型 processFragment(n1, n2, container, parentComponent); break; case Text: // 文本类型 processText(n1, n2, container); break; default: // 元素类型 if (typeof type == "string") { processElement(n1, n2, container, parentComponent, anchor); } // 组件类型 else if (shared.isObject(type)) { // TODO 组件也应该要传anchor,渲染subTree时可能需要渲染在锚点前 processComponent(n1, n2, container, parentComponent); } } } // 处理组件类型 function processComponent(n1, n2, container, parentComponent) { if (!n1) { // 初始化组件 mountComponent(n2, container, parentComponent); } else { // 更新组件 updateComponent(n1, n2); } } // 初始化组件 function mountComponent(vnode, container, parentComponent) { const instance = (vnode.component = createComponentInstance(vnode, parentComponent)); setupComponent(instance); setupRenderEffect(instance, vnode, container); } function setupRenderEffect(instance, vnode, container) { // 将 effect 返回的 runner 函数保存到组件实例 update 字段 instance.update = reactivity.effect(() => { // 执行render函数,得到当前组件的根元素虚拟节点 // render函数执行中会访问到响应式对象reactive或ref,触发依赖收集 const subTree = instance.vnode.type.render.call(instance.proxy, instance.proxy); // TODO 如果根节点本身改变了,比如div变成p,怎么处理? // TODO 是否要2个新旧subTree比较一下先 // 如果是首次执行,instance.subTree将为null,执行的也是初始化的流程 patch(instance.subTree, subTree, container, instance); // 保存最新subTree instance.subTree = subTree; console.log(instance.vnode.type.name, "setupRenderEffect"); }, { scheduler() { queueJobs(instance.update); }, }); } // 更新组件 function updateComponent(n1, n2) { // 从旧虚拟节点反向获取组件实例 const instance = n1.component; // 重新关联新虚拟节点和组件实例 n2.component = instance; instance.vnode = n2; // 检测是否需要更新 let shouldUpdate = false; // 遍历 props for (const key in n2.props) { if (n2.props[key] != n1.props[key]) { shouldUpdate = true; break; } } // 需要更新,执行 update(其实是 effect 返回的 runner 函数) if (shouldUpdate) { queueJobs(instance.update); } } // 处理元素类型 function processElement(n1, n2, container, parentComponent, anchor) { if (!n1) { // 初始化元素 mountElement(n2, container, parentComponent, anchor); } else { // 更新元素 updateElement(n1, n2, parentComponent); } } // 初始化元素 function mountElement(vnode, container, parentComponent, anchor) { const { type, props, children } = vnode; // 创建元素 const el = hostCreateElement(type); // 处理props for (let key in props) { hostPatchProp(el, key, null, props[key]); } // 处理children if (typeof children == "string") { // 设置文本 hostSetElementText(el, children); } else if (Array.isArray(children)) { // 递归子元素 for (let v of children) { patch(null, v, el, parentComponent); } } vnode.el = el; hostInsert(el, container, anchor); } // 更新元素 function updateElement(n1, n2, parentComponent) { const el = (n2.el = n1.el); // 更新元素属性 patchProps(el, n1.props, n2.props); // 更新元素子节点 patchChildren(n1, n2, el, parentComponent); } // 更新元素属性 function patchProps(el, oldProps, newProps) { for (const key in newProps) { const prevProp = oldProps[key]; const nextProp = newProps[key]; // 属性值改变 if (prevProp !== nextProp) { hostPatchProp(el, key, prevProp, nextProp); } } for (const key in oldProps) { // 旧属性在新属性中不存在 if (!(key in newProps)) { hostPatchProp(el, key, oldProps[key], null); } } } // 更新元素子节点 function patchChildren(n1, n2, container, parentComponent) { const c1 = n1.children; const c2 = n2.children; // 新的是字符串 if (typeof c2 == "string" && c1 != c2) { hostSetElementText(container, c2); } // 新的是数组 else if (Array.isArray(c2)) { // 旧的是文本,直接渲染数组 if (typeof c1 == "string") { hostSetElementText(container, ""); for (let v of c2) { patch(null, v, container, parentComponent); } } // 旧的也是数组,两个数组diff算法 else { patchKeyedChildren(c1, c2, container, parentComponent); } } } // 两个数组diff算法 function patchKeyedChildren(c1, c2, container, parentComponent) { // 左侧序号 let i = 0; // 右侧序号 let e1 = c1.length - 1; let e2 = c2.length - 1; //* 左侧对比 while (i <= e1 && i <= e2) { // 相同元素,递归子元素 if (isSameVNodeType(c1[i], c2[i])) { patch(c1[i], c2[i], container, parentComponent); } else { // 不同则跳出(目的是记录此时的i,即为左侧首次不同的序号) break; } i++; } //* 右侧对比 while (i <= e1 && i <= e2) { // 相同元素,递归子元素 if (isSameVNodeType(c1[e1], c2[e2])) { patch(c1[e1], c2[e2], container, parentComponent); } else { // 不同则跳出(目的是记录此时的e1和e2,即为右侧首次不同的序号) break; } e1--; e2--; } //* 1. 新旧一样 if (i > e1 && i > e2) { return; } //* 2.新的比旧的长 // (A B) 或 (A B) // (A B) C D C D (A B) if (i > e1) { // 有锚点时要插在锚点前面 const nextPos = e2 + 1; const anchor = nextPos < c2.length ? c2[nextPos].el : null; while (i <= e2) { patch(null, c2[i], container, parentComponent, anchor); i++; } return; } //* 3.旧的比新的长 // (A B) C D 或 C D (A B) // (A B) (A B) if (i > e2) { while (i <= e1) { hostRemove(c1[i].el); i++; } return; } //* 4.中间杂乱部分 // A B (C D E Z) F G // A B (D C Y E) F G // D 位置改变,Z 删除,Y 新增,C E 属于最长递增子序列 let s1 = i; let s2 = i; // 新数组中等待处理和已处理的节点数量, const toBePatched = e2 - s2 + 1; let patched = 0; // 保存新数组中 key-index 映射 const keyToNewIndexMap = new Map(); for (let i = s2; i <= e2; i++) { const key = c2[i].props.key; if (key != null) { keyToNewIndexMap.set(c2[i].props.key, i); } } // 新老数组中相同节点的序号映射关系,用数组表示 // 其中新数组中节点的序号(以中间部分开始)作为数组序号 // 对应旧数组中相同节点的序号作为数组的值 // A B (C D E Z) F G // A B (D C Y E) F G // 比如上面,最终为 [3, 2, undefined, 4] const indexArr = new Array(toBePatched); // 判断新旧数组的节点有没有发生顺序改变 // 如果没有则不需要获取最长递增子序列,减少性能消耗 let moved = false; // 辅助 moved,遍历旧数组中的相同节点,在新数组的序号,如果是不断递增,说明没有移动 let maxNewIndex = 0; // 遍历旧数组 for (let i = s1; i <= e1; i++) { const child = c1[i]; // 如果新数组中节点都处理完了,旧数组节点直接删除,无须再比对 if (patched >= toBePatched) { hostRemove(child.el); continue; } // 当前节点在新数组中的序号,下面这段都是为了找newIndex let newIndex; const key = child.props.key; if (key != null) { newIndex = keyToNewIndexMap.get(key); } else { // 没有key,只能嵌套遍历了 for (let j = s2; j <= e2; j++) { // isSameVNodeType 对比的是 key 和 标签,此处明显没有key了,相当于只对比标签 /* <div v-if="isChange">A</div> <div>B</div> <div>C</div> <div v-if="!isChange">D</div> */ // 顺序发生改变只由 v-for 和 v-if 造成,v-if vue会自动帮我们带上key // 旧:A(带key) B C // 新:B C D(带key) // 遍历旧时,由于 isSameVNodeType 只对比标签,旧B匹配到新B,旧C也匹配到新B // 所以需要判断 indexArr 中指定位置是否已有值,有空位才能占座 // 由于 B C 位置不变,因此按序占座,旧B-新B,旧C-新C if (indexArr[j - s2] == undefined && isSameVNodeType(c1[i], c2[j])) { newIndex = j; break; } } } //* 旧数组节点在新数组中不存在,删了 if (newIndex == undefined) { hostRemove(child.el); } //* 节点在新旧数组都存在 else { // 记录新旧数组序号映射 indexArr[newIndex - s2] = i; // 判断节点是否发生移动 if (newIndex >= maxNewIndex) { maxNewIndex = newIndex; } else { moved = true; } // 进行渲染 patch(child, c2[newIndex], container, parentComponent); patched++; } } // 获取最长递增子序列 const sequence = moved ? getSequence(indexArr) : null; // 记录 sequence 的最后一个序号,参与下面的倒序遍历 let j = sequence ? sequence.length - 1 : null; // 倒序遍历新数组 // 因为插入是插在锚点前,必须确定锚点是处理过的,所以倒序 for (let i = toBePatched - 1; i >= 0; i--) { const index = s2 + i; const child = c2[index]; // 获取锚点 const anchor = index < c2.length - 1 ? c2[index + 1].el : null; //* 如果当前节点没在新旧数组映射中,说明是新增的 if (!indexArr[i]) { patch(null, child, container, parentComponent, anchor); } // 发生过顺序变化 else if (moved) { //* 在最长递增子序列中,不需要移动 if (sequence[j] == i) { j--; } //* 否则进行移动 else { hostInsert(child.el, container, anchor); } } } } function isSameVNodeType(n1, n2) { return n1.type == n2.type && n1.props.key == n2.props.key; } // 处理Fragment类型 function processFragment(n1, n2, container, parentComponent) { if (!n1) { // 初始化 for (let v of n2.children) { patch(null, v, container, parentComponent); } } else { // 更新 patchChildren(n1, n2, container, parentComponent); } } // 处理文本类型 function processText(n1, n2, container) { if (!n1) { // 初始化 n2.el = document.createTextNode(n2.children); container.append(n2.el); } else { // 更新 let textNode = n1.el; textNode.data = n2.children; n2.el = textNode; } } return { createApp: createAppAPI(render), }; } Object.defineProperty(exports, 'toDisplayString', { enumerable: true, get: function () { return shared.toDisplayString; } }); exports.createElementVNode = createVNode; exports.createFragmentVNode = createFragmentVNode; exports.createRenderer = createRenderer; exports.createTextVNode = createTextVNode; exports.createVNode = createVNode; exports.getCurrentInstance = getCurrentInstance; exports.getSequence = getSequence; exports.inject = inject; exports.nextTick = nextTick; exports.provide = provide; exports.registerRuntimeCompiler = registerRuntimeCompiler; exports.renderSlot = renderSlot; exports.withCtx = withCtx; Object.keys(reactivity).forEach(function (k) { if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, { enumerable: true, get: function () { return reactivity[k]; } }); });