타입스크립트 기본 타입

타입스크립트 기본 & 기본 타입

Intro


타입스크립트 기본 타입


타입스크립트의 주요 원시 타입은 모두 소문자이다. String (X) string (O)

number 타입


정수, 실수형에 따른 타입은 없으며 숫자는 모두 number 타입이다.

ts
let num: number = 0;

num = 1;
num = 5.2;
num = 10;

string 타입


일반적인 문자 및 문자열 타입이다.

ts
let str: string = "";

str = "Hi";
str = "Hi";
str = `Hi`;

Boolean 타입


일반적인 Boolean 타입이다.

ts
let check: booelan = false;

check = true;
check = false;

타입 할당과 추론


타입 할당


아래 코드의 add함수의 경우 매개변수마다 타입이 명시적으로 지정되어있다. 이를 타입 할당이라고 한다.

ts
// app.ts
function add(n1: number, n2: number, showResult: boolean, phrase: string) {
  const result = n1 + n2;
  if (!showResult) {
    console.log(phrase + result);
  } else {
    return result;
  }
}

타입 추론


명시적으로 타입이 배정되지 않은 경우 초기화된 값으로 타입을 추론해 할당한다. number1, number2의 경우 number타입 값으로 초기화 했디 때문에 number타입이다. let 변수 방식으로 초기화 한 변수의 타입을 보면 number인 것을 볼 수 있다.

만일 매개변수 타입이 고정되지 않는 경우 타입을 지정하지 않는 방식으로 사용한다. 이때 타입은 any타입이다. 추론된 타입이라도 해당 타입을 어기면 에러가 발생한다.

ts
// app.ts
let number1 = 5; // let nubmer1 : number = 5;
const number2 = 2.8;
const printResult = true;
let resultPhrase = "Result is: "; // string

resultPhrase = 0; // Type '0' is not assinable to type 'string'.

객체(object) 형태에서의 타입


객체 형태의 키 또한 타입 할당/추론을 통해 타입이 매겨진다.

객체 형태의 타입 추론


객체 형태의 타입 추론의 경우 타입을 명시하지 않으면 초기화된 값으로 타입이 매겨진다.

ts
// basic.ts

const person = {
    name: 'Lim', // string
    age: 27 // number
};

console.log(person);
>> {name: "Lim", age: 27}

console.log(person.name);
>> Lim

객체 형태의 타입 할당


객체에 타입을 지정하는 경우 키와 값의 타입을 할당해야한다. object로 타입을 정의한 경우 키와 값의 타입 지정이 안되어 있기 때문에 값을 조회하는 경우 에러가 발생한다.

때문에 키와 타입의 값이 만들어진 {} 타입을 할당한다.

ts
const persontest: object = {
    name: 'Lim',
    age: 27
}

console.log(persontest.name)
>> Property 'name' does not exist on type 'object';

const person: {
    name: string;
    age: number;
} = {
    name: 'Lim',
    age: 30
};

console.log(person.name)
>> Lim

객체 안에 객체 타입을 지정하는 경우


객체 안에 객체가 있는 경우의 타입도 할당/추론을 통해 타입이 매겨진다.

ts
const person: {
  name: string;
  age: number;
  address: {
    zip: number;
    datail: string;
  };
} = {
  name: "Lim",
  age: 27,
  address: {
    zip: 41242,
    detail: "서울특별시 중랑구",
  },
};

배열(Array) 타입


배열타입의 경우 자료형[] 형태로 타입을 할당할 수 있다.

ts
// array.ts
const person = {
  name: "Lim", // string
  age: 27, // number
  hobbies: ["Health", "Game"], //string[]
};

여러 타입의 값이 들어가는 경우 any를 이용할 수 있다.

ts
// array.ts
let favoriteActivities: any[];
favoriteActivities = ["Health", 1];

타입 추론을 통해 할당된 타입을 통해 for 문을 이용하는 경우 타입을 지정하지 않아도 된다.

ts
for (const hobby of person.hobbies) {
  // const hobby: sring
  console.log(hobby); // 이미 person.hobbies가 string인 이유로
  // 추론을 통해 string인 것을 알 수 있음.
}

튜플(Tuple) 타입


타입스크립트에서 새로 추가된 타입이다. (자바스크립트에는 없다.)
배열과 비슷하지만 길이와 타입이 고정된다.

만일 [2,'author']과 같이 값이 할당된 경우 해당 튜플의 타입은 (string | number)[] 이 된다.
이는 첫 요소는 문자열 두번째 요소는 문자열를 저장할 수 있는 배열을 값으로 가질 수 있다. 공용체 타입[unit] 이라고도 한다.

ts
// tuple.ts
const person = {
  name: "Lim", // string
  age: 27, // number
  hobbies: ["Health", "Game"], //string[]
  role: [2, "author"], // role : (string | number)[]
};

타입 할당은 아래와 같은 방법을 사용한다.

값을 할당하는 도중 push의 경우는 에러를 발생시키지 않는다. 예외적으로 튜플에서 허용되기 때문이다.
하지만 에러 표시를 하지 않을뿐 길이를 넘긴다면 잘못된 값을 할당하진 않는다.

ts
// tuple.ts
const person: {
  name: string;
  age: number;
  hobbies: string[];
  role: [number, string]; // 튜플 타입
} = {
  name: "Lim", // string
  age: 27, // number
  hobbies: ["Health", "Game"], //string[]
  role: [2, "author"], // role : (string | number)[]
};

person.role.push("admin"); // 푸시는 예외적으로 튜플에서 허용되어 타입스크립트는 에러를 발생시키지 않는다.
person.rile[1] = 10; // 두번째 요소 타입은 string으로. number를 두번째 요소로 지정하면 에러가 발생한다.

person.role = [0, "admin"];

배열의 타입을 이미 알고 있는 상황에서는 작업 중인 데이터 타입과 예상되는 데이터 타입은 명확히 파악할 수 있다.

열거형(Enum) 타입

식별자 및 전역 상수등을 정의하는 열거형타입또한 타입스크립트에서 추가되었다.
쉽게 사용권한을 체크하는 경우 아래와 같은 코드를 사용할 수 있을 것이다.

ts
// enum.ts
const ADMIN = 0;
const READ_ONLY = 1;
const AUTHOR = 2;

const person = {
    name: 'Lim',
    age: 27,
    hobbies: ['Health', 'Game'],
    role: ADMIN
}

if(person.role === ADMIN) {
    console.log("is admin")
    >> is admin
}

enum 키워드로 보다 효울적으로 위와같은 권한의 상수를 정의하고 관리할 수 있다.
enum 은 각 라벨을 숫자로 할당해준다.

ts
// enum.ts
enum Role {
  ADMIN,
  READ_ONLY,
  AUTHOR,
} // ADMIN = 0, READ_ONLY = 1, AUTHOR = 2

const person = {
  name: "Lim",
  age: 27,
  hobbies: ["Health", "Game"],
  role: Role.ADMIN,
};

if (person.role === Role.AUTHOR) {
  console.log("is author");
}

컴파일 이후에는 아래와 같은 자바스크립트로 컴파일되어 브라우저에 적용된다.

js
var Role;
(function (Role) {
  Role[(Role["ADMIN"] = 0)] = "ADMIN";
  Role[(Role["READ_ONLY"] = 1)] = "READ_ONLY";
  Role[(Role["AUTHOR"] = 2)] = "AUTHOR";
})(Role || (Role = {}));

var person = {
  name: "Lim",
  age: 27,
  hobbies: ["Headlth", "Game"],
  role: Role.ADMIN,
};

아래와 같이 직접 특정 숫자로 지정할 수 있다.

