테마
Redux 상태관리 컴포넌트 - ANTD 기반 MailList 구현
Redux Toolkit과 Ant Design을 조합하여 Shadow DOM 내부에서 동작하는 MailList 컴포넌트를 구현한다
학습 목표
- Redux Toolkit으로 마이크로 프론트엔드용 독립 스토어를 설계할 수 있다
- ANTD Table, Modal, Button을 활용한 MailList 컴포넌트를 구현할 수 있다
- Shadow DOM 내부에서 ANTD 스타일이 올바르게 동작하도록 설정할 수 있다
- 마이크로 컴포넌트의 상태관리 패턴을 이해한다
본문
1. MailList 컴포넌트 아키텍처
MailList 컴포넌트는 docs 프로젝트에서 제공하는 마이크로 컴포넌트로, 독립적인 Redux 스토어와 ANTD UI를 가진다. 이 컴포넌트는 다른 프로젝트(web, legacy)에서 자유롭게 소비될 수 있다.
2. Redux Toolkit으로 Store 구현
데이터 모델과 초기 상태
typescript
// store.ts
import { createSlice, configureStore, PayloadAction } from "@reduxjs/toolkit";
interface Email {
id: number;
sender: string;
receiver: string;
content: string;
}
const initialEmails: Email[] = [
{ id: 1, sender: "김철수", receiver: "이영희", content: "회의 일정 안내" },
{ id: 2, sender: "박지민", receiver: "최수진", content: "프로젝트 진행 상황" },
{ id: 3, sender: "정우성", receiver: "한지민", content: "코드 리뷰 요청" },
{ id: 4, sender: "이수현", receiver: "강동원", content: "배포 완료 알림" },
];Slice와 Store 설정
typescript
const emailSlice = createSlice({
name: "emails",
initialState: initialEmails,
reducers: {
deleteEmail: (state, action: PayloadAction<number>) => {
return state.filter((email) => email.id !== action.payload);
},
},
});
export const { deleteEmail } = emailSlice.actions;
// 셀렉터
export const selectEmails = (state: RootState) => state.emails;
// 스토어
const store = configureStore({
reducer: {
emails: emailSlice.reducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;3. ANTD Table로 MailList 컴포넌트 작성
컴포넌트 구현
tsx
import { Table, Button, Modal } from "antd";
import { useSelector, useDispatch } from "react-redux";
import { selectEmails, deleteEmail } from "./store";
function MailList() {
const emails = useSelector(selectEmails);
const dispatch = useDispatch();
const [selectedEmail, setSelectedEmail] = useState<Email | undefined>();
const columns = [
{ title: "보낸 사람", dataIndex: "sender", key: "sender" },
{ title: "받은 사람", dataIndex: "receiver", key: "receiver" },
{ title: "내용", dataIndex: "content", key: "content" },
{
title: "처리",
dataIndex: "id",
key: "action",
render: (id: number) => (
<Button
danger
onClick={(e) => {
e.stopPropagation();
dispatch(deleteEmail(id));
}}
>
삭제
</Button>
),
},
];
return (
<>
<Table
columns={columns}
dataSource={emails}
rowKey="id"
onRow={(record) => ({
onClick: () => setSelectedEmail(record),
style: { cursor: "pointer" },
})}
/>
<Modal
open={!!selectedEmail}
title="메일 상세"
onCancel={() => setSelectedEmail(undefined)}
getContainer={shadowRoot}
>
<p>보낸 사람: {selectedEmail?.sender}</p>
<p>받은 사람: {selectedEmail?.receiver}</p>
<p>내용: {selectedEmail?.content}</p>
</Modal>
</>
);
}4. GlobalProvider 구성
ANTD 스타일을 Shadow DOM 내부에 삽입하기 위해 @ant-design/cssinjs의 StyleProvider를 사용한다.
tsx
import { Provider } from "react-redux";
import { StyleProvider } from "@ant-design/cssinjs";
import store from "./store";
interface GlobalProviderProps {
children: React.ReactNode;
shadowRoot: ShadowRoot;
}
function GlobalProvider({ children, shadowRoot }: GlobalProviderProps) {
return (
<StyleProvider container={shadowRoot}>
<Provider store={store}>{children}</Provider>
</StyleProvider>
);
}5. Shadow DOM에서의 Modal 처리
ANTD Modal은 기본적으로 document.body에 포탈로 렌더링된다. Shadow DOM 환경에서는 getContainer로 렌더링 위치를 지정해야 한다.
6. UMD Export로 마이크로 컴포넌트 제공
typescript
// index.tsx
import MailList from "./MailList";
import GlobalProvider from "./GlobalProvider";
// UMD로 Export - 다른 프로젝트에서 소비 가능
const render = (container: HTMLElement, shadowRoot: ShadowRoot) => {
const root = ReactDOM.createRoot(container);
root.render(
<GlobalProvider shadowRoot={shadowRoot}>
<MailList />
</GlobalProvider>
);
return () => root.unmount();
};
// window 객체에 등록
if (!window.docs) window.docs = {};
window.docs.MailList = { render };
window.docs.default = { render: (container, props) => {
render(container, props?.shadowRoot);
}};7. Redux와 마이크로 프론트엔드의 관계
| 설계 원칙 | 설명 |
|---|---|
| 독립 스토어 | 각 마이크로 컴포넌트가 자체 Redux 스토어를 가진다 |
| 상태 비공유 | 프로젝트 간 Redux 상태를 직접 공유하지 않는다 |
| 컴포넌트 캡슐화 | Provider를 포함한 전체 구성을 하나의 단위로 제공한다 |
| 이벤트 통신 | 프로젝트 간 통신이 필요하면 Custom Event를 사용한다 |
핵심 정리
- Redux Toolkit의 간결함:
createSlice로 액션과 리듀서를 한 번에 정의하고,configureStore로 스토어를 구성한다 - ANTD + Shadow DOM:
StyleProvider의container에 Shadow Root를 전달하고, Modal 등 포탈 컴포넌트에는getContainer를 설정한다 - 마이크로 컴포넌트 패턴: Redux Provider를 포함한 전체 트리를 하나의 render 함수로 제공하여, 소비하는 측에서는 단순히 호출만 하면 된다
- 독립 상태관리: 각 마이크로 컴포넌트가 독립 스토어를 가지므로 프로젝트 간 상태 의존성이 없다