javascript - ประเภทที่สร้างด้วย keyof ไม่สามารถใช้ในการกำหนดดัชนีประเภทที่มันถูกสร้างมาจาก
ฉันกำลังพยายามเขียนฟังก์ชันยูทิลิตี้ในโปรเจกต์ TypeScript ของฉันเพื่อนำวัตถุมาจัดเรียง/กรองในโมเดลข้อมูลที่ค่อนข้างคาดเดาได้ (ฉันกำลังใช้ Astro สำหรับเว็บไซต์นี้) วัตถุเหล่านี้เก็บข้อมูลทั้งหมดที่มีประโยชน์สำหรับการจัดเรียง/กรองในคุณสมบัติ data
ฉันไม่สามารถเข้าใจได้ว่าควรพิมพ์ฟังก์ชันนี้อย่างถูกต้องอย่างไร ซึ่งฟังก์ชันนี้รับคอลเลกชันของวัตถุและชื่อของคุณสมบัติเป็นอาร์กิวเมนต์: foo( collection, property ) { ... }.
นี่คือเวอร์ชันที่ง่ายมากของสิ่งที่ฉันพยายามทำ มันไม่ได้ใช้คอลเล็กชันเป็นอาร์กิวเมนต์ตัวแรก แต่แนวคิดหลักเหมือนกัน: ฉันต้องการส่งชื่อของคุณสมบัติใน data และจากนั้นเข้าถึงค่าของคุณสมบัตินั้น
interface User {
data: {
id: number;
name: string;
}
}
interface House {
data: {
number: number;
street: string;
}
}
type Obj = User | House;
function getProperty<
T extends Obj,
K extends keyof T['data']
>(obj: T, key: K): T['data'][K] {
return obj.data[key];
// Type 'K' cannot be used to index type '{ id: number; name: string; } | { number: number; street: string; }'.ts(2536)
}
const user: User = { data: { id: 1, name: "Alice" }};
const house: House = { data: { number: 2, street: "First" }};
const userName = getProperty(user, "name");
const houseNumber = getProperty(house, "number");
TypeScript เข้าใจว่าเกิดอะไรขึ้นกับฟังก์ชันนี้ เนื่องจากสองบรรทัดสุดท้ายจะไม่ให้ฉันส่งชื่อพร็อพเพอร์ตีที่ไม่มีอยู่ในข้อมูลของออบเจ็กต์ที่ตรงกัน แต่ตลอดชีวิตของฉัน ฉันไม่รู้ว่าจะทำให้ K ทำงานได้อย่างไร
คำตอบ #1
คุณได้พบปัญหาที่รายงานใน microsoft/TypeScript#33181 แล้ว หากคุณมีอ็อบเจ็กต์ที่มีชนิดเป็นชนิดทั่วไป เช่น o ที่มีชนิด O extends {x: string} และคุณเข้าถึงมันด้วยคีย์ เฉพาะ (ไม่ใช่ชนิดทั่วไป) เช่น "x" TypeScript มักจะ ขยาย ชนิดทั่วไปให้เป็นข้อจำกัดของมันก่อนที่จะทำการเข้าถึง ดังนั้นชนิดของ o.x จะถูกมองว่าเป็นเพียง string ไม่ใช่ชนิดการเข้าถึงดัชนีทั่วไป O["x"] การขยายนี้ไม่ได้หมายความว่า ผิด เสมอไป แต่บางครั้งก็อาจไม่ตรงกับความต้องการของนักพัฒนา
จนกว่าจะมีการแก้ไข microsoft/TypeScript#33181 คุณจะต้องหาวิธีแก้ปัญหาแบบชั่วคราว หนึ่งในวิธีคือการระบุประเภทของตัวแปรอย่างชัดเจนและกำหนดผลลัพธ์จากการเข้าดัชนีให้กับมัน เช่น const ox: O["x"]=o.x สำหรับโค้ดของคุณที่ดูเหมือนว่า
function getProperty<
T extends Obj,
K extends keyof T['data']
>(obj: T, key: K): T['data'][K] {
const data: T['data'] = obj.data;
return data[key];
}
อีกวิธีหนึ่งคือการเปลี่ยนประเภทเพื่อให้ประเภทของวัตถุที่ถูกเข้าด้วยดัชนีไม่ใช่พารามิเตอร์ประเภททั่วไป แต่เป็นประเภทวัตถุเฉพาะที่ คุณสมบัติ เป็นประเภททั่วไป เช่นนี้:
function getProperty<
D extends Obj['data'],
K extends keyof D
>(obj: { data: D } & Obj, key: K): D[K] {
return obj.data[key];
}
ที่นี่ D แทนที่ T["data"] โปรดทราบว่าฉันได้จำกัด obj ไม่ใช่แค่ {data: D} แต่เป็น {data: D} & Obj ด้วย เพราะโดยปกติในทางปฏิบัติ Obj อาจมีคุณสมบัติอื่นนอกจาก data และเราต้องการให้แน่ใจว่า obj เป็นไปตามนั้น (การใช้ intersection แทน constraint เป็นเทคนิคที่สามารถใช้แก้ปัญหาต่าง ๆ ใน TypeScript เช่น microsoft/TypeScript#7234)
ลิงก์ Playground ไปยังโค้ด
คำตอบ #2
ฉันพบว่ามักจะช่วยได้มากหากลองปรับเปลี่ยนว่าสิ่งที่เป็นชนิดทั่วไปคืออะไร ตัวอย่างเช่น นี่ได้ผล:
function getProperty<T, K extends keyof T>(
obj: { data: T },
key: K,
): T[K] {
return obj.data[key];
}
