typescript - Kuinka määritellä geneerinen tyyppi oikein sisäkkäiselle objektille
Yritän luoda yksinkertaista rekursiivista funktiota, joka käsittelee objektia, jolla on dynaaminen rakenne, ja minulla on ongelmia tyyppien kanssa.
interface Nested {
id: number;
children?: Nested[];
}
interface Props<T> {
elements: T[];
childProp: string;
idProp: string;
}
function recursive<T>(element: T, childProp: string, idProp: string) {
console.log(element[idProp], childProp, element[childProp]);
if (element[childProp]) {
element[childProp].forEach((el: T) => {
recursive<T>(el, childProp, idProp);
});
}
}
function test<T>(props: Props<T>) {
props.elements.forEach((element) => {
recursive<T>(element, props.childProp, props.idProp);
});
}
const nested: Nested[] = [
{
id: 1,
children: [
{
id: 2,
children: [
{
id: 3
}
]
},
{
id: 4,
children: [
]
},
]
},
{
id: 5
}
]
test<Nested>({
elements: nested,
childProp: 'children',
idProp: 'id'
});
Teknisesti koodi toimii, mutta recursive-funktiossa saan implisiittisen any-virheen. Sisäkkäisillä objekteilla on jokin kenttä, joka kertoo sen id:n (ei aina id, voi olla categoryId tai mikä tahansa muu) ja valinnainen kenttä, joka sisältää taulukon saman rakenteen objekteja (ei aina children).
Ongelmia on
function recursive<T>(element: T, childProp: string, idProp: string) {
console.log(element[idProp], childProp, element[childProp]);
if (element[childProp]) {
element[childProp].forEach((el: T) => {
recursive<T>(el, childProp, idProp);
});
}
}
elementin element[idProp] ja element[childProp] kanssa
Vastaus #1
Alkuperäisessä määritelmässäsi termille recursive geneerinen tyyppiparametri T on täysin rajoittamaton ja voi olla mitä tahansa. Lisäksi tyyppitasolla childProp ja idProp eivät voi oikeastaan vaikuttaa tyyppimäärityksiin niin geneerisen tyypin (string) kanssa, kun haluamme, että niiden arvot ovat merkityksellisiä. Toisin sanoen, haluamme niille kirjallisia tyyppejä.
Kokeile seuraavaa, joka pyrkii antamaan yleisemmän määritelmän etsimiemme kohteiden muodolle:
type MyElement<CKey extends string, IKey extends string>
= { [K in CKey]?: MyElement<CKey, IKey>[] } & { [K in IKey]: number } & Record<string, any>;
{ [K in CKey]?: MyElement<CKey, IKey>[] }: kuvaa objektia, jonka ominaisuudet nimetään CKey mukaan, ja ne ovat valinnainen taulukko lapsielementeistä, jotka jakavat saman CKey- ja IKey-arvon.
{ [K in IKey]: number }: kuvaa objektia, jonka ominaisuudet, joilla on nimet IKey, ovat number.
Record<string, unknown>: kuvaa objektia, jolla on lisäominaisuuksia tuntemattomilla tyypeillä. Käytämme unknown-tyyppiä, jotta sen käyttö antaa paremman virheen kuin any, joka sallii hiljaa tyyppijärjestelmän ohittamisen. Tätä käytetään ilmoittamaan, että objektin lisäominaisuudet ovat sallittuja.
Sitten yhdistämme molemmat &-merkillä sanoaksemme, että objektin on täytettävä kaikki rajoitukset. Katsotaanpa esimerkkiä:
const t: MyElement<'children', 'testId'> = { testId: 30, children: [{ testId: 40 }] };
Nyt voimme päivittää recursive-funktion allekirjoituksen hyödyntämään uusia rajoituksia:
function recursive<CKey extends string, IKey extends string>(element: MyElement<CKey, IKey>, childProp: CKey, idProp: IKey) {
console.log(element[idProp], childProp, element[childProp]);
if (element[childProp]) {
element[childProp].forEach(el => {
recursive(el, childProp, idProp);
});
}
}
Ja tietenkin joitain testejä varmistaaksemme, että kaikki tyyppitarkistetaan odotetusti:
recursive({ testId: 10 }, 'children', 'testId');
recursive({ testId: 10, children: [], anyprop: 'something', date: new Date() }, 'children', 'testId');
// Expected error, children elements must have a `testId`
recursive({ testId: 10, children: [{}] }, 'children', 'testId');
recursive({ testId: 10, children: [{ testId: 13 }] }, 'children', 'testId');
recursive({ testId: 10, children: [{ testId: 13, children: [{ testId: 15 }] }] }, 'children', 'testId');
// Expected error, the deepest `children` must be an array our these id'd elements
recursive({ testId: 10, children: [{ testId: 13, children: {} }] }, 'children', 'testId');
Kokeile sitä leikkikentällä!
