React Bind Handle的思考

文章来自我我的的 Github
)

在平时的开发里面,总会碰到handle绑定的问题。若是你和我同样懒或者思考过,你会以为这个过程实在是太烦了吧。这里记录一下个人思路和历程。git

这里以一个按钮的点击事件来作示例。github

class App extends React.Components {
    state = {
        count: 0
    }

    clickHandler () {
        const count = this.state.count + 1
        this.setState({ count })
    }

    render() {
         return (
            <button>
                Click me to show something in dev tools
            </button>
        )
    }
}

这个例子的目的是点击按钮触发clickHandler来让计数器加1。咱们能够用两种不一样的方式来触发这个handle,由于咱们使用了this.setState,因此咱们都必需要给函数绑定this。亦或是使用箭头函数处理这个地方。typescript

直接在jsx里面bind(this)

<button onClick={this.clickHandler.bind(this)}>
    Click me to show something in dev tools
</button>

嗯 这个的确能够。可是写起来很是的长看起来也挺丑的。有个问题是每次重渲染的时候都会从新bind一次函数,对于比较大的列表来讲这个地方很是不可取。babel

使用箭头函数

clickHandler改为以下的范式。ide

clickHandler = () => {
    const count = this.state.count + 1
    this.setState({ count })
}

// render ...
<button onClick={this.clickHandler)}>
    Click me to show something in dev tools
</button>

诶这样看起来会好不少诶。可是若是你有强迫症你会发现一件事情。若是咱们加上生命周期函数和一些其余的handler ... 好比这样。函数

componentDidMount () {}
componentWillMount () {}
componentWillUpdate () {}

clickHandler = () => {
    const count = this.state.count + 1
    this.setState({ count })
}

antoherHandle = () => {}

你会发现这里生命周期函数和handler的写法不同。可是你的确可让它们变得同样,好比把生命周期函数改为箭头函数。但是这看起来不会以为很怪异吗。毕竟你一直以来都不是这么作的。性能

除此以外箭头函数没法被继承,这意味着若是你的子组件须要继承函数,这将会致使没法作到。更加须要注意的东西是没法继承带来的性能问题。这会致使每次建立组件都会建立新的方法致使额外的开销(由于箭头函数的实现实际上是直接在constructor函数里丢方法),若是是经过继承,那么它们这些方法老是来自同一个prototype,js编译器是会作优化的。测试

详细文章能够看这一篇Arrow Functions in Class Properties Might Not Be As Great As We Thinkfetch

在构造器里面使用bind(this)

经过构造器来写绑定函数其实看起来是不错的优化

constructor (props) {
    super(props)
    this.clickHandler = this.clickHandler.bind(this)
    this.antoherHandle = this.antoherHandle.bind(this)
}

既解决了性能(内存)的问题。还能作不少有意思的事情好比说,利用现有的方法添加更有语义化的方法。

constructor (props) {
    super(props)
    this.clickHandler = this.clickHandler.bind(this)
    this.antoherHandle = this.antoherHandle.bind(this)
    this.clickWithOne = this.clickHandler.bind(this, 1)
}

这样就能产生每次都会传参数1的新事件。看起来的确是还不错。可是仍然有问题。当你的方法线性的增长的时候,若是有三个四个五个六个的时候,你可能须要一个一个的绑定。添加它们到构造函数里面,更糟糕的多是经过复制粘贴之前写的方法,你会绑定错误的函数。就像这样。

constructor (props) {
    super(props)
    this.clickHandler = this.clickHandler.bind(this)
    this.antoherHandle = this.antoherHandle.bind(this)
    this.clickWithOne = this.antoherHandle.bind(this, 1)
}

你必须在运行的时候才知道你的clickWithOne绑定的实际上是antoherHandle。若是你没测试过,那么极可能就会出现一些你难以理解的问题或者bug。

自动绑定

若是你动脑想一想会发现能够写一个autobind的方法来自绑定函数呀。可是你很懒没有去写,你经过github搜索到了一个叫作React-autobind的库。看起来好像还不错。

