UNPKG

hta

Version:

The tiny framework for building Hyper Text Application with ease

184 lines (166 loc) 4.48 kB
import createInstance from "./createInstance"; import createMarker from "./createMarker"; import isEqual from "./isEqual"; import objectEqual from "./objectEqual"; import tryReorder from "./tryReorder"; import tryUnmount from "./tryUnmount"; import { EMPTY_OBJECT, FUNC, INNER, RENDERER, SCOPE } from "./types"; import { enqueue1, isArray, margeState } from "./util"; export default function renderComponent( renderContent, app, context, parent, key, marker, component, props, children ) { if (!props) props = EMPTY_OBJECT; if (children) props = { children, ...props }; if (component.type === RENDERER) { return component(app, context, parent, key, marker, props, renderContent); } let instance = createInstance(parent, key, component, null, mount, [ renderContent, app, context, marker, component, ]); instance.update(props); } function mount(renderContent, app, context, marker, component) { let innerMaker = createMarker(); let prevProps; let data = {}; let firstRender = true; let extras = app.extras.component; let updateAsyncToken; let storeValues = []; let unmounted = false; let currentState; let error; let subscriptions = new Map(); let rendered = false; let instance = { updates: 0, firstNode() { return data[INNER].firstNode(); }, forceUpdate, forceUpdateAsync, update, unmount, select, reorder, subscribe, }; let state = {}; let api = { select, dispatch: app.store.dispatch, getState, setState, }; function getState(initializer) { if (!rendered && arguments.length) { state = (typeof initializer === "function" ? initializer() : initializer) || state; } return state; } function setState(changes, callback) { let next = margeState(state, changes); if (next !== state) { state = next; forceUpdate(); callback && callback(state); } } function subscribe(subscribeFn, handler) { if (subscriptions.has(subscribeFn)) return; subscriptions.set(subscribeFn, subscribeFn(handler)); } function invoke(handler, args) { handler && handler({ ...args, app, context, instance, component, props: prevProps }); } function update(props) { if (prevProps && objectEqual(prevProps, props)) return; forceUpdate(props); } function reorder() { marker.before(innerMaker); tryReorder(data[INNER]); } function forceUpdate() { arguments.length && (prevProps = arguments[0]); let prevScope = SCOPE.current; SCOPE.current = { app, context, component: instance, props: prevProps }; currentState = null; error && invoke(extras.error, { error }); storeValues = []; invoke(extras.updating); try { instance.updates++; let content = component(prevProps, api); rendered = true; renderContent(app, context, data, INNER, innerMaker, content); } catch (error) { invoke(extras.error, { error }); } finally { instance.updates--; SCOPE.current = prevScope; if (firstRender) { firstRender = false; invoke(extras.mount); } invoke(extras.updated); } } function handleStateChange() { if (unmounted) return; error = undefined; try { let state = app.store.getState(); if (storeValues.every((x) => isEqual(x.selector(state), x.value))) return; } catch (e) { error = e; } forceUpdate(); } function select(selector) { if (typeof selector !== FUNC) { if (isArray(selector)) return selector.map(select); return select((state) => state[selector]); } if (!currentState) currentState = app.store.getState(); let value = selector(currentState); storeValues[storeValues.length] = { selector, value }; subscribe(app.store.subscribe, handleStateChange); return value; } function forceUpdateAsync() { let token = (updateAsyncToken = {}); enqueue1(() => { token === updateAsyncToken && forceUpdate(); }); } function unmount() { if (unmounted) return; unmounted = true; for (let unsubscribe of subscriptions.values()) { unsubscribe && unsubscribe(); } // if an error occurs during rendering phase, no INNER rendered tryUnmount(data[INNER]); innerMaker.remove(); invoke(extras.unmount); } marker.before(innerMaker); invoke(extras.init); return instance; }