|
| 1 | +import { EventType } from 'overmind' |
| 2 | +import { h } from 'snabbdom/h' |
| 3 | +import { VNode, VNodeData } from 'snabbdom/vnode' |
| 4 | + |
| 5 | +import { getApp, getName, patch } from './utils' |
| 6 | + |
| 7 | +const SELF = 'self' |
| 8 | + |
| 9 | +function copyVNode(source: VNode, target: VNode): void { |
| 10 | + target.elm = source.elm |
| 11 | + |
| 12 | + target.data = source.data |
| 13 | + target.children = source.children |
| 14 | + target.text = source.text |
| 15 | + target.elm = source.elm |
| 16 | +} |
| 17 | + |
| 18 | +let nextId = 0 |
| 19 | +let nextComponentId = {} |
| 20 | +let nextComponentInstanceId = {} |
| 21 | + |
| 22 | +function init(componentVNode: VNode): void { |
| 23 | + try { |
| 24 | + const component = (componentVNode.data as VNodeData).component |
| 25 | + const name = component.name |
| 26 | + const tree = component.tree |
| 27 | + let currentNode = componentVNode |
| 28 | + |
| 29 | + function createComponentVNode(newState) { |
| 30 | + return (component.fn as any)(component.props, { |
| 31 | + changeState: update, |
| 32 | + state: |
| 33 | + typeof newState === 'undefined' |
| 34 | + ? (currentNode.data as VNodeData).component.state |
| 35 | + : newState, |
| 36 | + store: tree.state, |
| 37 | + actions: getApp().actions, |
| 38 | + }) |
| 39 | + } |
| 40 | + |
| 41 | + function update(newState?) { |
| 42 | + tree.track(onFlush) |
| 43 | + try { |
| 44 | + currentNode.elm = componentVNode.elm |
| 45 | + const newNode = createComponentVNode(newState) |
| 46 | + newNode.elm = componentVNode.elm |
| 47 | + currentNode = patch(currentNode, newNode) |
| 48 | + currentNode.elm = componentVNode.elm |
| 49 | + } catch (error) { |
| 50 | + console.log(name, error.message, currentNode) |
| 51 | + } |
| 52 | + ;(currentNode.data as any).component = (componentVNode.data as any).component |
| 53 | + copyVNode(currentNode, componentVNode) |
| 54 | + tree.stopTracking() |
| 55 | + } |
| 56 | + |
| 57 | + function onFlush(_, __, flushId) { |
| 58 | + update() |
| 59 | + |
| 60 | + getApp().eventHub.emitAsync(EventType.COMPONENT_UPDATE, { |
| 61 | + componentId: (currentNode.data as any).component.componentId, |
| 62 | + componentInstanceId: (currentNode.data as any).component |
| 63 | + .componentInstanceId, |
| 64 | + name, |
| 65 | + flushId, |
| 66 | + paths: Array.from(tree.pathDependencies) as any, |
| 67 | + }) |
| 68 | + } |
| 69 | + tree.track(onFlush) |
| 70 | + currentNode = createComponentVNode(undefined) |
| 71 | + ;(currentNode.data as any).component = (componentVNode.data as any).component |
| 72 | + |
| 73 | + if (currentNode.sel !== SELF) { |
| 74 | + throw new Error( |
| 75 | + `You are not returning a <self /> element from the component ${name} ` |
| 76 | + ) |
| 77 | + } |
| 78 | + tree.stopTracking() |
| 79 | + copyVNode(currentNode, componentVNode) |
| 80 | + |
| 81 | + if (!(name in nextComponentId)) { |
| 82 | + nextComponentId[name] = nextId++ |
| 83 | + } |
| 84 | + if (!(name in nextComponentInstanceId)) { |
| 85 | + nextComponentInstanceId[name] = 0 |
| 86 | + } |
| 87 | + |
| 88 | + nextComponentInstanceId[name] = nextId++ |
| 89 | + |
| 90 | + const componentId = nextComponentId |
| 91 | + const componentInstanceId = nextComponentInstanceId[name] |
| 92 | + |
| 93 | + Object.assign(component, { |
| 94 | + componentId, |
| 95 | + componentInstanceId, |
| 96 | + }) |
| 97 | + |
| 98 | + getApp().eventHub.emitAsync(EventType.COMPONENT_ADD, { |
| 99 | + componentId, |
| 100 | + componentInstanceId, |
| 101 | + name, |
| 102 | + paths: Array.from(tree.pathDependencies) as any, |
| 103 | + }) |
| 104 | + } catch (error) { |
| 105 | + console.log(error.message) |
| 106 | + } |
| 107 | +} |
| 108 | + |
| 109 | +function prepatch(oldVnode: VNode, vnode: VNode): void { |
| 110 | + try { |
| 111 | + const oldComponent = (oldVnode.data as VNodeData).component |
| 112 | + const newComponent = (vnode.data as VNodeData).component |
| 113 | + const oldProps = oldComponent.props || {} |
| 114 | + const props = newComponent.props || {} |
| 115 | + const oldPropsKeys = Object.keys(oldProps) |
| 116 | + const propsKeys = Object.keys(props) |
| 117 | + const overmind = { |
| 118 | + changeState: newComponent.changeState, |
| 119 | + state: newComponent.state, |
| 120 | + store: newComponent.tree.state, |
| 121 | + actions: getApp().actions, |
| 122 | + } |
| 123 | + |
| 124 | + if ( |
| 125 | + oldComponent.fn !== newComponent.fn || |
| 126 | + oldPropsKeys.length !== propsKeys.length |
| 127 | + ) { |
| 128 | + copyVNode((newComponent.fn as any)(props, overmind), vnode) |
| 129 | + return |
| 130 | + } |
| 131 | + |
| 132 | + const keys = new Set<string>([...oldPropsKeys, ...propsKeys]) |
| 133 | + |
| 134 | + for (let key of keys) { |
| 135 | + if (oldProps[key] !== props[key]) { |
| 136 | + copyVNode((newComponent.fn as any)(props, overmind), vnode) |
| 137 | + return |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + copyVNode(oldVnode, vnode) |
| 142 | + } catch (error) { |
| 143 | + console.log(error.message) |
| 144 | + } |
| 145 | +} |
| 146 | + |
| 147 | +function postpatch(prevNode, nextNode) { |
| 148 | + try { |
| 149 | + nextNode.data.component = prevNode.data.component |
| 150 | + } catch (error) { |
| 151 | + console.log(error.message) |
| 152 | + } |
| 153 | +} |
| 154 | + |
| 155 | +function destroy(node) { |
| 156 | + try { |
| 157 | + getApp().disposeTree(node.data.component.tree) |
| 158 | + } catch (error) { |
| 159 | + console.log(error.message) |
| 160 | + } |
| 161 | +} |
| 162 | + |
| 163 | +export const component = (component, props, children) => { |
| 164 | + const name = getName(component.name) |
| 165 | + |
| 166 | + return h(name, { |
| 167 | + key: props ? props.key : undefined, |
| 168 | + hook: { |
| 169 | + init, |
| 170 | + prepatch, |
| 171 | + postpatch, |
| 172 | + destroy, |
| 173 | + }, |
| 174 | + component: { |
| 175 | + fn: component, |
| 176 | + name, |
| 177 | + state: undefined, |
| 178 | + changeState: null, |
| 179 | + props: { |
| 180 | + ...props, |
| 181 | + children: children.length ? children : null, |
| 182 | + }, |
| 183 | + tree: getApp().proxyStateTree.getTrackStateTree(), |
| 184 | + }, |
| 185 | + }) |
| 186 | +} |
0 commit comments