UNPKG

coolink-ts

Version:

Coocaa Coolink APP JavaScript library base on Thresh-lib.

549 lines (505 loc) 19.5 kB
/** * MIT License * * Copyright (c) 2020 ManBang Group * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * */ import Widget from './Widget' import { ListView } from './basicWidget' import Util from '../shared/Util' import { RenderData, ThreshProvider } from '../types/type' import threshApp from './ThreshApp' import appContainer from './AppContainer' import TimerManager from '../manager/TimerManager' import UtilManager from '../manager/UtilManager' export type PropChildrenVNodeType = VNode | VNode[] const ChildrenKey: string = 'children' type W = Widget<any, any> type Key = string | number | void type Ref = ((widget: W) => void) | void export interface PropChildrenVNode { [childrenPropName: string]: PropChildrenVNodeType } interface NodeEvents { [eventName: string]: Function } interface VNodeUpdateInfo { updateNodeId: string, // flutter 端需要被更新的原子组件 node id invokeUpdateNodeId: string // js 端需要出发 update 的自定义组件 node id } export default class VNode { // 节点类型 type: string // 节点属性 props: any // 父节点 parent: VNode | void // 子节点 children: VNode[] = [] // 原子节点属性上的子节点 basicWidgetPropChildren: PropChildrenVNode = {} // 节点的 widget 实例 widget: W // 节点实例构造器 widgetBuilder: any // 是否原子节点 isBasicWidget: boolean // 原子节点 children 属性对应的 name childrenMapedName: string = ChildrenKey // 原子节点 children 属性对应的类型是否为 array childrenIsArray: boolean = true // 节点 id nodeId: string // 节点为 NativeView 时的 viewId nativeViewId?: string // 节点上的事件 events: NodeEvents = {} // 节点所属页面 pageName?: string // 子节点中存在 Page 时保存 Page Node pageNode: VNode | void // 节点是否已挂载 hasMount: boolean = false // context id contextId: string = appContainer.contextId as string isPageNode: boolean = false isNativeViewNode: boolean = false isInputNode: boolean = false key?: Key parentKey?: Key ref?: Ref updateInfo?: VNodeUpdateInfo shouldRender: boolean = true private _refHasMount: boolean = false static nodeIdIndex: number = 0 static getNodeId(type: string): string { return `${type}#${++VNode.nodeIdIndex}` } static asyncEventTypes: string[] = [ 'onRefresh', 'onLoadMore', 'onStopBack', ] static eventTypes: string[] = [ 'onTap', 'onLongTap', 'onLongTapStart', 'onLongTapEnd', 'onPan', 'onScroll', 'onChange', 'onLoad', 'onLayout', 'onFocus', 'onBlur', 'onActionsOpen', 'onActionsClose', 'willDragStatusChange', 'onDragStatusChange', 'onDragPositionChange', 'onOpen', 'onSubmitted', 'onClicked', 'onEnterClicked', 'onRawKeyFocusChanged', 'dispatchKeyEvent', ...VNode.asyncEventTypes, ] static throttledEventTypes: string[] = ['onTap'] constructor({ type, props, widgetBuilder, isBasicWidget, childrenMapedName, childrenIsArray, parent, key, ref, }: { type: string, props: any, widgetBuilder: any, isBasicWidget: boolean, childrenMapedName: string childrenIsArray: boolean parent?: VNode | void, key?: Key, ref?: Ref, }) { this.type = type this.props = props this.widgetBuilder = widgetBuilder this.isBasicWidget = isBasicWidget this.childrenMapedName = childrenMapedName this.childrenIsArray = childrenIsArray this.parent = parent this.key = key this.ref = ref this.nodeId = VNode.getNodeId(type) if (isBasicWidget) { this.isPageNode = type === 'Page' this.isNativeViewNode = type === 'NativeView' this.isInputNode = type === 'Input' } if (this.isNativeViewNode) { this.nativeViewId = `${this.nodeId}#${this.props.type}#${Date.now()}` } this.findVNodeInProps() } // 在 props 中查找所有的 vnode findVNodeInProps() { for (let key in this.props) { const item: any = this.props[key] if (item instanceof VNode && this.isBasicWidget) { item.parent = this this.basicWidgetPropChildren[key] = item } if (Array.isArray(item)) { item.forEach((subItem: any) => { if (subItem instanceof VNode) { subItem.parent = this if (key === ChildrenKey) this.appendChild(subItem) else if (this.isBasicWidget) this.appendChildInArrayProps(subItem, key) } }) } } } // 在 props 中查找事件 findEventsInProps() { if (!this.isBasicWidget) return this.events = {} let eventId: number = 0 VNode.eventTypes.forEach(key => { let prop: any = this.props[key] if (prop instanceof Function) { const eventCacheId: string = `event/${++eventId}` if (VNode.throttledEventTypes.includes(key)) prop = Util.throttle(prop) this.events[eventCacheId] = prop this.props[`_${key}Id`] = eventCacheId } }) } // 触发组件实例 render doRender() { if (!this.shouldRender) return this.shouldRender = false if (!this.widget) { this.widget = new this.widgetBuilder(this.props) this.widget.__vNode__ = this } this.widget.props = this.props if (!this.isBasicWidget) { const node: VNode | void = this.widget.render() if (!node) throw new Error(`Widget's render method must return a widget. Error in: <${this.type} />`) node.parent = this this.children = [node] } } // 执行组件更新 doUpdate(): VNode | void { if(!this.widget) { this.widget = new this.widgetBuilder(this.props) this.widget.__vNode__ = this } const newRenderNode: VNode | void = this.widget.render() if (!newRenderNode) throw new Error(`Widget's render method must return a widget. Error in: <${this.type} />`) return newRenderNode } // 新旧节点融合 doMerge(oldNode: VNode) { this.contextId = oldNode.contextId this.hasMount = true this.nodeId = oldNode.nodeId this.nativeViewId = oldNode.nativeViewId this.widget = oldNode.widget this.widget.__vNode__ = this } // 将 vNode 转换为 flutter 可用的 json toRenderData(pageName: string, setStateful: boolean = true): RenderData { this.pageName = pageName if (this.pageNode) this.pageNode = void 0 this.findEventsInProps() this.doRender() if (!this.isBasicWidget) { // 非原子组件,children 只有 1 个元素 // flutter version 1.1.0 及以上,所有自定义组件都是 stateful // flutter version 1.1.0 以下,只有声明了 state 的自定义组件才是 stateful if (this.children.length) { const child: VNode = this.children[0] if (Util.isNil(child.key)) child.parentKey = this.key || this.parentKey return child.toRenderData(pageName, true) } } else { let childrenRenderData: any = {} this.mapChildren((vNode: VNode, key: string, fromArray: boolean) => { if (!fromArray) childrenRenderData[key] = vNode.toRenderData(pageName, false) else { if (!childrenRenderData[key]) childrenRenderData[key] = [] childrenRenderData[key].push(vNode.toRenderData(pageName, false)) } if (key === ChildrenKey && !this.childrenIsArray) { childrenRenderData[this.childrenMapedName] = childrenRenderData[key][0] delete childrenRenderData[key] } }) if (this.isPageNode) { const rootNode: VNode = this.fetchRootNode() rootNode.pageNode = this } if (this.isNativeViewNode) { const params = this.props.params || {} if (!params.__viewId__) { params.__viewId__ = this.nativeViewId this.props.params = params } } return { name: this.type, widgetId: this.nodeId, isStateful: setStateful, pageName, props: this.execPropsProvider(Object.assign({}, this.props, childrenRenderData)), key: this.key || this.parentKey } } } private execPropsProvider(props: Object): Object { const propsProviders: ThreshProvider[] = threshApp.providers.propsProvider || [] let index = propsProviders.length - 1 while (index > -1) { const provider = propsProviders[index--] const tempProps = provider.propsProvider(props) if (tempProps) props = tempProps } return props } // 在当前 vnode 上查找目标节点 fetch(targetNodeId: string): VNode { if (this.nodeId === targetNodeId) return this let targetVNode: VNode this.mapChildren((vNode: VNode) => { if (vNode.nodeId === targetNodeId) { targetVNode = vNode return false } targetVNode = vNode.fetch(targetNodeId) return !targetVNode }) return targetVNode } // 查找距离当前节点向上最近的可更新的原子节点 fetchNearlyCanUpdateBasicNode(): VNode { const node: VNode = this.fetchNearlyCustomNode() let firstChildNode: VNode = node.children[0] while (firstChildNode && !firstChildNode.isBasicWidget) { firstChildNode = firstChildNode.children[0] } return firstChildNode } // 查找距离当前节点向上最近的非原子节点 fetchNearlyCustomNode(): VNode { if (!this.isBasicWidget) return this const parent: VNode | void = this.parent if (parent) return parent.fetchNearlyCustomNode() return this } // 查找距离当前节点向下最近的原子节点 fetchNearlyBasicNode(): VNode { if (this.isBasicWidget) return this return this.children[0].fetchNearlyBasicNode() } // 查找根节点 fetchRootNode(): VNode { if (!this.parent) return this const parent: VNode | void = this.parent if (parent) return parent.fetchRootNode() return this } // 查找页面名 fetchNodePageName(): string { if (this.pageName) return this.pageName if (this.parent) return this.parent.fetchNodePageName() return '' } // 毕竟两个节点的 type & key 是否相等 isSameAs(otherNode: VNode) { return this.type === otherNode.type && this.key === otherNode.key } // 触发组件上的事件 async invokeEvent(targetNodeId: string, eventId: string, eventType: string, params: any) { const targetNode: VNode = this.fetch(targetNodeId) if (!targetNode) return const eventFn: Function = targetNode.events[eventId] if (!eventFn) return if (!eventType || !VNode.asyncEventTypes.includes(eventType)) eventFn(params) else { try { await eventFn(params) } finally { // Hack fix // 异步操作完成后的32毫秒再发送停止异步操作的通知 // 异步操作中可能含有setState,从而保证停止通知在setState后再发送 TimerManager.setTimeout(() => { this.stopAsyncEvent(targetNode, eventType) }, 32) } } } // 停止异步事件 stopAsyncEvent(node: VNode, eventType: string) { if (!node || !eventType || !VNode.asyncEventTypes.includes(eventType)) return if (node.type === 'ListView') { (node.widget as unknown as ListView).stopAsyncOperate(eventType === 'onRefresh' ? 'refresh' : 'loadMore') } } // 触发组件生命周期方法 invokeLifeCycle(lifeStep: string) { if (!this.widget || !LifeCycle.isExist(lifeStep)) return const lifeStepIsDidUnmount = lifeStep === LifeCycle.widgetDidUnmount // 当生命周期不是卸载时,从子组件向父组件依次触发 if (!lifeStepIsDidUnmount) { this._invokeChildrenLifeCycle(lifeStep) } if (!this.hasMount && lifeStep === LifeCycle.widgetDidUpdate) { this.hasMount = true this._invokeMountRef() this.widget.widgetDidMount() } else { this.hasMount = !lifeStepIsDidUnmount this._invokeMountRef() this.widget[lifeStep]() } // 当组件卸载时,didUnmount从父组件向子组件触发 if (lifeStepIsDidUnmount) { this._invokeChildrenLifeCycle(lifeStep) } } private _invokeChildrenLifeCycle(lifeStep: string) { this.mapChildren((vNode: VNode) => { vNode.invokeLifeCycle(lifeStep) }) } private _invokeMountRef() { if (!this.ref) return if (this.hasMount && !this._refHasMount) { this.ref(this.widget) this._refHasMount = true } else if (!this.hasMount && this._refHasMount) { this.ref(undefined) this._refHasMount = false } } // 向数组尾部添加子节点 appendChild(child: VNode, target: VNode[] = this.children) { if (target.includes(child)) this.removeChild(child) target.push(child) } // 移除数组中的某个节点 removeChild(child: VNode, target: VNode[] = this.children) { const removeIndex: number = target.indexOf(child) if (removeIndex > -1) target.splice(removeIndex, 1) } // 向 basicWidgetPropChildren 中的某个数组属性添加子节点 appendChildInArrayProps(child: VNode, propName: string) { this.appendChild(child, this.getTargetChildrenArrayInPropChildren(propName)) } // 遍历当前节点或目标中的所有子节点 private mapChildren( cb: (vNode: VNode, key: string, fromArray: boolean) => boolean | void, mapTarget?: PropChildrenVNode | VNode[] ) { if (!mapTarget) { for (let i = 0; i < this.children.length; i++) { const item: VNode = this.children[i] if (item instanceof VNode && cb(item, ChildrenKey, true) === false) return } for (let key in this.basicWidgetPropChildren) { const item: PropChildrenVNodeType = this.basicWidgetPropChildren[key] if (!Array.isArray(item)) { if (item instanceof VNode && cb(item, key, false) === false) return } else { for (let i = 0; i < item.length; i++) { if (item[i] instanceof VNode && cb(item[i], key, true) === false) return } } } } else { if (Util.isArray(mapTarget)) { for (let i = 0; i < (mapTarget as VNode[]).length; i++) { const item: VNode = mapTarget[i] if (item instanceof VNode && cb(item, '', true) === false) return } } else { for (let key in mapTarget) { const item: PropChildrenVNodeType = mapTarget[key] if (!Array.isArray(item)) { if (item instanceof VNode && cb(item, key, false) === false) return } else { for (let i = 0; i < item.length; i++) { if (item[i] instanceof VNode && cb(item[i], key, true) === false) return } } } } } } // 获取 basicWidgetPropChildren 中的某个数组属性 private getTargetChildrenArrayInPropChildren(propName: string): VNode[] { if (!this.basicWidgetPropChildren[propName]) this.basicWidgetPropChildren[propName] = [] return this.basicWidgetPropChildren[propName] as VNode[] } } /** * 生命周期类 */ export class LifeCycle { private static lifes: string[] = [ 'widgetDidMount', 'widgetDidUpdate', 'widgetDidUnmount', /// Called when the current route has been pushed. /// 当前的页面被push显示到用户面前 viewWillAppear. 'pageDidPush', /// Called when the current route has been popped off. /// 当前的页面被pop viewWillDisappear. 'pageDidPop', /// Called when the top route has been popped off, and the current route /// shows up. /// 上面的页面被pop后当前页面被显示时 viewWillAppear. 'pageDidPopNext', /// Called when a new route has been pushed, and the current route is no /// longer visible. /// 从当前页面push到另一个页面 viewWillDisappear. 'pageDidPushNext', ] static widgetDidMount: string = LifeCycle.lifes[0] static widgetDidUpdate: string = LifeCycle.lifes[1] static widgetDidUnmount: string = LifeCycle.lifes[2] static pageDidPush: string = LifeCycle.lifes[3] static pageDidPop: string = LifeCycle.lifes[4] static pageDidPopNext: string = LifeCycle.lifes[5] static pageDidPushNext: string = LifeCycle.lifes[6] static isExist(lifeStep: string): boolean { return LifeCycle.lifes.indexOf(lifeStep) > -1 } }