diff, renderIntoRealDom, dynamic count of elements rendering (without 'key'), useState hook only
This commit is contained in:
parent
110906370b
commit
5ca8f7e1c0
@ -1,24 +1,42 @@
|
||||
import {useState} from "../keact/hooks";
|
||||
import {Toggle} from "./Toggle";
|
||||
import {CheckEmptyFragment} from "./CheckEmptyFragment";
|
||||
import {ChooseBg} from "./ChooseBg";
|
||||
import {TextComponent} from "./TextComponent";
|
||||
import {NamesList} from "./namesList";
|
||||
|
||||
export const App = () => {
|
||||
let [clicked, setClicked] = useState(0);
|
||||
const text = "Hello world!";
|
||||
function clickHandler() {
|
||||
const newClicked = +clicked + 1;
|
||||
setClicked(newClicked);
|
||||
console.log(`clicked ${newClicked}-th time`);
|
||||
}
|
||||
// let [clicked, setClicked] = useState(0);
|
||||
// const text = "Hello world!";
|
||||
// function clickHandler() {
|
||||
// const newClicked = +clicked + 1;
|
||||
// setClicked(newClicked);
|
||||
// console.log(`clicked ${newClicked}-th time`);
|
||||
// }
|
||||
|
||||
return <div>
|
||||
<NamesList/>
|
||||
</div>
|
||||
// let [isSwap, setIsSwap] = useState(false);
|
||||
// return <div>
|
||||
// <div>
|
||||
// <span>swapped: </span><span>{isSwap ? 'true' : 'false'}</span>
|
||||
// </div>
|
||||
// <div>
|
||||
// <button onClick={() => {setIsSwap(!isSwap)}}>swap</button>
|
||||
// </div>
|
||||
// {isSwap ? [<Toggle/>, <TextComponent/>] : [<TextComponent/>, <Toggle/>]}
|
||||
// </div>
|
||||
// return <div>
|
||||
// {text}
|
||||
// </div>
|
||||
return <div>
|
||||
<div className="text_holder"
|
||||
onClick={clickHandler}
|
||||
>
|
||||
<span>{text}</span>
|
||||
<span>clicked {clicked} times</span>
|
||||
</div>
|
||||
<Toggle clicked={clicked}/>
|
||||
</div>
|
||||
// return <div>
|
||||
// <div className="text_holder"
|
||||
// onClick={clickHandler}
|
||||
// >
|
||||
// <span>{text}</span>
|
||||
// <span>clicked {clicked} times</span>
|
||||
// </div>
|
||||
// <Toggle clicked={clicked}/>
|
||||
// </div>
|
||||
}
|
||||
15
src/components/CheckEmptyFragment.tsx
Normal file
15
src/components/CheckEmptyFragment.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import {useState} from "../keact/hooks";
|
||||
|
||||
export function CheckEmptyFragment(props) {
|
||||
// return <></>
|
||||
// const [isShow, setIsShow] = useState(false)
|
||||
// function toggleIsShow() {
|
||||
// setIsShow(!isShow);
|
||||
// }
|
||||
|
||||
debugger;
|
||||
if (props.isShow) {
|
||||
return <span>component will return false</span>
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -1,13 +1,22 @@
|
||||
import {useState} from "../keact/hooks";
|
||||
|
||||
export function ChooseBg() {
|
||||
const [bgColor, setBgColor] = useState('#ff0000');
|
||||
const [bgColor, setBgColor] = useState('#ffffff');
|
||||
document.body.style.background = '#ffffff';
|
||||
|
||||
function setNewBgColor(newColor: string) {
|
||||
setBgColor(newColor);
|
||||
document.body.style.background = newColor;
|
||||
}
|
||||
|
||||
function resetBgColor() {
|
||||
setNewBgColor('#ffffff');
|
||||
}
|
||||
|
||||
function changeBgColor(e: any) {
|
||||
const value = e.target.value;
|
||||
if ([4, 7].includes(value.length)) {
|
||||
setBgColor(e.target.value);
|
||||
document.body.style.background = e.target.value;
|
||||
setNewBgColor(value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,8 +25,10 @@ export function ChooseBg() {
|
||||
<span>input your own bg color:</span>
|
||||
<input type="text"
|
||||
placeholder="#ff0000"
|
||||
style={{color: bgColor !== '#ffffff' ? bgColor : '#000000'}}
|
||||
onInput={changeBgColor}
|
||||
/>
|
||||
{bgColor === '#ffffff' ? [<span>now it's </span>, <span style={{background: '#000', color: '#fff'}}>white</span>] : <button onClick={resetBgColor}>reset</button>}
|
||||
</div>
|
||||
<span>current bg color is {bgColor}</span>
|
||||
</div>
|
||||
|
||||
3
src/components/TextComponent.tsx
Normal file
3
src/components/TextComponent.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
export function TextComponent() {
|
||||
return "simple text component"
|
||||
}
|
||||
@ -7,11 +7,6 @@ export const Toggle = (props) => {
|
||||
function toggle() {
|
||||
const newVal = !isOn;
|
||||
setIsOn(newVal);
|
||||
if (newVal) {
|
||||
document.body.style.background = '#ff0000';
|
||||
} else {
|
||||
document.body.style.background = '';
|
||||
}
|
||||
}
|
||||
return <div>
|
||||
{
|
||||
|
||||
48
src/components/namesList.tsx
Normal file
48
src/components/namesList.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import {useState} from "../keact/hooks";
|
||||
|
||||
export function NamesList() {
|
||||
const [namesList, setNamesList] = useState([
|
||||
"Greg",
|
||||
"Elon",
|
||||
"Mark",
|
||||
"Olaph"
|
||||
]);
|
||||
|
||||
const [newName, setNewName] = useState("");
|
||||
|
||||
function addNewName() {
|
||||
namesList.push(newName);
|
||||
setNewName("");
|
||||
setNamesList(namesList);
|
||||
}
|
||||
|
||||
function deleteName(index: number) {
|
||||
namesList.splice(index, 1);
|
||||
setNamesList(namesList);
|
||||
}
|
||||
|
||||
return <div>
|
||||
<h1>Names list:</h1>
|
||||
<div>
|
||||
{namesList.map((n, i) =>
|
||||
<div>
|
||||
<span>#{i + 1} - {n}</span><button onClick={deleteName.bind(this, i)}>x</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<h2>add to list:</h2>
|
||||
<input type="text"
|
||||
placeholder="Elon"
|
||||
value={newName}
|
||||
onInput={(e) => {
|
||||
//@ts-ignore
|
||||
setNewName(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
disabled={!newName}
|
||||
onClick={addNewName}>add</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import {reRenderElement, VirtualElementContext} from "./keact";
|
||||
import {ComponentContext, reRenderComponent} from "./keact-dom";
|
||||
|
||||
let currentInstance: VirtualElementContext;
|
||||
let currentInstance: ComponentContext;
|
||||
let cursor = 0;
|
||||
|
||||
export function useState(initial: any): [any, Function] {
|
||||
@ -11,18 +11,18 @@ export function useState(initial: any): [any, Function] {
|
||||
return result;
|
||||
}
|
||||
const setFunc = setState.bind(this, currentInstance, cursor);
|
||||
state.set(cursor, initial)
|
||||
setStateFuncs.set(cursor, setFunc)
|
||||
state.set(cursor, initial)
|
||||
cursor++;
|
||||
return [initial, setFunc];
|
||||
}
|
||||
|
||||
function setState(currentInstance: VirtualElementContext, cursor: number, newValue: any) {
|
||||
function setState(currentInstance: ComponentContext, cursor: number, newValue: any) {
|
||||
currentInstance.state.set(cursor, newValue);
|
||||
reRenderElement(currentInstance);
|
||||
reRenderComponent(currentInstance.element);
|
||||
}
|
||||
|
||||
export const setContext = (context: VirtualElementContext) => {
|
||||
export const setContext = (context: ComponentContext) => {
|
||||
currentInstance = context;
|
||||
cursor = 0;
|
||||
}
|
||||
@ -1,9 +1,285 @@
|
||||
import {VirtualElement} from "./keact";
|
||||
import {
|
||||
createElement, FC,
|
||||
Props, resolveChildrenIntoVirtualElements,
|
||||
VirtualElement,
|
||||
VirtualElementComponent,
|
||||
VirtualElementEmpty,
|
||||
VirtualElementFragment,
|
||||
VirtualElementTag,
|
||||
VirtualElementText
|
||||
} from "./keact";
|
||||
import {setContext} from "./hooks";
|
||||
|
||||
const childrenByHtml: Map<HTMLElement, { children: VDOMElement[] }> = new Map()
|
||||
|
||||
export type ComponentContext = {
|
||||
state: Map<number, any>,
|
||||
setStateFuncs: Map<number, Function>
|
||||
element: VDOMComponent,
|
||||
};
|
||||
export type VDOMElement = VDOMComponent | VDOMElementTag | VDOMElementSimple | VDOMElementFragment;
|
||||
export type VDOMComponent = VirtualElementComponent & {
|
||||
//TODO: delete context, use instance as key in map
|
||||
context: ComponentContext;
|
||||
childrenDomElements: VDOMElement[];
|
||||
parent: HTMLElement;
|
||||
};
|
||||
export type VDOMElementTag = VirtualElementTag & {
|
||||
childrenDomElements: VDOMElement[];
|
||||
node: HTMLElement,
|
||||
parent: HTMLElement;
|
||||
funcProps: Record<string, Function>;
|
||||
}
|
||||
export type VDOMElementFragment = VirtualElementFragment & {
|
||||
childrenDomElements: VDOMElement[];
|
||||
parent: HTMLElement;
|
||||
}
|
||||
export type VDOMElementSimple = (VirtualElementText | VirtualElementEmpty) & {
|
||||
node: Node,
|
||||
}
|
||||
|
||||
function runComponentsFunction(generate: FC, props: Props): VirtualElement[] {
|
||||
const result = generate(props);
|
||||
if (typeof result === 'object') {
|
||||
if (Array.isArray(result)) {
|
||||
const toReturn = [];
|
||||
for (const c of result) {
|
||||
if (typeof c === 'object') {
|
||||
toReturn.push(c);
|
||||
} else {
|
||||
toReturn.push(...resolveChildrenIntoVirtualElements(c))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return [result];
|
||||
}
|
||||
}
|
||||
return resolveChildrenIntoVirtualElements(result)
|
||||
}
|
||||
|
||||
export function reRenderComponent(component: VDOMComponent) {
|
||||
setContext(component.context)
|
||||
const newChildren = runComponentsFunction(component.generate, component.props);
|
||||
|
||||
diff(component.childrenDomElements, newChildren, component.parent);
|
||||
}
|
||||
|
||||
function render($element: VirtualElement | undefined, root: HTMLElement) {
|
||||
if (!$element) return;
|
||||
|
||||
root.append($element.node);
|
||||
if (!childrenByHtml.has(root)) {
|
||||
childrenByHtml.set(root, {
|
||||
children: [],
|
||||
})
|
||||
}
|
||||
|
||||
const { children } = childrenByHtml.get(root);
|
||||
diff(children, [$element], root);
|
||||
}
|
||||
|
||||
function diff(oldChildren: VDOMElement[], newChildren: VirtualElement[], parentNode: HTMLElement) {
|
||||
for (let i = 0, length = Math.max(newChildren.length, oldChildren.length); i < length; i++) {
|
||||
const oldChild = oldChildren[i] as VDOMElement | undefined;
|
||||
const newChild = newChildren[i] as VirtualElement | undefined;
|
||||
if (!newChild && !oldChild) continue;
|
||||
if (newChild && !oldChild) {
|
||||
oldChildren[i] = renderIntoRealDom(newChild, parentNode);
|
||||
continue;
|
||||
} else if (!newChild && oldChild) {
|
||||
removeFromDom(oldChild);
|
||||
oldChildren[i] = undefined;
|
||||
continue;
|
||||
} else {
|
||||
if (oldChild.type === 'empty' && newChild.type === 'empty') {
|
||||
continue;
|
||||
} else if (oldChild.type === 'text' && newChild.type === 'text') {
|
||||
if (oldChild.text !== newChild.text) {
|
||||
console.warn(`updateText in real dom ${oldChild.text} -> ${newChild.text}`);
|
||||
oldChild.node.textContent = newChild.text;
|
||||
oldChild.text = newChild.text;
|
||||
}
|
||||
} else if (oldChild.type === 'component' && newChild.type === 'component') {
|
||||
if (oldChild.generate !== newChild.generate) {
|
||||
let obj: VDOMElement = oldChild;
|
||||
while(obj.type === "fragment" || obj.type === 'component') {
|
||||
obj = obj.childrenDomElements[0];
|
||||
}
|
||||
const insertBefore = obj.node;
|
||||
|
||||
const newComponentDom = renderIntoRealDom(newChild, oldChild.parent, insertBefore)
|
||||
removeFromDom(oldChild)
|
||||
oldChildren[i] = newComponentDom;
|
||||
}
|
||||
else if (JSON.stringify(oldChild.props) !== JSON.stringify(newChild.props)) {
|
||||
setContext(oldChild.context);
|
||||
const newChildren = runComponentsFunction(newChild.generate, newChild.props)
|
||||
|
||||
oldChild.props = newChild.props;
|
||||
diff(oldChild.childrenDomElements, newChildren, oldChild.parent)
|
||||
}
|
||||
} else if (oldChild.type === 'fragment' && newChild.type === 'fragment') {
|
||||
// let obj: VDOMElement = oldChild.childrenDomElements[0];
|
||||
// while(obj.type === "fragment" || obj.type === 'component') {
|
||||
// obj = obj.childrenDomElements[0];
|
||||
// }
|
||||
// const insertBefore = obj.node;
|
||||
diff(oldChild.childrenDomElements, newChild.children, oldChild.parent);
|
||||
} else if (oldChild.type === 'tag' && newChild.type === 'tag') {
|
||||
if (oldChild.tag !== newChild.tag) {
|
||||
const newVDOMTag = renderIntoRealDom(newChild, oldChild.parent, oldChild.node) as VDOMElementTag;
|
||||
removeFromDom(oldChild);
|
||||
oldChildren[i] = newVDOMTag;
|
||||
} else if (JSON.stringify(oldChild.props) !== JSON.stringify(newChild.props)) {
|
||||
clearPropsFromHtmlElement(oldChild.node, oldChild.props)
|
||||
applyPropsToHtmlElement(oldChild.node, newChild.props, oldChild.funcProps);
|
||||
oldChild.props = newChild.props;
|
||||
} else {
|
||||
updateFuncObjFromProps(newChild.props, oldChild.funcProps)
|
||||
diff(oldChild.childrenDomElements, newChild.children, oldChild.node);
|
||||
}
|
||||
} else {
|
||||
let obj: VDOMElement = oldChild;
|
||||
while(obj.type === "fragment" || obj.type === 'component') {
|
||||
obj = obj.childrenDomElements[0];
|
||||
}
|
||||
const insertBefore = obj.node;
|
||||
oldChildren[i] = renderIntoRealDom(newChild, insertBefore.parentElement, insertBefore);
|
||||
removeFromDom(oldChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateFuncObjFromProps(props: Props, funcObj: Record<string, Function>) {
|
||||
for (const [propKey, propVal] of Object.entries(props)) {
|
||||
if (typeof propVal === 'function') {
|
||||
funcObj[propKey] = propVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearPropsFromHtmlElement(el: HTMLElement, props: Props) {
|
||||
for (const [propKey, propVal] of Object.entries(props)) {
|
||||
if (propKey === 'children') continue;
|
||||
if (typeof propVal === 'function') {
|
||||
el[propKey.toLowerCase()] = undefined;
|
||||
} else {
|
||||
el[propKey] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function applyPropsToHtmlElement(el: HTMLElement, props: Props, funcPropsObj: Record<string, Function>){
|
||||
for (const [propKey, propVal] of Object.entries(props)) {
|
||||
if (propKey === 'children') continue;
|
||||
if (typeof propVal === 'function') {
|
||||
funcPropsObj[propKey] = propVal;
|
||||
el[propKey.toLowerCase()] = (...args: any[]) => {funcPropsObj[propKey](...args)};
|
||||
} else if (typeof propVal === 'object' && el[propKey]) {
|
||||
Object.assign(el[propKey], propVal);
|
||||
} else {
|
||||
el[propKey] = propVal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderIntoRealDom(el: VirtualElement, parent: HTMLElement, insertBefore?: Node): VDOMElement {
|
||||
if (el.type === 'empty') {
|
||||
const node = document.createTextNode(" ")
|
||||
appendOrReplace(node, parent, insertBefore);
|
||||
return {
|
||||
node,
|
||||
...el,
|
||||
};
|
||||
} else if (el.type === 'text') {
|
||||
const node = document.createTextNode(el.text)
|
||||
appendOrReplace(node, parent, insertBefore);
|
||||
return {
|
||||
node,
|
||||
...el,
|
||||
};
|
||||
} else if (el.type === 'tag') {
|
||||
const node = document.createElement(el.tag);
|
||||
const newChildren: VDOMElement[] = [];
|
||||
for (let i = 0; i < el.children.length; i++) {
|
||||
newChildren.push(renderIntoRealDom(el.children[i], node));
|
||||
}
|
||||
|
||||
const funcProps: Record<string, Function> = {};
|
||||
applyPropsToHtmlElement(node, el.props, funcProps)
|
||||
appendOrReplace(node, parent, insertBefore);
|
||||
return {
|
||||
node,
|
||||
parent,
|
||||
childrenDomElements: newChildren,
|
||||
funcProps,
|
||||
...el,
|
||||
};
|
||||
} else if (el.type === 'component') {
|
||||
const component: VDOMComponent = {
|
||||
context: {
|
||||
state: new Map(),
|
||||
setStateFuncs: new Map(),
|
||||
element: null,
|
||||
},
|
||||
parent,
|
||||
childrenDomElements: [],
|
||||
// parent,
|
||||
...el
|
||||
};
|
||||
component.context.element = component;
|
||||
setContext(component.context);
|
||||
const children = runComponentsFunction(el.generate, el.props)
|
||||
component.childrenDomElements = [];
|
||||
for (const c of children) {
|
||||
component.childrenDomElements.push(renderIntoRealDom(c, parent));
|
||||
}
|
||||
return component;
|
||||
} else if (el.type === 'fragment') {
|
||||
const node = document.createDocumentFragment();
|
||||
const newChildren: VDOMElement[] = [];
|
||||
for (let i = 0; i < el.children.length; i++) {
|
||||
newChildren.push(renderIntoRealDom(el.children[i], node as undefined as HTMLElement));
|
||||
}
|
||||
appendOrReplace(node, parent, insertBefore);
|
||||
newChildren.forEach(child => {
|
||||
if ('parent' in child) {
|
||||
child.parent = parent;
|
||||
}
|
||||
});
|
||||
return {
|
||||
parent,
|
||||
childrenDomElements: newChildren,
|
||||
...el,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function removeFromDom(el: VDOMElement) {
|
||||
console.warn('removeFromDom:', el);
|
||||
if (el.type === "fragment" || el.type === 'component') {
|
||||
const stack = [el.childrenDomElements];
|
||||
while(stack.length) {
|
||||
stack.pop().forEach(c => {
|
||||
if (c.type === "fragment" || c.type === 'component') {
|
||||
stack.push(c.childrenDomElements);
|
||||
} else {
|
||||
c.node.parentElement.removeChild(c.node);
|
||||
}
|
||||
})
|
||||
}
|
||||
} else {
|
||||
el.node.parentElement.removeChild(el.node);
|
||||
}
|
||||
}
|
||||
|
||||
function appendOrReplace(node: Node, parent: HTMLElement | DocumentFragment, insertBefore?: Node) {
|
||||
console.warn('appendOrReplace:', node);
|
||||
if (insertBefore) {
|
||||
parent.insertBefore(node, insertBefore);
|
||||
} else {
|
||||
parent.append(node);
|
||||
}
|
||||
}
|
||||
|
||||
const KeactDOM = {
|
||||
|
||||
@ -1,238 +1,103 @@
|
||||
import {setContext} from "./hooks";
|
||||
import {Fragment} from "./jsx-runtime";
|
||||
|
||||
export type Props = Record<string, any>;
|
||||
export type VirtualElementContext = {
|
||||
state: Map<number, any>,
|
||||
setStateFuncs: Map<number, Function>
|
||||
element: any,
|
||||
}
|
||||
export type VirtualElement = VirtualElementTag | VirtualElementText | VirtualElementComponent | VirtualElementEmpty | VirtualElementFragment;
|
||||
|
||||
export type VirtualElementComponent = {
|
||||
type: 'component',
|
||||
node: Node,
|
||||
context: VirtualElementContext,
|
||||
props: Props,
|
||||
generate: FC,
|
||||
children: VirtualElement[],
|
||||
name: string,
|
||||
instance: {
|
||||
element: VirtualElementComponent;
|
||||
},
|
||||
};
|
||||
export type VirtualElementTag = {
|
||||
node: HTMLElement;
|
||||
type: 'tag';
|
||||
tag: string; //html tag
|
||||
props: Props;
|
||||
children: VirtualElement[];
|
||||
otherProps: [string, any][];
|
||||
propsFunctions: [string, Function][];
|
||||
// context: VirtualElementContext
|
||||
};
|
||||
export type VirtualElementText = {
|
||||
type: 'text',
|
||||
node: Node,
|
||||
text: string,
|
||||
};
|
||||
export type VirtualElementEmpty = {
|
||||
type: 'empty',
|
||||
node: Node,
|
||||
};
|
||||
export type VirtualElementFragment = {
|
||||
node: Node,
|
||||
type: 'fragment',
|
||||
children: VirtualElement[];
|
||||
};
|
||||
|
||||
export type VirtualElementValid = VirtualElementComponent | VirtualElementTag | VirtualElementFragment;
|
||||
export type FC = (props: Props) => VirtualElement
|
||||
type VirtualElementChildrenSingular = false | string | VirtualElement;
|
||||
type VirtualElementChildren = VirtualElementChildrenSingular | (VirtualElementChildrenSingular)[];
|
||||
export type FC = (props: Props) => VirtualElementChildren;
|
||||
|
||||
export function reRenderElement(context: VirtualElementContext) {
|
||||
setContext(context);
|
||||
createElement(context.element.generate, {
|
||||
componentToUpdate: context.element,
|
||||
...context.element.props
|
||||
});
|
||||
function createElementFragment(props: Props): VirtualElementFragment {
|
||||
return {
|
||||
type: 'fragment',
|
||||
children: resolveChildrenIntoVirtualElements(props.children || [" "]),
|
||||
}
|
||||
}
|
||||
|
||||
function createElementComponent(generateFunc: FC, props: Props): VirtualElementComponent {
|
||||
const component: VirtualElementComponent = {
|
||||
type: 'component',
|
||||
generate: generateFunc,
|
||||
props: props,
|
||||
name: generateFunc.name,
|
||||
instance: undefined as {element: VirtualElementComponent},
|
||||
}
|
||||
component.instance = {
|
||||
element: component,
|
||||
}
|
||||
return component;
|
||||
}
|
||||
|
||||
function createElementTag(tag: string, props: Props): VirtualElementTag {
|
||||
return {
|
||||
type: 'tag',
|
||||
tag,
|
||||
props: Object.assign({}, props, {children: null}),
|
||||
children: props.children ? resolveChildrenIntoVirtualElements(props.children) : [],
|
||||
}
|
||||
}
|
||||
|
||||
export function createElement(type: string | FC, props: Props): VirtualElementValid {
|
||||
// type="div"
|
||||
// props = {
|
||||
// children: "Hello world!"
|
||||
// }
|
||||
if (type === Fragment) {
|
||||
const element: VirtualElementFragment = {
|
||||
type: 'fragment',
|
||||
node: document.createTextNode(" "),
|
||||
children: [],
|
||||
return createElementFragment(props);
|
||||
}
|
||||
resolveChildren(props.children, element)
|
||||
return element;
|
||||
if (typeof type === 'function') {
|
||||
return createElementComponent(type, props);
|
||||
}
|
||||
const isComponent = typeof type === 'function';
|
||||
|
||||
//create component
|
||||
if (isComponent) {
|
||||
console.log('createElement() component')
|
||||
const component: VirtualElementComponent = props.componentToUpdate || {
|
||||
type: 'component',
|
||||
node: null,
|
||||
generate: type,
|
||||
children: [],
|
||||
context: {
|
||||
state: new Map(),
|
||||
setStateFuncs: new Map(),
|
||||
element: null,
|
||||
},
|
||||
props: props,
|
||||
}
|
||||
component.context.element = component;
|
||||
|
||||
setContext(component.context);
|
||||
const c= type(props);
|
||||
if (c.type === 'fragment') {
|
||||
throw new Error('first element of component can\'t be fragment');
|
||||
}
|
||||
if (!component.children.length) {
|
||||
component.children.push(c);
|
||||
component.node = c.node;
|
||||
} else {
|
||||
//@ts-ignore
|
||||
replaceChanged(component, {children: [c]});
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
// if (!element.node) {
|
||||
// const node = document.createElement(type);
|
||||
// element.node = node;
|
||||
// } else if (element.tag !== type) {
|
||||
// const node = document.createElement(type);
|
||||
// console.warn(`element.node.parentElement.replaceChild, tag: ${type}`);
|
||||
// element.node.parentElement.replaceChild(node, element.node);
|
||||
// element.node = node;
|
||||
// element.tag = type;
|
||||
// }
|
||||
|
||||
console.log(`createElement() tag, ${type}`);
|
||||
const element: VirtualElementTag = {
|
||||
node: document.createElement(type),
|
||||
// node: null,
|
||||
type: 'tag',
|
||||
tag: type,
|
||||
children: [],
|
||||
otherProps: [],
|
||||
propsFunctions: [],
|
||||
};
|
||||
|
||||
for (const [propKey, propVal] of Object.entries(props)) {
|
||||
if (propKey === 'children') {
|
||||
resolveChildren(propVal, element);
|
||||
} else if (typeof propVal === 'function') {
|
||||
element.propsFunctions.push([propKey.toLowerCase(), propVal]);
|
||||
element.node[propKey.toLowerCase()] = propVal;
|
||||
} else {
|
||||
element.otherProps.push([propKey, propVal]);
|
||||
element.node[propKey] = propVal;
|
||||
}
|
||||
}
|
||||
// if (className) {
|
||||
// element.node.className = className;
|
||||
// }
|
||||
// if (onClick) {
|
||||
// element.node.onclick = onClick;
|
||||
// }
|
||||
return element;
|
||||
return createElementTag(type, props);
|
||||
}
|
||||
|
||||
type VirtualElementChildren = false | string | VirtualElement | (false | VirtualElement | string)[];
|
||||
|
||||
function replaceChanged(oldHead: VirtualElementValid, newHead: VirtualElementValid) {
|
||||
if (oldHead.children.length !== newHead.children.length) {
|
||||
alert('replaceChanged: oldHead.children.length !== newHead.children.length')
|
||||
throw new Error('replaceChanged: oldHead.children.length !== newHead.children.length');
|
||||
}
|
||||
for(let i = 0; i < oldHead.children.length; i++) {
|
||||
const oldChild = oldHead.children[i];
|
||||
const newChild = newHead.children[i];
|
||||
|
||||
if (
|
||||
oldChild.type !== newChild.type ||
|
||||
(
|
||||
oldChild.type === 'tag' &&
|
||||
newChild.type === 'tag' &&
|
||||
(
|
||||
oldChild.tag !== newChild.tag ||
|
||||
JSON.stringify(oldChild.otherProps) !== JSON.stringify(newChild.otherProps)
|
||||
)
|
||||
) ||
|
||||
(
|
||||
oldChild.type === 'text' &&
|
||||
newChild.type === 'text' &&
|
||||
oldChild.text !== newChild.text
|
||||
)
|
||||
) {
|
||||
console.warn(`replace old node with new. old node:`, oldChild.node, 'new node:', newChild.node)
|
||||
|
||||
if (oldChild.type === 'fragment') {
|
||||
for (const c of oldChild.children) {
|
||||
oldChild.node.parentElement.removeChild(c.node);
|
||||
}
|
||||
}
|
||||
if (newChild.type === 'fragment') {
|
||||
for (const c of newChild.children) {
|
||||
oldChild.node.parentElement.insertBefore(c.node, oldChild.node);
|
||||
}
|
||||
}
|
||||
|
||||
oldChild.node.parentElement.replaceChild(newChild.node, oldChild.node);
|
||||
oldHead.children[i] = newChild;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (oldChild.type === 'tag' && newChild.type === 'tag') {
|
||||
for (const [propKey, propVal] of newChild.propsFunctions) {
|
||||
oldChild.node[propKey.toLowerCase()] = propVal;
|
||||
}
|
||||
}
|
||||
//@ts-ignore
|
||||
if (oldChild?.children?.length) {
|
||||
//@ts-ignore
|
||||
replaceChanged(oldChild, newChild)
|
||||
}
|
||||
if (oldChild.type === 'component' && newChild.type === 'component') {
|
||||
newChild.node = oldChild.node;
|
||||
newChild.children = oldChild.children;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resolveChildren(list: VirtualElementChildren, parent: VirtualElementValid) {
|
||||
export function resolveChildrenIntoVirtualElements(list: VirtualElementChildren): VirtualElement[] {
|
||||
const array = Array.isArray(list) ? list : [list];
|
||||
const result: VirtualElement[] = [];
|
||||
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
const child = array[i];
|
||||
if (typeof child === 'boolean') {
|
||||
parent.children[i] = {
|
||||
// node: null,
|
||||
node: document.createTextNode(" "),
|
||||
result.push({
|
||||
type: 'empty',
|
||||
};
|
||||
});
|
||||
} else if (typeof child !== 'object') {
|
||||
parent.children[i] = {
|
||||
// node: null,
|
||||
node: document.createTextNode(child),
|
||||
result.push({
|
||||
type: 'text',
|
||||
text: child,
|
||||
};
|
||||
text: `${child}`,
|
||||
});
|
||||
} else {
|
||||
if (child.type === 'fragment') {
|
||||
for (const c of child.children) {
|
||||
parent.node.appendChild(c.node);
|
||||
if (Array.isArray(child)) {
|
||||
result.push(...resolveChildrenIntoVirtualElements(child))
|
||||
} else {
|
||||
result.push(child)
|
||||
}
|
||||
}
|
||||
parent.children[i] = child;
|
||||
}
|
||||
|
||||
if (parent.type !== 'fragment') {
|
||||
parent.node.appendChild(parent.children[i].node);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user