Today I Learned (TIL)

타입스크립트로 리액트 카운트 앱 만들기23.12.13

폼폼코 2023. 12. 13. 21:03
728x90
반응형

타입스크립트를 이용한 간단한 리액트 카운트 앱을 만들어봤다 리액트 프로젝트를

타입스크립트로 세팅하는 방법은 2가지가 있다고하는데

<aside>
💡 **리액트 프로젝트를 TS로 세팅하는 방법은 두 가지가 있습니다.**

1. 프로젝트 생성 초기부터 이미 리액트 전용 TS를 세팅하여 시작하는 방법
    - 특징
        1. 프로젝트 초기부터 TS 관련 옵션이 모두 설정되어 있으므로 상당히 간편하고 빠르게 프로젝트를 시작할 수 있습니다.
        2. 초기 설정과 빌드 시스템이 이미 TypeScript를 지원하도록 구성되어 있으므로, TypeScript로 바로 작업을 시작할 수 있습니다.
        3. 프로젝트 구조가 이미 TypeScript 프로젝트에 최적화되어 있어 처음부터 일관성 있는 코드를 작성할 수 있습니다.
2. CRA로 리액트 프로젝트를 구성한 후, TS를 나중에 추가하는 방법
    - 특징
        1. 기존의 Javascript 코드를 TS로 변환해야 합니다.
        2. 프로젝트의 설정을 다수 변경하여 TS를 지원토록 해야 합니다.
        3. TS 추가하고자 하는 시점을 자유롭게 조절할 수 있습니다. 처음부터 TS를 사용할 필요가 없는 경우에 해당합니다.
</aside>
❗️ 참고로 CRA 보일러 플레이트로 만든 리액트를 실행시킬 때(위 내용의 1번에 해당)는 
tsc 컴파일러가 내부적으로 실행되어 컴파일된 JS파일은 
파일시스템에 저장되지 않고 눈에 보이지 않는 메모리상에서만 생성되어 실행됩니다. 
 

2번째 방법으로 하면 머리가 터질수도있으므로 처음부터 TS 세팅후 시작해본다


1. 프로젝트 생성 대상 경로로 이동

PowerShell, 터미널(또는 iTerm 등)에서 프로젝트를 생성하고자 하는 경로로 이동합니다.

보일러 플레이트 편하게 기본 세팅이 가능하다 리액트랑 별반 차이없다

# yarn을 사용하는 경우
yarn create react-app my-app --template typescript

# npx를 사용하는 경우
npx create-react-app my-app --template typescript
 
tsconfig.json, App.tsx, index.tsx 등 타입스크립트로 세팅된 리액트 프로젝트 구조가 보인다

 

.tsx 탁스라고 읽는다

 

간단한 카운터 예제

import { useEffect, useState } from "react";
import "./App.css";

const App = () => {
	// <number> 제네릭은 여기서는 생략해도 됩니다.
	// 생략 시 initialState 값인 0에 따라 타입추론되어 number의 type은 number가 됩니다.
  const [number, setNumber] = useState<number>(0);

  const handleMinusButtonClick = () => setNumber((prev) => prev - 1);
  const handlePlusButtonClick = () => setNumber((prev) => prev + 1);

	// typescript에서는 함수를 생성할 떄, input/output에 대한 type을 명시해야해요!
  const createAlertMsg = (num: number): string => {
    return `현재 숫자는 ${num}입니다.`;
  };

	// input/output이 없는 함수는 타입을 적지 않아도 무방합니다.
	// 만약 명시하고 싶다면, output에 해당하는 return 값을 void 라고 표기해줘도 좋습니다. 
	// const alertMsg = () : void ⇒ { … }
  const alertMsg = () => {
    alert(createAlertMsg(number));
  };

  useEffect(() => {
    console.log(`number가 ${number}로 변경되어 알려드립니다.`);
  }, [number]);

  return (
    <>
      <div>
        <h4>{number}</h4>
      </div>
      <div>
        <button onClick={handleMinusButtonClick}>감소</button>
        <button onClick={handlePlusButtonClick}>증가</button>
      </div>
      <button onClick={alertMsg}>메시지 출력</button>
    </>
  );
}

export default App;
 

<number> 이걸 제네릭이라고하는데 해당 함수의 type값을 고정시켜준다 다른값이 들어오는 오류를 방지한다.

 

💡 [useState에서 <number> 즉, 제네릭은 왜 사용하나요?]

  1. const [number, setNumber] = useState<number>(0);
  2. const [number, setNumber] = useState(0);

