[TypeScript] 타입

string_main·2023년 1월 23일
0

TypeScript

목록 보기
2/2
post-thumbnail

TypeScript의 타입


타입예시설명
number1, 5.3, -10모든 숫자들, 정수와 실수형 간 차이 없음
string‘Hi’, “Hi”, `Hi`모든 문자 값
booleantrue, false오직 참과 거짓 두 가지 값
object{age: 30}모든 자바스크립트 객체, 객체의 타입 지정을 구체적으로 명시할 수 있음
array[1, 2, 3]모든 자바스크립트 배열, 타입은 요소의 타입에 따라 유연하거나 엄격할 수 있음
tuple[1, 2]타입스크립트에 의해 추가된 타입: 정해진 길이의 배열
enumenum {NEW, OLD}타입스크립트에 의해 추가된 타입: 자동적으로 열거된 전역 상수 식별자
any*모든 종류의 값, 타입 배정이 필요하지 않음
unknown*모든 종류의 값, any와 유사하지만, unknown 타입에 어떤 것을 대입하는 것이 유효하지 않아 더 안전함
functionfunction a(n1: number, n2: number) : number매개변수 타입과 반환 타입 표기가 가능함
voidfunction noop(): void { return; }값을 반환하지 않는 함수의 반환 값을 의미함
unionnumberstring
intersectionPerson & Developerunion이 or이라면 intersection은 and, 자주 사용되는 방법은 아님
literal'as-number''as-text'
nullnull명시적으로 빈 값을 나타냄
undefinedundefined선언 후 아무 값도 할당받지 않은 초기 상태
neverfunction fail(msg: string): never { throw new Error(msg); }절대 관측될 수 없는 값을 의미, 함수가 예외를 발생시키거나, 프로그램 실행을 종료함을 의미함

TypeScript의 장점 및 Tips


function add(n1, n2) {
  return n1 + n2;
}

const number1 = '5';
const number2 = 2.8;

const result = add(number1, number2);
console.log(result); // 52.8

위의 예시는 수학적 계산 결과가 아니라 문자열과 숫자가 연결되어 반환된다.
이러한 부분은 에러를 찾기 어렵게 만들고 버그로 이어질 가능성이 있다.

  • 타입스크립트를 사용하면 타입 에러를 개발 단계에서 발견할 수 있다.

  • 타입스크립트는 컴파일을 차단하지 않고 실수를 알려준다.

  • 동일한 이름의 js 파일과 ts 파일을 동시에 열어두면 Duplicate function implementation 오류가 발생하니 주의하자.

  • 타입스크립트 기능은 자바스크립트 엔진에 내장되어 있지 않기 때문에 개발 단계에서만 지원을 받을 수 있다. 그래서 타입스크립트는 브라우저에서 실행할 수 없으며 개발 단계에서 컴파일 할 때만 실행 가능하다.

  • 타입스크립트의 주요 원시 타입은 모두 소문자이다.

타입 추론 (Type Inference)


let number1: number = 5;
  • 타입스크립트는 위와 같이 타입 배정을 하지 않아도 타입 추론이라는 내장 기능을 활용해 추론한다.
let str = "test";
str = 0; // 에러 발생
  • 숫자형이 할당되어 타입 추론을 알아서 해주기 때문에 굳이 타입을 명시할 필요가 없다. 명시적인 타입 지정은 좋은 방식이 아니다.

객체 (Object)


const person: {
    name: string;
    age: number;
}
  • 타입스크립트의 객체 타입 정의는 쉼표가 아닌 세미 콜론으로 마무리 된다.

JavaScript 객체

const product = {
  id: 'abc1',
  price: 12.99,
  tags: ['great-offer', 'hot-and-new'],
  details: {
    title: 'Red Carpet',
    description: 'A great carpet - almost brand-new!'
  }
}

TypeScript 타입

{
  id: string;
  price: number;
  tags: string[];
  details: {
    title: string;
    description: string;
  }
}

배열 (Array)과 튜플 (Tuple)


  • any는 유연하지만 타입 등의 좋은 기능들을 활용하지 못하게 된다.
