Skip to content

Latest commit

 

History

History
591 lines (547 loc) · 19.4 KB

从0到1模拟合成事件.md

File metadata and controls

591 lines (547 loc) · 19.4 KB

合成事件

合成事件分为三个阶段:

  • 事件注册
  • 事件绑定
  • 事件执行

渲染元素

<!doctype html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>React Event</title>
    <style>
        html, body {
            margin: 0;
            padding: 0
        }
    </style>
</head>
<body>
    <div id="root"></div>
</body>
<script>
    const root = document.getElementById('root')
    const elementTree = {
        type: 'div',
        props: {
            onClick: () => { 
                console.log('父元素冒泡')
            },
            onClickCapture: (e) => {
                // e.stopPropagation()
                console.log('父元素捕获')
            }
        },
        children: {
            type: 'button',
            props: {
                onClick: () => { 
                    console.log('子元素冒泡')
                },
                onClickCapture: (e) => {
                    // e.stopPropagation()
                    console.log('子元素捕获')
                }
            },
            children: '点击'
        }
    }
    const render = (element, parentNode) => {
        const type = element.type
        let dom = null
        if(typeof element === 'string' || typeof element === 'number'){
            dom = document.createTextNode(element)
        } else {
            dom = document.createElement(type)
        }
        const returnFiber = parentNode.__reactFiber || null
        const fiber = { 
            type, 
            stateNode: dom, 
            return: returnFiber
        }
        dom.__reactFiber = fiber
        dom.__reactProps = element.props
        if(element.children){
            render(element.children, dom)
        }
        parentNode.appendChild(dom)
    }
    render(elementTree, root)
</script>
</html>

第一阶段 事件注册

这一阶段包括:

  • 收集所有的原生事件名称allNativeEvents,并生成原生事件名称和合成事件名称的映射关系topLevelEventsToReactNames
  • 同时,生成所有的合成事件对象构造函数
// 第一阶段 注册事件 以及 模拟合成事件
// 创建合成事件的工厂函数
function createSyntheticEvent(Interface){
    function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget){
        this._reactName = reactName
        this._targetInst = targetInst
        this.type = reactEventType
        this.nativeEvent = nativeEvent
        this.target = nativeEventTarget
        this.currentTarget = null // 当前的事件源
        for(const propName in Interface){
            this[propName] = nativeEvent[propName]
        }

        this.isDefaultPrevented = () => false
        this.isPropagationStopped = () => false

        return this
    }

    Object.assign(SyntheticBaseEvent.prototype, {
        preventDefault(){
            // event.defaultPrevented返回一个布尔值,表明当前事件是否调用了event.preventDefault()方法。
            // 因此在模拟的preventDefault方法里需要手动设置这个属性
            this.defaultPrevented = true
            const event = this.nativeEvent
            if(event.preventDefault){
                event.preventDefault()
            } else {
                event.returnValue = true; // IE
            }
            this.isDefaultPrevented = () => true
        },
        stopPropagation(){
            const event = this.nativeEvent
            if(event.stopPropagation){
                event.stopPropagation()
            } else {
                event.cancelBubble = true; // IE
            }
            this.isPropagationStopped = () => true
        }
    })

    return SyntheticBaseEvent
}


const MouseEventInterface = {
    clientX: 0,
    clientY: 0
}

const SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface)

// 将浏览器支持的所有原生事件枚举出来,下面的事件两两一对,奇数位代表原生事件名称,
// 偶数位用于模拟react事件名称,比如 'keyDown' 用于 'on' + 'KeyDown',或者 'on' + 'KeyDown' + 'Capture'
const discreteEventPairsForSimpleEventPlugin = [
    'click', 'Click', 
    // 'keydown', 'KeyDown',
    // 'keypress', 'KeyPress',
    // 'keyup', 'KeyUp',
    // 'mousedown', 'MouseDown',
    // 'mouseup', 'MouseUp'
]
const topLevelEventsToReactNames = new Map()
const allNativeEvents = new Set()
// const registrationNameDependencies = {}
function registerSimpleEvents(){
    for(let i = 0; i < discreteEventPairsForSimpleEventPlugin.length; i+=2){
        const topEvent = discreteEventPairsForSimpleEventPlugin[i] // 原生事件名称
        const reactName = 'on' + discreteEventPairsForSimpleEventPlugin[i+1] // react事件
        topLevelEventsToReactNames.set(topEvent, reactName)
        // registrationNameDependencies[reactName] = [topEvent]
        // registrationNameDependencies[reactName + 'Capture'] = [topEvent]
        allNativeEvents.add(topEvent)
    }
}
registerSimpleEvents()

