fe-migrate
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFE Migration Guide
前端迁移指南
$ARGUMENTS从中解析迁移源和目标,提供并执行分步指南。
$ARGUMENTS지원 마이그레이션
支持的迁移场景
| From | To | 키워드 |
|---|---|---|
| Pages Router | App Router | |
| JavaScript | TypeScript | |
| CRA (Create React App) | Vite | |
| CSS/SCSS | Tailwind CSS | |
| Class Components | Hooks | |
| Redux | Zustand | |
| Jest | Vitest | |
| Axios | Fetch | |
| Moment.js | date-fns | |
인자가 없으면 사용자에게 어떤 마이그레이션을 원하는지 질문한다.
| 源技术栈 | 目标技术栈 | 关键词 |
|---|---|---|
| Pages Router | App Router | |
| JavaScript | TypeScript | |
| CRA (Create React App) | Vite | |
| CSS/SCSS | Tailwind CSS | |
| Class Components | Hooks | |
| Redux | Zustand | |
| Jest | Vitest | |
| Axios | Fetch | |
| Moment.js | date-fns | |
若未传入参数,则询问用户需要哪种迁移。
마이그레이션 공통 절차
迁移通用流程
- 현재 상태 분석: 프로젝트 구조, 의존성, 설정 파일을 파악한다
- 영향 범위 파악: 변경이 필요한 파일 목록을 Glob/Grep으로 추출한다
- 마이그레이션 계획 제시: 단계별 작업 목록을 사용자에게 보여준다
- 단계별 실행: 승인 후 순차적으로 변환을 진행한다
- 검증: 빌드 및 테스트 통과를 확인한다
- 当前状态分析:梳理项目结构、依赖项、配置文件
- 影响范围确认:通过Glob/Grep提取需要修改的文件列表
- 制定迁移计划:向用户展示分步任务清单
- 分步执行:获得用户确认后依次完成转换
- 验证:确认构建及测试全部通过
Pages Router → App Router
Pages Router → App Router
단계별 가이드
分步指南
1단계: 기반 설정
src/app/layout.tsx ← pages/_app.tsx 에서 이동
src/app/page.tsx ← pages/index.tsx 에서 이동
src/app/globals.css ← styles/globals.css 에서 이동2단계: 페이지 변환 규칙
| Pages Router | App Router |
|---|---|
| |
| |
| |
| |
| |
3단계: 데이터 페칭 변환
tsx
// Before: getServerSideProps
export async function getServerSideProps() {
const data = await fetchData();
return { props: { data } };
}
export default function Page({ data }) { /* ... */ }
// After: Server Component async
export default async function Page() {
const data = await fetchData();
return /* ... */;
}tsx
// Before: getStaticProps + getStaticPaths
export async function getStaticPaths() {
return { paths: [...], fallback: false };
}
export async function getStaticProps({ params }) {
const data = await fetchData(params.id);
return { props: { data }, revalidate: 60 };
}
// After: generateStaticParams + fetch with revalidate
export async function generateStaticParams() {
return [...];
}
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const data = await fetchData(id);
return /* ... */;
}4단계: Hook/상태 사용 컴포넌트 → "use client"
- ,
useState,useEffect, 이벤트 핸들러가 있는 컴포넌트에useContext추가"use client" - 가능한 Client Component를 작게 분리
5단계: Head → Metadata
tsx
// Before: next/head
import Head from "next/head";
<Head><title>Page</title></Head>
// After: Metadata export
export const metadata: Metadata = {
title: "Page",
};1阶段:基础配置迁移
src/app/layout.tsx ← 从pages/_app.tsx迁移而来
src/app/page.tsx ← 从pages/index.tsx迁移而来
src/app/globals.css ← 从styles/globals.css迁移而来2阶段:页面转换规则
| Pages Router | App Router |
|---|---|
| |
| |
| |
| |
| |
3阶段:数据获取逻辑转换
tsx
// 之前:getServerSideProps
export async function getServerSideProps() {
const data = await fetchData();
return { props: { data } };
}
export default function Page({ data }) { /* ... */ }
// 之后:Server Component异步写法
export default async function Page() {
const data = await fetchData();
return /* ... */;
}tsx
// 之前:getStaticProps + getStaticPaths
export async function getStaticPaths() {
return { paths: [...], fallback: false };
}
export async function getStaticProps({ params }) {
const data = await fetchData(params.id);
return { props: { data }, revalidate: 60 };
}
// 之后:generateStaticParams + 带revalidate的fetch
export async function generateStaticParams() {
return [...];
}
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const data = await fetchData(id);
return /* ... */;
}4阶段:含Hook/状态的组件 → 添加"use client"
- 对包含、
useState、useEffect或事件处理器的组件添加useContext指令"use client" - 尽量将Client Component拆分为更小的模块
5阶段:Head组件 → Metadata导出
tsx
// 之前:next/head
import Head from "next/head";
<Head><title>Page</title></Head>
// 之后:导出Metadata
export const metadata: Metadata = {
title: "Page",
};JavaScript → TypeScript
JavaScript → TypeScript
단계별 가이드
分步指南
1단계: TypeScript 설치 & 설정
bash
pnpm add -D typescript @types/react @types/react-dom @types/node
npx tsc --init2단계: 파일 확장자 변경
- →
.js(로직 파일).ts - →
.jsx(JSX 포함 파일).tsx
3단계: 점진적 타입 추가
typescript
// tsconfig.json — 느슨하게 시작
{
"compilerOptions": {
"strict": false, // 점진적으로 true로 전환
"allowJs": true, // JS 파일 허용
"noImplicitAny": false // 나중에 true로
}
}4단계: strict mode 순차 활성화
noImplicitAny: truestrictNullChecks: truestrict: true
1阶段:安装TypeScript并配置
bash
pnpm add -D typescript @types/react @types/react-dom @types/node
npx tsc --init2阶段:修改文件扩展名
- →
.js(逻辑文件).ts - →
.jsx(包含JSX的文件).tsx
3阶段:逐步添加类型定义
typescript
// tsconfig.json — 从宽松模式开始
{
"compilerOptions": {
"strict": false, // 逐步切换为true
"allowJs": true, // 允许JS文件
"noImplicitAny": false // 后续改为true
}
}4阶段:依次启用严格模式
noImplicitAny: truestrictNullChecks: truestrict: true
CRA → Vite
CRA → Vite
변환 포인트
转换要点
| CRA | Vite |
|---|---|
| |
| |
| |
| |
| |
| CRA | Vite |
|---|---|
| |
| |
| |
| |
| |
Jest → Vitest
Jest → Vitest
변환 포인트
转换要点
| Jest | Vitest |
|---|---|
| |
| |
| |
| |
| |
| |
| |
typescript
// vitest.config.ts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import path from "path";
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: "jsdom",
setupFiles: "./src/test/setup.ts",
css: true,
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});| Jest | Vitest |
|---|---|
| |
| |
| |
| |
| |
| |
| vite配置中的 |
typescript
// vitest.config.ts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
import path from "path";
export default defineConfig({
plugins: [react()],
test: {
globals: true,
environment: "jsdom",
setupFiles: "./src/test/setup.ts",
css: true,
},
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});Redux → Zustand
Redux → Zustand
변환 패턴
转换示例
typescript
// Before: Redux Toolkit
const userSlice = createSlice({
name: "user",
initialState: { name: "", email: "" },
reducers: {
setUser: (state, action) => { Object.assign(state, action.payload); },
clearUser: () => ({ name: "", email: "" }),
},
});
// After: Zustand
interface UserState {
name: string;
email: string;
setUser: (user: { name: string; email: string }) => void;
clearUser: () => void;
}
const useUserStore = create<UserState>()((set) => ({
name: "",
email: "",
setUser: (user) => set(user),
clearUser: () => set({ name: "", email: "" }),
}));typescript
// 之前:Redux Toolkit
const userSlice = createSlice({
name: "user",
initialState: { name: "", email: "" },
reducers: {
setUser: (state, action) => { Object.assign(state, action.payload); },
clearUser: () => ({ name: "", email: "" }),
},
});
// 之后:Zustand
interface UserState {
name: string;
email: string;
setUser: (user: { name: string; email: string }) => void;
clearUser: () => void;
}
const useUserStore = create<UserState>()((set) => ({
name: "",
email: "",
setUser: (user) => set(user),
clearUser: () => set({ name: "", email: "" }),
}));실행 규칙
执行规则
- 인자가 없으면 사용자에게 마이그레이션 종류를 질문한다
- 마이그레이션 전 현재 프로젝트 상태를 반드시 분석한다
- 대규모 변경은 단계별로 나눠서 진행하고, 각 단계 후 빌드/테스트를 확인한다
- 기존 코드를 삭제하기 전에 새 코드가 동작하는지 확인한다
- 변경이 필요하면 명령어를 안내하고 사용자 승인 후 실행한다
package.json - 지원 목록에 없는 마이그레이션도 요청 시 분석 후 가이드를 제공한다
- 若无参数,则询问用户需要的迁移类型
- 迁移前必须分析当前项目状态
- 大规模变更需拆分阶段执行,每个阶段后确认构建/测试通过
- 删除原有代码前,需确认新代码可正常运行
- 若需修改,需先提供命令并获得用户确认后再执行
package.json - 对于支持列表外的迁移请求,也需分析后提供指导方案