Thực thi kiểu các thành viên được lập chỉ mục của một đối tượng Typecript?


289

Tôi muốn lưu trữ một ánh xạ của chuỗi -> chuỗi trong một đối tượng Typecript và thực thi rằng tất cả các khóa ánh xạ thành chuỗi. Ví dụ:

var stuff = {};
stuff["a"] = "foo";   // okay
stuff["b"] = "bar";   // okay
stuff["c"] = false;   // ERROR!  bool != string

Có cách nào để tôi thực thi rằng các giá trị phải là chuỗi (hoặc bất kỳ loại nào ..) không?

Câu trả lời:


518
var stuff: { [key: string]: string; } = {};
stuff['a'] = ''; // ok
stuff['a'] = 4;  // error

// ... or, if you're using this a lot and don't want to type so much ...
interface StringMap { [key: string]: string; }
var stuff2: StringMap = { };
// same as above

13
Điều thú vị về cú pháp này là bất kỳ loại nào ngoài 'chuỗi' cho khóa thực sự sai. Làm cho ý nghĩa hoàn hảo, cho rằng các bản đồ JS được khóa rõ ràng bởi các chuỗi, nhưng nó làm cho cú pháp có phần dư thừa. Một cái gì đó như '{}: chuỗi' để chỉ định các loại giá trị có vẻ đơn giản hơn, trừ khi chúng sẽ thêm vào một cách nào đó để cho phép tự động ép các khóa như một phần của mã được tạo.
Armentage

42
numbercũng được cho phép như một kiểu lập chỉ mục
Ryan Cavanaugh

3
đáng lưu ý: trình biên dịch chỉ thực thi loại giá trị, không phải loại khóa. bạn có thể làm công cụ [15] = 'bất cứ điều gì' và nó sẽ không phàn nàn.
amwinter

3
Không, nó thực thi loại khóa. Bạn không thể thực hiện công cụ [myObject] = 'sao cũng được' ngay cả khi myObject có triển khai toString () đẹp.
AlexG

7
@RyanCavanaugh Có thể (hoặc sẽ có thể) sử dụng một type Keys = 'Acceptable' | 'String' | 'Keys'loại chỉ mục (khóa) không?
calebboyd

130
interface AgeMap {
    [name: string]: number
}

const friendsAges: AgeMap = {
    "Sandy": 34,
    "Joe": 28,
    "Sarah": 30,
    "Michelle": "fifty", // ERROR! Type 'string' is not assignable to type 'number'.
};

Ở đây, giao diện AgeMapthực thi các khóa dưới dạng chuỗi và giá trị dưới dạng số. Từ khóa namecó thể là bất kỳ định danh nào và nên được sử dụng để đề xuất cú pháp của giao diện / loại của bạn.

Bạn có thể sử dụng một cú pháp tương tự để thực thi rằng một đối tượng có một khóa cho mỗi mục trong loại kết hợp:

type DayOfTheWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday";

type ChoresMap = { [day in DayOfTheWeek]: string };

const chores: ChoresMap = { // ERROR! Property 'saturday' is missing in type '...'
    "sunday": "do the dishes",
    "monday": "walk the dog",
    "tuesday": "water the plants",
    "wednesday": "take out the trash",
    "thursday": "clean your room",
    "friday": "mow the lawn",
};

Tất nhiên, bạn cũng có thể biến nó thành một loại chung chung!

type DayOfTheWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday";

type DayOfTheWeekMap<T> = { [day in DayOfTheWeek]: T };

const chores: DayOfTheWeekMap<string> = {
    "sunday": "do the dishes",
    "monday": "walk the dog",
    "tuesday": "water the plants",
    "wednesday": "take out the trash",
    "thursday": "clean your room",
    "friday": "mow the lawn",
    "saturday": "relax",
};

const workDays: DayOfTheWeekMap<boolean> = {
    "sunday": false,
    "monday": true,
    "tuesday": true,
    "wednesday": true,
    "thursday": true,
    "friday": true,
    "saturday": false,
};

