diff --git a/packages/inula-novdom/src/dom.ts b/packages/inula-novdom/src/dom.ts index ff11474e332698d360049243cb33e8388a17e8f4..5a955cae05298c9361df425d6c53655edf4f8b77 100644 --- a/packages/inula-novdom/src/dom.ts +++ b/packages/inula-novdom/src/dom.ts @@ -37,118 +37,139 @@ export function template(html: string): () => Node { }; } -export function insert(parent: Node, maybeSignal: any, marker?: Node, initial?: any[]): any { - if (marker !== undefined && !initial) { +export function insert(parent: Node, maybeSignal: any, marker?: Node) { + let initial: any; + if (marker !== undefined) { initial = []; } if (isReactiveObj(maybeSignal)) { - watchRender((current: any) => { - return insertExpression(parent, maybeSignal.get(), current, marker); + watchRender((prevValue: any) => { + return insertExpression(parent, maybeSignal.get(), prevValue, marker); }, initial); } else { - return insertExpression(parent, maybeSignal, initial, marker); + insertExpression(parent, maybeSignal, initial, marker); } } -function watchRender(fn: (value: any) => any, prevValue: any): void { - let nextValue = prevValue; +function watchRender(fn: (value: any) => any, initial?: any): void { + let prevValue = initial; watch(() => { - nextValue = fn(nextValue); + prevValue = fn(prevValue); }); } -function insertExpression(parent, value, current, marker, unwrapArray) { - while (typeof current === 'function') current = current(); - if (value === current) return value; +function insertExpression(parent: Node, value: any, prevValue: any, marker?: Node): any { + let result: any; + while (typeof prevValue === 'function') { + prevValue = prevValue(); + } + + if (value === prevValue) { + return value; + } - const t = typeof value, - multi = marker !== undefined; + const t: string = typeof value; + const multi: boolean = marker !== undefined; if (t === 'string' || t === 'number') { if (t === 'number') value = value.toString(); if (multi) { - let node = current[0]; + let node: Node | Text = prevValue[0]; if (node && node.nodeType === 3) { - node.data = value; + (node as Text).data = value; } else { node = document.createTextNode(value); } - current = cleanChildren(parent, current, marker, node); + result = cleanChildren(parent, prevValue, marker, node); } else { - if (current !== '' && typeof current === 'string') { - current = parent.firstChild.data = value; - } else current = parent.textContent = value; + if (prevValue !== '' && typeof prevValue === 'string') { + result = (parent.firstChild as Text).data = value; + } else { + result = parent.textContent = value; + } } } else if (value == null || t === 'boolean') { - current = cleanChildren(parent, current, marker); + result = cleanChildren(parent, prevValue, marker); } else if (t === 'function') { // 在watch里面执行 - watch(() => { + watchRender((prev) => { let v = value(); while (isReactiveObj(v)) { v = v.get(); } + result = insertExpression(parent, v, prev, marker); + return result; + }, prevValue); - current = insertExpression(parent, v, current, marker); - }); - return () => current; + return () => result; } else if (Array.isArray(value)) { - // return [() => {}, () => {}, ...] - const array = []; - const currentArray = current && Array.isArray(current); - if (normalizeIncomingArray(array, value, current, unwrapArray)) { - watchRender(() => (current = insertExpression(parent, array, current, marker, true))); - return () => current; + // value:[() => {}, () => {}, ...] + const array: any[] = []; + const isPrevArray: boolean = prevValue && Array.isArray(prevValue); + if (flattenArray(array, value)) { + watchRender((prev) => { + result = insertExpression(parent, array, prev, marker); + return result; + }, prevValue); + + return () => result; } - if (array.length === 0) { - // 当前没有节点 - current = cleanChildren(parent, current, marker); - if (multi) return current; - } else if (currentArray) { - if (current.length === 0) { + if (array.length === 0) { // 当前没有节点 + result = cleanChildren(parent, prevValue, marker); + if (multi) { + return result; + } + } else if (isPrevArray) { + if (prevValue.length === 0) { appendNodes(parent, array, marker); // 原来没有节点 } else { - reconcileArrays(parent, current, array); // 原本有节点,现在也有节点 + reconcileArrays(parent, prevValue, array); // 原本有节点,现在也有节点 } } else { - current && cleanChildren(parent); + if (prevValue) { + parent.textContent = ''; // 原来有节点,但不是数组 + } + appendNodes(parent, array); } - current = array; - } else if (value.nodeType) { - if (Array.isArray(current)) { - if (multi) return (current = cleanChildren(parent, current, marker, value)); - cleanChildren(parent, current, null, value); - } else if (current == null || current === '' || !parent.firstChild) { + result = array; + } else if (value.nodeType) { // 是Node节点 + if (Array.isArray(prevValue)) { + if (multi) { + return cleanChildren(parent, prevValue, marker, value); + } else { + cleanChildren(parent, prevValue, null, value); + } + } else if (prevValue == null || prevValue === '' || !parent.firstChild) { parent.appendChild(value); } else { parent.replaceChild(value, parent.firstChild); } - current = value; + result = value; } - return current; + return result; } -function cleanChildren(parent: Node, current: Node[], marker?: Node, replacement?: Node): Node[] { +function cleanChildren(parent: Node, prevNodes: Node[], marker?: Node, replacement?: Node): Node[] { if (marker === undefined) { parent.textContent = ''; return []; } const node = replacement || document.createTextNode(''); - if (current.length) { + if (prevNodes.length) { let inserted = false; - for (let i = current.length - 1; i >= 0; i--) { - const el = current[i]; + for (let i = prevNodes.length - 1; i >= 0; i--) { + const el = prevNodes[i]; if (node !== el) { const isParent = el.parentNode === parent; if (!inserted && !i) { isParent ? parent.replaceChild(node, el) : parent.insertBefore(node, marker); } else { - isParent && el.remove(); + isParent && (el as ChildNode).remove(); } } else { inserted = true; @@ -168,7 +189,7 @@ function appendNodes(parent: Node, array: Node[], marker: Node | null = null): v } // 拆解数组,如:[[a, b], [c, d], ...] to [a, b, c, d] -function normalizeIncomingArray(normalized: Node[], array: any[]): boolean { +function flattenArray(normalized: Node[], array: any[]): boolean { let dynamic = false; for (let i = 0, len = array.length; i < len; i++) { const item = array[i]; @@ -177,7 +198,7 @@ function normalizeIncomingArray(normalized: Node[], array: any[]): boolean { // matches null, undefined, true or false // skip } else if (Array.isArray(item)) { - dynamic = normalizeIncomingArray(normalized, item) || dynamic; + dynamic = flattenArray(normalized, item) || dynamic; } else if ((t = typeof item) === 'string' || t === 'number') { normalized.push(document.createTextNode(item)); } else if (t === 'function') { @@ -225,7 +246,7 @@ export default function reconcileArrays(parentNode: Node, oldChildren: Node[], n // 新节点全部和新节点相同(不是完全相同, 如:旧 abefcd 新 abcd) while (oStart < oEnd) { if (!map || !map.has(oldChildren[oStart])) { - oldChildren[oStart].remove(); + (oldChildren[oStart] as ChildNode).remove(); } oStart++; } @@ -276,7 +297,7 @@ export default function reconcileArrays(parentNode: Node, oldChildren: Node[], n oStart++; } } else { - oldChildren[oStart++].remove(); + (oldChildren[oStart++] as ChildNode).remove(); } } } diff --git a/packages/inula-novdom/tests/For.bench.tsx b/packages/inula-novdom/tests/For.bench.tsx index 3771a5593d24259711dfab28f7b17f839a80be56..db2c4a3a346b56f1caa104ab5ee8b4445cd8c8ca 100644 --- a/packages/inula-novdom/tests/For.bench.tsx +++ b/packages/inula-novdom/tests/For.bench.tsx @@ -20,7 +20,7 @@ import { setAttribute as $$attr, effect as $$effect, } from '../src/dom'; -import { runComponent as $$runComponent, render as $$render } from '../src/core'; +import { runComponent as $$runComponent, render } from '../src/core'; import { delegateEvents as $$delegateEvents, addEventListener as $$on } from '../src/event'; import { For } from '../src/components/For'; @@ -100,7 +100,7 @@ bench('For', () => { * ); * }; * - * $$render(() =>
, document.getElementById("app")); + * render(() =>
, document.getElementById("app")); */ // 编译后: @@ -229,7 +229,7 @@ bench('For', () => { return _el$5; })(); }; - $$render(() => $$runComponent(Main, {}), container); + render(() => $$runComponent(Main, {}), container); $$delegateEvents(['click']); container.querySelector('#run').click(); diff --git a/packages/inula-novdom/tests/For.test.tsx b/packages/inula-novdom/tests/For.test.tsx index 46c4b94fb99dbf637954183ec7c0ee4fc10d0b91..532a39a93da89f41f7313a19f0d141957aa1a94c 100644 --- a/packages/inula-novdom/tests/For.test.tsx +++ b/packages/inula-novdom/tests/For.test.tsx @@ -22,7 +22,7 @@ import { setAttribute as $$attr, effect as $$effect, } from '../src/dom'; -import { runComponent as $$runComponent, render as $$render } from '../src/core'; +import { runComponent as $$runComponent, render } from '../src/core'; import { delegateEvents as $$delegateEvents, addEventListener as $$on } from '../src/event'; import { describe, expect } from 'vitest'; import { domTest as it } from './utils'; @@ -73,7 +73,7 @@ describe('For', () => { * ; * }; * - * $$render(() => , document.getElementById("app")); + * render(() => , document.getElementById("app")); */ // 编译后: @@ -158,7 +158,7 @@ describe('For', () => { return _el$5; })(); }; - $$render(() => $$runComponent(CountingComponent, {}), container); + render(() => $$runComponent(CountingComponent, {}), container); $$delegateEvents(['click']); expect(container.querySelector('#todos').innerHTML).toEqual( @@ -251,7 +251,7 @@ describe('For', () => { * ); * }; * - * $$render(() =>
, document.getElementById("app")); + * render(() =>
, document.getElementById("app")); */ // 编译后: @@ -380,7 +380,7 @@ describe('For', () => { return _el$5; })(); }; - $$render(() => $$runComponent(Main, {}), container); + render(() => $$runComponent(Main, {}), container); $$delegateEvents(['click']); expect(container.querySelector('#tbody').innerHTML).toEqual( diff --git a/packages/inula-novdom/tests/conditions.test.tsx b/packages/inula-novdom/tests/conditions.test.tsx index d4b1753c9486f60537556541a9f031c096b6c905..01f85abc147ef110ccbedbff970fb8e75ddff968 100644 --- a/packages/inula-novdom/tests/conditions.test.tsx +++ b/packages/inula-novdom/tests/conditions.test.tsx @@ -19,7 +19,7 @@ import { describe, expect } from 'vitest'; import { domTest as it } from './utils'; import { template as $$template, insert as $$insert } from '../src/dom'; import { Cond } from '../src/components/Cond'; -import { runComponent as $$runComponent, render as $$render } from '../src/core'; +import { runComponent as $$runComponent, render } from '../src/core'; import { reactive } from 'inula-reactive'; describe('conditions', () => { @@ -45,7 +45,7 @@ describe('conditions', () => { * * ); * } - * $$render(() => , container); + * render(() => , container); */ // 编译后: @@ -104,7 +104,7 @@ describe('conditions', () => { })(); } - $$render(() => $$runComponent(App, {}), container); + render(() => $$runComponent(App, {}), container); expect(container.innerHTML).toBe('

xxx

7 is between 5 and 10

'); change(11); @@ -130,7 +130,7 @@ describe('conditions', () => { * * ); * } - * $$render(() => , container); + * render(() => , container); */ // 编译后: @@ -169,7 +169,7 @@ describe('conditions', () => { })(); } - $$render(() => $$runComponent(App, {}), container); + render(() => $$runComponent(App, {}), container); expect(container.innerHTML).toMatchInlineSnapshot('"

xxx

"'); change(11); @@ -203,7 +203,7 @@ describe('conditions', () => { * * ); * } - * $$render(() => , container); + * render(() => , container); */ // 编译后: @@ -281,7 +281,7 @@ describe('conditions', () => { })(); } - $$render(() => $$runComponent(App, {}), container); + render(() => $$runComponent(App, {}), container); expect(container.innerHTML).toMatchInlineSnapshot('"

xxx

7 is 7 or less

"'); change(11); expect(container.innerHTML).toMatchInlineSnapshot('"

xxx

11 is greater than 10

"'); @@ -316,7 +316,7 @@ describe('conditions', () => { * * ); * } - * $$render(() => , container); + * render(() => , container); */ // 编译后: @@ -387,7 +387,7 @@ describe('conditions', () => { })(); } - $$render(() => $$runComponent(App, {}), container); + render(() => $$runComponent(App, {}), container); expect(container.innerHTML).toMatchInlineSnapshot('"

parallel

XXX

YYY

ZZZ

"'); // hide X, Y, Z randomly diff --git a/packages/inula-novdom/tests/event.test.tsx b/packages/inula-novdom/tests/event.test.tsx index f1ce7b7a727c458371855d39170d6c694115dea9..9b4900a1c13ce719cc64346c23da814fb93f957c 100644 --- a/packages/inula-novdom/tests/event.test.tsx +++ b/packages/inula-novdom/tests/event.test.tsx @@ -18,7 +18,7 @@ import { describe, expect, vi } from 'vitest'; import { domTest as it } from './utils'; import { template as $$template } from '../src/dom'; -import { runComponent as $$runComponent, render as $$render} from '../src/core'; +import { runComponent as $$runComponent, render} from '../src/core'; import { delegateEvents as $$delegateEvents, addEventListener as $$on } from '../src/event'; function dispatchMouseEvent(element: HTMLElement, eventType = 'click') { @@ -50,7 +50,7 @@ describe('event', () => { * ; * }; * - * $$render(() => , container); + * render(() => , container); */ // 编译后: @@ -97,7 +97,7 @@ describe('event', () => { })(), ]; }; - $$render(() => $$runComponent(Comp), container); + render(() => $$runComponent(Comp), container); $$delegateEvents(['click']); dispatchChangeEvent(document.getElementById('inline-fn-change'), 'change'); diff --git a/packages/inula-novdom/tests/render.test.tsx b/packages/inula-novdom/tests/render.test.tsx index a45eb5282e3b16e4ae299a7c69a717c49890f4bf..89509b807e9fef5ba0ecfb9bb0f8d0fb400c688e 100644 --- a/packages/inula-novdom/tests/render.test.tsx +++ b/packages/inula-novdom/tests/render.test.tsx @@ -23,7 +23,7 @@ import { className as $$className, setAttribute as $$attr, } from '../src/dom'; -import { runComponent as $$runComponent, render as $$render } from '../src/core'; +import { runComponent as $$runComponent, render } from '../src/core'; import { delegateEvents as $$delegateEvents, addEventListener as $$on } from '../src/event'; import { describe, expect } from 'vitest'; import { domTest as it } from './utils'; @@ -36,7 +36,7 @@ describe('render', () => { * return
Count value is 0.
; * }; * - * $$render(() => , container); + * render(() => , container); */ // 编译后: @@ -44,7 +44,7 @@ describe('render', () => { const CountingComponent = () => { return $tmpl(); }; - $$render(() => $$runComponent(CountingComponent, {}), container); + render(() => $$runComponent(CountingComponent, {}), container); expect(container.querySelector('#count').innerHTML).toEqual('Count value is 0.'); }); @@ -56,7 +56,7 @@ describe('render', () => { * return
Count value is {0}.
; * }; * - * $$render(() => , container); + * render(() => , container); */ // 编译后: @@ -70,7 +70,7 @@ describe('render', () => { return _el$; })(); }; - $$render(() => $$runComponent(CountingComponent, {}), container); + render(() => $$runComponent(CountingComponent, {}), container); expect(container.querySelector('#count').innerHTML).toEqual('Count value is 0.'); }); @@ -114,7 +114,7 @@ describe('render', () => { })(), ]; }; - $$render(() => $$runComponent(CountingComponent, {}), container); + render(() => $$runComponent(CountingComponent, {}), container); $$delegateEvents(['click']); @@ -127,22 +127,22 @@ describe('render', () => { /** * 源码: * const CountValue = (props) => { - * return
Count value is {props.count} .
; + * return
Count value is {props.count} .
; * } * * const CountingComponent = () => { * const [count, setCount] = createSignal(0); * const add = () => { - * setCount((c) => c + 1); - * } + * setCount((c) => c + 1); + * } * * return
- * + * *
- *
; + * ; * }; * - * $$render(() => , document.getElementById("app")); + * render(() => , document.getElementById("app")); */ // 编译后: @@ -177,7 +177,7 @@ describe('render', () => { return _el$5; })(); }; - $$render(() => $$runComponent(CountingComponent, {}), container); + render(() => $$runComponent(CountingComponent, {}), container); $$delegateEvents(['click']); container.querySelector('#btn').click(); @@ -285,7 +285,7 @@ describe('render', () => { * ); * }; * - * $$render(() => , document.getElementById("app")); + * render(() => , document.getElementById("app")); */ // 编译后: @@ -329,7 +329,7 @@ describe('render', () => { return _el$6; })(); }; - $$render(() => $$runComponent(CountingComponent, {}), container); + render(() => $$runComponent(CountingComponent, {}), container); $$delegateEvents(['click']); expect(container.querySelector('h1').innerHTML).toMatchInlineSnapshot('"0"'); @@ -339,7 +339,7 @@ describe('render', () => { it('should throw error when container is not valid', () => { [undefined, null, 0, 1, true, false, 'string', Symbol('symbol'), {}].forEach(container => { - expect(() => $$render(() => $$runComponent(() => null, undefined), container)).toThrowError( + expect(() => render(() => $$runComponent(() => null, undefined), container)).toThrowError( 'Render target is not valid.' ); }); @@ -352,7 +352,7 @@ describe('render', () => { * return
Count value is 0.
; * }; * - * $$render(() => , container); + * render(() => , container); */ // 编译后: @@ -360,7 +360,7 @@ describe('render', () => { const Comp = () => { return $tmpl(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').style.color).toEqual('red'); }); @@ -372,7 +372,7 @@ describe('render', () => { * const color = 'red'; * return
Count value is 0.
; * } - * $$render(() => , container); + * render(() => , container); * */ // 编译后: @@ -382,7 +382,7 @@ describe('render', () => { return $tmpl(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').style.color).toEqual('red'); }); @@ -402,7 +402,7 @@ describe('render', () => { return _el$; })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').style.color).toEqual('red'); }); @@ -425,7 +425,7 @@ describe('render', () => { })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').style.color).toEqual('red'); }); @@ -450,7 +450,7 @@ describe('render', () => { })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').style.color).toEqual('red'); container.querySelector('div').style.color = 'green'; expect(container.querySelector('div').style.color).toEqual('green'); @@ -475,7 +475,7 @@ describe('render', () => { })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').style.color).toEqual('red'); container.querySelector('div').style.color = 'green'; expect(container.querySelector('div').style.color).toEqual('green'); @@ -493,7 +493,7 @@ describe('render', () => { const Comp = () => { return $tmpl(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').className).toEqual('red'); }); @@ -515,7 +515,7 @@ describe('render', () => { return _el$; })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').className).toEqual('red'); }); @@ -537,7 +537,7 @@ describe('render', () => { return _el$; })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').className).toEqual('red'); }); @@ -557,7 +557,7 @@ describe('render', () => { return _el$; })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').className).toEqual('red green'); }); @@ -579,7 +579,7 @@ describe('render', () => { return _el$; })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').className).toEqual('red'); container.querySelector('div').className = 'green'; expect(container.querySelector('div').className).toEqual('green'); @@ -607,7 +607,7 @@ describe('render', () => { return _el$; })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').className).toEqual('red'); }); @@ -629,7 +629,7 @@ describe('render', () => { return _el$; })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').className).toEqual('red green'); }); @@ -651,7 +651,7 @@ describe('render', () => { return _el$; })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').id).toEqual('test'); }); @@ -675,7 +675,7 @@ describe('render', () => { return _el$; })(); }; - $$render(() => $$runComponent(Comp, {}), container); + render(() => $$runComponent(Comp, {}), container); expect(container.querySelector('div').id).toEqual('el'); id.set('test'); expect(container.querySelector('div').id).toEqual('test');