ts
// ADMIN을 5로 지정한 경우 뒤에 라벨들은 +1 씩 증가한다.
enum Role {
  ADMIN = 5,
  READ_ONLY,
  AUTHOR,
} // ADMIN = 5, READ_ONLY = 6, AUTHOR = 7

// 문자도 지정할 수 있다.
enum Role {
  ADMIN = "ADMIN",
  READ_ONLY = 100,
  AUTHOR = "AUTHOR",
} // ADMIN = "ADMIN",, READ_ONLY = 100, AUTHOR = "AUTHOR"

모든(Any) 타입


가장 유연한 타입으로 모든 종류의 값을 저장할 수 있다.

ts
// any.ts
let favoriteActivities: any[];
favoriteActivities = ["Health", 1];

단 타입스크립트가 주는 모든 장점을 any가 상쇄시키기 때문에 타입스크립트 컴파일러가 작동하지 않는다.
따라서 어떤 값이나 종류의 데이터가 어디에 저장될지 알 수 없는 경우 혹은 런타임 검사를 수행하는 경우에만 사용한다.
그 외에는 any를 사용하지 않는게 좋다.

조합(Union) 타입


아래와 같은 예시로 숫자, 문자가 합쳐진 결과를 반환하는 경우 매개변수에 Union(|) 타입 을 이용해 숫자, 문자 타입을 받을 수 있도록 허용할 수 있다.

단, 연산과정에서 타입스크립트는 유니언 타입만 이해할뿐 유니언 타입 내 어떤 타입의 값이 담겨있는지 알지 못한다. 때문에 이를 if문을 통한 검증으로 해결할 수 있다.

ts
// app.ts
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);
>> 56

const combinedNames = combine('Max', 'Anna');
console.log(combinedNames);
>> MaxAnna

타입에 따른 다른 로직을 적용해야하므로 여러 유형의 값으로 작동할 수 있다.

리터럴 타입


리터럴 타입은 단순한 변수나 매개변수가 아니며 숫자나 문자열도 아니다. 리터럴 타입은 정확한 값을 가지는 타입이다.

ts
// literal.ts
const number2 = 2.8; // const number2: 2.8

코드와 같이 number2를 오버하면 const number2: 2.8이라고 표시된다. 이와 같은 상수는 할당된 값이 정확히 지정된 타입임을 알기 때문이다. 숫자형 타입에서 도출된 값이면서도 특정한 값이다. 문자열도 같다.

위의 조합(Union) 타입에서 세번째 매개변수를 추가해보록하자. 세번째 매개변수는 반환값으로 string, number를 선택할 수 있다.
여기서 string결과를 원한다면 값으로 as-text number결과를 원한다면 값으로 as-number 를 입력한다.
(이런 경우 enum 으로 타입 설정 후 사용하는것도 좋다.)

