스크립트 유형, 개체 유형 병합?
두 개의 일반 객체 유형의 소품을 병합할 수 있습니까?다음과 유사한 기능이 있습니다.
function foo<A extends object, B extends object>(a: A, b: B) {
return Object.assign({}, a, b);
}
저는 A에 있는 모든 속성 중 B에 없는 속성을 B에 있는 모든 속성을 타입으로 하고 싶습니다.
merge({a: 42}, {b: "foo", a: "bar"});
의 다소 이상한 유형을 제공합니다.{a: number} & {b: string, a: string}
,a
그래도 문자열입니다.실제 반품은 정확한 유형을 제공하지만, 어떻게 명시적으로 작성해야 할지 모르겠습니다.
TS4.1+용 업데이트
원래 답변은 여전히 작동하지만(설명이 필요하면 읽어보셔야 합니다), 이제 재귀적 조건부 유형이 지원되므로, 우리는 쓸 수 있습니다.merge()
변수가 될 경우:
type OptionalPropertyNames<T> =
{ [K in keyof T]-?: ({} extends { [P in K]: T[K] } ? K : never) }[keyof T];
type SpreadProperties<L, R, K extends keyof L & keyof R> =
{ [P in K]: L[P] | Exclude<R[P], undefined> };
type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never
type SpreadTwo<L, R> = Id<
& Pick<L, Exclude<keyof L, keyof R>>
& Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>>
& Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>>
& SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>
>;
type Spread<A extends readonly [...any]> = A extends [infer L, ...infer R] ?
SpreadTwo<L, Spread<R>> : unknown
type Foo = Spread<[{ a: string }, { a?: number }]>
function merge<A extends object[]>(...a: [...A]) {
return Object.assign({}, ...a) as Spread<A>;
}
테스트할 수 있습니다.
const merged = merge(
{ a: 42 },
{ b: "foo", a: "bar" },
{ c: true, b: 123 }
);
/* const merged: {
a: string;
b: number;
c: boolean;
} */
원답
의 TypeScript 표준 라이브러리 정의에 의해 생성된 교차 유형은 나중의 인수에 이전 인수와 동일한 이름의 속성이 있는 경우 발생하는 작업을 제대로 나타내지 않는 근사치입니다.하지만, 아주 최근까지, 이것은 당신이 타입스크립트의 타입 시스템에서 할 수 있는 최선이었습니다.
그러나 TypeScript 2.8에서 조건부 유형을 소개하는 것부터 시작하여 보다 가까운 근사치를 사용할 수 있습니다.이러한 개선 사항 중 하나는 유형 함수를 사용하는 것입니다.Spread<L,R>
다음과 같이 정의됩니다.
// Names of properties in T with types that include undefined
type OptionalPropertyNames<T> =
{ [K in keyof T]: undefined extends T[K] ? K : never }[keyof T];
// Common properties from L and R with undefined in R[K] replaced by type in L[K]
type SpreadProperties<L, R, K extends keyof L & keyof R> =
{ [P in K]: L[P] | Exclude<R[P], undefined> };
type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never // see note at bottom*
// Type of { ...L, ...R }
type Spread<L, R> = Id<
// Properties in L that don't exist in R
& Pick<L, Exclude<keyof L, keyof R>>
// Properties in R with types that exclude undefined
& Pick<R, Exclude<keyof R, OptionalPropertyNames<R>>>
// Properties in R, with types that include undefined, that don't exist in L
& Pick<R, Exclude<OptionalPropertyNames<R>, keyof L>>
// Properties in R, with types that include undefined, that exist in L
& SpreadProperties<L, R, OptionalPropertyNames<R> & keyof L>
>;
)를 사용Exclude
대신 표준 Diff
요.Spread
no-op으로 합니다.Id
: .) type)의 경우에는 다음과 같이 합니다.
사용해 보겠습니다.
function merge<A extends object, B extends object>(a: A, b: B) {
return Object.assign({}, a, b) as Spread<A, B>;
}
const merged = merge({ a: 42 }, { b: "foo", a: "bar" });
// {a: string; b: string;} as desired
은 그것을 볼 수 .a
에서 출에서이인식됩다니게르올바제로 올바르게 됩니다.string
에 string & number
야호!
그러나 이는 여전히 근사치입니다.
Object.assign()
열거 가능한 복사본, 소유 속성만 있고 형식 시스템은 필터링할 속성의 열거 가능성과 소유권을 나타내는 방법을 제공하지 않습니다.는 의미입니다.merge({},new Date())
은 유처럼보것일입다니형▁like▁type처럼 보일 입니다.Date
시 TypeScript가 , " "는 TypeScript로 변환됩니다.Date
이고 출력은 기본적으로 메드는복출기본로으적입니다.{}
이것은 현재로서는 어려운 한계입니다.으로, 으로적는의 의정의가.
Spread
누락된 속성과 정의되지 않은 값을 가진 속성을 구분하지 않습니다.그렇게merge({ a: 42}, {a: undefined})
를 잘못 했습니다.{a: number}
로 할 때{a: undefined}
이 문제는 아마도 재정의함으로써 해결될 수 있습니다.Spread
100% 확신할 수는 없습니다그리고 대부분의 사용자에게 필요하지 않을 수 있습니다. (편집: 이것은 정의를 재정의하여 수정할 수 있습니다.type OptionalPropertyNames<T> = { [K in keyof T]-?: ({} extends { [P in K]: T[K] } ? K : never) }[keyof T]
)유형 시스템은 모르는 속성으로는 아무것도 할 수 없습니다.
declare const whoKnows: {}; const notGreat = merge({a: 42}, whoKnows);
은 "" " " " " " 입니다.{a: number}
할 때, 만약 일시파만라면, 그나약러.whoKnows
공교롭게도{a: "bar"}
((으)로{}
), 그러면notGreat.a
런타임에는 문자열이지만 컴파일 시간에는 숫자입니다.
하세요; 러니주세요하것타; 하는핑의이그▁of것▁typing▁so를 타이핑합니다.Object.assign()
교로또교서로차로는차로Spread<>
일종의 "최선의" 것이고, 가장자리 사건에서 당신을 오해하게 만들 수 있습니다.
*참고:Id<T>
ID 유형이므로 원칙적으로 해당 유형에 대해 어떤 작업도 수행해서는 안 됩니다.누군가가 이 답변을 삭제하고 다음으로 바꾸기 위해 이 답변을 편집했습니다.T
교차로를를 통해 .교차로를 제거하기 위해 키를 통해 반복하는 것입니다.비교:
type Id<T> = T extends infer U ? { [K in keyof U]: U[K] } : never
type Foo = { a: string } & { b: number };
type IdFoo = Id<Foo>; // {a: string, b: number }
를 IdFoo
교차점이 제거되고 두 성분이 단일 유형으로 병합된 것을 볼 수 있습니다.다시 말하지만, 사이에 실질적인 차이는 없습니다.Foo
그리고.IdFoo
할당 가능성 측면에서; 단지 후자가 어떤 상황에서는 읽기 쉽다는 것입니다.
업데이트:
re-gor의 의견 덕분에 나는 이것을 다시 검토했고 병합에 대해 더 명확하게 하기 위해 구문을 업데이트했습니다.
type Merge<A, B> = {
[K in keyof A | keyof B]:
K extends keyof A & keyof B
? A[K] | B[K]
: K extends keyof B
? B[K]
: K extends keyof A
? A[K]
: never;
};
를 keyof
에서 의로교원까지.A
그리고.B
개별적으로 모든 키가 노출됩니다.
유형을 가 중된차 3유사용키다가있먼다확니합인에 있는지 합니다.A
그리고.B
그리고 그것이 있을 때, 유형은.A[K] | B[K]
.
다음으로, 키가 다음에서만 올 때B
유형은 그다면종류는렇종▁is는▁then류입니다.B[K]
.
다음으로, 키가 다음에서만 올 때A
유형은 그다면종류는렇종▁is는▁then류입니다.A[K]
.
마지막으로 키는 어느 쪽에도 존재하지 않습니다.A
도 아니다B
유형은 고종류는그리종는류▁is▁and.never
.
type X = Merge<{ foo: string; bar: string }, { bar: number }>;
>>> type X = { foo: string; bar: string | number; }
이전:
두 개체의 모든 속성을 병합하는 형식을 선언하는 구문을 찾았습니다.
type Merge<A, B> = { [K in keyof (A | B)]: K extends keyof B ? B[K] : A[K] };
이 유형을 사용하면 A와 B 두 개체를 지정할 수 있습니다.
이러한 개체에서 사용 가능한 키에서 파생된 키를 가진 매핑된 유형이 생성됩니다.키는 다음에서 가져옵니다.keyof (A | B)
.
그런 다음 소스에서 적절한 유형을 찾아 각 키를 해당 키 유형에 매핑합니다.가 키가제는경우되에서 오는 .B
그런 다음 유형은 해당 키의 유형입니다.B
이 작업은 다음과 같이 수행됩니다.K extends keyof B ?
이 부분은 다음과 같은 질문을 합니다.K
에의열쇠에서 온 B
, ? 해당키가려면오져을유형의,면▁?려,K
조회를 합니다.B[K]
.
가 키가원 ▁from의 것이 B
에서 온 것이 틀림없습니다A
이렇게 해서 3항성이 완성됩니다.
K extends keyof B ? B[K] : A[K]
것은 으로 감싸여 있습니다.{ }
키가 두 개체에서 파생되고 유형이 소스 유형에 매핑되는 매핑된 개체 유형으로 만듭니다.
속성 순서를 보존하려면 다음 솔루션을 사용하십시오.
여기에서 실제로 확인하십시오.
export type Spread<L extends object, R extends object> = Id<
// Merge the properties of L and R into a partial (preserving order).
Partial<{ [P in keyof (L & R)]: SpreadProp<L, R, P> }> &
// Restore any required L-exclusive properties.
Pick<L, Exclude<keyof L, keyof R>> &
// Restore any required R properties.
Pick<R, RequiredProps<R>>
>
/** Merge a property from `R` to `L` like the spread operator. */
type SpreadProp<
L extends object,
R extends object,
P extends keyof (L & R)
> = P extends keyof R
? (undefined extends R[P] ? L[Extract<P, keyof L>] | R[P] : R[P])
: L[Extract<P, keyof L>]
/** Property names that are always defined */
type RequiredProps<T extends object> = {
[P in keyof T]-?: undefined extends T[P] ? never : P
}[keyof T]
/** Eliminate intersections */
type Id<T> = { [P in keyof T]: T[P] }
tl;dr
type Expand<T> = T extends object
? T extends infer O
? { [K in keyof O]: O[K] }
: never
: T;
type UnionToIntersection<U> = Expand<
(U extends any ? (k: U) => void : never) extends (k: infer I) => void
? I
: never
>;
const merge = <A extends object[]>(...a: [...A]) => {
return Object.assign({}, ...a) as UnionToIntersection<A[number]>;
};
또 다른 대답에 대한 동기 부여
jcalz 답변은 좋고 저를 위해 수년간 일했습니다.그러나 병합된 개체 수가 특정 숫자로 증가하면 유형 스크립트에서 다음 오류가 발생합니다.
Type instantiation is excessively deep and possibly infinite. [2589]
결과 개체 유형을 추론하지 못합니다.이 문제는 다음 github 문제에서 과도하게 논의된 유형 스크립트 문제 때문에 발생합니다. https://github.com/microsoft/TypeScript/issues/34933
세부 사항
merge()
위의 A[number]
typestype의 됩니다. UnionToIntersection
메타함수는 유니언을 교차로로 변환합니다. Expand
교차로를 평평하게 만들어 IntelliSense와 같은 도구로 보다 쉽게 읽을 수 있도록 합니다.
자한내다용참참오시십조하조에 대한 자세한 하십시오.UnionToIntersection
그리고.Expand
구현:
https://.com/a/50375286/5000057https ://stackoverflow.com/a/50375286/5000057
https://github.com/shian15810/://github.com/shian15810/type-expand
추가의
을 할 때merge()
함수입니다. 병합된 개체의 키 중복이 오류일 수 있습니다.이러한 과 중복을 과 같은 함수를 할 수 .throw Error
:
export const mergeAssertUniqueKeys = <A extends object[]>(...a: [...A]) => {
const entries = a.reduce((prev, obj) => {
return prev.concat(Object.entries(obj));
}, [] as [string, unknown][]);
const duplicates = new Set<string>();
entries.forEach((pair, index) => {
if (entries.findIndex((p) => p[0] === pair[0]) !== index) {
duplicates.add(pair[0]);
}
});
if (duplicates.size > 0) {
throw Error(
[
'objects being merged contain following key duplicates:',
`${[...duplicates].join(', ')}`,
].join(' '),
);
}
return Object.assign({}, ...a) as UnionToIntersection<A[number]>;
};
내 생각에 당신은 더 많은 조합을 찾고 있는 것 같습니다.|
(으)로 입력합니다.&
에 더 원하는 것에 더 가깝습니다.
function merge<A, B>(a: A, b: B): A | B {
return Object.assign({}, a, b)
}
merge({ a: "string" }, { a: 1 }).a // string | number
merge({ a: "string" }, { a: "1" }).a // string
TS를 배우면서 이 페이지로 돌아오는 데 많은 시간을 보냈습니다.그것은 좋은 읽을거리이고 (만약 당신이 그런 종류의 것에 관심이 있다면) 많은 유용한 정보를 줍니다.
저도 찾고 있었기 때문에 조금 더 간단하게 했습니다.단계별로 공유하고 설명해 드리겠습니다.
- 을 합니다.
A
병합 유형의 기준으로 입력합니다. - 키가오기의 .
B
에 것A
(제외와 같은 유틸리티 유형이 도움이 됩니다. - 마지막으로, 단계에서 유형을 교차시킵니다.
#1
그리고.#2
와 함께&
결합된 유형을 가져옵니다.
type Merge<A, B> = A & { [K in Exclude<keyof B, keyof A>]: B[K] };
이것이 조금 더 낫습니다.
type Merge<A extends {}, B extends {}> = {
[K in keyof (A & B)]: (
K extends keyof B
? B[K]
: (K extends keyof A ? A[K] : never)
)
};
운동장에서 차이를 확인할 수 있습니다.
아마 모든 답이 맞겠지만, 저는 더 짧은 것을 만들었습니다.Merge
일반적으로 필요한 작업을 수행합니다.
type Merge<T, K> = Omit<T, keyof K> & K;
언급URL : https://stackoverflow.com/questions/49682569/typescript-merge-object-types
'source' 카테고리의 다른 글
오류: 기능이 제대로 배포되지 않았습니다. (0) | 2023.06.26 |
---|---|
SQL 쿼리를 사용하여 쉼표로 구분된 목록을 만들려면 어떻게 해야 합니까? (0) | 2023.06.26 |
세션을 종료하지 않고 오라클에서 단일 쿼리를 종료할 수 있습니까? (0) | 2023.06.26 |
Git 저장소에 있는 모든 태그를 생성된 날짜까지 나열하려면 어떻게 해야 합니까? (0) | 2023.06.26 |
Ruby, exec, 시스템 및 %x() 또는 Backticks 간의 차이 (0) | 2023.06.26 |