반대하지 않는 이유.키가 TypeScript에서 유형의 키를 반환합니까?
제목이 모든 것을 말해준다 - 왜 타입스크립트에서 타입을 반환하지 않는가? 그것이 바로 그런 것이기 때문에, 반환 유형을 단순히 그렇게 만들지 않는 것이 타입스크립트 정의 파일 작성자들의 명백한 실수인 것 같다.
GitHub repo에 버그를 기록해야 할까요, 아니면 그냥 PR을 보내서 수정해야 할까요?
작업 중인 개체에 추가 속성이 없다고 확신하는 경우의 해결 방법은 다음과 같습니다:
const obj = {a: 1, b: 2}
const objKeys = Object.keys(obj) as Array<keyof typeof obj>
// objKeys has type ("a" | "b")[]
다음과 같은 경우 함수에 추출할 수 있습니다:
const getKeys = <T>(obj: T) => Object.keys(obj) as Array<keyof T>
const obj = {a: 1, b: 2}
const objKeys = getKeys(obj)
// objKeys has type ("a" | "b")[]
보너스로, 다음에서 인출:
type Entries<T> = {
[K in keyof T]: [K, T[K]]
}[keyof T][]
function entries<T>(obj: T): Entries<T> {
return Object.entries(obj) as any;
}
현재 반환 유형()은 의도적입니다. 이유는 무엇입니까?
다음과 같은 유형을 고려하십시오:
interface Point {
x: number;
y: number;
}
다음과 같은 코드를 작성합니다:
function fn(k: keyof Point) {
if (k === "x") {
console.log("X axis");
} else if (k === "y") {
console.log("Y axis");
} else {
throw new Error("This is impossible");
}
}
질문 하나 하죠:
제대로 된 유형의 프로그램에서 오류 사례를 처리하기 위한 법적 요청이 가능한가요?
물론 대답은 "아니오"입니다. 하지만 이게 무슨 상관이야?
이제 이 코드를 고려해 보십시오:
interface NamedPoint extends Point {
name: string;
}
const origin: NamedPoint = { name: "origin", x: 0, y: 0 };
TypeScript의 형식 시스템에 따르면 모든 s는 유효합니다.
이제 다음과 같이 쓰자:
function doSomething(pt: Point) {
for (const k of Object.keys(pt)) {
// A valid call if Object.keys(pt) returns (keyof Point)[]
fn(k);
}
}
// Throws an exception
doSomething(origin);
우리의 잘 짜여진 프로그램은 예외를 던졌다!
여기 뭔가 잘못됐어요! 에서 되돌아옴으로써, 우리는 완전한 목록을 형성하는 가정을 위반했습니다. 왜냐하면 객체에 대한 참조가 있다고 해서 그것이 슈퍼 타입의 것이 아니라는 것을 의미하지 않기 때문입니다.
기본적으로 (적어도) 다음 네 가지 중 하나는 사실일 수 없습니다:
- 의 키를 모두 나열한 목록입니다
- 추가 속성이 있는 유형은 항상 기본 유형의 하위 유형입니다
- Subtype 값에 Supertype 참조를 사용하여 별칭을 지정하는 것은 합법적입니다
- 돌아온다
1점을 버리는 것은 거의 쓸모가 없게 만든다. 왜냐하면 그것은 그렇지 않은 어떤 가치일 수도 있기 때문이다.
포인트 2를 버리면 타입스크립트의 타입 시스템이 완전히 파괴된다. 선택 사항이 아니야.
포인트 3을 버리면 타입스크립트의 타입 시스템도 완전히 파괴된다.
4점을 버리는 것은 괜찮고 프로그래머인 당신이 상대하는 객체가 당신이 가지고 있다고 생각하는 것의 하위 유형에 대한 별칭인지 아닌지 생각하게 한다.
이 기능을 만드는 "누락 기능"은 다음과 같습니다. 이 기능을 사용하면 #2 지점의 대상이 아닌 새로운 유형을 선언할 수 있습니다. 만약 이 기능이 존재한다면, 로 선언된 것에 대해서만 반품이 가능할 것이다.
부록: 물론 제네릭?
논평가들은 만약 그 주장이 일반적인 가치라면 그것이 안전하게 돌아올 수 있다고 암시했다. 이것은 여전히 잘못된 것이다. 고려 사항:
class Holder<T> {
value: T;
constructor(arg: T) {
this.value = arg;
}
getKeys(): (keyof T)[] {
// Proposed: This should be OK
return Object.keys(this.value);
}
}
const MyPoint = { name: "origin", x: 0, y: 0 };
const h = new Holder<{ x: number, y: number }>(MyPoint);
// Value 'name' inhabits variable of type 'x' | 'y'
const v: "x" | "y" = (h.getKeys())[0];
또는 명시적인 형식 인수가 필요하지 않은 예제:
function getKey<T>(x: T, y: T): keyof T {
// Proposed: This should be OK
return Object.keys(x)[0];
}
const obj1 = { name: "", x: 0, y: 0 };
const obj2 = { x: 0, y: 0 };
// Value "name" inhabits variable with type "x" | "y"
const s: "x" | "y" = getKey(obj1, obj2);
이것은 이런 유형의 문제에 대한 구글의 최고 히트작이기 때문에, 저는 앞으로 나아가는 데 도움을 주고 싶었습니다.
이러한 방법은 다른 답변/댓글 섹션에서 링크를 찾을 수 있는 다양한 이슈 페이지에 대한 긴 토론에서 주로 사용되었습니다.
그럼, 좀 먹었다고 해봐요:
const obj = {};
Object.keys(obj).forEach((key) => {
obj[key]; // blatantly safe code that errors
});
다음은 앞으로 나아가는 몇 가지 방법입니다:
키가 필요 없고 값만 필요한 경우 키를 반복하는 대신 또는 를 사용합니다.
const obj = {}; Object.values(obj).forEach(value => value); Object.entries(obj).forEach([key, value] => value);
도우미 기능을 만듭니다:
function keysOf<T extends Object>(obj: T): Array<keyof T> { return Array.from(Object.keys(obj)) as any; } const obj = { a: 1; b: 2 }; keysOf(obj).forEach((key) => obj[key]); // type of key is "a" | "b"
유형을 다시 캐스트하십시오(이것은 코드를 많이 다시 쓰지 않아도 되는 데 많은 도움이 됩니다)
const obj = {}; Object.keys(obj).forEach((_key) => { const key = _key as keyof typeof obj; obj[key]; });
이 중에서 가장 고통스럽지 않은 것은 주로 여러분 자신의 프로젝트에 달려 있습니다.
가능한 해결책
const isName = <W extends string, T extends Record<W, any>>(obj: T) =>
(name: string): name is keyof T & W =>
obj.hasOwnProperty(name);
const keys = Object.keys(x).filter(isName(x));
그냥 이렇게 하면 문제가 없어진다
declare global {
interface ObjectConstructor {
keys<T>(o: T): (keyof T)[]
// @ts-ignore
entries<U, T>(o: { [key in T]: U } | ArrayLike<U>): [T, U][]
}
}
나는 // @ts-ignore를 추가했다. 왜냐하면 ts는 나에게 다음을 알려줄 것이기 때문이다:
Type 'T' is not assignable to type 'string | number | symbol
만약 누군가가 T의 역동적인 측면을 보존하는 능력을 잃지 않고 // @ts-ignore를 제거할 수 있는 해결책을 가지고 있다면, 우리에게 댓글로 알려주세요
이로 인해 코드가 손상된 경우 다음 작업을 수행할 수 있습니다:
Object.tsKeys = function getObjectKeys<Obj>(obj: Obj): (keyof Obj)[] {
return Object.keys(obj!) as (keyof Obj)[]
}
// @ts-ignore
Object.tsEntries = function getObjectEntries<U, T>(obj: { [key in T]: U }): [T, U][] {
return Object.entries(obj!) as unknown as [T, U][]
}
declare global {
interface ObjectConstructor {
// @ts-ignore
tsEntries<U, T>(o: { [key in T]: U }): [T, U][]
tsKeys<T>(o: T): (keyof T)[]
}
}
나도 이 문제가 있었고, 이 방법들에 대한 몇 가지 유형 선언을 작성했다.
그러나 이러한 방법은 숫자를 키로 반환하지 않으므로 키를 항상 문자열로 입력할 수 있습니다:
/**
* Converts object keys to their string literal types, preserving type information.
* These methods return string versions of the keys, even when they are numbers:
*
* Object.keys(), Object.entries(), and Object.getOwnPropertyNames()
*
* Note: these functions don't return Symbols, so we don't need to support them.
*/
type ToStringKey<T> = `${Extract<keyof T, string | number>}`;
export const getTypedKeys = Object.keys as <T extends object>(obj: T) => Array<ToStringKey<T>>;
export const getTypedValues = Object.values as <T extends object>(obj: T) => Array<T[keyof T]>;
export const getTypedEntries = Object.entries as <T extends object>(
obj: T
) => Array<[ToStringKey<T>, T[keyof T]]>;
export const getTypedOwnPropertyNames = Object.getOwnPropertyNames as <T extends object>(
obj: T
) => Array<ToStringKey<T>>;
용도:
const myObject = {
a: 1,
2: 'b',
[Symbol(3)]: 'c',
};
const keys = getTypedKeys(myObject);
// const keys: ("a" | "2")[]
편집:
나는 이상한 행동을 발견했기 때문에 원래 작성했던 전역을 함수로 대체했다.
그래서 나는 이것들을 글로벌로 사용하는 것을 추천하지 않는다. 나는 해결책을 찾고 있지만, 아마도 타이핑된 버전을 사용하는 것에 대해 명시적으로 말하는 것이 최선일 것이다.
추가적인 제안이 간절합니다. 이것이 누군가에게 유용하기를 바랍니다 :)
'개발하자' 카테고리의 다른 글
TypeScript는 내보내기 선언 대신 정의된 선언이 있는 .d.ts 파일을 생성합니다 (0) | 2023.04.29 |
---|---|
Float - 눌린 상태의 용기? (0) | 2023.04.29 |
파이썬 스크립트의 테라폼 출력 (0) | 2023.04.28 |
Kubernetes 및 JVM 메모리 설정 (0) | 2023.04.27 |
AKS(Azure Managed Kubernetes)에서 모든 컴퓨팅 중지 (0) | 2023.04.27 |