From 3997acad389bb682a93bc5ded25bebb7d2451ca7 Mon Sep 17 00:00:00 2001 From: yuche Date: Wed, 6 Dec 2017 11:34:17 +0800 Subject: [PATCH] refactor: simplify event system --- jest.config.js | 2 +- packages/nerv/__tests__/event.spec.js | 6 +- packages/nerv/src/create-element.ts | 10 +- packages/nerv/src/hooks/event-hook.ts | 176 +++++++++++--------------- packages/nerv/src/hooks/vhook.ts | 6 - packages/nerv/src/vdom/patch.ts | 22 +++- packages/nerv/src/vdom/unmount.ts | 11 +- 7 files changed, 102 insertions(+), 131 deletions(-) delete mode 100644 packages/nerv/src/hooks/vhook.ts diff --git a/jest.config.js b/jest.config.js index e125abaa..c13e1952 100644 --- a/jest.config.js +++ b/jest.config.js @@ -28,7 +28,7 @@ module.exports = { }, rootDir: __dirname, testMatch: [ - // '/packages/nerv/__tests__/refs.spec.js', + // '/packages/nerv/__tests__/event.spec.js', // '/packages/nerv/__tests__/component.spec.js', // '/packages/nerv/__tests__/render.spec.js', // '/packages/nerv/__tests__/lifecycle.spec.js', diff --git a/packages/nerv/__tests__/event.spec.js b/packages/nerv/__tests__/event.spec.js index 267ae865..37f58c31 100644 --- a/packages/nerv/__tests__/event.spec.js +++ b/packages/nerv/__tests__/event.spec.js @@ -189,7 +189,7 @@ describe('Events', () => { const input = scratch.childNodes[0] const proto = input.constructor.prototype const addEventListenerSpy = sinon.spy(proto, 'addEventListener') - // const removeEventListenerSpy = sinon.spy(proto, 'removeEventListener') + const removeEventListenerSpy = sinon.spy(proto, 'removeEventListener') // https://stackoverflow.com/questions/1096436/document-getelementbyidid-focus-is-not-working-for-firefox-or-chrome input.focus() await nextTick() @@ -202,11 +202,11 @@ describe('Events', () => { scratch.childNodes[0].focus() await nextTick() // @TODO: IMPORTANT - // expect(removeEventListenerSpy.called).toBeTruthy() + expect(removeEventListenerSpy.called).toBeTruthy() }) // @TODO - it.skip('should change/fix event name', () => { + it('should change/fix event name', () => { const container = document.createElement('div') document.body.appendChild(container) const onchange = function () {} diff --git a/packages/nerv/src/create-element.ts b/packages/nerv/src/create-element.ts index db9837a9..2d3046e0 100644 --- a/packages/nerv/src/create-element.ts +++ b/packages/nerv/src/create-element.ts @@ -1,13 +1,11 @@ import h from './vdom/h' import { isFunction, - isString, - isAttrAnEvent + isString } from 'nerv-utils' import FullComponent from './full-component' import StatelessComponent from './stateless-component' import CurrentOwner from './current-owner' -import EventHook from './hooks/event-hook' import { Props, Component, @@ -20,12 +18,6 @@ function transformPropsForRealTag (type: string, props: Props) { const newProps: Props = {} for (const propName in props) { const propValue = props[propName] - if (isAttrAnEvent(propName)) { - newProps[propName] = !(propValue instanceof EventHook) - ? new EventHook(propName, propValue) - : propValue - continue - } if (propName === 'defaultValue') { newProps.value = props.value || props.defaultValue continue diff --git a/packages/nerv/src/hooks/event-hook.ts b/packages/nerv/src/hooks/event-hook.ts index 000258fc..31b72e0b 100644 --- a/packages/nerv/src/hooks/event-hook.ts +++ b/packages/nerv/src/hooks/event-hook.ts @@ -1,5 +1,4 @@ import { isFunction, MapClass } from 'nerv-utils' -import { VHook } from './vhook' const ONINPUT = 'oninput' const ONPROPERTYCHANGE = 'onpropertychange' @@ -67,105 +66,77 @@ if (navigator.userAgent.indexOf('MSIE 9') >= 0) { }) } -class EventHook { - vhook = VHook.Event - eventName: string +export function attachEvent ( + domNode: Element, + eventName: string, handler: Function - constructor (eventName: string, handler) { - this.eventName = getEventName(eventName) - this.handler = handler +) { + eventName = fixEvent(domNode, eventName) + /* istanbul ignore next */ + if (eventName === ONPROPERTYCHANGE) { + processOnPropertyChangeEvent(domNode, handler) + return } - - hook (node, prop, prev) { - if ( - prev && - prev.vhook === VHook.Event && - prev.handler === this.handler && - prev.eventName === this.eventName - ) { - return + let delegatedRoots = delegatedEvents.get(eventName) + if (unbubbleEvents[eventName] === 1) { + if (!delegatedRoots) { + delegatedRoots = new MapClass() } - const eventName = fixEvent(node, this.eventName) - this.eventName = eventName - /* istanbul ignore next */ - if (eventName === ONPROPERTYCHANGE) { - processOnPropertyChangeEvent(node, this.handler) - return + const event = attachEventToNode(domNode, eventName, delegatedRoots) + delegatedEvents.set(eventName, delegatedRoots) + if (isFunction(handler)) { + delegatedRoots.set(domNode, { + eventHandler: handler, + event + }) } - let delegatedRoots = delegatedEvents.get(eventName) - if (unbubbleEvents[eventName] === 1) { - if (!delegatedRoots) { - delegatedRoots = new MapClass() + } else { + if (!delegatedRoots) { + delegatedRoots = { + items: new MapClass() } - const event = attachEventToNode(node, eventName, delegatedRoots) + delegatedRoots.event = attachEventToDocument( + doc, + eventName, + delegatedRoots + ) delegatedEvents.set(eventName, delegatedRoots) - if (isFunction(this.handler)) { - delegatedRoots.set(node, { - eventHandler: this.handler, - event - }) - } - } else { - if (!delegatedRoots) { - delegatedRoots = { - items: new MapClass() - } - delegatedRoots.event = attachEventToDocument( - doc, - eventName, - delegatedRoots - ) - delegatedEvents.set(eventName, delegatedRoots) - } - if (isFunction(this.handler)) { - delegatedRoots.items.set(node, this.handler) - } - } - } - /* istanbul ignore next */ - unhook (node, prop, next) { - if ( - next && - next.vhook === VHook.Event && - next.handler === this.handler && - next.eventName === next.eventName - ) { - return } - const eventName = fixEvent(node, this.eventName) - if (eventName === ONPROPERTYCHANGE) { - return - } - const delegatedRoots = delegatedEvents.get(eventName) - if (unbubbleEvents[eventName] === 1 && delegatedRoots) { - const event = delegatedRoots.get(node) - node.removeEventListener(parseEventName(eventName), event.event, false) - /* istanbul ignore next */ - const delegatedRootsSize = delegatedRoots.size - if (delegatedRoots.delete(node) && delegatedRootsSize === 0) { - delegatedEvents.delete(eventName) - } - } else if (delegatedRoots && delegatedRoots.items) { - const items = delegatedRoots.items - if (items.delete(node) && items.size === 0) { - doc.removeEventListener( - parseEventName(eventName), - delegatedRoots.event, - false - ) - delegatedEvents.delete(eventName) - } + if (isFunction(handler)) { + delegatedRoots.items.set(domNode, handler) } } } -function getEventName (eventName) { - if (eventName === 'onDoubleClick') { - eventName = 'ondblclick' - } else if (eventName === 'onTouchTap') { - eventName = 'onclick' +export function detachEvent ( + domNode: Element, + eventName: string, + handler: Function +) { + eventName = fixEvent(domNode, eventName) + if (eventName === ONPROPERTYCHANGE) { + return + } + const delegatedRoots = delegatedEvents.get(eventName) + if (unbubbleEvents[eventName] === 1 && delegatedRoots) { + const event = delegatedRoots.get(domNode) + domNode.removeEventListener(parseEventName(eventName), event.event, false) + /* istanbul ignore next */ + const delegatedRootsSize = delegatedRoots.size + if (delegatedRoots.delete(domNode) && delegatedRootsSize === 0) { + delegatedEvents.delete(eventName) + } + } else if (delegatedRoots && delegatedRoots.items) { + const items = delegatedRoots.items + if (items.delete(domNode) && items.size === 0) { + doc.removeEventListener( + parseEventName(eventName), + delegatedRoots.event, + false + ) + delegatedEvents.delete(eventName) + } } - return eventName.toLowerCase() } let propertyChangeActiveElement @@ -194,10 +165,14 @@ function processOnPropertyChangeEvent (node, handler) { propertyChangeActiveHandler = handler if (!bindFocus) { bindFocus = true - doc.addEventListener('focusin', () => { - unbindOnPropertyChange() - bindOnPropertyChange(node) - }, false) + doc.addEventListener( + 'focusin', + () => { + unbindOnPropertyChange() + bindOnPropertyChange(node) + }, + false + ) doc.addEventListener('focusout', unbindOnPropertyChange, false) } } @@ -252,11 +227,16 @@ function detectCanUseOnInputNode (node) { ) } -function fixEvent (node, eventName) { - if (detectCanUseOnInputNode(node)) { - if (eventName === 'onchange') { - eventName = ONINPUT in window ? ONINPUT : ONPROPERTYCHANGE - } +function fixEvent (node: Element, eventName: string) { + if (eventName === 'onDoubleClick') { + eventName = 'ondblclick' + } else if (eventName === 'onTouchTap') { + eventName = 'onclick' + // tslint:disable-next-line:prefer-conditional-expression + } else if (eventName === 'onChange' && detectCanUseOnInputNode(node)) { + eventName = ONINPUT in window ? ONINPUT : ONPROPERTYCHANGE + } else { + eventName = eventName.toLowerCase() } return eventName } @@ -341,5 +321,3 @@ function attachEventToNode (node, eventName, delegatedRoots) { node.addEventListener(parseEventName(eventName), eventHandler, false) return eventHandler } - -export default EventHook diff --git a/packages/nerv/src/hooks/vhook.ts b/packages/nerv/src/hooks/vhook.ts deleted file mode 100644 index 0e1b3e6d..00000000 --- a/packages/nerv/src/hooks/vhook.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const enum VHook { - Attribute = 1, - Event = 1 << 1, - HTML = 1 << 2, - Ref = 1 << 3 -} diff --git a/packages/nerv/src/vdom/patch.ts b/packages/nerv/src/vdom/patch.ts index e68b70a7..40e463b1 100644 --- a/packages/nerv/src/vdom/patch.ts +++ b/packages/nerv/src/vdom/patch.ts @@ -20,6 +20,7 @@ import { } from 'nerv-shared' import { unmount, unmountChildren } from './unmount' import Ref from './ref' +import { attachEvent, detachEvent } from '../hooks/event-hook' export function patch ( lastVnode, @@ -475,6 +476,20 @@ function setStyle (domStyle, style, value) { } } +function patchEvent ( + eventName: string, + lastEvent: Function, + nextEvent: Function, + domNode: Element +) { + if (lastEvent !== nextEvent) { + if (isFunction(lastEvent)) { + detachEvent(domNode, eventName, lastEvent) + } + attachEvent(domNode, eventName, nextEvent) + } +} + function patchStyle (lastAttrValue, nextAttrValue, dom) { const domStyle = dom.style let style @@ -530,10 +545,7 @@ export function patchProp ( } } } else if (isAttrAnEvent(prop)) { - if (isFunction(lastValue)) { - lastValue.unhook(domNode, prop, nextValue) - } - nextValue.hook(domNode, prop, lastValue) + patchEvent(prop, lastValue, nextValue, domNode) } else if (prop === 'style') { patchStyle(lastValue, nextValue, domNode) } else if ( @@ -587,7 +599,7 @@ function patchProps ( const value = previousProps[propName] if (isNullOrUndef(nextProps[propName]) && !isNullOrUndef(value)) { if (isAttrAnEvent(propName)) { - value.unhook(domNode, propName, nextProps[propName]) + detachEvent(domNode, propName, value) } else { domNode.removeAttribute(propName) } diff --git a/packages/nerv/src/vdom/unmount.ts b/packages/nerv/src/vdom/unmount.ts index 399faf42..85e2114f 100644 --- a/packages/nerv/src/vdom/unmount.ts +++ b/packages/nerv/src/vdom/unmount.ts @@ -1,11 +1,7 @@ -import { - isNullOrUndef, - isInvalid, - VType, - VirtualChildren -} from 'nerv-shared' +import { isNullOrUndef, isInvalid, VType, VirtualChildren } from 'nerv-shared' import { isAttrAnEvent, isArray } from 'nerv-utils' import Ref from './ref' +import { detachEvent } from '../hooks/event-hook' export function unmountChildren ( children: VirtualChildren, @@ -35,9 +31,8 @@ export function unmount (vnode, parentDom?) { const { props, children, ref } = vnode unmountChildren(children) for (const propName in props) { - const property = props[propName] if (isAttrAnEvent(propName)) { - property.unhook(dom, propName, null) + detachEvent(dom, propName, props[propName]) } } if (ref !== null) {