Umi 연동하기
Umi 연동하기 (Usage with Umi)
실제 프로젝트 개발에서는 Ant Design 같은 UI 라이브러리 외에도 빌드 도구, 라우팅, CSS, 데이터 흐름, 요청 라이브러리·요청 솔루션, i18n, 권한, 아이콘 솔루션 등이 필요하죠. 알리바바가 비즈니스 시나리오를 바탕으로 만든 React 기반 엔터프라이즈 애플리케이션 프레임워크 Umi를 프로젝트에서 사용하는 걸 권장해요.
출처: 문서
본문
실제 프로젝트 개발에서는 Ant Design 같은 UI 라이브러리 외에도 빌드 도구, 라우팅 솔루션, CSS 솔루션, 데이터 흐름 솔루션, 요청 라이브러리와 요청 솔루션, i18n 솔루션, 권한 솔루션, 아이콘 솔루션 등이 필요할 수 있어요. 우리는 비즈니스 시나리오를 바탕으로 React 기반 엔터프라이즈 애플리케이션 프레임워크인 Umi를 출시했고, 프로젝트에서 사용하길 권장해요.
Umi는 확장 가능한 엔터프라이즈 프론트엔드 애플리케이션 프레임워크이자 알리바바 그룹(Alibaba)의 기반 프론트엔드 프레임워크로, 10,000개 이상의 애플리케이션에 직간접적으로 서비스를 제공했어요.
이 글에서는 Umi, Ant Design, Ant Design Pro를 사용해 간단한 애플리케이션을 처음부터 만드는 과정을 안내할게요.
프로젝트 초기화
Umi 스캐폴드를 만드는 권장 방법은 pnpm으로 아래 명령을 실행하는 거예요.
$ mkdir myapp && cd myapp
$ pnpm create umi
npm을 쓴다면
npm create umi로 같은 효과를 볼 수 있어요. yarn이라면yarn create umi, bun을 쓴다면(아주 힙스터시군요)bunx create-umi를 실행하면 돼요 (create와 umi 사이에-가 있음을 주의하세요).
여기서는 "처음부터" 시작하고 싶으니 "Simple App"을 선택하세요.
? Pick Umi App Template › - Use arrow-keys. Return to submit.
❯ Simple App
Ant Design Pro
Vue Simple App
여기서는 "pnpm"을 권장해요. pnpm은 속도와 고스트 의존성(ghost dependencies) 처리에서 더 좋아요.
? Pick Npm Client › - Use arrow-keys. Return to submit.
npm
cnpm
tnpm
yarn
❯ pnpm
중국에 있다면 "taobao"를, 그렇지 않다면 "npm"을 선택하는 걸 권장해요.
? Pick Npm Registry › - Use arrow-keys. Return to submit.
❯ npm
taobao
그러면 도구가 자동으로 의존성을 설치하고 umi의 초기화 스크립트를 실행해요.
프로젝트를 시작하기 전에, 이 튜토리얼에서 사용할 의존성을 몇 가지 더 설치해 볼게요.
$ pnpm i @umijs/plugins -D
$ pnpm i antd axios @ant-design/pro-components -S
@umijs/plugins는 Umi의 공식 플러그인 묶음으로, valtio, react-query, styled-components, locale, access, qiankun 등 많은 플러그인을 포함해요. antd는 설명이 필요 없겠죠. axios는 요청 라이브러리이고, @ant-design/pro-components는 레이아웃을 생성하는 데 쓰는 컴포넌트예요.
완료되면 다음 명령으로 프로젝트를 시작해요.
$ npm run dev
umi dev
info - Umi v4.0.46
╔════════════════════════════════════════════════════╗
║ App listening at: ║
║ > Local: http://localhost:8000 ║
ready - ║ > Network: http://*********:8000 ║
║ ║
║ Now you can open browser with the above addresses↑ ║
╚════════════════════════════════════════════════════╝
화면의 안내를 따라 커맨드라인의 url을 클릭하면 브라우저가 자동으로 열려요. 잘 진행됐다면 아래 화면이 보일 거예요.

새 라우트 만들기
우리는 제품 목록을 보여주는 애플리케이션을 작성해 볼 거예요. 첫 단계는 라우트를 만드는 일인데, 라우트는 애플리케이션을 구성하는 서로 다른 페이지라고 생각하면 돼요. Umi 사용자는 보통 Umi 뒤의 구현에 신경 쓸 필요가 없지만, 궁금해하는 분들을 위해 말하자면 Umi의 라우트는 [email protected] 기반이에요 (참고: 최신 6.4가 아니라요. 6.4에는 Umi에 필요 없는 loader와 action 기능이 들어 있어요).
CLI로 라우트를 만들 수 있어요.
$ npx umi g page products
Write: src/pages/products.tsx
Write: src/pages/products.less
그런 다음 설정 파일 .umirc.ts에 새 라우트 선언을 추가해요.
import { defineConfig } from "umi";
export default defineConfig({
routes: [
{ path: "/", component: "index" },
{ path: "/docs", component: "docs" },
+ { path: "/products", component: "products" },
],
npmClient: "pnpm",
});
보일러플레이트는 설정형 라우팅(configured routing)을 사용하므로, 이름처럼 라우트를 사람이 한 줄씩 설정해요. 다소 번거롭지만 더 유연하죠. 이 방식은 설정에 routes 필드를 추가해야 해요. Umi 라우팅 문서를 참고하세요. 또한 Umi는 파일 시스템이 곧 라우트가 되는 규약형 라우팅(convention-based routing)도 지원해서, 라우트를 설정하지 않아도 동작해요.
그다음 src/layouts/index.tsx 파일을 수정해서 전역 레이아웃 라우트에 /products 경로로 가는 내비게이션을 추가해요.
<li>
<Link to="/docs">Docs</Link>
</li>
+ <li>
+ <Link to="/products">Products</Link>
+ </li>
http://localhost:8000/products 를 열어 보고, 잘 진행됐다면 아래 페이지가 보일 거예요.

