!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
This commit is contained in:
parent
85a779c187
commit
332bd7ae47
|
@ -13,14 +13,21 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { insert } from './dom';
|
import {insert} from './dom';
|
||||||
|
|
||||||
export function createComponent<T>(Comp, props) {
|
type ComponentConstructor<T> = (props: T) => any;
|
||||||
|
type CodeFunction = () => any;
|
||||||
|
type InitFunction = () => void;
|
||||||
|
type Options = Record<string, any>;
|
||||||
|
type RootFunction = () => () => void;
|
||||||
|
type UpdateFunction = () => any;
|
||||||
|
|
||||||
|
export function createComponent<T>(Comp: ComponentConstructor<T>, props?: T): any {
|
||||||
return Comp(props || ({} as T));
|
return Comp(props || ({} as T));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function render(code, element, init, options = {}) {
|
export function render(code: CodeFunction, element: HTMLElement, init: InitFunction): () => void {
|
||||||
let disposer;
|
let disposer: () => void;
|
||||||
|
|
||||||
createRoot(dispose => {
|
createRoot(dispose => {
|
||||||
disposer = dispose;
|
disposer = dispose;
|
||||||
|
@ -37,10 +44,10 @@ export function render(code, element, init, options = {}) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let Owner;
|
let Owner: any;
|
||||||
let Listener;
|
let Listener: any;
|
||||||
|
|
||||||
function createRoot(fn) {
|
function createRoot(fn: RootFunction): any {
|
||||||
const listener = Listener;
|
const listener = Listener;
|
||||||
const owner = Owner;
|
const owner = Owner;
|
||||||
const unowned = fn.length === 0;
|
const unowned = fn.length === 0;
|
||||||
|
@ -52,8 +59,8 @@ function createRoot(fn) {
|
||||||
owner: current,
|
owner: current,
|
||||||
};
|
};
|
||||||
const updateFn = () => {
|
const updateFn = () => {
|
||||||
// fn(() => cleanNode(root));
|
fn(() => {
|
||||||
fn(() => {});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Owner = root;
|
Owner = root;
|
||||||
|
@ -66,10 +73,11 @@ function createRoot(fn) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let Updates, Effects;
|
let Updates: UpdateFunction[] | null;
|
||||||
|
let Effects: any[] | null;
|
||||||
let ExecCount = 0;
|
let ExecCount = 0;
|
||||||
|
|
||||||
function runUpdates(fn, init) {
|
function runUpdates(fn: UpdateFunction, init: boolean): any {
|
||||||
if (Updates) return fn();
|
if (Updates) return fn();
|
||||||
let wait = false;
|
let wait = false;
|
||||||
if (!init) Updates = [];
|
if (!init) Updates = [];
|
||||||
|
@ -79,13 +87,6 @@ function runUpdates(fn, init) {
|
||||||
Effects = [];
|
Effects = [];
|
||||||
}
|
}
|
||||||
ExecCount++;
|
ExecCount++;
|
||||||
// try {
|
const res = fn();
|
||||||
const res = fn();
|
return res;
|
||||||
// completeUpdates(wait);
|
|
||||||
return res;
|
|
||||||
// } catch (err) {
|
|
||||||
// if (!wait) Effects = null;
|
|
||||||
// Updates = null;
|
|
||||||
// // handleError(err);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,28 +14,28 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { watch } from 'inula-reactive';
|
import { watch } from 'inula-reactive';
|
||||||
import { isReactiveObj } from 'inula-reactive';
|
import { isReactiveObj, ReactiveObj } from 'inula-reactive';
|
||||||
|
|
||||||
export function template(html) {
|
export function template(html: string): () => Node {
|
||||||
let node;
|
let node: Node | null;
|
||||||
const create = () => {
|
const create = (): Node => {
|
||||||
const t = document.createElement('template');
|
const t = document.createElement('template');
|
||||||
t.innerHTML = html;
|
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;
|
fn.cloneNode = fn;
|
||||||
return fn;
|
return fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insert(parent, accessor, marker, initial) {
|
export function insert(parent: Node, accessor: ReactiveObj<any> | any, marker?: Node, initial?: any[]): any {
|
||||||
if (marker !== undefined && !initial) {
|
if (marker !== undefined && !initial) {
|
||||||
initial = [];
|
initial = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isReactiveObj(accessor)) {
|
if (isReactiveObj(accessor)) {
|
||||||
watchRender(current => {
|
watchRender((current: any) => {
|
||||||
return insertExpression(parent, accessor.get(), current, marker);
|
return insertExpression(parent, accessor.get(), current, marker);
|
||||||
}, initial);
|
}, initial);
|
||||||
} else {
|
} 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;
|
let nextValue = prevValue;
|
||||||
watch(() => {
|
watch(() => {
|
||||||
nextValue = fn(nextValue);
|
nextValue = fn(nextValue);
|
||||||
|
@ -124,9 +124,10 @@ function insertExpression(parent, value, current, marker, unwrapArray) {
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanChildren(parent, current, marker, replacement) {
|
function cleanChildren(parent: Node, current: Node[], marker?: Node, replacement?: Node): Node[] {
|
||||||
if (marker === undefined) {
|
if (marker === undefined) {
|
||||||
return (parent.textContent = '');
|
parent.textContent = '';
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const node = replacement || document.createTextNode('');
|
const node = replacement || document.createTextNode('');
|
||||||
|
@ -152,7 +153,7 @@ function cleanChildren(parent, current, marker, replacement) {
|
||||||
return [node];
|
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++) {
|
for (let i = 0, len = array.length; i < len; i++) {
|
||||||
parent.insertBefore(array[i], marker);
|
parent.insertBefore(array[i], marker);
|
||||||
}
|
}
|
||||||
|
@ -173,7 +174,9 @@ function normalizeIncomingArray(normalized, array, unwrap) {
|
||||||
normalized.push(document.createTextNode(item));
|
normalized.push(document.createTextNode(item));
|
||||||
} else if (t === 'function') {
|
} else if (t === 'function') {
|
||||||
if (unwrap) {
|
if (unwrap) {
|
||||||
while (typeof item === 'function') item = item();
|
while (typeof item === 'function') {
|
||||||
|
item = item();
|
||||||
|
}
|
||||||
dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item]) || dynamic;
|
dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item]) || dynamic;
|
||||||
} else {
|
} else {
|
||||||
normalized.push(item);
|
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,
|
let nLength = newChildren.length,
|
||||||
oEnd = oldChildren.length,
|
oEnd = oldChildren.length,
|
||||||
nEnd = nLength,
|
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) {
|
if (value == null) {
|
||||||
node.removeAttribute(name);
|
node.removeAttribute(name);
|
||||||
} else {
|
} 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) {
|
if (value == null) {
|
||||||
node.removeAttribute('class');
|
node.removeAttribute('class');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -17,11 +17,11 @@ const $$EVENTS = "_$DX_DELEGATE";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在 document上注册事件
|
* 在 document上注册事件
|
||||||
* @param eventNames
|
* @param eventNames 事件名称数组
|
||||||
* @param document
|
* @param document 默认为window.document,可以传入其他document对象
|
||||||
*/
|
*/
|
||||||
export function delegateEvents(eventNames, document = window.document) {
|
export function delegateEvents(eventNames: string[], document: Document = window.document): void {
|
||||||
const e = document[$$EVENTS] || (document[$$EVENTS] = new Set());
|
const e: Set<string> = document[$$EVENTS] || (document[$$EVENTS] = new Set());
|
||||||
for (let i = 0, l = eventNames.length; i < l; i++) {
|
for (let i = 0, l = eventNames.length; i < l; i++) {
|
||||||
const name = eventNames[i];
|
const name = eventNames[i];
|
||||||
if (!e.has(name)) {
|
if (!e.has(name)) {
|
||||||
|
@ -30,17 +30,19 @@ export function delegateEvents(eventNames, document = window.document) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export function clearDelegatedEvents(document = window.document) {
|
|
||||||
if (document[$$EVENTS]) {
|
export function clearDelegatedEvents(document: Document = window.document): void {
|
||||||
for (let name of document[$$EVENTS].keys()) document.removeEventListener(name, eventHandler);
|
const events: Set<string> | undefined = document[$$EVENTS];
|
||||||
|
if (events) {
|
||||||
|
for (let name of events.keys()) document.removeEventListener(name, eventHandler);
|
||||||
delete document[$$EVENTS];
|
delete document[$$EVENTS];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function eventHandler(e) {
|
function eventHandler(e: Event) {
|
||||||
const key = `$$${e.type}`;
|
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) {
|
if (e.target !== node) {
|
||||||
Object.defineProperty(e, "target", {
|
Object.defineProperty(e, "target", {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
@ -56,19 +58,19 @@ function eventHandler(e) {
|
||||||
|
|
||||||
// 冒泡执行事件
|
// 冒泡执行事件
|
||||||
while (node) {
|
while (node) {
|
||||||
const handler = node[key];
|
const handler = node[key as keyof typeof node];
|
||||||
if (handler && !node.disabled) {
|
if (handler && !node.disabled) {
|
||||||
const data = node[`${key}Data`];
|
const data = node[`${key}Data` as keyof typeof node];
|
||||||
data !== undefined ? handler.call(node, data, e) : handler.call(node, e);
|
data !== undefined ? (handler as Function).call(node, data, e) : (handler as Function).call(node, e);
|
||||||
if (e.cancelBubble) {
|
if (e.cancelBubble) {
|
||||||
return;
|
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 (delegate) {
|
||||||
if (Array.isArray(handler)) {
|
if (Array.isArray(handler)) {
|
||||||
node[`$$${name}`] = handler[0];
|
node[`$$${name}`] = handler[0];
|
||||||
|
@ -78,7 +80,7 @@ export function addEventListener(node, name, handler, delegate) {
|
||||||
}
|
}
|
||||||
} else if (Array.isArray(handler)) {
|
} else if (Array.isArray(handler)) {
|
||||||
const handlerFn = handler[0];
|
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 {
|
} else {
|
||||||
node.addEventListener(name, handler);
|
node.addEventListener(name, handler);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <div id="count">Count value is {count()}.</div>;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* render(() => <CountingComponent />, container);
|
||||||
|
*/
|
||||||
|
|
||||||
|
let g_count;
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const _tmpl$ = /*#__PURE__*/ _$template(`<div id="count">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 <>
|
||||||
|
* <div id="count">Count value is {count()}.</div>
|
||||||
|
* <div><button onClick={add}>add</button></div>
|
||||||
|
* </>;
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const _tmpl$ = /*#__PURE__*/ _$template(`<div id="count">Count value is <!>.`),
|
||||||
|
_tmpl$2 = /*#__PURE__*/ _$template(`<div><button id="btn">add`);
|
||||||
|
const CountingComponent = () => {
|
||||||
|
const count = reactive(0);
|
||||||
|
const add = () => {
|
||||||
|
count.set(c => c + 1);
|
||||||
|
};
|
||||||
|
return [
|
||||||
|
(() => {
|
||||||
|
const _el$ = _tmpl$(),
|
||||||
|
_el$2 = _el$.firstChild,
|
||||||
|
_el$4 = _el$2.nextSibling,
|
||||||
|
_el$3 = _el$4.nextSibling;
|
||||||
|
_$insert(_el$, count, _el$4);
|
||||||
|
return _el$;
|
||||||
|
})(),
|
||||||
|
(() => {
|
||||||
|
const _el$5 = _tmpl$2(),
|
||||||
|
_el$6 = _el$5.firstChild;
|
||||||
|
_el$6.$$click = add;
|
||||||
|
return _el$5;
|
||||||
|
})(),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
render(() => _$createComponent(CountingComponent, {}), container);
|
||||||
|
|
||||||
|
_$delegateEvents(['click']);
|
||||||
|
|
||||||
|
container.querySelector('#btn').click();
|
||||||
|
|
||||||
|
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('return 自定义组件', () => {
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const CountValue = (props) => {
|
||||||
|
* return <div>Count value is {props.count} .</div>;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const CountingComponent = () => {
|
||||||
|
* const [count, setCount] = createSignal(0);
|
||||||
|
* const add = () => {
|
||||||
|
* setCount((c) => c + 1);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* return <div>
|
||||||
|
* <CountValue count={count} />
|
||||||
|
* <div><button onClick={add}>add</button></div>
|
||||||
|
* </div>;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* render(() => <CountingComponent />, document.getElementById("app"));
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const _tmpl$ = /*#__PURE__*/ _$template(`<div id="count">Count value is <!>.`),
|
||||||
|
_tmpl$2 = /*#__PURE__*/ _$template(`<div><div><button id="btn">add`);
|
||||||
|
const CountValue = props => {
|
||||||
|
return (() => {
|
||||||
|
const _el$ = _tmpl$(),
|
||||||
|
_el$2 = _el$.firstChild,
|
||||||
|
_el$4 = _el$2.nextSibling,
|
||||||
|
_el$3 = _el$4.nextSibling;
|
||||||
|
_$insert(_el$, () => props.count, _el$4);
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
const CountingComponent = () => {
|
||||||
|
const count = reactive(0);
|
||||||
|
const add = () => {
|
||||||
|
count.set(c => c + 1);
|
||||||
|
};
|
||||||
|
return (() => {
|
||||||
|
const _el$5 = _tmpl$2(),
|
||||||
|
_el$6 = _el$5.firstChild,
|
||||||
|
_el$7 = _el$6.firstChild;
|
||||||
|
_$insert(
|
||||||
|
_el$5,
|
||||||
|
_$createComponent(CountValue, {
|
||||||
|
count: count,
|
||||||
|
}),
|
||||||
|
_el$6
|
||||||
|
);
|
||||||
|
_el$7.$$click = add;
|
||||||
|
return _el$5;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
render(() => _$createComponent(CountingComponent, {}), container);
|
||||||
|
_$delegateEvents(['click']);
|
||||||
|
|
||||||
|
container.querySelector('#btn').click();
|
||||||
|
|
||||||
|
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('使用Show组件', () => {
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const CountValue = (props) => {
|
||||||
|
* return <div id="count">Count value is {props.count()}.</div>;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const CountingComponent = () => {
|
||||||
|
* const [count, setCount] = createSignal(0);
|
||||||
|
* const add = () => {
|
||||||
|
* setCount((c) => c + 1);
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* return <div>
|
||||||
|
* <Show when={count() > 0} fallback={<CountValue count={999} />}>
|
||||||
|
* <CountValue count={count} />
|
||||||
|
* </Show>
|
||||||
|
* <div><button id="btn" onClick={add}>add</button></div>
|
||||||
|
* </div>;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* render(() => <CountingComponent />, document.getElementById("app"));
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const _tmpl$ = /*#__PURE__*/ _$template(`<div id="count">Count value is <!>.`),
|
||||||
|
_tmpl$2 = /*#__PURE__*/ _$template(`<div><div><button id="btn">add`);
|
||||||
|
const CountValue = props => {
|
||||||
|
return (() => {
|
||||||
|
const _el$ = _tmpl$(),
|
||||||
|
_el$2 = _el$.firstChild,
|
||||||
|
_el$4 = _el$2.nextSibling,
|
||||||
|
_el$3 = _el$4.nextSibling;
|
||||||
|
_$insert(_el$, () => props.count, _el$4);
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
const CountingComponent = () => {
|
||||||
|
const count = reactive(0);
|
||||||
|
const add = () => {
|
||||||
|
count.set(c => c + 1);
|
||||||
|
};
|
||||||
|
return (() => {
|
||||||
|
const _el$5 = _tmpl$2(),
|
||||||
|
_el$6 = _el$5.firstChild,
|
||||||
|
_el$7 = _el$6.firstChild;
|
||||||
|
_$insert(
|
||||||
|
_el$5,
|
||||||
|
_$createComponent(Show, {
|
||||||
|
get if() {
|
||||||
|
return computed(() => count.get() > 0);
|
||||||
|
},
|
||||||
|
get else() {
|
||||||
|
return _$createComponent(CountValue, {
|
||||||
|
count: 999,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
get children() {
|
||||||
|
return _$createComponent(CountValue, {
|
||||||
|
count: count,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
_el$6
|
||||||
|
);
|
||||||
|
_el$7.$$click = add;
|
||||||
|
return _el$5;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
render(() => _$createComponent(CountingComponent, {}), container);
|
||||||
|
_$delegateEvents(['click']);
|
||||||
|
|
||||||
|
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 999<!---->.');
|
||||||
|
|
||||||
|
container.querySelector('#btn').click();
|
||||||
|
|
||||||
|
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('使用For组件', () => {
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const Todo = (props) => {
|
||||||
|
* return <div>Count value is {props.todo.title}.</div>;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const CountingComponent = () => {
|
||||||
|
* const [state, setState] = createStore({
|
||||||
|
* counter: 2,
|
||||||
|
* todoList: [
|
||||||
|
* { id: 23, title: 'Birds' },
|
||||||
|
* { id: 27, title: 'Fish' }
|
||||||
|
* ]
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* const add = () => {
|
||||||
|
* setState('todoList', () => {
|
||||||
|
* return [
|
||||||
|
* { id: 23, title: 'Birds' },
|
||||||
|
* { id: 27, title: 'Fish' },
|
||||||
|
* { id: 27, title: 'Cat' }
|
||||||
|
* ];
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const push = () => {
|
||||||
|
* state.todoList.push({
|
||||||
|
* id: 27,
|
||||||
|
* title: 'Pig',
|
||||||
|
* },);
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* return <div>
|
||||||
|
* <div id="todos">
|
||||||
|
* <For each={state.todoList}>
|
||||||
|
* {todo => <><Todo todo={todo} /><Todo todo={todo} /></>}
|
||||||
|
* </For>
|
||||||
|
* </div>
|
||||||
|
* <div><button id="btn" onClick={add}>add</button></div>
|
||||||
|
* <div><button id="btn-push" onClick={push}>push</button></div>
|
||||||
|
* </div>;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* render(() => <CountingComponent />, document.getElementById("app"));
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const _tmpl$ = /*#__PURE__*/_$template(`<div>Count value is <!>.`),
|
||||||
|
_tmpl$2 = /*#__PURE__*/_$template(`<div><div id="todos"></div><div><button id="btn">add</button></div><div><button id="btn-push">push`);
|
||||||
|
const Todo = props => {
|
||||||
|
return (() => {
|
||||||
|
const _el$ = _tmpl$(),
|
||||||
|
_el$2 = _el$.firstChild,
|
||||||
|
_el$4 = _el$2.nextSibling,
|
||||||
|
_el$3 = _el$4.nextSibling;
|
||||||
|
_$insert(_el$, () => props.todo.title, _el$4);
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
const CountingComponent = () => {
|
||||||
|
const state = reactive({
|
||||||
|
counter: 2,
|
||||||
|
todoList: [
|
||||||
|
{
|
||||||
|
id: 23,
|
||||||
|
title: 'Birds',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 27,
|
||||||
|
title: 'Fish',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const add = () => {
|
||||||
|
state.todoList.set(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: 23,
|
||||||
|
title: 'Birds',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 27,
|
||||||
|
title: 'Fish',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 27,
|
||||||
|
title: 'Cat',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const push = () => {
|
||||||
|
state.todoList.push({
|
||||||
|
id: 27,
|
||||||
|
title: 'Pig',
|
||||||
|
},);
|
||||||
|
};
|
||||||
|
return (() => {
|
||||||
|
const _el$5 = _tmpl$2(),
|
||||||
|
_el$6 = _el$5.firstChild,
|
||||||
|
_el$7 = _el$6.nextSibling,
|
||||||
|
_el$8 = _el$7.firstChild,
|
||||||
|
_el$9 = _el$7.nextSibling,
|
||||||
|
_el$10 = _el$9.firstChild;
|
||||||
|
_$insert(
|
||||||
|
_el$6,
|
||||||
|
_$createComponent(For, {
|
||||||
|
get each() {
|
||||||
|
return state.todoList;
|
||||||
|
},
|
||||||
|
children: todo => [
|
||||||
|
_$createComponent(Todo, {
|
||||||
|
todo: todo,
|
||||||
|
}),
|
||||||
|
_$createComponent(Todo, {
|
||||||
|
todo: todo,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
_el$8.$$click = add;
|
||||||
|
_el$10.$$click = push;
|
||||||
|
return _el$5;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
render(() => _$createComponent(CountingComponent, {}), container);
|
||||||
|
_$delegateEvents(['click']);
|
||||||
|
|
||||||
|
expect(container.querySelector('#todos').innerHTML).toEqual(
|
||||||
|
'<div>Count value is Birds<!---->.</div><div>Count value is Birds<!---->.</div><div>Count value is Fish<!---->.</div><div>Count value is Fish<!---->.</div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
container.querySelector('#btn').click();
|
||||||
|
|
||||||
|
expect(container.querySelector('#todos').innerHTML).toEqual(
|
||||||
|
'<div>Count value is Birds<!---->.</div><div>Count value is Birds<!---->.</div><div>Count value is Fish<!---->.</div><div>Count value is Fish<!---->.</div><div>Count value is Cat<!---->.</div><div>Count value is Cat<!---->.</div>'
|
||||||
|
);
|
||||||
|
|
||||||
|
container.querySelector('#btn-push').click();
|
||||||
|
|
||||||
|
expect(container.querySelector('#todos').innerHTML).toEqual(
|
||||||
|
'<div>Count value is Birds<!---->.</div><div>Count value is Birds<!---->.</div><div>Count value is Fish<!---->.</div><div>Count value is Fish<!---->.</div><div>Count value is Cat<!---->.</div><div>Count value is Cat<!---->.</div><div>Count value is Pig<!---->.</div><div>Count value is Pig<!---->.</div>'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('使用effect, setAttribute, addEventListener', () => {
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const A = ['pretty', 'large', 'big', 'small', 'tall', 'short', 'long', 'handsome', 'plain', 'quaint', 'clean',
|
||||||
|
* 'elegant', 'easy', 'angry', 'crazy', 'helpful', 'mushy', 'odd', 'unsightly', 'adorable', 'important', 'inexpensive',
|
||||||
|
* 'cheap', 'expensive', 'fancy'];
|
||||||
|
*
|
||||||
|
* const random = (max: any) => Math.round(Math.random() * 1000) % max;
|
||||||
|
*
|
||||||
|
* let nextId = 1;
|
||||||
|
*
|
||||||
|
* function buildData(count: number) {
|
||||||
|
* let data = new Array(count);
|
||||||
|
*
|
||||||
|
* for (let i = 0; i < count; i++) {
|
||||||
|
* data[i] = {
|
||||||
|
* id: nextId++,
|
||||||
|
* label: `${A[random(A.length)]}`,
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* return data;
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* const Row = (props) => {
|
||||||
|
* const selected = createMemo(() => {
|
||||||
|
* return props.item.selected ? 'danger' : '';
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* return (
|
||||||
|
* <tr class={selected()}>
|
||||||
|
* <td class="col-md-1">{props.item.label}</td>
|
||||||
|
* </tr>
|
||||||
|
* )
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* const RowList = (props) => {
|
||||||
|
* return <For each={props.list}>
|
||||||
|
* {(item) => <Row item={item}/>}
|
||||||
|
* </For>;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* const Button = (props) => (
|
||||||
|
* <div class="col-sm-6">
|
||||||
|
* <button type="button" id={props.id} onClick={props.cb}>{props.title}</button>
|
||||||
|
* </div>
|
||||||
|
* );
|
||||||
|
*
|
||||||
|
* 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 (
|
||||||
|
* <div>
|
||||||
|
* <div>
|
||||||
|
* <div>
|
||||||
|
* <div><h1>Horizon-reactive-novnode</h1></div>
|
||||||
|
* <div>
|
||||||
|
* <div>
|
||||||
|
* <Button id="run" title="Create 1,000 rows" cb={run}/>
|
||||||
|
* </div>
|
||||||
|
* </div>
|
||||||
|
* </div>
|
||||||
|
* </div>
|
||||||
|
* <table>
|
||||||
|
* <tbody id="tbody"><RowList list={state.data}/></tbody>
|
||||||
|
* </table>
|
||||||
|
* </div>
|
||||||
|
* );
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* render(() => <Main />, document.getElementById("app"));
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const _tmpl$ = /*#__PURE__*/_$template(`<tr><td class="col-md-1">`),
|
||||||
|
_tmpl$2 = /*#__PURE__*/_$template(`<div class="col-sm-6"><button type="button">`),
|
||||||
|
_tmpl$3 = /*#__PURE__*/_$template(`<div><div><div><div><h1>Horizon-reactive-novnode</h1></div><div><div></div></div></div></div><table><tbody id="tbody">`);
|
||||||
|
const A = ['pretty', 'large', 'big', 'small', 'tall', 'short', 'long', 'handsome', 'plain', 'quaint', 'clean', 'elegant', 'easy', 'angry', 'crazy', 'helpful', 'mushy', 'odd', 'unsightly', 'adorable', 'important', 'inexpensive', 'cheap', 'expensive', 'fancy'];
|
||||||
|
const random = max => Math.round(Math.random() * 1000) % max;
|
||||||
|
let nextId = 1;
|
||||||
|
function buildData(count) {
|
||||||
|
let data = new Array(count);
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
data[i] = {
|
||||||
|
id: nextId++,
|
||||||
|
label: `${A[random(A.length)]}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
const Row = props => {
|
||||||
|
const selected = computed(() => {
|
||||||
|
return props.item.selected.get() ? "danger" : "";
|
||||||
|
});
|
||||||
|
|
||||||
|
return (() => {
|
||||||
|
const _el$ = _tmpl$(),
|
||||||
|
_el$2 = _el$.firstChild;
|
||||||
|
_$insert(_el$2, () => props.item.label);
|
||||||
|
return _el$;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
const RowList = props => {
|
||||||
|
return _$createComponent(For, {
|
||||||
|
get each() {
|
||||||
|
return props.list;
|
||||||
|
},
|
||||||
|
children: item => _$createComponent(Row, {
|
||||||
|
item: item
|
||||||
|
})
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const Button = props => (() => {
|
||||||
|
const _el$3 = _tmpl$2(),
|
||||||
|
_el$4 = _el$3.firstChild;
|
||||||
|
_$addEventListener(_el$4, "click", props.cb, true);
|
||||||
|
_$insert(_el$4, () => props.title);
|
||||||
|
watch(() => _$setAttribute(_el$4, "id", props.id));
|
||||||
|
return _el$3;
|
||||||
|
})();
|
||||||
|
const Main = () => {
|
||||||
|
const state = reactive({
|
||||||
|
list: [{
|
||||||
|
id: 1,
|
||||||
|
label: '111'
|
||||||
|
}, {
|
||||||
|
id: 2,
|
||||||
|
label: '222'
|
||||||
|
}],
|
||||||
|
num: 2
|
||||||
|
});
|
||||||
|
function run() {
|
||||||
|
state.list.set(buildData(5));
|
||||||
|
}
|
||||||
|
return (() => {
|
||||||
|
const _el$5 = _tmpl$3(),
|
||||||
|
_el$6 = _el$5.firstChild,
|
||||||
|
_el$7 = _el$6.firstChild,
|
||||||
|
_el$8 = _el$7.firstChild,
|
||||||
|
_el$9 = _el$8.nextSibling,
|
||||||
|
_el$10 = _el$9.firstChild,
|
||||||
|
_el$11 = _el$6.nextSibling,
|
||||||
|
_el$12 = _el$11.firstChild;
|
||||||
|
_$insert(_el$10, _$createComponent(Button, {
|
||||||
|
id: "run",
|
||||||
|
title: "Create 1,000 rows",
|
||||||
|
cb: run
|
||||||
|
}));
|
||||||
|
_$insert(_el$12, _$createComponent(RowList, {
|
||||||
|
get list() {
|
||||||
|
return state.list;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return _el$5;
|
||||||
|
})();
|
||||||
|
};
|
||||||
|
render(() => _$createComponent(Main, {}), container);
|
||||||
|
_$delegateEvents(["click"]);
|
||||||
|
|
||||||
|
expect(container.querySelector('#tbody').innerHTML).toEqual(
|
||||||
|
'<tr><td class="col-md-1">111</td></tr><tr><td class="col-md-1">222</td></tr>'
|
||||||
|
);
|
||||||
|
|
||||||
|
container.querySelector('#run').click();
|
||||||
|
|
||||||
|
expect(container.querySelector('#tbody').children.length).toEqual(5);
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue