在JavaScript中,我可以定义一个构造函数,可以使用或不使用new
调用它:
function MyClass(val) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
}
this.val = val;
}
然后我可以使用以下任一语句构造MyClass
个对象:
var a = new MyClass(5);
var b = MyClass(5);
我尝试使用下面的TypeScript类获得类似的结果:
class MyClass {
val: number;
constructor(val: number) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
}
this.val = val;
}
}
但是调用MyClass(5)
会给我一个错误Value of type 'typeof MyClass' is not callable. Did you mean to include 'new'?
有什么办法可以让这个模式在TypeScript中运行吗?
答案 0 :(得分:15)
这个怎么样?描述MyClass
及其构造函数的所需形状:
interface MyClass {
val: number;
}
interface MyClassConstructor {
new(val: number): MyClass; // newable
(val: number): MyClass; // callable
}
请注意,MyClassConstructor
被定义为可以作为函数调用,也可以作为构造函数使用。然后实现它:
const MyClass: MyClassConstructor = function(this: MyClass | void, val: number) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
} else {
this!.val = val;
}
} as MyClassConstructor;
以上作品,虽然有一些小皱纹。 Wrinkle one:实现返回MyClass | undefined
,并且编译器没有意识到MyClass
返回值对应于可调用函数,undefined
值对应于newable构造函数。所以它抱怨。因此最后是as MyClassConstructor
。皱纹二:this
参数does not currently narrow via control flow analysis,因此我们必须声明this
在设置其void
属性时不是val
,即使此时我们知道它不能void
。所以我们必须使用non-null assertion operator !
。
无论如何,您可以验证这些工作:
var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass
希望有所帮助;祝你好运!
警告:正如@ Paleo answer中所述,如果您的目标是ES2015或更高版本,则在源代码中使用class
将在已编译的JavaScript中输出class
,并且那些需要 new()
根据规范。我看到过像TypeError: Class constructors cannot be invoked without 'new'
这样的错误。一些JavaScript引擎很可能忽略了规范,并且也很乐意接受函数式调用。如果您不关心这些警告(例如,您的目标明确是ES5,或者您知道您将在其中一个非规范的环境中运行),那么您肯定可以强制使用TypeScript随之而来:
class _MyClass {
val: number;
constructor(val: number) {
if (!(this instanceof MyClass)) {
return new MyClass(val);
}
this.val = val;
}
}
type MyClass = _MyClass;
const MyClass = _MyClass as typeof _MyClass & ((val: number) => MyClass)
var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass
在这种情况下,您已将MyClass
重命名为_MyClass
,并将MyClass
定义为两种类型(与_MyClass
相同)和一个值(与_MyClass
构造函数相同,但其类型被声明为可以像函数一样调用。)这在编译时工作,如上所示。您的运行时是否满意它受上述警告的影响。我个人在我的原始答案中坚持使用函数风格,因为我知道这些函数在es2015及更高版本中都是可调用的和可更新的。
再次祝你好运!
如果你只想找到一种方法从this answer声明bindNew()
函数的类型,这需要符合规范的class
并生成两者兼备的内容像函数一样可新增和可调用,你可以这样做:
function bindNew<C extends { new(): T }, T>(Class: C & {new (): T}): C & (() => T);
function bindNew<C extends { new(a: A): T }, A, T>(Class: C & { new(a: A): T }): C & ((a: A) => T);
function bindNew<C extends { new(a: A, b: B): T }, A, B, T>(Class: C & { new(a: A, b: B): T }): C & ((a: A, b: B) => T);
function bindNew<C extends { new(a: A, b: B, d: D): T }, A, B, D, T>(Class: C & {new (a: A, b: B, d: D): T}): C & ((a: A, b: B, d: D) => T);
function bindNew(Class: any) {
// your implementation goes here
}
这样可以正确输入:
class _MyClass {
val: number;
constructor(val: number) {
this.val = val;
}
}
type MyClass = _MyClass;
const MyClass = bindNew(_MyClass);
// MyClass's type is inferred as typeof _MyClass & ((a: number)=> _MyClass)
var a = new MyClass(5); // MyClass
var b = MyClass(5); // also MyClass
但要注意bindNew()
的重载声明不适用于每种可能的情况。具体来说,它适用于最多需要三个参数的构造函数。具有可选参数或多个过载签名的构造函数可能无法正确推断。因此,您可能需要根据用例调整输入。
好的,希望 有所帮助。祝你好运第三次。
TypeScript 3.0引入了tuples in rest and spread positions,允许我们轻松处理任意数量和类型参数的函数,而不会出现上述重载和限制。这是bindNew()
的新声明:
declare function bindNew<C extends { new(...args: A): T }, A extends any[], T>(
Class: C & { new(...args: A): T }
): C & ((...args: A) => T);
答案 1 :(得分:5)
ES6类需要关键字new
:
但是,您只能通过new调用类,而不是通过函数调用(规范中的第9.2.2节)[source]
答案 2 :(得分:0)
instanceof
和extends
的解决方案我见过的大多数解决方案都存在问题
使用x = X()
而不是x = new X()
是:
x instanceof X
不起作用class Y extends X { }
不起作用console.log(x)
打印除X
以外的其他类型x = X()
可以工作,但x = new X()
无效使用下面的代码(也在GitHub上-参见:ts-no-new),您可以编写:
interface A {
x: number;
a(): number;
}
const A = nn(
class A implements A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
);
或:
class $A {
x: number;
constructor() {
this.x = 10;
}
a() {
return this.x += 1;
}
}
type A = $A;
const A = nn($A);
而不是通常的:
class A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
可以使用a = new A()
或a = A()
使用instanceof
,extends
,适当的继承和对现代编译目标的支持(某些解决方案仅在编译到ES5或更早版本时才有效,因为它们依赖于class
转换为function
具有不同的调用语义)。
type cA = () => A;
function nonew<X extends Function>(c: X): AI {
return (new Proxy(c, {
apply: (t, _, a) => new (<any>t)(...a)
}) as any as AI);
}
interface A {
x: number;
a(): number;
}
const A = nonew(
class A implements A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
);
interface AI {
new (): A;
(): A;
}
const B = nonew(
class B extends A {
a() {
return this.x += 2;
}
}
);
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
class $A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
type A = $A;
const A: MC<A> = nn($A);
Object.defineProperty(A, 'name', { value: 'A' });
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
const B: MC<B> = nn($B);
Object.defineProperty(B, 'name', { value: 'B' });
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
type $c = { $c: Function };
class $A {
static $c = A;
x: number;
constructor() {
this.x = 10;
Object.defineProperty(this, 'constructor', { value: (this.constructor as any as $c).$c || this.constructor });
}
a() {
return this.x += 1;
}
}
type A = $A;
var A: MC<A> = nn($A);
$A.$c = A;
Object.defineProperty(A, 'name', { value: 'A' });
class $B extends $A {
static $c = B;
a() {
return this.x += 2;
}
}
type B = $B;
var B: MC<B> = nn($B);
$B.$c = B;
Object.defineProperty(B, 'name', { value: 'B' });
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
class $A {
x: number;
constructor() {
this.x = 0;
}
a() {
return this.x += 1;
}
}
type A = $A;
const A: MC<A> = nn($A);
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
const B: MC<B> = nn($B);
type NC<X> = { new (): X };
type FC<X> = { (): X };
type MC<X> = NC<X> & FC<X>;
function nn<X>(C: NC<X>): MC<X> {
return new Proxy(C, {
apply: (t, _, a) => new (<any>t)(...a)
}) as MC<X>;
}
class $A {
x: number;
constructor() {
this.x = 10;
}
a() {
return this.x += 1;
}
}
type A = $A;
var A: MC<A> = nn($A);
class $B extends $A {
a() {
return this.x += 2;
}
}
type B = $B;
var B: MC<B> = nn($B);
在#1 和#2 中:
instanceof
有效extends
有效console.log
可以正确打印constructor
属性指向实际的构造函数在#3 中:
instanceof
有效extends
有效console.log
可以正确打印constructor
实例的属性指向暴露的包装器(根据情况可能是优缺点)如果不需要,简化版本不会提供所有用于自省的元数据。
答案 3 :(得分:0)
我对类型和函数的解决方法:
class _Point {
public readonly x: number;
public readonly y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
export type Point = _Point;
export function Point(x: number, y: number): Point {
return new _Point(x, y);
}
答案 4 :(得分:0)
TL; DR
如果您以ES6为目标,并且确实要使用class
来存储数据,而不是function
:
function
,它只需使用其参数调用您的类构造函数即可; function
的{{1}}设置为班级的prototype
。从现在起,您可以使用或不使用 prototype
关键字来调用function
以生成新的类实例。
Typescript提供了一种以强类型创建此类new
(称为“可调用构造函数”)的功能。好吧,在中间类型定义中,function
类型是必需的(用any
替换会导致错误),但是这个事实不会会影响您的体验。
首先,我们需要定义基本类型来描述我们正在使用的实体:
unknown
下一步是编写一个接受常规类构造函数并创建相应的“可调用构造函数”的函数。
// Let's assume "class X {}". X itself (it has type "typeof X") can be called with "new" keyword,
// thus "typeof X" extends this type
type Constructor = new(...args: Array<any>) => any;
// Extracts argument types from class constructor
type ConstructorArgs<TConstructor extends Constructor> =
TConstructor extends new(...args: infer TArgs) => any ? TArgs : never;
// Extracts class instance type from class constructor
type ConstructorClass<TConstructor extends Constructor> =
TConstructor extends new(...args: Array<any>) => infer TClass ? TClass : never;
// This is what we want: to be able to create new class instances
// either with or without "new" keyword
type CallableConstructor<TConstructor extends Constructor> =
TConstructor & ((...args: ConstructorArgs<TConstructor>) => ConstructorClass<TConstructor>);
现在我们要做的就是创建“可调用的构造函数”并检查它是否确实有效。
function CreateCallableConstructor<TConstructor extends Constructor>(
type: TConstructor
): CallableConstructor<TConstructor> {
function createInstance(
...args: ConstructorArgs<TConstructor>
): ConstructorClass<TConstructor> {
return new type(...args);
}
createInstance.prototype = type.prototype;
return createInstance as CallableConstructor<TConstructor>;
}
答案 5 :(得分:0)
这是我在jest
中解决此问题的方法,用于测试不可变模型组。 makeHash
函数并没有做任何特别的事情,只是一个实用程序,它从uuid()
创建短随机字符串。
对我来说,“魔术”是将type
声明为new (...args: any[]) => any
,从而可以将其“更新”为let model = new set.type(...Object.values(set.args));
。因此,关于绕过new
的事情要少得多,而要以“可更新的”形式工作要更多。
// models/oauth.ts
export class OAuthEntity<T = string> {
constructor(public readonly id: T) {}
[key: string]: any;
}
export class OAuthClient extends OAuthEntity {
/**
* An OAuth Client
* @param id A unique string identifying the client.
* @param redirectUris Redirect URIs allowed for the client. Required for the authorization_code grant.
* @param grants Grant types allowed for the client.
* @param accessTokenLifetime Client-specific lifetime of generated access tokens in seconds.
* @param refreshTokenLifetime Client-specific lifetime of generated refresh tokens in seconds
* @param userId The user ID for client credential grants
*/
constructor(
public readonly id: string = '',
public readonly redirectUris: string[] = [],
public readonly grants: string[] = [],
public readonly accessTokenLifetime: number = 0,
public readonly refreshTokenLifetime: number = 0,
public readonly userId?: string,
public readonly privateKey?: string
) {
super(id);
}
}
// models/oauth.test.ts
import { makeHash, makePin } from '@vespucci/utils';
import { OAuthEntity, OAuthClient } from '@vespucci/admin/server/models/oauth';
type ModelData = { type: new (...args: any[]) => any; args: { [key: string]: any }; defs?: { [key: string]: any } };
describe('Model Tests', () => {
const dataSet: ModelData[] = [
{ type: OAuthEntity, args: { id: makeHash() } },
{
type: OAuthClient,
args: {
id: makeHash(),
redirectUris: [makeHash()],
grants: [makeHash()],
accessTokenLifetime: makePin(2),
refreshTokenLifetime: makePin(2),
userId: makeHash(),
privateKey: makeHash(),
},
},
{
type: OAuthClient,
args: {},
defs: {
id: '',
redirectUris: [],
grants: [],
accessTokenLifetime: 0,
refreshTokenLifetime: 0,
},
},
];
dataSet.forEach((set) => {
it(`Creates ${set.type.name} With ${Object.keys(set.args).length} Args As Expected`, () => {
let model!: any;
const checkKeys = Object.keys(set.args).concat(Object.keys(set.defs || {}).filter((k) => !(k in set.args)));
const checkValues: any = checkKeys
.map((key) => ({ [key]: set.args[key] || set.defs?.[key] }))
.reduce((p, c) => ({ ...p, ...c }), {});
expect(() => {
model = new set.type(...Object.values(set.args));
}).not.toThrow();
expect(model).toBeDefined();
checkKeys.forEach((key) => expect(model[key]).toEqual(checkValues[key]));
});
});
});
对我来说,最终结果是: