Xây dựng một đối tượng hàm với các thuộc tính trong TypeScript


83

Tôi muốn tạo một đối tượng hàm, đối tượng này cũng có một số thuộc tính được giữ trên đó. Ví dụ trong JavaScript tôi sẽ làm:

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

Bây giờ trong TypeScript, tôi có thể mô tả loại này như sau:

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

Tuy nhiên, tôi thực sự không thể xây dựng nó mà không yêu cầu diễn viên. Nhu la:

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

Làm thế nào bạn sẽ xây dựng này mà không có một diễn viên?


2
Đây không phải là một câu trả lời trực tiếp câu hỏi của bạn, nhưng đối với bất cứ ai muốn chính xác xây dựng một đối tượng hàm với tài sản, và là OK với đúc, các nhà khai thác đối tượng Spread dường như làm các trick: var f: { (): any; someValue: number; } = <{ (): any; someValue: number; }>{ ...(() => "Hello"), someValue: 3 };.
Jonathan

Câu trả lời:


31

Vì vậy, nếu yêu cầu là chỉ cần xây dựng và gán hàm đó cho "f" mà không cần ép kiểu, thì đây là một giải pháp khả thi:

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

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

Về cơ bản, nó sử dụng một hàm tự thực thi theo nghĩa đen để "xây dựng" một đối tượng sẽ khớp với chữ ký đó trước khi thực hiện nhiệm vụ. Điều kỳ lạ duy nhất là phần khai báo bên trong của hàm cần phải thuộc loại 'bất kỳ', nếu không trình biên dịch sẽ báo rằng bạn đang gán cho một thuộc tính chưa tồn tại trên đối tượng.

CHỈNH SỬA: Đơn giản hóa mã một chút.


5
Theo như tôi có thể hiểu, điều này sẽ không thực sự kiểm tra loại, vì vậy .someValue về cơ bản có thể là bất kỳ thứ gì.
shabunc

1
Câu trả lời tốt hơn / cập nhật vào năm 2017/2018 cho TypeScript 2.0 được đăng bởi Meirion Hughes bên dưới: stackoverflow.com/questions/12766528/… .
dùng3773048

107

Cập nhật: Câu trả lời này là giải pháp tốt nhất trong các phiên bản trước của TypeScript, nhưng có các tùy chọn tốt hơn có sẵn trong các phiên bản mới hơn (xem các câu trả lời khác).

Câu trả lời được chấp nhận hoạt động và có thể được yêu cầu trong một số trường hợp, nhưng có nhược điểm là không cung cấp sự an toàn kiểu nào cho việc xây dựng đối tượng. Kỹ thuật này ít nhất sẽ tạo ra một lỗi kiểu nếu bạn cố gắng thêm một thuộc tính không xác định.

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

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

// type error
f.notDeclard = 3

1
Không hiểu sao var fđường truyền không bị lỗi, do lúc đó không có someValuetài sản.

2
@torazaburo của nó vì nó không kiểm tra khi bạn truyền. Để gõ kiểm tra bạn cần làm: var f:F = function(){}điều này sẽ không thành công trong ví dụ trên. Câu trả lời này không phù hợp với những tình huống phức tạp hơn vì bạn mất kiểm tra kiểu ở giai đoạn bài tập.
Meirion Hughes

1
đúng nhưng làm thế nào để bạn làm điều này với một khai báo hàm thay vì một biểu thức hàm?
Alexander Mills

1
Điều này sẽ không còn hoạt động trong các phiên bản gần đây của TS, bởi vì việc kiểm tra giao diện chặt chẽ hơn nhiều và việc truyền hàm sẽ không còn được biên dịch do không có thuộc tính 'someValue' được yêu cầu. Điều gì sẽ làm việc, tuy nhiên, là<F><any>function() { ... }
galenus

khi bạn sử dụng tslint: đề nghị, định kiểu không làm việc và những gì bạn cần phải sử dụng là: (quấn các chức năng trong khung để làm cho nó một biểu thức, sau đó sử dụng như bất kỳ như F):var f = (function () {}) as any as F;
NikhilWanpal

78

Điều này có thể dễ dàng đạt được ngay bây giờ (typecript 2.x) với Object.assign(target, source)

thí dụ:

nhập mô tả hình ảnh ở đây

Điều kỳ diệu ở đây là nó Object.assign<T, U>(t: T, u: U)được gõ để trả lại giao điểm T & U .

Việc cưỡng chế giải quyết vấn đề này đối với một giao diện đã biết cũng được thực hiện ngay lập tức. Ví dụ:

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

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

lỗi nào do nhập không tương thích:

Lỗi: foo: số không thể gán cho foo: chuỗi
Lỗi: số không thể gán cho chuỗi [] (kiểu trả về)

cảnh báo: bạn có thể cần phải polyfill Object.assign nếu nhắm mục tiêu các trình duyệt cũ hơn.


1
Tuyệt vời, cảm ơn bạn. Kể từ tháng 2 năm 2017, đây là câu trả lời tốt nhất (cho người dùng Typecript 2.x).
Andrew Faulkner

Bây giờ là năm 2020, có giải pháp nào cập nhật không?
Archsx

46

TypeScript được thiết kế để xử lý trường hợp này thông qua hợp nhất khai báo :

bạn cũng có thể quen thuộc với thực hành JavaScript tạo một hàm và sau đó mở rộng hàm hơn nữa bằng cách thêm các thuộc tính vào hàm. TypeScript sử dụng kết hợp khai báo để xây dựng các định nghĩa như thế này theo cách an toàn về kiểu.

Khai báo hợp nhất cho phép chúng ta nói rằng một cái gì đó vừa là một hàm vừa là một không gian tên (mô-đun nội bộ):

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

