[TS] Part1-기본

JH Cho·2023년 1월 5일
0

TypeScript

목록 보기
1/6

타입스크립트 기본 타입 정리(원시)

let 이름: string = 'kim';

let 회원들: (string | number)[] = ['kim', 'park', 1];
let text = ['kim', 'park', 1];

let 회원들2: {
  member: string;
  member2: string;
} = {
  member: 'kim',
  member2: 'park',
};

// TS는 대부분 타입지정을 자동으로 해주니 위 코드들처럼
//너무 덕지덕지 붙일 필요 없다.(ex; text에 커서 올려봐라)

let myName: string = 'cho';
let myAge: number = 31;
let 출생지역: string = '김해';

let myFavorite: { title: string; singer: string } = {
  title: '막걸리',
  singer: '장범준',
};

let project: {
  member: string[];
  days: number;
  started: boolean;
} = {
  member: ['kim', 'park'],
  days: 30,
  started: true,
};

union / any / unknown

let 회원들: (number | string)[] = [1, '2'];
let 오브젝트: { a: string | number } = { a: '123' };

// any : TS 보호막 해제 사용을 지양하라
let 이름: any = 1;
이름 = 'string';
let 변수: number = 이름; //TS 아예 무시 에러발생 X

//unknown : any랑 비슷 but
let 이름2: unknown;
이름2 = 1;
이름2 = {};

// 지금 이름2의 타입은 객체이기 때문에 string 변수에
//할당 불가하다는 에러가 뜸.(any는 노상관)
let 변수1: string = 이름2;

이름2 - 1; //unknown타입은 수학연산 불가
// 수학 연산 가능 : any number bigint

/*
JS는 문자에도 + 사용가능. 타입스크립도 가능하지만
엄격히 number나 string 타입에만 가능하고 
string|number 는 별도의 타입이기 때문에 허용하지 않는다.
*/
let 나이: string | number;
나이 + 1;

// -----
let age: unknown = 1;
나이 - 1; // unknown은 연산 허용하지 않는 타입이니까
//Narrowing / Assertion으로 엄격히 코드 짜면 됨.

연습문제


왜 에러가 날까?
Q) 1
let 나이: string | number;
나이 + 1;

Q) 2
let 나이: unknown = 1;
나이 + 1;

A) TS는 엄격함 
string | number 는 union타입으로 새로운 타입을 생성한 것
unknown은 당연히 연산 허용 X 타입이라서 에러 발생.

Q) 2

let user: string = 'kim';
let age: undefined | number = undefined;
let married: boolean = false;

let 철수: (string | undefined | number | boolean)[] = [user, age, married];

Q) 3

let 학교: {
  score: (number | boolean)[];
  teacher: string;
  friend: string | string[];
} = {
  score: [100, 97, 84],
  teacher: 'Phil',
  friend: 'John',
};
학교.score[4] = false;
학교.friend = ['Lee', 학교.teacher];

함수에 타입지정 & void

함수 타입 지정 기본


function 함수(x: number): number {
  return x * 2;
}

//타입지정된 파라미터는 필수로 넣어야함
함수(); //에러

void

// void
function 리턴안해(x: number): void {
  console.log('리턴ㄴㄴ');
}

// 옵셔널 넣어주면 인자 없어도 에러 띄우지 않음.
function 옵션파라미터(x?: number) {}
옵션파라미터();

// x?: number === x: number | undefined

중간 퀴즈

// 중간 퀴즈
function 퀴즈(x: number | string): void {
  console.log(x + 3); // 1. 에러가 뜨는 이유?
  // 유니온 타입은 새로운 타입이기 때문.
  // string, number, any 타입이 아니란 것.
}
퀴즈(2);

Narrowing

// 2. 어떻게 에러를 해결해볼 수 있을까
// Narrowing!!
function 에러해결(x: number | string): void {
  if (typeof x === 'number') {
    console.log(x + 3);
  }
}

