From 332bd7ae477d0e9c5fbf255c7181692bca9cd56b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E8=B6=85=E6=B6=9B?= <10857776+chaoling83@user.noreply.gitee.com> Date: Mon, 5 Feb 2024 06:34:00 +0000 Subject: [PATCH] !132 Merge remote-tracking branch 'origin/reactive' into reactive * Merge remote-tracking branch 'origin/reactive' into reactive * fix(no-vdom): add TS * fix(no-vdom): change js 2 ts --- packages/inula-novdom/src/core.ts | 41 +- packages/inula-novdom/src/dom.ts | 35 +- packages/inula-novdom/src/event.ts | 32 +- packages/inula-novdom/tests/no-vdom.test.ts | 586 ++++++++++++++++++++ 4 files changed, 643 insertions(+), 51 deletions(-) create mode 100644 packages/inula-novdom/tests/no-vdom.test.ts diff --git a/packages/inula-novdom/src/core.ts b/packages/inula-novdom/src/core.ts index 3d8c3c76..e4c5fc39 100644 --- a/packages/inula-novdom/src/core.ts +++ b/packages/inula-novdom/src/core.ts @@ -13,14 +13,21 @@ * See the Mulan PSL v2 for more details. */ -import { insert } from './dom'; +import {insert} from './dom'; -export function createComponent(Comp, props) { +type ComponentConstructor = (props: T) => any; +type CodeFunction = () => any; +type InitFunction = () => void; +type Options = Record; +type RootFunction = () => () => void; +type UpdateFunction = () => any; + +export function createComponent(Comp: ComponentConstructor, props?: T): any { return Comp(props || ({} as T)); } -export function render(code, element, init, options = {}) { - let disposer; +export function render(code: CodeFunction, element: HTMLElement, init: InitFunction): () => void { + let disposer: () => void; createRoot(dispose => { disposer = dispose; @@ -37,10 +44,10 @@ export function render(code, element, init, options = {}) { }; } -let Owner; -let Listener; +let Owner: any; +let Listener: any; -function createRoot(fn) { +function createRoot(fn: RootFunction): any { const listener = Listener; const owner = Owner; const unowned = fn.length === 0; @@ -52,8 +59,8 @@ function createRoot(fn) { owner: current, }; const updateFn = () => { - // fn(() => cleanNode(root)); - fn(() => {}); + fn(() => { + }); }; Owner = root; @@ -66,10 +73,11 @@ function createRoot(fn) { } } -let Updates, Effects; +let Updates: UpdateFunction[] | null; +let Effects: any[] | null; let ExecCount = 0; -function runUpdates(fn, init) { +function runUpdates(fn: UpdateFunction, init: boolean): any { if (Updates) return fn(); let wait = false; if (!init) Updates = []; @@ -79,13 +87,6 @@ function runUpdates(fn, init) { Effects = []; } ExecCount++; - // try { - const res = fn(); - // completeUpdates(wait); - return res; - // } catch (err) { - // if (!wait) Effects = null; - // Updates = null; - // // handleError(err); - // } + const res = fn(); + return res; } diff --git a/packages/inula-novdom/src/dom.ts b/packages/inula-novdom/src/dom.ts index 4b16fe64..15bada2f 100644 --- a/packages/inula-novdom/src/dom.ts +++ b/packages/inula-novdom/src/dom.ts @@ -14,28 +14,28 @@ */ import { watch } from 'inula-reactive'; -import { isReactiveObj } from 'inula-reactive'; +import { isReactiveObj, ReactiveObj } from 'inula-reactive'; -export function template(html) { - let node; - const create = () => { +export function template(html: string): () => Node { + let node: Node | null; + const create = (): Node => { const t = document.createElement('template'); t.innerHTML = html; - return t.content.firstChild; + return t.content.firstChild as Node; }; - const fn = () => (node || (node = create())).cloneNode(true); + const fn = (): Node => (node || (node = create())).cloneNode(true); fn.cloneNode = fn; return fn; } -export function insert(parent, accessor, marker, initial) { +export function insert(parent: Node, accessor: ReactiveObj | any, marker?: Node, initial?: any[]): any { if (marker !== undefined && !initial) { initial = []; } if (isReactiveObj(accessor)) { - watchRender(current => { + watchRender((current: any) => { return insertExpression(parent, accessor.get(), current, marker); }, initial); } else { @@ -43,7 +43,7 @@ export function insert(parent, accessor, marker, initial) { } } -function watchRender(fn, prevValue) { +function watchRender(fn: (value: any) => any, prevValue: any): void { let nextValue = prevValue; watch(() => { nextValue = fn(nextValue); @@ -124,9 +124,10 @@ function insertExpression(parent, value, current, marker, unwrapArray) { return current; } -function cleanChildren(parent, current, marker, replacement) { +function cleanChildren(parent: Node, current: Node[], marker?: Node, replacement?: Node): Node[] { if (marker === undefined) { - return (parent.textContent = ''); + parent.textContent = ''; + return []; } const node = replacement || document.createTextNode(''); @@ -152,7 +153,7 @@ function cleanChildren(parent, current, marker, replacement) { return [node]; } -function appendNodes(parent, array, marker = null) { +function appendNodes(parent: Node, array: Node[], marker: Node | null = null): void { for (let i = 0, len = array.length; i < len; i++) { parent.insertBefore(array[i], marker); } @@ -173,7 +174,9 @@ function normalizeIncomingArray(normalized, array, unwrap) { normalized.push(document.createTextNode(item)); } else if (t === 'function') { if (unwrap) { - while (typeof item === 'function') item = item(); + while (typeof item === 'function') { + item = item(); + } dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item]) || dynamic; } else { normalized.push(item); @@ -187,7 +190,7 @@ function normalizeIncomingArray(normalized, array, unwrap) { } // 原本有节点,现在也有节点 -export default function reconcileArrays(parentNode, oldChildren, newChildren) { +export default function reconcileArrays(parentNode: Node, oldChildren: Node[], newChildren: Node[]): void { let nLength = newChildren.length, oEnd = oldChildren.length, nEnd = nLength, @@ -278,7 +281,7 @@ export default function reconcileArrays(parentNode, oldChildren, newChildren) { } } -export function setAttribute(node, name, value) { +export function setAttribute(node: Element, name: string, value: string | null): void { if (value == null) { node.removeAttribute(name); } else { @@ -286,7 +289,7 @@ export function setAttribute(node, name, value) { } } -export function className(node, value) { +export function className(node: Element, value: string | null): void { if (value == null) { node.removeAttribute('class'); } else { diff --git a/packages/inula-novdom/src/event.ts b/packages/inula-novdom/src/event.ts index 661a6e0d..f89ef510 100644 --- a/packages/inula-novdom/src/event.ts +++ b/packages/inula-novdom/src/event.ts @@ -17,11 +17,11 @@ const $$EVENTS = "_$DX_DELEGATE"; /** * 在 document上注册事件 - * @param eventNames - * @param document + * @param eventNames 事件名称数组 + * @param document 默认为window.document,可以传入其他document对象 */ -export function delegateEvents(eventNames, document = window.document) { - const e = document[$$EVENTS] || (document[$$EVENTS] = new Set()); +export function delegateEvents(eventNames: string[], document: Document = window.document): void { + const e: Set = document[$$EVENTS] || (document[$$EVENTS] = new Set()); for (let i = 0, l = eventNames.length; i < l; i++) { const name = eventNames[i]; if (!e.has(name)) { @@ -30,17 +30,19 @@ export function delegateEvents(eventNames, document = window.document) { } } } -export function clearDelegatedEvents(document = window.document) { - if (document[$$EVENTS]) { - for (let name of document[$$EVENTS].keys()) document.removeEventListener(name, eventHandler); + +export function clearDelegatedEvents(document: Document = window.document): void { + const events: Set | undefined = document[$$EVENTS]; + if (events) { + for (let name of events.keys()) document.removeEventListener(name, eventHandler); delete document[$$EVENTS]; } } -function eventHandler(e) { +function eventHandler(e: Event) { const key = `$$${e.type}`; - let node = (e.composedPath && e.composedPath()[0]) || e.target; + let node: EventTarget & Element = (e.composedPath && e.composedPath()[0]) || e.target; if (e.target !== node) { Object.defineProperty(e, "target", { configurable: true, @@ -56,19 +58,19 @@ function eventHandler(e) { // 冒泡执行事件 while (node) { - const handler = node[key]; + const handler = node[key as keyof typeof node]; if (handler && !node.disabled) { - const data = node[`${key}Data`]; - data !== undefined ? handler.call(node, data, e) : handler.call(node, e); + const data = node[`${key}Data` as keyof typeof node]; + data !== undefined ? (handler as Function).call(node, data, e) : (handler as Function).call(node, e); if (e.cancelBubble) { return; } } - node = node._$host || node.parentNode || node.host; + node = (node as any)._$host || node.parentNode || (node as any).host; } } -export function addEventListener(node, name, handler, delegate) { +export function addEventListener(node: Element, name: string, handler: Function | [Function, any], delegate?: boolean): void { if (delegate) { if (Array.isArray(handler)) { node[`$$${name}`] = handler[0]; @@ -78,7 +80,7 @@ export function addEventListener(node, name, handler, delegate) { } } else if (Array.isArray(handler)) { const handlerFn = handler[0]; - node.addEventListener(name, (handler[0] = e => handlerFn.call(node, handler[1], e))); + node.addEventListener(name, (handler[0] = (e: Event) => handlerFn.call(node, handler[1], e))); } else { node.addEventListener(name, handler); } diff --git a/packages/inula-novdom/tests/no-vdom.test.ts b/packages/inula-novdom/tests/no-vdom.test.ts new file mode 100644 index 00000000..54de71ff --- /dev/null +++ b/packages/inula-novdom/tests/no-vdom.test.ts @@ -0,0 +1,586 @@ +/* + * Copyright (c) 2024 Huawei Technologies Co.,Ltd. + * + * openInula is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * + * http://license.coscl.org.cn/MulanPSL2 + * + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +import { computed, reactive, watch } from 'inula-reactive'; +import { template as _$template, insert as _$insert, setAttribute as _$setAttribute } from '../src/dom'; +import { createComponent as _$createComponent, render } from '../src/core'; +import { delegateEvents as _$delegateEvents, addEventListener as _$addEventListener } from '../src/event'; + +import { Show } from '../src/components/Show'; +import { For } from '../src/components/For'; + +describe('test no-vdom', () => { + it('简单的使用signal', () => { + /** + * 源码: + * const CountingComponent = () => { + * const [count, setCount] = useSignal(0); + * + * return
Count value is {count()}.
; + * }; + * + * render(() => , container); + */ + + let g_count; + + // 编译后: + const _tmpl$ = /*#__PURE__*/ _$template(`
Count value is .`); + const CountingComponent = () => { + const count = reactive(0); + g_count = count; + let View + + watch: if (count > 0) { + View.$viewValue = createView(() => {}) + } + + View = createView((() => { + const _el$ = tmp.cloneNode(true), + _el$2 = _el$.firstChild, + _el$4 = _el$2.nextSibling, + _el$3 = _el$4.nextSibling; + _$insert(_el$, count, _el$4); + return _el$; + })()); + + return View + }; + function createView(el) { + Object.defineProperty(el, '$viewValue', { + set: value => { + el.replaceWith(value); + } + }) + } + + render(() => _$createComponent(CountingComponent, {}), container); + + expect(container.querySelector('#count').innerHTML).toEqual('Count value is 0.'); + + g_count.set(c => c + 1); + + expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1.'); + }); + + it('return数组,click事件', () => { + /** + * 源码: + * const CountingComponent = () => { + * const [count, setCount] = createSignal(0); + * const add = () => { + * setCount((c) => c + 1); + * } + * return <> + *
Count value is {count()}.
+ *
+ * ; + * }; + */ + + // 编译后: + const _tmpl$ = /*#__PURE__*/ _$template(`
Count value is .`), + _tmpl$2 = /*#__PURE__*/ _$template(`
+ *
; + * }; + * + * render(() => , document.getElementById("app")); + */ + + // 编译后: + const _tmpl$ = /*#__PURE__*/ _$template(`
Count value is .`), + _tmpl$2 = /*#__PURE__*/ _$template(`
+ *
; + * }; + * + * render(() => , document.getElementById("app")); + */ + + // 编译后: + const _tmpl$ = /*#__PURE__*/ _$template(`
Count value is .`), + _tmpl$2 = /*#__PURE__*/ _$template(`
+ *
+ *
; + * }; + * + * render(() => , document.getElementById("app")); + */ + + // 编译后: + const _tmpl$ = /*#__PURE__*/_$template(`
Count value is .`), + _tmpl$2 = /*#__PURE__*/_$template(`
+ *
+ * ); + * + * const Main = () => { + * const [state, setState] = createStore({data: [{id: 1, label: '111', selected: false}, {id: 2, label: '222', selected: false}], num: 2}); + * + * function run() { + * setState('data', buildData(5)); + * } + * + * return ( + *
+ *
+ *
+ *

Horizon-reactive-novnode

+ *
+ *
+ *
+ *
+ *
+ *
+ * + * + *
+ *
+ * ); + * }; + * + * render(() =>
, document.getElementById("app")); + */ + + // 编译后: + const _tmpl$ = /*#__PURE__*/_$template(``), + _tmpl$2 = /*#__PURE__*/_$template(`