[Typescript] String vs string (원시형, 레퍼형)

in-ch·2023년 12월 7일
1

typescript

목록 보기
2/4
post-thumbnail

Intro


useState(””)useState(””)은 typescript 컴파일러가 인식할 때 둘 다 문제가 없다고 보는 것 같다.

음.. 차이점을 생각해보자.

  1. string 타입: 소문자로 작성된 string은 원시 자료형(primitive type)인 문자열을 나타내는 타입

    이는 일반적인 문자열 값이다.

    const myName: string = "John";
  2. String 타입: 대문자로 시작하는 String은 원시 자료형이 아닌 래퍼 객체(wrapper object)로서의 문자열을 나타내는 타입

    이것은 일반 문자열 값을 감싸고 추가적인 속성과 메소드를 제공하는 객체이다.

    한마디로 객체였던 거임..

    쓰지 말자.

레퍼 객체 (Wrapper Objects)


원시 데이터 유형에 대해 메서드를 호출하려면 JavaScript에서는 래퍼 객체를 사용한다.
래퍼 객체란 원시 값 주위에 생성된 객체로, 해당 원시 값을 감싸는 역할을 하게 된다. 주로 원시형 값에 대한 추가적인 메서드와 속성을 제공하기 위해 사용된다.

예를 들어, 문자열 레퍼 객체는 String 객체이고, 숫자 레퍼 객체는 Number 객체이다.

// 문자열 래퍼 객체
let str = "Hello";
let strObj = new String(str);

// 숫자 래퍼 객체
let num = 42;
let numObj = new Number(num);

console.log(str.length);  // 문자열 래퍼 객체의 메서드 호출
console.log(numObj.toFixed(2));  // 숫자 래퍼 객체의 메서드 호출

근데 여기서 한 가지 기억할 점이 있다 !

자바스크립트는 필요에 따라 자동으로 원시값과 레퍼 객체를 변환하여 사용자가 편리하게 코드를 작성할 수 있게 해준다.

let str = "Hello";
let strLength = str.length;  // 문자열이 원시형이지만 래퍼 객체가 자동으로 생성됨

그렇다고 str.length가 호출된 순간 str 변수의 타입이 레퍼 객체로 변환되는 것은 아니다.

또 비슷한 거 더 나열해보자.

boolean vs Boolean

  • boolean: 원시 자료형으로 참 또는 거짓을 나타내는 값.
  • Boolean: 래퍼 객체로서 논리값을 나타내며 JavaScript의 Boolean 생성자 함수로 생성한다.
const isTrue: boolean = true; // 원시 타입
const isTrueWrapper: Boolean = new Boolean(true); // 래퍼 객체

object vs Object

  • object: 모든 객체(비원시 값)의 타입
  • Object: 모든 객체의 부모 객체로서 Object 생성자 함수로 생성된 객체
const myObj: object = { key: 'value' }; // 모든 객체 타입
const myObject: Object = new Object(); // Object 생성자로 생성된 객체

array vs Array

  • array: 원시 자료형이 아닌, JavaScript의 배열 객체
  • Array: 배열 객체를 생성하기 위한 생성자 함수로, 보통 배열 리터럴([])을 사용
const myArr: array = [1, 2, 3]; // 배열 객체
const myArray: Array<number> = [1, 2, 3]; // 배열 생성자 함수로 생성된 객체

function vs Function

  • function: JavaScript의 함수 객체
  • Function: 함수를 생성하기 위한 생성자 함수로, 사용보다는 () => {}와 같은 리터럴 함수 표현을 선호
const myFunc: function = () => {}; // 함수 객체
const myFunction: Function = new Function(); // 함수 생성자 함수로 생성된 함수 객체 (권장하지 않음)

가장 큰 차이점은 뭘까?

바로 값을 저장하는 방식의 차이가 있다.

먼저 원시 타입은 불변 형태의 값으로 저장된다. 그리고 이 값은 변수 할당 시점에 메모리 영역을 차지하고 저장된다.
반대로 객체는 프로퍼티를 삭제, 추가, 수정할 수 있으므로 원시 값과 다르게 변경 가능한 형태로 저장되며, 값을 복사할 때도 값이 아닌 참조가 전달된다. 때문에 다음과 같은 현상이 발생하는데 ...


const a = function() {}
const b = function() {}
  
a === b // false

객체 레퍼는 참조를 저장(값 저장 x, 가변할 수 있기 때문)하기 때문에 앞서 동일하게 선언한 객체라 하더라도 저장하는 순간 다른 참조를 본다. 따라서 위의 코드 처럼 동등 연산자 비교에서 false가 나오는 것이다.

useState 타입 분석해보기


근데 왜 useState에서 레퍼 타입이랑 원시형 타입이랑 상관없다고 보는 걸까..?
일단 useState를 분석해보자 .

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];

1. 제네릭(Generic) 타입 S: <S>는 제네릭 타입 S를 받아들이며, 이는 상태의 타입을 나타낸다.

2. 매개변수 initialState: useState는 initialState라는 매개변수를 가지며, 이는 초기 상태를 나타냅니다. 초기 상태는 제네릭 타입 S 또는 () => S 형태일 수 있습니다. 만약 initialState이 함수 형태인 경우, 해당 함수가 호출되어 반환된 값이 초기 상태로 사용된다.

3. 반환 값의 타입: [S, Dispatch<SetStateAction<S>>] 형태의 튜플(tuple)을 반환

4. S: 현재 상태의 타입
Dispatch<SetStateAction<S>>: 상태를 갱신. SetStateAction은 제네릭 타입 S 또는 상태를 갱신하는 함수 ((prevState: S) => S)의 유니온 타입

오옹? 왜 통과하는 걸까


이는 TypeScript가 명시적인 형변환 없이도 일반적인 경우에 타입을 유추하고 호환성을 제공하기 때문이다 .. !!

다음과 같은 코드가 있다고 해보자.

const bool1: boolean = true;
const bool2: Boolean = new Boolean(true);

const shouldBeBoolean1: boolean = bool2; // 호환됨
const shouldBeBoolean2: Boolean = bool1; // 호환됨

컴파일 에러가 나오지 않는다.. !! (참고로 vscode에서 자동으로 컴파일해서 타입체킹을 그때그때 해준다.)


즉, 위와 같이 boolean과 Boolean은 서로 호환이 되기 때문에, useState(true)에서도 타입 체킹이 통과하게 되는 것이다.

결론


다시 예를 들자면, Boolean 래퍼 객체와 boolean 원시 타입은 비슷하지만 다르다.
래퍼 객체에는 추가적인 메서드 등이 존재하므로 useState에 넣고 사용했을 때 예상치 못한 오류가 나올 수 있다.

Typescript의 호환성 때문에 당장은 빨간 밑줄이 나오지는 않지만 조심하자.. !!


끝... !!
profile
인치

0개의 댓글