[Typescript] Narrowing & Assertion (4)
function 내함수(x :number | string){
return x + 1 //에러남
}
Operator '+' cannot be applied to types 'string | number' and 'number' 라는 에러가 납니다.
string | number 같은 union type 에는 일반적으로 조작을 못하게 막아놔서 그렇습니다.
이런 메세지를 보면 1. 타입을 하나로 Narrowing 해주거나 2. Assert 해주거나 둘 중 하나 해주면 됩니다.
Type Narrowing
if문 등으로 타입을 하나로 정해주는 것을 뜻합니다.
function 내함수(x :number | string){
if (typeof x === 'number') {
return x + 1
}
else if (typeof x === 'string') {
return x + 1
}
else {
return 0
}
}
if문과 typeof 키워드로 현재 파라미터의 타입을 검사해서
"이게 'number' 타입일 경우 이렇게 해주세요~"
"이게 'string' 타입일 경우 이렇게 해주세요~"
이렇게 코드를 짜야 정상적으로 사용이 가능합니다. 타입이 확실하지 않을 때 생기는 부작용을 막기위한 장치라고 보시면 되겠습니다. 가끔 이걸 "defensive 하게 코딩한다"라고 하기도 합니다.
근데 또 함수 안에서 if문 쓸 때는 마지막에 else {} 없으면 에러가 납니다.
return 하지않는 조건문이 있다면 나중에 버그가 생길 수 있어서 에러를 내주는 것인데
이게 성가시다면 tsconfig.js 파일에서 "noImplicitReturns": false, 추가하면 됩니다.
- 꼭 typeof를 쓸 필요는 없고 타입을 하나로 확정지을 수 있는 코드라면 어떤 것도 Narrowing 역할을 할 수 있습니다.
- in, instanceof 키워드도 사용가능합니다.
Type Assertion
아니면 타입을 간편하게 assert 할 수도 있습니다. "이 변수의 타입을 number로 생각해주세요"
변수명 as string 이런 식으로 as라는 키워드 쓰면 됩니다.
function 내함수(x :number | string){
return (x as number) + 1
}
console.log( 내함수(123) )
변수명 as number 라고 쓰시면 "나는 이 변수를 number라고 주장하겠습니다~" 라는 뜻이며 실제로 그렇게 타입을 변경해줍니다.
근데 이러려면 "함수에 무조건 숫자가 들어올 것이다"라는 사실을 알고 있어야 안전하게 쓸 수 있는 문법이겠죠?
as 키워드 사용시 특징이 있는데
1. as 키워드는 union type 같은 복잡한 타입을 하나의 정확한 타입으로 줄이는 역할을 수행합니다. (number 타입을 as string 이렇게 바꾸려고 하면 에러남)
2. 실은 그냥 타입실드 임시 해제용입니다. 실제 코드 실행결과는 as 있을 때나 없을 때나 거의 동일합니다.
아무튼 그러면 이제 변수를 숫자로 가정해서 가공할 수 있습니다.
Q. 함수('123') 이렇게 숫자말고 문자를 입력하면 어떻게 될까?
A. as number라고 썼긴 했지만 number 타입처럼 +1 해주진 않습니다. 콘솔창에 결과 출력해보면 '1231' 이렇게 출력됩니다. as는 그냥 주장만 하는거지 실제로 타입을 바꿔주는건 아니기 때문입니다.
as 쓰면 간편 하지만 정확히 코드짜려면 narrowing을 쓰는게 좋습니다 .
as 키워드는 맘대로 타입을 개발자 맘대로 주장하는 역할이라 때문에 엄격한 타입체크기능을 잠깐 안쓰겠다는 뜻과 동일합니다.
그래서 as 문법은 이럴 때 쓰도록 합시다.
1. 왜 타입에러가 나는지 정말 모르겠는 상황에 임시로 에러해결용으로 사용하거나
2. 내가 어떤 타입이 들어올지 정말 확실하게 알고 있는데 컴파일러 에러가 방해할 때
물론 대부분의 상황에선 as 보다 훨씬 엄격하고 좋은 type narrowing으로 해결할 수 있습니다.옛날assertion 문법은
as 키워드 대신 <> 이런 괄호를 이용했습니다.
let 이름 :number = 123;
(이름 as string) + 1; //현재문법
<string>이름 + 1; //옛날문법
위에 있는 두 줄의 코드는 똑같은 의미입니다.
다만 html과 js가 혼연일체된 리액트에서 사용시 html태그 이런거랑 헷갈릴 수 있기 때문에
지금은 그냥 as 키워드를 주로 씁니다.
가끔 타입을 강제로 부여하는 기계를 하나 만들어쓰고 싶은 때가 있습니다.
그럴 때 함수에 데이터를 넣으면 as 타입명을 붙여서 return 하는 함수를 만들어서 사용하면 됩니다.
type Person = {
name : string
}
function 변환기<T>(data: string): T {
return JSON.parse(data) as T;
}
const jake = 변환기<Person>('{"name":"kim"}');
Q1)
숫자여러개를 array 자료에 저장해놨는데가끔 '4', '5' 이런 식의 문자타입의 숫자가 발견되고 있습니다.
이걸 클리닝해주는 함수가 필요합니다. 클리닝함수( ['1', 2, '3'] ) 이렇게 숫자와 문자가 섞인 array를 입력하면
[1,2,3] 이렇게 숫자로 깔끔하게 변환되어 나오는 클리닝함수를 만들어오고 타입지정까지 확실히 해보십시오.
function 클리닝함수(a :(number|string)[]){
let 클리닝완료된거 :number[] = [];
a.forEach((b)=>{
if (typeof b === 'string') {
클리닝완료된거.push(parseFloat(b))
} else {
클리닝완료된거.push(b)
}
})
return 클리닝완료된거
}
console.log( 클리닝함수([123,'3']) )
파라미터로 집어넣은 array 자료를 반복문 돌려서하나하나 숫자로 바꿔서 새로운 array에 집어넣는 방식으로 코드를 짰습니다.
1. 클리닝 함수를 만들었습니다. 파라미터로 array를 집어넣을 수 있다고 해놨습니다.
2. 클리닝완료된 array를 클리닝완료된거라고 작명해서 만들어뒀습니다.
3. 저는 집어넣은 array를 반복문을 돌리려고 forEach()를 썼습니다.
4. 그리고 반복문 돌리면 array 안에 있던 하나하나의 자료가 b라는 파라미터로 나오는데
그게 string 타입이면 parseFloat(b)에 넣어서 숫자로 바꾸고 클리닝완료된거 array에 집어넣었습니다.
number 타입이면 그냥 클리닝완료된거 array에 집어넣었습니다.
Q2)
다음과 같은 함수를 만들어보십시오.
let 철수쌤 = { subject : 'math' }
let 영희쌤 = { subject : ['science', 'english'] }
let 민수쌤 = { subject : ['science', 'art', 'korean'] }
지금 여러 변수에 선생님이 가르치고 있는 과목이 저장이 되어있습니다.
과목 1개만 가르치는 쌤들은 문자 하나로 과목이 저장이 되어있고
과목 2개 이상 가르치는 쌤들은 array 자료로 과목들이 저장되어있습니다.
철수쌤같은 선생님 object 자료를 집어넣으면 그 선생님이 가르치고 있는 과목중 맨 뒤의 1개를 return 해주는 함수를 만들어봅시다. 그리고 타입지정도 엄격하게 해보도록 합시다.
(동작예시)
만들함수( { subject : 'math' } ) //이 경우 'math'를 return
만들함수( { subject : ['science', 'art', 'korean'] } ) //이 경우 'korean'을 return
만들함수( { hello : 'hi' } ) //이 경우 타입에러 나면 됩니다
(코드)
function 만들함수( x :{subject : string | string[]} ){
if (typeof x.subject === 'string') {
return x.subject
} else if (Array.isArray(x.subject) ){
return x.subject[x.subject.length - 1]
} else {
return '없쪄'
}
}
console.log( 만들함수( { subject : ['english', 'art'] } ) )
1. 함수에 object 자료를 입력할 수 있다고 써야하는데 저는 type alias를 만들어사용했습니다.
2. 그리고 만들함수()를 디자인했다고 합니다.
3. 지금 x.subject 라는 파라미터는 케이스가 2개니까 if문을 두개 썼는데 안전하게 마지막 else 문도 추가했습니다. 아니면 그냥 if 한개 else 한개 이렇게 해도 똑같을 듯요
4. 이 변수가 array 자료인지 확인하려면 typeof 이거는 못사용하고 Array.isArray() 이거나 아니면 다른 여러 방법을 쓰셔야합니다.
5. 그래서 실제 실행해보니 { subject : ['english', 'art'] } 입력해보면 'art'가 잘 나옵니다.