如何在类型方法中使用另一个类方法和this上下文? [英] How to use another class method and the this context in a type method?

查看:103
本文介绍了如何在类型方法中使用另一个类方法和this上下文?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想重写JavaScript方法,下面在TypeScript中进一步介绍。
我很想像这样做,就像这样:

I want to rewrite the JavaScript "method" further below in TypeScript. I am tempted to do it as class, like this:

// 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 & handleGridKeyDown 都引用 this事件上下文以及 this类上下文来获取选项。

However, handleGridClick & handleGridKeyDown both reference the 'this' "event-context", as well as the 'this' class-context to get the options.

问题是,我不能仅将this绑定到构造函数中,因为否则,该对象的this-context是错误的。我该怎么做?

Problem is, I can't just bind the this in the constructor, because otherwise, the this-context of the object is wrong. How can I do this?

这是普通的JavaScript变体:

This is the plain JavaScript variant:

// 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
    };
}


推荐答案

这更多的是JavaScript而不是打字稿问题。

This is more of a javascript than a typescript question.

在示例1中,您尝试使用类模式,在示例2中,您使用诸如闭包类之类的名称

In example 1 you are trying to use a "class pattern", in example 2 you use something like a "closure class" (theres a name for that pattern which I don't remember).

这两种模式都可以在TS中编写,我个人更喜欢保留闭包类(示例2)。因此,您只需保留代码并添加类型注释即可。启用 strict:true 并为编译器大喊的您具有隐式任何类型提供类型注释。

Both patterns are writeable in TS, and I personally prefer to keep "closure class" (example 2). So, you can just keep your code and add type annotations. Turn on strict: true and give type annotations to whatever the compiler yells you "has an implicit any type".

个人意见

模式nr.2通常更易于维护(来源?),模式1较难重构,需要更多类型注释,请多加思考,并为绑定问题留出空间。您仍然希望在性能要求较高的事物上使用模式1(例如,一个游戏,这似乎不是您的情况),除此之外,模式2是您的最佳选择。即使是经典的OOP案例(扩展类并重写方法),也可以通过选项2(选项袋模式)轻松获得。

Pattern nr.2 is usually more maintanable (sources?), pattern 1 is harder to refactor, requires more type annotations, more thinking and gives you room for the this binding issue. You'd still want to use pattern 1 on performance intensive things (ex: a game, which seems not to be your case), other than that, pattern 2 is your go. Even classic OOP cases (extend a class and override a method) are easily obtainable through option 2 (option bag pattern).

Typescript的类型系统具有结构性-更为强大而不是经典 java / C#-并且类在TS中不是名义的。这是不使用类的另外两个原因。 class A {} class B {} 或任何具有相同属性的对象都是可以分配的。

Typescript's type system is structural -- quite more powerful than "classic" java/C# -- and classes are not nominal in TS. Those are 2 more reasons not to use classes. class A {} and class B {} or any object are assignable if they have the same properties.

编辑:关于此绑定问题

如果您真的要坚持下去。 ..类...

If you really want to stick to ehh... classes...


  • 您不能拥有这个同时做两件事。如果您的 this 是您的课程,则可以通过 event.target 找到您的元素。如果您的这个反弹到一个元素...哦,很好。

  • You can't have your this be 2 things at the same time. If your this is your class, then you can find your element through event.target. If your this was rebound to an element... Oh, well.

所以您会必须调用 element.addEventListener( click,Instance.doSomething.bind(this))之类的东西。 addEventListener 重新绑定了函数的 this .bind 说:否。

So you'll have to call something like element.addEventListener("click", Instance.doSomething.bind(this)). addEventListener rebinds your function's this. .bind says: no.

Or element.addEventListener( click ,(... i)=> Instance.doSomething(... i))

如果您的方法是真的是要从另一个 this 上下文中调用,然后编写类似

If your method is really meant to be called from another this context, then write something like

方法之类的方法(this:HTMLInputElement,x:数字,y:字符串){}

method(this: HTMLInputElement, x: number, y: string) { }

是无非就是某种隐藏的函数参数(例如,python和lua明确将其作为第一个参数发送),它被 onX 调用覆盖,这是其中之一JS十亿美元的问题,这是JS类很烂的原因之一。

this is nothing more than sort of a hidden function parameter (python and lua explicitly send this as a 1st parameter, for instance), which is overriden by the onX calls, which is one of the JS billion-dollar problems, which is one of the reasons why JS classes suck.

这篇关于如何在类型方法中使用另一个类方法和this上下文?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