[ Typescript ] - 함수의 타입지정에 관하여. ( feat: as const )

최문길·2023년 12월 23일
1

typescript 일지

목록 보기
1/5
post-thumbnail

블로그의 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 속성에 const assertion을 적용하거나,
  • 두 객체에 통째로 const assertion을 적용하면

위에서 발생한 타입 에러가 해결됩니다.

참조 블로그 링크

사실 말이 와닿지가 않았다.
그러므로 차근차근 조그마한 것 부터 다시 한번 해보려 한다.




함수의 타입에 관하여, parameter,return,내부



하나의 타입

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 에는

  • name: string
  • age: number

의 객체 '만' 올수 있어요 라고 정의하면

당연히 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 하는 것이 필요하다 .

  • if문을 통한 narrowing을 하거나,
  • as 를 통해 타입을 강제로 확정시켜버리거나


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)




정리

  • 함수에 들어오는 타입이 2가지 이상이 될 수 있고 return을 가지는 함수라면
  • 함수 내부에서도 타입들을 narrowing 해주는 것이 반드시 필요하다.



비유

함수안에 들어올 수 있는 것이 서류에 합격한 사람들이라면,
면접관이 '이름', '수험번호' 등을 정확하게 불러 호출한 사람만이 면접을 볼 수 있는것과 비슷 한것 아닐까 싶다.


추가적으로
타입 이라기 보다는

형태라고 생각하는 것이 더욱 타입스크립트를 이해하는데 있어서 도움이 되는 것 같다 .

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;
  }
}

0개의 댓글