網站搜尋

typescript - 在不使用 any 的情況下訪問對象的子對象


我正在將更好的 TypeScript 應用到一個專案中。我的後端傳來一個複雜的對象,它是一個有數十個屬性的類,其中大多數屬性本身也是類。我們有一個表單,有一個通用的變更處理器,目前裡面有一些 any 才能正常工作,但我希望使用泛型來修正它。

這是我應用程式中模式的簡化版本,但我能夠重現我看到的相同錯誤:

class PhoneNumber {
  prefix: number;
  exchange: number;
  line: number;
}

class Address {
  address1: string;
  address2: string;
  city: string;
}

class User {
  phone: PhoneNumber;
  address: Address;
}

const user = new User();

function setValue<A extends keyof User, T extends User[A], K extends keyof T, V extends T[K]>(
  value: V,
  fieldType: new () => T,
  fieldName: A,
  fieldAttribute: K
) {

  if (!user[fieldName]) {
    user[fieldName] = new fieldType();
  }

  const field = user[fieldName];

  // error here.
  field[fieldAttribute] = value;
}

setValue(408, PhoneNumber, 'phone', 'prefix');

我嘗試了幾種方法,這是我能夠接近解決方案的程度。我能在我的 IDE 中檢查最後的函數調用,看起來泛型被填充為合理的內容:function setValue<"phone", PhoneNumber, "prefix", number>(),但在編譯時我收到一個錯誤:

TS2536:型別 'K' 無法用於索引型別 'User[A]'。

我是不是搞錯方式了?如果我搞不清楚這個,我最終只會把不同類型分開到不同的處理器中。

也有一種情況是某些欄位是物件陣列,這使整個事情變得更加複雜。

答案#1

使用目前 setValue 的參數類型,可以提供一個 子類別 B 作為 fieldType(該類別 繼承 自 user[fieldName] 類別 A),並針對 B 的 fieldAttribute,而該屬性在 A 中可能不存在:

class PhoneNumber2 extends PhoneNumber {
  extension: number;
}

// Complies with setValue types,
// but if `user` already has a basic PhoneNumber instance,
// the code within setValue fails
setValue(408, PhoneNumber2, "phone", "extension");

// within setValue:
user["phone"]["extension"] = 408;
// ...but user["phone"] may be a PhoneNumber,
// not necessarily a PhoneNumber2!

在你的情況下,不是使用 K extends keyof T(T 是 fieldType 的返回類型,可能是子類),你可以直接使用 K extends keyof User[A],以確保 fieldAttribute 針對的是基類:

function setValue2<A extends keyof User, T extends User[A], K extends keyof User[A], V extends T[K]>(
  value: V,
  fieldType: new () => T,
  fieldName: A,
  fieldAttribute: K
) {

  if (!user[fieldName]) {
    user[fieldName] = new fieldType();
  }

  const field = user[fieldName];

  field[fieldAttribute] = value; // Now okay
}

setValue2(408, PhoneNumber, 'phone', 'prefix');
setValue2(408, PhoneNumber2, 'phone', 'prefix');
setValue2(408, PhoneNumber2, 'phone', 'extension'); // Error: Argument of type '"extension"' is not assignable to parameter of type 'keyof PhoneNumber'.

你甚至可以去掉通用型別 T:

function setValue3<A extends keyof User, K extends keyof User[A], V extends User[A][K]>(
    value: V,
    fieldType: new () => User[A],
    fieldName: A,
    fieldAttribute: K
) {}

遊樂場連結