Cập nhật ngày 10.10.2018: Kiểm tra câu trả lời của @ dracstaxi bên dưới - giờ đây đã có loại tích hợp sẵn Recordmà hầu hết điều này giúp bạn.

Cập nhật 1.2.2020: Tôi đã loại bỏ hoàn toàn các giao diện ánh xạ được tạo sẵn khỏi câu trả lời của tôi. @ dracstaxi's câu trả lời làm cho chúng hoàn toàn không liên quan. Nếu bạn vẫn muốn sử dụng chúng, hãy kiểm tra lịch sử chỉnh sửa.


1
{[khóa: số]: T; } không an toàn vì bên trong các khóa của một đối tượng luôn là một chuỗi - xem bình luận về câu hỏi của @tep để biết thêm chi tiết. ví dụ: Chạy x = {}; x[1] = 2;trong Chrome sau đó Object.keys(x)trả về ["1"] và JSON.stringify(x)trả về '{"1": 2}'. Các trường hợp góc với typeof Number(ví dụ: Infinity, NaN, 1e300, 999999999999999999999, v.v.) được chuyển đổi thành các khóa chuỗi. Cũng hãy cẩn thận với các trường hợp góc khác cho các khóa chuỗi như x[''] = 'empty string';, x['000'] = 'threezeros'; x[undefined] = 'foo'.
robocat

@robocat Điều này là đúng và tôi đã quay lại chỉnh sửa để xóa các giao diện khóa số khỏi câu trả lời này trong một thời gian. Cuối cùng, tôi đã quyết định giữ chúng vì TypeScript về mặt kỹ thuậtđặc biệt cho phép khóa số. Có nói rằng, tôi hy vọng rằng bất cứ ai quyết định sử dụng các đối tượng được lập chỉ mục với số đều thấy nhận xét của bạn.
Sandy Gifford

75

Cập nhật nhanh: vì Bản mô tả 2.1 có một loại được xây dựng Record<T, K>hoạt động như một từ điển.

Một ví dụ từ các tài liệu:

// For every properties K of type T, transform it to U
function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>

const names = { foo: "hello", bar: "world", baz: "bye" };
const lengths = mapObject(names, s => s.length);  // { foo: number, bar: number, baz: number }

Tài liệu TypeScript 2.1 trên Record<T, K>

Nhược điểm duy nhất tôi thấy khi sử dụng điều này {[key: T]: K}là bạn có thể mã hóa thông tin hữu ích về loại khóa bạn đang sử dụng thay cho "khóa", ví dụ nếu đối tượng của bạn chỉ có các khóa chính bạn có thể gợi ý như vậy : {[prime: number]: yourType}.

Đây là một regex tôi đã viết để giúp với các chuyển đổi này. Điều này sẽ chỉ chuyển đổi các trường hợp nhãn là "chìa khóa". Để chuyển đổi các nhãn khác, chỉ cần thay đổi nhóm chụp đầu tiên:

Tìm thấy: \{\s*\[(key)\s*(+\s*:\s*(\w+)\s*\]\s*:\s*([^\}]+?)\s*;?\s*\}

Thay thế: Record<$2, $3>


3
Điều này sẽ nhận được nhiều sự ủng hộ hơn - về cơ bản đó là phiên bản mới hơn, câu trả lời của tôi.
Sandy Gifford

Có hồ sơ biên dịch thành một {}?
Douglas Gaskell

Các loại @DoumundGaskell không có bất kỳ sự hiện diện nào trong mã được biên dịch. Records (không giống như, giả sử, Javascript Map) chỉ cung cấp một cách để thực thi nội dung của một đối tượng theo nghĩa đen. Bạn không thể new Record...const blah: Record<string, string>;sẽ biên dịch thành const blah;.
Sandy Gifford

Bạn thậm chí không thể tưởng tượng được câu trả lời này đã giúp tôi nhiều như thế nào, cảm ơn bạn rất nhiều :)
Federico Grandi