연습해보기

// 숙제1 이름 넣으면 안녕하세요 이름, 안넣으면 이름 없다
//출력하는 함수 만들기

function 인사(x?: string): void {
  if (typeof x === 'string') {
    // if(x) 도 가능
    console.log(`안녕하세요 ${x}`);
  } else if (typeof x === 'undefined') {
    // else만 해도 됨.
    console.log('이름이 없습니다.');
  }
}

//숙제2 함수에 숫자 또는 문자를 넣으면 자릿수를 세어 출력하는 함수

function 자리수세기(x: number | string): void {
  let count: number = 0;
  if (typeof x === 'number') {
    let st = x.toString();
    for (let i = 0; i < st.length; i++) {
      count++;
    }
  } else {
    for (let i = 0; i < x.length; i++) {
      count++;
    }
  }
  console.log(count);
}
바보다.걍 문자열.length 깜빡함..

// console.log(x.toString().length)


// 숙제3 결혼 가능 확률 계산기
/*
1. 파라미터, 월소득(만원), 집보유(boolean), 매력(상중하)
2. 월소득 1점, 집 500점, 매력점수 상 100점
3. 총 점수 >= 600 결혼가능 리턴, 그 외엔 아무것도 리턴X
*/
function 결혼가능판독기(월소득:number,집보유:boolean,매력:string):string|undefined {
  let score = 0;
  score += 월소득;
  if (집보유) score += 500;
  if (매력 === '상') score += 100;

  if(score>=600) return '결혼가능'
  
}

Narrowing & Assertion

Narrowing

  • 불확실한 변수의 타입(union type 등)일 때
  • 조건을 통해 타입을 확실하게 만들어줌.
function 내함수(x: number | string) {
  if (typeof x === 'number') {
    return x + 1;
  } else {
    return x + '1';
  }
}
console.log(내함수('1'));

function 함수(x: number | string) {
  let array: number[] = [];
  array[0] = x; //에러
  // x가 확실히 number일 때만 들어갈 수 있음.

  if (typeof x === 'number') {
    array[0] = x;
  }
}
// Narrowing 판정 문법
/*
  1. typeof 변수
  2. 속성명 in obj
  3. 인스턴스 instancof 부모
  */

Assertion 문법 (타입 덮어쓰기; )

  • 조건문 간단히 표현 가능
  • 간편하지만 사용 지양..

function 내함수(x: number | string) {
  let array: number[] = [];
  array[0] = x as number;

  (x as string) + 1; //현재문법
  <string>x + 1; //옛날문법

  // 편하지만 용도가 있음.
  // x as boolean <- 이렇게 타입 변경해주는 게 아니고
  // union 타입 등으로 인한 불확실성을 확실하게 해주는 용도
}
  • 용도1. Narrowing할 때
  • 용도2. 무슨 타입이 들어올지 100% 확실할 때 사용
  • reason: 이것도 any랑 비슷하게 타입에러를 잡아주지 않음.
  • as는 지양 but 유용하게 쓰이는 상황(type 부분에서 정확히 다룸LINK)
type Person = {
  name: string;
};
function 변환기<T>(data: string): T {
  return JSON.parse(data) as T;
}
const jake = 변환기<Person>('{"name":"kim"}');

console.log(jake); // { name: 'kim' }

연습해보기

//연습1: 배열에 문자타입 숫자를 number로 변환기
function numberConverter(x: (number | string)[]): number[] {
  return x.map((num) => {
    return Number(num);
  });
}

//연습2 1과목 쌤 스트링 2과목이상 배열
/*
let 철수쌤 = { subject : 'math' }
let 영희쌤 = { subject : ['science', 'english'] }
let 민수쌤 = { subject : ['science', 'art', 'korean'] }

만들함수( { subject : 'math' } )  //이 경우 'math'를 return
만들함수( { subject : ['science', 'art', 'korean'] } ) //이 경우 'korean'을 return
만들함수( { hello : 'hi' } )  //이 경우 타입에러 나면 됩니다 
*/

