1. React 组件化开发实战心得
在AiTrader项目中,组件化开发是前端架构的核心。经过多次实践,我发现合理的组件拆分能显著提升代码可维护性。以头像组件为例,最初项目中至少有5处重复的头像显示逻辑,通过提取为独立组件后,不仅减少了代码量,更实现了UI一致性。
1.1 组件接口设计技巧
在定义Avatar组件时,我采用了TypeScript接口来规范props:
typescript复制interface AvatarProps {
size?: 'small' | 'medium' | 'large';
src: string;
alt?: string;
className?: string;
onClick?: () => void;
}
这里有几个设计要点:
- 必选属性只有src,其他都是可选
- 使用联合类型限制size的取值范围
- 预留className方便外部覆盖样式
- 事件处理使用可选回调
经验:组件props设计要遵循"最小接口原则",只暴露必要的属性。内部状态尽量用useState管理,避免props臃肿。
1.2 条件渲染的工程实践
BottomNav组件的显示逻辑值得深入探讨。我们不仅需要根据路由路径判断,还要考虑移动端横屏情况:
typescript复制const { pathname } = useLocation();
const [isLandscape, setIsLandscape] = useState(false);
useEffect(() => {
const handler = () => {
setIsLandscape(window.matchMedia('(orientation: landscape)').matches);
};
window.addEventListener('resize', handler);
return () => window.removeEventListener('resize', handler);
}, []);
if (pathname.startsWith('/login') || isLandscape) {
return null;
}
这种复合条件判断在实际项目中很常见,建议:
- 将条件判断逻辑提取到自定义hook中
- 使用startsWith而非精确匹配,提高路由判断的灵活性
- 响应式设计要考虑各种设备状态
2. 状态管理深度解析
2.1 Context API的进阶用法
MomentsContext的设计经历了三次迭代:
- 初始版本:简单value传递
- 优化版:加入useReducer管理复杂状态
- 生产版:集成持久化功能
最终版的关键实现:
typescript复制const MomentsContext = createContext<{
state: State;
dispatch: Dispatch<Action>;
} | null>(null);
function MomentsProvider({ children }: { children: ReactNode }) {
const [state, dispatch] = useReducer(reducer, initialState, () => {
const saved = localStorage.getItem('moments');
return saved ? JSON.parse(saved) : initialState;
});
useEffect(() => {
localStorage.setItem('moments', JSON.stringify(state));
}, [state]);
return (
<MomentsContext.Provider value={{ state, dispatch }}>
{children}
</MomentsContext.Provider>
);
}
2.2 Fast Refresh的陷阱与解决方案
开发时遇到的热更新问题很典型:当修改context值时,FastRefresh会导致状态丢失。解决方案是:
- 在根组件外声明初始状态
- 使用useRef保持引用稳定
- 添加eslint禁用规则要谨慎:
typescript复制// 正确的禁用方式(限定范围)
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
// 依赖项不完整的effect
}, []);
// 错误的做法(全局禁用)
/* eslint-disable react-hooks/exhaustive-deps */
3. 路由安全体系构建
3.1 路由守卫的实现细节
ProtectedRoute组件需要考虑多种场景:
typescript复制function ProtectedRoute({ children }: { children: JSX.Element }) {
const { user } = useAuth();
const location = useLocation();
const navigate = useNavigate();
if (!user) {
// 保留原路径以便登录后跳转
return (
<LoginPrompt
onSuccess={() => navigate(location.pathname, { replace: true })}
/>
);
}
return children;
}
关键点:
- 使用replace而非push避免历史记录污染
- 通过state传递原始路径
- 提供友好的登录提示而非直接跳转
3.2 动态路由的最佳实践
对于AiTrader这种需要动态加载路由的场景,推荐使用懒加载+错误边界:
typescript复制const TradePage = lazy(() => import('./pages/Trade'));
const PortfolioPage = lazy(() => import('./pages/Portfolio'));
function Router() {
return (
<ErrorBoundary fallback={<ErrorPage />}>
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/trade" element={<ProtectedRoute><TradePage /></ProtectedRoute>} />
<Route path="/portfolio" element={<ProtectedRoute><PortfolioPage /></ProtectedRoute>} />
</Routes>
</Suspense>
</ErrorBoundary>
);
}
4. CSS工程化方案
4.1 CSS Modules的实战技巧
在大型项目中,样式冲突是常见问题。我们的解决方案:
- 命名约定:
[组件名]-[元素]__[修饰符] - 使用composes组合样式:
css复制.button { composes: base from './shared.css'; /* 其他样式 */ } - 变量管理:通过PostCSS处理全局变量
4.2 交互细节优化实录
移除focus边框的正确方式:
css复制/* 错误做法:完全移除焦点样式 */
*:focus {
outline: none;
}
/* 正确做法:提供替代焦点样式 */
button:focus-visible {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
悬浮状态优化经验:
- 使用transform而非margin实现点击反馈
- 添加过渡效果提升流畅度
- 考虑触摸设备的不同交互方式
5. 调试与工程规范
5.1 ESLint配置技巧
针对React项目的推荐规则:
json复制{
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"import/order": ["error", {
"groups": ["builtin", "external", "internal"],
"pathGroups": [
{
"pattern": "react",
"group": "external",
"position": "before"
}
],
"newlines-between": "always"
}]
}
}
5.2 常见错误排查指南
-
JSX闭合标签错误:
- 使用Prettier自动格式化
- VSCode安装React插件获得实时提示
-
变量作用域问题:
typescript复制// 错误示例 for (var i = 0; i < 10; i++) { setTimeout(() => console.log(i), 100); } // 修复方案 for (let i = 0; i < 10; i++) { setTimeout(() => console.log(i), 100); } -
异步数据竞争:
- 使用AbortController取消过时请求
- 添加请求标识符匹配响应
在AiTrader项目开发过程中,最大的收获是理解了工程化思维的重要性。每个技术决策都要考虑可维护性、性能优化和团队协作。比如在组件设计时,我会预留足够的扩展点;在状态管理时,会考虑数据流的清晰度;在样式编写时,会建立可复用的设计系统。这些经验远比单纯掌握某个API更有价值。