230726 TypeScript Type 익히기

나윤빈·2023년 7월 25일
0

TIL

목록 보기
24/55

📌 타입

💡 타입을 제대로 이해하고 활용하면 코드의 품질과 유지 보수성이 향상되고 실수를 줄일 수 있다!

  • 변수 그리고 타입이란? 프로그래밍 언어에서 변수는 데이터를 저장하는 공간이다. 변수에 저장되는 데이터에는 숫자, 문자열, 논리값, 배열, 객체 등이 있다.

  • 잘못된 타입을 사용할 경우에는 오류가 발생할 수 있는데, 이러한 오류는 컴파일 타임이 아니라 실행 시간에 발생한다.

  • 타입을 제대로 이해하면 이러한 오류들을 미리 방지할 수 있다.

  • 타입에 대한 제대로 된 이해가 중요한 이유이다.

  • 타입 안정성이란? 코드가 예상한 타입대로 동작함을 보장하는 것이다.

  • 타입 안정성이 높을수록 코드 품질이 좋아지고, 실행 시간에 발생할 수 있는 오류를 줄일 수 있다.

  • 타입을 제대로 이해하고 활용하면 코드의 안정성이 향상되고, 테스트와 디버깅 시간을 줄일 수 있다.

  • 타입을 명확하게 명시하고 사용하면 코드의 가독성이 높아진다.

  • 이는 곧 다른 개발자들이 이해하기 쉽다는 뜻이며 유지보수가 용이해짐을 의미한다.

💡 Typescript는 정적 타입 시스템을 도입함으로써 다양한 이점을 제공하고 있다. 개발자들이 더 안정적이고 효율적인 코드를 작성할 수 있도록 도와주며, 이를 바탕으로 프로젝트의 전반적인 품질이 향상되고, 더 나은 소프트웨어 개발 경험을 제공할 수 있다.

📌 기본 타입

1) boolean

  • boolean 타입은 참(true) 또는 거짓(false) 값을 나타낸다.
  • 조건문, 비교 연산 등에서 주로 사용된다.
function isValidPassword(password: string): boolean {
  return password.length >= 8;
}

const password = "q1w2e3r4!";
const valid = isValidPassword(password);

if (valid) {
  console.log("유효한 패스워드입니다!");
} else {
  console.log("유효하지 않은 패스워드입니다!");
}

💡 두 가지 상태를 표현하고 싶은 경우 boolean을 사용해야 한다. 세 가지 이상의 상태를 표현하고 싶은 경우는 enum이나 string을 사용한다.

2) number

  • number 타입 TypeScript에서 사용하는 모든 숫자를 나타낸다.
  • 보통 일반적인 프로그래밍 언어에서는 각 숫자의 유형마다 타입이 다르다.
  • 하지만 TypeScript에서 number 타입이 정수, 실수, 2, 8, 16진수까지 표현할 수 있다.
  • 모든 수치 연산에 사용되는 값은 number 타입으로 명시한다.
function calculateArea(radius: number): number {
  return Math.PI * radius * radius;
}

const radius = 5;
const area = calculateArea(radius);
console.log(`반지름이 ${radius}인 원의 넓이: ${area}`);

💡 Math.PI radius radius와 같이 무리수와 정수를 곱한 값도 number로 취급을 할 수 있기 때문에 calculateArea 함수의 리턴 타입도 number이다.

3) string

  • string 타입은 텍스트 데이터를 나타낸다.
  • 작은 따옴표('), 큰 따옴표("), 백틱(``)을 사용하여 표현한다.
  • 텍스트를 조작하거나 출력할 때 사용한다.
function greet(name: string): string {
  return `안녕, ${name}!`;
}

const name = "Spartan";
const greeting = greet(name);
console.log(greeting);

4) 배열

  • 배열은 기본타입에 []가 붙은 형태의 타입이다.
function calculateSum(numbers: number[]): number {
  let sum: number = 0;
  for (let i = 0; i < numbers.length; i++) {
    sum += numbers[i];
  }
  return sum;
}

const testScores: number[] = [90, 85, 78, 92, 88];
const sumScore = calculateSum(testScores);
console.log(`점수의 총합: ${sumScore}`);

5) tuple

  • 튜플은 서로 다른 타입의 원소를 순서에 맞게 가질 수 있는 특수하나 형태의 배열이다.
  • 배열은 number[], string[] 처럼 같은 타입의 원소만 가질 수 있으나, 튜플은 어떤 타입의 원소를 허용할 것인지 정의만 해주면 허용된 타입의 데이터들을 저장할 수 있다.
  • 정의된 데이터 타입의 개수와 순서에 맞추어 저장하는 것이 필수이다.
  • 튜플에서도 배열의 메소드인 push를 사용하여 정의된 데이터 타입의 개수보다 더 저장할 수는 있지만, 이러한 경우 튜플 구조가 내부적으로 변경되기 때문에 좋은 선택은 아니다.