function 과목반환(x: { subject: string | 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 '과목이 없습니다.';
  }
}

Type Alias && readonly

// 원시타입
type Animal = string | number | undefined;

let 동물: Animal = 123;
동물 = 'answk';
동물 = undefined;
// 객체
type CamelType = { name: string; age: number };

let 타조: CamelType = { name: 'kim', age: 20 };

// readonly
type CountryHome = {
  readonly region: string;
};
const 출생지역: CountryHome = { region: 'Seoul' };
출생지역.region = 'Busan'; //에러

type extend

// 타입 alias도 union type화 가능.
type Name = string;
type Age = number;
type Person = Name | Age;

// --- 객체 타입 Extend(합침)
type X = { x: number };
type X = { z: string };
//타입 재정의 불가!

type Y = { y: number };

type NewType = X & Y;

const 합치기: NewType = { x: 1, y: 2 };

Literal type(const 유사품)

원시타입

let 이름: 'kim';
이름 = 'park'; //에러
// 다수도 가능
let 나야: '대머리' | '솔로';
나야 = '커플'; //에러

함수

function 함수(a: 'hello'): 1 {
  return 1;
}

함수('bye'); //에러

중간 연습

//중간 연습
//1. 가위바위보 파라미터
//2. 가위 바위 보만 들어갈 수 있는 array 리턴

function 가위바위보(param: '가위' | '바위' | '보'): ('가위' | '바위' | '보')[] {
  return ['가위'];
}

결국 Literal type은 const 키워드의 업글버전

let 성별: '남자' | '여자';
성별 = '남자';
성별 = '여자';
성별 = '자웅동체'; //에러

let 자료 = {
  name: 'kim',
};
자료.name; // 'kim'

// Literal type의 문제점
function 내함수(a: 'kim') {}

내함수('kim');
내함수(자료.name); //에러
  • 설명
    a: 'kim'은 'kim'이라는 타입을 지정한 것
    a가 'kim'이 아니라.
    자료.name의 타입은 string
    a:'kim'은 타입 'kim'
  • 에러 안띄우려면??
// 첫번째 방법 : 첨부터 타입 지정
let 자료2: { name: 'kim' } = { name: 'kim' };
// name 값의 타입은 string이 아닌 'kim' 임
내함수(자료2.name);
// 두번째 방법 : assertion 타입을 덮어버림.
내함수(자료.name as 'kim');

세번째 방법 : as const (자물쇠)

let 자료3 = {
  name: 'kim',
} as const; //(자물쇠!)
// 프로퍼티의 값을 키의 type으로 선언한다~
// 또한 readonly 속성도 부여함.


//## 함수와 methods에 type alias 지정하기
//- 함수타입 alias는 함수선언문 x 화살표함수로
type 함수타입 = (a: string) => number;

//사용은 함수 표현식에
const 함수: 함수타입 = function (a) {
  return 10;
};

console.log(함수('a')); //10

//## 메서드 type alias

type Member = {
  name: string;
  age: number;
  plusOne: (x: number) => number;
  changeName: () => void;
};

let 회원정보: Member = {
  name: 'john',
  age: 23,
  plusOne(x) {
    return this.age + x;
  },
  changeName: () => {
    console.log(this.name); //error
  },
};

참고 : changeName의 this만 에러가 발생하는 이유.

console.log(회원정보.plusOne(1)); //24
회원정보.changeName(); //undefined

연습해보기

type Zero = (x: string) => string;

const cutZero: Zero = (x) => {
  let result: string = '';
  if (x[0] === '0') {
    result = [...x].splice(1, x.length - 1).join('');
  } else {
    result = x;
  }
  return result;
};
// 계속 [...x] 에서 에러가 났는데
// confing가 es5라 최신문법 적용이 안됐음.
// 하지만 호환성때문에 es5쓰는게 낫다는데..

