React knowledge

171 阅读4分钟

React中的合成事件

Synthetic /sɪnˈθetɪk/ Event
合成事件是围绕浏览器原生事件,充当跨浏览器包装器的对象;它们将不同浏览器的行为合并为一个 API,这样做是为了确保事件在不同浏览器中显示一致的属性!

合成事件的基本操作

基础语法
在JSX元素上,直接基于 onXxx={函数} 进行事件绑定!
浏览器标准事件,在React中大部分都支持
developer.mozilla.org/zh-CN/docs/…

import React, { Component } from "react";

export default class App extends Component {

    state = {

        num: 0

    };

    render() {

        let { num } = this.state;

        return <div>

            {num}

            <br />

            <button onClick={(ev) => {

                // 合成事件对象 :SyntheticBaseEvent

                console.log(ev); 

                this.setState({

                    num: num + 1

                });

            }}>处理</button>

        </div>;

    }

};

合成事件中的this和传参处理
在类组件中,我们要时刻保证,合成事件绑定的函数中,里面的this是当前类的实例!
也需要保障传参的正常!

import React, { Component } from "react";

export default class App extends Component {

    handler1 = (ev) => {

        console.log(ev, this);

    };

    handler2 = (x, y, ev) => {

        console.log(x, y, ev, this);

    };

    render() {

        return <div>

            <button onClick={this.handler1}>测试1</button>

            <button onClick={this.handler2.bind(null, 10, 20)}>

                测试2

            </button>

        </div>;

    }

};

JS中的事件委托(事件代理)

事件和事件绑定

  • 事件是浏览器内置行为

  • 事件绑定是给事件行为绑定方法

    • 元素.onxxx=function…
    • 元素.addEventListener(‘xxx’,function(){},true/false)

事件的传播机制

  • 捕获 CAPTURING_PHASE
  • 目标 AT_TARGET
  • 冒泡 BUBBLING_PHASE

阻止冒泡传播

  • ev.cancelBubble=true 「<=IE8」
  • ev.stopPropagation()
  • ev.stopImmediatePropagation() 阻止监听同一事件的其他事件监听器被调用

Alt text

事件委托(代理),就是利用事件的“冒泡传播机制”实现的
例如:给父容器做统一的事件绑定(点击事件),这样点击容器中的任意元素,都会传播到父容器上,触发绑定的方法!在方法中,基于不同的事件源做不同的事情!

  • 性能得到很好的提高「减少内存消耗」
  • 可以给动态增加的元素做事件绑定
  • 某些需求必须基于其完成
const container = document.querySelector('.container'),

    box = document.querySelector('.box');

container.addEventListener('click', function (ev) {

    let target = ev.target,

        targetTag = target.tagName,

        targetClass = target.classList;

    if (targetTag === 'BUTTON' && targetClass.contains('submit')) {

        console.log('我是按钮');

        return;

    }

    if (targetTag === 'A' && targetClass.contains('link')) {

        console.log('我是超链接按钮');

        return;

    }

    console.log('我是新增的PBOX');

});



box.onclick = function (ev) {

    console.log('我是DIV盒子');

    ev.stopPropagation();

};



setTimeout(() => {

    const pBox = document.createElement('p');

    pBox.className = 'p-box';

    pBox.innerHTML = '我是P段落盒子';

    container.appendChild(pBox);

}, 2000);

合成事件的底层机制

总原则:基于事件委托实现

React17及以后,是委托给#root元素

const root = document.querySelector('#root'),

    outer = document.querySelector('.outer'),

    inner = document.querySelector('.inner');

/* 原理 */

const dispatchEvent = function dispatchEvent(ev, isCapture) {

    let path = ev.path,

        target = ev.target;

    if (isCapture) {

        [...path].reverse().forEach(elem => {

            let handler = elem.onClickCapture;

            if (typeof handler === "function") handler(ev);

        });

        return;

    }

    path.forEach(elem => {

        let handler = elem.onClick;

        if (typeof handler === "function") handler(ev);

    });

};

root.addEventListener('click', function (ev) {

    dispatchEvent(ev, false);

}, false);

root.addEventListener('click', function (ev) {

    dispatchEvent(ev, true);

}, true);



......

React17以前,是委托给document元素,并且没有实现捕获阶段的派发

const outer = document.querySelector('.outer'),

    inner = document.querySelector('.inner');

/* 原理 */

const dispatchEvent = function dispatchEvent(ev) {

    let path = ev.path,

        target = ev.target;

    [...path].reverse().forEach(elem => {

        let handler = elem.onClickCapture;

        if (typeof handler === "function") handler(ev);

    });

    path.forEach(elem => {

        let handler = elem.onClick;

        if (typeof handler === "function") handler(ev);

    });

};

document.addEventListener('click', function (ev) {

    dispatchEvent(ev);

}, false);



......

事件对象池
16版本中,存在事件对象池

  • 缓存和共享:对于那些被频繁使用的对象,在使用完后,不立即将它们释放,而是将它们缓存起来,以供后续的应用程序重复使用,从而减少创建对象和释放对象的次数,进而改善应用程序的性能!
  • 使用完成之后,释放对象「每一项内容都清空」,缓存进去!
  • 调用 event.persist() 可以保留住这些值!

17版本及以后,移除了事件对象池!

syntheticInnerBubble = (syntheticEvent) => {

    // syntheticEvent.persist();    

    setTimeout(() => {

        console.log(syntheticEvent); //每一项都置为空

    }, 1000);

};

click延迟和Vue中的事件处理机制

click事件在移动端存在300ms延迟

  • pc端的click是点击事件
  • 移动端的click是单击事件
import React from 'react';

import ReactDOM from 'react-dom/client';

import App from './App';



// 解决移动端300ms延迟

import fastclick from 'fastclick';

fastclick.attach(document.body);



const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(

  <App />

);

也可以自己基于touch事件模型去实现

const box = document.querySelector('.box');

box.ontouchstart = function (ev) {

    let point = ev.changedTouches[0];

    this.startX = point.pageX;

    this.startY = point.pageY;

    this.isMove = false;

};

box.ontouchmove = function (ev) {

    let point = ev.changedTouches[0];

    let changeX = point.pageX - this.startX;

    let changeY = point.pageY - this.startY;

    if (Math.abs(changeX) > 10 || Math.abs(changeY) > 10) {

        this.isMove = true;

    }

};

box.ontouchend = function (ev) {

    if (!this.isMove) {

        console.log('点击触发');

    }

};

Vue中的事件处理机制

核心:给创建的DOM元素,单独基于addEventListener实现事件绑定
Vue事件优化技巧:手动基于事件委托处理

<template>

  <div id="app">

    <ul class="box" @click="handler">

      <li

        class="item"

        v-for="(item, index) in arr"

        :data-item="item"

        :key="index"

      >

        {{ item }}

      </li>

    </ul>

  </div>

</template>



<script>

export default {

  name: "App",

  data() {

    return {

      arr: [10, 20, 30],

    };

  },

  methods: {

    handler(ev) {

      let target = ev.target;

      if (target.tagName === "LI") {

        console.log(target.getAttribute("data-item"));

      }

    },

  },

};

</script>