我想重写JavaScript"方法"在TypeScript下面。 我很想在课堂上这样做:
// export default class
export default class GroupItemMetadataProvider1
{
protected m_instance;
protected _grid;
protected _defaults;
protected m_options;
constructor(options)
{
this.m_instance = this;
this._defaults = {
groupCssClass: "slick-group",
groupTitleCssClass: "slick-group-title",
totalsCssClass: "slick-group-totals",
groupFocusable: true,
totalsFocusable: false,
toggleCssClass: "slick-group-toggle",
toggleExpandedCssClass: "expanded",
toggleCollapsedCssClass: "collapsed",
enableExpandCollapse: true,
groupFormatter: this.defaultGroupCellFormatter,
totalsFormatter: this.defaultTotalsCellFormatter
};
options = $.extend(true, {}, this._defaults, options);
this.m_options = options;
}
protected defaultGroupCellFormatter(row, cell, value, columnDef, item)
{
if (!this.m_options.enableExpandCollapse)
{
return item.title;
}
let indentation = item.level * 15 + "px";
return "<span class='" + this.m_options.toggleCssClass + " " +
(item.collapsed ? this.m_options.toggleCollapsedCssClass : this.m_options.toggleExpandedCssClass) +
"' style='margin-left:" + indentation + "'>" +
"</span>" +
"<span class='" + this.m_options.groupTitleCssClass + "' level='" + item.level + "'>" +
item.title +
"</span>";
}
protected defaultTotalsCellFormatter(row, cell, value, columnDef, item)
{
return (columnDef.groupTotalsFormatter && columnDef.groupTotalsFormatter(item, columnDef)) || "";
}
protected init(grid)
{
this._grid = grid;
this._grid.onClick.subscribe(this.handleGridClick);
this._grid.onKeyDown.subscribe(this.handleGridKeyDown);
}
protected destroy()
{
if (this._grid)
{
this._grid.onClick.unsubscribe(this.handleGridClick);
this._grid.onKeyDown.unsubscribe(this.handleGridKeyDown);
}
}
protected handleGridClick(e, args)
{
let context = (<any>this);
let item = context.getDataItem(args.row);
if (item && item instanceof Slick.Group && $(e.target).hasClass(this.m_options.toggleCssClass))
{
let range = this._grid.getRenderedRange();
context.getData().setRefreshHints({
ignoreDiffsBefore: range.top,
ignoreDiffsAfter: range.bottom + 1
});
if (item.collapsed)
{
context.getData().expandGroup(item.groupingKey);
} else
{
context.getData().collapseGroup(item.groupingKey);
}
e.stopImmediatePropagation();
e.preventDefault();
}
}
// TODO: add -/+ handling
protected handleGridKeyDown(e)
{
let context = (<any>this);
if (this.m_options.enableExpandCollapse && (e.which == Slick.keyCode.SPACE))
{
let activeCell = context.getActiveCell();
if (activeCell)
{
let item = context.getDataItem(activeCell.row);
if (item && item instanceof Slick.Group)
{
let range = this._grid.getRenderedRange();
context.getData().setRefreshHints({
ignoreDiffsBefore: range.top,
ignoreDiffsAfter: range.bottom + 1
});
if (item.collapsed)
{
context.getData().expandGroup(item.groupingKey);
} else
{
context.getData().collapseGroup(item.groupingKey);
}
e.stopImmediatePropagation();
e.preventDefault();
}
}
}
}
public getGroupRowMetadata(item)
{
return {
selectable: false,
focusable: this.m_options.groupFocusable,
cssClasses: this.m_options.groupCssClass,
columns: {
0: {
colspan: "*",
formatter: this.m_options.groupFormatter,
editor: null
}
}
};
}
public getTotalsRowMetadata(item)
{
return {
selectable: false,
focusable: this.m_options.totalsFocusable,
cssClasses: this.m_options.totalsCssClass,
formatter: this.m_options.totalsFormatter,
editor: null
};
}
}
然而,handleGridClick&amp; handleGridKeyDown都引用了&#39; this&#39; &#34; event-context&#34;,以及&#39; this&#39; class-context获取选项。
问题是,我不能在构造函数中绑定它,因为否则,对象的this-context是错误的。我怎样才能做到这一点 ?
这是普通的JavaScript变体:
// import $ from '../../wwwroot/jQuery-3.3.js';
import Slick from './slick.core.js';
export default GroupItemMetadataProvider;
/***
* Provides item metadata for group (Slick.Group) and totals (Slick.Totals) rows produced by the DataView.
* This metadata overrides the default behavior and formatting of those rows so that they appear and function
* correctly when processed by the grid.
*
* This class also acts as a grid plugin providing event handlers to expand & collapse groups.
* If "grid.registerPlugin(...)" is not called, expand & collapse will not work.
*
* @class GroupItemMetadataProvider
* @module Data
* @namespace Slick.Data
* @constructor
* @param options
*/
function GroupItemMetadataProvider(options)
{
let _grid;
let _defaults = {
groupCssClass: "slick-group",
groupTitleCssClass: "slick-group-title",
totalsCssClass: "slick-group-totals",
groupFocusable: true,
totalsFocusable: false,
toggleCssClass: "slick-group-toggle",
toggleExpandedCssClass: "expanded",
toggleCollapsedCssClass: "collapsed",
enableExpandCollapse: true,
groupFormatter: defaultGroupCellFormatter,
totalsFormatter: defaultTotalsCellFormatter
};
options = $.extend(true, {}, _defaults, options);
function defaultGroupCellFormatter(row, cell, value, columnDef, item)
{
if (!options.enableExpandCollapse)
{
return item.title;
}
let indentation = item.level * 15 + "px";
return "<span class='" + options.toggleCssClass + " " +
(item.collapsed ? options.toggleCollapsedCssClass : options.toggleExpandedCssClass) +
"' style='margin-left:" + indentation + "'>" +
"</span>" +
"<span class='" + options.groupTitleCssClass + "' level='" + item.level + "'>" +
item.title +
"</span>";
}
function defaultTotalsCellFormatter(row, cell, value, columnDef, item)
{
return (columnDef.groupTotalsFormatter && columnDef.groupTotalsFormatter(item, columnDef)) || "";
}
function init(grid)
{
_grid = grid;
_grid.onClick.subscribe(handleGridClick);
_grid.onKeyDown.subscribe(handleGridKeyDown);
}
function destroy()
{
if (_grid)
{
_grid.onClick.unsubscribe(handleGridClick);
_grid.onKeyDown.unsubscribe(handleGridKeyDown);
}
}
function handleGridClick(e, args)
{
let item = this.getDataItem(args.row);
if (item && item instanceof Slick.Group && $(e.target).hasClass(options.toggleCssClass))
{
let range = _grid.getRenderedRange();
this.getData().setRefreshHints({
ignoreDiffsBefore: range.top,
ignoreDiffsAfter: range.bottom + 1
});
if (item.collapsed)
{
this.getData().expandGroup(item.groupingKey);
} else
{
this.getData().collapseGroup(item.groupingKey);
}
e.stopImmediatePropagation();
e.preventDefault();
}
}
// TODO: add -/+ handling
function handleGridKeyDown(e)
{
if (options.enableExpandCollapse && (e.which == Slick.keyCode.SPACE))
{
let activeCell = this.getActiveCell();
if (activeCell)
{
let item = this.getDataItem(activeCell.row);
if (item && item instanceof Slick.Group)
{
let range = _grid.getRenderedRange();
this.getData().setRefreshHints({
ignoreDiffsBefore: range.top,
ignoreDiffsAfter: range.bottom + 1
});
if (item.collapsed)
{
this.getData().expandGroup(item.groupingKey);
} else
{
this.getData().collapseGroup(item.groupingKey);
}
e.stopImmediatePropagation();
e.preventDefault();
}
}
}
}
function getGroupRowMetadata(item)
{
return {
selectable: false,
focusable: options.groupFocusable,
cssClasses: options.groupCssClass,
columns: {
0: {
colspan: "*",
formatter: options.groupFormatter,
editor: null
}
}
};
}
function getTotalsRowMetadata(item)
{
return {
selectable: false,
focusable: options.totalsFocusable,
cssClasses: options.totalsCssClass,
formatter: options.totalsFormatter,
editor: null
};
}
function getOptions()
{
return options;
}
return {
init,
destroy,
getGroupRowMetadata,
getTotalsRowMetadata,
getOptions
};
}
答案 0 :(得分:4)
这更像是一个javascript而不是打字稿问题。
在示例1中,您尝试使用“类模式”,在示例2中,您使用类似“闭包类”的东西(这是我不记得的模式的名称)。
这两种模式都可以在TS中编写,我个人更喜欢保留“封闭类”(例2)。因此,您可以保留代码并添加类型注释。打开strict: true
并为编译器发出的任何内容提供类型注释“具有隐式任意类型”。
提前个人意见
模式nr.2通常更具有可维护性(来源?),模式1更难以重构,需要更多类型注释,更多思考并为this
绑定问题提供空间。你仍然希望在性能密集的事情上使用模式1(例如:游戏,这似乎不是你的情况),除此之外,模式2是你的去。即使是经典的OOP案例(扩展类和覆盖方法)也可以通过选项2(选项包模式)轻松获得。
Typescript的类型系统是结构化的 - 比“经典”java / C#强大得多 - 并且类在TS中不是名义上的。这些是不使用课程的另外两个原因。 class A {}
和class B {}
或任何对象如果具有相同的属性,则可分配。
编辑:关于此绑定问题
如果你真的想坚持下去......班......
您不能同时拥有this
两件事。如果您的this
是您的班级,那么您可以通过event.target
找到您的元素。如果你的this
反弹到一个元素......哦,好吧。
所以你必须打电话给element.addEventListener("click", Instance.doSomething.bind(this))
。 addEventListener
重新绑定你的函数this
。 .bind
说:不。
或element.addEventListener("click", (...i) => Instance.doSomething(...i))
如果您的方法真的要从另一个this
上下文调用,那么请写一些类似
方法(这:HTMLInputElement,x:number,y:string){}
this
只不过是一个隐藏的函数参数(例如,python和lua显式地将其作为第一个参数发送),它被onX
调用覆盖,这是一个数十亿美元的问题,这是JS课程吮吸的原因之一。
答案 1 :(得分:0)
我不完全确定我已正确理解您的问题,但我认为您说的是事件处理程序引用了错误的对象。
您需要在事件之外保存您的上下文/范围,然后您可以在内部引用它,如此
class GroupItemMetadataProvider1
{
function init(grid)
{
let context = this;
_grid = grid;
_grid.addEventListener('click', (e, args) => context.handleGridClick(e, args));
_grid.onKeyDown.subscribe(handleGridKeyDown);
}
function handleGridClick(e, args)
{
console.log(this); // will reference GroupItemMetadataProvider1
}
}
答案 2 :(得分:0)
使用can arrow函数从声明上下文中捕获this
,并使用辅助函数将事件上下文作为参数传递。辅助函数将使事件订阅并将订阅推送到数组中,以便取消订阅所有事件。
subscriptions: Array<{ unsubscribe: () => any; }> = []
bindAndSubscribe<TArg1, TArg2>(target: {
subscribe(fn: (e: TArg1, data: TArg2) => any)
unsubscribe(fn: (e: TArg1, data: TArg2) => any)
}, handler: (context: any, e: TArg1, arg: TArg2) => void) {
let fn = function (e: TArg1, arg: TArg2) { handler(this, e, arg) };
target.subscribe(fn);
this.subscriptions.push({
unsubscribe: () => target.unsubscribe(fn)
});
}
protected init(grid: Slick.Grid<any>)
{
this._grid = grid;
// note paramters a and e are inffred correctly, if you ever want to add types
this.bindAndSubscribe(this._grid.onClick, (c, e, a)=> this.handleGridClick(c, e, a));
this.bindAndSubscribe(this._grid.onKeyDown, (c,e, a) => this.handleGridKeyDown(c,e));
}
protected destroy()
{
if (this._grid)
{
this.subscriptions.forEach(s=> s.unsubscribe());
}
}
protected handleGridClick(context, e, args)
{
// correct this
this.m_options.toggleCssClass
//...
}
protected handleGridKeyDown(context, e)
{
// Correct this, context is a parameter
//...
}
您也可以直接将处理程序声明为箭头函数,任何一种方法都可以:
protected init(grid: Slick.Grid<any>)
{
this._grid = grid;
// Arrow function not needed here anymore, the handlers capture this themselves.
this.bindAndSubscribe(this._grid.onClick, this.handleGridClick);
this.bindAndSubscribe(this._grid.onKeyDown, this.handleGridKeyDown);
}
protected handleGridClick = (context, e, args) =>
{
// correct this
this.m_options.toggleCssClass
//...
}
protected handleGridKeyDown = (context, e) =>
{
// Correct this, context is a parameter
//...
}
答案 3 :(得分:0)
正如本related ES6 answer中所解释的,这个问题在遗留的JavaScript库(D3等)中依然存在,它依赖于事件处理程序中的动态this
,而不是将所有必要的数据作为参数传递。
同样的解决方案也适用于TypeScript,需要维护类型 安全
一种解决方案是使用旧的self = this
配方,该配方已被弃用ES6箭头但在这种情况下仍需要并且有一些气味:
handleGridKeyDown(context: DynamicThisType, e: any) {
// class instance is available as `this`
// dynamic `this` is available as `context` param
}
...
const self = this;
this._grid.onKeyDown.subscribe(function (this: DynamicThisType, e: any) {
return self.handleGridKeyDown(this, e);
});
另一个解决方案是使用TypeScript装饰器将此配方应用于某些方法,这也会产生一个方法,其类实例为this
,动态this
为context
参数:
function bindAndPassContext(target: any, prop: string, descriptor?: PropertyDescriptor) {
const fn = target[prop];
return {
configurable: true,
get: function () {
const classInstance = this;
function wrapperFn (...args) {
return fn.call(classInstance, this, ...args);
}
Object.defineProperty(this, prop, {
configurable: true,
writable: true,
value: wrapperFn
});
return wrapperFn;
}
};
}
...
@bindAndPassContext
handleGridKeyDown(context: DynamicThisType, e: any) {
// class instance is available as `this`
// dynamic `this` is available as `context` param
}
...
this._grid.onKeyDown.subscribe(this.handleGridKeyDown);