JiSoo's Devlog

[Udemy React 완벽 가이드] 리액트 + TypeScript 본문

Frontend/Typescript

[Udemy React 완벽 가이드] 리액트 + TypeScript

지숭숭숭 2024. 6. 4. 13:47

타입스크립트자바스크립트의 '슈퍼셋' 언어

자바스크립트를 기반으로 하되 더 확장된 프로그래밍 언어

 

타입스크립트는 리액트와 달리 자바스크립트 라이브러리가 아니기 때문에 자바스크립트의 기존 기능을 기반으로 새로운 기능을 만들거나 기능을 확장하지 않는다

가장 중요한 건 정적 타입의 특징을 갖는다는 것 !!

런타임에 오류의 원인을 찾을 필요 없이 코드를 작성할 때 바로 오류가 표시된다

 

TypeScript: JavaScript With Syntax For Types. (typescriptlang.org)

 

JavaScript With Syntax For Types.

TypeScript extends JavaScript by adding types to the language. TypeScript speeds up your development experience by catching errors and providing fixes before you even run your code.

www.typescriptlang.org

npm install typescript

 

자바스크립트 문법에 타입 표기 구문이 추가되었다

 

컴파일러를 사용하려면

npx tsc

 

 

변수 선언

기본형 타입 : 숫자형, 문자열, 논리형

let age: number;
age = 12;

let age:number = 12;

해당 변수에 저장할 자료형 지정

값을 나중에 할당할 때 실수형을 포함해 숫자형이면 다 가능

 

let userName: string;
userName = 'Max';
let isInstructor: boolean;
isInstructor = true;

number과 string 모두 소문자

대문자로 쓰면 자바스크립트의 Number 객체가 된다

 

배열 및 객체 유형

let hobbies: string[];
hobbies = ['sports', 'cooking'];

string 뒤에 대괄호를 붙이면 문자열 배열을 만들 수 있다

 

타입을 설정해주지 않으면 기본 타입이 any가 되는데 이렇게 하면 저장할 값의 타입에 대해 알려줄 게 없다는 뜻이 된다

하지만 이 타입은 예비적으로 사용되는 타입이므로 사용하지 않는 게 좋다

 

let person: {
  name: string;
  age: number;
}

person = {
  name:'Jisoo',
  age:24
}

객체의 타입 지정

 

let people: {
  name: string;
  age: number;
}[];

객체 배열도 설정 가능

 

 

타입 추론

기본적으로 타입스크립트는 가능한 많은 타입을 유추하려고 한다

 

타입 추론 기능을 활용해 코드를 작성하는 게 권장되는 방식

→ 불필요한 타입 지정을 하지 않아도 되기 때문

 

 

유니온 타입

유니온 타입은 타입을 정의할 때 한 개 이상의 타입을 사용할 수 있다

let course: string | number = "apple";

course = 123;

첫 번째 타입 뒤에 파이프 문자(|)를 넣고 뒤에 다른 타입을 추가하면 된다

값과 타입을 좀 더 유연하게 정의할 수 있게 해 준다

 

 

타입 별칭(Type Alias)

기본 타입을 만들어 거기에 복잡한 타입을 정의해 두고 그 타입을 사용하는 것

type Person = {
  name: string;
  age: number;
};

let person: Person;
let people: Person[];

type 키워드를 사용해서 type 새로운 이름 = 타입 정의

이 기능은 타입스크립트에만 존재하기 때문에 자바스크립트로 컴파일하면 코드에서 사라지게 된다

한 번만 정의해서 필요한 모든 곳에 반복해서 사용할 수 있다

 

 

함수 유형

function add(a: number, b: number) {
  return a + b;
}

함수의 매개변수에도 타입을 지정할 수 있다

반환값이 갖는 타입을 통해 함수 타입을 추론하는 것

function add(a: number, b: number): number {
  return a + b;
}

명시적으로 지정도 가능하지만 꼭 지정해야 할 이유가 없다면 지정하지 않은 게 좋다

함수에서 타입을 사용할 때는 매개변수의 타입뿐만 아니라 반환 값의 타입도 생각해야 한다

 

function print(value: any) {
  console.log(value);
}

이런 식으로 return문이 없고 반환값이 없다면 반환 타입이 void가 된다

void는 함수에 반환값이 없다는 걸 뜻하고 함수에만 있는 특수한 타입이다

 

 

제네릭 타입

함수 안에서만 사용할 수 있는 타입이고 보통 Type의 T를 따서 사용하지만 어떤 식별자든 상관없다

 

