jsx onClick绑定函数

1,790 阅读2分钟

code review时,发现自己在jsx种绑定onClick函数有过这种写法:

// 第一种
class Movie extends React.Component{
    constructor(props) {
        super(props)
        this.state = {
            name: 'yuly'
        }
    }
    handleClick (){
        console.log(this) // Movie
    }
    render() {
        const { name } = this.state
        return (
            <div>
                <p>this is movie Component --- {name}</p>
                <button onClick={()=> this.handleClick()}>click me</button>
            </div>
        )
    }
}

这是不推荐的,因为在每次渲染Movie,render函数执行都会重新创建匿名回调函数。 下面这第二种写法,虽然不会在每次render执行时创建匿名函数,但是在 JavaScript 中,class 的方法默认不会绑定 this。所以调用这个函数的时候, handleClick内部输出 this 的值为 undefined。

// 第二种
<button onClick={this.handleClick>click me</button>  //点击 控制台输出undefined

this的指向当前调用栈的第二层(从栈底开始数),在第一种写法中,由于是在箭头函数中调用handleClick,handleClick的调用栈为:匿名箭头函数 -> handleClick,handleClick调用时this的调用位置在箭头函数,而箭头函数会继承外层函数调用的this绑定,所以这时handleClick函数执行this输出Movie。

所以在他人代码中还会看见第三种写法:

// 第三种
class Movie extends React.Component{
    constructor(props) {
        super(props)
        this.state = {
            name: 'yuly'
        }
    }
    handleClick (){
        console.log(this) // Movie
    }
    render() {
        const { name } = this.state
        return (
            <div>
                <p>this is movie Component --- {name}</p>
                <button onClick={this.handleClick.bind(this)}>click me</button>
            </div>
        )
    }
}

其实这种写法从弊端上来讲与第一种写法殊途同归; MDN提供的一种bind(..)实现:

   if(!Function.prototype.bind) {
          Function.prototype.bind = function(oThis) {
                if(typeof this !== 'function'){
                    throw new TypeError(
                        'Function.prototype.bind - what is tring' + 
                        'to be bound is not callable'
                    );
                }
                var aArgs = Array.prototype.slice.call( arguments, 1),
                    fToBind = this,
                    fNOP = function(){},
                    fBound = function() {
                        return fToBind.apply(
                            (
                                this instanceof fNOP &&
                                oThis ? this : oThis
                            ),
                            aArgs.concat(
                                Array.prototype.slice.call(arguments)
                            )
                        )
                }
                fNOP.prototype = this.prototype
                fBound.prototype = new fNOP() // 创建了一个新函数实例对象
                
                return fBound;
            }
        }

我们可以看出在bind内部同样创建了一个新的函数。 正确写法是第四种,将handleClick用箭头函数定义,因此 this是 定义时上下文的this -> Movie:

// 第四种
class Movie extends React.Component{
    constructor(props) {
        super(props)
        this.state = {
            name: 'yuly'
        }
    }
    handleClick = () =>{
        console.log(this) // Movie
    }
    render() {
        const { name } = this.state
        return (
            <div>
                <p>this is movie Component --- {name}</p>
                <button onClick={this.handleClick}>click me</button>
            </div>
        )
    }
}

如果需要向handleClick函数传递参数呢?官方推荐写法:

//  传递参数
class Movie extends React.Component{
    constructor(props) {
        super(props)
        this.state = {
            name: 'yuly'
        }
    }
    handleClick (e, id) {
        console.log(this) // Movie
        console.log(e, id) 
    }
    render() {
        const { name } = this.state
        return (
            <div>
                <p>this is movie Component --- {name}</p>
                <button onClick={(e) => this.handleClick(e,id)}>click me</button>
              // 或者
             // </div><button onClick={this.deleteRow.bind(this, id)}>click me</button>
       )
    }
}

欢迎指正