ES6中嵌套类的上下文很奇怪 [英] Context for nested classes in ES6 is strange

查看:110
本文介绍了ES6中嵌套类的上下文很奇怪的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下嵌套类结构:

 从'react'导入React,{Component};来自'@ material-ui / core'的
import {TextField};
import'./ProfileEditor.css';

导出默认类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中提供了冲突的定义。



有人可以告诉我如何避免这个问题,以及可能导致这个问题的原因吗?

解决方案

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



用Babel编译的示例输出 ProfileEditor as this 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] =参数[_key2];
}

返回(
(_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); //应该_this2
}),
_temp2 )),
_possibleConstructorReturn(_this2,_ret2)
);
}

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



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

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

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

示例从类字段更改为构造函数代码,它输出 SyncedTextField



使用TypeScript编译的相同示例(默认情况下由Stackblitz使用)在React模板中)按预期工作并在 onChange SyncedTextField as this $ c>。



由于很少以这种方式定义类,因此Babel bug通常不适用。



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



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

  class SyncedTextField extends Component {...} 

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


I have the following nested class structure:

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>
        );
    }
}

When the code is bundled by Webpack, and run in Firefox, it runs this.onChange correctly, but the outputted this refers the the context of the ProfileEditor class instead.

This is excessively strange because in the JSX, when I refer to "this" it points to SyncedTextField correctly, but in the onChange method, it points to ProfileEditor.

I did add some properties to ProfileEditor to sanity check myself, and the properties showed up as declared in ProfileEditor, even when a conflicting definition was provided in SyncedTextField.

Can someone please tell me how I can avoid this issue, and what may be causing it?

解决方案

Incorrect behaviour may be specific to browser development tools. But in this case it's caused by how transpiler works. There is a bug in Babel 6 class fields (which are stage 3 proposal) transform implementation.

The example compiled with Babel outputs ProfileEditor as this in onChange.

Here's SyncedTextField constructor from Babel output:

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)
  );
}

Notice that transpilers create _this, _this2, etc. temporary variables to provide lexical this in arrow functions but Babel uses wrong variable.

onChange = ... class field is syntactic sugar for:

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

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

When the example is changed from class fields to constructor code, it outputs SyncedTextField.

The same example compiled with TypeScript (used by Stackblitz by default in React template) works as expected and outputs SyncedTextField as this in onChange.

Since classes are rarely defined this way, Babel bug is usually not applicable.

SyncedTextField = class SyncedTextField extends Component {...} is an antipattern. There is no reason to nest class expression like that. It is inefficient because it is evaluated on each ProfileEditor instantiation. It should be class declaration, can be used as <SyncedTextField/> this way.

Even if SyncedTextField should be defined as a property of ProfileEditor component for testability or extensibility reasons, it's preferable to make it prototype property:

class SyncedTextField extends Component {...}

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

这篇关于ES6中嵌套类的上下文很奇怪的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