Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/362.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 我需要如何更改这些TypeScript混合类型定义,以便允许定义允许类扩展特性的混合类型? 对于这个问题的目的,考虑一个“MIXIN”是一个函数。在这种情况下,mixin扩展了接收mixin的类。我试图做一些不同的事情:启用“traits”,我在这里定义为可重用类,这些类提供公共和非公共实例成员,这些成员可以被扩展trait的类继承和重写,而不像mixin_Javascript_Typescript_Traits_Multiple Inheritance_Mixins - Fatal编程技术网

Javascript 我需要如何更改这些TypeScript混合类型定义,以便允许定义允许类扩展特性的混合类型? 对于这个问题的目的,考虑一个“MIXIN”是一个函数。在这种情况下,mixin扩展了接收mixin的类。我试图做一些不同的事情:启用“traits”,我在这里定义为可重用类,这些类提供公共和非公共实例成员,这些成员可以被扩展trait的类继承和重写,而不像mixin

Javascript 我需要如何更改这些TypeScript混合类型定义,以便允许定义允许类扩展特性的混合类型? 对于这个问题的目的,考虑一个“MIXIN”是一个函数。在这种情况下,mixin扩展了接收mixin的类。我试图做一些不同的事情:启用“traits”,我在这里定义为可重用类,这些类提供公共和非公共实例成员,这些成员可以被扩展trait的类继承和重写,而不像mixin,javascript,typescript,traits,multiple-inheritance,mixins,Javascript,Typescript,Traits,Multiple Inheritance,Mixins,随后尝试了一个解决方案,但打字不太正确,这就是我一直坚持的部分。请注意,这在JavaScript中非常有效,我编写的npm包就证明了这一点 我的问题是如何更改下面的类型定义,以便代码编译和测试通过 首先,这里有一个模块,traitify.ts,它试图成为这个模块的“库”(我知道它的类型定义不正确): 标记属性的默认实现是有意的,因为在这个模式中,我希望允许扩展trait的类只覆盖它希望覆盖的trait的那些成员 在保持示例最少但全面的同时,我必须再包含一个示例特征,以说明当类扩展多个特征时的模式

随后尝试了一个解决方案,但打字不太正确,这就是我一直坚持的部分。请注意,这在JavaScript中非常有效,我编写的npm包就证明了这一点

我的问题是如何更改下面的类型定义,以便代码编译和测试通过

首先,这里有一个模块,
traitify.ts
,它试图成为这个模块的“库”(我知道它的类型定义不正确):

标记
属性的默认实现是有意的,因为在这个模式中,我希望允许
扩展
trait的类只覆盖它希望覆盖的trait的那些成员

在保持示例最少但全面的同时,我必须再包含一个示例特征,以说明当类扩展多个特征时的模式,因此这里有一个可命名的特征,非常类似于上面的可标记的特征

// in file Nameable.ts

import { Constructor } from './traitify';

export interface INameable {
  name?: string;
}

export const Nameable = <S extends Constructor<object>>(superclass: S) =>
  class extends superclass implements INameable {
    _name?: string; // TODO: make protected when https://github.com/microsoft/TypeScript/issues/36060 is fixed

    get name() {
      return this._name;
    }

    set name(name) {
      this._doSetName(this._testSetName(name));
    }

    constructor(...args: any[]) {
      super(...args);
    }

    _testSetName(name?: string) { // TODO: make protected
      return name;
    }

    _doSetName(name?: string) { // TODO: make protected
      this._name = name;
    }
  };
我得到的编译器错误如下:

src/lib/traitify.spec.ts:84:10 - error TS2339: Property 'name' does not exist on type 'Person'.

84   person.name = 'Felix';
            ~~~~

src/lib/traitify.spec.ts:86:15 - error TS2339: Property 'name' does not exist on type 'Person'.

86   t.is(person.name, 'Felix');
                 ~~~~

src/lib/traitify.spec.ts:87:25 - error TS2339: Property 'name' does not exist on type 'Person'.

87   t.throws(() => person.name = null);
                           ~~~~

src/lib/traitify.spec.ts:127:10 - error TS2339: Property 'name' does not exist on type 'Person'.

127   person.name = 'a person';
             ~~~~

src/lib/traitify.spec.ts:129:15 - error TS2339: Property 'name' does not exist on type 'Person'.

129   t.is(person.name, 'a person');
                  ~~~~

src/lib/traitify.spec.ts:130:25 - error TS2339: Property 'name' does not exist on type 'Person'.

130   t.throws(() => person.name = 'an animal');
                            ~~~~

src/lib/traitify.spec.ts:131:25 - error TS2339: Property 'name' does not exist on type 'Person'.

131   t.throws(() => person.name = 'nothing');
                            ~~~~

src/lib/traitify.ts:48:35 - error TS2345: Argument of type 'S' is not assignable to parameter of type 'S'.
  'S' is assignable to the constraint of type 'S', but 'S' could be instantiated with a different subtype of constraint 'Constructor<object>'.
    Type 'Constructor<object>' is not assignable to type 'S'.
      'Constructor<object>' is assignable to the constraint of type 'S', but 'S' could be instantiated with a different subtype of constraint 'Constructor<object>'.

48     return new TraitBuilder(trait(this.superclass))
                                     ~~~~~~~~~~~~~~~


Found 8 errors.
src/lib/traitify.spec.ts:84:10-错误TS2339:类型“Person”上不存在属性“name”。
84 person.name='Felix';
~~~~
src/lib/traitify.spec.ts:86:15-错误TS2339:类型“Person”上不存在属性“name”。
86 t.is(人名“Felix”);
~~~~
src/lib/traitify.spec.ts:87:25-错误TS2339:类型“Person”上不存在属性“name”。
87 t.throws(()=>person.name=null);
~~~~
src/lib/traitify.spec.ts:127:10-错误TS2339:类型“Person”上不存在属性“name”。
127 person.name='a person';
~~~~
src/lib/traitify.spec.ts:129:15-错误TS2339:类型“Person”上不存在属性“name”。
129 t.is(person.name,'a person');
~~~~
src/lib/traitify.spec.ts:130:25-错误TS2339:类型“Person”上不存在属性“name”。
130吨(()=>person.name='animal');
~~~~
src/lib/traitify.spec.ts:131:25-错误TS2339:类型“Person”上不存在属性“name”。
131 t.throws(()=>person.name='nothing');
~~~~
src/lib/traitify.ts:48:35-错误TS2345:类型为“S”的参数不能分配给类型为“S”的参数。
“S”可分配给“S”类型的约束,但“S”可以用约束“构造函数”的不同子类型实例化。
类型“构造函数”不可分配给类型“S”。
“Constructor”可分配给“S”类型的约束,但“S”可以用约束“Constructor”的不同子类型实例化。
48返回新的TraitBuilder(trait(this.superclass))
~~~~~~~~~~~~~~~
发现8个错误。
注意:有一个Git repo可用于此,如果您想在播放时使用它,请执行Git clonehttps://github.com/matthewadams/typescript-trait-test &&cd类型脚本特性测试和npm安装和npm测试


注:我觉得这真的是我所能提供的最低限度,以演示我试图启用的模式。

好的,我终于在TypeScript中找到了一个不完美但可用的特质模式

TL;DR:如果你只是想看看演示模式的代码,那就太简单了。查看
测试
文件夹

在本讨论中,
trait
只是一个函数,它接受一个可选的超类,并返回一个扩展给定超类并实现一个或多个特定于trait的接口的新类。这是已知的子类工厂模式的变体的组合

这里有一个强制性的,虽然太少了,“你好,世界!”

这就是图书馆,如果你想叫它:

/**
 * Type definition of a constructor.
 */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Constructor<T> = new (...args: any[]) => T

/**
 * The empty class.
 */
export class Empty {}

/**
 * A "trait" is a function that takes a superclass of type `Superclass` and returns a new class that is of type `Superclass & TraitInterface`.
 */
// eslint-disable-next-line @typescript-eslint/ban-types
export type Trait<Superclass extends Constructor<object>, TraitInterface> = (
  superclass: Superclass
) => Constructor<Superclass & TraitInterface>
下面是如何编写一个类来表达这个特性。这是我的摩卡单元测试

// in file traitify.spec.ts

  it('expresses the simplest possible "Hello, world!" trait', function () {
    class HelloWorld extends Greetable.trait() {
      constructor(greeting = 'Hello') {
        super()
        this.greeting = greeting
      }
    }

    const greeter = new HelloWorld()

    expect(greeter.greet('world')).to.equal('Hello, world!')
  })
现在您已经看到了最简单的示例,让我来分享一个更现实的“Hello,world!”,它演示了一些更有用的东西。这是另一个Greetable特性,但其行为可由expressing类自定义

// in file Greetable2.ts

import { Constructor, Empty } from '../main/traitify'

/**
 * Public trait interface
 */
export interface Public {
  greeting?: string

  greet(greetee: string): string
}

/**
 * Nonpublic trait interface
 */
export interface Implementation {
  _greeting?: string

  /**
   * Validates, scrubs & returns given value
   */
  _testSetGreeting(value?: string): string | undefined

  /**
   * Actually sets given value
   */
  _doSetGreeting(value?: string): void
}

/**
 * The trait function.
 */
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types
export const trait = <S extends Constructor<object>>(superclass?: S) =>
  /**
   * Class that implements the trait
   */
  class Greetable2 extends (superclass || Empty) implements Implementation {
    _greeting?: string

    /**
     * Constructor that simply delegates to the super's constructor
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    constructor(...args: any[]) {
      super(...args)
    }

    get greeting() {
      return this._greeting
    }

    set greeting(value: string | undefined) {
      this._doSetGreeting(this._testSetGreeting(value))
    }

    greet(greetee: string): string {
      return `${this.greeting}, ${greetee}!`
    }

    _testSetGreeting(value?: string) {
      return value
    }

    _doSetGreeting(value?: string) {
      this._greeting = value
    }
  }
在本文中有更详尽的例子

有时候,TypeScript仍然会出错,通常是当你表达了一个以上的特征时,这是很常见的。有一个方便的方法,呃,帮助,TypeScript获取表达traits的类的正确类型:将表达类的构造函数的范围缩小为
受保护的
,并创建一个名为
new
的静态工厂方法,该方法使用
as
返回表达类的实例,以告诉TypeScript正确的类型是什么。这里有一个例子

  it('express multiple traits with no superclass', function () {
    class Point2 extends Nameable.trait(Taggable.trait()) {
      // required to overcome TypeScript compiler bug?
      static new(x: number, y: number) {
        return new this(x, y) as Point2 & Taggable.Public & Nameable.Public
      }

      protected constructor(public x: number, public y: number) {
        super(x, y)
      }

      _testSetTag(tag?: string) {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        tag = super._testSetTag(tag)

        if (!tag) throw new Error('no tag given')
        else return tag.toLowerCase()
      }

      _testSetName(name?: string) {
        name = super._testSetName(name)

        if (!name) throw new Error('no name given')
        else return name.toLowerCase()
      }
    }

    const point2 = Point2.new(10, 20)
    point2.tag = 'hello'

    expect(point2.tag).to.equal('hello')
    expect(() => (point2.tag = '')).to.throw()
  })
付出的代价相当小:你使用类似于
const it=it.new()
的东西,而不是
const it=new it()
。这会让你走得更远一些,但是你仍然需要在这里和那里撒上一个
/@ts ignore
,让TypeScript知道你知道自己在做什么

最后,有一个限制。如果您想要一个类来表达traits并扩展基类,那么如果traits具有相同的名称,那么它们将覆盖基类的方法。然而,在实践中,这并不是一个严重的限制,因为一旦你采用了特质模式,它就有效地取代了传统的
extends

您应该始终编写任何可重用的基于类的代码作为特征, 那就让你的班级来表达你的特点,不是吗
// in file Greetable.ts

import { Constructor, Empty } from '../main/traitify'

/*
 * Absolutely minimal demonstration of the trait pattern, in the spirit of "Hello, world!" demos.
 * This is missing some common stuff because it's so minimal.
 * See Greetable2 for a more realistic example.
 */

/**
 * Public trait interface
 */
export interface Public {
  greeting?: string

  greet(greetee: string): string
}

/**
 * The trait function.
 */
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types
export const trait = <S extends Constructor<object>>(superclass?: S) =>
  /**
   * Class that implements the trait
   */
  class Greetable extends (superclass || Empty) implements Public {
    greeting?: string

    /**
     * Constructor that simply delegates to the super's constructor
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    constructor(...args: any[]) {
      super(...args)
    }

    greet(greetee: string): string {
      return `${this.greeting}, ${greetee}!`
    }
  }
// in file traitify.spec.ts

  it('expresses the simplest possible "Hello, world!" trait', function () {
    class HelloWorld extends Greetable.trait() {
      constructor(greeting = 'Hello') {
        super()
        this.greeting = greeting
      }
    }

    const greeter = new HelloWorld()

    expect(greeter.greet('world')).to.equal('Hello, world!')
  })
// in file Greetable2.ts

import { Constructor, Empty } from '../main/traitify'

/**
 * Public trait interface
 */
export interface Public {
  greeting?: string

  greet(greetee: string): string
}

/**
 * Nonpublic trait interface
 */
export interface Implementation {
  _greeting?: string

  /**
   * Validates, scrubs & returns given value
   */
  _testSetGreeting(value?: string): string | undefined

  /**
   * Actually sets given value
   */
  _doSetGreeting(value?: string): void
}

/**
 * The trait function.
 */
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/explicit-module-boundary-types
export const trait = <S extends Constructor<object>>(superclass?: S) =>
  /**
   * Class that implements the trait
   */
  class Greetable2 extends (superclass || Empty) implements Implementation {
    _greeting?: string

    /**
     * Constructor that simply delegates to the super's constructor
     */
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    constructor(...args: any[]) {
      super(...args)
    }

    get greeting() {
      return this._greeting
    }

    set greeting(value: string | undefined) {
      this._doSetGreeting(this._testSetGreeting(value))
    }

    greet(greetee: string): string {
      return `${this.greeting}, ${greetee}!`
    }

    _testSetGreeting(value?: string) {
      return value
    }

    _doSetGreeting(value?: string) {
      this._greeting = value
    }
  }
// in file traitify.spec.ts

  it('expresses a more realistic "Hello, world!" trait', function () {
    class HelloWorld2 extends Greetable2.trait() {
      constructor(greeting = 'Hello') {
        super()
        this.greeting = greeting
      }

      /**
       * Overrides default behavior
       */
      _testSetGreeting(value?: string): string | undefined {
        value = super._testSetGreeting(value)

        if (!value) {
          throw new Error('no greeting given')
        }

        return value.trim()
      }
    }

    const greeter = new HelloWorld2()

    expect(greeter.greet('world')).to.equal('Hello, world!')
    expect(() => {
      greeter.greeting = ''
    }).to.throw()
  })
  it('express multiple traits with no superclass', function () {
    class Point2 extends Nameable.trait(Taggable.trait()) {
      // required to overcome TypeScript compiler bug?
      static new(x: number, y: number) {
        return new this(x, y) as Point2 & Taggable.Public & Nameable.Public
      }

      protected constructor(public x: number, public y: number) {
        super(x, y)
      }

      _testSetTag(tag?: string) {
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        tag = super._testSetTag(tag)

        if (!tag) throw new Error('no tag given')
        else return tag.toLowerCase()
      }

      _testSetName(name?: string) {
        name = super._testSetName(name)

        if (!name) throw new Error('no name given')
        else return name.toLowerCase()
      }
    }

    const point2 = Point2.new(10, 20)
    point2.tag = 'hello'

    expect(point2.tag).to.equal('hello')
    expect(() => (point2.tag = '')).to.throw()
  })