Skip to content

vue3转react会遇到的难点问题

元宝回答

展开查看

企业级 React 项目中常见的 10 大开发难点及解决方案:

一、请求竞态问题(Race Condition)

难点:高频请求(如搜索联想)导致旧数据覆盖新数据
解决方案:请求 ID 追踪 + AbortController

tsx
// 使用 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 接口定义与类型安全

难点:复杂接口类型定义与前后端联调
解决方案:接口分层 + 工具类型

typescript
// 接口分层定义
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 状态管理 + 选择器优化

tsx
// 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 的二次开发

tsx
// 自定义主题变量
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

tsx
// 主应用配置
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);
};

关键点:沙箱隔离 + 资源预加载


六、性能优化策略

难点:大型列表渲染与首屏加载速度
解决方案:虚拟化 + 代码分割

tsx
// 虚拟列表实现
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 高级类型

难点:复杂泛型与类型守卫
解决方案:条件类型 + 类型工具

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

tsx
// 错误边界组件
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 框架 + 原生模块桥接

tsx
// 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

bash
# .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 + 类型检查 + 单元测试


快速上手路线图

  1. 环境搭建:使用 Vite + React18 + TS 初始化项目
  2. 核心库集成:配置 React Router18、Zustand、React Query
  3. 类型体系:建立全局接口类型、工具类型库
  4. 组件开发:从原子组件到复合组件的分层开发
  5. 状态管理:按业务模块划分 Redux slice 或 Zustand store
  6. API 层:封装 Axios 实例 + 统一错误处理
  7. 部署优化:配置 Webpack 分割 + CDN 静态资源托管

通过掌握这些企业级开发模式,你将能快速理解React项目的架构设计,并高效完成功能开发。建议从简单模块入手,逐步深入复杂业务场景。

千问回答

展开查看

React 企业级项目开发的 10 大核心难点 及其解决方案(含代码示例),结合 Vue3 转 React18 的思维转换技巧,帮助你快速上手开发:


1. 请求竞态(Request Race Conditions)

问题:组件卸载或新请求触发后,旧请求的响应仍可能更新状态,导致数据错乱。
解决方案:使用 AbortControlleruseSWR 管理请求生命周期。
代码示例

tsx
// 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 类型守卫。
代码示例

ts
// 定义统一响应类型
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 实现作用域隔离。
代码示例

tsx
// 避免全局状态污染
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 优化渲染。
代码示例

tsx
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 QueryuseReducer 管理异步状态。
代码示例(RTK Query):

ts
// 定义 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 明确组件签名。
代码示例

tsx
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 捕获异常。
代码示例

tsx
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 实现懒加载。
代码示例

tsx
const LazyComponent = React.lazy(() => import('./HeavyComponent'));

const App = () => (
  <React.Suspense fallback={<div>Loading...</div>}>
    <LazyComponent />
  </React.Suspense>
);

10. 依赖版本冲突

问题:多版本 React/Redux 导致构建失败。
解决方案:强制统一依赖版本(通过 overrides 字段)。
package.json 示例

json
{
  "overrides": {
    "react": "18.2.0",
    "react-dom": "18.2.0"
  }
}

Vue3 → React18 快速上手技巧

  1. 思维转换

    • Vue 的 data → React 的 useState
    • Vue 的 $emit → React 的 props.callback
    • Vue 的 mixins → React 的 Custom Hooks
  2. 工具链适配

    • 使用 Vite 替代 Vue CLI,配置更灵活。
    • 使用 ESLint + Prettier 保持代码风格统一。
  3. 学习路径

    • 先掌握 useEffect 替代 Vue 的 mounted/lifecycle
    • 学习 useContext 替代 Vue 的 provide/inject

豆包回答

展开查看

以下是React18+TS企业级项目中最常见的12个核心难点,每个难点都包含问题背景(对比Vue3差异)解决方式可直接复用的代码示例,确保你能快速理解并上手:


1. React请求竞态问题

问题描述

Vue3中也可能遇到,但React因组件卸载/重渲染更易出现:快速切换页面/触发多次请求时,后发请求先返回,覆盖先发请求的结果,导致数据错乱(比如切换列表页,第二页请求先返回,覆盖第一页数据)。 Vue3通常用AbortController + watch取消请求,React需结合useEffect清理函数处理。

解决方式

  • AbortController中断过期请求(推荐);
  • 用状态标记过滤过期响应;
  • 封装通用请求Hook统一处理竞态。

代码示例(封装通用防竞态请求Hook)

tsx
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类型定义)

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保存最新值解决闭包问题;
  • 空依赖仅用于“组件初始化执行一次”场景。

代码示例

tsx
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(轻量,推荐新手先学)

tsx
// 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企业级主流)

ts
// 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)

tsx
// 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实现复杂表单)

tsx
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企业级配置)

tsx
// 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:缓存计算结果,避免重复计算;
  • 拆分组件,缩小重渲染范围。

代码示例

tsx
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(推荐)

css
/* OrderList.module.css */
.container {
  padding: 20px;
}

.item {
  margin: 10px 0;
  padding: 10px;
  border: 1px solid #eee;
}

.active {
  border-color: #1890ff;
}
tsx
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(动态样式)

tsx
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上报错误。

代码示例

tsx
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合成事件(与原生事件的区别)。

代码示例

tsx
// 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并发渲染机制。

代码示例

tsx
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企业级项目,核心要掌握以下关键点:

  1. 核心差异:React是单向数据流、需手动处理Hooks依赖,Vue3是双向绑定、自动收集依赖;
  2. 高频难点:请求竞态(AbortController)、TS类型分层、自定义Hook复用、React Router 6配置、重渲染优化(React.memo/useCallback);
  3. 工具选型:状态管理用Zustand(轻量)/RTK(企业级)、表单用react-hook-form、样式用CSS Modules/Styled Components。

以上代码均可直接复用,掌握这些难点后,你能快速理解React项目的代码结构,并独立开发功能需求。