const person = {
  name: 'Maximilian',
  age: 30,
  hobbies: ['Sports', 'Cooking'],
  role: [2, 'author']
};

person.role.push('admin');
person.role[1] = 10;
  • role 속성은 배열의 첫번째 요소가 숫자여야 하고 두번째 요소가 문자열이어야 하는데, 공용 타입(union)으로는 구조를 명시할 수 없다. 순서에 상관없이 두 타입 모두 가능하기 때문이다. 이러한 경우에 튜플(tuple)이 적합하다.
const person: {
  name: string;
  age: number;
  hobbies: string[];
  role: [number, string];
} = {
  name: "Maximilian",
  age: 30,
  hobbies: ["Sports", "Cooking"],
  role: [2, "author"],
};

person.role.push('admin');
person.role[1] = 10; // 에러 발생
  • push는 예외적으로 튜플에서 허용되어서 에러를 걸러낼 순 없지만, 적어도 잘못된 값을 할당하지는 않는다.

열거형 (Enum)


const person = {
  name: "Maximilian",
  age: 30,
  hobbies: ["Sports", "Cooking"],
  role: 2,
};

// role 2가 admin 권한인지 read only인지..
  • role 등을 설정할 때 숫자로만 설정한다면 사람이 바로 알기 어려워서 사람이 이해하기 쉬운 식별자로 설정하는 것이 효율적이다.
const person = {
  name: "Maximilian",
  age: 30,
  hobbies: ["Sports", "Cooking"],
  role: 'READ ONLY USER',
};

if(person.role === 'READ-ONLY-USER') {
  console.log('is read only');
}
  • 그렇다고 또 문자열로 식별자를 설정하게 되면, 정확한 문자열을 알고있어야 하는 단점이 존재한다.
const ADMIN = 0;
const READ_ONLY = 1;
const AUTHOR = 2;

const person = {
  name: "Maximilian",
  age: 30,
  hobbies: ["Sports", "Cooking"],
  role: ADMIN,
};

if (person.role === ADMIN) {
  console.log("is admin");
}
  • 이 방법도 나쁘지 않지만, 모든 상수를 정의하고 관리해야 하는 문제점이 존재한다.
enum Role { ADMIN, READ_ONLY, AUTHOR };

const person = {
  name: "Maximilian",
  age: 30,
  hobbies: ["Sports", "Cooking"],
  role: Role.ADMIN,
};

if (person.role === Role.AUTHOR) {
  console.log("is author");
}
  • enum이 각 lable을 숫자로 할당하게 해주어 편리하다.
var Role;
(function (Role) {
    Role[Role["ADMIN"] = 0] = "ADMIN";
    Role[Role["READ_ONLY"] = 1] = "READ_ONLY";
    Role[Role["AUTHOR"] = 2] = "AUTHOR";
})(Role || (Role = {}));
;
  • 컴파일 하면 enum이 구현된 자바스크립트 코드를 확인할 수 있다.
enum Role { ADMIN='admin', READ_ONLY=1, AUTHOR };
  • 초기값 지정도 가능하다.

Any


  • 유연한 타입이라 편할 것 같지만?… 사용하면 자바스크립트 쓰는거랑 진배없다^^
  • 어떤 값도 저장하지 않아서 타입스크립트 컴파일러가 작동하지 않음.
  • 어떤 값이나 종류의 데이터가 어디에 저장될지 알 수 없는 경우에 대비하거나 런타임 검사 수행 시 작업의 범위를 좁히기 위해 사용하는 것 이외의 경우에는 사용하지 않는 것이 좋다.
// 런타임 검사 예시
if(typeof n1 !== 'number' || typeof n2 !== 'number') {
	throw new Error('Incorrect input');
}

조합 (Union)


function combine(input1: number | string, input2: number | string) { // Union Type
  const result = input1 + input2; // Error: Operator '+' cannot be applied to types 'string | number' and 'string | number'.

  return result;
}

const combinedAges = combine(30, 26);
console.log(combinedAges);

