본문 바로가기
React/TS

React에서 TypeScript 사용하기 - Component, Hooks

by 강깅꽁 2020. 10. 25.
반응형

만약, TypeScript에 대해 궁금하시다면 아래 링크를 참조해주세요

typescript-kr.github.io/

 

TypeScript 한글 문서

TypeScript 한글 번역 문서입니다

typescript-kr.github.io

기존 React Project에 TypeScript 도입

arincblossom.wordpress.com/2019/11/08/%EA%B8%B0%EC%A1%B4-react-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-typescript-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0/

 

기존 React 프로젝트에 TypeScript 도입 + TypeScript로 React 요소 나타내기

기존에 React로 작성된 프로젝트에 TypeScript를 도입 할 때 js 혹은 jsx 파일을 tsx로 바꾸는 과정도 필요하지만 그 전부터 세팅 작업이 필요하다. IDE는 VSCode를 쓰자. TypeScript가 MS에서 만든 것이다보니

arincblossom.wordpress.com

 

TypeScript를 사용하는 React Project 만들기

 

npx create-react-app {project-name} --typescript

ex. npx create-react-app react-ts --typescript

 

 

JSX를 사용하는 typescript의 파일 확장자는 .tsx

 

함수형 컴포넌트의 타입 설정

 

변수에 arrow function을 할당할 때 변수 타입을 React.FC로 지정해줄 수 있습니다.

여기에는 몇 가지 특징이 있습니다.

1. HelloProps에 children을 정하지 않아도 자동으로 등록되어 있습니다. 

2. defaultProps가 잘 작동하지 않습니다. 

import React from "react";

type HelloProps = {
  word: string,
};

const Hello: React.FC<HelloProps> = ({ word }) => {
  return <div>{word}</div>;
};

export default Hello;

 

다른 방법은 function 키워드를 사용하여 Component를 만들 수 있습니다.

이런 경우 defaultProps가 정상 작동합니다.

다만, children props를 설정하려면 아래 처럼 children?: React.ReactNode를 설정해줘야 합니다. 

오히려 아래와 같은 방법이 children을 사용하는지 안하는지 확실히 알 수 있어서 좋을 수 있습니다. 

import React from "react";

type HelloProps = {
  word: string;
  children?: React.ReactNode;
};

function Hello({ word }: HelloProps) {
  return <div>{word}</div>;
}

export default Hello;

 

Hooks

 

useState 및 event 객체의 type을 설정하도록 하겠습니다.

 

useState는 Generic 타입을 설정해 줄 수 있습니다. 번거롭다면 생략해주셔도 상관없습니다. 

여기서 문제는 onChange 함수를 선언할 때 event객체를 파라미터로 받습니다. 

이 객체의 타입을 알기 위해서는 VS Code에서  input Tag의 onChange에 마우스 커서를 가져다 놓으면

다음과 같이 설명칸을 보고 타입을 알아낼 수 있습니다. 

event 객체의 타입은 React.ChangeEvent<HTMLInputElement>입니다.

 

import React, { useState } from "react";

type Inputs = {
  name: string;
  age: string;
};

type InputsProps = {
  onSubmit(inputs: Inputs): void;
}
export default function Inputs({onSubmit}: InputsProps) {
  const [inputs, setInputs] = useState<Inputs>({
    name: "",
    age: "",
  });

  const { name, age } = inputs;
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setInputs({ ...inputs, [name]: value });
  };
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    onSubmit(inputs);
    setInputs({
      name: '',
      age: '',
    })
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" value={name} onChange={onChange} />
      <input name="age" value={age} onChange={onChange} />
      <button type="submit">등록</button>
    </form>
  );
}

 

useReducer

 

useReducer Hook은 순수 함수인 reducer를 받고 다음으로 initialState를 받습니다.

reudcer에서 필요한 파라미터 타입과 리턴 타입을 설정해주면 됩니다.

ctrl + space bar 누르시면 해당 타입에서 필요한 부분을 IDE가  알려줍니다.

