React Bind Handle的思考

44次阅读

共计 4567 个字符,预计需要花费 12 分钟才能阅读完成。

文章来自我个人的 Github)
在平时的开发里面,总会碰到 handle 绑定的问题。如果你和我一样懒或者思考过,你会觉得这个过程实在是太烦了吧。这里记录一下我的思路和历程。
这里以一个按钮的点击事件来做示例。
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。亦或是使用箭头函数处理这个地方。
直接在 jsx 里面 bind(this)

<button onClick={this.clickHandler.bind(this)}>
Click me to show something in dev tools
</button>
嗯 这个的确可以。但是写起来非常的长看起来也挺丑的。有个问题是每次重渲染的时候都会重新 bind 一次函数,对于比较大的列表来说这个地方非常不可取。
使用箭头函数
把 clickHandler 改成如下的范式。
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 Think。
在构造器里面使用 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
如果你了解过 ES7 的 decorator。你会发现上面的写法完全可以使用 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,认为这是所有解决方案里面最好的一个了,但是要引入一个额外的依赖还是有点麻烦。

正文完
 0