UNPKG

@msom/dom

Version:

@msom/dom

344 lines (326 loc) 9.12 kB
import { Event, Nullable, compareObjects, getComponentDefinition, getGlobalData, isComponent, isObject, ownKeysAndPrototypeOwnKeys, parseClass, parseStyle, setGlobalData, } from "@msom/common"; import { createReaction, withoutTrack } from "@msom/reaction"; import { IComponent, IComponentProps } from "./IComponent"; import { IRef } from "./Ref"; type $DOM = { rendering?: IComponent; }; setGlobalData("@msom/dom", {} as $DOM); const componentVDOMMap = new WeakMap<IComponent, Msom.MsomNode | Nullable>(); export const TEXT_NODE = "TEXT_NODE"; function isIterator<T extends unknown = unknown>(v: unknown): v is Iterable<T> { if ((typeof v === "object" && v !== null) || typeof v === "function") { return Reflect.has(v, Symbol.iterator); } else { return false; } } export function createElement<T extends Msom.JSX.ElementType>( type: T, config: Omit<Msom.H<T>, "children"> | null | undefined, ...children: Msom.MsomNode[] ): Msom.MsomElement { config = config || {}; const _config = { ...config, children: children.map<Msom.MsomElement<any>>((v) => { const handle = (_v: Msom.MsomNode) => { if (isIterator(_v)) { return [..._v].map(handle); } else if ( typeof _v === "object" || _v === undefined || _v === false || _v === null ) { return _v; } else { return createTextElement(String(_v)); } }; return handle(v); }), }; return { type, props: _config, }; } function createTextElement(text: string | Function): Msom.MsomElement { return { type: TEXT_NODE, props: { nodeValue: text, children: [], }, }; } function createDom< T extends Msom.JSX.ElementType | keyof Msom.JSX.IntrinsicElements >(element: Msom.MsomElement<T>) { const { children, class: _class, style, $key, $ref, $context, ...props } = element.props; // 创建元素 const dom = element.type === TEXT_NODE ? document.createTextNode("") : document.createElement(element.type as string); // 给元素赋属性值 // 处理class if (_class) { props.className = `${parseClass(_class)} ${props.className || ""}`.trim(); } // 处理style if (style) { Object.assign(props, { style: parseStyle(style) }); } // 处理事件 Reflect.ownKeys(props) .filter((key) => typeof key === "string" && key.startsWith("on")) .forEach((key: string) => { const event = Reflect.get(props, key, props); Reflect.deleteProperty(props, key); dom.addEventListener( key.slice(2).toLowerCase(), function (this: typeof dom, e) { const _e = new Proxy(e, { get: (target, prop, receiver) => { if (prop === "nativeEvent") { return receiver; } const value = Reflect.get(target, prop, target); return typeof value === "function" ? value.bind(target) : value; }, set: Reflect.set, }); event.bind(this)(_e); } ); }); Object.assign(dom, props); return dom; } const eventBindingMap = new WeakMap< WeakKey, { [K in PropertyKey]: Parameters<Event["on"]>[1] } >(); const componentCache = new Map<any, IComponent>(); function _mountComponent(element: Msom.MsomElement<any>, container: Element) { withoutTrack(() => { let { children, $ref, ...props } = element.props; const componentDefinition = getComponentDefinition(element.type); if (!componentDefinition) { return; } // 处理自定义事件 // 组件声明的事件 const { $events } = componentDefinition; const $eventKeys = ownKeysAndPrototypeOwnKeys($events); const { ..._props } = props; // 去除props中的事件 if ($eventKeys.size()) { for (const eventKey of $eventKeys) { if (Reflect.has(_props, eventKey)) { delete _props[eventKey]; } } } // lifeCircle: create const component = (() => { let component = componentCache.get(_props.$key); if (!component) { component = new element.type(_props) as IComponent; _props.$key != undefined && componentCache.set(_props.$key, component); } else { component.set(_props as IComponentProps); } return component; })(); // 处理传递的子元素 children = [children].flat(); if (children && children.length > 0) { const c = children.map((c) => { if (c.type === TEXT_NODE && typeof c.props.nodeValue === "function") { return c.props.nodeValue; } else { return c; } }); component.setJSX(c.length > 1 ? c : c[0]); } // 处理ref if ($ref) { const _$ref = [$ref].flat(); for (const ref of _$ref) { ref.set(component); } } // 事件绑定 if ($eventKeys.size()) { const binding = eventBindingMap.get(component) || {}; eventBindingMap.set(component, binding); for (const _key of $eventKeys) { const key = _key as keyof (typeof component extends IComponent< any, infer Es > ? Es : never); // 清除上次注册的事件 component.un(key, binding[key]); Reflect.deleteProperty(binding, key); // 绑定新事件 const on = props[key]; if (on && typeof on === "function") { component.on(key, on); Object.assign(binding, { [key]: { on } }); } } } // lifeCircle: created component.created(); // 挂载组件 const domGlobalData = getGlobalData("@msom/dom") as $DOM; const { rendering } = domGlobalData; component.$owner = rendering; component.onunmounted( createReaction( () => { const prevVDOM = componentVDOMMap.get(component); const mounted = component.isMounted(); if (component.el) { container.removeChild(component.el); } const vDOM = component.mount() || undefined; componentVDOMMap.set(component, vDOM); const isChanged = patchVDOM(vDOM, prevVDOM); const dom = renderer(vDOM, container); component.rendered(); if (!isChanged) { return; } component.el = dom as HTMLElement; if (dom) { // 将类组件实例附着在dom上 // TODO: 生产环境禁用 if (!Reflect.get(dom, "$owner")) { Object.assign(dom, { $owner: component }); } container.appendChild(dom); if (!mounted) { component.mounted(); } } }, { scheduler: "nextFrame", // scheduler: (cb) => { // requestIdleCallback(({ timeRemaining }) => { // timeRemaining() > 0 && cb(); // }); // }, } ).disposer() ); }); } function renderer( element: Msom.MsomNode, container: Element ): HTMLElement | Text | undefined { if (!element) { return; } if (typeof element !== "object") { element = createTextElement( typeof element === "function" ? element : String(element) ); } if (isIterator(element)) { for (const e of element) { renderer(e, container); } return; } const _element = element as Exclude<typeof element, Iterable<any>>; let { children, $ref } = _element.props; if (typeof _element.type === "function") { if (isComponent(_element.type)) { // 类组件 _mountComponent(_element, container); } else { // TODO: 函数组件 // renderFunctionComponent(_element, container); } } else { // 普通元素 const dom: HTMLElement | Text = createDom(_element); // 普通元素ref绑定生成的元素 if ($ref) { const refs: IRef<any>[] = [$ref].flat(); refs.forEach((ref) => ref.set(dom)); } // children children = [children].flat(); if (children && children.length > 0) { [...children].flat().forEach((child) => { const childDom = renderer(child, dom as HTMLElement); if (childDom) { dom.appendChild(childDom); } }); } container.appendChild(dom); return dom; } } export function mountWith( mount: () => Msom.MsomElement | void, container: Element ) { const element = mount(); element && renderer(element, container); } export function mountComponent(component: IComponent, container: Element) { const element = component.mount(); element && renderer(element, container); } /** * * @param vDOM * @param prevVDOM * @returns 返回时否有变 */ function patchVDOM( vDOM: Msom.MsomNode | Nullable, prevVDOM: Msom.MsomNode | Nullable ) { if (!prevVDOM) { return true; } else { if (isObject(vDOM) && isObject(prevVDOM)) { return !compareObjects(vDOM, prevVDOM); } else { return !Object.is(vDOM, prevVDOM); } } }