본문 바로가기

개발하자

Typescript d.ts 파일에 정의된 인터페이스 속성 유형을 재정의하는 중

반응형

Typescript d.ts 파일에 정의된 인터페이스 속성 유형을 재정의하는 중

in type 스크립트에 정의된 인터페이스 속성 유형을 변경할 수 있는 방법이 있습니까?

예: 의 인터페이스는 다음과 같이 정의됩니다.

interface A {
  property: number;
}

내가 쓰는 스크립트 파일 형식에서 변경하고 싶다.

interface A {
  property: Object;
}

아니면 이마저도 효과가 있을 것이다.

interface B extends A {
  property: Object;
}

이 접근법이 효과가 있을까요? 내가 시스템을 사용해봤을 때 작동하지 않았다. 그게 가능한지 확인하고 싶어서요?




기존 속성의 유형을 변경할 수 없습니다.

속성을 추가할 수 있습니다.

interface A {
    newProperty: any;
}

그러나 기존 유형의 변경:

interface A {
    property: any;
}

오류가 발생합니다.

후속 변수 선언의 형식은 동일해야 합니다. 변수 'property'는 'number' 유형이어야 하지만 여기에는 'any' 유형이 있습니다.

물론 기존 인터페이스를 확장하는 자신만의 인터페이스를 가질 수 있다. 이 경우 다음과 같이 유형을 호환되는 유형으로만 재정의할 수 있습니다.

interface A {
    x: string | number;
}

interface B extends A {
    x: number;
}

그나저나, 당신은 아마도 타입으로 사용하는 것을 피하고, 타입을 사용해야 할 것이다.

다음과 같이 명시되어 있습니다.

모든 유형은 기존 JavaScript에서 작업할 수 있는 강력한 방법으로 컴파일 중에 형식 검사를 점차적으로 선택 및 해제할 수 있습니다.아웃할 수 있습니다. 오브젝트가 다른 언어에서와 유사한 역할을 수행할 것으로 예상할 수 있습니다. :

let notSure: any = 4;
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)

let prettySure: Object = 4;
prettySure.toFixed(); // Error: Property 'toFixed' doesn't exist on type 'Object'.



같은 사건을 해결할 가능성을 조사하느라 하루를 보내는 게 웃기네요. 나는 이렇게 하는 것이 불가능하다는 것을 알았다.

// a.ts - module
export interface A {
    x: string | any;
}

// b.ts - module
import {A} from './a';

type SomeOtherType = {
  coolStuff: number
}

interface B extends A {
    x: SomeOtherType;
}

원인 A 모듈은 당신의 응용 프로그램에서 사용 가능한 모든 유형을 알지 못할 수 있다. 그리고 모든 곳에서 이런 코드를 하는 것은 꽤 지루한 항구입니다.

export interface A {
    x: A | B | C | D ... Million Types Later
}

나중에 유형을 정의해야 자동 완성이 잘 됩니다.


그래서 너는 조금 속일 수 있다:

// a.ts - module
export interface A {
    x: string;
}

재정의할 필요가 없는 경우 자동 완성 작업을 허용하는 일부 유형을 기본적으로 남겨두었습니다.

그리고나서

// b.ts - module
import {A} from './a';

type SomeOtherType = {
  coolStuff: number
}

// @ts-ignore
interface B extends A {
    x: SomeOtherType;
}

깃발을 사용하여 우리가 잘못하고 있다고 말하는 어리석은 예외를 여기서 비활성화하십시오. 그리고 웃긴 것은 모든 것이 예상대로 작동한다는 것이다.

내 경우, 나는 유형의 범위 시야를 줄이고, 그것은 내가 코드를 더 엄격하게 할 수 있게 해준다. 예를 들어, 당신은 100개의 부동산 목록을 가지고 있고 바보 같은 상황을 피하기 위해 그것을 10개로 줄인다.




필드를 먼저 필터링한 다음 결합하는 방법을 사용합니다.

언급

interface A {
    x: string
}

export type B = Omit<A, 'x'> & { x: number };

인터페이스의 경우:

interface A {
    x: string
}

interface B extends Omit<A, 'x'> {
  x: number
}



@zSkycat의 응답을 조금 확장하면 두 개체 유형을 수락하고 두 번째 개체 유형이 첫 번째 멤버를 재정의하는 병합 유형을 반환하는 제네릭을 만들 수 있습니다.

type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type Merge<M, N> = Omit<M, Extract<keyof M, keyof N>> & N;

interface A {
    name: string;
    color?: string;
}

// redefine name to be string | number
type B = Merge<A, {
    name: string | number;
    favorite?: boolean;
}>;

let one: A = {
    name: 'asdf',
    color: 'blue'
};

// A can become B because the types are all compatible
let two: B = one;

