在打字稿中构建具有属性的功能对象

发布于 2025-02-11 00:25:50 字数 423 浏览 2 评论 0 原文

我想创建一个函数对象,该对象还具有一些属性。例如,在JavaScript中,我会做:

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

现在,在打字稿中,我可以将其描述为:

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

但是我实际上不能构建它,而不需要铸造。诸如:

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

您如何在没有演员的情况下构建它?

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

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

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

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

旧伤慢歌 2025-02-18 00:25:51

使用 object.assign(target,source)

示例:

“在这里输入映像”

魔术是 object.assign&lt; t, u&gt;(t:t,u:u)键入以返回 Intersection t&amp; 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:字符串
错误:数字不可分配给字符串[](返回类型)

caveat:您可能需要 polyfill object.sign如果针对较旧的浏览器。

This is easily achievable now (typescript 2.x) with 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 intersection 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)

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

辞别 2025-02-18 00:25:51

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

公认的答案有效,在某些情况下可能需要,但没有提供任何类型的安全性来构建对象的缺点。如果您尝试添加未定义的属性,此技术至少会丢弃类型错误。

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

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

// type error
f.notDeclard = 3

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

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
别在捏我脸啦 2025-02-18 00:25:51

Typescript旨在通过 声明合并 a>:

您可能还熟悉创建功能的JavaScript练习,然后通过将属性添加到功能中进一步扩展功能。 Typescript使用声明合并以类型安全的方式构建这样的定义。

声明合并的声明让我们说某物既是功能又是一个名称空间(内部模块):

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

这可以保留键入,并让我们同时编写 f() f.somevalue 。在编写 .d.t.ts 为现有JavaScript代码的文件时,请使用 neclare

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

将属性添加到功能中通常是一个令人困惑或意外的模式,因此请避免它,但是,使用或转换较旧的JS代码时可能有必要。这是将内部模块(名称空间)与外部混合的唯一时间之一。

TypeScript is designed to handle this case through declaration merging:

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.

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.ts file for existing JavaScript code, use 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.

故乡的云 2025-02-18 00:25:51

因此,如果要求只需简单地将该函数构建并分配给“ f”,而无需演员,则可能是一个可能的解决方案:

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

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

本质上,它使用自我执行函数字面来“构造”一个​​对象,该对象将在分配为之前匹配该签名完毕。唯一的怪异是,该函数的内部声明必须为“任何”类型,否则编译器您要分配给对象上不存在的属性。

编辑:稍微简化了代码。

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

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.

EDIT: Simplified the code a bit.

无边思念无边月 2025-02-18 00:25:51

旧问题,但是对于以3.1开头的打字稿版本,只要您使用函数声明或 const 关键字,您就可以像在普通js中一样进行属性分配:

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

< a href =“ https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-1.html#properties-declaraties-on-functions” rel =“ noreferrer”> 在线示例

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 const keyword for your variable:

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

Reference and online example.

爱给你人给你 2025-02-18 00:25:51

作为快捷方式,您可以使用['属性']访问者动态分配对象值:

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

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

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

但是,如果您真的希望使用该类型检查属性值,则这将无法使用。

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

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.

始于初秋 2025-02-18 00:25:51

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

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

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

如果您感到好奇可选

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

心作怪 2025-02-18 00:25:51

一个更新的答案:由于通过&amp; 添加了相交类型,因此可以随时“合并”两种推断类型。

这是一个通用的助手,它从中读取某些对象的属性,然后将它们通过对象将其复制到上。它将同一对象返回到

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}`)
        }
    }
);

单击此处在打字稿游乐场中尝试一下。请注意,我们已将 foo 限制为类型 foo ,因此 Merge 的结果必须是完整的 foo 。因此,如果您将 bar 重命名为 bad ,则会获得类型错误。

nb 但是,这里仍然有一个类型的孔。 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;
}

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

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

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:

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 foo to be of type Foo, so the result of merge has to be a complete Foo. So if you rename bar to bad then you get a type error.

NB There 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:

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;
}
森林散布 2025-02-18 00:25:51

这与强大的打字相反,但是如果您想像我发现这个问题时那样,您可以

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

像我那样绕过压迫性强的打字。可悲的是,这是一个案例打字稿在完全有效的JavaScript上失败,因此您必须告诉Typescript退缩。

“您的javaScript是完全有效的打字稿”评估为false。 (注意:使用0.95)

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.

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文