const combinedNames = combine('Max', 'Anna');
console.log(combinedNames);
  • 더하기 연산자는 숫자와 문자열 모두 사용할 수 있어서 문제가 없어야 하지만, 타입스크립트는 유니언 타입만 이해할 뿐 유니언 타입 내에 무엇이 있는지는 분석하지 못해서 더하기 연산자를 사용할 수 없다고 판단해 오류가 발생한다.
function combine(input1: number | string, input2: number | string) {
  let result;

  if(typeof input1 === 'number' && typeof input2 === 'number') {
    result = input1 + input2;
  }else {
    result = input1.toString() + input2.toString();
  }
  
  return result;
}

const combinedAges = combine(30, 26);
console.log(combinedAges);

const combinedNames = combine('Max', 'Anna');
console.log(combinedNames);
  • 런타임 타입 검사를 추가해 해결할 수 있다.

  • Union 타입을 사용하면 매개변수를 보다 유연하게 사용할 수 있다.

리터럴 (Literal)


function combine(
  input1: number | string,
  input2: number | string,
  resultConversion: string
) {
  let result;

  if (typeof input1 === "number" && typeof input2 === "number" || resultConversion === 'as-number') {
    result = +input1 + +input2;
  } else {
    result = input1.toString() + input2.toString();
  }

  return result;
}

const combinedAges = combine(30, 26, "as-number");
console.log(combinedAges);

const combinedStringAges = combine('30', "26", "as-number");
console.log(combinedStringAges);

const combinedNames = combine("Max", "Anna", "as-text");
console.log(combinedNames);
  • 이 방법의 단점은 개발자가 문자열을 기억해야 한다는 점, 실수가 발생할 수 있다.
function combine(
  input1: number | string,
  input2: number | string,
  resultConversion: 'as-number' | 'as-text' // Literal Type
) {
  let result;

  if (typeof input1 === "number" && typeof input2 === "number" || resultConversion === 'as-number') {
    result = +input1 + +input2;
  } else {
    result = input1.toString() + input2.toString();
  }

  return result;
}

const combinedAges = combine(30, 26, "as-number");
console.log(combinedAges);

const combinedStringAges = combine('30', "26", "as-number");
console.log(combinedStringAges);

const combinedNames = combine("Max", "Anna", "as-text");
console.log(combinedNames);
  • 리터럴 타입으로 개선 가능, 오타 시 Error 발생

별칭 (Alias)


  • 유니언 타입으로 작업을 수행할 때 매번 반복하는 것은 유지보수에 번거롭다. → 타입에 별칭을 지정해 해결!
    // Type Alias
    type Combinable = number | string;
    type ConversionDescriptor = "as-number" | "as-text";
    
    function combine(
      input1: Combinable,
      input2: Combinable,
      resultConversion: ConversionDescriptor
    ) {
      let result;
    
      if (
        (typeof input1 === "number" && typeof input2 === "number") ||
        resultConversion === "as-number"
      ) {
        result = +input1 + +input2;
      } else {
        result = input1.toString() + input2.toString();
      }
    
      return result;
    }
    
    const combinedAges = combine(30, 26, "as-number");
    console.log(combinedAges);
    
    const combinedStringAges = combine('30', "26", "as-number");
    console.log(combinedStringAges);
    
    const combinedNames = combine("Max", "Anna", "as-text");
    console.log(combinedNames);
  • 객체에도 별칭 지정 가능
    type User = { name: string; age: number };
    const u1: User = { name: 'Max', age: 30 };
  • 아래와 같이 사용할 수도 있다.
    type User = { name: string } | string;
    let u1: User = {name: 'Max'};
    u1 = 'Michael';

함수 반환 타입과 void


function add(n1: number, n2: number): string {
  return n1 + n2; // Error!
}
  • 리턴 타입도 명시할 수 있지만, 타입스크립트가 추론하게 하는 것이 바람직함.
function add(n1: number, n2: number) {
  return n1 + n2;
}

function printResult(num: number): void {
  console.log('Result: ' + num);
}

printResult(add(5, 12)); // Result: 17
console.log(printResult(add(5, 12))); // undefined
  • 함수가 아무것도 반환하지 않으면 void 타입을 가진다.
function add(n1: number, n2: number) {
  return n1 + n2;
}

