React 事件和事件绑定(类组件的事件绑定)

134 阅读10分钟

规则模式

  1. 类似于原生:on + 方法名(首字母大写)
  2. 一定要给事件赋值一个方法

使用React元素处理事件与处理DOM元素上的事件非常相似,但语法上有差异。

  • React事件使用小驼峰命名法,而不是全部小写命名,首字母一定要大写。
  • React事件使用JSX传递一个函数作为事件处理程序,而不是一个字符串。React中事件处理程序不能加小括号传参,否则在render函数运行时会立即调用,当用户交互时就不能调用

事件类型

  • 鼠标事件:onClick onDoubleClick onMouseDown
  • 触摸事件:onTouchStart onTouchMove onTouchEnd
  • 键盘事件:onKeyDown
  • 剪切事件:onCopy onCut onPaste
  • 表单事件:onChange onInput onSubmit
  • 焦点事件:onFocus

特别注意问题

  • 不作处理的情况下,this会指向undefined
  • 给事件绑定的一定要是一个方法,但不能直接调用该方法,调用方法只会在页面初次渲染指向方法。

事件对象

React对原生的事件系统也进行了封装,在React中的事件对象实际上是一个跨浏览器的虚拟事件对象 ,它拥有和原生事件对象相同的属性和方法。

在React中没有像Vue中的事件修饰符来阻止默认事件和事件冒泡,只能在事件回调函数中通过事件对象调用event.preventDefault()阻止默认事件event.stopPropagation()阻止冒泡

事件绑定

事件绑定需要注意的是this问题和传参。

事件绑定时,事件处理函数可以是function普通函数,也可以是对象的方法和箭头函数。

class组件的事件绑定

// App.js
import Home from './home'
//普通函数
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
function App() {
  return (
    <div>
      <Home />
      <Welcome name="KUGOU"/>
    </div>
  );
}

export default App;

// Home.js
import React, { Component } from 'react'

export default class Home extends Component {
  constructor(){
    super();
    // 构造函数中给创建的对象添加成员
    this.fn1=function(e){
      console.log(e,'fn1');
    };
    this.fn2=(e)=>{
      console.log(e,'fn2');
    };
  }
  // 类中给创建的对象声明成员,声明对象方法
    fn3(e,arg){
      console.log(e,'fn3事件函数的事件对象',arg);
    };
    // 声明对象的属性,属性值是事件处理函数,该函数是箭头函数
    fn4=(e,arg)=>{
      console.log(e,'fn4事件函数的事件对象',arg);
    };
    // 声明对象的属性,属性值是事件处理函数,该函数是function普通函数
    fn5=function(e,arg){
      console.log(e,'fn5事件函数的事件对象',arg)
    };
  render() {
    return (
      <div>
        {/* 不传参,默认会将事件对象传入,事件处理函数用形参接受 */}
          <button onClick={this.fn1}>btnfn1</button>
          <button onClick={this.fn2}>btnfn2</button>
          {/* 传参,在事件绑定时传入一个函数,需要将事件对象自行传入这个函数 */}
          <button onClick={(e)=>{this.fn3(e,'fn3')}}>btnfn3</button>
          <button onClick={(e)=>{this.fn4(e,'fn4')}}>btnfn4</button>
          <button onClick={(e)=>{this.fn4(e,'fn5')}}>btnfn5</button>
      </div>
    )
  }
}

如果需要传参,可以在事件绑定时传入一个函数,将事件处理函数放在传入的函数中调用,如此在render函数运行时,事件处理函数并没有立即调用,而是当事件触发时会调用传入的函数,然后才会调用真正的处理函数。

image.png

关于this问题

事件处理函数内部使用this关键词时其指向需要注意。

import React, { Component } from "react";
export default class Home extends Component {
    constructor() {
        super();
        // 构造函数中给创建的对象添加成员,属性值是一个function普通函数
        this.fn1 = function () {
            console.log(this, "事件回调函数是function普通函数this", 'fn1');
        };
        //   
        this.fn2 = () => {
            console.log(this, '事件回调函数是箭头函数this', "fn2");
        };
    }
    // 类中给创建的对象声明成员,声明对象方法
    fn3() {
        console.log(this, '事件回调函数是对象方法this', "fn3");
    };
    // 声明对象的属性,属性值是事件处理函数,该函数是箭头函数
    fn4 = () => {
        console.log(this, '事件回调函数是箭头函数this', "fn4");
    };
    // 声明对象的属性,属性值是事件处理函数,该函数是function普通函数
    fn5 = function () {
        console.log(this, '事件回调函数是function普通函数this', "fn5")
    };
    render() {
        return (
            <div>
                <button onClick={this.fn1}>btnfn1</button>
                <button onClick={this.fn2}>btnfn2</button>
                <button onClick={this.fn3}>btnfn3</button>
                <button onClick={this.fn4}>btnfn4</button>
                <button onClick={this.fn5}>btnfn5</button>
            </div>
        )
    }
}

image.png

事件处理函数是箭头函数,则this就是指向当前组件,而普通函数和对象方法this是undefined

解决方法

  • 第一种:将this作为参数传入,因为此时this是render函数中的this指向就是当前组件,然后在事件处理函数中用形参接受,就用形参使用this
import React, { Component } from "react";
export default class Home extends Component {
    constructor() {
        super();
        // 构造函数中给创建的对象添加成员,属性值是一个function普通函数
        this.fn1 = function () {
            console.log(this, "事件回调函数是function普通函数this", 'fn1');
        };
        //   
        this.fn2 = () => {
            console.log(this, '事件回调函数是箭头函数this', "fn2");
        };
    }
    // 类中给创建的对象声明成员,声明对象方法
    fn3() {
        console.log(this, '事件回调函数是对象方法this', "fn3");
    };
    // 声明对象的属性,属性值是事件处理函数,该函数是箭头函数
    fn4 = () => {
        console.log(this, '事件回调函数是箭头函数this', "fn4");
    };
    // 声明对象的属性,属性值是事件处理函数,该函数是function普通函数
    fn5 = function (arg) {
        console.log(this, '事件回调函数是function普通函数this', "fn5")
        console.log(arg, "fn5")
    };
    render() {
        return (
            <div>
                <button onClick={this.fn1}>btnfn1</button>
                <button onClick={this.fn2}>btnfn2</button>
                <button onClick={this.fn3}>btnfn3</button>
                <button onClick={this.fn4}>btnfn4</button>
                {/* 将this作为参数传入,因为此时this是render函数中的this指向就是当前组件,然后在事件处理函数中用形参接受 */}
                <button onClick={() => { this.fn5(this) }}>btnfn5</button>
            </div>
        )
    }
}

image.png

  • 第二种:对事件处理函数bind 绑定this,bind会劫持传入对象的this。这种方法要求必须是定义式的函数即有个变量或者对象属性来指向该函数,bind直接绑定函数要求是定义式函数
import React, { Component } from "react";
export default class Home extends Component {
    constructor() {
        super();
        // 对事件处理函数bind 绑定this,bind中的传入需要劫持的this就是用类创建的对象的this的指向为当前组件
        this.fn1 = function () {
            console.log(this, 'fn1');
        }.bind(this);
        this.fn2 = () => {
            console.log(this, '事件回调函数是箭头函数this', "fn2");
        };
    }
    // 类中给创建的对象声明成员,声明对象方法
    fn3() {
        console.log(this, '事件回调函数是对象方法this', "fn3");
    };
    // 声明对象的属性,属性值是事件处理函数,该函数是箭头函数
    fn4 = () => {
        console.log(this, '事件回调函数是箭头函数this', "fn4");
    };
    // 对事件处理函数bind 绑定this,bind中的传入需要劫持的this就是用类创建的对象的this的指向为当前组件
    fn5 = function () {
        console.log(this, "fn5")
    }.bind(this);
    render() {
        return (
            <div>
                <button onClick={this.fn1}>btnfn1</button>
                <button onClick={this.fn2}>btnfn2</button>
                <button onClick={this.fn3}>btnfn3</button>
                <button onClick={this.fn4}>btnfn4</button>
                <button onClick={this.fn5}>btnfn5</button>
            </div>
        )
    }
}

