我有一个TypeScript类,有一个我打算用作回调的函数:
removeRow(_this:MyClass): void {
...
// 'this' is now the window object
// I must use '_this' to get the class itself
...
}
我把它传递给另一个函数
this.deleteRow(this.removeRow);
反过来调用jQuery Ajax方法,如果成功,则调用这样的回调:
deleteItem(removeRowCallback: (_this:MyClass) => void ): void {
$.ajax(action, {
data: { "id": id },
type: "POST"
})
.done(() => {
removeRowCallback(this);
})
.fail(() => {
alert("There was an error!");
});
}
我可以保留对此类的'this'引用的唯一方法是将其传递给回调,如上所示。它有效,但它是裤子代码。如果我没有像这样连接'this'(抱歉),那么回调方法中对此的任何引用都已恢复为Window对象。因为我一直在使用箭头函数,所以我期望'this'将成为类本身,就像我班上的其他地方一样。
任何人都知道如何在TypeScript中传递回调,保留词法范围?
答案 0 :(得分:56)
新读者,请务必查看Zac's answer below。
他有一个更简洁的解决方案,可让您使用胖箭头语法在类定义中定义和实例化范围函数。
我要补充的唯一一点是,对于Zac的答案中的选项5 ,可以使用以下语法指定方法签名和返回类型而不重复:
public myMethod = (prop1: number): string => {
return 'asdf';
}
定义函数属性类型的语法已更改(因为TypeScript版本为0.8)。
以前你要定义一个这样的函数类型:
class Test {
removeRow: (): void;
}
现在已改为:
class Test {
removeRow: () => void;
}
我已在下面更新了我的答案,以包含这一新变化。
进一步说:如果你需要为同一个函数名定义多个函数签名(例如运行时函数重载),那么你可以使用对象映射表示法(这在jQuery中广泛使用)描述符文件):
class Test {
removeRow: {
(): void;
(param: string): string;
};
}
您需要将removeRow()
的签名定义为类的属性,但在构造函数中指定实现。
有几种不同的方法可以做到这一点。
class Test {
// Define the method signature here.
removeRow: () => void;
constructor (){
// Implement the method using the fat arrow syntax.
this.removeRow = () => {
// Perform your logic to remove the row.
// Reference `this` as needed.
}
}
}
如果你想保持你的构造函数最小,那么你可以将removeRow
方法保留在类定义中,只需在构造函数中指定一个代理函数:
class Test {
// Again, define the method signature here.
removeRowProxy: () => void;
constructor (){
// Assign the method implementation here.
this.removeRowProxy = () => {
this.removeRow.apply(this, arguments);
}
}
removeRow(): void {
// ... removeRow logic here.
}
}
最后,如果您使用像下划线或jQuery这样的库,那么您可以使用他们的实用工具方法来创建代理:
class Test {
// Define the method signature here.
removeRowProxy: () => void;
constructor (){
// Use jQuery to bind removeRow to this instance.
this.removeRowProxy = $.proxy(this.removeRow, this);
}
removeRow(): void {
// ... removeRow logic here.
}
}
然后你可以稍微整理一下deleteItem
方法:
// Specify `Function` as the callback type.
// NOTE: You can define a specific signature if needed.
deleteItem(removeRowCallback: Function ): void {
$.ajax(action, {
data: { "id": id },
type: "POST"
})
// Pass the callback here.
//
// You don't need the fat arrow syntax here
// because the callback has already been bound
// to the correct scope.
.done(removeRowCallback)
.fail(() => {
alert("There was an error!");
});
}
答案 1 :(得分:27)
更新:请参阅Sly的更新答案。它包含了以下选项的改进版本。
另一个更新:泛型
有时您希望在函数签名中指定泛型类型,而不必在整个类中指定它。我花了一些时间来弄清楚语法,所以我认为值得分享:
class MyClass { //no type parameter necessary here
public myGenericMethod = <T>(someArg:string): QPromise<T> => {
//implementation here...
}
}
选项4
这里还有一些语法可以添加到Sly_cardinal的答案中。这些示例将函数声明和实现保持在同一位置:
class Test {
// Define the method signature AND IMPLEMENTATION here.
public removeRow: () => void = () => {
// Perform your logic to remove the row.
// Reference `this` as needed.
}
constructor (){
}
}
或
选项5
稍微紧凑,但放弃显式返回类型(如果不明确,编译器应该推断返回类型):
class Test {
// Define implementation with implicit signature and correct lexical scope.
public removeRow = () => {
// Perform your logic to remove the row.
// Reference `this` as needed.
}
constructor (){
}
}
答案 2 :(得分:1)
这是另一个答案(Is there an alias for 'this' in TypeScript?)的交叉帖子。我使用上面的例子重新应用了这个概念。我比上面的选项更喜欢它,因为它明确地支持类实例以及调用该方法的动态上下文实体的“this”范围。
下面有两个版本。我喜欢第一个,因为编译器帮助正确使用它(你不会轻易地尝试滥用回调lambda本身作为回调,因为显式类型参数)。
测试出来: http://www.typescriptlang.org/Playground/
class Test {
private testString: string = "Fancy this!";
// Define the method signature here.
removeRowLambdaCallback(outerThis: Test): {(): void} {
alert("Defining callback for consumption");
return function(){
alert(outerThis.testString); // lexically scoped class instance
alert(this); // dynamically scoped context caller
// Put logic here for removing rows. Can refer to class
// instance as well as "this" passed by a library such as JQuery or D3.
}
}
// This approach looks nicer, but is more dangerous
// because someone might use this method itself, rather
// than the return value, as a callback.
anotherRemoveRowLambdaCallback(): {(): void} {
var outerThis = this;
alert("Defining another callback for consumption");
return function(){
alert(outerThis.testString); // lexically scoped class instance
alert(this); // dynamically scoped context caller
// Put logic here for removing rows. Can refer to class
// instance as well as "this" passed by a library such as JQuery or D3.
}
}
}
var t = new Test();
var callback1 = t.removeRowLambdaCallback(t);
var callback2 = t.anotherRemoveRowLambdaCallback();
callback1();
callback2();
答案 3 :(得分:1)
使用.bind()来保留回调中的上下文。
工作代码示例:
window.addEventListener(
"resize",
(()=>{this.retrieveDimensionsFromElement();}).bind(this)
)
原始问题中的代码会变成这样的:
$.ajax(action, {
data: { "id": id },
type: "POST"
})
.done(
(() => {
removeRowCallback();
}).bind(this)
)
它会将回调函数中的上下文(this)设置为作为bind函数的参数传递的内容,在这种情况下是原始的this对象。
答案 4 :(得分:0)
建立在狡猾和Zac的答案类型: 一个完整的hello world示例。我希望这是受欢迎的,因为这是Google搜索“typescript javascript回调”时的最佳结果
x/sb 0x804a46c
这可以转化为:
type MyCallback = () => string;
class HelloWorld {
// The callback
public callback: MyCallback = () => {
return 'world';
}
// The caller
public caller(callback: MyCallback) {
alert('Hello ' + callback());
}
}
let hello = new HelloWorld();
hello.caller(hello.callback);
希望有人觉得它有点用处。 :)