constructor(props) {
  super(props);
  autoBind(this);
}

甚至能够不绑定某些方法。

constructor(props) {
  super(props);
  autoBind(this, {
    wontBind: ['leaveAlone1', 'leaveAlone2']
  });
}

或者指定只绑定某些方法。

constructor(props) {
  super(props);
  autoBind(this, {
    bindOnly: ['myMethod1', 'myMethod2']
  });
}

看起来彷佛是妙极了。可是你会发现这个写法其实仍是很繁琐啊。要写一坨东西。。打开源码看一眼你会发现有一个默认的wonbind列表。

let wontBind = [
  'constructor',
  'render',
  'componentWillMount',
  'componentDidMount',
  'componentWillReceiveProps',
  'shouldComponentUpdate',
  'componentWillUpdate',
  'componentDidUpdate',
  'componentWillUnmount'
];

表示不须要自动绑定的函数的名字。可是这个列表很是的糟糕,由于随着React版本的提高,某些钩子和方法都会被废弃,随着时间可能还会增长增多的方法。

这个库也好久没更新了。差评仍是放弃吧。。。

Autobind-decorator

若是你了解过ES7decorator。你会发现上面的写法彻底可使用decorator的形式表示,而且这个库也支持在typescript上面使用。而且结构会很是的清晰。因而你找到了autobind-decorator这个库。它能帮助到咱们,给咱们想要的东西,文档一开始就告诉咱们。

// Before:
<button onClick={ this.handleClick.bind(this) }></button>

// After:
<button onClick={ this.handleClick }></button>

用以前...用以后的样子,很好就是咱们要的。 这个库有个缺点就是必须的IE11+以上的版本才支持,可是这其实也还好。

另外就是你的开启decorator的支持在babel的配置里面。

{
  "plugins": [
    ["@babel/plugin-proposal-decorators", { "legacy": true }],
  ]
}

咱们来看看推荐的用法。

import {boundMethod} from 'autobind-decorator'

class Component {
  constructor(value) {
    this.value = value
  }

  @boundMethod
  method() {
    return this.value
  }
}

let component = new Component(42)
let method = component.method // .bind(component) isn't needed!
method() // returns 42

给方法绑定this,而不是整个类,这么作是更加合理的。由于不是每一个方法都须要用到this的。如Dan所说。

It is unnecessary to do that to every function. This is just as bad as autobinding (on a class). You only need to bind functions that you pass around. e.g. onClick={this.doSomething}. Or fetch.then(this.handleDone) -- Dan Abramov‏

既能够在函数上,也能够在类上使用的@autobind

import autobind from 'autobind-decorator'

class Component {
  constructor(value) {
    this.value = value
  }

  @autobind
  method() {
    return this.value
  }
}

let component = new Component(42)
let method = component.method // .bind(component) isn't needed!
method() // returns 42

// Also usable on the class to bind all methods
// Please see performance if you decide to autobind your class
@autobind
class Component { }

只能做用于类的@boundClass,咱们不免也会有全都须要绑定到this的状况这时候咱们直接boundClass会更加的简洁。

import {boundClass} from 'autobind-decorator'

@boundClass
class Component {
  constructor(value) {
    this.value = value
  }

  method() {
    return this.value
  }
}

let component = new Component(42)
let method = component.method // .bind(component) isn't needed!
method() // returns 42

缺点也是有的,并不能像constructor那样本身随随便便的定不一样的方法名经过原有的方法,必须的写出一个新的,可是这是小问题,无伤大雅。而且descorator并无成为标准,可是其实也差很少了,并不担忧。

结语

这里的全部的解决思路都各有千秋吧。怎么取舍仍是看本身,这里就不一一列出来各自的对比了 ,于我我的而言会偏好Autobind-decorator,认为这是全部解决方案里面最好的一个了,可是要引入一个额外的依赖仍是有点麻烦。

相关文章
相关标签/搜索