#typescript
#typescript
Вопрос:
Я пытаюсь создать простую рекурсивную функцию, которая будет работать с объектом, имеющим динамическую структуру, и у меня возникают проблемы с наборами.
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'
});
Технически код работает, но в recursive
функции я получаю неявную ошибку any. Вложенные объекты будут иметь некоторое поле, которое будет указывать его идентификатор (не всегда id, может быть CategoryID или что-то еще) и необязательное поле, содержащее массив объектов с одинаковой структурой (не всегда дочерних).
Проблемы заключаются в
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);
});
}
}
с element[idProp]
помощью и element[childProp]
Ответ №1:
В вашем первоначальном определении recursive
параметр общего типа T
совершенно не ограничен и может быть любым. Кроме того, на уровне типа, childProp
и idProp
не может реально способствовать наборам с таким общим типом ( string
), когда мы хотим, чтобы их значения имели значение. т. Е. Нам нужно больше литеральных типов для них.
Попробуйте следующее, которое пытается дать более общее определение формы объектов, которые мы ищем:
type MyElement<CKey extends string, IKey extends string>
= { [K in CKey]?: MyElement<CKey, IKey>[] } amp; { [K in IKey]: number } amp; Record<string, any>;
{ [K in CKey]?: MyElement<CKey, IKey>[] }
: описывает объект со свойствами, названными by CKey
, как необязательный массив дочерних элементов, которые используют один и тот же CKey
и IKey
.
{ [K in IKey]: number }
: описывает объект со свойствами, названными через IKey
, чтобы быть number
.
Record<string, unknown>
: описывает объект с дополнительными свойствами неизвестных типов. Мы используем unknown
такие, чтобы их использование давало лучшую ошибку, чем any
та, которая молча позволит вам выйти из системы типов. Это используется, чтобы сказать, что дополнительные свойства объектов в порядке.
Затем мы объединяем оба с amp;
, чтобы сказать, что объект должен удовлетворять всем ограничениям. Взгляните на пример:
const t: MyElement<'children', 'testId'> = { testId: 30, children: [{ testId: 40 }] };
Теперь мы можем обновить сигнатуру recursive
, чтобы использовать новые ограничения:
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);
});
}
}
И, конечно, некоторые тесты, чтобы убедиться, что все выполняется с проверкой типов, как ожидалось:
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');
Попробуйте это на игровой <a rel="noreferrer noopener nofollow" href="https:///www.typescriptlang.org/play/#src=type MyElement
= { [K in CKey]?: MyElement[] } & { [K in IKey]: number } & Record;
const t: MyElement = { testId: 30, children: [{ testId: 40 }] };
function recursive(element: MyElement, childProp: CKey, idProp: IKey) {
console.log(element[idProp], childProp, element[childProp]);
if (element[childProp]) {
element[childProp].forEach((el) => {
recursive(el, childProp, idProp);
});
}
}
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’);
// Error, the deepest `children` must be an array our these id’d elements
recursive({ testId: 10, children: [{ testId: 13, children: {} }] }, ‘children’, ‘testId’);» rel=»nofollow noreferrer»>площадке!
Комментарии:
1. Спасибо, это действительно полезно, но как насчет объектов, которые имеют больше свойств: например.
{ children: [], id: 'something', anyprop: 'something' date: new Date() }
2. Хорошее наблюдение, обновлено, чтобы включить это
Record<string, unknown>
, чтобы разрешить дополнительные свойства с типомunknown
, поскольку наша функция никогда их не затрагивает.3. Еще раз спасибо, это именно то, что мне было нужно