[Study] React _ TypeScript 적용하기 #1

kyle kwon·2022년 11월 9일
2

React

목록 보기
7/15
post-thumbnail

Prologue

이번주차 스터디 공유물로 리액트에 점진적으로 TypeScript를 적용하는 방법에 대해서 알아보겠습니다. create-react-app을 이용해서 리액트 어플리케이션을 만든다면, TypeScript + React 환경을 쉽게 구축할 수 있습니다.



React + TypeScript Settings

TypeScript를 활용한 리액트 앱을 만들려면, 다음과 같은 명령어를 터미널에 작성하시면 됩니다.

$ npx create-react-app 디렉토리명 --template typescript

이전에 자바스크립트로 create-react-app을 실행해보신 분이라면, 뒤에 --template typescript라는 속성 없이 쉽게 리액트 환경을 구축할 수 있었을 텐데요. 디렉토리명 뒤에 ---template typescript라고 작성해 주면, 우리는 타입스크립트 언어를 기반으로 리액트 환경을 구성할 것이다라고 생각하시면 되겠습니다.


환경설정이 완료되고, cd 디렉토리명을 터미널에 입력하시고,
npm을 사용하신다면 npm start,
yarn을 사용하신다면 yarn start 명령어를 실행하시면 됩니다.


vscode에 들어 오시면, 가장 먼저 src 디렉토리 내에 App.tsx라는 파일이 있을 것입니다.
자바스크립트로 환경을 구성할 때와 달리, 타입스크립트로 환경을 구성하면 리액트 컴포넌트들은 .tsx라는 확장자 명으로 파일이 구성됩니다.


리액트 컴포넌트의 Type

리액트의 컴포넌트에 타입을 지정해줘야 하는데요. 이전에 hook을 사용하지 못하던 버전들에서는 컴포넌트를 대개 클래스형으로 작성했는데, 최근에는 함수형 컴포넌트로 작성하는 것이 트렌드입니다. 공식 문서에서도 함수형 컴포넌트를 권장하고 있고, 베타 버전으로 따로 함수형 컴포넌트로 공식문서를 작성하고 있을 정도로 함수형 컴포넌트 중심적으로 돌아간다고 말할 수 있을 것 같습니다.

https://beta.reactjs.org/


타입스크립트 환경에서는 우리가 작성한 코드들의 표현을 함수형 컴포넌트를 베이스로 하고, 함수형 컴포넌트로 인지되지 않거나 확인되지 않을 경우, 클래스 컴포넌트로 해석한다고 볼 수 있습니다. 기본적으로 우리가 반환값, 즉 return하는 JSX의 결과 타입은 any이지만, JSX.Element 인터페이스를 통해서 원하는 타입으로 변경가능합니다.

클래스형 컴포넌트의 경우 render() 메서드를 통해서 ReactNode를 반환하는 반면에, 함수형 컴포넌트는 ReactElement 를 반환합니다.

ReactElement는 React.createElement 로 컴파일되고, JSX.elementany타입의 props와 type을 가진 React.createElement입니다.

ReactNode > React.Element Generic > JSX.Element 의 범위로 연관관계를 갖는 다고 생각하시면 쉽게 이해하실 수 있습니다.


01 React.FC

FC는 Function Component의 줄임말로, 함수형 컴포넌트의 타입 정의 시에 React.FC를 타입으로 붙여주면 됩니다.

function App: React.FC<string>{
 	return (
  		<div></div>
  	)
}

React.FC로 타입을 지정하는 경우에는 props에 기본적으로 children이 담겨있어, props의 타입을 정할 수 없다는 단점이 있습니다. children의 요소로 어떤 타입이 들어올 지 예측하기 어렵기 때문입니다. React 18 버전부터는 prop에 대한 타입이 Generic으로 바뀌면서, 직접 props에 대한 타입을 지정해줘야 하기 때문에, React.FC는 많은 단점을 갖고 있어 사용하는 것을 지양하고 있습니다.

따라서, 쉽게 정리하면 컴포넌트에 대한 타입 React.FC을 지정하는 대신, 인자로 받는 props에 대한 타입을 interface 또는 type 을 이용해서 선언하고, 해당 props에 타입을 명시하는 방법으로 사용하는 것을 지향해야 한다고 기억하시면 됩니다.

특히, create-react-app을 이용해서 리액트 프로젝트를 생성할 경우 React.FC의 사용이 어려우므로 사용을 지양합니다.


interface Identification {
  name : string;
  age: number;
  phoneNumber: number;
}

function App(props: Identification){
  const { name, age, phoneNumber } = props;
 	return (
      <div>
      	<h1>{name}</h1>
      	<p>{age}</p>
      	<p>{phoneNumber}</p>
    </div>
     )
}

02 JSX.Element

우리가 일반적으로 자바스크립트에서 함수를 작성하는 방식에는 1) 함수 선언식, 2) 함수 표현식 방법이 있는데요. 그 중에서도 function 키워드를 사용하는 1) 함수 선언식 방법과, 2) 함수 표현식 방법 중에서도 화살표 함수를 활용한 방식을 많이 사용합니다.

