04. Type Script

attjyh12·2023년 8월 19일
0

Type Script

목록 보기
4/6

함수 타입

Type Script는 인수에도 함수를 사용할 수 있습니다. 함수 그 자체의 타입을 기입하는 방법입니다.

다음과 같은 표기법으로 함수 타입을 나타냅니다 인수명은 실제 함수의 인수명과 대응할 필요는 없습니다.

(인수명: 인수_타입) ==> 반환값_타입

다음 코드는 예시입니다. singBirds는 인수가 문자열이고 반환값이 배열(문자인 배열)인 함수를 인수로 받습니다.

function genBirdsInfo(name: string): string[]{
    return name.split(',')
}
// 함수 타입을 사용
// (x: string) => string[]
function singBirds(birdInfo: (x: string) => string[]): string{
    return birdInfo('오리, 비둘기')[0] + '꽥꽥'
}
console.log(singBirds(genBirdsInfo)) // 오리 꽥꽥
console.log(singBirds('참새')) // 타입이 맞지 않으므로 에러

기본적인 타입의 기능

타입스크립트의 타입 기능 중, 기본적인 것

타입 추론

타입스크립트에서는 명시적인 변수의 초기화를 수행하면 타입 추론을 통해 자동적으로 타입이 결정되는 기능이 있습니다.

const age = 25
console.log(age.length) //에러: age는 number타입이므로 length속성은 없다

const user = {
    name: 'JinYoung',
    age: 25
}
console.log(user.age.length) //에러: age는 number 타입이므로 length속성은 없다

함수의 반환값도 마찬가지

function getUser() {
    return {
        name: 'JinYoung',
        age: 25
    }
}
const user = getUser()
console.log(user.age.length) // 에러: age는 number타입이므로 length속성은 없다

그리고 타입스크립트 타입 추론은 대입할 대상 변숫값의 타입이 결정되어 있을 때, 대입할 값과 타입이 일치하지 않는 경우 에러가 발생하는 추론 기능도 있습니다. 다음은 자바스크립트를 실행할 때 표준으로 갖는 window 객체 함수 예시입니다.

// window.confirm 함수의 반환 타입은 boolean인 것을 타입스크립트가 
// 알고 있으므로 대압하는 함수 타입이 일치하지 않으면 컴파일 에러가 뜬다
window.confirm = () => {
    //boolean을 return하지 않는 한 에러가 된다
    console.log('confirm 함수')
}

타입스크립트는 정적 타입 언어이지만 우수한 타입 추론이 있기 때문에, 타입을 작성하는 복잡함을 크게 줄일 수 있습니다.

타입 어서션

다음 코드는 자바스크립트에서는 에러, 타입스크립트에서는 컴파일 에러가 발생합니다.
ducument.getElementById()HTMLElement를 반환하며, HTMLCanvasElement를 반환하지 않으므로 타입이 일치하지 않는다는 에러가 발생합니다.

const myCanvas = document.getElementById('main_canvas')
console.log(myCanvas.width) // error TS2239: Property 'width' does not exist on type 'HTMLElement'

만약 개발자가 대상 ID를 가진 DOM 노드가 HTMLElement 중에서도 HTMLCanvasElement라는 것을 알고 있다면, 명시적으로 타입을 지정할 수 있습니다.

변수 =as 타입
const myCanvas = document.getElementById('main_canvas') as HTMLCanvasElement

타입스크립트에서 타입 어서션을 인정하는 것은 대상이 되는 타입보다 구체적이거나 범용적인 타입으로 변환하는 경우입니다. 이 규칙은 보수적이기 때문에 복잡한 어서션을 수행할 때는 잘 표현하기 어렵습니다. 이런 경우에는 먼저 any로 변환한 뒤, 원하는 타입으로 변환하는 2단계 어서션으로 구현할 수 있습니다.

const result = (response as any) as User

단, 타입 어서션은 실핼 시에 에러를 일으킬 가능성이 있으므로 주의해야 함. 다음코드 예시에서는 number 타입으로 타입 변환(casting)하는 것을 가정했습니다. 컴파일 시 에러는 발생하지 않지만, 실행 시 에러가 발생합니다.

const foo: any = 'test'
const bar: number = foo as number

// 컴파일 시에는 number 타입으로서 다뤄져 에러가 발생하지 않지만, 실행 시 사실은 string 타입이 전달되므로 다음 에러가 발생
// TypeError: fuga.toFixed is not a function
console.log(bar.toFixed(2))

타입 앨리어스