다른 풀이 (정규표현식 공부 정리해보자.)

const cutZero2: Zero = (x) => {
  let result = x.replace(/^0+/, '');
  return result;
};

// 연습 2) 스트링의 '-' 모두 빼버리고 숫자로 리턴
function removeDash(x: string): number {
  let result = x.replace(/-/g, '');
  return Number(result);
}

//연습 3) 콜백함수 연습2함수 결과를 연습1함수에 넣고
//결과를 콘솔창에 띄우기

type 함수1 = (a: string) => string;
type 함수2 = (a: string) => number;

function 종합함수(x: string, y: 함수1, z: 함수2): void {
  let a = z(x);
  let result = y(a);
  console.log(result);
}

html 조작 시 주의 사항

//첫번째 실습
let 제목 = document.querySelector('#title');

// ### narrowing 방법 5가지
//힌트) 제목에 커서 올려보면 Element | null

//null인 경우 제외
if (제목 !== null) {
  제목.innerHTML = '반가워요';
}

// 가장 권장하는 방법
if (제목 instanceof Element) {
  제목.innerHTML = '반가워요';
}

// optional 연산자
if (제목?.innerHTML) {
  제목.innerHTML = '반가워요';
}

//assertion
let 제목2 = document.querySelector('#title') as Element;

// 5번째 tsconfig의 nullcheck 옵션 false

//두번째 실습
let 링크 = document.querySelector('.link');

// element의 정확한 타입명을 지정.
if (링크 instanceof HTMLAnchorElement) {
  링크.href = 'https://kakao.com';
}

/*
HTMLAnchorElement
HTMLHeadingElement
HTMLButtonElement
...
*/

//세번째 실습
let 버튼 = document.querySelector('#button');

// 얘는 아래처럼 희안하게 옵셔널만으로 퉁쳐짐.
버튼?.addEventListener('click', function () {});

연습해보기

// ### 연습해보기1
// src의 src를 new.jpg로 바꾸기

let 이미지 = document.querySelector('#image');
if (이미지 instanceof HTMLImageElement) {
  이미지.src = 'new.jpg';
  console.log(이미지.src);
}

// ### 연습해보기2
// 여러개의 html요소 전부 바꾸기.

let 앵커들 = document.querySelectorAll('.naver');
console.log(앵커들);

앵커들.forEach((element) => {
  if (element instanceof HTMLAnchorElement) {
    element.href = 'kakao.com';
  }
});

생성자함수, 클래스, 프로토타입에 관한 글

class만들 때 타입지정

//## class, prototype 벨로그 링크.

// ## class에 타입지정하기

class Person {
  name;
  age;
  constructor(a = '길동', b = 20) {
    this.name = a;
    this.age = b;
  }

  함수(a: string): void {
    console.log(`hi ${this.name}${a}`);
  }
}

let 홍길동 = new Person('길동', 10);
console.log(홍길동);

홍길동.함수('님');

// ### 연습해보기

// 위 Person class의 컨스트럭터의 파라미터 타입
// 지정 안해도 되는 방법이 있다는데?
// a,b가 undefined면 기본값 설정해둔 걸로 들어감.
constructor(a = '길동', b = 20){
  this.name = a;
  this.age = b
}

연습해보기

class Person {
  add(숫자: number): void {
    console.log(숫자 + 1);
  }
}

//### 연습
class Car {
  model;
  price;
  constructor(a: string, b: number) {
    this.model = a;
    this.price = b;
  }

  tax(): void {
    console.log(this.price * 0.1);
  }
}

let car1 = new Car('소나타', 3000);

console.log(car1);
//Car { model: '소나타', price: 3000 }
car1.tax();
//300

//### 연습2
class Word {
  nums;
  strings;

