在使用"keyof typeof"与Object.keys()时,忽略TypeScript中的"unique symbol"。

3
我想显示当前redux状态中的所有值,但涉及到“独特符号”类型时出现了错误,我不知道如何解决。
Object.keys() 表示它返回的是 “string[]”,但“typeof state”的 keyof 包括“unique symbol”,因此“ .map() ”不起作用。我添加了一个“ .filter() ”子句,但没什么帮助。
在JavaScript中运行正常,但TypeScript会报错并拒绝编译。
简化的例子:
const state = useAppSelector(state => state);
return (
    <div>
        {Object.keys(state).filter((x: keyof typeof state) => x instanceof String).map((x) =>
            <div>
                <label>{x}</label>
                <div>
                    {Object.keys(state[x]).filter((y: keyof typeof state[x]) => y instanceof String).map((y) =>
                        <span>{y}: {JSON.stringify(state[x][y])}</span>
                    )}
                </div>
            </div>
        )}
    </div>
);

我该如何正确地输入这个内容?

编辑:

我尝试使用Omit<>实用程序类型:

Object.keys(state).map((x: Omit<keyof typeof state, symbol>) =>

但是,当通过state[x]访问状态时,仍会出现错误:

Type 'Omit<"auth" | "developer" | "errors" | "info" | unique symbol, symbol>' cannot be used as an index type. ts(2538)

1个回答

1

Object.keys 只会按照规范返回字符串,所以你的 .filter() 调用并没有起到任何帮助作用。关于为什么 Object.keys() 不会返回 keyof T[] 的原因在 this answer 中已经详细解释过了。


阅读了链接的答案,并理解使用keyof对于这些情况是不安全的,有两种方法可以使用Object.entries

第一种安全版本是将值类型转换为unknown,并在需要实际类型时使用类型保护:

// Object.entries throws on null/undefined
function isEntriesCallable(arg: T): arg is Record<any,unknown> {
    return arg !== null && typeof arg !== "undefined";
}

const state = useAppSelector(state => state);
return (
    <div>
        {Object.entries(state as Record<any, unknown>).map(([x, stateX]) =>
            <div>
                <label>{x}</label>
                <div>
                    { isEntriesCallable(stateX) ? Object.entries(stateX).map(([y, stateY]) =>
                        <span>{y}: {JSON.stringify(stateY)}</span>
                    ) : <span>null/undefined</span>}
                </div>
            </div>
        )}
    </div>
);

优点/缺点是所有值类型现在都是“unknown”,如果想要缩小它们的范围,就必须调用类型保护。在您的示例中,JSON.stringify接受任何类型,因此拥有未知值实际上并不会影响任何内容。将来对代码的任何编辑都将迫使开发人员使用类型保护,如果他们想要调用除“any”或“unknown”之外需要类型的任何内容。
一种不太安全的版本存在与链接答案中突出的过度属性问题。然而,如果您知道类型是精确的,这将会将 stateX 缩小到 typeof state 的字符串键属性值类型的联合。它使用了一个映射类型的强制转换,该类型执行了您使用 Omit 所意图的操作。提供的类型保护还允许对 stateX 值类型进行一些类型缩小,以排除原始值或 null 值,从而避免在原始值或 null 上调用 Object.entries
// Extract an object containing only string property keys
// guarantees your type will match `{[key: string]: T}` for the
// argument to the Object.entries type definition.
type StringKeyedObject<T extends object, K extends keyof T = keyof T> = {
    [Key in (K extends string ? K : never)]:  T[Key];
}

type NonObject = number | string | boolean | symbol | bigint | undefined | null;
function isObjectLike<T extends object>(arg: T | NonObject): arg is T { return arg !== null && typeof arg === "object"; }

const state = useAppSelector(state => state);
return (
    <div>
        {Object.entries(state as StringKeyedObject<typeof state>).map(([x, stateX]) =>
            <div>
                <label>{x}</label>
                <div>
                    {isObjectLike(stateX) ? Object.entries(stateX as StringKeyedObject<typeof stateX>).map(([y, stateY]) =>
                        <span>{y}: {JSON.stringify(stateY)}</span>
                    ) : <span>{JSON.stringify(stateX)}</span>}
                </div>
            </div>
        )}
    </div>
);

优点:

  • 使用您自己声明的类型进行推断/缩小。

缺点:

  • 不安全的类型转换可能会导致未处理的对象属性上不存在于类型定义中的错误。

非常感谢!这是一个令人沮丧的问题。我使用了第二个解决方案,因为它更好地定义了类型,而我的redux存储没有使用部分类型。由于我正在使用eslint,我确实不得不添加一个规则"@typescript-eslint/ban-types": "off",以便它不会对“object”抛出错误,我认为这应该是一个警告。 - Andy Hubbard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接