타입 앨리어스(type alias)는 타입 지정의 별명(alias)을 덧붙이는 기능입니다. 타입 앨리어스를 활용하면 타입 정의에 이름을 붙일 수 있습니다. 그 이름을 참조해서 같은 타입을 여러 차례 재사용할 수 있습니다.

type 키워드를 사용해서 지정

type 타입명 =

원시 타입에 별명을 붙여서 사용할 수 있습니다. 타입명은 일반적으로 대문자로 시작

type Name = string

타음 코드는 x와 y 좌표 속성을 갖는 Point라는 타입 앨리어스를 정의한 예시입니다.

type Point = {
    x: number;
    y: number;
}

function printPoint(point: Point){
    console.log(`x 좌표는 ${point.x}입니다`)
    console.log(`y 좌표는 ${point.y}입니다`)
}

printPoint({ x: 100, y:100 })
// 타입이 맞아도 속성명이 다르면 에러
// printPoint({ z:100, t:100 })

함수 자체의 타입도 타입 앨리어스로 정의할 수 있습니다. 다음 코드 string을 인수로 받고 string 타입을 반환하는, 주어진 문자열의 포맷을 번경하는 함수의 타입을 지정하는 예시입니다. 타입앨리어스를 사용함으로써 코드 기술이 단순하게 되므로 가독성이 좋아집니다.

type Formatter = (a: string) => string

function printName(firstName: string, formatter: Formatter) {
    console.log(formatter(firstName))
}

또한 객체의 키 이름을 명시하지 않고 타입 앨리어스를 정의할 수도 있습니다. 이것은 인덱스 타입이라 불리는 타입 앨리어스 입니다. 키 이름과 키 숫자가 미리 정해지지 않는 경우의 객체를 정의할 때 편리합니다.

type Label = {
    [key: string] : string
}

const labels: Label = {
    topTitle: '탑 페이지의 제목입니다',
    topSubTitle: '탑 페이지의 하위 제목입니다',
    topFeatuer1: '탑 페이지의 기능 1입니다',
    topFeatuer2: '탑 페이지의 기능 2입니다'
}


const foo: Label = {
    message: 100  // 값 부분의 타입이 맞지 않으므로 에러

}

인터페이스