리액트에서도 함수형 컴포넌트를 작성 시 화살표 함수로도 사용할 수 있지만, 최근의 트렌드로는 function 키워드를 이용해 컴포넌트를 작성하는 것을 공식문서와 여러 프로젝트들에서 확인할 수 있을 것입니다.

interface Identification {
  name : string;
  age: number;
  phoneNumber: number;
}

function App(props: Identification): JSX.Element{
  return (
    <div>
      <h1>Hello React</h1>
    </div>
  );
}

쉽게 생각하면, 컴포넌트의 리턴값인 JSX에 대한 타입을 지정하는 방법입니다.
React.FC의 방법보다는 JSX.Element의 방식이 더 가독성도 좋고, props에 대한 타입 지정과 분리하여 사용하게 됨에 따라 가독성도 좋고, 개발자 입장에서 코드를 작성하기 훨씬 편리하다고 생각됩니다.



리액트 Hooks의 Type 지정 및 사용

여러 hook들 중에 대표적인 상태 관리 hook인 useState, useReducer, 그리고 비제어 컴포넌트 관리 시 불필요한 렌더링을 방지해주는 useRef hook의 타입 지정에 대해서 알아보겠습니다.

useState()

useState를 사용할 때는 Generic을 사용해서 타입을 지정하는데, 사실 타입스크립트가 타입 추론을 잘 해내기 때문에, 꼭 generic을 사용하지 않고 생략해도 됩니다.

const [number, setNumber] = useState(0);

단, state가 null일 수도 있고 아닐 수도 있을 때는 Generic을 사용하는 것이 좋습니다.
또, 단순 원시값이 아닌 참조값을 가리키는 객체나 배열이 상태에 담겨있을 경우 Generic으로 명시하는 것이 좋습니다.

type Identification = {
  name: string;
  age: number;
  address: string;
}
const [info, setInfo] = useState<Idenfication | null>({
 	name: 'kyle',
  	age: 99,
  	address: 'Seoul',
});

useReducer

useReducer를 사용할 때 코드의 구조는 다음과 같습니다.

const [state, dispatch] = useReducer(reducer, initialState);

useReduceruseState처럼 타입 명시를 생략해도 괜찮습니다. 단, 컴포넌트의 외부에 작성될 reducer 함수에서 받아오는 파라미터의 타입과 return 타입을 동일하게 해주는 것이 매우 중요합니다.

type Action = { type: 'plus' } | { type: 'minus'} // union type을 활용하여 타입 별칭으로 다음과 같이 타입을 지정가능합니다.

const initNumber: number = 100;
const reducer = (number:number, action:Action):number => {
 	switch (action.type) {
      case 'plus':
        return number + 1;
      case 'minus':
        return number - 1;
      default :
        break;
    }
}


function App():JSX.Element {
 	const [number, dispatch] = useReducer(reducer, initNumber);
 	const plusHandler = () => dispatch({type: 'plus'});
	const minusHandler = () => dispatch({type: 'minus'});
  
  return(
    <div>
      <h1> {number}</h1>
      <button onClick={plusHandler}>plus</button>
      <button onClick={minusHandler}>minus</button>
    </div>
   )
}

useRef

useRef는 useState, useReducer와 같이 generic을 통해 타입을 지정할 수 있습니다.

const ref = useRef<number | null>(0);

generic을 이용해서 ref.current를 통해 값을 추론할 수 있습니다.

useRef는 DOM을 특정 값 안에 담을 때(DOM 요소를 직접 제어) 사용하는데, 특히, input에 focus 를 시킬 때 종종 사용합니다.

코드를 통해 살펴보겠습니다.

function App():JSX.Element{
  	const inputRef = useRef<HTMLInputElement>(null);
	const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
      	e.preventDefault();
      	e.target.value = '';
  		inputRef.current.focus();
	}
    
    return (
      	<form onSubmit={handleSubmit}>
        	<input type="text" ref={inputRef}/>
        	<button type="submit">로그인</button>
        </form>
      )
}

위와 같이 inputRef라는 변수에는 input이라는 DOM이 담길 것인데, 그 전의 초기 상태는 아무런 값도 담겨있지 않으므로, 초기값으로 null을 지정해주면 됩니다.

추가적으로, event 핸들러 함수인 handleSubmitevent라는 합성 이벤트 객체를 파라미터로 받는데, 이 event의 타입도 지정해주어야 합니다. 해당 타입도 generic을 활용해서 React.FormEvent<HTMLFormElement>로 지정해주면 됩니다. vscode가 타입스크립트로 만들어진 코드 에디터이기 때문에, handleSubmit에 마우스를 살짝 올려보면, 타입에 대해서 추론해 잘 알려주곤 합니다!




Conclusion

이렇게 첫 create-react-app을 통해 타입스크립트 환경에서 리액트 설정을 하는 방법부터, 컴포넌트의 타입 지정, hooks들 중 대표적인 hook의 타입 지정을 하는 방법에 대해서 알아보았습니다.

다음에는 context API를 활용할 때 타입 지정을 하는 방법과 React Router에 대해서 정리해 보겠습니다.



[참고자료]

profile
FrontEnd Developer - 현재 블로그를 kyledot.netlify.app으로 이전하였습니다.

0개의 댓글