let three: B = {
    name: 1
};

three.name = 'Bee';
three.favorite = true;
three.color = 'green';

// B cannot become A because the type of name (string | number) isn't compatible
// with A even though the value is a string
// Error: Type {...} is not assignable to type A
let four: A = three;



 type ModifiedType = Modify<OriginalType, {
  a: number;
  b: number;
}>
 
interface ModifiedInterface extends Modify<OriginalType, {
  a: number;
  b: number;
}> {}

솔루션에서 영감을 받아 다음과 같이 생각해냈습니다.

type Modify<T, R> = Omit<T, keyof R> & R;

// before typescript@3.5
type Modify<T, R> = Pick<T, Exclude<keyof T, keyof R>> & R

예:

interface OriginalInterface {
  a: string;
  b: boolean;
  c: number;
}

type ModifiedType  = Modify<OriginalInterface , {
  a: number;
  b: number;
}>

// ModifiedType = { a: number; b: number; c: number; }

단계별 진행:

type R0 = Omit<OriginalType, 'a' | 'b'>        // { c: number; }
type R1 = R0 & {a: number, b: number }         // { a: number; b: number; c: number; }

type T0 = Exclude<'a' | 'b' | 'c' , 'a' | 'b'> // 'c'
type T1 = Pick<OriginalType, T0>               // { c: number; }
type T2 = T1 & {a: number, b: number }         // { a: number; b: number; c: number; }

TypeScript 유틸리티 유형


심층 수정 v3

interface Original {
  a: {
    a: string
    b: { a: string }
    c: string
    d: string         // <- keep this one 
  }
}

interface Overrides {
  a: {
    a: { a: number }  // <- overwrite string with object
    b: number         // <- overwrite object with number
    c: number         // <- overwrite string with number
    e: number         // <- new property
  }
}

type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
const example: ModifiedType = {
  a: {
    a: { a: number },
    b: number,
    c: number,
    d: string,
    e: number,
  }
}

찾아내다.




이를 위해 다른 사용자가 일반 유틸리티 유형이 필요한 경우 다음과 같은 솔루션을 생각해냈습니다.

/**
 * Returns object T, but with T[K] overridden to type U.
 * @example
 * type MyObject = { a: number, b: string }
 * OverrideProperty<MyObject, "a", string> // returns { a: string, b: string }
 */
export type OverrideProperty<T, K extends keyof T, U> = Omit<T, K> & { [P in keyof Pick<T, K>]: U };

저는 이것이 필요했습니다. 왜냐하면 제 경우, 재정의하는 열쇠는 제네릭 그 자체였기 때문입니다.

준비가 안 된 경우, 을 참조하십시오.




속성 유형을 좁히기 위해 다음과 같이 간단한 작업이 완벽합니다.

interface A {
    x: string | number;
}

interface B extends A {
    x: number;
}

확폭 또는 일반적인 유형의 경우 다음을 수행할 수 있습니다.

interface A {
    x: string
}

export type B = Omit<A, 'x'> & { x: number };

그러나 인터페이스가 일반 인터페이스를 확장하는 경우 을(를) 사용할 때 나머지 속성의 사용자 지정 유형이 손실됩니다.

예.

interface A extends Record<string | number, number | string | boolean> {
    x: string;
    y: boolean;
}

export type B = Omit<A, 'x'> & { x: number };

let b: B = { x: 2, y: "hi" }; // no error on b.y! 

그 이유는, 내부적으로는 우리의 경우에 일반적일 키들만을 검토하기 때문입니다. 따라서 에서는 의 유형을 가진 모든 추가 속성을 허용하고 사용할 수 있습니다.


이를 해결하기 위해 다음과 같은 다른 유틸리티 유형을 고안했습니다.

type OverrideProps<M, N> = { [P in keyof M]: P extends keyof N ? N[P] : M[P] };

예:

type OverrideProps<M, N> = { [P in keyof M]: P extends keyof N ? N[P] : M[P] };

interface A extends Record<string | number, number | string | boolean> {
    x: string;
    y: boolean;
}

export type B = OverrideProps<A, { x: number }>;

let b: B = { x: 2, y: "hi" }; // error: b.y should be boolean!



인터페이스 확장 시 속성:

interface A {
  a: number;
  b: number;
}

interface B extends Omit<A, 'a'> {
  a: boolean;
}



이전 답변이 작성되었을 때 이 답변에서 사용하는 구문을 사용할 수 있었는지 확실하지 않지만


이 항목(인터페이스 속성 덮어쓰기)과 관련된 몇 가지 문제가 있으며, 이 문제를 처리하는 방법은 다음과 같습니다.

  1. 먼저 사용할 수 있는 유형을 사용하여 일반 인터페이스를 만듭니다.

