Tìm kiếm trang web

typescript - Làm thế nào để liên kết kiểu của tham số hàm với một giá trị enum nhất định?


Tôi muốn có thể liên kết một liệt kê và một kiểu khi biên dịch trong TypeScript.

Mục tiêu là sửa tham số của một phương thức bằng một enum. Tôi cố gắng làm điều đó với một closure:

enum EnumType {
    TYPE1,
    TYPE2
}

interface Type1 {
    name: string;
}

interface Type2 {
    value: number;
}


function closure(tEnum: EnumType) {
    switch(tEnum){
        case EnumType.TYPE1: 
        return (t: Type1) => { console.log(t); }
        case EnumType.TYPE2: 
        return (t: Type2) => { console.log(t); }
    }
}

const foo1 = closure(EnumType.TYPE1);
foo1({ name: 'test'}); // here no error Type1 (but there is an error)
foo1({ value: 2}); // here error because not Type1

Sân chơi

Làm sao tôi có thể làm cho ví dụ này hoạt động hoặc có một phương án thay thế không?

Câu trả lời #1

Đây là một nơi khá cổ điển để sử dụng nạp chồng hàm:

function closure(tEnum: EnumType.TYPE1): (arg: Type1) => void;
function closure(tEnum: EnumType.TYPE2): (arg: Type2) => void;
function closure(tEnum: EnumType) {
    switch(tEnum){
        case EnumType.TYPE1: 
            return (t: Type1) => { console.log(t); }
        case EnumType.TYPE2: 
            return (t: Type2) => { console.log(t); }
        default:
            throw new Error(`Invalid enum value ${tEnum}`);
    }
}

Liên kết sân chơi

Lưu ý rằng chỉ hai chữ ký đầu tiên là công khai; chữ ký thứ ba chỉ là chữ ký triển khai mà các mã khác không thấy.

Câu trả lời #2

Như một lựa chọn thay thế cho việc nạp chồng hàm, bạn có thể tạo liên kết trong một kiểu tiện ích và sau đó sử dụng các tham số kiểu tổng quát để liên kết kiểu của tham số tEnum với trường hợp được chọn:

enum EnumType {
    TYPE1,
    TYPE2
}

interface Type1 {
    name: string;
}

interface Type2 {
    value: number;
}

type TypeMap = {
    [EnumType.TYPE1]: Type1;
    [EnumType.TYPE2]: Type2;
};

function closure<T extends EnumType>(tEnum: T): (t:TypeMap[T]) => void {
    switch(tEnum){
        case EnumType.TYPE1: 
        return (t: TypeMap[T]) => { console.log(t); }
        case EnumType.TYPE2: 
        return (t: TypeMap[T]) => { console.log(t); }
    }

    throw new Error(`${tEnum} is not a type`);
}

const foo1 = closure(EnumType.TYPE1);
foo1({ name: 'test'}); // OK
foo1({ value: 2}); // error, OK

const foo2 = closure(EnumType.TYPE2);
foo2({ name: "test" }); // error, OK
foo2({ value: 2 }); // OK

Sân chơi


Theo cuộc thảo luận với jcalz, đáng chú ý là ví dụ trên là không đủ nếu bạn truy cập các thuộc tính trên t bên trong thân hàm closure, vì bản chất T vẫn chưa được giải quyết, TypeMap[T] được suy luận thành một hợp loại Type1 | Type2, điều này có nghĩa là bạn sẽ không thể truy cập các thuộc tính không được chia sẻ bởi cả hai interface ở đó.

Một giải pháp đơn giản cho việc này là bỏ switch để thay bằng một đối tượng thông thường khớp với TypeMap thông qua một kiểu ánh xạ:

function closure<T extends EnumType>(tEnum: T) {
    const map: { [P in keyof TypeMap]: (t: TypeMap[P]) => void } = {
        [EnumType.TYPE1]: (t) => { console.log(t.name); },
        [EnumType.TYPE2]: (t) => { console.log(t.value); }
    };

    return map[tEnum];
}

Sân chơi