ค้นหาเว็บไซต์

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