본문 바로가기

Typescript

React Flow에서 제네릭(Generic) 이해하기

Generic을 실제 프로젝트에 적용해보았다. 진행 중인 프로젝트에서는 React Flow(현재는 @xyflow/react)를 사용하고 있고, 아래와 같은 방법으로 generic을 사용할 수 있었다. 특히, 노드 데이터 구조가 복잡해질수록 제네릭을 쓰는 게 훨씬 안전하다는 것을 체감했다.

 

React Flow의 구조

먼저, 프로젝트 구조 예시인데, React Flow에서는 노드 타입별로 렌더링할 컴포넌트를 지정할 수 있다.

const nodeTypes = {
  group: LogicGroupNode,
  table: LogicTableNode,
};

return (
  <ReactFlow
    nodes={nodesWithStyle}
    edges={edges}
    nodeTypes={nodeTypes}
  >
    <MiniMap pannable zoomable />
    <Controls />
    <Background gap={24} size={1} />
  </ReactFlow>
);

 

React Flow에서는 노드 타입별로 렌더링할 컴포넌트를 지정할 수 있다.

내 프로젝트에서는 이런 식으로 구현했다.

export default function LogicTableNode({
  data,
}: NodeProps<Node<LogicNodeData>>) {
  ...
}

 

Node와 NodeProps에 generic을 적용해보자

 

React Flow에서 노드는 다음과 같은 타입으로 정의되어 있다.

type Node<Data = any> = {
  id: string;
  type?: string;
  position: XYPosition;
  data: Data; // 이 부분이 바로 제네릭
  ...
};

 

즉, Node<T>는 T 타입의 데이터를 가진 Node를 의미한다.

NodeProps 타입도 제네릭으로 정의되어 있다.

type NodeProps<T extends Node = Node> = {
  id: string;
  data: T["data"];
  selected?: boolean;
  ...
};

 

타입 검증을 해준다.

즉, NodeProps<Node<LogicNodeData>>처럼 명시해두면 그 안의 data가 LogicNodeData 타입으로 한정되어, 아래와 같이 data 내부 속성에 접근할 때 타입이 정확히 보장된다.

data.label;      // ✅ string 타입으로 인식
data.tableId;    // ✅ string | undefined
data.unknownKey; // ❌ 존재하지 않는 필드 → 타입 에러

 

제네릭을 사용하는 이유

제네릭은 유연하면서도 타입 안전한 재사용성을 제공한다. any를 사용하면 유연하긴 하지만, 타입 오류를 컴파일 타임에 잡을 수 없다. 반면, 제네릭을 활용하면 데이터 구조가 바뀌더라도 타입을 추론해 오류를 예방할 수 있다.