避免在ES6

时间:2018-05-13 19:23:00

标签: javascript typescript d3.js ecmascript-6 arrow-functions

例如,我正在运行一个带有D3.js的项目,导入特定的模块并调用它们的函数。

设定:

  • 打字稿/ ES6
  • 导入特定的D3组件
  • Angular 6

我有一个对象,在这种情况下是一个角度指令,并将一些圆圈绘制到SVG画布上,并希望它们在拖动事件上触发一个函数。

缩减的代码段 请查看此代码段底部的drawPoints()

import { ElementRef, HostListener, Output, EventEmitter, OnInit, Input, OnChanges, SimpleChanges, Directive } from '@angular/core';
import * as Selection from 'd3-selection';
import * as Shape from 'd3-shape';
import * as Random from 'd3-random';
import * as Drag from 'd3-drag';

import { Config } from './../models/config.model';
import { Point } from './../models/point.model';
import { Param } from './../models/param.model';

@Directive({
  selector: '[appCanvas]'
})
export class CanvasDirective implements OnInit, OnChanges {
  private canvas: any;
  private defs: any;
  private gradient: any;
  private svg: any;
  private expandedPoints: Point[];
  private drag: Point;
  public config: Config;

  @Input()
  private param: Param;

  @Output()
  private emitConfig: EventEmitter<Config>;

  @HostListener('window:resize', ['$event'])
  private onResize(event) {
    this.init();
  }

  constructor(
    private el: ElementRef
  ) {
    this.canvas = el.nativeElement;
    this.emitConfig = new EventEmitter();
  }

  ngOnInit() {
    intSvg();
    // ..
  }

  private initSvg() {
    if (!this.svg) {
      this.svg = Selection.select(this.canvas).append('svg');
    }
    this.svg
      .attr('width', this.config.width)
      .attr('height', this.config.height);
  }

  private drawPoints(points: Point[]) {
    points.forEach(point => {
      this.svg.append('circle')
        .attr('r', point.color ? 20 : 10)
        .attr('cx', point.x)
        .attr('cy', point.y)
        .attr('fill', point.color ? point.color : 'lightgray')
        .attr('stroke-width', !point.color ? 2 : 0)
        .attr('stroke', !point.color ? 'gray' : '')
        .call(Drag.drag()
          .on('drag', () => {
            // What to call here?
            // Selection.select(this) will not work
            // So how to target the correct „this“?
          }));
    });
  }
  // ...
}

发生的问题无法在附加圈子的拖动功能内找到正确的this

有多个例子,但是他们不会在课堂上工作,因为this参数受到保护。

致Mike Bostock的例子https://bl.ocks.org/mbostock/22994cc97fefaeede0d861e6815a847e

2 个答案:

答案 0 :(得分:2)

像D3这样的旧库依赖于动态this上下文,而不是将所有必要的数据作为参数传递,并且需要使用const self = this技巧在回调中到达词法this。这个技巧在ES6中被认为是过时的,但在这种情况下是必要的。需要使用常规函数而不是箭头才能获得动态上下文:

private drawPoints(points: Point[]) {
  const self = this;
  ...
    .call(Drag.drag()
      .on('drag', function (this: ProperContextTypeIfNecessary) {
        Selection.select(this);
        // class instance can be referred as `self`
      }));
});

类实例在一个地方应该被称为this而在另一个地方应该被称为self似乎不一致(这在ES5中不是问题因为self应该是在这种情况下要彻底使用以保持一致性。)

this related answer中所述,另一种选择是包装函数,它将回调函数D3上下文作为参数提供,而this仍然可以引用类实例。在这种情况下可以使用箭头函数:

function contextWrapper(fn) {
    const self = this;

    return function (...args) {
        return fn.call(self, this, ...args);
    }
}

...

private drawPoints(points: Point[]) {
  const self = this;
  ...
    .call(Drag.drag()
      .on('drag', contextWrapper((d3Context: ProperContextTypeIfNecessary) => {
        Selection.select(d3Context);
        // class instance can be referred as `this`
      }));
});

答案 1 :(得分:1)

通过避免使用ES6组件中的流行箭头函数语法并使用旧的function() {}语法,问题得以解决。箭头函数改变了游戏,因为在这些被调用的函数中,事实证明this没有被简化为函数的被调用上下文,而是上下文全局扩展到类级别。

<强>解决方案:

private drawPoints(points: Point[]) {
  points.forEach(point => {
    this.svg.append('circle')
      .attr('r', point.color ? 20 : 10)
      .attr('cx', point.x)
      .attr('cy', point.y)
      .attr('fill', point.color ? point.color : 'lightgray')
      .attr('stroke-width', !point.color ? 2 : 0)
      .attr('stroke', !point.color ? 'gray' : '')
      .call(Drag.drag()
        .on('drag', function() {
          console.log(this);
        }));
  });
}

进一步阅读:

  

[...]和ES2015 [ES6]引入了箭头函数,它们不提供它们自己的这种绑定(它保留了封闭词汇上下文的这个值)。   rwaldron, abasao, martian2049 et. al, this, MDN web docs