  constructor(a: (number | string)[]) {
    this.nums = [];
    this.strings = [];
    for (let i = 0; i < a.length; i++) {
      if (typeof a[i] === 'string') {
        this.strings.push(a[i]);
      } else {
        this.nums.push(a[i]);
      }
    }
  }
}

let arr = new Word(['a', 1, 'kim', 123123]);

console.log(arr);
//Word { nums: [ 1, 123123 ], strings: [ 'a', 'kim' ] }

// 모범 답안.
class Word2 {
  num;
  str;

  constructor(...param: (number | string)[]) {
    let 숫자들: number[] = [];
    let 문자들: string[] = [];

    param.forEach((i) => {
      if (typeof i === 'string') {
        문자들.push(i);
      } else {
        숫자들.push(i);
      }
    });

    this.num = 숫자들;
    this.str = 문자들;
  }
}

interface

type Square = {
  color: string;
  width: number;
};

//위랑 같음
interface Square2 {
  color: string;
  width: number;
}

let 네모: Square2 = { color: 'red', width: 100 };
  • 중간퀴즈

interface Student {
  name: string;
}
// interface Teacher {
//   name: string;
//   age: number;
// }

// extends이용하여 인터페이스 하나로 쓰기
interface Teacher extends Student {
  age: number;
}

let 학생: Student = { name: 'kim' };
let 선생: Teacher = { name: 'kim', age: 20 };

// type alias에서 합쳤던거 기억하기
//intersection type
// & 좌 우 다 만족하는 객체 타입 지정해주세요~
type dog = { name: string };
type cat = { age: number } & dog;

const animal: cat = {
  age: 123,
  name: 'gg',
};

타입과의 차이와 장점


// 1. interface는 중복선언 가능
interface A {
  name: string;
}
interface A {
  age: number;
}
//A인터페이스는 name과 age 모두 가짐.

// 2. 장점 외부 라이브러리의 interface를
// 내가 확장(커스터마이징)해서 사용 가능.

// 다른 사람들이 내가만든 obj 많이 쓸 것 같으면
// interface 권장.

근데 interface의 속성이 중복되면?


interface 중복 {
  name: string;
}
interface 중복대상 extends 중복 {
  name: number;
}

//아래 상황이나 똑같은 상황이라 허용하지 않음.
interface 중복대상 {
  name: string;
  name: number;
}

& 쓸 떄 중복이 발생하면 ?

type Animal = { name: string };
type Tajo = { name: number } & Animal;
// 합칠 때는 에러가 발생하지 않는다.

let a: Tajo = { name: 'ss' }; // type never
// 하지만 Tajo(number)도 만족하고
// Animal(string)도 만족하라는 명령이라
// string이나 number를 값으로 할당해주면
//에러가 발생한다.

연습해보기

interface Product {
  brand: string;
  serialNumber: number;
  model: string[];
}

let 상품: Product = {
  brand: 'Samsung',
  serialNumber: 1360,
  model: ['TV', 'phone'],
};
interface Bucket {
  product: string;
  price: number;
}

let 장바구니: Bucket[] = [
  { product: '청소기', price: 7000 },
  { product: '삼다수', price: 800 },
];
interface Bucket {
  product: string;
  price: number;
}

let 장바구니: Bucket[] = [
  { product: '청소기', price: 7000 },
  { product: '삼다수', price: 800 },
];

interface Bucket2 extends Bucket {
  card: boolean;
}

{ product : '청소기', price : 7000, card : false }
  1. 어떻게 interface 만들 수 있을까?
interface Calc {}

let obj = {
  plus(a, b) {
    return a + b;
  },
  minus(a, b) {
    return a - b;
  },
};
interface Calc {
  plus: (a: number, b: number) => number;
  minus: (a: number, b: number) => number;
}

let obj: Calc = {
  plus(a, b) {
    return a + b;
  },
  minus(a, b) {
    return a - b;
  },
};
profile
주먹구구식은 버리고 Why & How를 고민하며 프로그래밍 하는 개발자가 되자!

0개의 댓글