반응형
Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- interceptors
- DP #c++
- html entities
- React #Hook rules #Hook 규칙
- 빡킹독
- useState #Hooks
- React #effect hook #useEffect
- 백준 #직각삼각형
- 버블링 #갭쳐링 #이벤트 #JS
- React-Query
- 백준 #적록색약
- rate limit
- 다익스트라 #파티 #백준
- raect typescript #react #typescript #styled-component
- React #controlled component #비제어 컴포넌트 #제어 컴포넌트
- 노마드 코더 #타입스크립트 #typescript #class
- 플로이드 #c++
- 이친수
- JWT #토큰 #refreshToken #accessToken #Token #token #localStorage #sessionStorage
- react #useCallback #react Hook
- 얕은 복사 #깊은 복사 #shallow copy #deep copy
- react
- npm #not being able to find a file #npm install Error
- React #리액트 이벤트 주기 #리액트 이벤트
- axios
- RateLimit
- react fragment
- #useRef #언제 쓰는데?
- donwstream #upstream #origin
- 코드스테이츠 #알고리즘 #그리디
Archives
- Today
- Total
꿈꾸는 개발자
9장 타입 제한자 본문
9.1 top 타입
- top 타입: 가능한 모든 값을 나타내는 타입 ⇒ 모든 타입은 top에 할당할 수 있다.
9.1.1 any 다시 보기
- any 타입 top 타입과 유사함(모든 타입의 위치에서 제공 가능)
let value:any;
value="string";//ok
value=123;//ok
console.log(value);
- 하지만, any의 경우 타입 검사를 수행하지 않음 (타입스크립트의 유용성이 떨어짐)
- unknow으로 선언하는 것이 더 안전함
9.1.2 unknown
- TS에서 unknown 타입은 진정한 top 타입임
- any와의 공통점/차이점
- ****************공통점:****************모든 타입을 unknown타입 위치로 전달 가능(any와 유사함)
- 차이점: TS는 unknown 타입의 값을 더 제한적으로 취급함
- TS는 unknown 타입 값의 속성에 직접 접근X
- unknown 타입은 top 타입이 아닌 타입에는 할당X
function greet(name:unknown){
console.log(`${name.toUpperCase()}`);//name 속성에 접근하고자 하면 에러 발생!
}
- unknown 타입인 name에 접근할 수 있는 유일한 방법은 값의 타입이 제한된 경우
function greet(name:unknown){
if(typeof name==="string"){
console.log(`${name.toUpperCase()}`);
}else{
console.log(`well, I'm off.`);
}
}
greet("Beey");
greet({}); //로그 없음?
- 위와 같은 이유들로 unknown이 any보다 훨씬 안전하다.
9.2 타입 서술어
function A(value:unknown){
return['number','string'].includes(typeof value); //boolean값을 반환함
}
function B(value:number|string|null|undefined){
if(A(value)){ //true값이기 때문에 number/string 중 하나라고 생각하겠지만
//TS는 오직 boolean이란 사실 밖에 인지하지 못함!
value.toString(); //Error:Object is possibly undefined.
}else{
console.log("does not exist:",value);
}
}
- TS는 위의 과정이 인수 좁히기란 것을 인지하지 못함!
- 타입 서술어(type predicate) ⇒ 사용자 정의 타입 가드: 인수가 특정 타입인지 여부를 확인하기 위해 boolean을 return 하는 함수를 위한 것!(instanceof/typeof를 통해 자체적인 타입 가드를 생성한다)
- 목적: 매개변수로 전달된 인수가 매개변수의 타입보다 더 구체적인 타입인지 확인
function typePredicate(input:WideType):input is NarrowType
- 위와 같이 사용 가능! ⇒ value 코드에서 is를 사용해 사용자 정의 타입 가드를 지정할 경우 밑과 같이 코드를 작성할 수 있다.
function A(value:unknown):value is number|string{
return['number','string'].includes(typeof value); //boolean값을 반환함
}
function B(value:number|string|null|undefined){
if(A(value)){
// value: number | string 타입
value.toString(); //Error가 사라짐!
}else{
//null | undefined
console.log("does not exist:",value);
}
}
- 위와 같을 경우 TS는 value가 string | number일 경우 ⇒ string | number으로 추론한다.
- 필자 피셜 is의 용도: 보통 한 인터페이스의 인스턴스로 알려진 객체가 더 구체적인 인터페이스의 인스턴스 여부를 검사하기 위해 주로 사용한다고 함!
interface A{
funny:boolean;
}
interface B extends A{
routine:string;
}
//더 구체적인 interface를 명시함 =>인터페이스의 경우 범위의 확장
//let b:B로 타입 애너테이션이 된 경우는 A/B 둘의 인스턴스로 생성이 가능하다!
function C(value:A): value is B{
return "routine" in value
}
function D(value:A){
if(C(value)){
//value의 타입 => B에 해당함!
console.log(value.routine);
}
console.log(value.routine); //Error 발생 type A로 간주 routine 속성 존재X
}
- 하지만 타입 서술어는 false 조건에서 타입을 좁히기 때문에 주의가 필요!
function A(input:string|undefined):input is string{
return !!(input&&input.length>=7);
}
function B(text:string|undefined){
if(A(text)){
//text:string 타입
console.log(`${text}`);
}else{
//text는 undefined;
console.log(`${text?.length}`);//Error length does not exists on type 'never'
}
}
- 즉 입력된 타입 이상(여기에서 말하는 이상은 타입 외 추가적인 내용을 검색하고자 할 때를 의미하는 듯) ⇒ 즉, 위 예시의 경우 string타입을 확인하고자 하는 목적 외에도 string의 길이를 통해 좁히기를 시도하려고 했음 ⇒ 이럴 경우 string임에도 불구하고, 단지 길이가 7미만이란 이유로 TS는 이를 undefined으로 간주할 수 있다는 의미!
9.3 타입 연산자
9.3.1 keyof
interface A{
aud:number;
cri: number;
}
function B(rate:A,key:string):number{
return rate[key]; //Error => string 형식의 인덱스 접근이 허용되지 않음!
}
const rating:A={aud:66,cri:84};
B(rating,'aud');// 허용됨!
B(rating,"not valid") //허용되만 사용하면 안됨!
- type string은 A 인터페이스에서 속성으로 허용되지 않는 값을 허용?
- string이란 넓은 범주가 오류의 가능성을 내포하기 있기 때문에?
- A는 string 키를 허용하는 인덱스 시그니처를 선언하지 않음
- 말 그대로, aud/cri 외 다른 string 타입의 문자열이 들어왔을 때 허용하지 않았음에도 밑 코드는 그것을 허용하고 있는 것을 보여줌!(B(rating, “not valid”)의 예시가 잘 보여줌)
interface A{
aud:number;
cri: number;
}
function B(rate:A,key:string):number{
return rate[key]; //Error => string 형식의 인덱스 접근이 허용되지 않음!
}
const rating:A={aud:66,cri:84};
B(rating,'aud');// 허용됨!
B(rating,"not valid") //허용되지만 사용하면 안됨!
function C(rate:A,key:"aud"|"cri"):number{
return rate[key]; //리터럴 값으로 유니언 타입을 형성했을 때는 허용한다.
//컨테이너에 존재하는 key만을 적절하게 제한하는 것이 중요함!
}
const ratings:A={aud:55,cri:85};
C(rating,"aud"); //ok
C(rating,"not valid") //에러 발생
위는 keyof를 사용하지 않으면 발생하는 불편한 점을 보여주기 위해 있는 것들 이제부터 핵심:
- C와 같은 방법은 속성이 많아지면 하기 힘들어짐
- keyof를 사용해라 (기존에 존재하는 타입 + 타입에 허용되는 모든 키의 조합을 반환함)
interface A{
aud:number;
cri: number;
}
function B(rate:A,key: keyof A):number{
return rate[key]; //ok
}
const rating:A={aud:45,cri:55};
B(rating,"aud");//ok
B(rating,"not valid"); //Error:keyof A에 할당 불가능하다고 나옴!
- 위 문제를 간결하게 해결함, 매개변수에 유니언 타입을 선언할 필요도 없음! ⇒ (keyof가 생성해줌)
9.3.2 typeof
- typeof: 제공되는 값의 타입을 반환함
const original={
medi:"movie",
title:"mean",
};
let adaptatio: typeof original;
if(Math.random()>0.5){
adaptatio={...original,medi:"play"} //ok
}else{
adaptatio={...original,medi:2}; //Error: number is not assignable to string
}
- JS typeof vs TS typeof
- JS typeof: 타입에 대한 문자열 이름을 반환하는 런타임 연산자
- TS typeof: 제공된 값의 타입 반환(TS에서만 사용 가능)
<aside> 💡 keyof/typeof: 값의 타입 검색/값에 허용된 키 검색
</aside>
const ratings={
imdb:8.4,
metacri:82,
}
//키가 rating 값 타입의 키 중 하나임을 나타내야 함!
function logRate(key: keyof typeof ratings){
}
logRate("imdb"); //ok
logRate("invalid")// Argument of type '"invalid"' is not assignable to parameter of type '"imdb" | "metacri
- keyof + typeof를 혼합해서 사용하면 ⇒ 명시적 인터페이스가 없는 객체에 허용된 키들의 타입의 코드를 작성하느라 수고할 필요X
9.4 타입 어서션
- 강력하게 타입화(strongly typed)될 때 TS코드는 잘 작동함 (모든 값이 정확히 알려진 타입의 경우를 의미함)
- 경우에 따라서, 코드 작동을 알려주지 못할 수도 있음
- ex) JSON.parse는 의도적으로 top 타입인 any를 반환함 ⇒ JSON.parse가 특정 문자열이 주어졌을 때 특정한 값 타입을 반환해야 한다고 안전하게 알려줄 방법은 없음! (제네릭의 황금률을 위반하기 때문에!)
- 경우에 따라서, 코드 작동을 알려주지 못할 수도 있음
- 타입 어서션 or Type cast: 값의 타입을 재정의 (타입 다음에 as 키워드 추가)
const rawData=["a","b"];
//타입 any
JSON.parse(rawData);//Error 발생 stirng[] => string에 할당 불가!
//타입 string[];
JSON.parse(rawData) as string[];
- 교재에선 위 예시들을 제시하지만, 애초에 JSON.parse()의 인수로 string[]을 전달할 수 없는 것으로 보인다.
const rawData="hello";
//타입 any
JSON.parse(rawData);//Error 발생 stirng[] => string에 할당 불가!
//타입 string[];
JSON.parse(rawData) as string[];
//타입 [string,string];
JSON.parse(rawData) as [string, string];
- 오히려 일반 문자열 리터럴을 할당했을 때 에러 발생X ⇒그리고 위와 같이 해도 as를 사용하는 목적에 위배되지 않는 것으로 보인다. 애초에 as를 사용하지 않으면 JSON.parse()는 any를 반환하기 때문에!
- 흠….필자에 따르면 애초에 TS의 모범 사례는 타입 어서션을 사용하지 않는 것이라고 한다 (코드의 완전한 타입화를 지향함)⇒ BUT, 종종 유용하기도 함!
9.4.1 포착된 오류 타입 어서션
오류를 처리할 때 타입 어서션이 매우 유용할 수 있음 ⇒ try 블록에서 예상과 다른 객체를 발생 가능? ⇒ catch()에서 오류의 타입을 알기 어려움
- 만약 Error 클래스의 인스턴스가 발생될거라고 확신을 한다면, 타입 어서션을 사용해 어서션 오류 처리 가능
try{
//오류를 발생시키는 코드
}catch(error){
//Error 클래스의 인스턴스라고 가정하고, error의 message 속성에 접근!
console.warn((error as Error).message);
}
- instanceof와 같은 타입 내로링을 사용하는 것이 더 안전할 수 있음!
try{
//오류를 발생시키는 코드
}catch(error){
console.warn(error instanceof Error? error.message:error);
}
9.4.2 non-null 어서션
- null+undefined을 포함할 수 있는 변수에서 null과 undefined를 제거할 때 타입 어셔선을 주로 사용함 ⇒ 예약어를 제공함(!)
let a=Math.random()>0.5? undefined:new Date();
//타입이 Date라고 간주됨!
a as Date;
//타입이 Date라고 간주됨(undefined를 제거)
a!
const a=new Map([["a","a"],["b","b"],])
//type:string|undefined;
const maybeValue=a.get("a");
console.log(maybeValue.toUpperCase());//Error: maybeValue' is possibly 'undefined
//type:stirng으로 고정됨?
const knowValue=a.get("a")!; // !을 사용함으로 undefined이 제외됨!
console.log(knowValue.toUpperCase());//ok undefined이 제거됨!
- 예시에서는[”a”,1]이런 형태인데 이러면 get을 사용하면 number타입 아닌가? 위 코드는 교재에 있는 코드를 임의로 변경한 것 그래야 두 번째 console.log()에서 에러 발생X
9.4.3 타입 어서션 주의 사항
- any 타입과 마찬가지로 가능한 사용 자제를 해야 함!
const a = new Map([["a", "a"], ["b", "b"],]);
const b = a.get("a")!;
console.log(typeof b);//타입은 string임
console.log(b.toUpperCase());//타입 오류는 아니지만, runtime에서 오류가 발생함?
- 실제 위의 코드를 브라우저 개발자 환경에서 실행했을 때 문제없이작동하지만, 교재에서는 Map의 값이 변경되는 경우를 상정했기 때문에 타입이 변경되면 toUpperCase()메서드가 문제될 수 있음!
어서션 vs 선언
변수 타입을 선언하기 위해 타입 애너테이션 사용 VS 변수 타입을 변경하기 위해 타입 어서션을 사용!
- 타입 선언을 위한 타입 애너테이션: 타입 검사기는 초깃값에 대한 할당 가능성을 검사함
- 타입 어서션: TS에 타입 검사 중 일부를 건너뛰도록 명시!
interface A{
acts:string[];
name:string;
}
const declared: A={
//Property 'acts' is missing in type '{ name: string; }'
//but required in type 'A'
name:"aa",
}
const asserted={
name:"bbb" //여기에선 오류가 발생X
}as A
//아래 코도는 런타임 에러로 인해 정상 작동X
console.log(declared.act.join(", "));
console.log(asserted.act.join(", "))
- 따라서, 처음부터 타입 애너테이션 or TS가 초깃값에서 변수의 타입을 유추할 수 있도록 하는 것이 좋음!
어서션 할당 가능성
- 타입 어서션은 둘 중 하나의 타입에 할당 가능한 경우 허용됨!
- 서로 전혀 관련없을 경우 타입 오류 감지함!
let myValue="aaa" as number; //Error 발생 겹치지 않기 때문에!
- 완전히 관련없는 타입으로 전환해야 하는 경우 이중 타입 어서션을 사용함 ⇒ 값을 any/unknown 같은 top 타입으로 전환 후 그 결과를 관련없는 타입으로 전환함!
let myValueDouble="1234" as unknown as number;
- 필자는 위의 방법은 안전하지 않고, 코드 문제가 쉽지 발생할 수 있다고 경고함!
9.5 const 어서션
- const 어서션은 모든 값(배열, 원시 타입, 값 등등) 상수로 취급할 때 사용함
- 배열은 가변 배열이 아니라 읽기 전용 튜플로 취급
- 리터럴은 ≠일반 원시 타입 ⇒ 리터럴로 취급
- 객체 속성은 읽기 전용으로 간주
//타입:(number | string)[]; [0," "]; //타입:readonly [0," "]; [0," "] as const;
9.5.1 리터럴에서 원시 타입으로
- 특정 리터럴을 반환하는 함수에 사용하면 적절!
const getName=()=>"Maria Bam";//()=>string;
const getNameConst=()=>"Maria" as const;//()=>"Maria"
interface A{
quo:string;
sty: "story"|"one-liner";
}
function tellJoke(joke:A){
if(joke.sty==="story"){
console.log(`${joke.quo}`);
}else{
console.log(joke.quo.split("\\n"));
}
}
const narrowJ={
quo:"hello world",
sty:"one-liner" as const,
}
tellJoke(narrowJ) //ok
const wideObject={
quo:"heelo world",
sty:"one-liner",
}
tellJoke(wideObject);//Error:Type 'string' is not assignable to type '"story" | "one-liner"'
- 위처럼 값의 특정 필드가 더 구체적인 리터럴 값을 갖도록 사용 가능!
9.5.2 읽기 전용 객체
function des(preference:"maybe"|"no"|"yes"){
switch(preference){
case "maybe":
return "I supp";
case "no":
return "no"
case "yes":
return "yes"
}
}
const preferencesMutable={
movie:"maybe",
standup:"yes",
}
//Error:stirng is not assignable to "maybe"|"no"|"yes"
des(preferencesMutable.movie);
preferencesMutable.movie="no"//ok
const preferenceReadonly={
movie:"maybe",
standup:"yes",
}as const;
des(preferenceReadonly.movie);//ok
preferenceReadonly.movie="no";
//Error: cannot assign to "movie" because it is a read-only property
- 재귀적으로 const 어서션이 적용된다 ⇒ 즉 중첩 객체에도 적용이 된다는 의미임!
Reference:
조시 골드버그, 러닝 타입스크립트, 고승원 옮김, 2023