에서 볼 수 있듯이 일반 매개 변수에 대한 값 선택을 사용할 수도 있습니다.

type SOME_OBJECT = { foo: "bar" }

interface INTERFACE_A <T extends number | SOME_OBJECT = number> {
  property: T;
}
  1. 그런 다음 일반 매개 변수에 값을 전달하여(또는 생략하고 기본값을 사용하여) 해당 계약에 따라 새 유형을 생성할 수 있습니다.
type A_NUMBER = INTERFACE_A;                   // USES THE default = number TYPE. SAME AS INTERFACE_A<number>
type A_SOME_OBJECT = INTERFACE_A<SOME_OBJECT>  // MAKES { property: SOME_OBJECT }

그리고 그 결과는 다음과 같습니다.

const aNumber: A_NUMBER = {
    property: 111  // THIS EXPECTS A NUMBER
}

const anObject: A_SOME_OBJECT = {
    property: {   // THIS EXPECTS SOME_OBJECT
        foo: "bar"
    }
}

타이프스크립트 놀이터




나처럼 게으른 사람들을 위한 짧은 대답:

type Overrided = Omit<YourInterface, 'overrideField'> & { overrideField: <type> }; 
interface Overrided extends Omit<YourInterface, 'overrideField'> {
  overrideField: <type>
}



날짜: 2021년 3월 19일 나는 최신 타입스크립트(4.1.2) 버전이 파일의 오버라이드를 지원하고 있다고 생각한다.

// in test.d.ts

interface A {
  a: string
}

export interface B extends A {
  a: number
}

// in any ts file
import { B } from 'test.d.ts'

// this will work
const test: B = { a: 3 }

// this will not work
const test1: B = { a: "3" }




기존 속성의 유형만 수정하고 제거하지 않으려는 경우 다음과 같이 하면 됩니다.

// Style that accepts both number and percent(string)
type BoxStyle = {
  height?: string | number,
  width?: string | number,
  padding?: string | number,
  borderRadius?: string | number,
}

// These are both valid
const box1: BoxStyle = {height: '20%', width: '20%', padding: 0, borderRadius: 5}
const box2: BoxStyle = {height: 85, width: 85, padding: 0, borderRadius: 5}

// Override height and width to be only numbers
type BoxStyleNumeric = BoxStyle & {
  height?: number,
  width?: number,
}

// This is still valid
const box3: BoxStyleNumeric = {height: 85, width: 85, padding: 0, borderRadius: 5}

// This is not valid anymore
const box4: BoxStyleNumeric = {height: '20%', width: '20%', padding: 0, borderRadius: 5}



유틸리티 유형 솔루션을 확장하여 의 키를 에 있는 키로 제한하고 IntelliSense를 추가합니다.

export type Modify<T, R extends Partial<Record<keyof T, any>>> = Omit<T, keyof R> & R;



를 기반으로 사용하기 편리하고 코드의 의도를 명확하게 설명하는 추상화된 제네릭 유형을 생성할 수 있습니다.

type Override<T, K extends keyof T, N> = Omit<T, K> & { [K1 in K]: N };

여기서:

  • = 현존형
  • = 재정의할 유형의 키
  • = 재정의할 기존 유형의 키에 대한 새 유형

사용 예:

type GraphQLCodegenConfig = Override<CodegenConfig, 'schema', DocumentNode>;




수정자 유형 만들기

type Modify<T, R extends {[P in keyof T]:any} > = Omit<T, keyof R> & R;

그리고 할 수 있다.

interface ModifiedInterface extends Modify<OriginalType, {
  a: number;
  b: number;
}> {}

자동 완성 유형을 제공합니다.




다음 유형의 별칭을 사용할 수 있습니다.

type Override<T, K extends { [P in keyof T]: any } | string> =
  K extends string
    ? Omit<T, K>
    : Omit<T, keyof K> & K;

및 아래 구문에서 동일하게 사용합니다.

글로벌 인터페이스

interface IFirst {
    username: string;
}

올바른 이름으로 인터페이스

interface ISecond extends Override<IFirst, 'username'> {
    username: number;
}

별칭을 입력합니다.

type IThird = Override<IFirst, { username: boolean }>;



심층 수정 v3

*

interface Original {
  a: {
    a: string
    b: { a: string }
    c: string
    d: string         // <- keep this one 
  }
}

interface Overrides {
  a: {
    a: { a: number }  // <- overwrite string with object
    b: number         // <- overwrite object with number
    c: number         // <- overwrite string with number
    e: number         // <- new property
  }
}