타입스크립트의 인터페이스(interface)는 타입 앨리어스와 비슷한 기능, 클래스와 함께 많이 사용합니다. 인터페이스로 객체 타입을 지정할 때는 다음과 같이 사용 type과 비슷하지만 = 가 필요하지 않고, 반드시 {로 타입의 정의가 시작되어야 하는 등 몇가지 차이점.

interface 타입명 {
  속성_1: 타입_1;
  속성_2: 타입_2;
//...
}

좌표 x와 y를 갖는 Point 인터페이스를 작성하고 나중에 좌표 x를 추가하는 예시입니다.

interface Point {
    x: number;
    y: number;
}

function printPoint(point: Point) {
    console.log(`x 좌표는 ${point.x}입니다`)
    console.log(`y 좌표는 ${point.y}입니다`)
    console.log(`z 좌표는 ${point.z}입니다`)
}

interface Point {
    z: number;
}

// 인수의 객체에 z가 존재하지 않으므로 컴파일 시 에러가 된다
printPoint({ x:100, y:100 })

// 문제없이 작동한다
printPoint({ x:100, y:100, z:200 })

Point에 나중에 z를 추가한 것처럼 인터페이스를 확장할 수 있습니다. 타입 앨리어스를 사용할 때는 나중에 같은 이름으로 타입을 정의할 수 없습니다.

인터페이스에서는 클래스의 작동 타입을 정의하고, implements를 사용해 클래스에 구현을 위임할 수 있습니다.

interface Point {
    x: number;
    y: number;
    z: number;
}

// 클래스가 인터페이스를 implements했을 때, z가 존재하지 않으므로 컴파일 시 에러
class MyPoint implements Point {
    x: number;
    y: number;
}

속성 정의에 ?를 사용하면 옵셔널(생략 가능) 속성이 됩니다.

interface Point {
    x: number;
    y: number;
    z?: number;
}

// 에러는 발생하지 않는다
class MyPoint implements Point {
    x: number;
    y: number;
}

또한 인터페이스에서는 extends를 사용해 다른 인터페이스를 확장할 수 있습니다.

interface Colorful {
    color: string;
}

interface Circle {
    radius: number;
}

// 여러 인터페이스를 상속해서 새로운 인터페이스를 정의할 수 있다
interface ColorfulCircle extends Colorful, Circle {}

const cc: ColorfulCircle = {
    color: '빨강',
    radius: 10
}

객체 타입을 정의할 때 인터페이스와 타입 앨리어스 모두 사용할 수 있으며, 상속에 관한 세세한 기능의 큰 차이는 없이 거의 비슷한 기능을 갖고있다.

단, 타입스크립트의 설계 사상을 고려했을 때 이 2가지 기능은 다소 다른점이 있다

  • 인터페이스는 클래스나 데이터의 한쪽 측명을 정의한 타입 즉, 인터페이스에 매치하는 타입이라도 그 값 이외에 다른 필드나 메서드가 있음을 전제로 한 것
  • 타입 앨리어스는 객체의 타입 자체를 의미함.

클래스

타입스크립트는 ES2015에서 자바스크립트에 도입된 클래스 표기법에 타입을 붙일 수 있다.

class Point {
    x: number;
    y: number;

    // 인수가 없는 경우의 초깃값을 지정한다
    construntor(x: number = 1, y: number = 0) {
        this.x = x
        this.y = y
    }

    // 반환값이 없는 함수를 정의할 때는 void를 지정한다
    moveX(n: number): void {
        this.x += n
    }
    moveY(n: number): void {
        this.y += n
    }
}

const point = new Point()
point.moveX(11)
console.log(`${point.x}, ${point.y}`) // 11, 0

클래스는 extends를 사용해 다른 클래스를 상속할 수 있다. 다음 코드는 앞에서 정의한 Point 클래스를 상속하는 예시

class Point {
    x: number;
    y: number;

    // 인수가 없는 경우의 초깃값을 지정한다
    constructor(x: number = 1, y: number = 0) {
        this.x = x
        this.y = y
    }

    // 반환값이 없는 함수를 정의할 때는 void를 지정한다
    moveX(n: number): void {
        this.x += n
    }
    moveY(n: number): void {
        this.y += n
    }
}

const point = new Point()
point.moveX(11)
console.log(`${point.x}, ${point.y}`) // 11, 0

class Point3d extends Point {
    z: number;

    constructor(x: number = 0, y: number = 0, z:number = 0) {
        // 상속원의 생성자를 호출한다
        super(x, y)
        this.z = z
    }

    moveZ(n: number): void {
        this.z += n
    }
}
const point3D = new Point3d();
// 상속원을 메서드를 호출할 수 있다
point3D.moveX(11)
point3D.moveZ(22)
console.log(`${point3D.x}, ${point3D.y}, ${point3D.z}`) // 11 0 22

인터페이스에 implements를 사용해 클래스에 대한 구현을 강제할 수 있습니다. 다음 코드는 User라는 인터페이스를 구현하는 클래스의 예시

// 머릿 글자의 I는 인터페이스임을 나타내기 위한 것
interface IUser {
    name: string;
    age: number;
    sayHello: () => string; // 인수 없이 문자열을 반환한다
}

class User implements IUser {
    name: string;
    age: number;

    constructor() {
        this.name = ''
        this.age = 0
    }
    // 인터페이스에 정의되어 있는 메서드를 구현하지 않으면 커파일시 에러가 된다
    sayHello(): string {
        return `안녕하세요 저는 ${this.name}이며, ${this.age}살 입니다.`
    }
}
const user = new User()
user.name = 'JinYoung'
user.age = 25
console.log(user.sayHello()) // 안녕하세요 저는 JinYoung이며 25살 입니다.

접근 수정자

타입스크립트의 클래스에서는 접근 수정자(Access Modifier)로 public, private, protected를 제공합니다. 이들을 부여함으로써 멤버나 메서드의 접근 범위를 제어할 수 있습니다. 접근 수정자를 지정하지 않으면 public으로 취급됩니다.

class BasePoint3D {
    public x: number;
    private y: number;
    protected z: number;
}

// 인터페이스화했을 때의 접근 제어 예
const basePoint = new BasePoint3D()
basePoint.x // OK
basePoint.y // 컴파일시 에러 private이므로 접근할 수 없다.
basePoint.z // 컴파일시 에러 protected이므로 접근할 수 없다.

// 클래스를 상속했을 때의 접근 제어 예
class ChildPoint extends BasePoint3D {
    constructor() {
        super()
        this.x // OK
        this.y // 컴파일시 에러 private 이므로 접근할 수 없다.
        this.z // protected는 문제없이 접근 OK
    }
}

클래스의 기본적인 기능들을 배워 봤습니다. 자바스크립트의 클래스와의 차이나 상세한 기능은 공개된 문서를 찾아봐야 할거 같네요..

0개의 댓글