【问题标题】:Context for nested classes in ES6 is strangeES6 中嵌套类的上下文很奇怪
【发布时间】:2018-08-02 04:32:54
【问题描述】:

我有以下嵌套类结构:

import React, {Component} from 'react';
import {TextField} from '@material-ui/core';
import './ProfileEditor.css';

export default class ProfileEditor extends Component {
    SyncedTextField = class SyncedTextField extends Component {
        onChange = event => {
            console.log(this);
        };

        render() {
            return (
                <TextField
                    {...this.props}
                    onChange={this.onChange}/>
            );
        }
    };

    render() {
        return (
            <form className={"ProfileEditor"}>
                <this.SyncedTextField/>
            </form>
        );
    }
}

当代码被 Webpack 打包并在 Firefox 中运行时,它可以正确运行 this.onChange,但输出的 this 引用了 ProfileEditor 类的上下文。

这太奇怪了,因为在 JSX 中,当我引用“this”时,它正确指向 SyncedTextField,但在 onChange 方法中,它指向 ProfileEditor

我确实向ProfileEditor 添加了一些属性以自我检查,并且这些属性显示为ProfileEditor 中声明的那样,即使在 SyncedTextField 中提供了冲突的定义。

谁能告诉我如何避免这个问题,以及可能是什么原因造成的?

【问题讨论】:

  • 在 Microsoft Edge 中测试时会发生同样的情况。
  • 是的,没错——因为它是一个箭头函数......其this 与函数是常规符号不同(即你是如何做到的render()
  • 哦,所以箭头函数将保持绑定到ProfileEditor 范围,而不是SyncedTextField?对我来说似乎有点奇怪,但谢谢!
  • 这与定义函数时this 的含义有关-我在解释箭头函数时很糟糕:p 有什么理由你不能onChange(event) { 。 .. 等等?
  • 不,没有,我只是在试验上下文会是什么,结果真的让我吃惊。我认为这可能是因为class 在内部是一种更好的方式来提供带有原型的函数,我在登录时看到了它。

标签: javascript reactjs firefox webpack


【解决方案1】:

不正确的行为可能特定于浏览器开发工具。但在这种情况下,它是由转译器的工作方式引起的。 Babel 6 类字段(即第 3 阶段提案)转换实现中存在错误。

The example 使用 Babel 编译输出 ProfileEditorthis in onChange

这是来自 Babel 输出的 SyncedTextField 构造函数:

function SyncedTextField() {
  var _ref2;

  var _temp2, _this2, _ret2;

  _classCallCheck(this, SyncedTextField);

  for (
    var _len2 = arguments.length, args = Array(_len2), _key2 = 0;
    _key2 < _len2;
    _key2++
  ) {
    args[_key2] = arguments[_key2];
  }

  return (
    (_ret2 = ((_temp2 = ((_this2 = _possibleConstructorReturn(
      this,
      (_ref2 =
        SyncedTextField.__proto__ ||
        Object.getPrototypeOf(SyncedTextField)).call.apply(
        _ref2,
        [this].concat(args)
      )
    )),
    _this2)),
    (_this2.onChange = function(event) {
      console.log(_this); // SHOULD BE _this2
    }),
    _temp2)),
    _possibleConstructorReturn(_this2, _ret2)
  );
}

请注意,转译器会创建 _this_this2 等临时变量以在箭头函数中提供词法 this,但 Babel 使用了错误的变量。

onChange = ... 类字段是语法糖:

  constructor(...args) {
    super(...args);

    this.onChange = event => {
        console.log(this);
    };
  }

the example is changed from class fields to constructor code时,输出SyncedTextField

使用 TypeScript 编译的The same example(在 React 模板中默认由 Stackblitz 使用)按预期工作,并在 onChange 中将 SyncedTextField 输出为 this

由于很少这样定义类,Babel bug 通常不适用。

SyncedTextField = class SyncedTextField extends Component {...} 是一种反模式。没有理由像这样嵌套类表达式。这是低效的,因为它是在每个ProfileEditor 实例化上评估的。应该是类声明,可以作为&lt;SyncedTextField/&gt; this way.

即使出于可测试性或可扩展性原因,SyncedTextField 应定义为 ProfileEditor 组件的属性,但最好将其设为原型属性:

class SyncedTextField extends Component {...}

class ProfileEditor extends Component {
  get SyncedTextField() { return SyncedTextField }
  ...
}

【讨论】:

  • 惊人的答案。 Create React App 会自动添加 Babel 吗?附带说明一下,我不想提取 SyncedTextField,因为它管理 ProfileEditor 的状态。
  • create-react-app 只是用于设置github.com/facebook/create-react-app/tree/master/packages/… Webpack+Babel 模板的 CLI 工具。所以是的,它使用了 Babel。我希望这个问题会在 Babel 7 中得到解决,因此您可以弹出项目并将 Babel 升级到 7。因为它管理 ProfileEditor 的状态 - 这未在问题中显示。很可能有更多惯用的方法来做你想做的事情。如果需要,请考虑提出问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-01
  • 2015-02-27
  • 2021-03-25
相关资源
最近更新 更多