const person: [string, number, boolean] = ['Spartan', 25, false];
const person2: [string, number, boolean] = [25, 'Spartan', false]; // 오류!

6) enum

  • enum은 열거형 데이터 타입이다.
  • 다양한 상수를 보다 더 이해하기 쉬운 문자열 이름으로 접근하고 사용할 수 있게 하는 타입이다.
  • enum 안애 있는 각 요소는 값이 설정되어 있지 않으면 기본적으로 숫자 0으로 시작한다.
  • enum 안에 있는 요소에는 number 혹은 string 타입의 값만 할당할 수 있다.
enum UserRole {
  ADMIN = "ADMIN",
  EDITOR = "EDITOR",
  USER = "USER",
}

enum UserLevel {
  NOT_OPERATOR, // 0
  OPERATOR // 1
}

function checkPermission(userRole: UserRole, userLevel: UserLevel): void {
  if (userLevel === UserLevel.NOT_OPERATOR) {
    console.log('당신은 일반 사용자 레벨이에요');
  } else {
    console.log('당신은 운영자 레벨이군요');
  } 

  if (userRole === UserRole.ADMIN) {
    console.log("당신은 어드민이군요");
  } else if (userRole === UserRole.EDITOR) {
    console.log("당신은 에디터에요");
  } else {
    console.log("당신은 사용자군요");
  }
}

const userRole: UserRole = UserRole.EDITOR;
const userLevel: UserLevel = UserLevel.NOT_OPERATOR;
checkPermission(userRole, userLevel);

💡 enum은 명확하게 관련된 상수 값들을 그룹화하고자 할 때 사용하는 것이 좋다. 하지만 값의 수가 많지 않고, 값들 사이의 관계가 뚜렷하지 않은 경우에는 사용하지 않는 게 낫다.

📌 const readonly

💡 const와 readonly는 불변성을 보장한다.

1) let

  • let 키워드를 사용하여 선언하면 변수가 된다.
  • 변수는 값을 변경할 수 있다.
let num: number = 5;
console.log(num);  // 출력: 5

num = 10;
console.log(num);  // 출력: 10

2) const

  • const 키워드를 사용하여 선언하면 변수가 아닌 상수가 된다.
  • 상수는 값을 변경할 수 없다.
  • 즉, 연산자로 다시 할당이 불가능하다.
const num: number = 5;
console.log(num);  // 출력: 5

num = 10;  // 에러: 'num'은 const로 선언되었으므로 다시 할당될 수 없다.
const nums: number[] = [];
console.log(nums);  // 출력: []
nums.push(1); // 할당은 되지 않아도 배열에 데이터를 추가/삭제하는 것은 문제가 되지 않는다.
nums.push(2); // 은근히 헷갈릴 수 있지만 = 연산자 기준으로만 생각하면 매우 쉽다.
console.log(nums);  // 출력: [1, 2]

nums = [];  // 에러: 'nums'는 const로 선언되었으므로 다시 할당될 수 없다.

3) readonly

  • readonly는 TypeScript에서 등장한 키워드로 TypeScript에서 객체의 속성을 불변으로 만드는 데 사용된다.
  • 즉, 클래스의 속성이나 인터페이스의 속성을 변경할 수 없게 만들 수 있다.
class Person { 
  readonly name: string;
  readonly age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

const person = new Person('Spartan', 30);

console.log(person.name);  // 출력: 'Spartan'
console.log(person.age);   // 출력: 30

person.name = 'Jane';  // 에러: 'name'은 readonly 속성이므로 다시 할당할 수 없다.
person.age = 25;       // 에러: 'age'은 readonly 속성이므로 다시 할당할 수 없다.

💡 클래스의 속성에는 const 키워드를 사용할 수 없다. const 키워드는 일반 변수를 상수화 할 때 사용하는 것이다.

📌 any와 unknown, union

💡 어쩔 수 없이 가변적인 타입의 데이터를 저장하고 싶다면 any 보다는 unknown을 사용하는 것이 좋다. 또한 가변적인 타입을 일일이 정의할 수 있다면 union을 사용하는 것이 가장 적합하다.

1) any

  • TypeScript에서 any 타입은 어떤 타입의 값이든 저장할 수 있음을 의미한다.
  • JavaScript의 object 타입과 같은 최상위 타입이라고 할 수 있다.
let anything: any;
anything = 5; // 최초에는 숫자를 넣었지만
anything = 'Hello'; // 문자열도 들어가고
anything = { id: 1, name: 'John' }; // JSON도 들어간다.

