Saya mencoba mendefinisikan fungsi salinan bidang generik di TypeScript tetapi gagal membuat pengetikan berfungsi. Lihat metode 4 dalam kode di bawah ini. Pertanyaan saya adalah, bagaimana saya bisa menulis fungsi yang dapat membuat pengetikan TypeScript berfungsi dengan baik dan memeriksa jenis tujuan dan jenis sumber?

Cerita panjang: Saya bekerja di sebuah proyek yang membaca data dari database NoSQL dan kembali ke klien melalui API. Dari sudut pandang keamanan, lebih baik melakukan proyeksi (salinan bidang eksplisit) untuk menghindari penambahan bidang yang dikembalikan ke klien di masa mendatang. Saya mencari cara mudah untuk melakukan ini.

// ----------
// TypeScript type definition
// ----------
interface Student {
  name: string
  studentId?: string
}

interface StudentDbRecord extends Student {
  createTimestamp: number
}

interface StudentApiResult extends Student {
  // We use NoSQL, so ID is not part of the record
  id: string
}

// ----------
// Database data
// ----------
const docId: string = '6542fdba-fcae-4b15-a1c8-72a2a57f51c7'
const dbRecord: StudentDbRecord = {
  name: 'Chirs',
  createTimestamp: Date.now()
}

// ----------
// Implementation
// ----------
// Method 1: An extra `createTimestamp` field is in `apiResult1` and returned to API caller
const apiResult1: StudentApiResult = {
  ...dbRecord,
  id: docId
}

const apiResult2: StudentApiResult = {
  id: docId,
  name: dbRecord.name,
  // Method 2: This result in a field with `undefined` value, which causes other issues
  studentId: dbRecord.studentId
}

// Method 3: This works, but easier to make mistake because `studentId` is specified 3 times
const apiResult3: StudentApiResult = {
  id: docId,
  name: dbRecord.name
}
if (dbRecord.studentId !== null) { apiResult3.studentId = dbRecord.studentId }

// Method 4, this should be the best but unable to get it working in TypeScript
function copyField<D, S, K extends (keyof D & keyof S)>(dest: D, src: S, key: K): void {
  if (src[key] !== undefined) {
    // Error ts(2322): 'D' could be instantiated with an arbitrary type which could be unrelated to 'S'
    dest[key] = src[key]
  }
}

const apiResult4: StudentApiResult = {
  id: docId,
  name: dbRecord.name
}
copyField(apiResult4, dbRecord, 'studentId')
0
VCD 4 April 2021, 17:12

1 menjawab

Jawaban Terbaik

Triknya adalah hanya berkomitmen pada pengetikan minimal untuk objek tujuan dan src masuk. Lihat saya repro minimal dibuat untuk Anda dan solusi potensial di ini bermain

function copyField<Key extends keyof any, Value>(
  key: Key,
  source: { [K in Key]: Value },
  dest: { [K in Key]: Value }
): void {
  const sourceValue = source[key];
  if (sourceValue !== undefined) {
    dest[key] = sourceValue;
  }
}

const rover: Dog = {
  hasFleas: true,
  hasStick: false,
};

const felix: Cat = {
  hasFleas: false,
  hasMouse: true,
};

const klaus: Fish = {
  hasTeeth: false,
};

copyField("hasFleas", rover, felix);

//this is (correctly) a compiler error
copyField("hasFleas", felix, klaus);

interface Dog {
  hasFleas: boolean;
  hasStick: boolean;
}

interface Cat {
  hasFleas: boolean;
  hasMouse: boolean;
}

interface Fish {
  hasTeeth: boolean;
}

1
cefn 4 April 2021, 15:43