第二阶段 事件绑定

这一阶段包括:

  • 遍历 allNativeEvents 中的所有原生事件名称,给容器root注册所有的捕获和冒泡事件
// 第二阶段 事件绑定
// 根据收集的allNativeEvents给容器root注册所有的事件
function listenToAllSupportedEvents(container){
    allNativeEvents.forEach(domEventName => {
        listenToNativeEvent(domEventName, false, container) // 绑定冒泡事件
        listenToNativeEvent(domEventName, true, container) // 绑定捕获事件
    })
}
function listenToNativeEvent(domEventName, isCaptruePhaseListener, rootContainerElement){
    let listener = dispatchEvent.bind(null, domEventName, isCaptruePhaseListener, rootContainerElement)
    if(isCaptruePhaseListener){
        rootContainerElement.addEventListener(domEventName, listener, true)
    } else {
        rootContainerElement.addEventListener(domEventName, listener, false)
    }
}
listenToAllSupportedEvents(root)

第三阶段 事件触发

这一阶段包括:

  • 点击按钮,触发事件执行,首先执行dispatchEvent方法
  • 执行 extractEvents 方法收集所有的捕获阶段或者冒泡阶段事件,并生成对应的原生事件的合成事件对象,填充dispatchQueue数组
  • processDispatchQueue按顺序执行对应的事件处理函数
// 第三阶段 事件触发
// 事件触发首先执行的是dispatchEvent
function dispatchEvent(domEventName, isCaptruePhaseListener, targetContainer, nativeEvent){
    // 获取原生的事件源
    const target = nativeEvent.target || nativeEvent.srcElement || window
    
    // 获取fiber实例
    const targetInst = target.__reactFiber;
    
    dispatchEventForPluginEventSystem(
        domEventName,
        isCaptruePhaseListener,
        nativeEvent,
        targetInst,
        targetContainer
    )
}

function dispatchEventForPluginEventSystem(domEventName, isCaptruePhaseListener, nativeEvent, targetInst, targetContainer){
    const nativeEventTarget = nativeEvent.target;
    const dispatchQueue = []
    // 提取事件处理函数,填充 dispatchQueue 数组
    extractEvents(
        dispatchQueue,
        domEventName,
        targetInst,
        nativeEvent,
        nativeEventTarget,
        isCaptruePhaseListener,
        targetContainer
    )

    processDispatchQueue(dispatchQueue, isCaptruePhaseListener)
}

function processDispatchQueue(dispatchQueue, isCaptruePhaseListener){
    for(let i = 0; i < dispatchQueue.length; i++){
        const { event, listeners } = dispatchQueue[i]
        if(isCaptruePhaseListener){
            for(let i = listeners.length - 1; i >= 0; i--){
                const [ currentTarget, listener ] = listeners[i]
                if(event.isPropagationStopped()){
                    return
                }
                execDispatch(event, listener, currentTarget)
            }
        } else {
            for(let i = 0; i < listeners.length; i++){
                const [ currentTarget, listener ] = listeners[i]
                if(event.isPropagationStopped()){
                    return
                }
                execDispatch(event, listener, currentTarget)
            }
        }
    }
}
function execDispatch(event, listener, currentTarget){
    event.currentTarget = currentTarget
    listener(event)
    event.currentTarget = null
}

// 提取事件处理函数 生成合成事件对象 填充dispatchQueue数组
function extractEvents(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, isCaptruePhaseListener, targetContainer){
    const reactName = topLevelEventsToReactNames.get(domEventName)
    let SyntheticEventCtor;
    let reactEventType = domEventName
    // 不同的事件,合成事件对象是不一样的,对应不同合成事件构造函数
    switch(domEventName){
        case 'click':
            SyntheticEventCtor = SyntheticMouseEvent;
            break
        default:
            break;
    }
    const listeners = accumulateSinglePhaseListeners(
        targetInst,
        reactName,
        nativeEvent.type,
        isCaptruePhaseListener
    )
    if(listeners.length){
        const event = new SyntheticEventCtor(
            reactName, 
            reactEventType, 
            targetInst, 
            nativeEvent,
            nativeEventTarget
        )
        dispatchQueue.push({
            event,
            listeners
        })
    }
}
function accumulateSinglePhaseListeners(targetFiber, reactName, nativeType, inCapturePhase){
    const captureName = reactName + 'Capture'
    const reactEventName = inCapturePhase ? captureName : reactName
    const listeners = []
    let instance = targetFiber

    while(instance){
        const stateNode = instance.stateNode
        const listener = stateNode.__reactProps[reactEventName]
        if(listener){
            listeners.push([instance, listener, stateNode])
        }
        instance = instance.return
    }
    return listeners
}