제네릭 타입을 사용해 타입스크립트에게 any 타입이 아니라는 걸 알려줬기 때문에 updatedArray가 숫자 배열이라는 것을 알게 된다

대신 array 배열과 value 값이 같은 타입을 가져야 한다는 걸 알려줬다

 

어떤 타입이든 사용할 수 있지만 특정 타입을 사용해 함수를 실행하고 나면 해당 타입으로 고정되어 동작한다

유연성타입 안정성 측면에서 모두 도움이 된다

 

let numbers: number[] = [1, 2, 3];

이렇게 명시적으로 할당하는 것은 실제로 문법적 대안이다

실제 유형은 Array, 모든 배열은 Array 유형이고 Array도 제네릭 유형이다

let numbers: Array<number> = [1, 2, 3];

 

꺾쇠괄호 <>를 사용해 제네릭 유형을 정의할 수 있을 뿐만 아니라 제네릭 유형을 사용하고 사용해야 하는 자리 표시자 유형을 명시적으로 설정할 수도 있다

 

 

타입스크립트 기반 프로젝트 생성

npx create-react-app my-app --template typescript

 

파일 확장자가 .tsx인 이유는 그 안에서 JSX 문법을 사용하기 때문

JSX 문법을 사용할 때 개발 툴에 불필요한 경고창이 뜨지 않게 하려면 확장자로 .tsx를 사용해야 한다

타입스크립트를 자바스크립트로 컴파일하는 단계가 있기 때문에 우리가 직접 변환할 필요가 없다

"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",

이게 자바스크립트 라이브러리에 타입 표기 기능을 추가해 준다

 

 

리액트에서 props는 언제나 객체 형태

