TS装饰器bindThis优雅实现React类组件中this绑定

 更新时间:2022年11月27日 17:17:12   作者:RyanOnCloud  
这篇文章主要为大家介绍了TS装饰器bindThis优雅实现React类组件中this绑定,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

为什么this会是undefined

初学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 <div onClick={this.handleClick}>{this.state.a}</div>;
  }
}

上面的代码在执行 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 <div onClick={this.handleClick.bind(this)}>{this.state.a}</div>;
}

或箭头函数

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 // 通过 `方法装饰器` 自动绑定this
  handleClick() {
    this.setState({ a: 2 });
  }
  render() {
    return <div onClick={this.handleClick}>{this.state.a}</div>;
  }
}

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

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 descriptoraccessor descriptor,两者的区别主要在内在属性字段上:

configurableenumerablevaluewritablegetset
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 装饰器就这么愉快地搞定了。

参考

考虑边界条件更为详细的 @bindThis 版本可参考:autobind-decorator

以上就是TS装饰器bindThis优雅实现React类组件中this绑定的详细内容,更多关于React类组件this绑定的资料请关注脚本之家其它相关文章!

相关文章

  • React报错之组件不能作为JSX组件使用的解决方法

    React报错之组件不能作为JSX组件使用的解决方法

    本文主要介绍了React报错之组件不能作为JSX组件使用的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • react.js框架Redux基础案例详解

    react.js框架Redux基础案例详解

    这篇文章主要介绍了react.js框架Redux基础案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-09-09
  • React-Native 组件之 Modal的使用详解

    React-Native 组件之 Modal的使用详解

    本篇文章主要介绍了React-Native 组件之 Modal的使用详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • react中使用better-scroll滚动插件的实现示例

    react中使用better-scroll滚动插件的实现示例

    滚动在很多地方都可以使用,本文主要介绍了react中使用better-scroll滚动插件的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-07-07
  • 浅谈React前后端同构防止重复渲染

    浅谈React前后端同构防止重复渲染

    这篇文章主要介绍了浅谈React前后端同构防止重复渲染,首先解释React前后端同构、React首屏渲染的概念。然后通过这2个概念解决服务端渲染完成后浏览器端重复渲染的问题。有兴趣的可以了解一下
    2018-01-01
  • react中使用Modal.confirm数据不更新的问题完美解决方案

    react中使用Modal.confirm数据不更新的问题完美解决方案

    这篇文章主要介绍了react中使用Modal.confirm数据不更新的问题解决方案,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-09-09
  • 详解如何在React中优雅的使用addEventListener

    详解如何在React中优雅的使用addEventListener

    这篇文章主要为大家详细介绍了如何在React中优雅的使用addEventListener,文中的示例代码简洁易懂,对大家学习React有一定的帮助,需要的可以参考一下
    2023-01-01
  • React使用UI(Ant Design)框架的详细过程

    React使用UI(Ant Design)框架的详细过程

    Ant Design主要用于中后台系统的使用,它提供了丰富的组件和工具,可以帮助开发人员快速构建出美观、易用的界面,同时,Ant Design还提供了详细的文档和示例,方便开发者学习和使用,这篇文章主要介绍了React使用UI(Ant Design)框架,需要的朋友可以参考下
    2023-12-12
  • React antd中setFieldsValu的简便使用示例代码

    React antd中setFieldsValu的简便使用示例代码

    form.setFieldsValue是antd Form组件中的一个方法,用于动态设置表单字段的值,它接受一个对象作为参数,对象的键是表单字段的名称,值是要设置的字段值,这篇文章主要介绍了React antd中setFieldsValu的简便使用,需要的朋友可以参考下
    2023-08-08
  • 详解在React里使用

    详解在React里使用"Vuex"

    本篇文章主要介绍了详解在React里使用"Vuex",小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-04-04

最新评论