Product UI 컴포넌트 구현하기
애플리케이션이 커지면 여러 페이지에서 UI 요소를 공유해야 할 때가 와요 (또는 한 페이지에서 여러 번 사용). Umi에서는 이것을 컴포넌트로 추상화할 수 있어요. 제품 목록을 여러 곳에서 표시할 수 있도록 ProductList 컴포넌트를 작성해 볼게요.
새 파일 src/components/ProductList.tsx를 다음 코드로 만들어요.
import React from 'react';
import { Button, Popconfirm, Table } from 'antd';
import type { TableProps } from 'antd';
interface DataType {
id: string;
name: string;
}
const ProductList: React.FC<{ products: DataType[]; onDelete: (id: string) => void }> = ({
onDelete,
products,
}) => {
const columns: TableProps<DataType>['columns'] = [
{
title: 'Name',
dataIndex: 'name',
},
{
title: 'Actions',
render(text, record) {
return (
<Popconfirm title="Delete?" onConfirm={() => onDelete(record.id)}>
<Button>Delete</Button>
</Popconfirm>
);
},
},
];
return <Table rowKey="id" dataSource={products} columns={columns} />;
};
export default ProductList;
Mock 데이터 준비
백엔드 개발자와 API 인터페이스를 합의했다고 가정해 봅시다. 이제 Mock 데이터로 API가 반환해야 할 데이터를 로컬에서 흉내내면, 백엔드 API가 아직 개발 중이라도 프론트엔드 작업이 막히지 않고 양쪽 개발을 동시에 진행할 수 있어요. Umi는 바로 쓸 수 있는 Mock 기능을 제공해서, 편리하고 쉽게 Mock 데이터를 설정할 수 있어요.
루트 디렉터리에 새 파일 mock/products.ts를 아래 코드로 만들어요.
import { defineMock } from 'umi';
type Product = {
id: string;
name: string;
};
let products: Product[] = [
{ id: '1', name: 'Umi' },
{ id: '2', name: 'Ant Design' },
{ id: '3', name: 'Ant Design Pro' },
{ id: '4', name: 'Dva' },
];
export default defineMock({
'GET /api/products': (_, res) => {
res.send({
status: 'ok',
data: products,
});
},
'DELETE /api/products/:id': (req, res) => {
products = products.filter((item) => item.id !== req.params.id);
res.send({ status: 'ok' });
},
});
그런 다음 http://localhost:8000/api/products 를 방문하면 api 응답을 확인할 수 있어요.
제품 페이지 완성하기
UI 컴포넌트와 Mock 데이터가 준비됐으니, 이제 이들을 한데 묶을 차례예요. 여기서는 요청 솔루션이 필요한데, 우리 선택은 react-query예요 (@tanstack/react-query라고 한다면, 맞아요 같은 라이브러리예요. @tanstack/react-query는 react-query가 이름을 바꾼 패키지예요). 시작하기 전에 설정을 바꿔 Umi용 react-query 플러그인을 한 번에 활성화해야 해요.
먼저 .umirc.ts를 수정해요.
import { defineConfig } from "umi";
export default defineConfig({
+ plugins: ['@umijs/plugins/dist/react-query'],
+ reactQuery: {},
routes: [
{ path: "/", component: "index" },
{ path: "/docs", component: "docs" },
{ path: "/products", component: "products" },
],
npmClient: 'pnpm',
});
src/pages/products.tsx를 아래 코드로 수정해요.
import React from 'react';
import axios from 'axios';
import { useMutation, useQuery, useQueryClient } from 'umi';
import styles from './products.less';
import ProductList from '@/components/ProductList';
export default function Page() {
const queryClient = useQueryClient();
const productsQuery = useQuery(['products'], {
queryFn() {
return axios.get('/api/products').then((res) => res.data);
},
});
const productsDeleteMutation = useMutation({
mutationFn(id: string) {
return axios.delete(`/api/products/${id}`);
},
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['products'] });
},
});
if (productsQuery.isLoading) {
return null;
}
return (
<div>
<h1 className={styles.title}>Page products</h1>
<ProductList
products={productsQuery.data.data}
onDelete={(id) => {
productsDeleteMutation.mutate(id);
}}
/>
</div>
);
}
여기서 우리는 useQuery()로 /api/products에서 데이터를 가져오고, onDelete 이벤트에서 useMutation()으로 /api/products/${id}에 DELETE 요청을 보내 삭제를 수행해요. react-query 사용에 대한 더 자세한 내용은 Umi React Query 플러그인과 React Query 공식 사이트를 참고하세요.
저장하면 아래 화면이 보일 거예요.