function Todos(props: { items: string[] }) {

 

props에는 컴포넌트에 추가한 키-값 쌍만이 아니라 특별한 프로퍼티인 children이 있다

props를 사용하도록 정의한 모든 컴포넌트에 대해 객체에 미리 정의된 props를 매번 추가해야 하는 건 번거로운 일이다

 

→ 함수형 컴포넌트를 제네릭 함수로 변환해 이용하기

import React from "react";

const Todos:React.FC = (props) => {

FC는 FunctionComponent라는 타입으로 리액트 패키지에 내장된 타입 정의

이렇게 타입을 정의함으로써 이 함수가 함수형 컴포넌트로 동작한다는 걸 명확히 하는 것이다

타입스크립트와 개발 툴은 React.FC라는 타입 표기로 이 함수가 받는 값이 props 객체라는 걸 이해하게 된다

React.FC 자체가 제네릭 타입이기 때문에 새로 정의한 타입을 props 객체에 합칠 수 있다

 

 

리액트와 타입스크립트로 함수형 컴포넌트를 만들려면 React.FC 타입을 함수형 컴포넌트 상수 옆에 사용하고

홑화살괄호 <{}>를 붙인 다음

사이에 필요한 형태의 props를 정의하면 된다

 

import React from "react";

const Todos: React.FC<{ items: string[] }> = (props) => {
  return (
    <ul>
      {props.items.map((item) => (
        <li key={item}>{item}</li>
      ))}
    </ul>
  );
};
export default Todos;
function App() {
  return (
    <div>
      <Todos items={["Learn React", "Learn TypeScript"]} />
    </div>
  );
}

 

리액트에서 타입스크립트를 사용하는 것의 장점이 컴포넌트 안에서 작업할 때 자동 완성 기능도 사용할 수 있다는 것!!

props를 새롭게 만들어 사용한다면 제네릭 타입 추가하고 홑화살괄호 안에 새로운 props 타입 정의하면 끝

 

 

데이터 모델 추가하기

타입스크립트를 사용할 때는 미리 프로퍼티를 정의하고 해당 프로퍼티에 어떤 타입을 가진 값이 저장되는지 명확히 밝혀야 한다

class Todo {
  id: string;
  text: string;

  constructor(todoText: string) {
    this.text = todoText;
    this.id = new Date().toISOString();
  }
}
export default Todo;

이 클래스는 새로운 객체를 생성하는 생성자 역할만이 아니라 타입 역할도 한다

 

const Todos: React.FC<{ items: Todo[] }> = (props) => {

이렇게 하면 items는 객체로 채워진 배열이고 그 객체는 Todo 클래스의 정의에 부합하는 객체이다

 

 

양식 제출

const submitHandler = (event: React.FormEvent) => {
    event.preventDefault();    
  };

 

여기서 React.FormEvent는 리액트 패키지가 제공하는 특수 타입

event 객체 타입은 폼 제출 이벤트를 수신하면 자동적으로 받게 된다

MouseEvent 타입도 있는데 이건 onClick 이벤트 리스너를 등록하면 받게 된다

 

refs 및 useRef 작업

const todoTextInputRef = useRef()


return (
    <form onSubmit={submitHandler}>
      <label htmlFor="text">Todo text</label>
      <input type="text" id="text" ref={todoTextInputRef}/>
      <button>Add Todo</button>
    </form>
  );

저렇게만 하면 레퍼런스가 입력창에 연결될 거라는 걸 알지 못하지 때문에

레퍼런스에 저장될 데이터가 어떤 타입인지 명확히 밝혀야 한다

 

const todoTextInputRef = useRef<HTMLInputElement>()

위처럼 하면 저기서 생성하면 레퍼런스가 HTMLInputElement와 연결된다는 게 명확해진다

 

모든 돔 요소들은 미리 정의된 타입을 가진다

input 요소의 타입은 HTMLInputElement

button 요소의 타입은 HTMLButtonElement

paragraph 요소의 타입은 HTMLParagraphElement

 

레퍼런스에 다른 요소가 할당되어 있을 수 있기 때문에 시작 값을 넣어줘야 한다

const todoTextInputRef = useRef<HTMLInputElement>(null);

 

레퍼런스에는 항상 current 프로퍼티가 있고 여기에 실제 값이 들어있다

const enteredText = todoTextInputRef.current?.value;

물음표를 추가해 타입스크립트에게 일단 값에 접근해 보고 접근이 가능하다면 그때 입력값을 가져와 enteredText에 저장하라고 알리는 것이다

접근이 불가능하면 아직 연결이 안 된 상태고 null이 enteredText에 저장된다

값이 null 이 아닌, 레퍼런스와 요소가 연결되었다는 걸 알고 있다면 물음표 대신 느낌표를 사용할 수 있다

느낌표는 이 값이 null이 될 수 있다는 건 알지만 이 시점에는 절대 null이 아니라는 걸 확신하는 경우에 사용해야 한다

const enteredText = todoTextInputRef.current!.value;

 

 

함수타입 정의

const NewTodo: React.FC<{ onAddTodo: () => void }> = (props) => {

 

onAddTodo는 반환값이 필요 없어 void로 지정

 

매개변수를 받아야 한다면

const NewTodo: React.FC<{ onAddTodo: (text: string) => void }> = (props) => {

 

 

타입이 never이라면 어떤 값도 추가될 수 없다

const [todos, setTodos] = useState<Todo[]>([]);

타입이 Todo로 구성된 배열

 

const addTodoHandler = (todoText: string) => {
    const newTodo = new Todo(todoText);

    setTodos((prevTodos) => {
      return prevTodos.concat(newTodo);
    });
  };

이전 state를 기반으로 state를 업데이트하기 위해 함수 형태로 state 업데이트

concat() 메서드를 사용해 새로운 배열 생성

 

함수의 인수 타입을 정의하는 건 선택 사항이다

const TodoItem: React.FC<{
  text: string;
  onRemoveTodo: (event: React.MouseEvent) => void;
}> = (props) =>
const TodoItem: React.FC<{
  text: string;
  onRemoveTodo: () => void;
}> = (props) =>

 

 

bind()는 자바스크립트에서 제공하는 메서드로 실행할 함수를 미리 설정할 수 있다

<TodoItem
          key={item.id}
          text={item.text}
          onRemoveTodo={props.onRemoveTodo.bind(null, item.id)}
        />

item.id를 onRemoveTodo가 매개변수로 받게 된다

 

 

tsconfig.json

'target'은 작성한 코드를 어떤 자바스크립트 버전을 변환할 건지 결정

"target": "es5",

 

'lib'에 포함된 라이브러리는 어떤 타입이 타입스크립트에서 기본으로 지원되는지 결정

"lib": [
      "dom",
      "dom.iterable",
      "esnext"
    ],

 

'allowJs'는 .js 파일 포함 여부 결정

일반 자바스크립트 파일을 프로젝트에 둘 건지 일반 자바스크립트 파일로부터 뭔가 가져올 때 오류를 표시하지 않을 건지

"allowJs": true,

 

'stric' 옵션은 매우 중요한데 true로 설정하면 이 프로젝트에 가장 엄격한 설정이 적용된다

다른 검증 기능도 활성화되지만 묵시적 any 타입을 잡아내는 기능이 가장 중요하다

"strict": true,

 

'jsx' 옵션은 JSX 코드를 지원할 건지 결과물로 어떤 코드를 생성할 건지 결정

"jsx": "react-jsx"

 

 

728x90