vue3转react会遇到的难点问题
元宝回答
展开查看
企业级 React 项目中常见的 10 大开发难点及解决方案:
一、请求竞态问题(Race Condition)
难点:高频请求(如搜索联想)导致旧数据覆盖新数据
解决方案:请求 ID 追踪 + AbortController
// 使用 useRef 保存最新请求 ID
const SearchComponent = () => {
const [keyword, setKeyword] = useState('');
const [results, setResults] = useState([]);
const requestIdRef = useRef(0);
useEffect(() => {
const currentRequestId = ++requestIdRef.current;
const abortController = new AbortController();
fetch(`/api/search?q=${keyword}`, { signal: abortController.signal })
.then(response => response.json())
.then(data => {
if (currentRequestId === requestIdRef.current) {
setResults(data);
}
})
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
return () => {
abortController.abort();
requestIdRef.current++;
};
}, [keyword]);
return <input value={keyword} onChange={(e) => setKeyword(e.target.value)} />;
};关键点:双重验证(请求 ID + 关键词)
二、TS 接口定义与类型安全
难点:复杂接口类型定义与前后端联调
解决方案:接口分层 + 工具类型
// 接口分层定义
interface UserBase {
id: string;
name: string;
}
interface UserDetail extends UserBase {
email: string;
roles: string[];
createdAt: Date;
}
// 工具类型复用
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
// API 响应包装
export type ApiResponse<T> = {
code: number;
message: string;
data: T;
timestamp: number;
};
// 使用示例
const getUser = async (id: string): Promise<ApiResponse<UserDetail>> => {
const res = await fetch(`/api/users/${id}`);
return res.json();
};关键点:严格类型校验 + 泛型封装
三、复杂状态管理
难点:跨组件状态共享与副作用隔离
解决方案:Zustand 状态管理 + 选择器优化
// store.ts
import create from 'zustand';
import { devtools } from 'zustand/middleware';
interface AppState {
user: UserDetail | null;
cart: ProductItem[];
loading: boolean;
}
export const useStore = create(devtools<AppState>((set) => ({
user: null,
cart: [],
loading: false,
fetchUser: async () => {
set({ loading: true });
try {
const user = await api.getUser();
set({ user, loading: false });
} catch (err) {
set({ loading: false });
}
}
})));
// 组件中使用
const UserProfile = () => {
const { user, loading, fetchUser } = useStore();
useEffect(() => fetchUser(), [fetchUser]);
if (loading) return <Spinner />;
return <div>{user?.name}</div>;
};关键点:状态切片 + 异步操作封装
四、组件库定制与主题配置
难点:统一设计规范与多项目复用
解决方案:基于 Arco Design 的二次开发
// 自定义主题变量
import { ConfigProvider } from 'arco-design';
import customTheme from './theme';
const App = () => (
<ConfigProvider theme={customTheme}>
<Button type="primary">定制按钮</Button>
</ConfigProvider>
);
// 主题变量定义(theme.ts)
export default {
token: {
colorPrimary: '#1890ff',
borderRadius: 4,
},
components: {
Button: {
defaultProps: {
size: 'medium',
},
},
},
};关键点:Token 化设计 + 按需加载
五、微前端架构集成
难点:多子应用通信与样式隔离
解决方案:qiankun + CSS-in-JS
// 主应用配置
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'order-app',
entry: '//localhost:7100',
container: '#subapp-container',
activeRule: '/order',
}
]);
// 子应用入口
if (window.__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
const mount = async () => {
const { container } = await window.__POWERED_BY_QIANKUN__.initGlobalState({});
renderApp(container);
};关键点:沙箱隔离 + 资源预加载
六、性能优化策略
难点:大型列表渲染与首屏加载速度
解决方案:虚拟化 + 代码分割
// 虚拟列表实现
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>Row {index}</div>
);
const VirtualList = () => (
<List
height={600}
itemCount={1000}
itemSize={50}
width={300}
>
{Row}
</List>
);
// 路由代码分割
const Home = lazy(() => import('./pages/Home'));
const routes = [
{
path: '/',
element: <Suspense fallback={<Loader />}><Home /></Suspense>
}
];关键点:React.memo + Webpack SplitChunks
七、TypeScript 高级类型
难点:复杂泛型与类型守卫
解决方案:条件类型 + 类型工具
// 泛型 API 请求封装
type RequestOptions<T = any> = {
url: string;
method: 'GET' | 'POST';
body?: T;
};
const request = async <T = any>({
url,
method = 'GET',
body
}: RequestOptions): Promise<T> => {
const res = await fetch(url, {
method,
body: body ? JSON.stringify(body) : undefined
});
return res.json();
};
// 类型守卫
const isUser = (obj: any): obj is User => {
return obj && typeof obj.id === 'string' && typeof obj.name === 'string';
};关键点:工具类型(Partial/Required) + 类型谓词
八、错误边界与监控
难点:全局错误捕获与日志上报
解决方案:Error Boundary + Sentry
// 错误边界组件
class ErrorBoundary extends Component {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
componentDidCatch(error: Error, info: ReactErrorInfo) {
Sentry.captureException(error, { extra: info });
}
render() {
return this.state.hasError ? <FallbackUI /> : this.props.children;
}
}
// 使用
const App = () => (
<ErrorBoundary>
<Router>
<Route path="/" element={<Home />} />
</Router>
</ErrorBoundary>
);关键点:错误边界层级 + 异常分类处理
九、跨平台开发
难点:React 与原生代码交互
解决方案:Lynx 框架 + 原生模块桥接
// Lynx 原生模块调用
import { NativeModules } from 'react-native';
const { CameraModule } = NativeModules;
const takePhoto = async () => {
try {
const result = await CameraModule.capture();
return result.uri;
} catch (err) {
console.error('拍照失败', err);
}
};关键点:原生组件封装 + 事件通信
十、工程化规范体系
难点:团队协作规范与自动化
解决方案:ESLint + Husky + CI/CD
# .husky/pre-commit
npx lint-staged
# .eslintrc.js
module.exports = {
extends: ['airbnb', 'plugin:@typescript-eslint/recommended'],
rules: {
'react/react-in-jsx-scope': 'off',
'@typescript-eslint/explicit-function-return-type': 'error'
}
};关键点:Git Hooks + 类型检查 + 单元测试
快速上手路线图
- 环境搭建:使用 Vite + React18 + TS 初始化项目
- 核心库集成:配置 React Router18、Zustand、React Query
- 类型体系:建立全局接口类型、工具类型库
- 组件开发:从原子组件到复合组件的分层开发
- 状态管理:按业务模块划分 Redux slice 或 Zustand store
- API 层:封装 Axios 实例 + 统一错误处理
- 部署优化:配置 Webpack 分割 + CDN 静态资源托管
通过掌握这些企业级开发模式,你将能快速理解React项目的架构设计,并高效完成功能开发。建议从简单模块入手,逐步深入复杂业务场景。
千问回答
展开查看
React 企业级项目开发的 10 大核心难点 及其解决方案(含代码示例),结合 Vue3 转 React18 的思维转换技巧,帮助你快速上手开发:
1. 请求竞态(Request Race Conditions)
问题:组件卸载或新请求触发后,旧请求的响应仍可能更新状态,导致数据错乱。
解决方案:使用 AbortController 或 useSWR 管理请求生命周期。
代码示例:
// Vue3 中常用 this.$axios,React 中需手动管理请求取消
const useFetch = (url: string) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch(url, { signal })
.then(res => res.json())
.then(setData)
.catch(err => {
if (err.name !== 'AbortError') setError(err);
});
return () => controller.abort(); // 组件卸载时取消请求
}, [url]);
return { data, error };
};2. TypeScript 接口定义混乱
问题:API 响应结构复杂,接口定义不规范导致类型推导错误。
解决方案:使用 axios 拦截器 + TypeScript 类型守卫。
代码示例:
// 定义统一响应类型
interface ApiResponse<T> {
code: number;
message: string;
data: T;
}
// axios 拦截器统一处理类型
axios.interceptors.response.use(
response => {
const data: ApiResponse<any> = response.data;
if (data.code !== 200) throw new Error(data.message);
return data.data; // 返回纯净的 data 字段
},
error => {
console.error('Request failed:', error);
throw error;
}
);
// 使用示例
type User = { id: number; name: string };
const fetchUser = async (id: number): Promise<User> => {
return axios.get(`/api/users/${id}`);
};3. 状态隔离与组件复用冲突
问题:复用组件时状态共享导致多个实例互相干扰(如购物车数量修改影响列表页)。
解决方案:将状态定义在组件内部,或通过 useContext 实现作用域隔离。
代码示例:
// 避免全局状态污染
const Counter = () => {
const [count, setCount] = useState(0);
return (
<div>
<span>{count}</span>
<button onClick={() => setCount(c => c + 1)}>+1</button>
</div>
);
};
// 复用时每个实例独立
<Counter />
<Counter />
<Counter />4. 组件重复渲染性能问题
问题:父组件更新导致子组件频繁重渲染,影响性能。
解决方案:使用 React.memo + useCallback 优化渲染。
代码示例:
const Child = React.memo(({ data, onClick }: { data: string; onClick: () => void }) => {
console.log('Child rendered');
return (
<div>
<span>{data}</span>
<button onClick={onClick}>Click</button>
</div>
);
});
const Parent = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => setCount(c => c + 1), []);
return (
<div>
<button onClick={handleClick}>Update Parent</button>
<Child data={`Count: ${count}`} onClick={handleClick} />
</div>
);
};5. 异步状态管理复杂性
问题:多个异步操作嵌套导致状态难以追踪(如加载中/成功/失败)。
解决方案:使用 RTK Query 或 useReducer 管理异步状态。
代码示例(RTK Query):
// 定义 API 切片
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
endpoints: (builder) => ({
getUser: builder.query<User, number>({
query: (id) => `users/${id}`,
}),
}),
});
// 使用 Hook
const { data, isLoading, error } = useGetUserQuery(1);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{data?.name}</div>;6. 类型安全的组件 Props 传递
问题:Vue3 中 props 是响应式的,React 中需手动定义类型。
解决方案:使用 TypeScript 接口 + React.FC 明确组件签名。
代码示例:
interface UserCardProps {
user: { id: number; name: string };
onEdit: (id: number) => void;
}
const UserCard: React.FC<UserCardProps> = ({ user, onEdit }) => {
return (
<div>
<h3>{user.name}</h3>
<button onClick={() => onEdit(user.id)}>Edit</button>
</div>
);
};7. 错误边界与全局异常处理
问题:组件崩溃导致整个页面不可用。
解决方案:使用 React.ErrorBoundary 捕获异常。
代码示例:
class ErrorBoundary extends React.Component<{ children: React.ReactNode }> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) {
return <div>Something went wrong.</div>;
}
return this.props.children;
}
}
// 包裹组件
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>8. 大型项目中的代码结构混乱
问题:文件结构杂乱,难以快速定位代码。
解决方案:采用 原子设计模式(Atoms → Molecules → Organisms)。
目录示例:
src/
├── components/ # 公共组件(Atoms)
├── features/ # 功能模块(Organisms)
├── services/ # API 服务
├── store/ # Redux 状态管理
├── types/ # 全局 TypeScript 接口9. 构建配置与性能优化
问题:Webpack/Vite 配置复杂,首次加载慢。
解决方案:使用 React.lazy + Suspense 实现懒加载。
代码示例:
const LazyComponent = React.lazy(() => import('./HeavyComponent'));
const App = () => (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</React.Suspense>
);10. 依赖版本冲突
问题:多版本 React/Redux 导致构建失败。
解决方案:强制统一依赖版本(通过 overrides 字段)。
package.json 示例:
{
"overrides": {
"react": "18.2.0",
"react-dom": "18.2.0"
}
}Vue3 → React18 快速上手技巧
思维转换:
- Vue 的
data→ React 的useState - Vue 的
$emit→ React 的props.callback - Vue 的
mixins→ React 的Custom Hooks
- Vue 的
工具链适配:
- 使用
Vite替代 Vue CLI,配置更灵活。 - 使用
ESLint + Prettier保持代码风格统一。
- 使用
学习路径:
- 先掌握
useEffect替代 Vue 的mounted/lifecycle。 - 学习
useContext替代 Vue 的provide/inject。
- 先掌握
豆包回答
展开查看
以下是React18+TS企业级项目中最常见的12个核心难点,每个难点都包含问题背景(对比Vue3差异)、解决方式和可直接复用的代码示例,确保你能快速理解并上手:
1. React请求竞态问题
问题描述
Vue3中也可能遇到,但React因组件卸载/重渲染更易出现:快速切换页面/触发多次请求时,后发请求先返回,覆盖先发请求的结果,导致数据错乱(比如切换列表页,第二页请求先返回,覆盖第一页数据)。 Vue3通常用AbortController + watch取消请求,React需结合useEffect清理函数处理。
解决方式
- 用
AbortController中断过期请求(推荐); - 用状态标记过滤过期响应;
- 封装通用请求Hook统一处理竞态。
代码示例(封装通用防竞态请求Hook)
import { useState, useEffect, useCallback } from 'react';
// 通用请求Hook,解决竞态问题
function useRequest<T>(fetchFn: (signal: AbortSignal) => Promise<T>, deps: any[] = []) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
// 创建AbortController实例
const controller = new AbortController();
const { signal } = controller;
try {
const result = await fetchFn(signal);
// 检查请求是否未被中断,再更新状态
if (!signal.aborted) {
setData(result);
}
} catch (err) {
if (!signal.aborted) { // 排除主动中断的错误
setError(err as Error);
}
} finally {
if (!signal.aborted) {
setLoading(false);
}
}
// 返回清理函数,组件卸载/依赖变化时中断请求
return () => controller.abort();
}, deps);
useEffect(() => {
const abort = fetchData();
// 组件卸载/依赖变化时执行清理
return () => abort();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
// 用法示例:请求列表数据
function ListPage({ id }: { id: number }) {
const { data, loading, error } = useRequest(
async (signal) => {
const res = await fetch(`/api/list/${id}`, { signal }); // 传入signal
return res.json();
},
[id] // 依赖id,变化时中断旧请求
);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误:{error.message}</div>;
return <div>{JSON.stringify(data)}</div>;
}2. TypeScript接口/类型定义不规范
问题描述
Vue3的TS类型侧重props/emits,React需覆盖组件Props、API响应、事件处理、状态等全场景;React项目要求类型分层(基础类型→API类型→组件类型),新手易出现any泛滥、类型嵌套过深问题。
解决方式
- 分层定义类型(基础类型→API类型→组件类型);
- 用泛型复用类型,禁止
any(配合ESLint); - 利用TS工具类型(
Partial/Pick/Required)简化定义。
代码示例(React风格TS类型定义)
// 1. 基础类型(通用枚举/基础接口)
export enum OrderStatus {
PENDING = 'pending',
PAID = 'paid',
DELIVERED = 'delivered',
}
// 2. API请求/响应类型(与后端对齐)
export interface GetOrderListRequest {
page: number;
size: number;
status?: OrderStatus;
}
export interface OrderItem {
id: string;
title: string;
price: number;
status: OrderStatus;
createTime: string;
}
export interface GetOrderListResponse {
list: OrderItem[];
total: number;
page: number;
}
// 3. 组件Props类型(结合工具类型)
export interface OrderListProps {
initialPage: number;
defaultStatus?: OrderStatus;
onStatusChange: (status: OrderStatus) => void;
}
// 工具类型复用示例:仅取OrderItem的部分字段
type OrderSimpleInfo = Pick<OrderItem, 'id' | 'title' | 'price'>;
// 组件使用示例(无any)
import { FC } from 'react';
export const OrderList: FC<OrderListProps> = ({
initialPage = 1,
defaultStatus = OrderStatus.PENDING,
onStatusChange,
}) => {
// 逻辑实现
return <div>订单列表</div>;
};3. Hooks依赖陷阱(useEffect/useCallback/useMemo)
问题描述
Vue3的watch自动收集依赖、计算属性无显式依赖;React的useEffect/useCallback/useMemo需手动声明依赖,漏写/错写会导致:
- 闭包陷阱(获取不到最新state);
- 无限重渲染;
- 依赖冗余导致不必要的执行。
解决方式
- 开启
eslint-plugin-react-hooks强制检查依赖; - 用
useRef保存最新值解决闭包问题; - 空依赖仅用于“组件初始化执行一次”场景。
代码示例
import { useState, useEffect, useRef, useCallback } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const latestCount = useRef(count); // 保存最新值,突破闭包
// 1. 解决闭包问题:获取最新count
useEffect(() => {
latestCount.current = count; // 同步最新值
const timer = setInterval(() => {
// 不用依赖count,直接取ref的值
console.log('最新count:', latestCount.current);
}, 1000);
return () => clearInterval(timer);
}, []); // 空依赖,仅初始化
// 2. useCallback缓存函数,避免子组件重渲染
const increment = useCallback(() => {
setCount(prev => prev + 1); // 函数式更新,无需依赖count
}, []); // 无依赖,始终返回同一个函数
return (
<div>
<p>{count}</p>
<button onClick={increment}>+1</button>
</div>
);
}4. 状态管理(Pinia vs Redux Toolkit/Zustand)
问题描述
Vue3常用Pinia(轻量、直观),React项目主流用Redux Toolkit (RTK)(中大型项目)或Zustand(轻量项目);新手难理解RTK的slice/thunk/selector逻辑,易出现状态冗余。
解决方式
- 小型项目:Zustand(学习成本低,接近Pinia);
- 中大型项目:RTK(React主流,内置immer、异步处理);
- 封装通用Hook简化状态调用。
代码示例
方式1:Zustand(轻量,推荐新手先学)
// store/order.ts
import { create } from 'zustand';
interface OrderStore {
list: OrderItem[];
total: number;
loading: boolean;
fetchOrderList: (params: GetOrderListRequest) => Promise<void>;
clearList: () => void;
}
export const useOrderStore = create<OrderStore>((set) => ({
list: [],
total: 0,
loading: false,
// 异步操作
fetchOrderList: async (params) => {
set({ loading: true });
try {
const res = await fetch('/api/order/list', {
method: 'POST',
body: JSON.stringify(params),
});
const data = await res.json();
set({ list: data.list, total: data.total });
} finally {
set({ loading: false });
}
},
// 同步操作
clearList: () => set({ list: [], total: 0 }),
}));
// 组件使用(类似Pinia)
function OrderPage() {
const { list, loading, fetchOrderList } = useOrderStore();
useEffect(() => {
fetchOrderList({ page: 1, size: 10 });
}, [fetchOrderList]);
return <div>{loading ? '加载中' : list.length}</div>;
}方式2:Redux Toolkit(React企业级主流)
// store/slices/orderSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import type { RootState } from '../store';
// 异步Action
export const fetchOrderList = createAsyncThunk(
'order/fetchList',
async (params: GetOrderListRequest) => {
const res = await fetch('/api/order/list', {
method: 'POST',
body: JSON.stringify(params),
});
return res.json() as Promise<GetOrderListResponse>;
}
);
const orderSlice = createSlice({
name: 'order',
initialState: {
list: [] as OrderItem[],
total: 0,
loading: false,
error: null as string | null,
},
reducers: {
clearList: (state) => {
state.list = []; // immer自动处理不可变
state.total = 0;
},
},
extraReducers: (builder) => {
builder
.addCase(fetchOrderList.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchOrderList.fulfilled, (state, action) => {
state.loading = false;
state.list = action.payload.list;
state.total = action.payload.total;
})
.addCase(fetchOrderList.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message || '请求失败';
});
},
});
export const { clearList } = orderSlice.actions;
// 选择器
export const selectOrderList = (state: RootState) => state.order.list;
export const selectOrderLoading = (state: RootState) => state.order.loading;
export default orderSlice.reducer;
// store/store.ts
import { configureStore } from '@reduxjs/toolkit';
import orderReducer from './slices/orderSlice';
export const store = configureStore({
reducer: {
order: orderReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// 组件使用
import { useDispatch, useSelector } from 'react-redux';
import { fetchOrderList, selectOrderList, selectOrderLoading } from './store/slices/orderSlice';
function OrderPage() {
const dispatch = useDispatch();
const list = useSelector(selectOrderList);
const loading = useSelector(selectOrderLoading);
useEffect(() => {
dispatch(fetchOrderList({ page: 1, size: 10 }));
}, [dispatch]);
return <div>{loading ? '加载中' : list.length}</div>;
}5. 组件复用(Vue组合式函数 vs React自定义Hook)
问题描述
Vue3用mixins/组合式函数复用逻辑,React无mixins,核心是自定义Hook(必须以use开头);新手易将逻辑写死在组件中,或Hook设计不规范。
解决方式
- 自定义Hook遵循“单一职责”;
- 命名以
use开头(React规范); - 抽离通用逻辑(表单、请求、权限)为Hook。
代码示例(封装通用权限Hook)
// hooks/useAuth.ts
import { useState, useEffect } from 'react';
// 通用权限Hook:判断用户是否有指定权限
function useAuth(permission: string) {
const [hasPermission, setHasPermission] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 模拟从接口获取用户权限列表
const fetchAuth = async () => {
const res = await fetch('/api/user/permissions');
const permissions = await res.json(); // ['order:view', 'order:edit']
setHasPermission(permissions.includes(permission));
setLoading(false);
};
fetchAuth();
}, [permission]);
return { hasPermission, loading };
}
// 组件复用示例
function OrderEditButton() {
// 复用权限Hook
const { hasPermission, loading } = useAuth('order:edit');
if (loading) return <div>加载中...</div>;
// 无权限则隐藏按钮
if (!hasPermission) return null;
return <button>编辑订单</button>;
}
function OrderDeleteButton() {
// 复用同一个Hook
const { hasPermission, loading } = useAuth('order:delete');
if (loading) return <div>加载中...</div>;
if (!hasPermission) return null;
return <button>删除订单</button>;
}6. 复杂表单处理(v-model vs 受控组件)
问题描述
Vue3的v-model双向绑定简洁,React只有单向数据流,复杂表单(多字段、联动、校验)需手动维护受控组件状态,易出现代码冗余。
解决方式
- 用
react-hook-form(React主流)简化表单处理; - 封装通用表单Hook,减少重复代码;
- 简单场景可用非受控组件(
ref)。
代码示例(react-hook-form实现复杂表单)
import { useForm } from 'react-hook-form';
import { OrderStatus } from './types';
// 表单类型定义
interface OrderFormValues {
title: string;
price: number;
status: OrderStatus;
remark?: string;
}
function OrderForm() {
// 初始化react-hook-form
const {
register, // 注册表单字段
handleSubmit, // 处理提交
formState: { errors, isSubmitting }, // 错误/提交状态
watch, // 监听字段变化(实现联动)
reset, // 重置表单
} = useForm<OrderFormValues>({
defaultValues: {
title: '',
price: 0,
status: OrderStatus.PENDING,
},
});
// 监听status变化(实现联动)
const currentStatus = watch('status');
// 提交处理
const onSubmit = async (data: OrderFormValues) => {
console.log('表单数据:', data);
// 模拟提交接口
await new Promise(resolve => setTimeout(resolve, 1000));
alert('提交成功');
reset(); // 重置表单
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
{/* 标题 */}
<div>
<label>标题:</label>
<input
{...register('title', {
required: '标题不能为空',
minLength: { value: 3, message: '标题至少3个字符' },
})}
/>
{errors.title && <span style={{ color: 'red' }}>{errors.title.message}</span>}
</div>
{/* 价格 */}
<div>
<label>价格:</label>
<input
type="number"
{...register('price', {
required: '价格不能为空',
min: { value: 0, message: '价格不能为负数' },
})}
/>
{errors.price && <span style={{ color: 'red' }}>{errors.price.message}</span>}
</div>
{/* 状态 */}
<div>
<label>状态:</label>
<select {...register('status')}>
<option value={OrderStatus.PENDING}>待支付</option>
<option value={OrderStatus.PAID}>已支付</option>
<option value={OrderStatus.DELIVERED}>已发货</option>
</select>
</div>
{/* 联动字段:仅状态为已支付时显示备注 */}
{currentStatus === OrderStatus.PAID && (
<div>
<label>备注:</label>
<textarea {...register('remark')} />
</div>
)}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '提交中...' : '提交'}
</button>
</form>
);
}7. React Router 6路由管理
问题描述
Vue Router和React Router 6语法差异大:React Router 6废除Switch/useHistory,新增createBrowserRouter/useNavigate/Outlet;React项目常用路由懒加载、权限路由、嵌套路由,新手易混淆配置方式。
解决方式
- 集中式配置路由(类似Vue Router);
- 封装权限路由组件;
- 用
lazy/Suspense实现路由懒加载。
代码示例(React Router 6企业级配置)
// router/index.tsx
import { createBrowserRouter, RouterProvider, Outlet, Navigate } from 'react-router-dom';
import { lazy, Suspense } from 'react';
import { useAuth } from '../hooks/useAuth';
// 1. 懒加载组件(优化首屏)
const Home = lazy(() => import('../pages/Home'));
const OrderList = lazy(() => import('../pages/OrderList'));
const OrderDetail = lazy(() => import('../pages/OrderDetail'));
const Login = lazy(() => import('../pages/Login'));
const NotFound = lazy(() => import('../pages/NotFound'));
// 2. 布局组件(嵌套路由)
const Layout = () => {
return (
<div>
<header>导航栏</header>
<main>
{/* 嵌套路由出口(类似Vue的<router-view>) */}
<Outlet />
</main>
<footer>页脚</footer>
</div>
);
};
// 3. 权限路由组件(校验登录/权限)
const PrivateRoute = ({ children }: { children: React.ReactNode }) => {
const { hasPermission, loading } = useAuth('user:login');
if (loading) return <div>加载中...</div>;
// 未登录跳转到登录页
if (!hasPermission) return <Navigate to="/login" replace />;
return children;
};
// 4. 路由配置(集中式,类似Vue Router)
const router = createBrowserRouter([
{
path: '/',
element: (
<PrivateRoute>
<Layout />
</PrivateRoute>
),
children: [
{ path: '', element: <Navigate to="/home" replace /> }, // 重定向
{ path: 'home', element: <Home /> },
{ path: 'order', element: <OrderList /> },
{ path: 'order/:id', element: <OrderDetail /> }, // 动态路由
],
},
{ path: '/login', element: <Login /> },
{ path: '*', element: <NotFound /> }, // 404
]);
// 5. 根组件挂载路由
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<RouterProvider router={router} />
</Suspense>
);
}
// 组件内使用路由示例(OrderList跳转到OrderDetail)
import { useNavigate, useParams } from 'react-router-dom';
function OrderList() {
const navigate = useNavigate(); // 替代useHistory
return <button onClick={() => navigate(`/order/123`)}>查看订单123</button>;
}
function OrderDetail() {
const { id } = useParams<{ id: string }>(); // 获取动态路由参数
return <div>订单ID:{id}</div>;
}8. React重渲染性能优化
问题描述
Vue3的响应式系统仅更新依赖组件,React默认父组件重渲染会导致所有子组件重渲染;React项目中易因重渲染导致卡顿,新手不知如何定位和优化。
解决方式
React.memo:浅比较props,避免子组件无意义重渲染;useCallback:缓存函数,避免props引用变化;useMemo:缓存计算结果,避免重复计算;- 拆分组件,缩小重渲染范围。
代码示例
import { useState, memo, useCallback, useMemo } from 'react';
// 子组件:用React.memo包裹,仅props变化时重渲染
const OrderItem = memo(({ item, onEdit }: { item: OrderItem; onEdit: (id: string) => void }) => {
console.log('OrderItem重渲染:', item.id); // 仅props变化时打印
return (
<div>
<span>{item.title}</span>
<button onClick={() => onEdit(item.id)}>编辑</button>
</div>
);
});
function OrderList() {
const [list, setList] = useState<OrderItem[]>([]);
const [count, setCount] = useState(0);
// 1. useCallback缓存函数:避免每次重渲染创建新函数,导致子组件重渲染
const handleEdit = useCallback((id: string) => {
console.log('编辑订单:', id);
}, []);
// 2. useMemo缓存计算结果:避免每次重渲染重复计算
const totalPrice = useMemo(() => {
console.log('计算总价');
return list.reduce((sum, item) => sum + item.price, 0);
}, [list]); // 仅list变化时重新计算
// 模拟加载数据
useState(() => {
setList([
{ id: '1', title: '订单1', price: 100, status: OrderStatus.PENDING, createTime: '' },
{ id: '2', title: '订单2', price: 200, status: OrderStatus.PAID, createTime: '' },
]);
}, []);
return (
<div>
<p>计数器:{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button> {/* 点击仅更新count,不影响子组件 */}
<p>总价:{totalPrice}</p>
{list.map(item => (
<OrderItem key={item.id} item={item} onEdit={handleEdit} />
))}
</div>
);
}9. 样式隔离(Vue scoped vs React CSS Modules)
问题描述
Vue3用scoped实现样式隔离,React无内置scoped;React项目常用CSS Modules(简单)或Styled Components(动态样式),新手易出现样式污染。
解决方式
- 简单场景:CSS Modules(文件名
.module.css); - 动态样式:Styled Components;
- 禁止全局样式泛滥(仅用于通用样式)。
代码示例
方式1:CSS Modules(推荐)
/* OrderList.module.css */
.container {
padding: 20px;
}
.item {
margin: 10px 0;
padding: 10px;
border: 1px solid #eee;
}
.active {
border-color: #1890ff;
}import styles from './OrderList.module.css'; // 导入样式
function OrderList() {
return (
<div className={styles.container}>
<div className={`${styles.item} ${styles.active}`}>
订单1(样式隔离,不会污染其他组件)
</div>
</div>
);
}方式2:Styled Components(动态样式)
import styled from 'styled-components';
// 定义样式组件
const Container = styled.div`
padding: 20px;
`;
// 动态样式:接收props
const OrderItem = styled.div<{ active: boolean }>`
margin: 10px 0;
padding: 10px;
border: 1px solid ${props => props.active ? '#1890ff' : '#eee'};
color: ${props => props.active ? 'red' : 'black'};
`;
function OrderList() {
return (
<Container>
<OrderItem active={true}>订单1(激活状态)</OrderItem>
<OrderItem active={false}>订单2(普通状态)</OrderItem>
</Container>
);
}10. 错误边界处理
问题描述
Vue3用errorCaptured钩子捕获组件错误,React需用ErrorBoundary组件(类组件/自定义Hook)捕获子组件错误,防止单个组件崩溃导致整个应用挂掉;新手易忽略,导致线上问题。
解决方式
- 封装通用ErrorBoundary组件;
- 在路由/关键组件外层包裹;
- 结合Sentry上报错误。
代码示例
import { Component, ErrorInfo, ReactNode } from 'react';
// 通用错误边界组件(类组件,React要求)
interface ErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode; // 错误时显示的UI
}
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
}
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
// 捕获子组件错误
static getDerivedStateFromError(error: Error) {
return { hasError: true, error };
}
// 上报错误(对接Sentry)
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('组件错误:', error, errorInfo);
// Sentry.captureException(error, { extra: errorInfo });
}
// 重置错误状态
resetError = () => {
this.setState({ hasError: false, error: undefined });
};
render() {
if (this.state.hasError) {
// 自定义错误UI
return this.props.fallback || (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h2>抱歉,组件出错了</h2>
<p>{this.state.error?.message}</p>
<button onClick={this.resetError}>刷新</button>
</div>
);
}
return this.props.children;
}
}
// 用法示例:包裹易出错的组件
function App() {
return (
<ErrorBoundary>
<OrderList /> {/* 若OrderList崩溃,仅显示错误UI,不影响整个应用 */}
</ErrorBoundary>
);
}
// 路由级别的错误边界
function RouterErrorBoundary() {
return (
<ErrorBoundary fallback={<div>页面加载出错,请刷新重试</div>}>
<Outlet />
</ErrorBoundary>
);
}11. 事件处理与this指向
问题描述
Vue3的事件处理自动绑定this,React类组件中this易丢失(需bind或箭头函数);函数组件无this,但合成事件(如e.preventDefault())与Vue3差异大。
解决方式
- 函数组件:直接用箭头函数处理事件;
- 类组件:用类字段语法(箭头函数)绑定
this; - 理解React合成事件(与原生事件的区别)。
代码示例
// 1. 函数组件事件处理(推荐)
function ButtonDemo() {
// 普通事件
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault(); // 合成事件阻止默认行为
console.log('按钮点击', e.target);
};
// 带参数的事件
const handleItemClick = (id: string, e: React.MouseEvent<HTMLLIElement>) => {
console.log('点击项ID:', id, e);
};
return (
<div>
<button onClick={handleClick}>普通按钮</button>
<ul>
<li onClick={(e) => handleItemClick('123', e)}>项123</li>
</ul>
</div>
);
}
// 2. 类组件事件处理(避免this丢失)
class ClassButtonDemo extends React.Component {
// 类字段语法:箭头函数自动绑定this
handleClick = () => {
console.log('this:', this); // 正确指向组件实例
};
render() {
return <button onClick={this.handleClick}>类组件按钮</button>;
}
}12. React18 Suspense与数据预加载
问题描述
Vue3的Suspense仅用于组件加载,React18的Suspense可结合React Query/useSWR实现数据预加载、懒加载;React项目常用此优化用户体验,新手易误用导致渲染异常。
解决方式
- 结合
React Query实现Suspense数据预加载; - 用
Suspense包裹懒加载组件; - 理解React18并发渲染机制。
代码示例
import { Suspense } from 'react';
import { QueryClient, QueryClientProvider, useQuery } from '@tanstack/react-query';
// 创建QueryClient实例
const queryClient = new QueryClient({
defaultOptions: {
queries: {
suspense: true, // 开启Suspense支持
},
},
});
// 数据请求Hook(结合Suspense)
function useOrderData(id: string) {
return useQuery({
queryKey: ['order', id], // 缓存key
queryFn: async () => {
const res = await fetch(`/api/order/${id}`);
return res.json();
},
});
}
// 订单详情组件(依赖数据预加载)
function OrderDetail({ id }: { id: string }) {
const { data } = useOrderData(id);
// 数据加载完成前,Suspense会显示fallback
return <div>订单详情:{JSON.stringify(data)}</div>;
}
// 父组件:用Suspense包裹,实现数据预加载
function OrderPage({ id }: { id: string }) {
return (
<Suspense fallback={<div>加载订单数据...</div>}>
<OrderDetail id={id} />
</Suspense>
);
}
// 根组件:包裹QueryClientProvider
function App() {
return (
<QueryClientProvider client={queryClient}>
<OrderPage id="123" />
</QueryClientProvider>
);
}总结
从Vue3迁移到React18并上手React企业级项目,核心要掌握以下关键点:
- 核心差异:React是单向数据流、需手动处理Hooks依赖,Vue3是双向绑定、自动收集依赖;
- 高频难点:请求竞态(AbortController)、TS类型分层、自定义Hook复用、React Router 6配置、重渲染优化(React.memo/useCallback);
- 工具选型:状态管理用Zustand(轻量)/RTK(企业级)、表单用react-hook-form、样式用CSS Modules/Styled Components。
以上代码均可直接复用,掌握这些难点后,你能快速理解React项目的代码结构,并独立开发功能需求。