例如,我正在运行一个带有D3.js的项目,导入特定的模块并调用它们的函数。
设定:
我有一个对象,在这种情况下是一个角度指令,并将一些圆圈绘制到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
答案 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