import React, { useReducer } from 'react'

type State = {
    count: number;
    text: string;
}

type Action = 
{ type: 'SET_NUMBER'; count: number}
| { type: 'SET_TEXT'; text: string};

function reducer(state: State, action: Action): State {
    switch (action.type) {
        case 'SET_NUMBER':
            return {
                ...state,
                count: action.count,
            }
        case 'SET_TEXT':
            return {
                ...state,
                text: action.text,
            }
        default:
            throw new Error('Unhandled Action Type')
    }
}

export default function TestReducer() {
    const [state, dispatch] = useReducer(reducer, {
        count: 0,
        text: 'test',
    });
    const setCount = () => dispatch({type: 'SET_NUMBER',count: 3})
    const setText = () => dispatch({type: 'SET_TEXT',text: 'hi'})
    
    return (
        <div>
            <p>{state.count}</p>
            <p>{state.text}</p>
            <button onClick={setCount}>숫자 설정</button>
            <button onClick={setText}>텍스트 설정</button>
        </div>
    )
}

 

ContextAPI

 

App.tsx

import React from "react";
import TestReducer from "./TestReducer";
import TestContext from "./TestContext";
const App = () => {
  return (
    <TestContext>
      <TestReducer />
    </TestContext>
  )
};

export default App;

 

TestContext.tsx // context 설정 컴포넌트

import React, {createContext, Dispatch, useContext, useReducer} from 'react'

type State = {
    count: number;
    text: string;
}

type Action = 
{ type: 'SET_NUMBER'; count: number}
| { type: 'SET_TEXT'; text: string};

function reducer(state: State, action: Action): State {
    switch (action.type) {
        case 'SET_NUMBER':
            return {
                ...state,
                count: action.count,
            }
        case 'SET_TEXT':
            return {
                ...state,
                text: action.text,
            }
        default:
            throw new Error('Unhandled Action Type')
    }
}

// 초기값이 null이기 때문에 제네릭 타입을 State | null로 설정
const TestContextState = createContext<State | null>(null);
// Dispatch 함수 타입은 React에 정의 되어 있음 
// Dispatch는 파라미터로 action 객체를 갖기 때문에 action 객체의 타입인 Action을 제네릭 타입에 설정
const TestContextDispatch = createContext<Dispatch<Action> | null>(null);

type TestContextProps = {
    children: React.ReactNode;
}

export default function TestContext({ children }: TestContextProps) {
    const [state, dispatch] = useReducer(reducer, {
        count: 0,
        text: 'hi',
    });

    return (
        <TestContextState.Provider value={state}>
            <TestContextDispatch.Provider value={dispatch}>
                {children}
            </TestContextDispatch.Provider>
        </TestContextState.Provider>
    )
}

/* 
값이 없을 때 error를 내주지 않으면 해당 함수의 리턴 값은 State | null이 되는데 
다른 컴포넌트에서 사용할 때 에러가 발생할 수 있기 때문에 return 값의 타입이 항상 State이도록 예외 처리 
*/
export function useTestState() {
    const state = useContext(TestContextState);
    if(!state) throw new Error("state is null")
    return state;
}

export function useTestDispatch() {
    const dispatch = useContext(TestContextDispatch);
    if(!dispatch) throw new Error('dispatch is null')
    return dispatch;
}

TestReudcer.tsx // context 사용 컴포넌트

import React from 'react'
import { useTestDispatch, useTestState } from './TestContext'

export default function TestReducer() {
    const state = useTestState();
    const dispatch = useTestDispatch();
    const setCount = () => dispatch({type: 'SET_NUMBER',count: 3})
    const setText = () => dispatch({type: 'SET_TEXT',text: 'hi'})   
    return (
        <div>
            <p>{state.count}</p>
            <p>{state.text}</p>
            <button onClick={setCount}>숫자 설정</button>
            <button onClick={setText}>텍스트 설정</button>
        </div>
    )
}