블로그의 as const에 관하여 보다가 이러한 예제를 보았다.
const circle = {
type: 'circle',
radius : 10
}
const square = {
type: 'square',
width: 10,
height:20
}
type Shape = typeof circle | typeof square;
function draw(shape: Shape) {
switch (shape.type){
case 'circle':
console.log(shape.radius);
break;
case 'square':
console.log(shape.width)
break
}
}
자바스크립트에서라면 문제되지 않을 코드이지만,
typescript에선 컴파일이 되지 않는 에러
미래의 나 찾았나?
그 블로그에서는 에러가 나는 이유를 이렇게 답했다.
" shape.type의 값이 'circle' 인지 확인되었음에도 불구하고 shape변수의 타입이 [typeof circle]로 좁혀지지 않았기 때문입니다."
[type: string]과 같이 문자열 리터럴의 타입이 제대로 추론되지 않은 것을 확인할 수 있습니다
해결 방안으로는
위에서 발생한 타입 에러가 해결됩니다.
사실 말이 와닿지가 않았다.
그러므로 차근차근 조그마한 것 부터 다시 한번 해보려 한다.
하나의 타입
type Obj = {
name: string,
age : number
}
function func(param : Obj) {}
// func에 넣을 객체 2개
var obj1 = {
name: 'choi',
age: 20
}
var obj2 = {
name: 'park',
age: '31'
}
func(obj1)
func(obj2)
func
라는 함수의 parameter 에는
의 객체 '만' 올수 있어요 라고 정의하면
당연히 obj1
이라는 객체만이 파라미터에 들어갈 수 있는 자격이 있다.
그런데 여기서 중요한 것은 객체의 타입이라는 것은
즉, 형태에는 key값에 반드시 name과,age가 들어가야 하며, 거기에는 각각 string, number의 value 가 들어가야 한다는 이야기 이다.
Union type (2가지 이상)
function func2(x :number | string) { x }
func2(1)
func2('1')
위와 같이 union type의 인자값의 타입은
number | string이 될 수 있다.
그런데...
function func2(x :number | string){
x + 1 //에러남
}
파라미터에 들어올 수 있는 타입은
number | string인데
x + 1 이라는 것은 오직 number
타입만이 가능하다.
즉 함수의 파라미터에 들어올 수 있는 값은 number | string, 둘 다 가능하지만 x+1이라는 값 자체는 number 타입만이 가능하다.
그러므로 x+1을 하고 싶다면,
함수의 내부안에서 다시 한 번더 타입을 narrowing 하는 것이 필요하다 .
function func2(x :number | string){
if(typeof x === 'number' ) return x + 1
// 아니면
return (x as number) + 1
}
위와 비슷한 예로
함수의 return Type
function func3(x:unknown[]){
return x[0]
}
let whatType = func3([4,2])
console.log(whatType+1) //타입이 unknown 이기에...
x
라는 파라미터의 타입이 unknown [ ]
이기에 return 되는 값의 타입도 당연히
unknown [ ] 타입이므로 +
연산기호를 사용 할 수 있는 것은 오직 number
타입인데 에러가 발생하는 것이다.
여기서도
as 문법으로 강제로 확정시켜버리거나.. ( 물론, 내가 120% 확신한다는 가정하에...)
type narrowing을 통해서 return 값을 선별할 수 가 있다.
as 문법으로 한다면...
let whatType = func3([4,2]) as number
console.log( whatType + 1 )
// 아니면
let whatType = func3([4,2])
console.log((whatType as number)+1)
정리
비유
함수안에 들어올 수 있는 것이 서류에 합격한 사람들이라면,
면접관이 '이름', '수험번호' 등을 정확하게 불러 호출한 사람만이 면접을 볼 수 있는것과 비슷 한것 아닐까 싶다.
추가적으로
타입 이라기 보다는
형태라고 생각하는 것이 더욱 타입스크립트를 이해하는데 있어서 도움이 되는 것 같다 .
union 타입이라는 것은 네모와 세모등.. 만이 들어올 수 있고
함수 내부에서 더욱더 세분화 하여 네모 형태, 세모 형태에 따른 로직 구해야 할것 같다.
마치 위 사진과 같이...
const circle = {
type: 'circle',
radius : 10
}
const square = {
type: 'square',
width: 10,
height:20
}
type Shape = typeof circle | typeof square;
function draw(shape: Shape) {
switch (shape.type){
case 'circle':
console.log(shape.radius);
break;
case 'square':
console.log(shape.width)
break
}
}
에러가 나는 이유는
타입이
const circle: {
type: string;
radius: number;
}
const square: {
type: string;
width: number;
height: number;
}
두 개가 draw
함수의 인자값으로 2개가 가능하다.
그런데 switch 문안에서
switch (shape.type){
case 'circle':
console.log(shape.radius);
break;
case 'square':
console.log(shape.width)
break
}
두개의 형태가 올 수 있기에,
타입 (= 형태) 부터 정의하고 그에 따른 분류를 해야 하지만,
값 만을 가지고 로직을 짜려 했기 때문에
위 사진과 같이
shape.type이라는 값이 circle일 수 있는데 radius가 있을 수 있기도 하고 없을 수도 있다 라는 식의 경고 문구를 띄운다.
다시 한번더 말하지만 올수있는 형태는
const circle: {
type: string;
radius: number;
}
const square: {
type: string;
width: number;
height: number;
}
위와 같은데 이 circle이라는 형태가 올 수도 있고 square라는 형태고 올 수도 있잖슴 이라는 경고 문구 이다 .
에러가 안나게 하려면 무언가 동일시 되는 형태를 추가하고 console로 띄워보면 에러가 안난다.
const circle: {
type: string;
radius: number;
width: number;
}
const square: {
type: string;
width: number;
height: number;
}
function draw(shape: Shape) {
switch (shape.type){
case 'circle':
console.log(shape.radius);
break;
case 'square':
console.log(shape.width)
break
}
}
서로 같은 형태의 width
에 대한 console은 로직상 이상없기에 경고 문구가 안뜨지만 radius와 같은 경우는
radius가 있을수도 있고 없을 수 도 있다라는 식의 문구를 띄워준다.
그렇다면
어떻게 하면 타입을 지정해 줄 수 있을까??
const circle = {
type: 'circle',
radius : 10
}
const square = {
type: 'square',
width: 10,
height:20
}
type Shape = typeof circle | typeof square;
function draw(shape: Shape) {
if('radius' in shape) {
switch (shape.type){
case 'circle':
console.log(typeof shape.radius);// 이친구는 가능하다 'radius' 라는 형태가 있으면 이라는 조건으로 narrowing 해줬으니까
break;
case 'square':
console.log(shape.width)// Property 'width' does not exist on type '{ type: string; radius:number }'
break
}
}
}
위와 같이 key
값에 radius
라는 값이 있으면이라는 조건문을 걸면 형태가 더욱 좁혀지고 그로 인해 문구가 안뜨는 것을 확인 할수가 있다.
그런데 귀찮지 않은가??
객체를 선언함과 동시에 그것 자체만으로도 type
이 되면...
as const 를 사용하면 객체의 속성 자체가 type이자 값인 형태를 띄운다.
따라서
const circle = {
type: 'circle',
radius : 10,
} as const
/* circle에 as const를 붙였기에
type const = {
readonly type: "circle";
readonly radius: 10;
readonly width: 10;
}*/
const square = {
type: 'square',
width: 10,
height:20
} as const
/* square에 as const를 붙였기에
type const = {
readonly type: "square";
readonly width: 10;
readonly height: 20;
}
*/
type Shape = typeof circle | typeof square;
function draw (shape: Shape) {
switch (shape.type){// 형태와 값을 일치시킨 switch문이다.
case 'circle':
console.log(shape.radius);// 그러므로 radius 자체가 type이자 값이다.
break;
case 'square' :
console.log(shape.width);// 그러므로 width 자체가 type이자 값이다.
break;
}
}
두 가지 이상의 형태가 파라미터로 들어오고 그에따른 로직을 분기처리 한다면 우선 형태의 타입부터 조건문으로 처리해주자
// type narrowing 하기전
const circle = {
type: 'circle',
radius : 10
}
const square = {
type: 'square',
width: 10,
height:20
}
type Shape = typeof circle | typeof square;
function draw(shape: Shape) {
switch (shape.type){
case 'circle':
console.log(shape.radius);// 들어올 수 있는 '형태가 두개' 이므로 radius라는 값이 있을 수도 없을 수도 있음
break;
case 'square':
console.log(shape.width)// 들어올 수 있는 '형태가 두개' 이므로 width가 있을 수도 없을 수도 있음
break
}
}
// type까지 명확하게 해주면
const circle = {
type: 'circle',
radius : 10,
} as const
const square = {
type: 'square',
width: 10,
height:20
} as const
type Shape = typeof circle | typeof square;
function draw (shape: Shape) {
switch (shape.type){ // shape.type이라는 '값' 자체가 type과 값이다.
case 'circle':
console.log(shape.radius); // 따라서 shpae.type='circle'이라는 타입이자 값이라 오류 안남
break;
case 'square' :
console.log(shape.width);// 따라서 shape.type='square'라는 타입이자 값이라 오류 안남
break;
}
}