다음 코드 ( 놀이터 )를 상상해보십시오 .
type AvailableTypes = {
'array': Array<any>;
'string': string;
'object': object;
}
class Wrapper<T extends keyof AvailableTypes> {
// Is either array, string or object
private readonly type: T;
// ERROR: Property 'value' has no initializer and is not definitely assigned in the constructor.
private readonly value: AvailableTypes[T];
constructor(type: T) {
this.type = type;
/**
* ERROR:
* TS2322: Type 'never[]' is not assignable to type 'AvailableTypes[T]'.
* Type 'never[]' is not assignable to type 'never'.
*/
switch (type) {
case 'array':
this.value = [];
break;
case 'string':
this.value = '';
break;
case 'object':
this.value = {};
break;
}
}
}
두 가지 주요 오류가 있습니다.
TS2322 : 'never []'유형은 'AvailableTypes [T]'유형에 할당 할 수 없습니다.
'never []'유형은 'never'유형에 할당 할 수 없습니다.
해도 AvailableTypes[T]
종류의 일에 항상 결의는 선언 AvailableTypes
과 함께, T
그것의 주요 인.
... 그리고
속성 'value'에는 이니셜 라이저가 없으며 생성자에 확실히 할당되지 않았습니다.
하지만 type
필수 및 필요하다 할 수 중 하나 string
, array
또는object.
내가 여기서 무엇을 놓치고 있습니까?
가능한 관련 SO 스레드 :
( @jcalz 답변 업데이트 ) 속성 value
에 따라 유형 검사가 가능해야합니다 type
.
// In the Wrapper class, should work since value can only be an array if type is 'array':
public pushValue(val: unknown) {
if (this.type === 'array') {
this.value.push(val);
}
}
근본적인 문제는 제네릭 형식 매개 변수가 제어 흐름 분석을 통해 좁혀지지 않는다는 것입니다. 이는 TypeScript에서 상당히 오랫동안 열려있는 문제입니다 . 자세한 내용 은 microsoft / TypeScript # 13995 를 참조하세요.
당신이 검사 할 때 type
A의 switch
/의 case
문, 컴파일러는 유형 좁힐 수 있습니다 type
리터럴 형식으로 변수를 "array"
하지만 좁은하지 않는 형식 매개 변수 T
에를 "array"
. 따라서 any[]
유형에 값을 할당하는 것이 안전한지 확인할 수 없습니다 AvailableTypes[T]
. 컴파일러는 현재 수행하지 않는 분석을 수행해야합니다. 예를 들어 "좋아요, if 유형에서 type === "array"
추론 한 다음이 블록 내에서로 범위 를 좁힐 수 있으므로 유형은 is , aka, 등입니다. 할당하는 것이 안전 합니다. " 그러나 이것은 일어나지 않습니다.T
type
case
T
"array"
this.value
AvailableTypes["array"]
any[]
[]
동일한 문제로 인해 " value
확실히 할당되지 않음"오류가 발생합니다. 컴파일러는 여기에서 제어 흐름 분석을 수행하지 않기 때문에 switch
/ case
가 모든 가능성을 소진 하는 것을 볼 수있는 수단 T
이 없습니다.
여기서 가장 쉬운 해결 방법은 유형 어설 션 을 사용 하여 컴파일러에게 확인할 수 없기 때문에 수행중인 작업을 알고 있음을 알리는 것입니다.
완전성 문제를 처리하기 위해 다음 default
과 같이 던지는 케이스를 만들 수 있습니다 .
class Wrapper<T extends keyof AvailableTypes> {
private readonly type: T;
private readonly value: AvailableTypes[T];
constructor(type: T) {
this.type = type;
switch (type) {
case 'array':
this.value = [] as AvailableTypes[T]; // assert
break;
case 'string':
this.value = '' as AvailableTypes[T]; // assert
break;
case 'object':
this.value = {} as AvailableTypes[T]; // assert
break;
default:
throw new Error("HOW DID THIS HAPPEN"); // exhaustive
}
}
}
또는 type
에서 T
로 확장 할 수 있습니다. keyof AvailableTypes
그러면 컴파일러가 케이스가 완전하다는 것을 이해하는 데 필요한 제어 흐름 분석을 수행 할 수 있습니다 .
class Wrapper<T extends keyof AvailableTypes> {
private readonly type: T;
private readonly value: AvailableTypes[T];
constructor(type: T) {
this.type = type;
const _type: keyof AvailableTypes = type; // widen to concrete type
switch (_type) {
case 'array':
this.value = [] as AvailableTypes[T]; // assert
break;
case 'string':
this.value = '' as AvailableTypes[T]; // assert
break;
case 'object':
this.value = {} as AvailableTypes[T]; // assert
break;
}
}
}
(다른 해결 방법 건전성 변화를 구현 사람 코멘트에서 언급은 ) 당신이 값이있는 경우는 사실을 활용하는 t
유형 T
, 및 키 k
유형을 K extends keyof T
다음의 유형 t[k]
으로 컴파일러에 의해 보일 것이다 T[K]
. 우리가 유효한을 할 수 그래서 만약 AvailableTypes
, 우리와 그것에 단지 인덱스 할 수 있습니다 type
. 아마도 다음과 같습니다.
class Wrapper<T extends keyof AvailableTypes> {
private readonly type: T
private readonly value: AvailableTypes[T];
constructor(type: T) {
this.type = type;
const initValues: AvailableTypes = {
array: [],
string: "",
object: {}
};
this.value = initValues[type];
}
}
이것은 타입 단언과 switch
문장 보다 훨씬 더 좋은 방법 이며 부팅하기에 상당히 안전한 방법입니다. 따라서 사용 사례에서 금지하지 않는 한이 솔루션을 사용하겠습니다.
좋아요, 그 중 하나가 도움이되기를 바랍니다. 행운을 빕니다!
업데이트 : 제네릭 클래스 대신 구별 된 공용체
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다