当前位置: 首页 > 知识库问答 >
问题:

javascript - typescript类型定义问题?

林国安
2024-06-28

代码如下,这是一段示例代码

type MenuItem<T extends string> =
    {
        name: string;
        command: T;
    }
    |
    {
        name: string;
        command: string;
        children: Array<{
            name: string;
            command: T;
        }>;
    }

type CommandType = 'a' | 'b' | 'c'

const menu: Array<MenuItem<CommandType>> = [];
const result: Array<MenuItem<CommandType>> = [];

for (let s = 0; s < menu.length; s++) {
    const level1 = menu[s];

    if (level1.children && Array.isArray(level1.children) && level1.children.length > 0) {
        const childrenList = level1.children;
        const children: MenuItem<string>['children'] = [];

        for (let a = 0; a < childrenList.length; a++) {
            if (childrenList[a].command) {
                children.push({
                    name: childrenList[a].name,
                    command: childrenList[a].command
                });
            }
        }
        if (children.length > 0) {
            result.push({
                name: level1.name,
                command: level1.command,
                children
            });
        }
    } else if (level1.command) {
        result.push({
            name: level1.name,
            command: level1.command
        });
    }
}

那段for循环是想过滤有效数据。

问题是这段代码ts报错,代码逻辑没问题,问题的根本出在MenuItem的类型定义上,本意是想定义一个二级级联数据MenuItem,之所以使用T extends string而不是直接写为string,是因为在某些交互函数的参数中,希望将代表command的参数定义一个固定的可选数据CommandType。

当有子级数据的时候,关心的是子级数据的command,父级数据的command不希望受到CommandType的制约,随便写字符串都可以;没有子级数据时,关心的是第一级数据的command,这时command希望受到CommandType的制约

现在MenuItem的这种定义方式导致下面的代码ts报错,应该怎么定义MenuItem才能在达到目的的同时ts还不报错呢?

共有1个答案

姬墨竹
2024-06-28

为了解决类型定义的问题,你可以对 MenuItem 的类型定义做一些调整,以区分具有 children 属性的项(其子级 command 类型为 string)和不具有 children 属性的项(其 command 类型为 CommandType)。

一种方法是使用条件类型(conditional types)和类型别名(type aliases)来创建一个能够处理不同情况的类型。但是,TypeScript 并没有直接的语法来在单个类型定义中基于是否存在某个属性来应用不同的类型约束。因此,我们需要创建两个独立的类型,并在需要时根据条件将它们组合起来。

不过,在你的具体场景中,由于我们关心的是 children 属性的存在性,我们可以将 MenuItem 定义为一个泛型类型,该类型接受一个类型参数 C(代表子级 command 的类型),并在需要时将其设置为 stringCommandType

下面是一个修改后的 MenuItem 类型定义,它应该可以满足你的需求:

type MenuItemNoChildren<C extends string> = {
    name: string;
    command: C;
};

type MenuItemWithChildren<C extends string> = {
    name: string;
    command: string; // 不受CommandType的制约
    children: Array<MenuItemNoChildren<C>>; // 子级command使用相同的泛型参数
};

// 合并两个类型定义,使用条件类型判断是否有children属性
type MenuItem<C extends string> =
    | (MenuItemNoChildren<C> & { children?: undefined })
    | (MenuItemWithChildren<C> & { children: non-null });

// 注意:TypeScript中没有直接的"non-null"类型,但我们可以使用Exclude等技巧来模拟
// 这里我们简单地假设如果children存在,它就不是null或undefined

// 定义CommandType保持不变
type CommandType = 'a' | 'b' | 'c';

// 使用MenuItem类型时,为没有children的项指定CommandType,为有children的项指定string
const menu: Array<MenuItem<CommandType | string>> = [];
const result: Array<MenuItem<CommandType | string>> = [];

// ...(其余代码保持不变)

但是,请注意,由于 TypeScript 没有直接的方式来表达 "如果 children 存在则它是非空的,并且 command 应该是 string,如果 children 不存在则 command 应该是 CommandType" 这样的条件,上面的代码使用了 CommandType | string 作为折衷方案。这意味着对于没有 children 的项,command 可以是 CommandType 中的任何值,也可以是任何字符串,这可能不是你想要的最严格的类型检查。

在实际应用中,你可能需要根据你的具体需求来调整这个类型定义,或者考虑使用不同的数据结构或类型系统来更精确地表达你的意图。

 类似资料:
  • 下面这个贴图中的告警怎么去除呢,需要怎么修改呢? 代码如下: 问题补充: 实例化后为其赋值时 ts警告:类型实例化过深,且可能无限 PbfLayer这个类中的完整代码如下:

  • 定义一个类型 条件1:变量类型是一个字符串。 条件2:变量值不是 get "typescript": "~5.2.2"

  • 在我的项目中,我希望有一个包含所有类型脚本定义的文件,如下所示: type_defs.tsx: 问题是,当我尝试导出typedefs时,如下所示: 我得到了错误: 正如我在网络中发现的那样,只能对typedef文件进行默认导出,这意味着我必须为每个类型定义准备一个文件,这绝对是荒谬的! 在我看来,不允许typedefs具有多个非默认导出的约束完全是无稽之谈,并且使编码变得多余且难以重用。拥有一个包

  • 在文件 /src/stores/otpInfos.ts 的第 7 行,TypeScript 报出以下错误(但是执行 pnpm dev 运行是没有任何问题的): 如果没有引用 ".pnpm/@vueuse+shared@9.13.0_vue@3.3.4/node_modules/@vueuse/shared",则无法命名 "useOTPInfosStore" 的推断类型。这很可能不可移植。需要类型注

  • 我想解决的是res.Code下面的红线问题 下面是loginApi接口的类型定义: 其中UserType类型的定义: IResponse类型的定义: 为什么Promise的返回值Code,Data,Msg下面都带红线? 代码:

  • 在尝试在其他地方发布的建议后,我发现自己无法运行使用非类型化 NPM 模块的打字稿项目。下面是一个最小示例和我尝试过的步骤。 对于这个最小的例子,我们将假设 没有现有的类型定义。因此,我们将忽略包 ,并尝试手动将其类型文件 添加到我们的项目中。 文件夹结构 < li >节点模块 < ul > < li>lodash < li>foo.ts 自定义 洛达什.d.ts 接下来是文件。 文件 文件直接从