React에서의 TypeScript 타입 지정

윤태현·2023년 11월 15일
1

REACT

목록 보기
17/19
post-thumbnail

React에서 TypeScript를 적용하여 타입을 지정해야 하는데,
일반적인 JavaScript에 타입 지정하는 방법에 비해 React는 컴포넌트 타입, 이벤트 핸들링, 상태 관리, 고차 컴포넌트 등 다양한 타입 지정 방법을 정리하면 좋을 것 같아서 작성하게 됐습니다.


1. 컴포넌트 타입 지정

React.FC

Functional Component를 나타내는 타입으로 함수형 컴포넌트의 속성을 정의할 수 있고 TypeScript와 함께 사용할 때 타입 안정성을 제공

  • Props 타입 정의 : 컴포넌트의 props에 대한 타입을 명시적으로 정의할 수 있음
  • Children Prop 자동 포함 : React.FC는 자동적으로 children prop을 포함
  • PropTypes 및 기본값 지원 : PropTypes의 검증과 기본값 설정을 할 수 있음

React.FC를 사용한 타입 지정

const MyComponent: React.FC = () => {
  return <div>내용</div>;
};

Props와 함께 사용하는 컴포넌트

interface MyComponentProps {
	title: string;
}

const MyComponent: React.FC<MyComponentProps> = ({ title }) => {
	return <div>{title}</div>
}



2. 상태 관리 및 Hooks 사용

useState

const [count, setCount] = useState<number>(0);
const [value, setValue] = useState<string>('');
const [value, setValue] = useState<string | number>(0);
const [isTrue, setIsTrue] = useState<boolean>(false);
const [arr, setArr] = useState<string[]>([]);

useRef

const myRef = useRef<HTMLDivElement>(null);		// div Element
const myRef = useRef<HTMLInputElement>(null);	// input Element
const myRef = useRef<HTMLButtonElement>(null); 	// button Element

useContext

import React, { useContext } from 'react';

interface AppContextInterface {
  name: string;
  author: string;
}

const AppContext = React.createContext<AppContextInterface | null>(null);

const DisplayComponent: React.FC = () => {
  const context = useContext(AppContext);

  return (
    <div>
      <p>앱 이름: {context?.name}</p>
      <p>작성자: {context?.author}</p>
    </div>
  );
};

useReducer

import React, { useReducer } from 'react';

interface StateType {
  count: number;
}

type ActionType = { type: 'increment' } | { type: 'decrement' };

function reducer(state: StateType, action: ActionType): StateType {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      return state;
  }
}

const CounterComponent: React.FC = () => {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <p>현재 카운트: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>증가</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>감소</button>
    </div>
  );
};

customHooks

import React, { useState, useEffect } from 'react';

function useCustomHook(defaultValue: number) {
  const [value, setValue] = useState<number>(defaultValue);

  useEffect(() => {
    // 부수 효과나 추가 로직
  }, [value]);

  return [value, setValue] as const; // const assertion으로 타입을 더 명확히 할 수 있음
}

const CustomHookComponent: React.FC = () => {
  const [count, setCount] = useCustomHook(0);

  return (
    <div>
      <p>현재 카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </div>
  );
};



3. 이벤트 핸들러 타입 지정

클릭이벤트

const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  // 로직 구현
};

return <>
  <button onClick={handleClick}>클릭</button>
</>

변경 이벤트

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  // 로직 구현
};

return <>
  <input type="text" onChange={handleChange} />
</>

Form

const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault();
  // 로직 구현
};

기타 이벤트 핸들러(마우스, 키보드 이벤트 등)

// 마우스 이벤트
const handleMouseOver = (e: React.MouseEvent<HTMLDivElement>) => {
  // 로직 구현
};


// 키보드 이벤트
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
  // 로직 구현
};



4. 컴포넌트 Props

ReactNode 사용

  • JSX 요소, 문자열, 숫자, 배열 등을 포함할 수 있는 가장 일반적인 타입
interface MyComponentProps {
  content: React.ReactNode; // ReactNode 타입의 프롭스
}

const MyComponent: React.FC<MyComponentProps> = ({ content }) => {
  return <div>{content}</div>;
};

ReactComponentType 사용

  • 컴포넌트를 나타내며 이 타입은 props를 받을 수 있고 함수형, 클래스 컴포넌트 모두 적용 가능
interface MyComponentProps {
  Component: React.ComponentType; // 함수 또는 클래스 컴포넌트
}

const MyComponent: React.FC<MyComponentProps> = ({ Component }) => {
  return <Component />;
};



5. 함수 Props

기본 함수 Props

