使用 TypeScript 中的属性构建函数对象

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12766528/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-11 00:50:38  来源:igfitidea点击:

Build a function object with properties in TypeScript

castingtypescriptfunction-object

提问by JL235

I want to create a function object, which also has some properties held on it. For example in JavaScript I would do:

我想创建一个函数对象,它也有一些属性。例如在 JavaScript 中,我会这样做:

var f = function() { }
f.someValue = 3;

Now in TypeScript I can describe the type of this as:

现在在 TypeScript 中,我可以将它的类型描述为:

var f: { (): any; someValue: number; };

However I can't actually build it, without requiring a cast. Such as:

但是,我实际上无法构建它,而无需演员阵容。如:

var f: { (): any; someValue: number; } =
    <{ (): any; someValue: number; }>(
        function() { }
    );
f.someValue = 3;

How would you build this without a cast?

如果没有演员,你会如何建造这个?

采纳答案by nxn

So if the requirement is to simply build and assign that function to "f" without a cast, here is a possible solution:

因此,如果要求是简单地构建并将该函数分配给“f”而不进行强制转换,那么这是一个可能的解决方案:

var f: { (): any; someValue: number; };

f = (() => {
    var _f : any = function () { };
    _f.someValue = 3;
    return _f;
})();

Essentially, it uses a self executing function literal to "construct" an object that will match that signature before the assignment is done. The only weirdness is that the inner declaration of the function needs to be of type 'any', otherwise the compiler cries that you're assigning to a property which does not exist on the object yet.

本质上,它使用自执行函数字面量来“构造”一个​​对象,该对象将在分配完成之前匹配该签名。唯一奇怪的是函数的内部声明需要是“any”类型,否则编译器会抱怨您正在分配对象上尚不存在的属性。

EDIT: Simplified the code a bit.

编辑:稍微简化了代码。

回答by Greg Weber

Update: This answer was the best solution in earlier versions of TypeScript, but there are better options available in newer versions (see other answers).

更新:此答案是较早版本的 TypeScript 中的最佳解决方案,但在较新版本中有更好的选择(请参阅其他答案)。

The accepted answer works and might be required in some situations, but have the downside of providing no type safety for building up the object. This technique will at least throw a type error if you attempt to add an undefined property.

接受的答案有效并且在某些情况下可能是必需的,但缺点是没有为构建对象提供类型安全。如果您尝试添加未定义的属性,此技术至少会引发类型错误。

interface F { (): any; someValue: number; }

var f = <F>function () { }
f.someValue = 3

// type error
f.notDeclard = 3

回答by Meirion Hughes

This is easily achievable now (typescript 2.x) with Object.assign(target, source)

这现在很容易实现(打字稿 2.x) Object.assign(target, source)

example:

例子:

enter image description here

在此处输入图片说明

The magic here is that Object.assign<T, U>(t: T, u: U)is typed to return the intersectionT & U.

这里的神奇之处在于它Object.assign<T, U>(t: T, u: U)被输入以返回交集T & U

Enforcing that this resolves to a known interface is also straight-forward. For example:

强制将其解析为已知接口也是直截了当的。例如:

interface Foo {
  (a: number, b: string): string[];
  foo: string;
}

let method: Foo = Object.assign(
  (a: number, b: string) => { return a * a; },
  { foo: 10 }
); 

which errors due to incompatible typing:

由于输入不兼容而导致的错误:

Error: foo:number not assignable to foo:string
Error: number not assignable to string[] (return type)

错误:foo:number 不可分配给 foo:string
错误:number 不可分配给 string[](返回类型)

caveat: you may need to polyfillObject.assign if targeting older browsers.

警告:如果针对旧浏览器,您可能需要polyfillObject.assign。

回答by mk.

TypeScript is designed to handle this case through declaration merging:

TypeScript 旨在通过声明合并来处理这种情况:

you may also be familiar with JavaScript practice of creating a function and then extending the function further by adding properties onto the function. TypeScript uses declaration merging to build up definitions like this in a type-safe way.

您可能还熟悉 JavaScript 创建函数然后通过向函数添加属性来进一步扩展该函数的做法。TypeScript 使用声明合并以类型安全的方式构建这样的定义。

Declaration merging lets us say that something is both a function and a namespace (internal module):

声明合并让我们说某物既是函数又是命名空间(内部模块):

function f() { }
namespace f {
    export var someValue = 3;
}

This preserves typing and lets us write both f()and f.someValue. When writing a .d.tsfile for existing JavaScript code, use declare:

这保留了输入并让我们同时编写f()f.someValue.d.ts为现有 JavaScript 代码编写文件时,请使用declare

declare function f(): void;
declare namespace f {
    export var someValue: number;
}

Adding properties to functions is often a confusing or unexpected pattern in TypeScript, so try to avoid it, but it can be necessary when using or converting older JS code. This is one of the only times it would be appropriate to mix internal modules (namespaces) with external.

在 TypeScript 中向函数添加属性通常是一种令人困惑或意想不到的模式,因此尽量避免它,但在使用或转换旧的 JS 代码时可能是必要的。这是将内部模块(命名空间)与外部模块混合使用的唯一时机之一。

回答by Rick Love

As a shortcut, you can dynamically assign the object value using the ['property'] accessor:

作为一种快捷方式,您可以使用 ['property'] 访问器动态分配对象值:

var f = function() { }
f['someValue'] = 3;

This bypasses the type checking. However, it is pretty safe because you have to intentionally access the property the same way:

这绕过了类型检查。但是,它非常安全,因为您必须以相同的方式有意访问该属性:

var val = f.someValue; // This won't work
var val = f['someValue']; // Yeah, I meant to do that

However, if you really want the type checking for the property value, this won't work.

但是,如果您真的想要对属性值进行类型检查,这将不起作用。

回答by Daniel Earwicker

An updated answer: since the addition of intersection types via &, it is possible to "merge" two inferred types on the fly.

更新的答案:由于添加了交叉类型 via &,因此可以动态“合并”两种推断类型。

Here's a general helper that reads the properties of some object fromand copies them over an object onto. It returns the same object ontobut with a new type that includes both sets of properties, so correctly describing the runtime behaviour:

这是一个通用助手,它读取某个对象的属性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;
}

This low-level helper does still perform a type-assertion, but it is type-safe by design. With this helper in place, we have an operator that we can use to solve the OP's problem with full type safety:

这个低级帮助程序仍然执行类型断言,但它在设计上是类型安全的。有了这个助手,我们就有了一个操作符,我们可以用它来解决 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. Note that we have constrained footo be of type Foo, so the result of mergehas to be a complete Foo. So if you rename barto badthen you get a type error.

单击此处在 TypeScript Playground 中试用。请注意,我们已约束foo为 类型Foo,因此 的结果merge必须是完整的Foo. 因此,如果您重命名bar为,bad则会出现类型错误。

NBThere is still one type hole here, however. TypeScript doesn't provide a way to constrain a type parameter to be "not a function". So you could get confused and pass your function as the second argument to merge, and that wouldn't work. So until this can be declared, we have to catch it at runtime:

注意这里仍然有一种类型的孔,但是。TypeScript 没有提供将类型参数限制为“非函数”的方法。因此,您可能会感到困惑并将您的函数作为第二个参数传递给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;
}

回答by thiagoh

I can't say that it's very straightforward but it's definitely possible:

我不能说这很简单,但绝对有可能:

interface Optional {
  <T>(value?: T): OptionalMonad<T>;
  empty(): OptionalMonad<any>;
}

const Optional = (<T>(value?: T) => OptionalCreator(value)) as Optional;
Optional.empty = () => OptionalCreator();

if you got curious this is from a gist of mine with the TypeScript/JavaScript version of Optional

如果你好奇,这是来自我的 TypeScript/JavaScript 版本的要点Optional

回答by Geo1088

Old question, but for versions of TypeScript starting with 3.1, you can simply do the property assignment as you would in plain JS, as long as you use a function declaration or the constkeyword for your variable:

老问题,但对于从 3.1 开始的 TypeScript 版本,您可以像在普通 JS 中一样简单地进行属性分配,只要您const为变量使用函数声明或关键字:

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'"

Referenceand online example.

参考在线示例

回答by WillSeitz

This departs from strong typing, but you can do

这与强类型背离,但你可以做

var f: any = function() { }
f.someValue = 3;

if you are trying to get around oppressive strong typing like I was when I found this question. Sadly this is a case TypeScript fails on perfectly valid JavaScript so you have to you tell TypeScript to back off.

如果你像我发现这个问题时那样试图绕过压迫性强类型。遗憾的是,这是 TypeScript 在完全有效的 JavaScript 上失败的情况,所以你必须告诉 TypeScript 退出。

"You JavaScript is perfectly valid TypeScript" evaluates to false. (Note: using 0.95)

“您的 JavaScript 是完全有效的 TypeScript”评估结果为 false。(注:使用0.95)