代码之家  ›  专栏  ›  技术社区  ›  Ruan Mendes

如何在TypeScript的decorators中重用decorators

  •  2
  • Ruan Mendes  · 技术社区  · 8 年前

    我正在尝试创建一些包装Angular2装饰器的功能。我想简化向主机添加CSS类的过程,因此我创建了以下内容:

    警告:不适用于AOT编译

    type Constructor = {new(...args: any[]): {}};
    
    export function AddCssClassToHost<T extends Constructor>(cssClass: string) {
        return function (constructor: T) {
            class Decorated extends constructor {
                @HostBinding("class") cssClass = cssClass;
            }
            // Can't return an inline class, so we name it.
            return Decorated;
        };
    }
    

    我还希望能够创建另一个添加特定CSS类的装饰器。

    /**
     * Decorator to be used for components that are top level routes. Automatically adds the content-container class that is
     * required so that the main content scrollbar plays nice with the header and the header won't scroll away.
     */
    export function TopLevelRoutedComponent<T extends Constructor>(constructor: T) {
       // This causes an error when called as a decorator
       return AddCssClassToHost("content-container");
        // Code below works, I'd like to avoid the duplication
        // class Decorated extends constructor {
        //     @HostBinding("class") cssClass = "content-container";
        // }
        // return Decorated;
    }
    
    // Called like
    @TopLevelRoutedComponent
    @Component({
        selector: "vcd-administration-navigation",
        template: `
            <div class="content-area">
                <router-outlet></router-outlet>
            </div>
            <vcd-side-nav [navMenu]="navItems"></vcd-side-nav>`
    })
    export class AdminNavigationComponent {
        navItems: NavItem[] = [{nameKey: "Multisite", routerLink: "multisite"}];
    }
    

    我收到的错误消息是

     TS1238: Unable to resolve signature of class decorator when called as an expression.
     Type '(constructor: Constructor) => { new (...args: any[]): AddCssClassToHost<Constructor>.Decorated; p...' is not assignable to type 'typeof AdminNavigationComponent'.
     Type '(constructor: Constructor) => { new (...args: any[]): AddCssClassToHost<Constructor>.Decorated; p...' provides no match for the signature 'new (): AdminNavigationComponent'
    

    我可以通过创建一个函数来解决这个问题,该函数由两个

    function wrapWithHostBindingClass<T extends Constructor>(constructor: T, cssClass: string) {
        class Decorated extends constructor {
            @HostBinding("class") cssClass = cssClass;
        }
        return Decorated; // Can't return an inline decorated class, name it.
    }
    export function AddCssClassToHost(cssClass: string) {
        return function(constructor) {
            return wrapWithHostBindingClass(constructor, cssClass);
        };
    }
    export function TopLevelRoutedComponent(constructor) {
        return wrapWithHostBindingClass(constructor, "content-container");
    }
    

    有没有一种方法可以使第一种样式工作,而不需要助手函数或复制代码?我意识到我的尝试不是很好,也没有意义,但我无法理解错误消息。

    AOT兼容的版本 由于以下操作简单得多,因此不会导致AOT编译器崩溃。但是,请注意,如果使用webpack编译器,自定义装饰程序将被剥离,因此它仅在使用ngc编译时才起作用。

    export function TopLevelRoutedComponent(constructor: Function) {
        const propertyKey = "cssClass";
        const className = "content-container";
        const descriptor = {
            enumerable: false,
            configurable: false,
            writable: false,
            value: className
        };
        HostBinding("class")(constructor.prototype, propertyKey, descriptor);
        Object.defineProperty(constructor.prototype, propertyKey, descriptor);
    } 
    
    1 回复  |  直到 8 年前
        1
  •  6
  •   Aluan Haddad Vikrant Kashyap    4 年前

    装饰器的类型签名中存在一些错误,使其无法满足typechecker的要求。

    为了解决这些问题,您必须了解 装饰师 装饰工厂 .

    正如您可能怀疑的那样,decorator工厂只不过是一个返回decorator的函数。

    如果要通过参数化自定义应用的装饰器,请使用装饰器工厂。

    以您的代码为例,下面是一个 装饰厂

    export type Constructor<T = object> = new (...args: any[]) => T;
    
    export function AddCssClassToHost<C extends Constructor>(cssClass: string) {
        return function (Class: C) {
            
            // TypeScript does not allow decorators on class expressions so we create a local
            class Decorated extends Class {
                @HostBinding("class") cssClass = cssClass;
            }
            return Decorated;
        };
    }
    

    decorator工厂接受一个参数,该参数定制它返回的decorator所使用的css,并用其替换目标(这是一个很好的例子)。

    但是现在我们想重用decorator工厂来添加一些特定的css。这意味着我们需要的是一个普通的老装饰师,而不是工厂。

    既然装饰厂正好归还我们所需要的东西,我们可以直接打电话给它。

    使用第二个示例,

    export const TopLevelRoutedComponent = AddCssClassToHost("content-container");
    

    现在我们在应用程序中遇到错误

    @TopLevelRoutedComponent
    @Component({...})
    export class AdminNavigationComponent {
        navItems = [{nameKey: "Multisite", routerLink: "multisite"}];
    }
    

    我们必须考虑原因。

    调用工厂函数将实例化其类型参数。

    我们需要一个创建泛型装饰器的工厂,而不是返回非泛型装饰器的泛型装饰器工厂!

    也就是说, TopLevelRoutedComponent 需要通用。

    我们可以通过简单地重写原始的decorator工厂,将类型参数移动到它返回的decorator来实现这一点

    export function AddCssClassToHost(cssClass: string) {
        return function <C extends Constructor>(Class: C) {
    
            // TypeScript does not allow decorators on class expressions so we create a local
            class Decorated extends Class {
                @HostBinding("class") cssClass = cssClass;
            }
            return Decorated;
        };
    }
    

    这是一个 live example

    推荐文章