简体   繁体   English

根据 TypeScript 中的值类型限制键

[英]Limiting keys based on value types in TypeScript

I need a sort of setProperty function that allows setting properties on an object, but limiting them to properties of type string .我需要一种setProperty函数,它允许在对象上设置属性,但将它们限制为string类型的属性。

It seems like I've figured out how to limit the allowed keys to the ones that have a value type of K , but TypeScript doesn't seem to recognize that entirely.似乎我已经想出了如何将允许的键限制为具有K值类型的键,但 TypeScript 似乎并没有完全识别出这一点。 Here's what I have:这是我所拥有的:

type KeysMatching<T, V> = {
  [K in keyof T]: T[K] extends V ? K : never;
}[keyof T];

function bindChange<C, K extends KeysMatching<C, string>>(
  container: C,
  key: K,
): (ev: KeyboardEvent) => void {
  return (ev: KeyboardEvent) =>
    // @ts-ignore how do we tell typescript container[key] is a string?
    (container[key] = (ev.target! as HTMLInputElement).value);
}

const sample = {
  foo: 'bar',
  baz: 42,
};

// this is correctly allowed
bindChange(sample, 'foo');

// this is correctly rejected
//bindChange(sample, 'baz');

Playground Version. 游乐场版。

What I'm trying to figure out is if there is a better approach that can eliminate the need for the @ts-ignore in there.我想弄清楚的是,是否有更好的方法可以消除对@ts-ignore的需求。

In all fairness, the function is not 100% type safe, there is no way to ensure container[key] is not a subtype of string .平心而论,该函数不是 100% 类型安全的,无法确保container[key]不是string的子类型。 The constraints are only a minimal requirement, the actual types can be more specific.约束只是最低要求,实际类型可以更具体。 Consider this:考虑一下:


const sample = {
  foo: 'bar',
  baz: 42,
} as const

// this is correctly allowed
bindChange(sample, 'foo'); // but 'foo' is of type 'bar' but is assigned a string

The problem is that in general Ts does not really do a lot of reasoning about conditional types that still have unresolved type parameters.问题是,通常 Ts 并没有真正对仍然具有未解析类型参数的条件类型进行大量推理。 So a lot of times generic function implementations will end up with type assertions.所以很多时候泛型函数的实现都会以类型断言告终。 Don't worry about it too much IMO.不要担心太多 IMO。

Since there is no way to make sure all properties are exactly string, the only solution, if you consider this corner case as not important, is to use a type assertion:由于无法确保所有属性都是字符串,如果您认为这种极端情况不重要,唯一的解决方案是使用类型断言:


function bindChange<C, K extends KeysMatching<C, string>>(
  container: C,
  key: K,
): (ev: KeyboardEvent) => void {
  return (ev: KeyboardEvent) =>
    (container[key] = (ev.target! as HTMLInputElement).value as unknown as C[K]);
}

Playground Link 游乐场链接

There is also this version, which does get around the issue, but looses intelisense for the function call, which is more important in my opinion:还有这个版本,它确实解决了这个问题,但失去了函数调用的智能感知,这在我看来更重要:

type KeysMatching<T, V> = {
  [K in keyof T]: T[K] extends V ? K : never;
}[keyof T];

function bindChange<C extends Record<K, string>, K extends PropertyKey>(
  container: C,
  key: K,
): (ev: KeyboardEvent) => void {
  return (ev: KeyboardEvent) =>
    (container[key] = (ev.target! as HTMLInputElement).value as unknown as C[K]);
}

const sample = {
  foo: 'bar',
  baz: 42,
};

// this is correctly allowed
bindChange(sample, 'foo');

Playground Link 游乐场链接

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM