!149 feat(no-vdom): Dynamic

* feat(no-vdom): Dynamic
This commit is contained in:
Hoikan 2024-02-20 06:52:10 +00:00 committed by 陈超涛
parent 5ef6dfb588
commit 3b3bf8e5d6
2 changed files with 228 additions and 0 deletions

View File

@ -0,0 +1,45 @@
/*
* 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 { FunctionComponent, Props } from '../type';
import { insert } from '../dom';
import { splitProps } from '../util';
type DynamicProps<P = Props> = {
// it can be a native element tag name or a component
component: string | FunctionComponent<P>;
} & P;
/**
* Dynamic component that can be used to switch between different components or native elements
* @example
* const comp = reactive('h1');
* <Dynamic component={comp.get()}>Title</Dynamic>
*/
export function Dynamic(props: DynamicProps) {
return () => {
if (typeof props.component === 'string') {
const el = document.createElement(props.component);
insert(el, props.children);
return el;
} else if (typeof props.component === 'function') {
// remove component from props, and pass the rest to the component
const [, compProps] = splitProps(props, ['component']);
return props.component(compProps);
} else {
throw new Error('Invalid component for Dynamic');
}
};
}

View File

@ -0,0 +1,183 @@
/*
* 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 { runComponent as $$runComponent, render } from '../src/core';
import { Dynamic } from '../src/components/Dynamic';
import { reactive } from 'inula-reactive';
describe('Dynamic', () => {
it('should work with native elements.', ({ container }) => {
/**
*
* function App() {
* return <Dynamic component="h1">foo</Dynamic>;
* }
* render(() => <App />, container);
*/
// 编译后:
function App() {
return $$runComponent(Dynamic, {
component: 'h1',
children: 'foo',
});
}
render(() => $$runComponent(App, {}), container);
expect(container).toMatchInlineSnapshot(`
<div>
<h1>
foo
</h1>
</div>
`);
});
it('should work with components.', ({ container }) => {
/**
*
* function App() {
* return <Dynamic component={Title} name="bar"/>;
* }
* function Title(props) {
* return <h1>{props.name}</h1>;
* }
* render(() => <App />, container);
*/
// 编译后:
function App() {
return $$runComponent(Dynamic, {
component: Title,
name: 'bar',
});
}
const _tmpl = /*#__PURE__*/ $$template('<h1>');
function Title(props) {
return (() => {
const _el$ = _tmpl();
$$insert(_el$, () => props.name, null);
return _el$;
})();
}
render(() => $$runComponent(App, {}), container);
expect(container).toMatchInlineSnapshot(`
<div>
<h1>
bar
</h1>
</div>
`);
});
it('should throw on invalid component.', ({ container }) => {
/**
*
* function App() {
* return <Dynamic component={null} />;
* }
* render(() => <App />, container);
*/
// 编译后:
function App() {
return $$runComponent(Dynamic, {
component: null,
});
}
expect(() => render(() => $$runComponent(App, {}), container)).toThrowError('Invalid component for Dynamic');
});
it('should change component.', async ({ container }) => {
/**
*
* const H1 = (props) => <h1>{props.children}</h1>;
* const H3 = (props) => <h3>{props.children}</h3>;
* function App() {
* const comp = reactive('h1');
* const comps = {
* H1,
* H3,
* h1: 'h1',
* h2: 'h2',
* }
* return (
* <div>
* <Dynamic component={comps[comp.get()]}>foo</Dynamic>
* </div>
* );
* }
* render(() => <App />, container);
*
*/
// 编译后:
const _tmpl$ = /*#__PURE__*/ $$template('<div></div>');
const _h1 = /*#__PURE__*/ $$template('<h1>'),
_h3 = /*#__PURE__*/ $$template('<h3>');
const H1 = (props: { children: any }) => {
const _el$ = _h1();
$$insert(_el$, () => props.children);
return _el$;
};
const H3 = (props: { children: any }) => {
const _el$3 = _h3();
$$insert(_el$3, () => props.children);
return _el$3;
};
const comp = reactive('h1');
function App() {
const comps = {
H1,
H3,
h1: 'h1',
h2: 'h2',
};
return (() => {
const _div = _tmpl$();
$$insert(
_div,
$$runComponent(Dynamic, {
get component() {
return comps[comp.get()];
},
children: 'foo',
})
);
return _div;
})();
}
render(() => $$runComponent(App, {}), container);
expect(container.innerHTML).toMatchInlineSnapshot('"<div><h1>foo</h1></div>"');
comp.set('h2');
expect(container.innerHTML).toMatchInlineSnapshot('"<div><h2>foo</h2></div>"');
comp.set('H3');
expect(container.innerHTML).toMatchInlineSnapshot('"<div><h3>foo</h3></div>"');
comp.set('H1');
expect(container.innerHTML).toMatchInlineSnapshot('"<div><h1>foo</h1></div>"');
});
});