最终demo

<!doctype html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>React Event</title>
    <style>
        html, body {
            margin: 0;
            padding: 0
        }
    </style>
</head>
<body>
    <div id="root"></div>
</body>
<script>
    const root = document.getElementById('root')
    const elementTree = {
        type: 'div',
        props: {
            onClick: () => { 
                console.log('父元素冒泡')
            },
            onClickCapture: (e) => {
                // e.stopPropagation()
                console.log('父元素捕获')
            }
        },
        children: {
            type: 'button',
            props: {
                onClick: () => { 
                    console.log('子元素冒泡')
                },
                onClickCapture: (e) => {
                    // e.stopPropagation()
                    console.log('子元素捕获')
                }
            },
            children: '点击'
        }
    }
    const render = (element, parentNode) => {
        const type = element.type
        let dom = null
        if(typeof element === 'string' || typeof element === 'number'){
            dom = document.createTextNode(element)
        } else {
            dom = document.createElement(type)
        }
        const returnFiber = parentNode.__reactFiber || null
        const fiber = { 
            type, 
            stateNode: dom, 
            return: returnFiber
        }
        dom.__reactFiber = fiber
        dom.__reactProps = element.props
        if(element.children){
            render(element.children, dom)
        }
        parentNode.appendChild(dom)
    }
    render(elementTree, root)


    // 第一阶段 注册事件 以及 模拟合成事件
    // 创建合成事件的工厂函数
    function createSyntheticEvent(Interface){
        function SyntheticBaseEvent(reactName, reactEventType, targetInst, nativeEvent, nativeEventTarget){
            this._reactName = reactName
            this._targetInst = targetInst
            this.type = reactEventType
            this.nativeEvent = nativeEvent
            this.target = nativeEventTarget
            this.currentTarget = null // 当前的事件源
            for(const propName in Interface){
                this[propName] = nativeEvent[propName]
            }

            this.isDefaultPrevented = () => false
            this.isPropagationStopped = () => false

            return this
        }

        Object.assign(SyntheticBaseEvent.prototype, {
            preventDefault(){
                // event.defaultPrevented返回一个布尔值,表明当前事件是否调用了event.preventDefault()方法。
                // 因此在模拟的preventDefault方法里需要手动设置这个属性
                this.defaultPrevented = true
                const event = this.nativeEvent
                if(event.preventDefault){
                    event.preventDefault()
                } else {
                    event.returnValue = true; // IE
                }
                this.isDefaultPrevented = () => true
            },
            stopPropagation(){
                const event = this.nativeEvent
                if(event.stopPropagation){
                    event.stopPropagation()
                } else {
                    event.cancelBubble = true; // IE
                }
                this.isPropagationStopped = () => true
            }
        })

        return SyntheticBaseEvent
    }


    const MouseEventInterface = {
        clientX: 0,
        clientY: 0
    }

    const SyntheticMouseEvent = createSyntheticEvent(MouseEventInterface)

    // 将浏览器支持的所有原生事件枚举出来,下面的事件两两一对,奇数位代表原生事件名称,
    // 偶数位用于模拟react事件名称,比如 'keyDown' 用于 'on' + 'KeyDown',或者 'on' + 'KeyDown' + 'Capture'
    const discreteEventPairsForSimpleEventPlugin = [
        'click', 'Click', 
        // 'keydown', 'KeyDown',
        // 'keypress', 'KeyPress',
        // 'keyup', 'KeyUp',
        // 'mousedown', 'MouseDown',
        // 'mouseup', 'MouseUp'
    ]
    const topLevelEventsToReactNames = new Map()
    const allNativeEvents = new Set()
    // const registrationNameDependencies = {}
    function registerSimpleEvents(){
        for(let i = 0; i < discreteEventPairsForSimpleEventPlugin.length; i+=2){
            const topEvent = discreteEventPairsForSimpleEventPlugin[i] // 原生事件名称
            const reactName = 'on' + discreteEventPairsForSimpleEventPlugin[i+1] // react事件
            topLevelEventsToReactNames.set(topEvent, reactName)
            // registrationNameDependencies[reactName] = [topEvent]
            // registrationNameDependencies[reactName + 'Capture'] = [topEvent]
            allNativeEvents.add(topEvent)
        }
    }
    registerSimpleEvents()


    // 第二阶段 事件绑定
    // 根据收集的allNativeEvents给容器root注册所有的事件
    function listenToAllSupportedEvents(container){
        allNativeEvents.forEach(domEventName => {
            listenToNativeEvent(domEventName, false, container) // 绑定冒泡事件
            listenToNativeEvent(domEventName, true, container) // 绑定捕获事件
        })
    }
    function listenToNativeEvent(domEventName, isCaptruePhaseListener, rootContainerElement){
        let listener = dispatchEvent.bind(null, domEventName, isCaptruePhaseListener, rootContainerElement)
        if(isCaptruePhaseListener){
            rootContainerElement.addEventListener(domEventName, listener, true)
        } else {
            rootContainerElement.addEventListener(domEventName, listener, false)
        }
    }
    listenToAllSupportedEvents(root)

    // 第三阶段 事件触发
    // 事件触发首先执行的是dispatchEvent
    function dispatchEvent(domEventName, isCaptruePhaseListener, targetContainer, nativeEvent){
        // 获取原生的事件源
        const target = nativeEvent.target || nativeEvent.srcElement || window
        
        // 获取fiber实例
        const targetInst = target.__reactFiber;
        
        dispatchEventForPluginEventSystem(
            domEventName,
            isCaptruePhaseListener,
            nativeEvent,
            targetInst,
            targetContainer
        )
    }

    function dispatchEventForPluginEventSystem(domEventName, isCaptruePhaseListener, nativeEvent, targetInst, targetContainer){
        const nativeEventTarget = nativeEvent.target;
        const dispatchQueue = []
        // 提取事件处理函数,填充 dispatchQueue 数组
        extractEvents(
            dispatchQueue,
            domEventName,
            targetInst,
            nativeEvent,
            nativeEventTarget,
            isCaptruePhaseListener,
            targetContainer
        )

        processDispatchQueue(dispatchQueue, isCaptruePhaseListener)
    }

    function processDispatchQueue(dispatchQueue, isCaptruePhaseListener){
        for(let i = 0; i < dispatchQueue.length; i++){
            const { event, listeners } = dispatchQueue[i]
            if(isCaptruePhaseListener){
                for(let i = listeners.length - 1; i >= 0; i--){
                    const [ currentTarget, listener ] = listeners[i]
                    if(event.isPropagationStopped()){
                        return
                    }
                    execDispatch(event, listener, currentTarget)
                }
            } else {
                for(let i = 0; i < listeners.length; i++){
                    const [ currentTarget, listener ] = listeners[i]
                    if(event.isPropagationStopped()){
                        return
                    }
                    execDispatch(event, listener, currentTarget)
                }
            }
        }
    }
    function execDispatch(event, listener, currentTarget){
        event.currentTarget = currentTarget
        listener(event)
        event.currentTarget = null
    }

    // 提取事件处理函数 生成合成事件对象 填充dispatchQueue数组
    function extractEvents(dispatchQueue, domEventName, targetInst, nativeEvent, nativeEventTarget, isCaptruePhaseListener, targetContainer){
        const reactName = topLevelEventsToReactNames.get(domEventName)
        let SyntheticEventCtor;
        let reactEventType = domEventName
        // 不同的事件,合成事件对象是不一样的,对应不同合成事件构造函数
        switch(domEventName){
            case 'click':
                SyntheticEventCtor = SyntheticMouseEvent;
                break
            default:
                break;
        }
        const listeners = accumulateSinglePhaseListeners(
            targetInst,
            reactName,
            nativeEvent.type,
            isCaptruePhaseListener
        )
        if(listeners.length){
            const event = new SyntheticEventCtor(
                reactName, 
                reactEventType, 
                targetInst, 
                nativeEvent,
                nativeEventTarget
            )
            dispatchQueue.push({
                event,
                listeners
            })
        }
    }
    function accumulateSinglePhaseListeners(targetFiber, reactName, nativeType, inCapturePhase){
        const captureName = reactName + 'Capture'
        const reactEventName = inCapturePhase ? captureName : reactName
        const listeners = []
        let instance = targetFiber

        while(instance){
            const stateNode = instance.stateNode
            const listener = stateNode.__reactProps[reactEventName]
            if(listener){
                listeners.push([instance, listener, stateNode])
            }
            instance = instance.return
        }
        return listeners
    }
</script>
</html>