테마
프래그먼트 컴포넌트 작성
네트워킹 팀이 제공하는 "추천 1촌 맺기" 프래그먼트와 채용 팀이 제공하는 "추천 채용 공고" 프래그먼트를 Container/Presentational 패턴으로 구현하고, API를 연동한다.
학습 목표
- 별도 런타임으로 프래그먼트를 생성하여 의존성을 격리하는 방법을 이해한다
- 기존 마이크로앱 내부에 프래그먼트를 추가하여 exposes를 확장하는 방법을 이해한다
- Container/Presentational 패턴으로 프래그먼트의 데이터 로직과 UI를 분리할 수 있다
- 프래그먼트에서 shell-router의 useAuth0Client와 useShellNavigate를 활용할 수 있다
1. 두 가지 프래그먼트 제공 방식
커리어업 프로젝트에서는 두 가지 다른 방식으로 프래그먼트를 구현한다.
2. 추천 1촌 프래그먼트 (별도 런타임)
네트워킹 팀이 제공하는 프래그먼트다. fragments/ 폴더에 독립 MF 앱으로 생성하여 의존성을 철저히 격리한다.
2-1. 프로젝트 구조
fragments/fragment-recommend-connections/
├── package.json
├── webpack.config.js
├── .eslintrc.js
└── src/
├── types.ts
├── apis.ts
├── containers/
│ └── RecommendConnectionsContainer.tsx
└── components/
├── RecommendConnections.tsx
└── recommend-connections.css2-2. 타입과 API
typescript
// src/types.ts
export interface ConnectionType {
name: string;
picture: string;
role: string;
networkCount: number;
}typescript
// src/apis.ts
import type { ConnectionType } from "./types";
export async function getConnections(token: string): Promise<ConnectionType[]> {
const response = await fetch("http://localhost:4000/connections", {
headers: { authorization: `Bearer ${token}` }
});
return await response.json();
}2-3. Container (데이터 로직)
typescript
// src/containers/RecommendConnectionsContainer.tsx
import React, { useState, useCallback } from "react";
import { useAuth0Client } from "@career-up/shell-router";
import { getConnections } from "../apis";
import type { ConnectionType } from "../types";
import RecommendConnections from "../components/RecommendConnections";
const RecommendConnectionsContainer: React.FC = () => {
const auth0Client = useAuth0Client();
const [connections, setConnections] = useState<ConnectionType[]>([]);
const patchConnections = useCallback(async () => {
try {
const token = await auth0Client.getTokenSilently();
const data = await getConnections(token);
setConnections(data);
} catch (e) {
alert(e);
}
}, [auth0Client]);
return (
<RecommendConnections
connections={connections}
patchConnections={patchConnections}
/>
);
};
export default RecommendConnectionsContainer;2-4. Presentational (UI)
typescript
// src/components/RecommendConnections.tsx
import React, { useEffect } from "react";
import "./recommend-connections.css";
import type { ConnectionType } from "../types";
interface Props {
connections: ConnectionType[];
patchConnections: () => Promise<void>;
}
const RecommendConnections: React.FC<Props> = ({
connections,
patchConnections
}) => {
useEffect(() => {
patchConnections();
}, [patchConnections]);
return (
<div className="fragment-recommend-connections">
<div className="fragment-recommend-connections__header">
<span>추천 1촌</span>
</div>
<div className="fragment-recommend-connections__list">
{connections.map((conn, idx) => (
<div key={idx} className="fragment-recommend-connections__item">
<img
src={conn.picture || "/default-avatar.png"}
alt={conn.name}
className="fragment-recommend-connections__avatar"
/>
<div className="fragment-recommend-connections__info">
<span className="fragment-recommend-connections__name">
{conn.name}
</span>
<span className="fragment-recommend-connections__role">
{conn.role}
</span>
</div>
<button className="fragment-recommend-connections__btn">
1촌 맺기
</button>
</div>
))}
</div>
</div>
);
};
export default RecommendConnections;3. 추천 채용 공고 프래그먼트 (기존 앱 확장)
채용 팀이 기존 apps/job 마이크로앱 내부에 프래그먼트를 추가하는 방식이다.
3-1. 프로젝트 구조 (기존 job 앱에 추가)
apps/job/src/
├── injector.tsx (기존 마이크로앱 진입점)
├── fragments/ (새 폴더)
│ ├── RecommendJobsContainer.tsx
│ ├── RecommendJobs.tsx
│ ├── RecommendJobs.styles.ts
│ ├── RecommendJob.tsx
│ └── RecommendJob.styles.ts
├── apis.ts (기존 API 재사용)
└── types.ts (기존 타입 재사용)3-2. Container
typescript
// src/fragments/RecommendJobsContainer.tsx
import React, { useState, useCallback } from "react";
import { useAuth0Client } from "@career-up/shell-router";
import { getJobs } from "../apis";
import type { JobType } from "../types";
import RecommendJobs from "./RecommendJobs";
const RecommendJobsContainer: React.FC = () => {
const auth0Client = useAuth0Client();
const [jobs, setJobs] = useState<JobType[]>([]);
const patchJobs = useCallback(async () => {
try {
const token = await auth0Client.getTokenSilently();
const allJobs = await getJobs(token);
setJobs(allJobs.slice(0, 3)); // 상위 3개만 추천
} catch (e) {
alert(e);
}
}, [auth0Client]);
return <RecommendJobs jobs={jobs} patchJobs={patchJobs} />;
};
export default RecommendJobsContainer;3-3. Presentational (useShellNavigate 활용)
typescript
// src/fragments/RecommendJob.tsx
import React from "react";
import { useShellNavigate } from "@career-up/shell-router";
import { RecommendJobWrapper } from "./RecommendJob.styles";
interface Props {
id: number;
position: string;
company: string;
}
const RecommendJob: React.FC<Props> = ({ id, position, company }) => {
const navigate = useShellNavigate();
const onClick = () => {
navigate(`/job/${id}`); // Shell의 BrowserRouter로 이동
};
return (
<RecommendJobWrapper onClick={onClick}>
<span className="job--recommend-job__position">{position}</span>
<span className="job--recommend-job__company">{company}</span>
</RecommendJobWrapper>
);
};
export default RecommendJob;기존 앱 확장 방식의 장점: 기존 apis.ts, types.ts를 그대로 재사용할 수 있다. 채용 팀이 이미 관리하고 있는 코드를 별도로 복사할 필요가 없다.
4. 두 방식의 비교와 코드 중복 문제
해결 방향: 별도 런타임 방식에서 코드가 중복되면, 팀 내부에서 공유 패키지(@career-up/network-shared)를 만들어 타입과 API 함수를 추출한다. 이것이 마이크로프론트엔드가 성숙해지며 자연스럽게 발전하는 패턴이다.
5. Webpack expose 설정
javascript
// 방식 1: fragments/fragment-recommend-connections/webpack.config.js
new ModuleFederationPlugin({
name: "fragment_recommend_connections",
filename: "remoteEntry.js",
exposes: {
"./container": "./src/containers/RecommendConnectionsContainer.tsx"
},
shared: {
react: { singleton: true, requiredVersion: false },
"react-dom": { singleton: true, requiredVersion: false },
"@career-up/shell-router": { singleton: true, requiredVersion: false },
"@career-up/uikit": { singleton: true, requiredVersion: false }
}
})javascript
// 방식 2: apps/job/webpack.config.js (기존 설정 확장)
new ModuleFederationPlugin({
name: "job",
filename: "remoteEntry.js",
exposes: {
"./injector": "./src/injector.tsx", // 기존
"./fragment-recommend-jobs": "./src/fragments/RecommendJobsContainer.tsx" // 추가
},
shared: { /* 기존 shared 설정 그대로 */ }
})핵심 정리
- 별도 런타임 방식은 의존성을 완벽히 격리하여 불필요한 코드 유입을 방지하지만, 초기 설정 비용이 높고 코드 중복이 발생할 수 있다
- 기존 앱 확장 방식은 설정이 간단하고 기존 코드를 재사용할 수 있지만, 프래그먼트에 불필요한 의존성이 딸려갈 수 있다
- Container는
useAuth0Client()로 토큰을 획득하고 API를 호출하며, Presentational은 순수하게 props만 받아 렌더한다 useShellNavigate()는 CustomEvent를 통해 Shell의 BrowserRouter를 제어하므로, 프래그먼트에서 다른 마이크로앱의 라우트로 이동할 수 있다- 코드 중복이 발견되면 팀 내부 공유 패키지를 만들어 타입과 API 함수를 추출하는 것이 자연스러운 발전 방향이다
다음 단계
- 03-모듈-페더레이션-프래그먼트-설정.md: 소비자(포스팅 앱) 측에서 webpack.config.js에 프래그먼트를 remote로 등록하고, React.lazy와 Suspense로 프래그먼트를 로딩하며, 에러 처리와 동적 리모트 로딩을 구현한다