원시 유형을 확장하는 TypeScript에서 공칭 유형을 만드는 방법이 있습니까?
두 칩시다.latitude
그리고.longitude
는 이 인 저는이을기표싶습다로 .number
▁a의 할당을 허용하지 않습니다.longitude
latitude
형식 스크립트의 변수입니다.
하위 분류 방법이 있습니까?number
원고가 이 할당을 불법으로 탐지하도록 원시적입니까?코드가 실패하도록 공칭 타이핑을 강요하는 방법?
var longitude : LongitudeNumber = new LongitudeNumber();
var latitude : LatitudeNumber;
latitude = longitude; // <-- type failure
"원시적 유형을 문자로 확장하는 방법?"에 대한 대답은 저를 올바른 방향으로 이끌 것처럼 보이지만, 저는 다른 종류의 숫자에 대해 뚜렷한 명목 하위 유형을 만들기 위해 그 솔루션을 어떻게 확장해야 할지 확신할 수 없습니다.
제가 원시적인 것을 포장해야 하나요?그렇다면 일반 번호처럼 원활하게 동작하게 하거나 하위 멤버를 참조해야 합니까?어떻게든 타이프스크립트 컴파일 타임 번호 하위 클래스를 만들 수 있습니까?
도우미 유형을 사용하여 유형 스크립트에서 불투명/공칭 유형을 근사화할 수 있습니다.자세한 내용은 다음 답변을 참조하십시오.
// Helper for generating Opaque types.
type Opaque<T, K> = T & { __opaque__: K };
// 2 opaque types created with the helper
type Int = Opaque<number, 'Int'>;
type ID = Opaque<number, 'ID'>;
// works
const x: Int = 1 as Int;
const y: ID = 5 as ID;
const z = x + y;
// doesn't work
const a: Int = 1;
const b: Int = x;
// also works so beware
const f: Int = 1.15 as Int;
다음은 더 자세한 답변입니다: https://stackoverflow.com/a/50521248/20489
이를 위한 다양한 방법에 대한 좋은 기사: https://michalzalecki.com/nominal-typing-in-typescript/
이를 위한 간단한 방법은 다음과 같습니다.
요구 사항들
필요한 기능은 숫자를 숫자 유형으로 변환하는 기능과 반대 공정으로 변환하는 기능 두 가지뿐입니다.다음 두 가지 기능이 있습니다.
module NumberType {
/**
* Use this function to convert to a number type from a number primitive.
* @param n a number primitive
* @returns a number type that represents the number primitive
*/
export function to<T extends Number>(n : number) : T {
return (<any> n);
}
/**
* Use this function to convert a number type back to a number primitive.
* @param nt a number type
* @returns the number primitive that is represented by the number type
*/
export function from<T extends Number>(nt : T) : number {
return (<any> nt);
}
}
사용.
다음과 같이 고유한 번호 유형을 만들 수 있습니다.
interface LatitudeNumber extends Number {
// some property to structurally differentiate MyIdentifier
// from other number types is needed due to typescript's structural
// typing. Since this is an interface I suggest you reuse the name
// of the interface, like so:
LatitudeNumber;
}
다음은 Latitude Number를 사용하는 방법의 예입니다.
function doArithmeticAndLog(lat : LatitudeNumber) {
console.log(NumberType.from(lat) * 2);
}
doArithmeticAndLog(NumberType.to<LatitudeNumber>(100));
로그에 기록됩니다.200
콘솔에
예상대로 이 함수는 숫자 프리미티브나 다른 숫자 유형으로 호출할 수 없습니다.
interface LongitudeNumber extends Number {
LongitudeNumber;
}
doArithmeticAndLog(2); // compile error: (number != LongitudeNumber)
doArithmeticAndLog(NumberType.to<LongitudeNumber>(2)); // compile error: LongitudeNumer != LatitudeNumber
작동 방식
이렇게 하면 Typescript가 단순히 원시 숫자가 실제로 숫자 인터페이스(숫자 유형이라고 함)의 확장이라고 믿도록 속이는 반면, 원시 숫자는 실제로 숫자 유형을 구현하는 실제 개체로 변환되지 않습니다.숫자 유형은 원시 숫자 유형처럼 작동하므로 변환할 필요가 없습니다. 숫자 유형은 단순히 숫자 원시입니다.
요령은 단순히 에 던져지는 것입니다.any
유형 확인을 중지할 수 있습니다.따라서 위의 코드는 다음과 같이 다시 작성할 수 있습니다.
function doArithmeticAndLog(lat : LatitudeNumber) {
console.log(<any> lat * 2);
}
doArithmeticAndLog(<any>100);
보시다시피, 함수 호출은 실제로 필요하지도 않습니다. 숫자와 해당 숫자 유형을 서로 교환하여 사용할 수 있기 때문입니다.즉, 런타임에 성능이나 메모리 손실이 전혀 발생하지 않아야 합니다.함수 호출은 거의 비용이 들지 않으며 캐스팅을 통해 기능 호출을 사용하는 것이 좋습니다.any
의 안전 너자신너는느예타안입전의한슨예▁(▁(안:전▁safety▁yourself)doArithmeticAndLog(<any>'bla')
컴파일되지만 런타임에 콘솔에 NaN이 기록됩니다.)...그러나 전체 성능을 원하는 경우 이 방법을 사용할 수 있습니다.
문자열 및 부울과 같은 다른 기본적인 경우에도 작동할 수 있습니다.
즐거운 타이핑!
Typescript 2.7에 소개된 고유 기호를 사용하면 실제로 두 줄로 매우 잘 수행할 수 있습니다.
declare const latitudeSymbol: unique symbol;
export type Latitude = number & { [latitudeSymbol]: never };
이쪽입니다.Latitude
어셰number
될 수 ), 플레인스(그및럼사수있음용다니일입반적될그나러처들▁s및▁s(다니),▁plain입일▁but반적▁(음그있러),▁can).number
s는 위도가 아닙니다.
데모
let myLatitude: Latitude;
myLatitude = 12.5 as Latitude; // works
myLatitude = 5; // error
let myOtherLatitude: Latitude = myLatitude // works
let myNumber: number = myLatitude // works
myLatitude = myNumber; // error
const added = myLatitude + myOtherLatitude; // works, result is number
두 번째 줄을 무시하면 오류 메시지는 대부분 정상입니다.
Type 'number' is not assignable to type 'Latitude'.
Type 'number' is not assignable to type '{ [latitudeSymbol]: never; }'.ts(2322)
언급
그unique symbol
는 성으로필새선다니언합기의 한 새 합니다.Latitude
우리는 기호를 수출하지 않기 때문에, 그것은 접근할 수 없고 따라서 소비자들에게 보이지 않습니다.
이것은 댓글에서 이의를 다룬다는 점을 제외하면 Biggle의 답변과 매우 유사합니다.
그런데: 이렇게 하면 좋은 동료가 될 것이고, 리액트와 리덕스는 비슷한 해킹을 사용하고 있습니다.
이렇게 할 방법이 없습니다.
GitHub 사이트에서 이를 추적하는 방법은 측정 단위입니다.
향후릴는을 사용할 수 .type
기본값에 대한 대체 이름을 정의하지만 연결된 검사는 없습니다.
type lat = number;
type lon = number;
var x: lat = 43;
var y: lon = 48;
y = 'hello'; // error
x = y; // No error
사실 성취하고 싶은 것을 성취하는 방법이 있지만, 그것은 약간 까다롭고, 몇 가지 한계가 있으며, 그 코드를 처음 보는 사람에게는 완전히 불합리할 수 있으므로, 실제 구현보다는 호기심으로 다루십시오;)
좋아요, 그럼 갑시다.먼저, 우리는 다음의 "하위 클래스"를 만들어야 합니다.Number
는 lib lib.d.ts를 선언한다는 Number
클래스가 아닌 인터페이스로 사용할 수 있습니다(합리적입니다. 메소드를 구현할 필요가 없습니다. 브라우저가 이를 처리합니다.에서 선언된 , 된 var의 할 수 . 다행히 선언된 var의 기존 구현을 사용할 수 있습니다.Number
.
class WrappedNumber implements Number {
//this will serve as a storage for actual number
private value: number;
constructor(arg?: number) {
this.value = arg;
}
//and these are the methods needed by Number interface
toString(radix?: number): string {
return Number.prototype.toString.apply(this.value, arguments);
}
toFixed(fractionDigits?: number): string {
return Number.prototype.toFixed.apply(this.value, arguments);
}
toExponential(fractionDigits?: number): string {
return Number.prototype.toExponential.apply(this.value, arguments);
}
toPrecision(precision: number): string {
return Number.prototype.toPrecision.apply(this.value, arguments);
}
//this method isn't actually declared by Number interface but it can be useful - we'll get to that
valueOf(): number {
return this.value;
}
}
여기 있습니다, 우리는 유형을 만들었습니다.WrappedNumber
숫자 유형과 똑같이 동작합니다.두 개를 추가할 수도 있습니다.WrappedNumber
s - 덕분에valueOf()
방법. 그러나 여기서 두 가지 제한 사항: 첫째, 이 작업을 수행하기 위해 변수를 캐스팅해야 합니다.두 번째: 결과는 정규일 것입니다.number
그래서 그것은 나중에 다시 포장되어야 합니다.추가의 예를 살펴보겠습니다.
var x = new WrappedNumber(5);
var y = new WrappedNumber(7);
//We need to cast x and y to <any>, otherwise compiler
//won't allow you to add them
var z = <any>x + <any>y;
//Also, compiler now recognizes z as of type any.
//During runtime z would be a regular number, as
//we added two numbers. So instead, we have to wrap it again
var z = new WrappedNumber(<any>x + <any>y); //z is a WrappedNumber, which holds value 12 under the hood
제 생각에 여기 가장 까다로운 부분이 있습니다.이제 두 개의 클래스를 만듭니다.Latitude
그리고.Longitude
로터물을받에서 상속될 입니다.WrappedNumber
하도록) (으으으으으으으으으으으으으으으으으으으으으으으으으으으으으으으으으으으으)
class Latitude extends WrappedNumber {
private $;
}
class Longitude extends WrappedNumber {
private $;
}
뭐야, 뭐야! 는 유형을할 때 합니다.TypeScript는 유형을 비교할 때 오리 타이핑을 사용합니다.즉, 두 가지 유형이 동일한 속성 집합을 가질 때 서로 다른 유형이 "호환성"(따라서 한 유형의 변수를 다른 값에 할당할 수 있음)으로 간주됩니다.해결책은 정말 간단합니다. 개인 회원을 추가하는 것입니다.이 개인 구성원은 순수 가상이며, 어디에서도 사용되지 않으며 컴파일되지 않습니다.를 TypeScript라고 .Latitude
그리고.Longitude
완전히 다른 유형이며, 우리가 더 관심 있는 것은 유형의 변수를 할당하는 것을 허용하지 않을 것입니다.Longitude
그런식으 타입의 으로.Latitude
.
var a = new Latitude(4);
var b: Longitude;
b = a; //error! Cannot convert type 'Latitude' to 'Longitude'
Voila! 그게 우리가 원했던 거예요.하지만 코드가 지저분하고 유형을 주조하는 것을 기억해야 하는데, 이것은 정말 불편하기 때문에 실제로 사용하지 마십시오.하지만, 보시다시피, 가능합니다.
Lodewijk Bogards 답변을 기반으로 합니다.
interface Casted extends Number {
DO_NOT_IMPLEMENT
toManipulate: { castToNumberType:numberType, thenTo: number }
}
interface LatitudeNumber extends Casted {
LatitudeNumber
}
interface LongitudeNumber extends Casted {
LongitudeNumber
}
type numberType = number | Casted
var lat = <LatitudeNumber><numberType>5
function doSomethingStupid(long: LongitudeNumber,lat: LatitudeNumber) {
var x = <number><numberType>long;
x += 25;
return { latitude:lat, longitude:<LongitudeNumber><numberType>x }
}
var a = doSomethingStupid(<LongitudeNumber><numberType>3.067, lat)
doSomethingStupid(a.longitude,a.latitude)
직접 캐스팅을 하는 것은 명목형의 의도, 숫자를 명확하게 유지한다고 생각합니다.유감스럽게도 유형 유형이 필요한 이유는 번호 또는 번호에 주조해도 추가가 허용되지 않는 이상한 설계 설명 때문입니다.확장된 Javascript는 복싱 없이 매우 간단합니다.
var lat = 5;
function doSomethingStupid(long, lat) {
var x = long;
x += 25;
return { latitude: lat, longitude: x };
}
var a = doSomethingStupid(3.067, lat);
doSomethingStupid(a.longitude, a.latitude);
여기 제 버전의 @bingles 코드가 있습니다.
에 컴일가표다니됩이 됩니다.DocumentID
에 Opaque<string, "DocumentID">
더 읽기 쉬운 것 같아요.언제든지 [ctrl+클릭]을 눌러 [뒤로](IDE 바로 가기에 따라 다름)로 이동하여 실제 유형을 빠르게 볼 수 있습니다.
class ID<T> {
// NOTE: undefined is absolutely necessary, otherwise variables of the union type will be flagged as never
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
public _: T | undefined;
}
export type DocumentId = string & ID<"DocumentId">;
특히 할당, 비교 및 라운드 트립이 유일한 목적이고 실제 원시 유형이 중요하지 않은 ID의 경우 다음 버전을 사용합니다.
declare const IdSymbol: unique symbol;
type Id<T> = unknown & { readonly [IdSymbol]: T }
언급URL : https://stackoverflow.com/questions/26810574/is-there-a-way-to-create-nominal-types-in-typescript-that-extend-primitive-types
'programing' 카테고리의 다른 글
VBA를 사용하여 Excel 2007에서 셀을 투명하게 만드는 방법 (0) | 2023.07.01 |
---|---|
Git에서 파일 하나만 풀 수 있습니까? (0) | 2023.07.01 |
트리 계층 구조를 가져오기 위한 CTE 재귀 (0) | 2023.07.01 |
Spring Bean을 다시 초기화하는 방법은 무엇입니까? (0) | 2023.07.01 |
ORA-00947 유형을 전역으로 선언하는 동안 값이 충분하지 않음 (0) | 2023.07.01 |