我在 TypeScript 中有这个类型定义:
export type xhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD";
不幸的是,这是大小写敏感的...有没有办法定义为不区分大小写?
谢谢
我在 TypeScript 中有这个类型定义:
export type xhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD";
不幸的是,这是大小写敏感的...有没有办法定义为不区分大小写?
谢谢
欢迎回来!现在 TypeScript 4.1 已经引入了模板字面量类型和Uppercase
/Lowercase
内置字符串映射类型, 我们现在可以回答这个问题而不需要使用正则表达式类型。
xhrTypes
转换为所有可能方式的具体联合类型,其中大小写不敏感:type xhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD";
type AnyCase<T extends string> =
string extends T ? string :
T extends `${infer F1}${infer F2}${infer R}` ? (
`${Uppercase<F1> | Lowercase<F1>}${Uppercase<F2> | Lowercase<F2>}${AnyCase<R>}`
) :
T extends `${infer F}${infer R}` ? `${Uppercase<F> | Lowercase<F>}${AnyCase<R>}` :
""
type AnyCaseXhrTypes = AnyCase<xhrTypes>;
如果您检查{{AnyCaseXhrTypes}},您会发现它是一个包含368个成员的联合体:
/* type AnyCaseXhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" |
"CONNECT" | "HEAD" | "GEt" | "GeT" | "Get" | "gET" | "gEt" | "geT" | "get" |
"POSt" | "POsT" | "POst" | "PoST" | "PoSt" | "PosT" | "Post" |
... 346 more ... | "head" */
您可以使用此类型替换xhrType
,无论何时您需要不区分大小写:
function acceptAnyCaseXhrType(xhrType: AnyCaseXhrTypes) { }
acceptAnyCaseXhrType("get"); // okay
acceptAnyCaseXhrType("DeLeTe"); // okay
acceptAnyCaseXhrType("poot"); // error! "poot" not assignable to big union
type xhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD"
| "LONG STRINGS MAKE THE COMPILER UNHAPPY";
type AnyCaseXhrTypes = AnyCase<xhrTypes>; // error!
// Type instantiation is excessively deep and possibly infinite.
// Union type is too complex to represent
T
是传递给acceptAnyCaseXhrType()
的字符串值的类型,则我们想要做的就是确保Uppercase<T>
可分配给xhrType
。这更像是约束而不是类型(尽管我们无法直接使用通用约束来表达此内容):function acceptAnyCaseXhrTypeGeneric<T extends string>(
xhrType: Uppercase<T> extends xhrTypes ? T : xhrTypes
) { }
acceptAnyCaseXhrTypeGeneric("get"); // okay
acceptAnyCaseXhrTypeGeneric("DeLeTe"); // okay
acceptAnyCaseXhrTypeGeneric("poot"); // error! "poot" not assignable to xhrTypes
这个解决方案要求你在可能不需要它们的地方拉动通用类型参数,但它确实可以很好地扩展。
所以,我们所要做的就是等待...(查看笔记)... 3年,而TypeScript已经推出!
为了回答这篇文章,没有,这是不可能的。
2018年5月15日更新: 仍然不可能。最接近的是使用正则表达式验证字符串类型,但这个提案在最近的语言设计会议上并未得到良好的反响。
namespace XhrTypes {
function m<T, K extends string, V extends string>(
t: T, ks: K[], v: V
): T & Record<K | V, V> {
(t as any)[v] = v;
ks.forEach(k => (t as any)[k] = v);
return t as any;
}
function id<T>(t: T): { [K in keyof T]: T[K] } {
return t;
}
const mapping = id(m(m(m(m(m(m(m({},
["get", "Get"], "GET"), ["post", "Post"], "POST"),
["put", "Put"], "PUT"), ["delete", "Delete"], "DELETE"),
["options", "Options"], "OPTIONS"), ["connect", "Connect"], "CONNECT"),
["head", "Head"], "HEAD"));
export type Insensitive = keyof typeof mapping
type ForwardMapping<I extends Insensitive> = typeof mapping[I];
export type Sensitive = ForwardMapping<Insensitive>;
type ReverseMapping<S extends Sensitive> =
{[K in Insensitive]: ForwardMapping<K> extends S ? K : never}[Insensitive];
export function toSensitive<K extends Insensitive>(
k: K ): ForwardMapping<K> {
return mapping[k];
}
export function matches<K extends Insensitive, L extends Insensitive>(
k: K, l: L ): k is K & ReverseMapping<ForwardMapping<L>> {
return toSensitive(k) === toSensitive(l);
}
}
type XhrTypes.Sensitive = "GET" | "POST" | "PUT" | "DELETE" |
"OPTIONS" | "CONNECT" | "HEAD"
type XhrTypes.Insensitive = "get" | "Get" | "GET" |
"post" | "Post" | "POST" | "put" | "Put" | "PUT" |
"delete" | "Delete" | "DELETE" | "options" | "Options" |
"OPTIONS" | "connect" | "Connect" | "CONNECT" | "head" |
"Head" | "HEAD"
以及这些功能
function XhrTypes.toSensitive(k: XhrTypes.Insensitive): XhrTypes.Sensitive;
function XhrTypes.matches(k: XhrTypes.Insensitive, l: XhrTypes.Insensitive): boolean;
interface HttpStuff {
url: string,
method: XhrTypes.Insensitive,
body?: any
}
const httpStuff: HttpStuff = {
url: "https://google.com",
method: "get"
}
interface StrictHttpStuff {
url: string,
method: XhrTypes.Sensitive,
body?: any
}
declare function needStrictHttpStuff(httpStuff: StrictHttpStuff): Promise<{}>;
needStrictHttpStuff(httpStuff); // error, bad method
needStrictHttpStuff({
url: httpStuff.url,
method: XhrTypes.toSensitive(httpStuff.method)
}); // okay
XhrTypes.toSensitive()
将大小写不敏感的值转换为大小写敏感的值,那么你可以安全地传入该函数。在这种情况下,编译器会验证"get"
是否是可接受"GET"
的变体。希望这对你有所帮助,祝你好运。pOsT
这样的单词)放入字典中的暴力解决方案。为什么我不能使用i
正则表达式标志呢? - Knu虽然不是所要求的类型,但如果可以使用枚举,则可以使用以下内容进行枚举字符串值的大小写不敏感匹配:
/**
* Gets an enumeration given a case-insensitive key. For a numeric enum this uses
* its members' names; for a string enum this searches the specific string values.
* Logs a warning if the letter case was ignored to find a match, and logs an error
* including the supported values if no match was found.
*/
static toEnumIgnoreCase<T>(target: T, caseInsentiveKey: string): T[keyof T] {
const needle = caseInsentiveKey.toLowerCase();
// If the enum Object does not have a key "0", then assume a string enum
const key = Object.keys(target)
.find(k => (target['0'] ? k : target[k]).toLowerCase() === needle);
if (!key) {
const expected = Object.keys(target)
.map(k => target['0'] ? k : target[k])
.filter(k => isNaN(Number.parseInt(k)))
.join(', ');
console.error(`Could not map '${caseInsentiveKey}' to values ${expected}`);
return undefined;
}
const name = target['0'] ? key : target[key];
if (name !== caseInsentiveKey) {
console.warn(`Ignored case to map ${caseInsentiveKey} to value ${name}`);
}
return target[key];
}
import Spy = jasmine.Spy;
import {ConfigHelper} from './config-helper';
// Should match on One, one, ONE and all:
enum NumberEnum { One, Two, Three }
// Should match on Uno, uno, UNO and all, but NOT on One, one, ONE and all:
enum StringEnum { One = 'Uno', Two = 'Dos', Three = 'Tres' }
describe('toEnumIgnoreCase', () => {
beforeEach(function () {
spyOn(console, 'warn');
spyOn(console, 'error');
});
it('should find exact match for numeric enum', () => {
const result = ConfigHelper.toEnumIgnoreCase(NumberEnum, 'One');
expect(result).toBe(NumberEnum.One);
expect(console.warn).not.toHaveBeenCalled();
expect(console.error).not.toHaveBeenCalled();
});
it('should find case-insensitive match for numeric enum', () => {
const result = ConfigHelper.toEnumIgnoreCase(NumberEnum, 'two');
expect(result).toBe(NumberEnum.Two);
expect(console.warn).toHaveBeenCalled();
expect((console.warn as Spy).calls.mostRecent().args[0])
.toMatch(/value Two/);
expect(console.error).not.toHaveBeenCalled();
});
it('should yield undefined for non-match for numeric enum', () => {
const result = ConfigHelper.toEnumIgnoreCase(NumberEnum, 'none');
expect(result).toBe(undefined);
expect(console.warn).not.toHaveBeenCalled();
expect(console.error).toHaveBeenCalled();
expect((console.error as Spy).calls.mostRecent().args[0])
.toMatch(/values One, Two, Three/);
});
it('should find exact match for string enum', () => {
const result = ConfigHelper.toEnumIgnoreCase(StringEnum, 'Uno');
expect(result).toBe(StringEnum.One);
expect(console.warn).not.toHaveBeenCalled();
expect(console.error).not.toHaveBeenCalled();
});
it('should find case-insensitive match for string enum', () => {
const result = ConfigHelper.toEnumIgnoreCase(StringEnum, 'dos');
expect(result).toBe(StringEnum.Two);
expect(console.warn).toHaveBeenCalled();
expect((console.warn as Spy).calls.mostRecent().args[0])
.toMatch(/value Dos/);
expect(console.error).not.toHaveBeenCalled();
});
it('should yield undefined for name rather than string value', () => {
const result = ConfigHelper.toEnumIgnoreCase(StringEnum, 'One');
expect(result).toBe(undefined);
expect(console.warn).not.toHaveBeenCalled();
expect(console.error).toHaveBeenCalled();
expect((console.error as Spy).calls.mostRecent().args[0])
.toMatch(/values Uno, Dos, Tres/);
});
it('should yield undefined for non-match for string enum', () => {
const result = ConfigHelper.toEnumIgnoreCase(StringEnum, 'none');
expect(result).toBe(undefined);
expect(console.warn).not.toHaveBeenCalled();
expect(console.error).toHaveBeenCalled();
expect((console.error as Spy).calls.mostRecent().args[0])
.toMatch(/values Uno, Dos, Tres/);
});
});