import { isFn, noop } from 'uni-shared' import { wrapperMPEvent } from 'uni-helpers/patch' import { VD_SYNC, UI_EVENT, PAGE_CREATE, PAGE_CREATED, MOUNTED_DATA, UPDATED_DATA, VD_SYNC_VERSION } from '../../../constants' import { generateId } from '../../../helpers/util' import { removeVdSync, registerVdSync } from '../subscribe-handlers/on-vd-sync' import { vdSyncCallbacks } from '../subscribe-handlers/on-vd-sync-callback' import { hookKeyboardEvent } from './keyboard' import parseComponentCreateOptions from './parse-component-create-options' function wrapperEvent (event) { event.preventDefault = noop event.stopPropagation = noop return wrapperMPEvent(event) } const handleVdData = { [UI_EVENT]: function onUIEvent (vdBatchEvent, vd) { vdBatchEvent.forEach(([cid, nid, event]) => { nid = String(nid) const target = vd.elements.find(target => target.cid === cid && target.nid === nid) if (!target) { if (process.env.NODE_ENV !== 'production') { console.error(`event handler[${cid}][${nid}] not found`) } return } const type = event.type const mpEvent = wrapperEvent(event) if (type === 'focus' || type === 'blur') { hookKeyboardEvent(mpEvent, event => { target.dispatchEvent(type, event) }) } else { target.dispatchEvent(type, mpEvent) } }) } } function onVdSync (vdBatchData, vd) { vdBatchData.forEach(([type, vdData]) => { handleVdData[type](vdData, vd) }) } export class VDomSync { constructor (pageId, pagePath, pageQuery, pageVm) { this.pageId = pageId this.pagePath = pagePath this.pageQuery = pageQuery this.pageVm = pageVm this.batchData = [] this.vms = Object.create(null) this.initialized = false this.pageCreateData = false this.elements = [] // 目前仅存储事件 element this._init() } _init () { registerVdSync(this.pageId, (vdBatchData) => { onVdSync(vdBatchData, this) }) } addMountedVm (vm) { vm._$mounted() // 触发vd数据同步 this.addVdSyncCallback(function mounted () { vm.__call_hook('mounted') }) } addUpdatedVm (vm) { vm._$updated() // 触发vd数据同步 this.addVdSyncCallback(function mounted () { vm.__call_hook('updated') }) } addVdSyncCallback (callback) { isFn(callback) && vdSyncCallbacks.push(callback) } getVm (id) { return this.vms[id] } addVm (vm) { const id = vm._$id const oldVm = this.vms[id] if (oldVm) { const newId = generateId(oldVm, oldVm.$parent, VD_SYNC_VERSION) oldVm._$id = newId this.vms[newId] = oldVm this.elements.forEach(element => { const cid = element.cid element.cid = cid === id ? newId : cid }) } this.vms[id] = vm } removeVm (vm) { const cid = vm._$id if (vm === this.vms[cid]) { // 仅相同vm的才移除,否则保留 // 目前同一位置的vm,cid均一样 // 移除尚未同步的data this.batchData = this.batchData.filter(data => data[1][0] !== cid) delete this.vms[cid] } } addElement (elm) { this.elements.indexOf(elm) === -1 && this.elements.push(elm) } removeElement (elm) { const elmIndex = this.elements.indexOf(elm) if (elmIndex === -1) { if (process.env.NODE_ENV !== 'production') { console.error(`removeElement[${elm.cid}][${elm.nid}] not found`) } return } this.elements.splice(elmIndex, 1) } push (type, cid, data, options) { const typeData = [cid, data] if (options) { typeData.push(options) } this.batchData.push([type, typeData]) } find (type, cid) { return this.batchData.find(data => data[0] === type && data[1][0] === cid) } sendPageCreate (data) { this.pageCreateData = data UniServiceJSBridge.publishHandler(VD_SYNC, { data: [ [PAGE_CREATE, data] ], options: { timestamp: Date.now() } }, [this.pageId]) } flush () { if (!this.initialized) { this.initialized = true this.batchData.push([PAGE_CREATED, [this.pageId, this.pagePath, this.pageQuery]]) } const batchData = this.batchData.filter(data => { if (data[0] === UPDATED_DATA && !Object.keys(data[1][1]).length) { return false } return true }) this.batchData.length = 0 if (batchData.length) { UniServiceJSBridge.publishHandler(VD_SYNC, { data: batchData, options: { timestamp: Date.now() } }, [this.pageId]) } } restorePageCreate () { this.batchData.push([PAGE_CREATE, this.pageCreateData]) } restoreMountedData () { const addMountedData = (vm) => { if (vm._$id) { this.push(MOUNTED_DATA, vm._$id, vm._$data, parseComponentCreateOptions(vm)) } // TODO vue 中 $children 顺序不可靠,可能存在恢复误差 vm.$children.forEach(childVm => addMountedData(childVm)) } addMountedData(this.pageVm) } restorePageCreated () { this.batchData.push([PAGE_CREATED, [this.pageId, this.pagePath, this.pageQuery]]) } restore () { this.initialized = true this.batchData.length = 0 this.restorePageCreate() this.restoreMountedData() this.restorePageCreated() this.flush() } destroy () { this.batchData.length = 0 this.vms = Object.create(null) this.initialized = false this.elements.length = 0 removeVdSync(this.pageId) } }