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 quill中图片上传由默认转成base64改成上传到服务器的方法

    react quill中图片上传由默认转成base64改成上传到服务器的方法

    这篇文章主要介绍了react quill中图片上传由默认转成base64改成上传到服务器的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-10-10
  • React插槽使用方法

    React插槽使用方法

    本文主要介绍了React插槽使用方法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-12-12
  • ReactNative错误采集原理在Android中实现详解

    ReactNative错误采集原理在Android中实现详解

    这篇文章主要为大家介绍了ReactNative错误采集原理在Android中实现详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • react中braft-editor的基本使用方式

    react中braft-editor的基本使用方式

    这篇文章主要介绍了react中braft-editor的基本使用方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • React使用Props实现父组件向子组件传值

    React使用Props实现父组件向子组件传值

    在React中,组件之间的数据传递通常是通过属性(Props)来实现的,父组件可以通过属性向子组件传递数据,这是React组件通信的基础模式之一,本文将探讨如何使用Props来实现父组件向子组件传递数据,需要的朋友可以参考下
    2025-04-04
  • 在React中引入Tailwind CSS的完整指南

    在React中引入Tailwind CSS的完整指南

    在现代前端开发中,使用 UI 库可以显著提高开发效率,Tailwind CSS 是一个功能类优先的 CSS 框架,本文将详细介绍如何在 React 项目中引入和使用 Tailwind CSS,包括各种配置选项和最佳实践,需要的朋友可以参考下
    2025-04-04
  • react如何修改循环数组对象的数据

    react如何修改循环数组对象的数据

    这篇文章主要介绍了react如何修改循环数组对象的数据问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-12-12
  • React Hook Form 优雅处理表单使用指南

    React Hook Form 优雅处理表单使用指南

    这篇文章主要为大家介绍了React Hook Form 优雅处理表单使用指南,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • React组件化学习入门教程讲解

    React组件化学习入门教程讲解

    React是现在前端使用频率最高的三大框架之一,React率先提出虚拟DOM的思想和实现,使其保持有良好的性能。本篇文章将对React组件化的入门学习进行讲解,同时针对模块化的思想进行概述,为接下来组件化开发的文章进行知识储备
    2022-09-09
  • react中使用better-scroll滚动插件的实现示例

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

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

最新评论