在 Typescript 中的 const 数组上进行类型安全查找

Fré*_*ric 5 typescript

我想知道是否可以find()在 const 数组上创建类型安全:

const MY_ARRAY = [
  { id: "a", name: "AAA" },
  { id: "b", name: "BBB" },
  { id: "c", name: "CCC" },
] as const;

type MyArrayId = (typeof MY_ARRAY[number])["id"]

function find<ID extends MyArrayId>(id: ID): MyTypesafeArrayFind<ID> {
  return MY_ARRAY.find(entry => entry.id === id);
}

// TODO: here lies the problem :)
type MyTypesafeArrayFind<ID extends MyArrayId> = unknown 

const typesafeResult = find("a");
// I'd expect `typeof typesafeResult` to be { id: "a", name: "AAA" }
// Note: I am *not* interested into something like:
//   { id: "a"|"b"|"c", name: "AAA"|"BBB"|"CCC" }
Run Code Online (Sandbox Code Playgroud)

看到这个游乐场

您认为这对于 const 数组可行吗?

作为精度,即使在我的示例中这不是类型安全表达的,id我的数组中的字段也将保证是唯一的(我们可以认为匹配搜索条件的第一个条目将匹配)

预先感谢所有参与人员。

cap*_*ian 3

这里有易于理解的解决方案:

const MY_ARRAY = [
  { id: "a", name: "AAA" },
  { id: "b", name: "BBB" },
  { id: "c", name: "CCC" },
] as const;

type MyArray = typeof MY_ARRAY

type MyArrayId = MyArray[number]["id"]

function find<ID extends MyArrayId>(id: ID): MyTypesafeArrayFind<ID>
function find<ID extends MyArrayId>(id: ID) {
  return MY_ARRAY.find(entry => entry.id === id);
}

type MyTypesafeArrayFind<Id extends MyArrayId> = Extract<MyArray[number], { id: Id }>

// const typesafeResult: {
//    readonly id: "a";
//    readonly name: "AAA";
// }

const typesafeResult = find("a")

const fn = (str: string) => {
  find(str) // error
}
Run Code Online (Sandbox Code Playgroud)

然而它也有其自身的缺点。不允许find在高阶函数内部使用。您只能使用现有的“id”。

如果你想让它超级安全,并且让你的同事生气,你可以考虑这个解决方案:

const MY_ARRAY = [
  { id: "a", name: "AAA" },
  { id: "b", name: "BBB" },
  { id: "c", name: "CCC" },
] as const;

type MyArray = typeof MY_ARRAY

type MyArrayId = MyArray[number]["id"]

/**
 * Returns true if provided generic is literal string type
 */
type IsLiteralString<Str extends string> = string extends Str ? false : true

const withTuple = <
  Elem extends { id: string },
  List extends Elem[]
>(tuple: readonly [...List]) => {
  function curry<Id extends List[number]['id']>(id: Id): Extract<List[number], { id: Id }>
  function curry<Id extends string>(id: IsLiteralString<Id> extends true ? Id extends List[number]['id'] ? Id : never : Id): List[number] | undefined
  function curry(id: string) {
    return tuple.find(entry => entry.id === id);
  }

  return curry

}

const find = withTuple(MY_ARRAY)

// {
//     readonly id: "a";
//     readonly name: "AAA";
// }
const typesafeResult = find("a")

let id = 'any id'


const typesafeResult2 = find(id,) // Element | undefined, expected and safe

const typesafeResult23 = find('aa',) // expected error, it is useles to use id which is not exists in our list

Run Code Online (Sandbox Code Playgroud)

操场

IsLiteralString- 检查提供的泛型是文字字符串类型还是只是string非推断类型。

withTuple- 期望您的推理列表并返回所需的函数。

curry- 在内部声明的函数withTuple是带有重载的函数。

第一个函数重载function curry<Id extends List[number]['id']>(id: Id): Extract<List[number], { id: Id }>推断数组中id存在并推断返回类型。id

第二个函数重载 function curry<Id extends string>(id: IsLiteralString<Id> extends true ? Id extends List[number]['id'] ? Id : never : Id): List[number] | undefined

string允许您为高阶函数提供非递归常规类型。还提供了id文字,并且它不存在于您的数组中,TS 将禁止您使用它。我称之为“打字稿否定”。您可以在我的文章答案中找到有关此方法的更多信息