ProLayout
표준 백엔드 프로젝트는 보통 레이아웃이 필요하고, 이 레이아웃은 서로 아주 비슷한 경우가 많아요. ProLayout은 흔한 메뉴, 브레드크럼(breadcrumb), 페이지 헤더 등의 기능을 캡슐화해서, 프레임워크에 의존하지 않고 바로 쓸 수 있는 고급 레이아웃 컴포넌트를 제공해요. 그리고 side, mix, top 세 가지 모드를 지원하며, 메뉴 선택, 메뉴로 브레드크럼 생성, 페이지 제목 자동 설정 로직도 내장되어 있어요.
각 라우트 설정에 ProLayout이 메뉴 렌더링에 쓸 수 있도록 name 필드를 추가해요.
import { defineConfig } from "umi";
export default defineConfig({
routes: [
- { path: "/", component: "index" },
+ { path: "/", component: "index", name: "home" },
- { path: "/docs", component: "docs" },
+ { path: "/docs", component: "docs", name: "docs" },
- { path: "/products", component: "products" },
+ { path: "/products", component: "products", name: "products" },
],
plugins: ["@umijs/plugins/dist/react-query"],
reactQuery: {},
npmClient: "pnpm",
});
src/layouts/index.tsx를 아래 코드로 수정해요.
import { ProLayout } from '@ant-design/pro-components';
import { Link, Outlet, useAppData, useLocation } from 'umi';
export default function Layout() {
const { clientRoutes } = useAppData();
const location = useLocation();
return (
<ProLayout
route={clientRoutes[0]}
location={location}
title="Umi x Ant Design"
menuItemRender={(menuItemProps, defaultDom) => {
if (menuItemProps.isUrl || menuItemProps.children) {
return defaultDom;
}
if (menuItemProps.path && location.pathname !== menuItemProps.path) {
return (
<Link to={menuItemProps.path} target={menuItemProps.target}>
{defaultDom}
</Link>
);
}
return defaultDom;
}}
>
<Outlet />
</ProLayout>
);
}
여기서 먼저 umi의 useAppData로 중첩 라우팅 객체인 전역 클라이언트 라우트 clientRoutes를 얻고, clientRoutes[0]을 ProLayout에 전달해요. 그런 다음 useLocation()으로 location 정보를 얻어 이것도 ProLayout에 전달해 어떤 메뉴를 강조할지 결정해요. 또 메뉴를 클릭하면 라우트 점프가 일어나도록, ProLayout의 menuItemRender 메서드를 커스터마이징해야 해요.
src/layouts/index.less가 참조되는 곳이 없다는 걸 눈치챘을 거예요. 프로젝트 파일을 깔끔하게 유지하려면 그 파일을 삭제해도 돼요.
이 시점에 브라우저가 자동으로 새로고침되고, 잘 진행됐다면 아래 화면이 보일 거예요.

애플리케이션 빌드
개발을 마치고 개발 환경에서 검증했다면, 다음 명령을 실행해 사용자에게 배포할 차례예요.
$ npm run build
info - Umi v4.0.46
✔ Webpack
Compiled successfully in 5.31s
info - File sizes after gzip:
122.45 kB dist/umi.js
575 B dist/src__pages__products.async.js
312 B dist/src__pages__index.async.js
291 B dist/layouts__index.async.js
100 B dist/layouts__index.chunk.css
55 B dist/src__pages__products.chunk.css
event - Build index.html
빌드 명령은 JavaScript, CSS, Web Fonts, 이미지, Html 등 모든 리소스를 패키징해요. 이 파일들은 dist/ 디렉터리에서 찾을 수 있어요.
다음 단계
간단한 애플리케이션을 완성했지만 아직 궁금한 점이 많을 수 있어요. 예를 들어:
- 오류를 일관되게 처리하는 방법은?
- 동적 라우팅, 중첩 라우팅, 권한 라우팅처럼 더 많은 라우팅을 처리하는 방법은?
- 데이터 흐름 솔루션을 사용하는 방법은?
- webpack 설정을 수정하거나 vite 빌드 모드로 전환하는 방법은?
- 등등
다음을 참고할 수 있어요.
- Umi 공식 사이트 방문
- Umi의 라우팅 알아보기
- Umi보다 더 통합된 Umi Max 알아보기
- 바로 쓸 수 있는 중대형 백엔드 스캐폴드 Ant Design Pro 알아보기
- 고급 레이아웃 ProLayout 알아보기
- 고급 테이블 ProTable 알아보기