@small-vue/runtime-core
Version:
简版vue3 runtime-core
652 lines (641 loc) • 22.5 kB
JavaScript
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]; }
});
});
;