ts
// app.ts
function combine(
  input1: number | string, 
  input2: number | string, 
  resultConversion: 'as-number' | 'as-text'
) {
  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');
>> 56

const combinedStringAges = combine('30', '26', 'as-number');
>> 56

const combinedNames = combine('Max', 'Anna', 'as-text');
>> MaxAnna

combine 함수의 매개변수로 resultConversion이 추가되었다. 해당 값의 타입으로는 string(원시 타입)이 아닌 특정한(정확한) 값인, as-number 혹은 as-text 타입을 가진다.
이외에 값은 허용되지 않는다. 이와같이 특정 문자 및 숫자와 같은 타입을 리터럴 타입이라고한다.

타입 엘리어스(사용자 정의 타입)


type(사용자 정의 타입또한 타입스크립트에서 새로 추가된 타입이다. 구문은 다음과 같다. type 별칭 = 인코딩 타입 보통 유니언 타입의 별칭으로 이용한다.

ts
// app.ts
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;
}

유니온 타입뿐만 아니라 복잡할 수 있는 객체 타입에도 별칭을 지정할 수 있다.

ts
type User = { name: string; age: number };
const u1: User = { name: 'Lim', age: 27 };

또한 불필요한 반복을 피하고 타입을 중심에서 관리할 수 있다.

js
function greet(user : { name: string; age: number }) {
  console.log('Hi, I am ' + user.name);
}

function isOlder(user : {name: string; age: number }, checkAge: number ) {
  return checkAge > user.age;
}

타입 별칭 사용 후

ts
type User = { name: string; age: number};

function greet(user: User) {
  console.log('Hi, I am ' + user.name);
}

function isOlder(user: User, checkAge: number) {
  return checkAge > user.age;
}

함수 반환 타입과 void 타입


다음과 같이 add함수의 경우 입력값이 number 이므로 결과또한 number 라고 추론한다.

ts
function add(n1: number, n2: number){
  return n1 + n2; // number + number 이므로 반환타입을 number로 추론한다.
  return n1.toString() + n2.toString(); // string + string 이므로 반환타입을 string으로 추론한다.
}

이때 함수명에 : 타입을 지정하면 반환타입을 명시할 수 있다. 타입을 명시적으로 설정해야 하는 경우 타입을 할당한다.

ts
function add(n1: number, n2: number) : string{
  return n1.toString() + n2.toString();
}

타입스크립트에서 새로 추가된 타입으로 void타입이 추가되었는데, 이는 반환값이 없음을 의미한다.

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

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

printResult(add(5, 12));
console.log(printResult(add(5,12)));

>> Result: 17
>> undefined // 존재하지 않는 객체 속성에 접근하려는 경우 출력되는 자바스크립트의 값

위 코드에는 반환타입으로 void로 할당했는데,
함수의 반환값이 없고 반환타입을 명시하지 않은 경우 타입스크립트는 해당 함수의 반환 타입을 void로 추론한다.

또한 void 반환 타입을 가진 함수를 console.log 로 호출하면 undefined를 반환하는 것을 볼 수 있는데, 앞서 말했듯이 void타입은 반환 값이 없다.
즉 타입스크립트에서 undefined이 아닌 타입으로 정의한다. void 반환 타입을 가진 함수는 undefined를 비롯해 아무것도 반환하지 않는다void를 사용해야한다.

아주 드문 경우로, 값을 반환하지 않는 함수를 사용하는 경우 void를 표준으로 사용하지만 반환 타입을 undefined로 지정한다면 실제 값을 반환하지 않을 때 사용할 수 있다.

타입의 기능을 하는 함수


다음과 일반 자바스크립트와 같이 변수에 값으로 함수를 저장하여 사용할 수 있다.

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

printResult(add(5, 12));

let combineValues;

combineValues = add;

console.log(combineValues(8, 8))
>> 16

combineValuesany 타입으로, 다음과 같이 코드를 작성한 경우, 안타깝게도 타입스크립트는 코드 에러를 발생시키지 않는다. 해당 에러는 런타임시 발생하게 된다.

ts
let combineValues;

combineValues = add;
combineValues = 5;

console.log(combineValues(8, 8))
>> Uncaught TypeError: combineValues is not a function

이런 에러를 방지하려면 해당 변수의 타입을 함수로 할당해야한다.

ts
let combineValues: Function;

combineValues = add;
combineValues = 5; // Type '5' is not assignable to tyle 'Functuon'.

console.log(combineValues(8, 8))
>> Uncaught TypeError: combineValues is not a function

다음과 같은 경우 문제가 발생하는데 현재combineValues 변수는 타입이 Function이다. 여기서 printResult함수로 저장해도 타입스크립트는 에러를 검출하지 못한다.

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

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

let combineValues: Function;

combineValues = add;
combineValues = printResult;

console.log(combineValues(8, 8))
>> undefined

화살표 함수로 해당 문제를 상쇄할 수 있다. 매개변수 타입과 반환 타입을 명시하면 에러를 검출할 수 있다.

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

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

let combineValues: (a: number, b: number) => number;

combineValues = add;
combineValues = printResult; // Type '(num: number) => void' is not assignable to type '(a: number,
                             // b: number => number'. Type 'void' is not assignable to type 'number'.

함수 타입 및 콜백


콜백과 함수 타입은 유사하게 동작한다.

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

1번 라인 함수를 보면 callback 함수는 매개변수로 number를 받아들이며 반환 타입은 void이다. 매개변수에 콜백 함수를 할당하는 경우의 이점은 함수 호출시 이미 함수내에서 콜백에 타입을 명시했기 때문에 넘겨주는 콜백의 타입을 지정하지 않아도 된다.

1번 라인 함수에서 콜백 매개변수의 반환 타입은 void이다. 이때 함수 호출시 콜백 함수로 값을 반환한다면 어떻게 될까? 타입스크립트는 이를 에러로 판단하지 않는다.
callback 타입에 void를 선언함으로서 해당 콜백 함수의 반환 결과를 모두 무시하게 되기 때문이다. 그래서 addAndHandle함수의 callback 함수가 return타입으로 아무 작업을 수행하지 않을 것이라고 입력한 것이다.

따라서 함수 호출시 아무 문제 없이 무언가를 return할 수 있는데, 이유는 addAndHandle함수에서 해당 콜백에서 반환되는 값으로 아무 작업도 수행하지 않는다고 callback 타입에 명확히 정의되어 있기 때문이다.

unknown(알 수 없는) 타입


unknown은 타입스크립트에서 기본 제공되는 any와는 다른 타입이다. 어떤 사용자가, 어떤 값(숫자, 문자열...)을 입력하는지 알 수 없는 타입이다.

ts
// app.ts
let userInput: unknown;
let userInput2: any;

let userName: string;

userInput = 5;
userInput = 'Lim';

userInput2 = 5;
userInput2 = 'Lim';

userName = userInput; // Type 'unknown' is not assginable to type 'string'.
userName = userInput2;

위와 같이 any타입은 원하는대로 작성이 가능한 반면 unknown은 보다 좀 더 제한적으로 작동한다. unknown 의 경우 값이 무엇인지에 따라 적용할 수 있으며, 적용할 수 없다. 타입체크를 통해 이를 판별한다.

ts
let userInput: unknown;
let userInput2: any;

userInput = 5;
userInput = 'Lim';

if(typeof userInput === 'string'){
  userNmae = userInput;
}

unknown을 사용해서 값을 고정된 값에 할당 가능하므로 사용 이유에 있어서 any보다 적합하다. 타입 검사가 가능하기 때문이다.

never(절대) 타입


아무것도 반환하지 않는 void타입과 달리 never타입은 함수가 반환할 수 있는 타입이다.

ts
function generateError(message: string, code: number) {
  throw { message: message, error: code };
}
generateError('에러가 발생했습니다!' 500);
>> Uncaught {message: "에러가 발생했습니다!", errorCode: 500}

위와 같은 함수는 언뜻 보면 void타입으로 볼 수 있다. return이 없기 때문이다. 하지만 이 함수는 never타입을 반환하며 반환 값을 생성하지 않는다.

ts
function generateError(message: string, code: number): never { // 마우스 오버시 함수(매개변수): void 로 표시된다. void로 추론하기 때문이다.
  throw { message: message, error: code };
}
const result = generateError('에러가 발생했습니다!' 500);
console.log(result);

이 함수는 아무것도 반환하지 않고 기본저긍로 스크립트나 스크립트의 일부를 충돌시키거나 해당 함수에서는 throw함수가 return 스크립트와 충돌이 발생해 thorw에러가 발생하고 console.log 메소드는 작동하지 않는다. 반환되지 않는 함수는 무한 루프 혹은 에러 함술르 절대 반환하지 않는 함수, 중단된 함수에 적용하기도한다.

추가 정보