🤔 TypeScript에서 any 타입을 쓰는 것이 맞을까?

  • TypeScript를 사용하는 주된 이유 중 하나는 프로그램의 타입 안정성을 확보하기 위함이다.
  • 그러나 any 타입은 코드의 안정성과 유지 보수성을 저해할 수 있기 때문에 가급적이면 사용을 하지 않는 것이 좋다.

2) unknown

  • unknown 타입은 any 타입과 비슷한 역할을 하지만 더 안전한 방식으로 동작한다.
  • unknown 타입의 변수에도 모든 타입의 값을 저장할 수 있다.
  • 하지만 그 값을 다른 타입의 변수에 할당하려면 명시적으로 타입을 확인해야 한다.
let unknownValue: unknown = '나는 문자열이지롱!';
console.log(unknownValue); // 나는 문자열이지롱!

let stringValue: string;
stringValue = unknownValue; // 에러 발생! unknownValue가 string임이 보장이 안되기 때문!
stringValue = unknownValue as string;
console.log(stringValue); // 나는 문자열이지롱!
  • stringValue = unknownValue as string; 코드를 Type Assertion(타입 단언)이라고 한다.
  • unkwown 타입의 변수를 다른 곳에서 사용하려면 타입 단언을 통해 타입 보장을 하여 사용할 수 있다.
let unknownValue: unknown = '나는 문자열이지롱!';
let stringValue: string;

if (typeof unknownValue === 'string') {
  stringValue = unknownValue;
  console.log('unknownValue는 문자열이네요~');
} else {
  console.log('unknownValue는 문자열이 아니었습니다~');
}
  • typeof 키워드를 이용하여 타입 체크를 미리한 후 unknown 타입의 변수를 string 타입의 변수에 할당할 수 있다.

3) union

  • unknown 타입은 재할당을 할 때 타입 체크가 되어 안전함을 보장하지만, unknown 타입도 재할당이 일어나지 않으면 타입 안전성이 보장되지 않는다.
  • 이를 위해 union 타입이 사용된다.
  • union은 여러 타입 중 하나를 가질 수 있는 변수를 선언할 때 사용된다.
  • union은 | 연산자를 사용하여 여러 타입을 결합하여 표현한다.
type StringOrNumber = string | number; // 원한다면 | boolean 이런식으로 타입 추가가 가능해요!

function processValue(value: StringOrNumber) {
  if (typeof value === 'string') {
    // value는 여기서 string 타입으로 간주된다.
    console.log('String value:', value);
  } else if (typeof value === 'number') {
    // value는 여기서 number 타입으로 간주된다.
    console.log('Number value:', value);
  }
}

processValue('Hello'); // 'String value:', Hello
processValue(42); // 'Number value:', 42

💡 TypeScript를 사용하면서 여러 타입을 하나의 변수로 해결하겠다는 생각은 가급적 지양해야 한다. 이러한 사소한 습관들이 코드의 안정성을 높이고 유지 보수성을 개선할 수 있다는 것을 명심하자.

📌 enum과 object literal

💡 enum은 간단한 상수 값의 경우 적합하고, object literal은 복잡한 구조 그리고 다양한 데이터 타입이 필요한 경우에 적합하다!

1) enum

  • enum은 열거형 데이터 타입으로 상수의 그룹화를 위한 좋은 타입이 될 수 있다.
  • enum 타입은 코드의 가독성을 높이고 병확한 상수 값을 정의할 수 있다.
  • 컴파일 시에 자동으로 숫자 값으로 매핑되기 때문에 따로 값을 할당할 필요가 없다
  • 하지만 특정 숫자 값으로 매핑되어야 한다면 이는 직접 할당 해주어야 한다.

2) object literal

  • 객체 리터럴은 key + value pair로 구성된 객체를 정의하는 방식이다.
  • enum의 각 멤버는 상수이기 때문에 number와 string 타입의 값만 대입할 수 있다.
  • 하지만 객체 리터럴에서는 어떤 타입의 값도 대입 가능하다.
  • 객체 리터럴은 다양한 데이터 타입을 지원하며 유연한 구조를 가진다.
  • 또한 코드 내에서 사용하기 전에 값이 할당되어야 함으로 런타임 에러를 방지할 수 있다.

3) enum과 object literal

  • 객체 리터럴은 멤버의 값이나 데이터 타입을 맘대로 변경할 수 있다.
  • 복잡한 구조와 다양한 데이터 타입을 사용해야 할 때는 객체 리터럴을 사용한다.
profile
프론트엔드 개발자를 꿈꾸는

0개의 댓글