function printResult(num: number): undefined { // Error: A function whose declared type is neither 'void' nor 'any' must return a value.
  console.log('Result: ' + num);
	// return; // return을 추가하면 에러가 발생하지 않음
}

printResult(add(5, 12));
console.log(printResult(add(5, 12))); // undefined
  • 리턴 값이 없는 함수를 찍어보면 undefined가 나오는데, 그렇다고 함수 반환 타입을 undefined로 하게 되면 에러가 발생한다. 타입스크립트에서는 의도적으로 함수의 반환문이 없다는 것을 void로 명시해주어야 한다.
function add(n1: number, n2: number) {
  return n1 + n2;
}

let combineValues; // any

combineValues = add;
combineValues = 5; // Runtime Error

console.log(combineValues(8, 8));
  • 함수를 변수에 할당한 후, 타입 지정을 해주지 않으면 any로 지정됨 → 런타임 에러 발생
function add(n1: number, n2: number) {
  return n1 + n2;
}

function printResult(num: number): void {
  console.log('Result: ' + num);
}

let combineValues: Function;

combineValues = add;
combineValues = 5; // Compile Time Error
combineValues = printResult; // Runtime Error

console.log(combineValues(8, 8));
  • 이 방법도 괜찮지만, 매개변수가 다른 함수가 할당될 수 있기 때문에 완벽하지 않은 방법이다.
let combineValues: (a: number, b: number) => number;
  • 함수 타입을 사용하여 이와 같이 개선할 수 있다.

  • 콜백과 함수 타입은 거의 같은 방식으로 생성된다.

function addAndHandle(n1: number, n2: number, cb: (num: number) => void) {
  const result = n1 + n2;
  cb(result);
}

addAndHandle(10, 20, (result) => {
  console.log(result);
  return result; // void라 정의해도 에러가 안남
});
  • 버그가 아니라 기본적으로 callback이 반환하는 값으로는 어떤 작업도 수행하지 않는다는 것을 명시하는 용도. 콜백 함수는 자신이 전달되는 인수가 반환 값을 기대하지 않는 경우에도 값을 반환할 수 있다.

알 수 없는 (Unknown) 타입


  • 어떤 사용자가 무엇을 입력할지 알 수 없을 때 사용하는 타입.
let userInput: unknown; // any일 경우 에러 안 남
let userName: string;

userInput = 5;
userInput = 'Max';
userName = userInput; // Error: Type 'unknown' is not assignable to type 'string'.
let userInput: unknown;
let userName: string;

userInput = 5;
userInput = "Max";

if (typeof userInput === "string") {
  userName = userInput;
}
  • 추가적인 타입 검사를 해 주면 에러 해결.
  • unknown이 any보다 좀 더 제한적이기 때문에 더 나은 선택이다.

절대 (Never) 타입


  • void와 다르게 함수가 반환할 수 있고, 아무것도 반환하지 않는다는 것을 명시하는 타입이다.
  • 관찰되지 않는 값을 나타내며, 리턴 타입에서 함수가 예외를 throw 하거나 프로그램 실행을 종료함을 의미한다. union에 남은 것이 없다고 판단할 때도 never가 나타난다.
function generateError(message: string, code: number) {
  throw { message: message, errorCode: code };
}

const result = generateError("An error occurred!", 500);
console.log(result); // 콘솔에 찍히지 않음
  • generateError 함수는 never를 반환함. throw 에러가 스크립트와 충돌해 스크립트가 취소된다.
  • 함수에 커서를 올리면 반환값이 void로 나오는데, never가 타입스크립트의 초기 버전부터 사용되진 않았기 때문에 반영이 되지 않은 것.
  • 장점 :
    • 코드 품질 관점에서 의도를 분명히 할 수 있다.
    • 스크립트 충돌을 일으켜 무한루프를 차단하려는 개발자 간 의도를 알 수 있게 한다.

이 글은 Udemy - Typescript :기초부터 실전형 프로젝트까지 with React + NodeJS 강의를 보고 정리한 내용입니다.
잘못된 부분이 있다면 댓글로 피드백 주시면 감사하겠습니다!

profile
FE developer

0개의 댓글