Kodokon kodokon.com

generic ขั้นสูง: constraint และค่าเริ่มต้น

คุณจะจำกัด type parameter ด้วย extends และกำหนดค่าเริ่มต้นเพื่อสร้าง generic API ที่ใช้งานง่าย

10 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

generic ที่ไม่มี constraint นั้นทึบแสง: ภายในตัวฟังก์ชัน T ไม่ให้คุณทำอะไรได้เลย คำสั่ง extends สร้างสัญญาขั้นต่ำบน type parameter ซึ่งปลดล็อกการเข้าถึง property ที่รับประกันไว้ พร้อมทั้งคงชนิดที่แม่นยำของผู้เรียกไว้ นั่นคือความแตกต่างพื้นฐานจากพารามิเตอร์ที่มีชนิดเป็น interface: constraint เก็บข้อมูลไว้ ส่วน interface ลบมันทิ้ง

TYPESCRIPT
function longest<T extends { length: number }>(
  a: T,
  b: T
): T {
  return a.length >= b.length ? a : b;
}

const word = longest("hello", "worldwide");
const list = longest([1, 2], [3, 4, 5]);
word ยังคงมีชนิดเป็น string, list เป็น number[]

คู่หู K extends keyof T คือรูปแบบที่คุ้มค่าที่สุดในการใช้งานประจำวัน: มันเชื่อม type parameter สองตัวเข้าด้วยกันและรับประกันแบบ static ว่าคีย์เป็นของอ็อบเจกต์จริง ชนิดค่าที่คืน T[K] (ซึ่งเป็น indexed access type) จะติดตาม property ที่ร้องขออย่างแม่นยำ นี่คือวิธีที่ฟังก์ชันอย่าง pick หรือ pluck จากไลบรารี utility ถูกกำหนดชนิดพอดี

TYPESCRIPT
function getProp<T extends object, K extends keyof T>(
  obj: T,
  key: K
): T[K] {
  return obj[key];
}

const config = { port: 8080, host: "localhost" };
const port = getProp(config, "port");
const host = getProp(config, "host");
port ถูก infer เป็น number, host เป็น string; คีย์อื่นใดจะถูกปฏิเสธ

ค่าเริ่มต้น (T = string) ทำให้ generic ไม่จำเป็นต้องระบุ ณ จุดเรียกใช้: ผู้ใช้จะระบุ type argument ก็ต่อเมื่อต้องการเบี่ยงจากกรณีทั่วไปเท่านั้น เมื่อรวมกับ constraint (T extends X = Y) มันจะกำหนดทั้งขอบเขตและพฤติกรรมโดยปริยาย ค่าเริ่มต้นต้องเป็นไปตาม constraint มิฉะนั้นการประกาศนั้นเองจะคอมไพล์ไม่ผ่าน นี่คือรูปแบบมาตรฐานสำหรับ HTTP client, store และ typed event emitter

TYPESCRIPT
interface ApiResponse<T = unknown> {
  status: number;
  data: T;
}

async function fetchJson<T = unknown>(
  url: string
): Promise<ApiResponse<T>> {
  const res = await fetch(url);
  return { status: res.status, data: await res.json() };
}

interface Post { id: number; title: string }
const typed = await fetchJson<Post>("/api/posts/1");
const loose = await fetchJson("/api/health");
หากไม่ระบุ type argument, data จะย้อนกลับเป็น unknown ไม่ใช่ any

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. constraint K extends keyof T รับประกันอะไรใน generic signature?
    • ว่า K เป็นสตริงใด ๆ ก็ได้
    • ว่า K เป็นคีย์ที่มีอยู่ของ T ซึ่งตรวจตอนคอมไพล์
    • ว่า T มีคีย์แบบ optional อย่างน้อยหนึ่งตัว
    • ว่า K และ T เป็นชนิดเดียวกัน
  2. longest<T extends { length: number }> มีข้อได้เปรียบอะไรเหนือ longest(a: { length: number }, b: ...) ที่ไม่ใช้ generic?
    • มันทำงานเร็วกว่า
    • มันคงชนิดที่แม่นยำของผู้เรียกไว้ในค่าที่คืน
    • มันรับชนิดของ input ได้มากกว่า
  3. ทำไมจึงควรเลือก <T = unknown> แทน <T = any> เป็นค่าเริ่มต้น?
    • unknown คอมไพล์เร็วกว่า any
    • any ถูกห้ามใน strict mode
    • unknown บังคับให้ผู้เรียก narrow ชนิดก่อนใช้ ส่วน any ปิดการตรวจสอบทั้งหมด
    • unknown รับค่าได้น้อยกว่า any