사실 위 두개의 코드 모두 동일하게 동작합니다. 그래서 생략해도 상관없어요!

 

하지만, 만약 상태가 ‘null이 들어올 수도 있다’라고 가정한다면 다음과 같이 써줄 수 있겠죠.

const [number, setNumber] = useState<number | null>(null);
 

간단한 예제 2 - 배열과 객체 다루기

import { useState } from "react";

const App = () => {
  // 배열로 지정된 state의 type을 지정해볼게요!
  const [names, setNames] = useState<string[]>([
    "튜터1",
    "튜터2",
    "튜터3",
    "튜터4",
  ]);

  // 타입이 지정된 객체 상태와 초기값을 세팅해요.
  const [person, setPerson] = useState<{ name: string; age: number }>({
    name: "르탄",
    age: 21,
  });

  // 기존의 배열에 새로운 이름을 추가하는 함수입니다.
	// 만약 input 또는 output이 었었다면 관련 type은 반드시 넣어줘야겠죠.
  const addNewName = () => {
		// 추가할 때 마다 배열의 개수가 덧붙여져 신규 튜터님 이름이 생성됩니다.
    const newName = `신규 튜터님 -> ${names.length + 1}`;
    setNames([...names, newName]);
  };

  // 나이를 업데이트하는 함수입니다.
  const updateAge = () => {
    setPerson({ ...person, age: person.age + 1 });
  };

  return (
    <div>
      <h1>리액트 튜터 목록</h1>
      <ul>
        {names.map((name: string, index: number) => (
          <li key={index}>{name}</li>
        ))}
      </ul>
      <button onClick={addNewName}>튜터 추가하기</button>

      <h1>가장 젊은 사람</h1>
      <p>Name: {person.name}</p>
      <p>Age: {person.age}</p>
      <button onClick={updateAge}>철들이기</button>
    </div>
  );
};

export default App;
 

간단한 예제 3 - 배열과 객체 다루기(2)

위 코드에서 type(또는 interface)을 별도로 분리할게요!

import { useState } from "react";

// (1) interface를 이용한 Person 타입 정의
// interface Person {
//   name: string;
//   age: number;
// }

// (2) type를 이용한 Person 타입 정의
type Person = {
  name: string;
  age: number;
};

const App = () => {
  // 타입이 지정된 배열 상태와 초기값
  const [names, setNames] = useState<string[]>([
    "튜터1",
    "튜터2",
    "튜터3",
    "튜터4",
  ]);

  // 타입이 지정된 객체 상태와 초기값
  const [person, setPerson] = useState<Person>({
    name: "원장",
    age: 21,
  });

  // 기존의 배열에 새로운 이름을 추가하는 함수
  const addNewName = () => {
		// 추가할 때 마다 배열의 개수가 덧붙여져 신규 튜터님 이름이 생성됩니다.
    const newName = `신규 튜터님 -> ${names.length + 1}`;
    setNames([...names, newName]);
  };

  // 나이를 업데이트하는 함수
  const updateAge = () => {
    setPerson({ ...person, age: person.age + 1 });
  };

  return (
    <div>
      <h1>리액트 튜터 목록</h1>
      <ul>
        {names.map((name: string, index: number) => (
          <li key={index}>{name}</li>
        ))}
      </ul>
      <button onClick={addNewName}>튜터 추가하기</button>

      <h1>가장 젊은 사람</h1>
      <p>Name: {person.name}</p>
      <p>Age: {person.age}</p>
      <button onClick={updateAge}>철들이기</button>
    </div>
  );
};

export default App;
 

< > 제네럴 분리 그리고 협업시에 타입스크립트 꿀팁

객체 안에 키 벨류 처럼 타입을 지정해 줘야하는데 이럴때는 함수 마냥 타입도 묶음으로 지정이 가능하다

import 뒤에 type 안넣어도 되는데 보기 편하게 넣어주는거다

한칸띄워서 import 하면 보기가 편하다는 튜터님 style

리덕스 store 마냥 Type 묶음들을 모아둬서 export 하는 방식으로도 사용이 가능하다

다른 사람들과 협력하거나 회사에서는 import 할것들이 굉장히 많기때문에

잘 알아볼수있도록 Type 표시를 따로 해두거나 한칸정도 띄워 놓으면 남들이 알아보기가 쉽다


 

리액트와 거의 유사한듯하다 아무래도 기존 JS 문법에 +a 인 타입스크립트이에 일단 익숙해 지는게 중요한듯하다

굉장히 초반단계라 아직까지는 덜 복잡한편

728x90
반응형