首页 > 解决方案 > 如何正确定义嵌套对象的泛型类型

问题描述

我正在尝试创建一个简单的递归函数,该函数将处理具有动态结构的对象并且我遇到了类型问题。

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函数中我得到一个隐含的任何错误。嵌套对象将有一些字段指示它的 id(不总是 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]

标签: typescript

解决方案


在您的原始定义中recursive,泛型类型参数T完全不受约束,可以是任何东西。最重要的是,在类型级别,当我们希望它们的值有意义时,childPropidProp不能真正为具有这种泛型类型 ( ) 的类型做出贡献。string即我们想要为他们提供更多的文字类型。

尝试以下尝试对我们正在寻找的对象的形状进行更通用的定义:

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>[] }: 描述一个对象,其属性名为 byCKey是一个可选的子数组,它们共享相同的CKeyIKey

{ [K in IKey]: number }: 描述一个对象,其属性命名IKey为 a number

Record<string, unknown>: 描述具有未知类型的附加属性的对象。我们使用unknown这样的方式,使用它们会产生更好的错误,而不是any默默地让你脱离类型系统。这用于表示对象的附加属性很好。

然后我们将两者放在一起&说对象必须满足所有约束。看一个例子:

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');

在操场上试试吧!


推荐阅读