interface MyComponentProps {
  onClick: () => void; // 매개변수 없고 반환 값 없는 함수
}

const MyComponent: React.FC<MyComponentProps> = ({ onClick }) => {
  return <button onClick={onClick}>클릭</button>;
};

매개변수를 가진 함수 Props

interface MyComponentProps {
  onSubmit: (value: string) => void; // 문자열 매개변수를 받는 함수
}

const MyComponent: React.FC<MyComponentProps> = ({ onSubmit }) => {
  const handleSubmit = () => {
    onSubmit('Some value');
  };

  return <button onClick={handleSubmit}>제출</button>;
};

이벤트 핸들러 함수 Props

interface MyComponentProps {
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; // 입력 변경 이벤트 핸들러
}

const MyComponent: React.FC<MyComponentProps> = ({ onChange }) => {
  return <input type="text" onChange={onChange} />;
};

제네릭 함수 Props

interface MyComponentProps<T> {
  onCustomAction: (item: T) => void; // 제네릭 타입을 매개변수로 받는 함수
}

const MyComponent: React.FC<MyComponentProps<number>> = ({ onCustomAction }) => {
  return <button onClick={() => onCustomAction(5)}>액션</button>;
};



6. 상태 변환 함수 Props

기본 상태 변환 함수 Props

interface MyComponentProps {
  setCount: React.Dispatch<React.SetStateAction<number>>; // 숫자 상태를 업데이트하는 함수
}

const MyComponent: React.FC<MyComponentProps> = ({ setCount }) => {
  return <button onClick={() => setCount(prevCount => prevCount + 1)}>증가</button>;
};

객체 상태 변환 함수 Props

interface MyState {
  name: string;
  age: number;
}

interface MyComponentProps {
  setState: React.Dispatch<React.SetStateAction<MyState>>; // 객체 상태를 업데이트하는 함수
}

const MyComponent: React.FC<MyComponentProps> = ({ setState }) => {
  return <button onClick={() => setState(prevState => ({ ...prevState, age: prevState.age + 1 }))}>나이 증가</button>;
};

선택적 상태 변환 함수 Props

interface MyComponentProps {
  setCount?: React.Dispatch<React.SetStateAction<number>>; // 선택적 상태 변환 함수
}

const MyComponent: React.FC<MyComponentProps> = ({ setCount }) => {
  return setCount ? <button onClick={() => setCount(prevCount => prevCount + 1)}>증가</button> : null;
};



전역 상태 관리 라이브러리 타입 지정

1. Redux Toolkit

슬라이드 생성

// counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface CounterState {
  value: number;
}

const initialState: CounterState = {
  value: 0
};

const counterSlice = createSlice({
  name: 'counter',
  initialState,
  reducers: {
    increment: state => {
      state.value += 1;
    },
    addAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    }
  }
});

export const { increment, addAmount } = counterSlice.actions;
export default counterSlice.reducer;

스토어 설정

// store.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

const store = configureStore({
  reducer: {
    counter: counterReducer
  }
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export default store;

컴포넌트 사용

// CounterComponent.tsx
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, addAmount } from './counterSlice';
import { RootState } from './store';

export const CounterComponent: React.FC = () => {
  const count = useSelector((state: RootState) => state.counter.value);
  const dispatch = useDispatch();

  return (
    <div>
      <p>현재 카운트: {count}</p>
      <button onClick={() => dispatch(increment())}>증가</button>
      <button onClick={() => dispatch(decrement())}>감소</button>
      <button onClick={() => dispatch(addAmount(5))}>5 더하기</button>
    </div>
  );
};

2. Recoil

카운터 상태 (atoms.ts)

// atoms.ts
import { atom } from 'recoil';

export const countState = atom({
  key: 'countState', // 고유한 키
  default: 0, // 초기값
});

카운터 상태의 파생 (selectors.ts)

// selectors.ts
import { selector } from 'recoil';
import { countState } from './atoms';

export const doubledCountState = selector({
  key: 'doubledCountState',
  get: ({ get }) => {
    const count = get(countState);
    return count * 2;
  },
});

컴포넌트에서 사용

// CounterComponent.tsx
import React from 'react';
import { useRecoilState, useRecoilValue } from 'recoil';
import { countState, doubledCountState } from './selectors';

export const CounterComponent: React.FC = () => {
  const [count, setCount] = useRecoilState(countState);
  const doubledCount = useRecoilValue(doubledCountState);

  return (
    <div>
      <p>현재 카운트: {count}</p>
      <p>두 배 카운트: {doubledCount}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
      <button onClick={() => setCount(count - 1)}>감소</button>
    </div>
  );
};

0개의 댓글