Có một `valueof` tương tự như` keyof` trong TypeScript không?


119

Tôi muốn có thể gán một thuộc tính đối tượng cho một giá trị được cung cấp một khóa và giá trị làm đầu vào nhưng vẫn có thể xác định loại giá trị. Hơi khó giải thích vì vậy mã này sẽ tiết lộ vấn đề:

type JWT = { id: string, token: string, expire: Date };
const obj: JWT = { id: 'abc123', token: 'tk01', expire: new Date(2018, 2, 14) };

function print(key: keyof JWT) {
    switch (key) {
        case 'id':
        case 'token':
            console.log(obj[key].toUpperCase());
            break;
        case 'expire':
            console.log(obj[key].toISOString());
            break;
    }
}

function onChange(key: keyof JWT, value: any) {
    switch (key) {
        case 'id':
        case 'token':
            obj[key] = value + ' (assigned)';
            break;
        case 'expire':
            obj[key] = value;
            break;
    }
}

print('id');
print('expire');
onChange('id', 'def456');
onChange('expire', new Date(2018, 3, 14));
print('id');
print('expire');

onChange('expire', 1337); // should fail here at compile time
print('expire'); // actually fails here at run time

Tôi đã cố gắng thay đổi value: anythành value: valueof JWTnhưng điều đó không hiệu quả.

Tốt nhất, onChange('expire', 1337)sẽ không thành công vì 1337không phải là loại Ngày.

Làm cách nào để tôi có thể thay đổi value: anygiá trị của khóa đã cho?

Câu trả lời:


220

CẬP NHẬT: Có vẻ như tiêu đề câu hỏi thu hút những người đang tìm kiếm sự kết hợp của tất cả các loại giá trị tài sản có thể có, tương tự như cách keyofcung cấp cho bạn sự kết hợp của tất cả các loại khóa tài sản có thể có. Hãy giúp những người đó trước. Bạn có thể tạo ValueOftương tự với keyof, bằng cách sử dụng các kiểu tra cứu với keyof Tlàm khóa, như sau:

type ValueOf<T> = T[keyof T];

cái nào cho bạn

type Foo = { a: string, b: number };
type ValueOfFoo = ValueOf<Foo>; // string | number

Đối với câu hỏi như đã nêu, bạn có thể sử dụng các khóa riêng lẻ, hẹp hơn keyof T, để chỉ trích xuất loại giá trị mà bạn quan tâm:

type sameAsString = Foo['a']; // lookup a in Foo
type sameAsNumber = Foo['b']; // lookup b in Foo

Để đảm bảo rằng cặp khóa / giá trị "khớp" đúng cách trong một hàm, bạn nên sử dụng chung cũng như các loại tra cứu, như sau:

declare function onChange<K extends keyof JWT>(key: K, value: JWT[K]): void; 
onChange('id', 'def456'); // okay
onChange('expire', new Date(2018, 3, 14)); // okay
onChange('expire', 1337); // error. 1337 not assignable to Date

Ý tưởng là keytham số cho phép trình biên dịch suy ra Ktham số chung . Sau đó, nó yêu cầu valuephù hợp với JWT[K]loại tra cứu bạn cần.

Hy vọng rằng sẽ giúp; chúc may mắn!


Tôi muốn tạo một tupletừ các giá trị. Có cơ hội nào để làm điều đó không?
Morteza Tourani

Điều đó nghe có vẻ như một câu hỏi không liên quan, vì vậy bạn có thể nên hỏi nó ở một nơi khác, với một ví dụ có thể tái tạo tối thiểu để mọi người chắc chắn hiểu trường hợp sử dụng của bạn. Chúc may mắn.
jcalz

1
Gặp sự cố khi sử dụng một enum có giá trị chuỗi với các thành viên hàm. Để xử lý tốt điều này, bạn có thể sử dụng type StringValueOf<T> = T[keyof T] & string;. Các tài liệu tốt nhất mà tôi tìm thấy trên enums chuỗi là nguyên cảo 2.9 release notes
karmakaze

41

Nếu ai đó vẫn đang tìm kiếm việc triển khai valueofcho bất kỳ mục đích nào, thì đây là một cách mà tôi đã nghĩ ra:

type valueof<T> = T[keyof T]

Sử dụng:

type actions = {
  a: {
    type: 'Reset'
    data: number
  }
  b: {
    type: 'Apply'
    data: string
  }
}
type actionValues = valueof<actions>

Hoạt động như mong đợi :) Trả về một Liên minh của tất cả các loại có thể


2
Cái này đẹp đấy. Nó gần như giải quyết được vấn đề khác của tôi , Tuy nhiên, công tắc dường như không thu hẹp loại nào cả.
styfle

Tôi muốn tạo một tupletừ các giá trị. Có cơ hội nào để làm điều đó không?
Morteza Tourani

12

Có một cách khác để trích xuất kiểu liên hợp của đối tượng:

  const myObj = { a: 1, b: 'some_string' } as const;
  type values = typeof myObj[keyof typeof myObj];

Kết quả: 1 | "some_string"


Điều này constrất có giá trị, TypeScript sẽ thực sự tự cung cấp các giá trị và loại bỏ các bản sao; Nó rất tốt cho từ điển.
Noam

Chính xác những gì tôi đang tìm kiếm !
Mavrick RMX '

2

Cảm ơn các câu trả lời hiện có giải quyết vấn đề một cách hoàn hảo. Chỉ muốn thêm một lib đã bao gồm loại tiện ích này, nếu bạn muốn nhập loại thông thường này.

https://github.com/piotrwitek/utility-types#valuestypet

import { ValuesType } from 'utility-types';

type Props = { name: string; age: number; visible: boolean };
// Expect: string | number | boolean
type PropsValues = ValuesType<Props>;

2

Thử cái này:

type ValueOf<T> = T extends any[] ? T[number] : T[keyof T]

Nó hoạt động trên một mảng hoặc một đối tượng thuần túy.

// type TEST1 = boolean | 42 | "heyhey"
type TEST1 = ValueOf<{ foo: 42, sort: 'heyhey', bool: boolean }>
// type TEST2 = 1 | 4 | 9 | "zzz..."
type TEST2 = ValueOf<[1, 4, 9, 'zzz...']>

0

Lót:

type ValueTypesOfPropFromMyCoolType = MyCoolType[keyof MyCoolType];

Ví dụ về một phương pháp chung:

declare function doStuff<V extends MyCoolType[keyof MyCoolType]>(propertyName: keyof MyCoolType, value: V) => void;
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.