TS装饰器bindThis优雅实现React类组件中this绑定
创始人
2024-03-17 13:23:38
0

初学React类组件时,最不爽的一点应该就是 this 指向问题了吧!初识React的时候,肯定写过这样错误的demo。

import React from 'react';
export class ReactTestClass extends React.Component {constructor(props) {
super(props);
this.state = { a: 1 };}handleClick() {
this.setState({a: 2})}render() {
return 
{this.state.a}
;} } 上面的代码在执行 onClick 时,就会如期遇到如下的错误...

this 丢失了。编译React类组件时,会将 jsx 转成 React.createElement,并onClick 事件用对象包裹一层传参给该函数。

// 编译后的结果
class ReactTestClass extends _react.default.Component {constructor(props) {
super(props);
this.state = {a: 1
};}handleClick() {
this.setState({a: 2
});}render() {
return /*#__PURE__*/ _react.default.createElement("div",{
onClick: this.handleClick // ❌ 鬼在这里},this.state.a
);}
}
exports.ReactTestClass = ReactTestClass;

 

写到这里肯定会让大家觉得是 React 在埋坑,其实不然,官方文档有澄清:

这并不是 React 自身的行为: 这是因为 函数在 JS 中就是这么工作的。通常情况下,比如onClick={this.handleClick},你应该 bind 这个方法。

经受过面向对象编程的洗礼,为什么还要在类中手动绑定 this? 我们参考如下代码、

class TestComponent {
logThis () {
console.log(this); // 这里的 `this` 指向谁?
}
privateExecute (cb) {cb();
}
execute () {
this.privateExecute(this.logThis); // 正确的情况应该传入 this.logThis.bind(this)
}
}
const instance = new TestComponent();
instance.execute();

上述代码如期打印了 undefined。就是在 privateRender 中执行回调函数(执行的是 logThis 方法)时,this 变成了 undefined。写到这里可能有人会提出疑问,就算不是类的实例调用的 logThis 方法,那 this 也应该是 window 对象。

没错!在非严格模式下,就是 window 对象,但是(知识点) 使用了 ES6 的 class 语法,所有在 class 中声明的方法都会自动地使用严格模式,故 this 就是 undefined

所以,在非React类组件内,有时候也得手动绑定 this

优雅的@bindThis

使用 .bind(this)

render() {
return {this.state.a}
; }

或箭头函数

handleClick = () => {
this.setState({a: 2})
}

都可以完美解决,但是早已习惯面向对象和喜欢搞事情的我总觉得处理的不够优雅而大方。最终期望绑定this的方式如下,

import React from 'react';
import { bindThis } from './bind-this';
export class ReactTestClass extends React.Component {constructor(props) {
super(props);
this.state = { a: 1 };}@bindThis // 通过 `方法装饰器` 自动绑定thishandleClick() {
this.setState({ a: 2 });}render() {
return {this.state.a}
;} }

对于 方法装饰器,该函数对应三个入参,依次是

export function bindThis(
target: Object, 
propertyKey: string, 
descriptor: PropertyDescriptor,
) {
// 如果要返回值,应返回一个新的属性描述器
}

target 对应的是类的 prototype

propertyKey 对应的是方法名称,字符串类型,例如 "handleClick"

descriptor 属性描述器

对于 descriptor 能会比较陌生,当前该属性打印出来的结果是,

{value: [Function: handleClick],writable: true,enumerable: false,configurable: true
}

参看 MDN 上的 Object.defineProperty,我们发现对于属性描述器一共分成两种,data descriptor 和 accessor descriptor,两者的区别主要在内在属性字段上:

✅ 可以存在的属性,❌ 不能包含的属性

其中,

configurable,表示两种属性描述器能否转换、属性能否被删除等,默认 false

enumerable,表示是否是可枚举属性,默认 false

value,表示当前属性值,对于类中 handleClick 函数,value就是该函数本身

writable,表示当前属性值能否被修改

get,属性的 getter 函数。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this对象(由于继承关系,这里的this并不一定是定义该属性的对象)

set,属性的 setter 函数。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的this对象。

既然 get 函数有机会传入 this 对象,我们就从这里入手,通过 @bindThis 装饰器给 handleClick 函数绑定真正的 this

export function bindThis(
target: Object, 
propertyKey: string, 
descriptor: PropertyDescriptor,
) {
const fn = descriptor.value; // 先拿到函数本身
return {
configurable: true,
get() {
const bound = fn.bind(this); // 这里的 this 是当前类的示例
return bound;
}
}
}

bingo~~~

一个优雅又不失功能的 @bindThis 装饰器就这么愉快地搞定了。 

相关内容

热门资讯

严重违背人伦底线,犯罪手段特别... 据“遵义审判”消息,2025年12月26日,贵州省遵义市中级人民法院依法对被告人刘仲杰故意杀人案进行...
原创 死... 死刑判决能否抚平受害者家属的创伤?法律与心理的双重拷问 当一纸死刑判决书尘埃落定,法庭外的受害者家属...
贵州工会“一函两书”典型案例⑩... 编者按:“一函两书”制度,是工会组织开展劳动法律监督,联合检察院、法院、人社等部门提醒用人单位落实好...
建元信托4.98亿诉讼纠纷再起... 今年以来,建元信托遭遇11起诉讼案件,包括2起大额诉讼。 文/每日财报 楚风 建元信托的诉讼风险仍...
【学政策·人事人才篇】取得哪些... 来源:人力资源和社会保障部微信公众号
“严重违背人伦、道德、法律底线... 贵州省遵义市凤冈县男子刘仲杰离婚冷静期间,将10岁的儿子和7岁的女儿毒杀,此事引发关注。12月26日...
文典律师首席合伙人、主任宋杨东... 2025年12月15日,成都市律师协会金牛律师工作委员会组织金牛区的部分律所主任前往上海申浩律师事务...
刘仲杰,严重违背人伦底线,犯罪... 据“遵义审判”消息,12月26日,贵州省遵义市中级人民法院依法对被告人刘仲杰故意杀人案进行一审公开宣...
12亿元项目背后|蓝天彬律师《... 近年来,“受贿行贿一起查”,成了一个反腐热词。 我的当事人赵赫(化名),涉嫌非国家工作人员受贿罪,...
张家口住房公积金政策新变化,与... 2025年9月,住房公积金新政正式在全国范围内实施,各地积极推进。12月24日,在张家口市政府新闻办...