Điều này bảo tồn việc nhập và cho phép chúng tôi viết cả hai f()f.someValue. Khi viết .d.tstệp cho mã JavaScript hiện có, hãy sử dụng declare:

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

Việc thêm thuộc tính vào các hàm thường là một mẫu khó hiểu hoặc không mong muốn trong TypeScript, vì vậy hãy cố gắng tránh nó, nhưng có thể cần thiết khi sử dụng hoặc chuyển đổi mã JS cũ hơn. Đây là một trong những lần duy nhất thích hợp để trộn các mô-đun bên trong (không gian tên) với bên ngoài.


Ủng hộ vì đã đề cập đến các mô-đun môi trường xung quanh trong câu trả lời. Đây là trường hợp rất điển hình khi chuyển đổi hoặc chú thích các mô-đun JS hiện có. Chính xác những gì tôi đang tìm kiếm!
Nipheris

Đáng yêu. Nếu bạn muốn sử dụng điều này với các mô-đun ES6, bạn có thể thực hiện function hello() { .. } namespace hello { export const value = 5; } export default hello;IMO, điều này sẽ gọn gàng hơn nhiều so với Object.assign hoặc các bản hack thời gian chạy tương tự. Không có thời gian chạy, không có xác nhận kiểu, không có gì.
skrebbel

Câu trả lời này rất hay, nhưng làm cách nào để bạn đính kèm một Biểu tượng chẳng hạn như Symbol.iterator vào một hàm?
kzh

Liên kết ở trên không hoạt động nữa, đúng sẽ là typescriptlang.org/docs/handbook/declaration-merging.html
iJungleBoy

Bạn nên biết rằng kết quả hợp nhất khai báo trong một số JavaScript khá phức tạp. Nó thêm vào một sự đóng cửa bổ sung có vẻ quá mức đối với tôi đối với một nhiệm vụ thuộc tính đơn giản. Đó là một cách làm rất phi JavaScript. Nó cũng giới thiệu vấn đề thứ tự phụ thuộc trong đó giá trị kết quả không nhất thiết phải là một hàm trừ khi bạn chắc chắn rằng hàm là thứ đầu tiên được yêu cầu .
John Leidegren

3

Tôi không thể nói rằng nó rất đơn giản nhưng chắc chắn là có thể:

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

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

nếu bạn tò mò, đây là từ một ý chính của tôi với phiên bản TypeScript / JavaScript củaOptional


Bây giờ là năm 2020, có giải pháp nào cập nhật không?
Archsx

2

Là một lối tắt, bạn có thể tự động gán giá trị đối tượng bằng cách sử dụng trình truy cập ['property']:

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

Điều này bỏ qua việc kiểm tra kiểu. Tuy nhiên, nó khá an toàn vì bạn phải cố ý truy cập vào tài sản theo cùng một cách:

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

Tuy nhiên, nếu bạn thực sự muốn loại kiểm tra giá trị thuộc tính, điều này sẽ không hoạt động.


1

Một câu trả lời được cập nhật: kể từ khi bổ sung các loại nút giao thông qua & , có thể "hợp nhất" hai loại suy luận một cách nhanh chóng.

Đây là một trình trợ giúp chung đọc các thuộc tính của một số đối tượng from và sao chép chúng qua một đối tượng onto. Nó trả về cùng một đối tượng ontonhưng với một kiểu mới bao gồm cả hai bộ thuộc tính, do đó, mô tả chính xác hành vi thời gian chạy:

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 {
    Object.keys(from).forEach(key => onto[key] = from[key]);
    return onto as T1 & T2;
}

Trình trợ giúp cấp thấp này vẫn thực hiện xác nhận kiểu, nhưng nó là kiểu an toàn theo thiết kế. Với trình trợ giúp này, chúng tôi có một toán tử mà chúng tôi có thể sử dụng để giải quyết vấn đề của OP với độ an toàn đầy đủ:

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

Nhấp vào đây để dùng thử trong TypeScript Playground . Lưu ý rằng chúng tôi đã hạn chế foolà loại Foo, vì vậy kết quả củamerge phải hoàn chỉnh Foo. Vì vậy, nếu bạn đổi tên barthành badthì bạn sẽ gặp lỗi kiểu.

NB Tuy nhiên, vẫn còn một loại lỗ ở đây. TypeScript không cung cấp cách hạn chế tham số kiểu là "không phải là một hàm". Vì vậy, bạn có thể nhầm lẫn và chuyển hàm của mình làm đối số thứ hai merge, và điều đó sẽ không hoạt động. Vì vậy, cho đến khi điều này có thể được khai báo, chúng ta phải bắt nó trong thời gian chạy:

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

1

Câu hỏi cũ, nhưng đối với các phiên bản TypeScript bắt đầu bằng 3.1, bạn có thể chỉ cần thực hiện việc gán thuộc tính như bạn làm trong JS thuần túy, miễn là bạn sử dụng khai báo hàm hoặc consttừ khóa cho biến của mình:

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

Tham khảoví dụ trực tuyến .


0

Điều này bắt nguồn từ việc gõ mạnh, nhưng bạn có thể làm

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

nếu bạn đang cố gắng tìm cách gõ mạnh như tôi khi tôi tìm thấy câu hỏi này. Đáng buồn thay, đây là trường hợp TypeScript không thành công trên JavaScript hoàn toàn hợp lệ, vì vậy bạn phải yêu cầu TypeScript lùi lại.

"JavaScript của bạn là hoàn toàn hợp lệ. TypeScript" được đánh giá là false. (Lưu ý: sử dụng 0,95)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.