type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}
const example: ModifiedType = {
  a: {
    a: { a: number },
    b: number,
    c: number,
    d: string,
    e: number,
  }
}
type ModifyDeep<A, B extends DeepPartialAny<A>> = {
  [K in keyof A | keyof B]:          // For all keys in A and B:
    K extends keyof A                // ───┐
      ? K extends keyof B            // ───┼─ key K exists in both A and B
        ? A[K] extends AnyObject     //    │  ┴──┐
          ? B[K] extends AnyObject   //    │  ───┼─ both A and B are objects
            ? ModifyDeep<A[K], B[K]> //    │     │  └─── We need to go deeper (recursively)
            : B[K]                   //    │     ├─ B is a primitive 🠆 use B as the final type (new type)
          : B[K]                     //    │     └─ A is a primitive 🠆 use B as the final type (new type)  
        : A[K]                       //    ├─ key only exists in A 🠆 use A as the final type (original type)   
      : B[K]                         //    └─ key only exists in B 🠆 use B as the final type (new type)
}

type AnyObject = Record<string, any>

// This type is here only for some intellisense for the overrides object
type DeepPartialAny<T> = {
  /** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
  [P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}

참고: type은 단지 type 힌트를 위한 것이지만 완벽합니다. 기술적으로, 이 유형의 논리는 리프 노드를 객체로 대체하고 그 반대도 가능하지만, 이와 같은 오류가 있는 플랫 프리미티브를 재정의할 때 불평할 것이다.

Type 'number' has no properties in common with type 'DeepPartialAny<{ a: string; }>'

그러나 오류를 안전하게 무시하거나 제약 조건을 모두 제거할 수 있습니다. 결과 유형이 올바르게 계산됩니다.

type ModifyDeep<A, B extends DeepPartialAny<A>> = {
  [K in keyof A | keyof B]:
    K extends keyof A
      ? K extends keyof B
        ? A[K] extends AnyObject
          ? B[K] extends AnyObject
            ? ModifyDeep<A[K], B[K]>
            : B[K
          : B[K
        : A[K
      : B[K
}

type AnyObject = Record<string, any>

type DeepPartialAny<T> = {
  /** Makes each property optional and turns each leaf property into any, allowing for type overrides by narrowing any. */
  [P in keyof T]?: T[P] extends AnyObject ? DeepPartialAny<T[P]> : any
}


interface Original {
  a: {
    a: string
    b: { a: string }
    c: { a: string }
  }
  b: string
  c: { a: string }
}

interface Overrides {
  a: {
    a: { a: number }  // <- overwrite string with object
    b: number         // <- overwrite object with number
    c: { b: number }  // <- add new child property
    d: number         // <- new primitive property
  }
  d: { a: number }    // <- new object property
}

type ModifiedType = ModifyDeep<Original, Overrides>
interface ModifiedInterface extends ModifyDeep<Original, Overrides> {}

const t: ModifiedType = {
  a: {
    a: { a: number },
    b: number,
    c: { a: string, b: number },
    d: number,
  }, 
  b: string,
  c: { a: string },
  d: { a: number },
}



declare const number: number
declare const string: string
declare const unknown: unknown




중첩된 인터페이스를 쉽게 재정의할 수 있도록 다음과 같은 유형을 만들었습니다.

export type DeepPartialAny<T> = {
  [P in keyof T]?: T[P] extends Obj ? DeepPartialAny<T[P]> : any;
};

export type Override<A extends Obj, AOverride extends DeepPartialAny<A>> = { [K in keyof A]:
  AOverride[K] extends never
    ? A[K]
    : AOverride[K] extends Obj
    ? Override<A[K], AOverride[K]>
    : AOverride[K]
};

그런 다음 다음과 같이 사용할 수 있습니다.

interface Foo {
  Bar: {
    Baz: string;
  };
}
type Foo2 = Override<Foo, { Bar: { Baz: number } }>;

const bar: Foo2['Bar']['Baz'] = 1; // number;



더 나은 해결책은 아래의 수정된 유형의 답변을 사용하는 것입니다(펀 의도)을 사용하는 것입니다.

export type Modify<T, R extends Partial<T>> = Omit<T, keyof R> & R;

이렇게 하면 덮어쓰는 키가 원래 인터페이스에도 있는지 확인할 수 있으므로 원래 인터페이스가 이름을 변경할 경우 컴파일 시간 오류가 발생하고 이름도 변경해야 합니다.

설명:

다음 예를 들어보자.

interface OriginalInterface {
    id: string
}

그리고 수정된 유형은 아래와 같습니다.

interface ModifiedInterface {
    id: number
}

자, 미래에는 's'가 's'로 이름이 바뀌면 my type 유틸리티를 사용하면 다음과 같은 오류가 발생할 것입니다.

interface ModifiedInterface {
    id: number // Type '{ geo_point1: GeoPoint | null; }' has no properties in common with type 'Partial<Address>'.ts(2559)
}

반응형