image.png

  • 第三种:每次事件绑定都对事件处理函数做bind 绑定,这样也可以给事件回调函数传参
import React, { Component } from "react";
export default class Home extends Component {
    constructor() {
        super();
        // 对事件处理函数bind 绑定this,bind中的传入需要劫持的this就是用类创建的对象的this的指向为当前组件
        this.fn1 = function (arg) {
            console.log(this, 'fn1', arg);
        }.bind(this,"fn1传参");
        this.fn2 = () => {
            console.log(this, '事件回调函数是箭头函数this', "fn2");
        };
    }
    // 类中给创建的对象声明成员,声明对象方法
    fn3(arg) {
        console.log(this, "fn3", arg);
    };
    // 声明对象的属性,属性值是事件处理函数,该函数是箭头函数
    fn4 = () => {
        console.log(this, '事件回调函数是箭头函数this', "fn4");
    };
    // 对事件处理函数bind 绑定this,而此时bind中传入的this就是用类创建的对象的this,即该this指向为当前组件
    fn5 = function (arg) {
        console.log(this, "fn5", arg)
    }.bind(this,"fn5传参");
    render() {
        return (
            <div>
                <button onClick={this.fn1}>btnfn1</button>
                <button onClick={this.fn2}>btnfn2</button>
                {/* 事件绑定时对事件处理函数做bind 绑定this并传参 */}
                <button onClick={this.fn3.bind(this,'fn3传参')}>btnfn3</button>
                <button onClick={this.fn4}>btnfn4</button>
                <button onClick={this.fn5}>btnfn5</button>
            </div>
        )
    }
}

image.png

  • 第四种:定义事件处理函数时使用箭头函数
import React, { Component } from "react";
export default class Home extends Component {
    constructor() {
        super();
        // 对事件处理函数bind 绑定this,bind中的传入需要劫持的this就是用类创建的对象的this的指向为当前组件
        this.fn1 = function (arg) {
            console.log(this, 'fn1', arg);
        }.bind(this,"fn1传参");
        // 定义事件处理函数时使用箭头函数
        this.fn2 = () => {
            console.log(this, '事件回调函数是箭头函数this', "fn2");
        };
    }
    // 类中给创建的对象声明成员,声明对象方法
    fn3(arg) {
        console.log(this, "fn3", arg);
    };
    // 定义事件处理函数时使用箭头函数
    fn4 = () => {
        console.log(this, '事件回调函数是箭头函数this', "fn4");
    };
    // 对事件处理函数bind 绑定this,bind中的传入需要劫持的this就是用类创建的对象的this的指向为当前组件
    fn5 = function (arg) {
        console.log(this, "fn5", arg)
    }.bind(this,"fn5传参");
    render() {
        return (
            <div>
                <button onClick={this.fn1}>btnfn1</button>
                <button onClick={this.fn2}>btnfn2</button>
                {/* 事件绑定时对事件处理函数做bind 绑定this并传参 */}
                <button onClick={this.fn3.bind(this,'fn3传参')}>btnfn3</button>
                <button onClick={this.fn4}>btnfn4</button>
                <button onClick={this.fn5}>btnfn5</button>
            </div>
        )
    }
}

image.png

当事件处理函数需要传参时

借助bind方法来传参,或者在事件绑定时传入一个函数,将事件处理函数放在传入的函数中调用

import React, { Component } from "react";
export default class Home extends Component {
    constructor() {
        super();
        this.fn1 = function (arg1, arg2) {
            console.log(this, arg1, arg2);
        }
        this.fn2 = (arg1, arg2) => {
            console.log(this, arg1, arg2);
        };
    }
    // 类中给创建的对象声明成员,声明对象方法
    fn3(arg1, arg2) {
        console.log(this, arg1, arg2);
    };
    // 定义事件处理函数时使用箭头函数
    fn4 = (arg1, arg2) => {
        console.log(this, arg1, arg2);
    };
    // 对事件处理函数bind 绑定this,bind中的传入需要劫持的this就是用类创建的对象的this的指向为当前组件
    fn5 = function (arg1, arg2) {
        console.log(this, arg1, arg2)
    }
    render() {
        return (
            <div>
                {/* call方法会立即执行,所以在事件绑定时传入一个函数,将事件处理函数放在传入的函数中调用 */}
                <button onClick={() => { this.fn1.call(this, 'fn1', 'fly') }}>btnfn1</button>
                {/* 定义事件回调函数的是箭头函数的传参 */}
                <button onClick={() => { this.fn2("fake", "fn2") }}>btnfn2</button>
                {/* 事件绑定时对事件处理函数做bind 绑定this并传参 */}
                <button onClick={this.fn3.bind(this, 'fn3', '启示录')}>btnfn3</button>
                {/* 定义事件回调函数的是箭头函数的传参 */}
                <button onClick={() => { this.fn4('city zoo', 'fn4') }}>btnfn4</button>
                {/* apply方法会立即执行,所以在事件绑定时传入一个函数,将事件处理函数放在传入的函数中调用 */}
                <button onClick={() => { this.fn5.apply(this, ["fn5", "IAG"]) }}>btnfn5</button>
            </div>
        )
    }

image.png

事件处理函数既要传参也要获取事件对象

import React, { Component } from "react";
export default class Home extends Component {
    constructor() {
        super();;
    }
    // 类中给创建的对象声明成员,声明对象方法
    fn3(e) {
        // 事件对象默认会传参,用参数接受即可
        console.log(this, "fn3this对象");
        console.log(e, "fn3的事件对象");
    };
    // 定义事件处理函数时使用箭头函数
    fn4 = (e, arg1) => {
        console.log(this, "fn4this对象");
        console.log(e, "fn4事件对象");
        console.log(arg1, "fn4的接受传参");
    };
    // 事件对象默认会传参,当时当传了其他参数,事件对象接受的参数是最后一个
    fn5 = function (arg1, arg2, e) {
        console.log(this, "fn5的this对象");
        console.log(arg1, arg2, "fn5的接受传参");
        console.log(e, "fn5的事件对象");
    }
    render() {
        return (
            <div>
                {/* 事件绑定时对事件处理函数做bind 绑定this不传参,事件对象默认会传参 */}
                <button onClick={this.fn3.bind(this)}>btnfn3</button>
                {/* 定义事件回调函数的是箭头函数的传参,在事件绑定时传入一个函数,需要将事件对象自行传入这个函数 */}
                <button onClick={(e) => { this.fn4(e, 'city zoo') }}>btnfn4</button>
                {/* 事件绑定时对事件处理函数做bind 绑定this传参,事件对象也默认会传参 */}
                <button onClick={this.fn5.bind(this, "fn5", "IAG")}>btnfn5</button>
            </div>
        )
    }
}

image.png

关于call,apply,bind的复习补充

每个函数都可以调用call,apply,bind方法,其作用是改变函数的this指向。

  • 1)call接受的第1个参数是this所需要指向的对象,后面的参数用逗号隔开是函数需要接受的参数。
  • 2)apply接受的第1个参数是this所需要指向的对象,第2个参数是函数接受的参数,以数组形式传入。
  • 3)bind接受的第1个参数是this所需要指向的对象,后面的参数用逗号隔开是函数需要接受的参数。

三者区别:

  • apply和call改变this的指向后原函数会立即运行,且只是临时改变this指向1次。
  • bind改变this指向后不会立即运行原函数,而是返回一个永久改变了this指向的函数,如果需要执行返回的函数需要加小括号调用。
  • bind的优先级高于apply和call。