javascript - Hur man använder en annan klassmetod och this-kontexten i en typmetod?
Jag vill skriva om JavaScript-"metoden" nedan i TypeScript. Jag är frestad att göra det som en klass, så här:
// 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
};
}
}
Men, handleGridClick och handleGridKeyDown refererar båda till 'this'-"event-kontexten", samt 'this'-klasskontexten för att få alternativen.
Problemet är att jag inte kan bara binda detta i konstruktorn, eftersom annars är objektets this-kontext fel. Hur kan jag göra detta?
Detta är den rena JavaScript-varianten:
// 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
};
}
Svar #1
Detta är mer en fråga om JavaScript än om TypeScript.
I exempel 1 försöker du använda ett "klassmönster", i exempel 2 använder du något som liknar en "stängningsklass" (det finns ett namn för det mönstret som jag inte kommer ihåg).
Båda mönstren kan skrivas i TS, och jag personligen föredrar att behålla "closure class" (exempel 2). Så, du kan helt enkelt behålla din kod och lägga till typannoteringar. Slå på strict: true och ge typannoteringar till vad än kompilatorn skriker att ”har en implicit any-typ”.
personlig åsikt framåt
Mönster nr.2 är vanligtvis mer underhållsvänligt (källor?), mönster 1 är svårare att refaktorera, kräver fler typangivelser, mer tänkande och ger dig utrymme för this-bindningsproblemet. Du skulle fortfarande vilja använda mönster 1 för prestandakrävande saker (t.ex.: ett spel, vilket inte verkar vara ditt fall), annars är mönster 2 ditt val. Även klassiska OOP-fall (ärva en klass och åsidosätta en metod) kan enkelt uppnås genom alternativ 2 (option bag-mönstret).
Typescripts typesystem är strukturellt - mycket mer kraftfullt än "klassiskt" java/C# - och klasser är inte nominella i TS. Det är ytterligare två skäl att inte använda klasser. class A {} och class B {} eller vilket objekt som helst kan tilldelas om de har samma egenskaper.
REDIGERA: Om detta bindingproblem
Om du verkligen vill hålla fast vid ehh... klasser...
Du kan inte ha din
thissom två saker samtidigt. Om dinthisär din klass, då kan du hitta ditt element genomevent.target. Om dinthisombindes till ett element... Nåväl.Så du kommer att behöva kalla något som
element.addEventListener("click", Instance.doSomething.bind(this)).addEventListenerbinder om ditt funktionsthis..bindsäger: nej.Eller
element.addEventListener("click", (...i) => Instance.doSomething(...i))Om din metod verkligen är avsedd att kallas från en annan
this-kontext, skriv då något sommetod(this: HTMLInputElement, x: number, y: string) { }
this är inget mer än en slags dold funktionsparameter (Python och Lua skickar detta uttryckligen som en första parameter, till exempel), vilken överskrivs av onX-anropen, vilket är ett av JS miljarddollarsproblem, vilket är en av anledningarna till varför JS-klasser suger.
Svar #2
Inte helt säker på att jag har förstått ditt problem korrekt, men jag tror att du säger att händelsehanteraren refererar till fel objekt.
Du behöver spara din kontext/omfång utanför händelsen, sedan kan du referera till det inuti, på detta sätt
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
}
}
