icon안동민 개발노트

React 컴포넌트와 타입스크립트 통합


 React와 타입스크립트를 통합하면 컴포넌트의 타입 안정성을 크게 향상시킬 수 있습니다.

 이 절에서는 React 컴포넌트를 타입스크립트로 정의하는 다양한 방법과 Best Practices를 다룹니다.

함수형 컴포넌트 정의

 함수형 컴포넌트는 다음과 같이 정의할 수 있습니다.

import React from 'react';
 
interface GreetingProps {
  name: string;
  age?: number;
}
 
const Greeting: React.FC<GreetingProps> = ({ name, age }) => {
  return (
    <div>
      Hello, {name}! {age && `You are ${age} years old.`}
    </div>
  );
};
 
// 사용
<Greeting name="Alice" age={30} />

클래스 컴포넌트 정의

 클래스 컴포넌트는 다음과 같이 정의할 수 있습니다.

import React, { Component } from 'react';
 
interface CounterProps {
  initialCount: number;
}
 
interface CounterState {
  count: number;
}
 
class Counter extends Component<CounterProps, CounterState> {
  constructor(props: CounterProps) {
    super(props);
    this.state = {
      count: props.initialCount
    };
  }
 
  increment = () => {
    this.setState(prevState => ({ count: prevState.count + 1 }));
  }
 
  render() {
    return (
      <div>
        Count: {this.state.count}
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}
 
// 사용
<Counter initialCount={0} />

Props와 State 타입 정의

 Props와 State의 타입은 interface나 type을 사용하여 정의할 수 있습니다.

interface User {
  name: string;
  email: string;
}
 
interface UserListProps {
  users: User[];
  onSelectUser: (user: User) => void;
}
 
const UserList: React.FC<UserListProps> = ({ users, onSelectUser }) => {
  // ...
};

 Best Practice

  • 재사용 가능한 타입은 별도의 파일로 분리
  • 가능한 한 구체적인 타입 사용 (any 타입 지양)
  • 선택적 props는 ?를 사용하여 명시

제네릭 컴포넌트

 제네릭 컴포넌트는 재사용 가능한 컴포넌트를 만들 때 유용합니다.

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}
 
function List<T>({ items, renderItem }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}
 
// 사용
<List
  items={['Apple', 'Banana', 'Orange']}
  renderItem={(item) => <span>{item}</span>}
/>

React.FC 타입 사용

 React.FC (Function Component) 타입의 장단점

 장점

  • children prop이 자동으로 포함됨
  • 컴포넌트의 반환 타입이 명시적

 단점

  • defaultProps와의 통합이 어려움
  • 제네릭 컴포넌트 정의가 복잡해짐

 대안적 접근

type GreetingProps = {
  name: string;
  children?: React.ReactNode;
};
 
const Greeting = ({ name, children }: GreetingProps) => {
  return (
    <div>
      Hello, {name}!
      {children}
    </div>
  );
};

Children Prop 타입 지정

 Children prop의 타입은 React.ReactNode를 사용하여 지정할 수 있습니다.

interface WrapperProps {
  children: React.ReactNode;
}
 
const Wrapper: React.FC<WrapperProps> = ({ children }) => {
  return <div className="wrapper">{children}</div>;
};

 주의사항

  • React.ReactNode는 매우 포괄적인 타입이므로, 가능한 경우 더 구체적인 타입을 사용하는 것이 좋습니다.

이벤트 핸들러 타입 지정

 이벤트 핸들러의 타입은 React에서 제공하는 타입을 사용할 수 있습니다.

import React from 'react';
 
interface ButtonProps {
  onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
}
 
const Button: React.FC<ButtonProps> = ({ onClick }) => {
  return <button onClick={onClick}>Click me</button>;
};

 Synthetic Event 객체 활용

const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  console.log(event.target.value);
};

defaultProps와 타입스크립트

 defaultProps는 타입스크립트 3.0 이후부터 권장되지 않습니다. 대신 다음과 같은 방법을 사용할 수 있습니다.

interface GreetingProps {
  name: string;
  greeting?: string;
}
 
const Greeting = ({ name, greeting = "Hello" }: GreetingProps) => {
  return <div>{greeting}, {name}!</div>;
};

Best Practices와 주의사항

  1. 명시적 타입 사용 : any 타입 사용을 최소화하고, 가능한 한 구체적인 타입을 사용합니다.
  2. Props 인터페이스 분리 : 재사용 가능한 props 인터페이스는 별도의 파일로 분리합니다.
  3. 불변성 유지 : readonly 修飾子를 사용하여 props의 불변성을 강제합니다.
interface UserProps {
  readonly name: string;
  readonly age: number;
}
  1. 타입 추론 활용 : 가능한 경우 타입스크립트의 타입 추론 기능을 활용합니다.
  2. Union 타입 활용 : 여러 가지 경우의 수가 있는 props는 union 타입을 사용합니다.
type ButtonSize = 'small' | 'medium' | 'large';
 
interface ButtonProps {
  size: ButtonSize;
}
  1. 제네릭 신중하게 사용 : 필요한 경우에만 제네릭을 사용하고, 과도한 사용은 피합니다.
  2. 이벤트 핸들러 타입 지정 : 정확한 이벤트 타입을 사용하여 타입 안정성을 높입니다.
  3. children prop 타입 지정 : 가능한 경우 React.ReactNode 대신 더 구체적인 타입을 사용합니다.
  4. HOC와 타입스크립트 : Higher Order Components를 사용할 때는 제네릭을 활용하여 타입 안정성을 유지합니다.
  5. 테스트 작성 : 타입스크립트로 작성된 컴포넌트에 대해 적절한 단위 테스트를 작성합니다.

 React 컴포넌트를 타입스크립트로 작성하면 컴포넌트의 props와 state에 대한 타입 안정성을 확보할 수 있습니다.

 이는 런타임 오류를 줄이고, 코드의 자기 문서화를 가능하게 하며, 더 나은 개발자 경험을 제공합니다.