Đáng nói là các hiệp hội chuỗi cũng hoạt động trong Records: const isBroken: Record<"hat" | "teapot" | "cup", boolean> = { hat: false, cup: false, teapot: true };
Sandy Gifford

10

Câu trả lời của @Ryan Cavanaugh hoàn toàn ổn và vẫn còn hiệu lực. Tuy nhiên, đáng để nói thêm là vào mùa thu 16 khi chúng ta có thể tuyên bố rằng ES6 được hỗ trợ bởi phần lớn các nền tảng, hầu như luôn luôn tốt hơn để bám vào Bản đồ bất cứ khi nào bạn cần liên kết một số dữ liệu với một số khóa.

Khi chúng ta viết, let a: { [s: string]: string; }chúng ta cần nhớ rằng sau khi bản thảo được biên dịch, không có thứ gì giống như dữ liệu kiểu, nó chỉ được sử dụng để biên dịch. Và {[s: chuỗi]: chuỗi; } sẽ biên dịch thành chỉ {}.

Điều đó nói rằng, ngay cả khi bạn sẽ viết một cái gì đó như:

class TrickyKey  {}

let dict: {[key:TrickyKey]: string} = {}

Điều này sẽ không được biên dịch (ngay cả đối với target es6, bạn sẽ nhận đượcerror TS1023: An index signature parameter type must be 'string' or 'number'.

Vì vậy, thực tế bạn bị giới hạn với chuỗi hoặc số là khóa tiềm năng nên không có ý nghĩa gì về việc thực thi kiểm tra loại ở đây, đặc biệt lưu ý rằng khi js cố gắng truy cập khóa theo số, nó sẽ chuyển đổi thành chuỗi.

Vì vậy, sẽ khá an toàn khi cho rằng thực tiễn tốt nhất là sử dụng Bản đồ ngay cả khi các khóa là chuỗi, vì vậy tôi vẫn sử dụng:

let staff: Map<string, string> = new Map();

4
Không chắc chắn nếu điều này là có thể khi câu trả lời này được đăng, nhưng hôm nay bạn có thể làm điều này: let dict: {[key in TrickyKey]: string} = {}- đâu TrickyKeylà một kiểu chữ theo chuỗi (ví dụ "Foo" | "Bar").
Roman Starkov

Cá nhân tôi thích định dạng bản thảo gốc nhưng bạn đúng nhất là sử dụng tiêu chuẩn. Nó cho tôi một cách để ghi lại "tên" khóa không thực sự có thể sử dụng được nhưng tôi có thể tạo khóa được gọi là "kiên nhẫn" :)
mã hóa

Câu trả lời này là hoàn toàn hợp lệ và đưa ra những quan điểm rất hay, nhưng tôi không đồng ý với ý kiến ​​cho rằng việc bám vào Mapcác vật thể bản địa luôn luôn tốt hơn . Các bản đồ đi kèm với chi phí bộ nhớ bổ sung và (quan trọng hơn) cần phải được khởi tạo thủ công từ bất kỳ dữ liệu nào được lưu dưới dạng chuỗi JSON. Chúng thường rất hữu ích, nhưng không hoàn toàn vì mục đích loại bỏ chúng.
Sandy Gifford

7

Xác định giao diện

interface Settings {
  lang: 'en' | 'da';
  welcome: boolean;
}

Thi hành khóa để trở thành một khóa cụ thể của giao diện Cài đặt

private setSettings(key: keyof Settings, value: any) {
   // Update settings key
}

3

Dựa trên câu trả lời của @ shabunc, điều này sẽ cho phép thực thi khóa hoặc giá trị - hoặc cả hai - là bất cứ điều gì bạn muốn thực thi.

type IdentifierKeys = 'my.valid.key.1' | 'my.valid.key.2';
type IdentifierValues = 'my.valid.value.1' | 'my.valid.value.2';

let stuff = new Map<IdentifierKeys, IdentifierValues>();

Cũng nên làm việc bằng cách sử dụng enumthay vì một typeđịnh nghĩa.

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.