sourcecode

TypeScript 유형 무시 대소문자

codebag 2023. 7. 7. 19:01
반응형

TypeScript 유형 무시 대소문자

TypeScript에는 다음과 같은 형식 정의가 있습니다.

export type xhrTypes = "GET" | "POST" | "PUT" | "DELETE" | "OPTIONS" | "CONNECT" | "HEAD";

슬프게도, 이것은 대소문자를 구분합니다. 대소문자를 구분하지 않는 것으로 정의할 수 있는 방법이 있습니까?

감사해요.

유형 스크립트 4.1+에 대한 새로운 답변

다시 오신 것을 환영합니다!이제 TypeScript 4.1에 템플릿 리터럴 유형과 /Lowercaseintental 문자열 매핑 유형이 도입되었으므로 정규식 유형 없이도 이 질문에 대답할 수 있습니다.


두 가지 주요 접근 방식이 있습니다."잔인한 힘" 접근법은 반복적인 조건부 유형과 조합을 많이 사용하여 당신을 변화시킵니다.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>;

AnyCaseXhrTypes368명의 조합원으로 구성된 조합임을 알게 될 것입니다.

/* 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

브루트 포스 접근 방식의 문제는 더 많거나 더 긴 문자열로 잘 확장되지 않는다는 것입니다.TypeScript의 Union 유형은 100,000개의 멤버로 제한되며, 재귀 조건부 유형은 컴파일러가 불평하기 전에 최대 20개 수준까지만 진행됩니다.따라서 적당히 긴 단어나 적당히 긴 단어 목록은 위의 접근법을 실행 불가능하게 만들 것입니다.

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일 업데이트: 여전히 불가능합니다.가장 가까운 것인 정규식 검증 문자열 유형은 언어 설계 회의에서 가장 최근에 제안되었을 때 좋은 평가를 받지 못했습니다.

@RyanCavanaugh가 말했듯이, TypeScript에는 대소문자를 구분하지 않는 문자열 리터럴이 없습니다.[편집: TypeScript가 정규화 검증된 문자열 리터럴을 지원하는 기존 제안이 있음을 상기합니다. 이를 허용할 수도 있지만 현재 이 언어의 일부는 아닙니다.]

제가 생각할 수 있는 유일한 해결책은 이러한 리터럴의 가장 가능성이 높은 변형(예: 모든 소문자, init cap)을 열거하고 필요한 경우 이들 사이에서 번역할 수 있는 함수를 만드는 것입니다.

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;

당신(@Knu)이 이것이 무엇에 필요한지 또는 어떻게 사용할 계획인지는 잘 모르겠지만, 민감한/무감한 메소드 간에 변환하거나 대소문자를 구분하지 않는 두 메소드가 일치하는지 확인하고 싶은 것 같습니다.런타임에 대문자로 변환하거나 대소문자를 구분하지 않는 비교를 수행하여 이러한 작업을 수행할 수 있지만 컴파일 시에는 위의 유형이 유용할 수 있습니다.

사용 예는 다음과 같습니다.

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"이 경우에는

좋아요, 도움이 되길 바랍니다.행운을 빌어요.

요청된 유형은 아니지만 열거형이 괜찮다면 다음을 대소문자를 구분하지 않는 열거형 문자열 값 일치에 사용할 수 있습니다.

/**
 * 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];
}

물론, 이것은 가능한 값을 루프하기 때문에 구성 파일과 같은 것들만 처리하기 위한 것입니다. 모든 코드는 정말로 사용해야 합니다.enum대신 값을 입력합니다.

일부 테스트:

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/);
    });
});

언급URL : https://stackoverflow.com/questions/43677527/typescript-type-ignore-case

반응형