UNPKG

reecho

Version:

Reecho 是一款基于依赖收集的MVVM框架,它具有以下特点 - 声明式数据: 基于proxy的依赖收集 - 使用函数定义组件,但组件不会如React一样重复执行造成心智负担 - 读写分离,读取状态和更改状态统一使用函数,避免vue3的ref一样有时需要`xxx.value`有时不需要的不一致性 - 使用TS编写,类型友好

452 lines (424 loc) 12.9 kB
import { ShapeFlags } from "src/shared/shapFlags"; import { VNode } from "./vnode"; import { createAppApI } from "./createAppApi"; import { setupComponent, createComponentInstance, ComponentInstance, shouldComponentUpdate, updateProps, } from "./components"; import { effect } from "src/reactivity"; import { isArray, invokeArrayFns } from "src/shared"; import { queueJob, queuePostFlushCb } from "./scheduler"; export interface RendererNode { [key: string]: any; } export interface RendererElement extends RendererNode {} export interface RendererOptions< HostNode = RendererNode, HostElement = RendererElement > { patchProp( el: HostElement, key: string, prevValue: any, nextValue: any, isSVG?: boolean, prevChildren?: VNode<HostNode, HostElement>[] ): void; insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void; remove(el: HostNode): void; createElement(type: string): HostElement; createText(text: string): HostNode; createComment(text: string): HostNode; setText(node: HostNode, text: string): void; setElementText(node: HostElement, text: string): void; parentNode(node: HostNode): HostElement | null; nextSibling(node: HostNode): HostNode | null; } type PatchFn = ( n1: VNode | null, // null means this is a mount n2: VNode, container: RendererElement, parant?: any, anchor?: RendererNode | null ) => void; export function createRenderer(options: any): any { const { patch } = renderOpsCreater(options); const render = (vnode: VNode, container: RendererElement) => { const prevVnode = container.vnode; if (vnode) { // 有新节点 patch patch(prevVnode, vnode, container); container.vnode = vnode; } else if (prevVnode && prevVnode) { // 没新节点有旧节点,移除 DOM options.remove(prevVnode.el); container.vnode = null; } }; return { render, createApp: createAppApI(render), }; } function renderOpsCreater(options: RendererOptions) { const { insert, createElement, patchProp, remove, setText, setElementText, } = options; // 比较并更新两个vnode const patch: PatchFn = ( n1: VNode, n2: VNode, container: RendererElement, parentComponent = null, anchor = null ) => { if (!n1) { const { shapeFlag: nextFlags } = n2; // 挂载 if (nextFlags & ShapeFlags.ELEMENT) { // 挂载元素 patchElement(null, n2, container, parentComponent, anchor); } else if (nextFlags & ShapeFlags.COMPONENT) { // 挂载组件 patchComponent(null, n2, container, parentComponent, anchor); } } else { const nextFlags = n2?.shapeFlag; const prevFlags = n1?.shapeFlag; // 更新; if (prevFlags !== nextFlags) { // 节点类型不同直接替换 replaceVNode(n1, n2, container, anchor); } else if (nextFlags & ShapeFlags.ELEMENT) { // 比较元素节点 patchElement(n1, n2, container, parentComponent, anchor); } else if (nextFlags & ShapeFlags.COMPONENT) { // 比较组件节点 patchComponent(n1, n2, container, parentComponent, anchor); } else if (nextFlags & ShapeFlags.TEXT_CHILDREN) { // 比较文本节点 patchText(n1, n2); } } }; // 挂载 function mount( vnode: VNode, container: RendererElement, parentComponent?: any, anchor = null ) { patch(null, vnode, container, parentComponent, anchor); } function patchElement( n1: VNode, n2: VNode, container: RendererElement, parentComponent = null, anchor = null ) { const flag = n2.shapeFlag; let el; if (n1 === null) { // 挂载 el = createElement(n2.type); n2.el = el; const props = n2.props; if (props) { // 如果 props 存在,则遍历 for (let key in props) { patchProp(el, key, null, props[key]); } } // 递归渲染子节点 const children = n2.children; if (flag & ShapeFlags.TEXT_CHILDREN) { // 文本子节点 setElementText(el, children as string); } else if (flag & ShapeFlags.ARRAY_CHILDREN) { // 数组节点 for (let i = 0; i < children.length; i++) { if (children[i]) { mount(children[i] as any, el, parentComponent); } } } // 节点插入容器 insert(el, container, anchor); } else { if (n1.type !== n2.type) { // 标签不同直接替换元素 replaceVNode(n1, n2, container); return; } el = n2.el = n1.el; const p1 = n1.props; const p2 = n2.props; // 比较props // 新增props if (p2) { for (let key in p2) { patchProp(el, key, p1[key], p2[key]); } } // 删除无用的props if (p1) { for (let key in p1) { if (p1[key] && !p2.hasOwnProperty(key)) { patchProp(el, key, p1[key], null); } } } patchChildren(n1, n2, el, parentComponent); } } const patchText = (n1: VNode, n2: VNode) => { const el = (n2.el = n1.el); if (n2.children !== n1.children) { setText(el, n2.children as string); } }; const replaceVNode = ( n1: VNode, n2: VNode, container: RendererElement, anchor = null ) => { if (n2) { mount(n2, container, null, n1.el); } if (n1.shapeFlag & ShapeFlags.COMPONENT) { // 卸载组件 // beforeUnmount const { instance } = n1; if (instance.bum) { invokeArrayFns(instance.bum); } remove(n1.el); // UnMounted if (instance.um) { invokeArrayFns(instance.um); } } else { remove(n1.el); } }; function patchComponent( n1: VNode, n2: VNode, container: RendererElement, parentComponent = null, anchor = null ) { if (!n1) { // 挂载 // 创建组件实例 const instance = (n2.instance = createComponentInstance( n2, parentComponent )); setupComponent(instance); setupRenderEffect(instance, container, n2); } else { // 更新 updateComponent(n1, n2, container); } } function setupRenderEffect( instance: ComponentInstance, container: RendererElement, vnode: VNode ) { instance.update = effect( () => { if (!instance.isMounted) { const { bm, m } = instance; // 首次挂载 // beforeMounted hooks if (bm) { invokeArrayFns(bm); } // 执行渲染函数并保存在实例上 const subTree = instance.render(); instance.subTree = subTree; instance.isMounted = true; mount(subTree, container, instance); vnode.el = subTree.el; // Mounted hooks if (m) { // mounted需要在渲染后执行,渲染为异步任务,这里做特殊处理 queuePostFlushCb(m); } } else { // 非首次 let { next, bu, u } = instance; // beforeUpdate hooks if (bu) { invokeArrayFns(bu); } if (next) { next.el = vnode.el; updateProps(instance, { ...next.props, children: next.children }); } else { next = vnode; } const nextSubtree = instance.render(); const prevSubTree = instance.subTree; instance.subTree = nextSubtree; patch(prevSubTree, nextSubtree, prevSubTree.el, instance); // Updated hooks if (u) { queuePostFlushCb(u); } } }, { scheduler: (job) => { queueJob(job); }, } ); } function updateComponent(n1: VNode, n2: VNode, container: RendererElement) { if (n1.type !== n2.type) { // 组件类型不同直接重新挂载 replaceVNode(n1, n2, container); return; } // 否则比较props(包括children) const instance = (n2.instance = n1.instance); const p1 = { ...n1.props, children: n1.children }; const p2 = { ...n2.props, children: n2.children }; if (shouldComponentUpdate(p1, p2)) { instance.next = n2; instance.update(); } else { n2.el = n1.el; instance.vnode = n2; } } const patchChildren = ( n1: VNode, n2: VNode, container: RendererElement, parentComponent = null ) => { let { shapeFlag: prevShapeFlag, children: c1 } = n1; let { shapeFlag, children: c2 } = n2; // 文本 if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // 子元素为text,判断前后是否相等,不相等直接更新 if (c1 !== c2) { setElementText(n1.el, c2 as string); } } else { if (!c2 || !c2.length) { n2.el.innerHTML = ""; } else if (isArray(c2)) { if (!isArray(c1)) { c2.forEach((child) => { if (child !== null && child !== undefined) { mount(child, container, parentComponent); } }); return; } c1 = c1.filter((item) => item !== false && item !== null); c2 = c2.filter((item) => item !== false && item !== null); // 双端比较 let oldStartIdx = 0; let oldEndIdx = c1.length - 1; let newStartIdx = 0; let newEndIdx = c2.length - 1; let oldStartVNode = c1[oldStartIdx]; let oldEndVNode = c1[oldEndIdx]; let newStartVNode = c2[newStartIdx]; let newEndVNode = c2[newEndIdx]; // 指针交汇前循环执行 while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) { if (!oldStartVNode) { oldStartVNode = c1[++oldStartIdx]; } else if (!oldEndVNode) { oldEndVNode = c1[--oldEndIdx]; } else if (oldStartVNode.key === newStartVNode.key) { // 1:头头比较 patch(oldStartVNode, newStartVNode, container); oldStartVNode = c1[++oldStartIdx]; newStartVNode = c2[++newStartIdx]; } else if (oldEndVNode.key === newEndVNode.key) { // 2: 尾尾比较 patch(oldEndVNode, newEndVNode, container); oldEndVNode = c1[--oldEndIdx]; newEndVNode = c2[--newEndIdx]; } else if (oldStartVNode.key === newEndVNode.key) { // 3: 头尾比较 patch(oldStartVNode, newEndVNode, container); container.insertBefore( oldStartVNode.el, oldEndVNode.el.nextSibling ); // 更新索引,指向下一个位置 oldStartVNode = c1[++oldStartIdx]; newEndVNode = c2[--newEndIdx]; } else if (oldEndVNode.key === newStartVNode.key) { // 4: 尾头比较 patch(oldEndVNode, newStartVNode, container); container.insertBefore(oldEndVNode.el, oldStartVNode.el); oldEndVNode = c1[--oldEndIdx]; newStartVNode = c2[++newStartIdx]; } else { // 5: 比较失败 遍历旧 children,试图寻找与 newStartVNode 拥有相同 key 值的元素 const index = c1.findIndex( (node) => node.key === newStartVNode.key ); if (index > 0) { // 找到了,则这个节点需要被移动 const vnodeToMove = c1[index]; // 调用 patch 函数完成更新 patch(vnodeToMove, newStartVNode, container); // 把 vnodeToMove.el 移动到最前面,即 oldStartVNode.el 的前面 container.insertBefore(vnodeToMove.el, oldStartVNode.el); // 由于旧 children 中该位置的节点所对应的真实 DOM 已经被移动,所以将其设置为 undefined c1[index] = undefined; } else { // 没找到就挂载新节点 mount( newStartVNode, container, oldStartVNode?.instance?.parent || null, oldStartVNode.el ); } // 将 newStartIdx 下移一位 newStartVNode = c2[++newStartIdx]; } if (oldEndIdx < oldStartIdx) { // 添加新节点 for (let i = newStartIdx; i <= newEndIdx; i++) { mount(c2[i], container, null, oldStartVNode?.el || null); } } else if (newEndIdx < newStartIdx) { // 移除操作 for (let i = oldStartIdx; i <= oldEndIdx; i++) { replaceVNode(c1[i], null, container); } } } } } // 元素 // 组件 }; return { mount, patch, }; }