我想创建一个函数对象,它还具有一些属性。例如,在JavaScript中,我会这样做:
var f = function() { }
f.someValue = 3;
现在在TypeScript中,我可以将其描述为:
var f: { (): any; someValue: number; };
但是,如果不需要演员,我实际上无法构建它。如:
var f: { (): any; someValue: number; } =
<{ (): any; someValue: number; }>(
function() { }
);
f.someValue = 3;
如果没有演员,你会如何构建它?
答案 0 :(得分:92)
更新:这个答案是早期版本的TypeScript中的最佳解决方案,但在较新版本中有更好的选择(参见其他答案)。
接受的答案有效,在某些情况下可能需要,但是在构建对象时没有提供类型安全的缺点。如果您尝试添加未定义的属性,此技术至少会抛出类型错误。
interface F { (): any; someValue: number; }
var f = <F>function () { }
f.someValue = 3
// type error
f.notDeclard = 3
答案 1 :(得分:50)
使用Object.assign(target, source)
示例:
这里的神奇之处在于输入Object.assign<T, U>(t: T, u: U)
来返回交集 T & U
。
强制执行此解析已知接口也很简单。例如:
interface Foo {
(a: number, b: string): string[];
foo: string;
}
let method: Foo = Object.assign(
(a: number, b: string) => { return a * a; },
{ foo: 10 }
);
由于不兼容的输入导致的错误:
错误:foo:数字不能分配给foo:string
错误:数字不能分配给字符串[](返回类型)
警告:如果定位旧浏览器,您可能需要polyfill Object.assign。
答案 2 :(得分:41)
TypeScript旨在通过declaration merging处理此案例:
您可能也熟悉创建函数的JavaScript实践,然后通过向函数添加属性来进一步扩展函数。 TypeScript使用声明合并以类型安全的方式构建这样的定义。
声明合并让我们说某事既是函数又是命名空间(内部模块):
function f() { }
namespace f {
export var someValue = 3;
}
这会保留输入内容,让我们同时写下f()
和f.someValue
。为现有JavaScript代码编写.d.ts
文件时,请使用declare
:
declare function f(): void;
declare namespace f {
export var someValue: number;
}
向函数添加属性通常是TypeScript中令人困惑或意外的模式,因此请尽量避免使用它,但在使用或转换旧的JS代码时可能需要这样做。这是将内部模块(名称空间)与外部模块混合的唯一时机之一。
答案 3 :(得分:18)
因此,如果要求只是简单地构建并将该函数分配给“f”而不进行强制转换,那么这是一个可能的解决方案:
var f: { (): any; someValue: number; };
f = (() => {
var _f : any = function () { };
_f.someValue = 3;
return _f;
})();
本质上,它使用自执行函数文字来“构造”一个对象,该对象在赋值完成之前将匹配该签名。唯一奇怪的是函数的内部声明需要是'any'类型,否则编译器会谴责你分配给对象上不存在的属性。
编辑:稍微简化了代码。
答案 4 :(得分:2)
作为一种快捷方式,您可以使用['property']访问者动态分配对象值:
var f = function() { }
f['someValue'] = 3;
这绕过了类型检查。但是,它非常安全,因为您必须以同样的方式故意访问该属性:
var val = f.someValue; // This won't work
var val = f['someValue']; // Yeah, I meant to do that
但是,如果你真的想要对属性值进行类型检查,那么这将不起作用。
答案 5 :(得分:1)
更新的答案:由于通过&
添加了交集类型,因此可以&#34;合并&#34;两种推断类型即时。
这是一个通用帮助程序,它读取某个对象from
的属性并将它们复制到对象onto
上。它返回相同的对象onto
,但使用包含两组属性的新类型,因此正确描述了运行时行为:
function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
Object.keys(from).forEach(key => onto[key] = from[key]);
return onto as T1 & T2;
}
这个低级助手仍然执行类型断言,但它在设计上是类型安全的。有了这个帮助,我们就有了一个操作员,我们可以使用它来解决OP的全类型安全问题:
interface Foo {
(message: string): void;
bar(count: number): void;
}
const foo: Foo = merge(
(message: string) => console.log(`message is ${message}`), {
bar(count: number) {
console.log(`bar was passed ${count}`)
}
}
);
Click here to try it out in the TypeScript Playground。请注意,我们已将foo
约束为Foo
类型,因此merge
的结果必须是完整的Foo
。因此,如果您将bar
重命名为bad
,则会出现类型错误。
NB 但是,这里仍有一个类型的洞。 TypeScript没有提供将类型参数约束为&#34;而不是函数&#34;的方法。所以你可能会感到困惑,并将你的函数作为merge
的第二个参数传递,这是行不通的。因此,在可以声明之前,我们必须在运行时捕获它:
function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
if (typeof from !== "object" || from instanceof Array) {
throw new Error("merge: 'from' must be an ordinary object");
}
Object.keys(from).forEach(key => onto[key] = from[key]);
return onto as T1 & T2;
}
答案 6 :(得分:1)
我不能说它非常简单,但绝对可能:
interface Optional {
<T>(value?: T): OptionalMonad<T>;
empty(): OptionalMonad<any>;
}
const Optional = (<T>(value?: T) => OptionalCreator(value)) as Optional;
Optional.empty = () => OptionalCreator();
如果你好奇this is from a gist of mine with the TypeScript/JavaScript version of Optional
答案 7 :(得分:0)
这远离强力打字,但你可以做到
var f: any = function() { }
f.someValue = 3;
如果你试图绕过压抑的强烈打字,就像我在发现这个问题时那样。遗憾的是,这是一个案例,TypeScript在完全有效的JavaScript上失败,因此您必须告诉TypeScript退出。
“您的JavaScript是完全有效的TypeScript”评估为false。 (注意:使用0.95)
答案 8 :(得分:0)
旧问题,但是对于以3.1开头的TypeScript版本,只要对变量使用函数声明或const
关键字,就可以像在普通JS中那样简单地进行属性分配:< / p>
function f () {}
f.someValue = 3; // fine
const g = function () {};
g.someValue = 3; // also fine
var h = function () {};
h.someValue = 3; // Error: "Property 'someValue' does not exist on type '() => void'"