- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
出于本问题的目的,将“mixin”视为 https://www.typescriptlang.org/docs/handbook/mixins.html 中所述的函数。 .在这种情况下,mixin 扩展了接收 mixin 的类。我正在尝试做一些不同的事情:启用“特征”,我在这里将其定义为可重用的类,这些类提供公共(public)和非公共(public)实例成员,这些成员可以被扩展特征的类继承和覆盖,这与 mixin 不同.
随后尝试解决方案,但类型不太正确,这就是我坚持的部分。请注意,这在 JavaScript 中非常有效,正如我编写的 npm 包 @northscaler/mutrait 所证明的那样。 .
我的问题是如何更改下面的类型定义以便代码编译和测试通过?
首先,这是模块,traitify.ts
,它试图成为这个的“库”(我知道它的类型定义不正确):
// in file traitify.ts
/**
* Type definition of a constructor.
*/
export type Constructor<T> = new(...args: any[]) => T;
/**
* A "trait" is a function that takes a superclass `S` and returns a new class `T extends S`.
*/
export type Trait<S extends Constructor<object>, T extends S> = (superclass: S) => T
/**
* Convenient function when defining a class that
* * extends a superclass, and
* * expresses one or more traits.
*/
export const superclass = <S extends Constructor<object>>(s?: S) => new TraitBuilder(s)
/**
* Convenient function to be used when a class
* * does not extend a superclass, and
* * expresses multiple traits.
*/
export const traits = <S extends Constructor<object>, T extends S>(t: Trait<S, T>) => superclass().with(t)
/**
* Convenient function to be used when defining a class that
* * does not extend a superclass, and
* * expresses exactly one trait.
*/
export const trait = <S extends Constructor<object>, T extends S>(t: Trait<S, T>) => traits(t).apply()
/**
* A convenient trait applier class that uses a builder pattern to apply traits.
*/
class TraitBuilder<S extends Constructor<object>> {
superclass: S;
constructor (superclass?: S) {
this.superclass = superclass || class {} as S // TODO: remove "as S" when figured out
}
/**
* Applies the trait to the current superclass then returns a new `TraitBuilder`.
* @param trait The trait that the current superclass should express.
*/
with <S extends Constructor<object>, T extends S>(trait: Trait<S, T>) {
// we have to return a new builder here because there's no way to take a collection of traits of differing types.
return new TraitBuilder(trait(this.superclass))
}
/**
* Return the class with all traits expressed.
*/
apply() {
return this.superclass || class {}
}
}
我希望能够定义一个
Taggable
特征,在
Taggable.ts
,如下所示,其中 trait 定义了一个 protected
_tag
字段,并提供
tag
的默认实现属性(property):
// in file Taggable.ts
import { Constructor } from './traitify';
export interface ITaggable {
tag?: string;
}
export const Taggable = <S extends Constructor<object>>(superclass: S) =>
class extends superclass implements ITaggable {
_tag?: string; // TODO: make protected when https://github.com/microsoft/TypeScript/issues/36060 is fixed
get tag() {
return this._tag;
}
set tag(tag) {
this._doSetTag(this._testSetTag(tag));
}
constructor(...args: any[]) {
super(...args);
}
_testSetTag(tag?: string) { // TODO: make protected
return tag;
}
_doSetTag(tag?: string) { // TODO: make protected
this._tag = tag;
}
};
tag
的默认实现属性是有意的,因为在这种模式中,我想允许
extend
的类trait 只覆盖它希望覆盖的那些 trait 成员。
extend
时的模式。多个特征,所以这里是
Nameable
特质,与
Taggable
非常相似以上。
// 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;
}
};
现在,通过我们的
traitify
库和两个特征,这里是我试图通过的测试,它说明了特征的使用者将如何使用它:
import { trait, superclass } from './traitify';
import test from 'ava';
import { Taggable } from './Taggable';
import { Nameable } from './Nameable';
test('express a single trait with no superclass', (t) => {
class Point extends trait(Taggable) {
constructor(public x: number, public y: number) {
super(...arguments);
this.x = x;
this.y = y;
}
_testSetTag(tag?: string) {
tag = super._testSetTag(tag);
if (!tag) throw new Error('no tag given');
else return tag.toLowerCase();
}
}
const point = new Point(10, 20);
point.tag = 'hello';
t.is(point.tag, 'hello');
t.throws(() => point.tag = '');
});
test('express a single trait and extend a superclass', (t) => {
class Base {
something: string = 'I am a base';
}
class Sub extends superclass(Base)
.with(Taggable).apply() {
constructor() {
super(...arguments);
}
_testSetTag(tag?: string): string | undefined {
tag = super._testSetTag(tag);
if (tag === 'throw') throw new Error('illegal tag value');
return tag;
}
}
const sub = new Sub();
t.assert(sub instanceof Sub);
t.assert(sub instanceof Base);
sub.tag = 'sub';
t.is(sub.tag, 'sub');
t.throws(() => sub.tag = 'throw');
});
test('express multiple traits and extend a superclass', (t) => {
class Animal {
}
class Person extends superclass(Animal)
.with(Nameable)
.with(Taggable).apply() {
constructor(...args: any[]) {
super(args);
}
_testSetName(name?: string) {
if (!name) throw new Error('no name given');
return name.trim();
}
}
const person = new Person();
t.assert(person instanceof Person);
t.assert(person instanceof Animal);
person.name = 'Felix';
t.is(person.name, 'Felix');
t.throws(() => person.name = null);
});
test('superclass expresses a trait, subclass expresses another trait but overrides method in superclass\'s trait', (t) => {
class Animal extends trait(Nameable) {
constructor(...args: any[]) {
super(args);
}
_testSetName(name?: string) {
if (!name) throw new Error('no name given');
if (name.toLowerCase().includes('animal')) throw new Error('name must include "animal"');
return name;
}
}
const animal = new Animal();
animal.name = 'an animal';
t.is(animal.name, 'an animal');
t.throws(() => animal.name = 'nothing');
class Person extends superclass(Animal)
.with(Taggable).apply() {
constructor(...args: any[]) {
super(args);
}
_testSetName(name?: string) {
if (!name) throw new Error('no name given');
if (name.toLowerCase().includes('person')) throw new Error('name must include "person"');
return name;
}
}
const person = new Person();
t.assert(person instanceof Person);
t.assert(person instanceof Animal);
person.name = 'a person';
t.is(person.name, 'a person');
t.throws(() => person.name = 'an animal');
t.throws(() => person.name = 'nothing');
});
我得到的编译器错误如下:
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.
注意:如果您想在
https://github.com/matthewadams/typescript-trait-test. 上使用它,可以使用 Git 存储库。要播放,请执行
git clone https://github.com/matthewadams/typescript-trait-test && cd typescript-trait-test && npm install && npm test
.
最佳答案
好的,我终于在 TypeScript 中找到了一个不完美但可用的特征模式。
TL;DR:如果您只想查看演示该模式的代码,它是 here .查看 main
& test
文件夹。
一个 trait
就本次讨论而言,它只是一个函数,它接受一个可选的父类(super class)并返回一个扩展给定父类(super class)并实现一个或多个特定于特征的接口(interface)的新类。这是众所周知的子类工厂模式的变体的组合 here和 intersection types .
这是强制性的,尽管太少了,“你好,世界!”。
这是图书馆,如果你想称之为:
/**
* 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>
这是一个
Greetable
特质,赋予
greeting
属性(property)和打招呼的方法。请注意,它包含一个用于返回特征
implements
的公共(public)行为的接口(interface)。 .
// 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}!`
}
}
下面是如何编写一个类来表达特征。这是来自我的 mocha 单元测试。
// 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!')
})
现在你已经看到了最简单的例子,让我分享一个更现实的“你好,世界!”这证明了一些更有用的东西。这是另一个 Greetable 特性,但它的行为可由表达类自定义。
// 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
}
}
这具有包括两个接口(interface)的关键特性,一个用于公共(public)行为,一个用于非公共(public)行为。
Public
接口(interface)表示对表达特征的类的客户可见的行为。
Implementation
interface 将实现的细节表示为一个接口(interface),而
Implementation extends Public
.然后 trait 函数返回一个类
implements Implementation
.
_testSetGreeting
方法验证、清理并返回设置的值,以及
_doSetGreeting
实际设置支持属性
_greeting
.
_testSetGreeting
以确保发出问候语并 trim 问候语。
// 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()
})
repo中有更详尽的例子.
protected
并创建一个名为
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 知道你知道你在做什么。
extends
的传统用法。 .
You should always author any reusable class-based code as a trait,then have your classes express the traits, not extend base classes.
trait
/
mixin
&
with
作为此模式语法糖的关键字,很像
Dart's mixin
& with
或
Scala's traits ,除糖之外,所有范围和类型安全问题都将得到解决(以及
diamond problem 的解决方案)。
关于javascript - 我需要如何更改这些 TypeScript mixin 类型定义才能允许定义允许类扩展特征的 mixin?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65922936/
我有一个 ASP.NET 网站,我希望只允许 AD 组中的用户访问该网站。我正在使用如下的 web.config 片段,但这似乎不起作用:
仅当选中所有框时才应禁用“允许”按钮。我该怎么做?我已经完成了 HTML 部分,如下所示。如何执行其中的逻辑部分?即使未选中一个复选框,也应禁用“允许”按钮
当前有一个Navigator.push(context,route),但是上下文部分返回了错误,在尝试调试后,我发现问题是因为我在调用一个函数而不是直接将home设置为widget树。但是现在我不确定
这是我的邮政编码正则表达式 ^[a-zA-Z0-9]{1,9}$ 但不允许 A-12345。如何更改 - 也将被允许的正则表达式? 最佳答案 在字符集的开头或结尾添加-([...]): ^[-a-zA
我目前正在建立我的网站,但遇到了一个问题 JavaScript 中的混合内容阻止 当我尝试加载和显示来自 的图像和页面时,Chrome、Mozilla 和 Explorer 会发生这种情况http 我
我见过使用: [mysqld] bind-address = 255.112.324.12 允许远程访问单个 IP。我如何允许从 mysql 远程访问所有 IP? 最佳答案 如果你想允许它用于所
我想知道是否可以使用模板实现某些功能。我想要做的是允许特定的“复制构造函数和赋值运算符”从一个模板到另一个模板并禁用其他模板。 我想我只完成了一件我想要的事情,所以我提供了下面的类(class)。对于
这个问题在这里已经有了答案: How to validate an email address in PHP (15 个答案) 关闭 2 年前。 正则表达式让我大吃一惊。我如何更改此设置以验证带有加
解析可以采用以下格式之一的日期的最佳方法是什么 "dd-MM-yyyy HH:mm" "dd/MM/yyyy HH:mm" "dd.MM.yyyy HH:mm" 无需创建 3 个 SimpleD
我们知道,下面的代码格式不正确,因为成员 x 在依赖的基类中。但是,将指定行上的 x 更改为 this->x 将修复错误。 template struct B { int x; }; tem
如果能帮助我理解“Java 并发实践”中的以下内容,我将不胜感激: Calling an overrideable instance method(one that is neither privat
此时如果上传一个不在预定义的安全扩展名列表,如.lrc,会报错: File type does not meet security guidelines. Try another. 解决此问题有
我有一个运行韵律,可以为我的几个域和一个 friend 域处理 XMPP。我 friend 域中的一位用户(他的妻子)想更改她的密码(实际上她忘记了她,所以我会用 prosodyctl 设置一个,然后
使用 nginx,您可以允许和拒绝范围和 ips (https://www.nginx.com/resources/admin-guide/restricting-access/)。使用realip模
什么是一些好的克里金法/插值想法/选项,可以让重度权重的点在绘制的 R map 上的轻权重点上流血? 康涅狄格州有八个县。我找到了质心并想绘制这八个县中每个县的贫困率。其中三个县人口稠密(约 100
我正在使用 virtualbox + ubuntu + vagrant . 但是我不能ping或 wget任何网址。请指导我如何允许虚拟机访问我的主机的互联网? 最佳答案 这对我有用。 使用此配置 V
标题可能有点令人困惑,所以让我向您解释一下。 在 Swift 中,我们可以拥有带有默认参数值的函数,例如: func foo(value: Int = 32) { } 我们也可以有 In-Out 参数
有TextView1 和TextView2。 TextView2 应该 float 在 TextView1 的右侧。只要两个 TextView 的总宽度不使 TextView2 与右侧的框重叠,Tex
使用 Magento 收集方法 addFieldToFilter 时是否可以允许按 NULL 值进行过滤?我想选择集合中具有自定义属性的所有产品,即使没有为该属性分配任何值。 最佳答案 您不需要使用
我正试图从 .htaccess 文件中的规则中“排除”一个目录(及其所有文件夹)... 不确定这是否可能? .htaccess 文件是这样的: Order Allow,Deny Deny from a
我是一名优秀的程序员,十分优秀!