如何安全地过滤具有唯一值和键的不可变数组?

问题描述 投票:0回答:1
    class Constants {
        static readonly STATES_AND_ACRONYMS = [
            ["AA", "NAME_A"],
            ["BB", "NAME_B"],
            ["CC", "NAME_C"],
            ["DD", "NAME_D"],
            ["EE", "NAME_E"],
            ["FF", "NAME_F"],
        ] as const;
    }
    
    type Acronym = typeof Constants.STATES_AND_ACRONYMS[number][0];
    type State = typeof Constants.STATES_AND_ACRONYMS[number][1];
    
    function getStateByAcronym(acronym : Acronym) : State{
        let entry = Constants.STATES_AND_ACRONYMS.find((entry) => entry[0]==acronym)
        if(entry == undefined){
            //impossible
            return Constants.STATES_AND_ACRONYMS[0][1];
        }
    
        return entry[1];
    }
    
    function getAcronymByState(state : State) : Acronym{
        let entry = Constants.STATES_AND_ACRONYMS.find((entry) => entry[1]==state)
        if(entry == undefined){
            //impossible
            return Constants.STATES_AND_ACRONYMS[0][0];
        }
        return entry[0];
    }

因此,有一个包含州及其缩写的数组,每个缩写以及州名称都是唯一且不可变的。基于所述数组创建两种类型:Acronym 和 State。然后我有这些函数来通过其首字母缩略词获取状态或获取状态的首字母缩略词,因为它们的参数是由数组生成的类型,我可以保证数组中会有一个带有该参数的条目。尽管如此,find 函数可能会返回未定义的结果,我有 if 来处理这个问题。它已经有点类型安全了,没有人能够传递不在数组中的参数,我只是想以打字稿理解的方式编写代码。

我想过制作自己的 for 循环来查找,但无论如何我都必须实例化变量,所以问题仍然存在,只是以不同的方式。我也知道我可以断言该变量不是未定义的,但我将再次处理打字稿不知道状态/首字母缩略词位于数组中的事实,而这不是我想要的。我想以打字稿知道的方式编写代码,如果我循环遍历数组,就会在数组中找到任何状态/首字母缩略词。

arrays typescript types constants immutability
1个回答
0
投票

TypeScript 在类型系统中没有详尽数组的表示。这或多或少是 microsoft/TypeScript#47404microsoft/TypeScript#46894 的主题。无法告诉 TypeScript

find()
一定会成功,因为至少有一个数组元素将与谓词匹配。目前还不清楚如何开始将这些东西编码到类型系统中。它超出了我们期望 TypeScript 跟踪的范围。


因此,您需要解决它。简单且安全的做法是执行冗余运行时检查,然后在不可能的情况下抛出异常:

function getStateByAcronym(acronym: Acronym): State { let entry = Constants.STATES_AND_ACRONYMS.find((entry) => entry[0] == acronym) if (entry == undefined) { throw new Error("OH NO") } return entry[1]; } function getAcronymByState(state: State): Acronym { let entry = Constants.STATES_AND_ACRONYMS.find((entry) => entry[1] == state) if (entry == undefined) { throw new Error("OH NO") } return entry[0]; }
这是运行时的一点额外工作,但现在 TypeScript 和任何不经意的观察者都会同意,如果函数完成,它会返回预期类型的值。


最简单且不太安全的做法是断言

find()

 的返回值是通过使用 
非空断言运算符 (!
)
:
来定义的

function getStateByAcronym(acronym: Acronym): State { let entry = Constants.STATES_AND_ACRONYMS.find((entry) => entry[0] == acronym)! return entry[1]; } function getAcronymByState(state: State): Acronym { let entry = Constants.STATES_AND_ACRONYMS.find((entry) => entry[1] == state)! return entry[0]; }
这实际上与上面的运行时检查没有太大区别,因为如果发生不可能的情况,您也会在这里抛出异常。索引到 

undefined

 是一个类型错误。


如果您确实希望 TypeScript 相信每个条目都存在,您可以重构以采用 TypeScript 已经理解为详尽的格式对数据进行编码。 例如,对象类型在每个已知键处肯定包含一个属性:

class Constants { static readonly ACRONYMS_TO_STATES = { AA: "NAME_A", BB: "NAME_B", CC: "NAME_C", DD: "NAME_D", EE: "NAME_E", FF: "NAME_F" } as const; } type Acronym = keyof typeof Constants.ACRONYMS_TO_STATES; function getStateByAcronym(acronym: Acronym): State { return Constants.ACRONYMS_TO_STATES[acronym] }
这是可行的,因为 TypeScript 知道您只是在查找某些内容,并且它会在那里。查找不可能落在某个列表的末尾。

反向查找也可以使用对象来完成,尽管您需要诸如

类型断言之类的东西来说服 TypeScript 翻转键和值会产生强类型结果。您可以将其放入实用函数中:

function reverse<T extends Record<keyof T, PropertyKey>>( obj: T ): { [K in keyof T as T[K]]: K } { return Object.fromEntries(Object.entries(obj).map(([k, v]) => [v, k])); }
然后编写相应的反向查找代码:

class Constants { static readonly STATES_TO_ACRONYMS = reverse(Constants.ACRONYMS_TO_STATES); } type State = keyof typeof Constants.STATES_TO_ACRONYMS function getAcronymByState(state: State): Acronym { return Constants.STATES_TO_ACRONYMS[state] }
这对于您的用例来说可能有点过头了,但有时最好使用直接而不是间接表示我们所需的不变量的数据结构。

Playground 代码链接

© www.soinside.com 2019 - 2024. All rights reserved.