parent
80f3be9436
commit
521344f8ff
|
@ -24,15 +24,13 @@ type Branch = JSXElement | (() => JSXElement);
|
|||
|
||||
export interface CondProps {
|
||||
// Array of tuples, first item is the condition, second is the branch to render
|
||||
branches: [CondExpression, Branch];
|
||||
branches: [CondExpression, Branch][];
|
||||
}
|
||||
|
||||
export function Cond(props: CondProps) {
|
||||
// Find the first branch that matches the condition
|
||||
// Any signal that used in condition expression, will trigger the condition to recompute
|
||||
const currentBranch = computed(() => {
|
||||
// clean up the previous branch
|
||||
|
||||
for (let i = 0; i < props.branches.length; i++) {
|
||||
const [condition, branch] = props.branches[i];
|
||||
if (typeof condition === 'function' ? condition() : condition) {
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* 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 { mergeProps, splitProps } from '../util';
|
||||
import { Context, WithChildren } from '../type';
|
||||
|
||||
// Reactive scope for cleanup and context
|
||||
export interface Scope<T = Context> {
|
||||
context: T;
|
||||
}
|
||||
|
||||
// The current scope for the current render
|
||||
let scope: Scope | null = null;
|
||||
|
||||
export function runWithScope<T>(fn: () => T, context: Context): T {
|
||||
const prevScope = scope;
|
||||
// Merge the context with the previous context for nested env
|
||||
let mergedContext = context;
|
||||
if (prevScope?.context) {
|
||||
mergedContext = mergeProps(prevScope.context, context);
|
||||
}
|
||||
// Set the new scope
|
||||
scope = {
|
||||
context: mergedContext,
|
||||
};
|
||||
try {
|
||||
// Run the children with the new scope
|
||||
return fn();
|
||||
} finally {
|
||||
// Restore the previous scope
|
||||
scope = prevScope;
|
||||
}
|
||||
}
|
||||
|
||||
export function readContext() {
|
||||
return scope?.context || {};
|
||||
}
|
||||
|
||||
type EnvProps<ContextType> = WithChildren<ContextType>;
|
||||
|
||||
/**
|
||||
* @description The Env component is used to provide a context for the children components.
|
||||
* @example
|
||||
* type ThemeContext = { theme: string };
|
||||
* <env<ThemeContext> theme="dark">
|
||||
* <Comp />
|
||||
* </env>
|
||||
* When Comp is rendered, it can access the theme from the env.
|
||||
* function Comp(props, context: UserContext & ThemeContext) {
|
||||
* return <h1>{theme}</h1>
|
||||
* }
|
||||
*/
|
||||
export function Env<ContextType = Context>(props: EnvProps<ContextType>) {
|
||||
// splitProps is used to extract the children from the props.
|
||||
// because when we directly access the children, we will trigger the children getter function.
|
||||
const [childrenProp, context] = splitProps(props, ['children']);
|
||||
|
||||
return runWithScope(() => childrenProp.children, context);
|
||||
}
|
|
@ -15,12 +15,16 @@
|
|||
|
||||
import { insert } from './dom';
|
||||
import { untrack } from 'inula-reactive';
|
||||
import { readContext } from './components/Env';
|
||||
|
||||
type ComponentConstructor<T> = (props: T) => any;
|
||||
type ComponentConstructor<T> = (props: T, context: any) => any;
|
||||
type CodeFunction = () => any;
|
||||
|
||||
export function runComponent<T>(Comp: ComponentConstructor<T>, props: T = {} as T): any {
|
||||
return untrack(() => Comp(props));
|
||||
return untrack(() => {
|
||||
const context = readContext();
|
||||
return Comp(props, context);
|
||||
});
|
||||
}
|
||||
|
||||
const ELEMENT_NODE = 1;
|
||||
|
|
|
@ -15,5 +15,8 @@
|
|||
|
||||
// TODO: JSX type
|
||||
export type JSXElement = any;
|
||||
export type FunctionComponent<Props = Record<string, unknown>> = (props: Props) => unknown;
|
||||
export type FunctionComponent<P = Props> = (props: P) => unknown;
|
||||
export type AppDisposer = () => void;
|
||||
export type Props = Record<string, unknown>;
|
||||
export type Context = Props;
|
||||
export type WithChildren<T = Props> = T & { children?: JSXElement };
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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 { Props } from './type';
|
||||
|
||||
/**
|
||||
* split props into different objects, should copy getter and setter descriptor.
|
||||
* @example
|
||||
* splitProps({a: 1, get b() { return 2 }}, ['a']) // => [{a: 1}, {get b() { return 2 }}]
|
||||
*/
|
||||
export function splitProps(props: Props, keys: string[]): [Props, Props] {
|
||||
const target: Props = {};
|
||||
const others: Props = {};
|
||||
for (const propName of Object.getOwnPropertyNames(props)) {
|
||||
copyProp(props, keys.includes(propName) ? target : others, propName);
|
||||
}
|
||||
return [target, others];
|
||||
}
|
||||
|
||||
function copyProp(resource: Props, target: Props, key: string): void {
|
||||
const desc = Object.getOwnPropertyDescriptor(resource, key)!;
|
||||
if (!desc.get && !desc.set && desc.enumerable && desc.writable && desc.configurable) {
|
||||
// if the prop is a plain value, we can directly assign it to the context
|
||||
target[key] = desc.value;
|
||||
} else {
|
||||
Object.defineProperty(target, key, desc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* merge two props into one object, including getter and setter descriptor.
|
||||
* @param a
|
||||
* @param b
|
||||
* @example
|
||||
* mergeProps({get a() { return 1 }}, {get b() { return 2 }}) // => {get a() { return 1 }, get b() { return 2 }}
|
||||
*/
|
||||
export function mergeProps(a: Props, b: Props): Props {
|
||||
const target: Props = {};
|
||||
for (const propName of Object.getOwnPropertyNames(a)) {
|
||||
copyProp(a, target, propName);
|
||||
}
|
||||
for (const propName of Object.getOwnPropertyNames(b)) {
|
||||
copyProp(b, target, propName);
|
||||
}
|
||||
return target;
|
||||
}
|
|
@ -0,0 +1,412 @@
|
|||
/*
|
||||
* 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 } 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, render as $$render } from '../src/core';
|
||||
import { reactive } from 'inula-reactive';
|
||||
import { Env } from '../src/components/Env';
|
||||
|
||||
describe('env', () => {
|
||||
it('should work.', ({container}) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const fn = vi.fn();
|
||||
* function App() {
|
||||
* const x = reactive(7);
|
||||
* const theme = reactive('dark');
|
||||
* return (
|
||||
* <env theme={theme.get()}>
|
||||
* <Child />
|
||||
* </env>
|
||||
* );
|
||||
* }
|
||||
*
|
||||
* function Child(props, context) {
|
||||
* return <div>{context.theme}</div>;
|
||||
* }
|
||||
* render(() => <App />, container);
|
||||
*/
|
||||
// 编译后:
|
||||
const theme = reactive('dark');
|
||||
|
||||
function App() {
|
||||
return $$runComponent(Env, {
|
||||
get children() {
|
||||
return $$runComponent(Child);
|
||||
},
|
||||
get theme() {
|
||||
return theme.get();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const _tmpl$ = /*#__PURE__*/ $$template(`<div>`);
|
||||
|
||||
function Child(props, context) {
|
||||
return (() => {
|
||||
const _el$ = _tmpl$();
|
||||
$$insert(_el$, () => context.theme);
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
|
||||
render(() => $$runComponent(App, {}), container);
|
||||
|
||||
expect(container.innerHTML).toBe('<div>dark</div>');
|
||||
theme.set('light');
|
||||
expect(container.innerHTML).toBe('<div>light</div>');
|
||||
});
|
||||
|
||||
it('should work with nested env.', ({container}) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const fn = vi.fn();
|
||||
* function App() {
|
||||
* const theme = reactive('dark');
|
||||
* return (
|
||||
* <env theme={theme.get()}>
|
||||
* <Child />
|
||||
* </env>
|
||||
* );
|
||||
* }
|
||||
*
|
||||
* function Child(props, context) {
|
||||
* return (
|
||||
* <env theme={context.theme}>
|
||||
* <GrandChild />
|
||||
* </env>
|
||||
* );
|
||||
* }
|
||||
*
|
||||
* function GrandChild(props, context) {
|
||||
* return <div>{context.theme}</div>;
|
||||
* }
|
||||
* render(() => <App />, container);
|
||||
*/
|
||||
// 编译后:
|
||||
const theme = reactive('dark');
|
||||
|
||||
function App() {
|
||||
return $$runComponent(Env, {
|
||||
get children() {
|
||||
return $$runComponent(Child);
|
||||
},
|
||||
get theme() {
|
||||
return theme.get();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const _tmpl$2 = /*#__PURE__*/ $$template(`<div>`);
|
||||
|
||||
function Child(props, context) {
|
||||
return $$runComponent(Env, {
|
||||
get children() {
|
||||
return $$runComponent(GrandChild);
|
||||
},
|
||||
get theme() {
|
||||
return context.theme;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function GrandChild(props, context) {
|
||||
return (() => {
|
||||
const _el$ = _tmpl$2();
|
||||
$$insert(_el$, () => context.theme);
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
|
||||
render(() => $$runComponent(App, {}), container);
|
||||
|
||||
expect(container.innerHTML).toBe('<div>dark</div>');
|
||||
theme.set('light');
|
||||
expect(container.innerHTML).toBe('<div>light</div>');
|
||||
});
|
||||
|
||||
it('should merged the parent env.', ({container}) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const fn = vi.fn();
|
||||
* function App() {
|
||||
* const theme = reactive('dark');
|
||||
* return (
|
||||
* <env theme={theme.get()}>
|
||||
* <env name="inula">
|
||||
* <env userInfo={{id: 1}}>
|
||||
* <Child />
|
||||
* </env>
|
||||
* </env>
|
||||
* </env>
|
||||
* );
|
||||
* }
|
||||
* function Child(props, context) {
|
||||
* return <div>Theme: {context.theme}, Name: {context.name}, Id: {context.userInfo.id}</div>;
|
||||
* }
|
||||
*/
|
||||
|
||||
// 编译后:
|
||||
const theme = reactive('dark');
|
||||
|
||||
function App() {
|
||||
return $$runComponent(Env, {
|
||||
get children() {
|
||||
return $$runComponent(Env, {
|
||||
get children() {
|
||||
return $$runComponent(Env, {
|
||||
get children() {
|
||||
return $$runComponent(Child);
|
||||
},
|
||||
get userInfo() {
|
||||
return {id: 1};
|
||||
},
|
||||
get name() {
|
||||
return 'inula';
|
||||
},
|
||||
});
|
||||
},
|
||||
get name() {
|
||||
return 'inula';
|
||||
},
|
||||
});
|
||||
},
|
||||
get theme() {
|
||||
return theme.get();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const _tmpl$ = /*#__PURE__*/$$template(`<div>Theme: <!>, Name: <!>, Id: `);
|
||||
|
||||
function Child(props, context) {
|
||||
return (() => {
|
||||
const _el$ = _tmpl$(),
|
||||
_el$2 = _el$.firstChild,
|
||||
_el$5 = _el$2.nextSibling,
|
||||
_el$3 = _el$5.nextSibling,
|
||||
_el$6 = _el$3.nextSibling,
|
||||
_el$4 = _el$6.nextSibling;
|
||||
$$insert(_el$, () => context.theme, _el$5);
|
||||
$$insert(_el$, () => context.name, _el$6);
|
||||
$$insert(_el$, () => context.userInfo.id, null);
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
|
||||
render(() => $$runComponent(App, {}), container);
|
||||
expect(container.innerHTML).toBe('<div>Theme: dark, Name: inula, Id: 1</div>');
|
||||
});
|
||||
|
||||
it('should not be affected by parrallel env.', ({container}) => {
|
||||
/**
|
||||
* 源码:
|
||||
* function App() {
|
||||
* const theme = reactive('dark');
|
||||
* return (
|
||||
* <>
|
||||
* <env theme={theme.get()}>
|
||||
* <Child />
|
||||
* </env>
|
||||
* <env theme="light">
|
||||
* <Child />
|
||||
* </env>
|
||||
* </>
|
||||
* );
|
||||
* }
|
||||
* function Child(props, context) {
|
||||
* return <div>{context.theme}</div>;
|
||||
* }
|
||||
* render(() => <App />, container);
|
||||
*/
|
||||
|
||||
// 编译后:
|
||||
const theme = reactive('dark');
|
||||
function App() {
|
||||
return [
|
||||
$$runComponent(Env, {
|
||||
get children() {
|
||||
return $$runComponent(Child);
|
||||
},
|
||||
get theme() {
|
||||
return theme.get();
|
||||
},
|
||||
}),
|
||||
$$runComponent(Env, {
|
||||
get children() {
|
||||
return $$runComponent(Child);
|
||||
},
|
||||
theme: 'light',
|
||||
}),
|
||||
];
|
||||
}
|
||||
const _tmpl$ = /*#__PURE__*/ $$template(`<div>`);
|
||||
function Child(props, context) {
|
||||
return (() => {
|
||||
const _el$ = _tmpl$();
|
||||
$$insert(_el$, () => context.theme);
|
||||
return _el$;
|
||||
})();
|
||||
}
|
||||
render(() => $$runComponent(App, {}), container);
|
||||
expect(container.innerHTML).toBe('<div>dark</div><div>light</div>');
|
||||
});
|
||||
|
||||
it('should work with recursive env, like menu.', ({container}) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const fn = vi.fn();
|
||||
* function App() {
|
||||
* return (
|
||||
* <Menu key="root">
|
||||
* <SubMenu key="sub1">
|
||||
* <MenuItem key="1" />
|
||||
* <MenuItem key="2" />
|
||||
* </SubMenu>
|
||||
* <SubMenu key="sub2">
|
||||
* <MenuItem key="3" />
|
||||
* <SubMenu key="sub3">
|
||||
* <MenuItem key="4" />
|
||||
* </SubMenu>
|
||||
* <MenuItem key="5" />
|
||||
* </SubMenu>
|
||||
* </Menu>
|
||||
* );
|
||||
* }
|
||||
*
|
||||
* function Menu(props, context) {
|
||||
* return (
|
||||
* <env path={[props.key]}>
|
||||
* <ul>
|
||||
* <li>{props.key}</li>
|
||||
* {props.children}
|
||||
* </ul>
|
||||
* </env>
|
||||
* );
|
||||
* }
|
||||
* function SubMenu(props, context) {
|
||||
* return (
|
||||
* <env path={[...context.path, props.key]}>
|
||||
* <ul>
|
||||
* <li>{props.key}</li>
|
||||
* {props.children}
|
||||
* </ul>
|
||||
* </env>
|
||||
* );
|
||||
* }
|
||||
* function MenuItem(props, context) {
|
||||
* return <li>{[...context.path, props.key].join('-')}</li>;
|
||||
* }
|
||||
* render(() => <App />, container);
|
||||
*/
|
||||
|
||||
// 编译后:
|
||||
|
||||
const _tmpl$ = /*#__PURE__*/ $$template(`<ul><li>`),
|
||||
_tmpl$2 = /*#__PURE__*/ $$template(`<li>`);
|
||||
|
||||
function App() {
|
||||
return $$runComponent(Menu, {
|
||||
key: 'root',
|
||||
get children() {
|
||||
return [
|
||||
$$runComponent(SubMenu, {
|
||||
key: 'sub1',
|
||||
get children() {
|
||||
return [
|
||||
$$runComponent(MenuItem, {
|
||||
key: '1',
|
||||
}),
|
||||
$$runComponent(MenuItem, {
|
||||
key: '2',
|
||||
}),
|
||||
];
|
||||
},
|
||||
}),
|
||||
$$runComponent(SubMenu, {
|
||||
key: 'sub2',
|
||||
get children() {
|
||||
return [
|
||||
$$runComponent(MenuItem, {
|
||||
key: '3',
|
||||
}),
|
||||
$$runComponent(SubMenu, {
|
||||
key: 'sub3',
|
||||
get children() {
|
||||
return $$runComponent(MenuItem, {
|
||||
key: '4',
|
||||
});
|
||||
},
|
||||
}),
|
||||
$$runComponent(MenuItem, {
|
||||
key: '5',
|
||||
}),
|
||||
];
|
||||
},
|
||||
}),
|
||||
];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function Menu(props, context) {
|
||||
return $$runComponent(Env, {
|
||||
get path() {
|
||||
return [props.key];
|
||||
},
|
||||
get children() {
|
||||
const _el$ = _tmpl$(),
|
||||
_el$2 = _el$.firstChild;
|
||||
$$insert(_el$2, () => props.key);
|
||||
$$insert(_el$, () => props.children, null);
|
||||
return _el$;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function SubMenu(props, context) {
|
||||
return $$runComponent(Env, {
|
||||
get path() {
|
||||
return [...context.path, props.key];
|
||||
},
|
||||
get children() {
|
||||
const _el$3 = _tmpl$(),
|
||||
_el$4 = _el$3.firstChild;
|
||||
$$insert(_el$4, () => props.key);
|
||||
$$insert(_el$3, () => props.children, null);
|
||||
return _el$3;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function MenuItem(props, context) {
|
||||
return (() => {
|
||||
const _el$5 = _tmpl$2();
|
||||
$$insert(_el$5, () => [...context.path, props.key].join('-'));
|
||||
return _el$5;
|
||||
})();
|
||||
}
|
||||
|
||||
render(() => $$runComponent(App, {}), container);
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||
'<ul><li>root</li><ul><li>sub1</li><li>root-sub1-1</li><li>root-sub1-2</li></ul><ul><li>sub2</li><li>root-sub2-3</li><ul><li>sub3</li><li>root-sub2-sub3-4</li></ul><li>root-sub2-5</li></ul></ul>',
|
||||
);
|
||||
});
|
||||
});
|
|
@ -49,7 +49,7 @@ describe('render', () => {
|
|||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 0.');
|
||||
});
|
||||
|
||||
it('should render jsx with slots', ({ container }) => {
|
||||
it('should render jsx expression with slots', ({ container }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const CountingComponent = () => {
|
||||
|
@ -123,7 +123,7 @@ describe('render', () => {
|
|||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
|
||||
});
|
||||
|
||||
it('should render sub components', ({ container }) => {
|
||||
it('should render components', ({ container }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const CountValue = (props) => {
|
||||
|
@ -185,7 +185,81 @@ describe('render', () => {
|
|||
expect(container.querySelector('#count').innerHTML).toEqual('Count value is 1<!---->.');
|
||||
});
|
||||
|
||||
it('should render nested components', ({ container }) => {
|
||||
it('should render components with slot', ({ container }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const CountValue = (props) => {
|
||||
* return <div>Title: {props.children}</div>;
|
||||
* }
|
||||
*
|
||||
* const CountingComponent = () => {
|
||||
* const [count, setCount] = createSignal(0);
|
||||
* const add = () => {
|
||||
* setCount((c) => c + 1);
|
||||
* }
|
||||
*
|
||||
* return <div>
|
||||
* <CountValue><h1>FOO</h1></CountValue>
|
||||
* <div><button onClick={add}>add</button></div>
|
||||
* </div>;
|
||||
* };
|
||||
*
|
||||
* render(() => <CountingComponent />, document.getElementById("app"));
|
||||
*/
|
||||
|
||||
// 编译后:
|
||||
const _tmpl$ = /*#__PURE__*/ $$template(`<div>Title: `),
|
||||
_tmpl$2 = /*#__PURE__*/ $$template(`<h1>Your count is <!>.`),
|
||||
_tmpl$3 = /*#__PURE__*/ $$template(`<div><div><button>add`);
|
||||
const CountValue = props => {
|
||||
return (() => {
|
||||
const _el$ = _tmpl$(),
|
||||
_el$2 = _el$.firstChild;
|
||||
$$insert(_el$, () => props.children, null);
|
||||
return _el$;
|
||||
})();
|
||||
};
|
||||
const CountingComponent = () => {
|
||||
const count = reactive(0);
|
||||
const add = () => {
|
||||
count.set(c => c + 1);
|
||||
};
|
||||
return (() => {
|
||||
const _el$3 = _tmpl$3(),
|
||||
_el$8 = _el$3.firstChild,
|
||||
_el$9 = _el$8.firstChild;
|
||||
$$insert(
|
||||
_el$3,
|
||||
$$runComponent(CountValue, {
|
||||
get children() {
|
||||
const _el$4 = _tmpl$2(),
|
||||
_el$5 = _el$4.firstChild,
|
||||
_el$7 = _el$5.nextSibling,
|
||||
_el$6 = _el$7.nextSibling;
|
||||
$$insert(_el$4, count, _el$7);
|
||||
return _el$4;
|
||||
},
|
||||
}),
|
||||
_el$8
|
||||
);
|
||||
$$on(_el$9, 'click', add, true);
|
||||
return _el$3;
|
||||
})();
|
||||
};
|
||||
render(() => $$runComponent(CountingComponent, {}), container);
|
||||
$$delegateEvents(['click']);
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||
`"<div><div>Title: <h1>Your count is 0<!---->.</h1></div><div><button>add</button></div></div>"`
|
||||
);
|
||||
|
||||
container.querySelector('button').click();
|
||||
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||
`"<div><div>Title: <h1>Your count is 1<!---->.</h1></div><div><button>add</button></div></div>"`
|
||||
);
|
||||
});
|
||||
|
||||
it('should render sub components', ({ container }) => {
|
||||
/**
|
||||
* 源码:
|
||||
* const CountValue = (props) => {
|
||||
|
|
|
@ -306,7 +306,7 @@ export function runEffects(): void {
|
|||
}
|
||||
|
||||
// 不进行响应式数据的使用追踪
|
||||
export function untrack(fn: () => void) {
|
||||
export function untrack<T>(fn: () => T): T {
|
||||
if (runningRNode === null) {
|
||||
return fn();
|
||||
}
|
||||
|
|
|
@ -22,7 +22,8 @@ import { getRNodeVal } from './RNodeAccessor';
|
|||
export function createReactive<T extends string>(raw?: T): Signal<string>;
|
||||
export function createReactive<T extends number>(raw?: T): Signal<number>;
|
||||
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 boolean>(raw?: T): Signal<boolean>;
|
||||
export function createReactive<T extends number | string | symbol | boolean>(raw?: T): Signal<T>;
|
||||
export function createReactive<T extends Record<any, any> | Array<any> | symbol>(raw?: T): DeepReactive<T>;
|
||||
export function createReactive<T extends NonFunctionType>(raw: T, deep = true): DeepReactive<T> | Signal<T> {
|
||||
// Function, Date, RegExp, null, undefined are simple signals
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
* See the Mulan PSL v2 for more details.
|
||||
*/
|
||||
|
||||
import { createComputed as computed, createReactive as reactive, createWatch as watch} from './RNodeCreator';
|
||||
import { createComputed as computed, createReactive as reactive, createWatch as watch } from './RNodeCreator';
|
||||
import { isReactiveObj } from './Utils';
|
||||
import { RNode, untrack, runEffects } from './RNode';
|
||||
import { setScheduler } from './SetScheduler';
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { reactive, computed, watch } from '../src';
|
||||
|
||||
describe('watch', () => {
|
||||
it('should not track the unused reactive', () => {
|
||||
const cond = reactive(true);
|
||||
const a = reactive(1);
|
||||
const b = reactive(1);
|
||||
const fn = jest.fn();
|
||||
watch(() => {
|
||||
fn();
|
||||
if (cond.get()) {
|
||||
a.get();
|
||||
} else {
|
||||
b.get();
|
||||
}
|
||||
});
|
||||
expect(fn).toBeCalledTimes(1);
|
||||
a.set(2);
|
||||
expect(fn).toBeCalledTimes(2);
|
||||
b.set(2);
|
||||
// should not trigger fn
|
||||
expect(fn).toBeCalledTimes(2);
|
||||
|
||||
cond.set(false);
|
||||
expect(fn).toBeCalledTimes(3);
|
||||
a.set(3);
|
||||
// should not trigger fn
|
||||
expect(fn).toBeCalledTimes(3);
|
||||
b.set(3);
|
||||
expect(fn).toBeCalledTimes(4);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue