!136 test(novdom): render and event
* chore: fix some lint * test(novdom): render and event
This commit is contained in:
parent
05f11d2c35
commit
62614d2dd5
|
@ -4,11 +4,15 @@
|
||||||
"description": "no vdom runtime",
|
"description": "no vdom runtime",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest --ui"
|
"test": "vitest --ui",
|
||||||
|
"bench": "vitest bench"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"inula-reactive": "workspace:^0.0.1",
|
"inula-reactive": "workspace:^0.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
|
"@testing-library/user-event": "^12.1.10",
|
||||||
"@vitest/ui": "^0.34.5",
|
"@vitest/ui": "^0.34.5",
|
||||||
"vitest": "^1.2.2"
|
"vitest": "^1.2.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,10 @@ export function createComponent<T>(Comp: ComponentConstructor<T>, props: T = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function render(codeFn: CodeFunction, element: HTMLElement): () => void {
|
export function render(codeFn: CodeFunction, element: HTMLElement): () => void {
|
||||||
|
if (!element) {
|
||||||
|
throw new Error('Render target is not provided');
|
||||||
|
}
|
||||||
|
|
||||||
const disposer = (): void => {
|
const disposer = (): void => {
|
||||||
// TODO
|
// TODO
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isReactiveObj, ReactiveObj, watch } from 'inula-reactive';
|
import { watch } from 'inula-reactive';
|
||||||
|
import { isReactiveObj } from 'inula-reactive';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a function that returns a Node created from the provided HTML string.
|
* Creates a function that returns a Node created from the provided HTML string.
|
||||||
|
@ -21,26 +22,32 @@ import { isReactiveObj, ReactiveObj, watch } from 'inula-reactive';
|
||||||
* @returns {() => Node} A function that returns a Node created from the provided HTML string.
|
* @returns {() => Node} A function that returns a Node created from the provided HTML string.
|
||||||
*/
|
*/
|
||||||
export function template(html: string): () => Node {
|
export function template(html: string): () => Node {
|
||||||
|
let node: Node | null;
|
||||||
const create = (): Node => {
|
const create = (): Node => {
|
||||||
const t = document.createElement('template');
|
const t = document.createElement('template');
|
||||||
t.innerHTML = html;
|
t.innerHTML = html;
|
||||||
return t.content.firstChild as Node;
|
return t.content.firstChild;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (): Node => create();
|
return function () {
|
||||||
|
if (!node) {
|
||||||
|
node = create();
|
||||||
|
}
|
||||||
|
return node.cloneNode(true);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insert(parent: Node, accessor: ReactiveObj<any> | any, marker?: Node, initial?: any[]): any {
|
export function insert(parent: Node, maybeSignal: any, marker?: Node, initial?: any[]): any {
|
||||||
if (marker !== undefined && !initial) {
|
if (marker !== undefined && !initial) {
|
||||||
initial = [];
|
initial = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isReactiveObj(accessor)) {
|
if (isReactiveObj(maybeSignal)) {
|
||||||
watchRender((current: any) => {
|
watchRender((current: any) => {
|
||||||
return insertExpression(parent, accessor.get(), current, marker);
|
return insertExpression(parent, maybeSignal.get(), current, marker);
|
||||||
}, initial);
|
}, initial);
|
||||||
} else {
|
} else {
|
||||||
return insertExpression(parent, accessor, initial, marker);
|
return insertExpression(parent, maybeSignal, initial, marker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,10 +168,10 @@ function appendNodes(parent: Node, array: Node[], marker: Node | null = null): v
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拆解数组,如:[[a, b], [c, d], ...] to [a, b, c, d]
|
// 拆解数组,如:[[a, b], [c, d], ...] to [a, b, c, d]
|
||||||
function normalizeIncomingArray(normalized: Node[], array: any[], unwrap?: boolean): boolean {
|
function normalizeIncomingArray(normalized: Node[], array: any[]): boolean {
|
||||||
let dynamic = false;
|
let dynamic = false;
|
||||||
for (let i = 0, len = array.length; i < len; i++) {
|
for (let i = 0, len = array.length; i < len; i++) {
|
||||||
let item = array[i];
|
const item = array[i];
|
||||||
let t: string;
|
let t: string;
|
||||||
if (item == null || item === true || item === false) {
|
if (item == null || item === true || item === false) {
|
||||||
// matches null, undefined, true or false
|
// matches null, undefined, true or false
|
||||||
|
@ -174,15 +181,8 @@ function normalizeIncomingArray(normalized: Node[], array: any[], unwrap?: boole
|
||||||
} else if ((t = typeof item) === 'string' || t === 'number') {
|
} else if ((t = typeof item) === 'string' || t === 'number') {
|
||||||
normalized.push(document.createTextNode(item));
|
normalized.push(document.createTextNode(item));
|
||||||
} else if (t === 'function') {
|
} else if (t === 'function') {
|
||||||
if (unwrap) {
|
normalized.push(item);
|
||||||
while (typeof item === 'function') {
|
dynamic = true;
|
||||||
item = item();
|
|
||||||
}
|
|
||||||
dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item]) || dynamic;
|
|
||||||
} else {
|
|
||||||
normalized.push(item);
|
|
||||||
dynamic = true;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
normalized.push(item);
|
normalized.push(item);
|
||||||
}
|
}
|
||||||
|
@ -192,13 +192,13 @@ function normalizeIncomingArray(normalized: Node[], array: any[], unwrap?: boole
|
||||||
|
|
||||||
// 原本有节点,现在也有节点
|
// 原本有节点,现在也有节点
|
||||||
export default function reconcileArrays(parentNode: Node, oldChildren: Node[], newChildren: Node[]): void {
|
export default function reconcileArrays(parentNode: Node, oldChildren: Node[], newChildren: Node[]): void {
|
||||||
let nLength = newChildren.length,
|
const nLength = newChildren.length;
|
||||||
oEnd = oldChildren.length,
|
let oEnd = oldChildren.length;
|
||||||
nEnd = nLength,
|
let nEnd = nLength;
|
||||||
oStart = 0,
|
let oStart = 0;
|
||||||
nStart = 0,
|
let nStart = 0;
|
||||||
after = oldChildren[oEnd - 1].nextSibling,
|
const after = oldChildren[oEnd - 1].nextSibling;
|
||||||
map = null;
|
let map = null;
|
||||||
|
|
||||||
while (oStart < oEnd || nStart < nEnd) {
|
while (oStart < oEnd || nStart < nEnd) {
|
||||||
// 从前到后对比相同内容
|
// 从前到后对比相同内容
|
||||||
|
@ -290,10 +290,59 @@ export function setAttribute(node: Element, name: string, value: string | null):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function className(node: Element, value: string | null): void {
|
export function className(node: Element, value: string | Record<string, boolean> | null): void {
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
node.removeAttribute('class');
|
node.removeAttribute('class');
|
||||||
} else {
|
} else {
|
||||||
|
// value is an array, like ['active', 'text-red']
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
node.className = value.join(' ');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// value is a object, like { active: true, 'text-red': false }
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
let className = '';
|
||||||
|
for (const key in value) {
|
||||||
|
if (value[key]) {
|
||||||
|
className += key + ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.className = className.trim();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// or value is a string
|
||||||
node.className = value;
|
node.className = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const effect = watch;
|
||||||
|
|
||||||
|
export function style(
|
||||||
|
node: HTMLElement,
|
||||||
|
value: Record<string, string> | string | null,
|
||||||
|
prevVal?: Record<string, string> | string
|
||||||
|
): void {
|
||||||
|
if (!value && prevVal) {
|
||||||
|
// remove all styles
|
||||||
|
setAttribute(node, 'style', null);
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
node.style.cssText = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse the previous style object and remove properties that are not in the new style object
|
||||||
|
if (typeof prevVal === 'object') {
|
||||||
|
for (const key in prevVal) {
|
||||||
|
if (value == null || !(key in value)) {
|
||||||
|
node.style[key] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Traverse the new style object and set the properties
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
for (const key in value) {
|
||||||
|
node.style[key] = value[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const $$EVENTS = "_$DX_DELEGATE";
|
const $$EVENTS = '_$DX_DELEGATE';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在 document上注册事件
|
* 在 document上注册事件
|
||||||
|
@ -34,34 +34,33 @@ export function delegateEvents(eventNames: string[], document: Document = window
|
||||||
export function clearDelegatedEvents(document: Document = window.document): void {
|
export function clearDelegatedEvents(document: Document = window.document): void {
|
||||||
const events: Set<string> | undefined = document[$$EVENTS];
|
const events: Set<string> | undefined = document[$$EVENTS];
|
||||||
if (events) {
|
if (events) {
|
||||||
for (let name of events.keys()) document.removeEventListener(name, eventHandler);
|
for (const name of events.keys()) document.removeEventListener(name, eventHandler);
|
||||||
delete document[$$EVENTS];
|
delete document[$$EVENTS];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function eventHandler(e: Event) {
|
function eventHandler(e: Event) {
|
||||||
const key = `$$${e.type}`;
|
const key = `$$${e.type}`;
|
||||||
let node: EventTarget & Element = (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,
|
||||||
value: node
|
value: node,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Object.defineProperty(e, "currentTarget", {
|
Object.defineProperty(e, 'currentTarget', {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
get() {
|
get() {
|
||||||
return node || document;
|
return node || document;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 冒泡执行事件
|
// 冒泡执行事件
|
||||||
while (node) {
|
while (node) {
|
||||||
const handler = node[key as keyof typeof node];
|
const handler = node[key] as EventListener | undefined;
|
||||||
if (handler && !node.disabled) {
|
if (handler && !node.disabled) {
|
||||||
const data = node[`${key}Data` as keyof typeof node];
|
const data = node[`${key}Data` as keyof typeof node];
|
||||||
data !== undefined ? (handler as Function).call(node, data, e) : (handler as Function).call(node, e);
|
data !== undefined ? handler.call(node, data, e) : handler.call(node, e);
|
||||||
if (e.cancelBubble) {
|
if (e.cancelBubble) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -70,18 +69,13 @@ function eventHandler(e: Event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addEventListener(node: Element, name: string, handler: Function | [Function, any], delegate?: boolean): void {
|
export function addEventListener(node: Element, name: string, handler: EventListener, delegate?: boolean): void {
|
||||||
if (delegate) {
|
const prev = node[`$$${name}`];
|
||||||
if (Array.isArray(handler)) {
|
if (!delegate) {
|
||||||
node[`$$${name}`] = handler[0];
|
if (prev) {
|
||||||
node[`$$${name}Data`] = handler[1];
|
node.removeEventListener(name, prev);
|
||||||
} else {
|
|
||||||
node[`$$${name}`] = handler;
|
|
||||||
}
|
}
|
||||||
} else if (Array.isArray(handler)) {
|
|
||||||
const handlerFn = handler[0];
|
|
||||||
node.addEventListener(name, (handler[0] = (e: Event) => handlerFn.call(node, handler[1], e)));
|
|
||||||
} else {
|
|
||||||
node.addEventListener(name, handler);
|
node.addEventListener(name, handler);
|
||||||
}
|
}
|
||||||
|
node[`$$${name}`] = handler;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,5 +14,5 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: JSX type
|
// TODO: JSX type
|
||||||
export type FunctionComponent<Props = {}> = (props: Props) => unknown;
|
export type FunctionComponent<Props = Record<string, unknown>> = (props: Props) => unknown;
|
||||||
export type AppDisposer = () => void;
|
export type AppDisposer = () => void;
|
||||||
|
|
|
@ -0,0 +1,235 @@
|
||||||
|
/*
|
||||||
|
* 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 { bench } from 'vitest';
|
||||||
|
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 { For } from '../src/components/For';
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
|
||||||
|
bench('For', () => {
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* 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) {
|
||||||
|
const 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']);
|
||||||
|
|
||||||
|
container.querySelector('#run').click();
|
||||||
|
});
|
|
@ -12,244 +12,22 @@
|
||||||
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
|
||||||
* See the Mulan PSL v2 for more details.
|
* See the Mulan PSL v2 for more details.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-nocheck For the compiled code.
|
||||||
|
|
||||||
import { computed, reactive, watch } from 'inula-reactive';
|
import { computed, reactive, watch } from 'inula-reactive';
|
||||||
import { template as _$template, insert as _$insert, setAttribute as _$setAttribute } from '../src/dom';
|
import {
|
||||||
|
template as _$template,
|
||||||
|
insert as _$insert,
|
||||||
|
setAttribute as _$setAttribute,
|
||||||
|
} from '../src/dom';
|
||||||
import { createComponent as _$createComponent, render } from '../src/core';
|
import { createComponent as _$createComponent, render } from '../src/core';
|
||||||
import { delegateEvents as _$delegateEvents, addEventListener as _$addEventListener } from '../src/event';
|
import { delegateEvents as _$delegateEvents, addEventListener as _$addEventListener } from '../src/event';
|
||||||
import { describe, expect } from 'vitest';
|
import { describe, expect } from 'vitest';
|
||||||
import { domTest as it } from './utils';
|
import { domTest as it } from './utils';
|
||||||
import { Show } from '../src/components/Show';
|
|
||||||
import { For } from '../src/components/For';
|
import { For } from '../src/components/For';
|
||||||
|
|
||||||
describe('insertion', () => {
|
describe('For', () => {
|
||||||
it('should support placeholder', ({ container }) => {
|
|
||||||
/**
|
|
||||||
* 源码:
|
|
||||||
* const count = reactive(0);
|
|
||||||
* const CountingComponent = () => {
|
|
||||||
* return <div id="count">Count value is {count()}.</div>;
|
|
||||||
* };
|
|
||||||
*
|
|
||||||
* render(() => <CountingComponent />, container);
|
|
||||||
*/
|
|
||||||
const count = reactive(0);
|
|
||||||
|
|
||||||
// 编译后:
|
|
||||||
const _tmpl$ = /*#__PURE__*/ _$template(`<div id="count">Count value is <!>.`);
|
|
||||||
const CountingComponent = () => {
|
|
||||||
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$;
|
|
||||||
})();
|
|
||||||
};
|
|
||||||
|
|
||||||
render(() => _$createComponent(CountingComponent, {}), container);
|
|
||||||
|
|
||||||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 0<!---->.');
|
|
||||||
|
|
||||||
count.set(c => c + 1);
|
|
||||||
|
|
||||||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('test no-vdom', () => {
|
|
||||||
it('return数组,click事件', ({ container }) => {
|
|
||||||
/**
|
|
||||||
* 源码:
|
|
||||||
* 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 自定义组件', ({ container }) => {
|
|
||||||
/**
|
|
||||||
* 源码:
|
|
||||||
* 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组件', ({ container }) => {
|
|
||||||
/**
|
|
||||||
* 源码:
|
|
||||||
* 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组件', ({ container }) => {
|
it('使用For组件', ({ container }) => {
|
||||||
/**
|
/**
|
||||||
* 源码:
|
* 源码:
|
||||||
|
@ -298,9 +76,9 @@ describe('test no-vdom', () => {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 编译后:
|
// 编译后:
|
||||||
const _tmpl$ = /*#__PURE__*/ _$template(`<div>Count value is <!>.`),
|
const _tmpl$ = /*#__PURE__*/ _$template('<div>Count value is <!>.'),
|
||||||
_tmpl$2 = /*#__PURE__*/ _$template(
|
_tmpl$2 = /*#__PURE__*/ _$template(
|
||||||
`<div><div id="todos"></div><div><button id="btn">add</button></div><div><button id="btn-push">push`
|
'<div><div id="todos"></div><div><button id="btn">add</button></div><div><button id="btn-push">push'
|
||||||
);
|
);
|
||||||
const Todo = props => {
|
const Todo = props => {
|
||||||
return (() => {
|
return (() => {
|
||||||
|
@ -476,10 +254,10 @@ describe('test no-vdom', () => {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 编译后:
|
// 编译后:
|
||||||
const _tmpl$ = /*#__PURE__*/ _$template(`<tr><td class="col-md-1">`),
|
const _tmpl$ = /*#__PURE__*/ _$template('<tr><td class="col-md-1">'),
|
||||||
_tmpl$2 = /*#__PURE__*/ _$template(`<div class="col-sm-6"><button type="button">`),
|
_tmpl$2 = /*#__PURE__*/ _$template('<div class="col-sm-6"><button type="button">'),
|
||||||
_tmpl$3 = /*#__PURE__*/ _$template(
|
_tmpl$3 = /*#__PURE__*/ _$template(
|
||||||
`<div><div><div><div><h1>Horizon-reactive-novnode</h1></div><div><div></div></div></div></div><table><tbody id="tbody">`
|
'<div><div><div><div><h1>Horizon-reactive-novnode</h1></div><div><div></div></div></div></div><table><tbody id="tbody">'
|
||||||
);
|
);
|
||||||
const A = [
|
const A = [
|
||||||
'pretty',
|
'pretty',
|
||||||
|
@ -512,7 +290,7 @@ describe('test no-vdom', () => {
|
||||||
let nextId = 1;
|
let nextId = 1;
|
||||||
|
|
||||||
function buildData(count) {
|
function buildData(count) {
|
||||||
let data = new Array(count);
|
const data = new Array(count);
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
data[i] = {
|
data[i] = {
|
||||||
id: nextId++,
|
id: nextId++,
|
|
@ -4,7 +4,7 @@ import { describe, it, expect } from 'vitest';
|
||||||
describe('DOM manipulation functions', () => {
|
describe('DOM manipulation functions', () => {
|
||||||
describe('template function', () => {
|
describe('template function', () => {
|
||||||
it('should create a node from HTML string', () => {
|
it('should create a node from HTML string', () => {
|
||||||
const node = template('<div>Test</div>')();
|
const node = template('<div>Test</div>')() as HTMLDivElement;
|
||||||
expect(node.outerHTML).toBe('<div>Test</div>');
|
expect(node.outerHTML).toBe('<div>Test</div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-nocheck For the compiled code.
|
||||||
|
|
||||||
|
import { describe, expect, vi } from 'vitest';
|
||||||
|
import { domTest as it } from './utils';
|
||||||
|
import { template as _$template, effect as _$effect } from '../src/dom';
|
||||||
|
import { createComponent as _$createComponent, render } from '../src/core';
|
||||||
|
import { delegateEvents as _$delegateEvents, addEventListener as _$addEventListener } from '../src/event';
|
||||||
|
import { reactive } from 'inula-reactive';
|
||||||
|
|
||||||
|
function dispatchMouseEvent(element: HTMLElement, eventType = 'click') {
|
||||||
|
element.dispatchEvent(new MouseEvent(eventType, { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
// mock input change event
|
||||||
|
function dispatchChangeEvent(input: HTMLElement, value: string) {
|
||||||
|
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
||||||
|
nativeInputValueSetter.call(input, value);
|
||||||
|
|
||||||
|
input.dispatchEvent(new Event('change'));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('event', () => {
|
||||||
|
it('should trigger delegated and bound event', ({ container }) => {
|
||||||
|
/**
|
||||||
|
* 源码:
|
||||||
|
* const fn = vi.fn();
|
||||||
|
* const Comp = () => {
|
||||||
|
* const handler = () => fn();
|
||||||
|
* return <>
|
||||||
|
* <input id="inline-fn-change" onChange={() =>fn("bound")}/>
|
||||||
|
* <input id="var-change" onChange={handler}/>
|
||||||
|
* <input id="hoisted-var-change" onChange={fn}/>
|
||||||
|
* <button id="inline-fn-click" onClick={() =>fn("delegated")}>Click Delegated</button>
|
||||||
|
* <button id="var-click" onClick={handler}>Click Delegated</button>
|
||||||
|
* <button id="hoisted-var-click" onClick={fn}>Click Delegated</button>
|
||||||
|
* </>;
|
||||||
|
* };
|
||||||
|
*
|
||||||
|
* render(() => <CountingComponent />, container);
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 编译后:
|
||||||
|
const _tmpl$ = /*#__PURE__*/ _$template(`<input id="inline-fn-change">`),
|
||||||
|
_tmpl$2 = /*#__PURE__*/ _$template(`<input id="var-change">`),
|
||||||
|
_tmpl$3 = /*#__PURE__*/ _$template(`<input id="hoisted-var-change">`),
|
||||||
|
_tmpl$4 = /*#__PURE__*/ _$template(`<button id="inline-fn-click">Click Delegated`),
|
||||||
|
_tmpl$5 = /*#__PURE__*/ _$template(`<button id="var-click">Click Delegated`),
|
||||||
|
_tmpl$6 = /*#__PURE__*/ _$template(`<button id="hoisted-var-click">Click Delegated`);
|
||||||
|
|
||||||
|
const fn = vi.fn();
|
||||||
|
const Comp = () => {
|
||||||
|
const handler = () => fn();
|
||||||
|
return [
|
||||||
|
(() => {
|
||||||
|
const _el$ = _tmpl$();
|
||||||
|
_el$.addEventListener('change', () => fn('bound'));
|
||||||
|
return _el$;
|
||||||
|
})(),
|
||||||
|
(() => {
|
||||||
|
const _el$2 = _tmpl$2();
|
||||||
|
_el$2.addEventListener('change', handler);
|
||||||
|
return _el$2;
|
||||||
|
})(),
|
||||||
|
(() => {
|
||||||
|
const _el$3 = _tmpl$3();
|
||||||
|
_el$3.addEventListener('change', fn);
|
||||||
|
return _el$3;
|
||||||
|
})(),
|
||||||
|
(() => {
|
||||||
|
const _el$4 = _tmpl$4();
|
||||||
|
_el$4.$$click = () => fn('delegated');
|
||||||
|
return _el$4;
|
||||||
|
})(),
|
||||||
|
(() => {
|
||||||
|
const _el$5 = _tmpl$5();
|
||||||
|
_el$5.$$click = handler;
|
||||||
|
return _el$5;
|
||||||
|
})(),
|
||||||
|
(() => {
|
||||||
|
const _el$6 = _tmpl$6();
|
||||||
|
_el$6.$$click = fn;
|
||||||
|
return _el$6;
|
||||||
|
})(),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
render(() => _$createComponent(Comp), container);
|
||||||
|
_$delegateEvents(['click']);
|
||||||
|
|
||||||
|
dispatchChangeEvent(document.getElementById('inline-fn-change'), 'change');
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1);
|
||||||
|
expect(fn).toHaveBeenCalledWith('bound');
|
||||||
|
dispatchChangeEvent(document.getElementById('var-change'), 'change');
|
||||||
|
expect(fn).toHaveBeenCalledTimes(2);
|
||||||
|
dispatchChangeEvent(document.getElementById('hoisted-var-change'), 'change');
|
||||||
|
expect(fn).toHaveBeenCalledTimes(3);
|
||||||
|
dispatchMouseEvent(document.getElementById('inline-fn-click'));
|
||||||
|
expect(fn).toHaveBeenCalledTimes(4);
|
||||||
|
expect(fn).toHaveBeenCalledWith('delegated');
|
||||||
|
dispatchMouseEvent(document.getElementById('var-click'));
|
||||||
|
expect(fn).toHaveBeenCalledTimes(5);
|
||||||
|
dispatchMouseEvent(document.getElementById('hoisted-var-click'));
|
||||||
|
expect(fn).toHaveBeenCalledTimes(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
File diff suppressed because it is too large
Load Diff
|
@ -175,8 +175,9 @@ export class RNode<T = any> implements Signal<T> {
|
||||||
sameGetsIndex = 0;
|
sameGetsIndex = 0;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Run cleanups first.
|
||||||
if (this.cleanups.length) {
|
if (this.cleanups.length) {
|
||||||
this.cleanups.forEach(c => c(this._value));
|
this.cleanups.forEach(cleanup => cleanup(this._value));
|
||||||
this.cleanups = [];
|
this.cleanups = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,9 @@ export function createReactive<T extends symbol>(raw?: T): Signal<symbol>;
|
||||||
export function createReactive<T extends number | string | symbol>(raw?: T): Signal<T>;
|
export function createReactive<T extends number | string | symbol>(raw?: T): Signal<T>;
|
||||||
export function createReactive<T extends Record<any, any> | Array<any> | symbol>(raw?: T): DeepReactive<T>;
|
export function createReactive<T extends Record<any, any> | Array<any> | symbol>(raw?: T): DeepReactive<T>;
|
||||||
export function createReactive<T extends NonFunctionType>(raw: T): DeepReactive<T> | Signal<T> {
|
export function createReactive<T extends NonFunctionType>(raw: T): DeepReactive<T> | Signal<T> {
|
||||||
if (isPrimitive(raw) || raw === null || raw === undefined) {
|
// Function, Date, RegExp, null, undefined are simple signals
|
||||||
return new RNode(raw, { isSignal: true });
|
if (isPrimitive(raw) || raw === null || raw === undefined || raw instanceof Date || raw instanceof RegExp || typeof raw === 'function') {
|
||||||
|
return new RNode(raw, {isSignal: true});
|
||||||
} else {
|
} else {
|
||||||
const node = new RProxyNode(raw);
|
const node = new RProxyNode(raw);
|
||||||
return node.proxy;
|
return node.proxy;
|
||||||
|
@ -34,14 +35,14 @@ export function createReactive<T extends NonFunctionType>(raw: T): DeepReactive<
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createComputed<T extends NoArgFn>(fn: T) {
|
export function createComputed<T extends NoArgFn>(fn: T) {
|
||||||
const rNode = new RProxyNode<T>(fn, { isComputed: true });
|
const rNode = new RProxyNode<T>(fn, {isComputed: true});
|
||||||
return rNode.proxy;
|
return rNode.proxy;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createWatch<T>(fn: T) {
|
export function createWatch<T>(fn: T) {
|
||||||
const rNode = new RNode(fn, {
|
const rNode = new RNode(fn, {
|
||||||
isEffect: true,
|
isEffect: true,
|
||||||
lazy: false
|
lazy: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return rNode;
|
return rNode;
|
||||||
|
@ -57,7 +58,7 @@ export function getOrCreateChildRNode(node: RProxyNode<any>, key: string | symbo
|
||||||
if (node.isComputed && !node.parent) {
|
if (node.isComputed && !node.parent) {
|
||||||
const root = node.read();
|
const root = node.read();
|
||||||
node.root = {
|
node.root = {
|
||||||
$: root
|
$: root,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let child = node.children?.get(key);
|
let child = node.children?.get(key);
|
||||||
|
@ -74,7 +75,7 @@ export function getOrCreateChildRNode(node: RProxyNode<any>, key: string | symbo
|
||||||
parent: node,
|
parent: node,
|
||||||
key: key,
|
key: key,
|
||||||
root: node.root,
|
root: node.root,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,8 +51,7 @@ export type KEY = string | symbol;
|
||||||
/**
|
/**
|
||||||
* RProxyNode
|
* RProxyNode
|
||||||
* @description
|
* @description
|
||||||
* RProxyNode is a proxy of RNode, it's used to create a reactive object.
|
* An agent between Proxy and RNode, the createReactive will return the proxy.
|
||||||
* It's agent between Proxy and RNode, the createReactive will return the proxy.
|
|
||||||
* When create a RProxyNode, it will create a signal as the root of the reactive object.
|
* When create a RProxyNode, it will create a signal as the root of the reactive object.
|
||||||
* When accessing the properties of the proxy, the RProxyNode will be created as the derived child of the root signal.
|
* When accessing the properties of the proxy, the RProxyNode will be created as the derived child of the root signal.
|
||||||
* And every layer of the RProxyNode will be created as the derived child of the parent RProxyNode.
|
* And every layer of the RProxyNode will be created as the derived child of the parent RProxyNode.
|
||||||
|
|
Loading…
Reference in New Issue