React核心知识与项目设计摘要
React面试题要点
核心概念
- React基础: 虚拟DOM、组件化、单向数据流、JSX语法
- 组件类型: 函数组件(推荐)与类组件的区别与应用场景
- 生命周期: 挂载、更新、卸载三大阶段及各阶段方法的应用
Hooks相关
- 基础Hooks: useState管理状态、useEffect处理副作用
- 性能优化: useCallback缓存函数、useMemo缓存值、自定义Hook复用逻辑
- 状态管理: useReducer处理复杂状态、useContext跨层级传递数据
性能优化
- 减少渲染: React.memo、shouldComponentUpdate、列表渲染key优化
- 代码分割: React.lazy与Suspense实现按需加载
- 数据获取: SWR/React Query管理服务端状态
架构设计
- 状态管理: Context/Redux/Zustand等方案的选择与应用
- 路由设计: React Router实现SPA导航与路由守卫
- 组件设计模式: HOC、Compound Components等高级组件模式
React新特性
- Concurrent Mode: 并发渲染、自动批处理
- Server Components: 服务端组件减少客户端负担
- Suspense: 声明式数据加载与错误边界处理
项目设计方案
规模化策略
- 小型项目: Context+函数组件、轻量级技术栈、简单目录结构
- 中型项目: 特性驱动开发、Zustand/Jotai状态管理、组件分层
- 大型项目: 微前端架构、Monorepo管理、DDD领域驱动设计
工程化实践
- 代码质量: TypeScript类型检查、ESLint规范、Jest自动化测试
- 项目结构: 按功能/页面组织代码、原子设计组件拆分
- 持续集成: Husky提交检查、CI/CD自动部署
性能与体验
- 首屏优化: SSR/SSG提升加载体验、代码分割降低包体积
- 用户体验: 虚拟列表处理长列表、Skeleton提升加载感知
- 监控系统: 错误追踪与性能监控方案
核心概念
1. React是什么?它的主要特点是什么?
答案: React是一个用于构建用户界面的JavaScript库。主要特点包括:
- 组件化开发
- 虚拟DOM,高效更新
- 单向数据流
- JSX语法支持
- 声明式编程
2. 什么是虚拟DOM?它如何提高性能?
答案: 虚拟DOM是React内存中DOM的表示。React通过比较前后两次虚拟DOM的差异(Diffing算法),最小化实际DOM操作,从而提高性能。
最佳实践: 避免直接操作DOM,让React处理DOM更新。使用shouldComponentUpdate或React.memo减少不必要的渲染。
3. 类组件与函数组件的区别?
答案:
- 类组件基于ES6 class,可使用生命周期方法和state
- 函数组件是纯函数,使用Hooks管理状态和副作用
- 函数组件性能更好,代码更简洁
最佳实践: 优先使用函数组件和Hooks,除非有特殊需求必须使用类组件。
Hooks相关
4. 解释useState和useEffect的用途
答案:
useState:在函数组件中添加状态useEffect:处理副作用,如API调用、订阅、手动DOM操作等
最佳实践:
// 正确用法
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `点击了${count}次`;
return () => { /* 清理函数 */ };
}, [count]); // 依赖数组
5. useCallback与useMemo的区别?
答案:
useCallback:缓存函数引用,避免不必要的渲染useMemo:缓存计算结果,避免昂贵计算的重复执行
最佳实践:
// 函数缓存
const handleClick = useCallback(() => {
console.log(count);
}, [count]);
// 值缓存
const doubleCount = useMemo(() => {
return expensiveCalculation(count);
}, [count]);
6. 自定义Hook的作用是什么?
答案: 自定义Hook用于提取组件逻辑到可重用的函数中,遵循use命名约定,可以调用其他Hook。
最佳实践:
function useWindowSize() {
const [size, setSize] = useState({ width: 0, height: 0 });
useEffect(() => {
const handleResize = () => {
setSize({ width: window.innerWidth, height: window.innerHeight });
};
window.addEventListener('resize', handleResize);
handleResize(); // 初始化
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
性能优化
7. React如何优化性能?
答案:
- 使用
React.memo避免不必要的重新渲染 - 使用
useCallback和useMemo缓存函数和值 - 使用
shouldComponentUpdate或PureComponent优化类组件 - 适当拆分组件,实现精确更新
- 使用
React.lazy和Suspense实现代码分割
最佳实践:
const MemoizedComponent = React.memo(MyComponent, (prevProps, nextProps) => {
// 返回true表示不需要重新渲染
return prevProps.id === nextProps.id;
});
8. 列表渲染为什么需要key,它的作用是什么?
答案: key帮助React识别哪些元素改变了,如增加或删除,帮助DOM高效更新。key应该是稳定、唯一、可预测的。
最佳实践:
// 好的做法
{items.map(item => <Item key={item.id} {...item} />)}
// 避免使用索引作为key,除非列表是静态的、不会重新排序
状态管理
9. Context API的使用场景是什么?
答案: Context用于多层组件共享数据,避免层层Props传递(prop drilling)。适用于主题、语言、用户认证等全局状态。
最佳实践:
// 创建Context
const ThemeContext = React.createContext('light');
// Provider
function App() {
return (
<ThemeContext.Provider value="dark">
<ThemedButton />
</ThemeContext.Provider>
);
}
// 消费Context
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>按钮</button>;
}
10. Redux与React Context的区别?
答案:
- Redux提供全局状态管理,有严格的单向数据流
- Context主要用于避免props传递,处理局部状态共享
- Redux适合复杂应用,有时间旅行调试等工具
- Context适合中小型应用或特定场景
最佳实践: 先考虑使用Context和useReducer,当状态逻辑复杂时再考虑引入Redux。
路由
11. React Router的工作原理?
答案: React Router通过匹配URL和路由配置,渲染对应的组件。它使用History API在不刷新页面的情况下更新URL。
最佳实践:
import { BrowserRouter, Routes, Route } from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:userId" element={<UserProfile />} />
</Routes>
</BrowserRouter>
);
}
其他热门问题
12. React Fiber是什么?
答案: Fiber是React 16引入的新调和算法,支持任务分割和优先级,可中断渲染过程,提高响应性,为异步渲染奠定基础。
13. 如何处理React中的表单?
答案:
- 受控组件:表单元素值由React状态控制
- 非受控组件:表单元素值存在DOM中,通过ref访问
最佳实践:
// 受控组件
function Form() {
const [value, setValue] = useState('');
const handleChange = (e) => {
setValue(e.target.value);
};
return <input value={value} onChange={handleChange} />;
}
14. React 18有哪些新特性?
答案:
- 并发渲染
- 自动批处理
- Suspense SSR支持
- useTransition/useDeferredValue Hooks
- 新的Root API
最佳实践:
// 使用新的Root API
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'));
root.render(<App />);
// 使用useTransition处理低优先级更新
const [isPending, startTransition] = useTransition();
startTransition(() => {
setSearchResults(filterItems(input));
});
15. 如何实现React组件的条件渲染?
答案: 使用逻辑运算符、三元表达式或提前return实现条件渲染。
最佳实践:
// 使用&&运算符
{isLoggedIn && <UserProfile />}
// 使用三元表达式
{isLoggedIn ? <LogoutButton /> : <LoginButton />}
// 函数内提前返回
if (!items.length) return <EmptyState />;
生命周期与更新机制
16. React组件的生命周期有哪些阶段?
答案: React 16.3后的生命周期分为三个阶段:
- 挂载: constructor → getDerivedStateFromProps → render → componentDidMount
- 更新: getDerivedStateFromProps → shouldComponentUpdate → render → getSnapshotBeforeUpdate → componentDidUpdate
- 卸载: componentWillUnmount
最佳实践: 在函数组件中使用useEffect替代生命周期方法。
17. React中setState是同步还是异步的?
答案: setState在React事件处理和生命周期中是批量异步更新的,在setTimeout、Promise等原生事件中是同步的。React 18中,所有setState都会自动批处理。
最佳实践:
// 依赖前一个状态
setState(prevState => ({
counter: prevState.counter + 1
}));
// 需要立即使用更新后的状态时,使用useEffect
useEffect(() => {
console.log(count); // 这里的count是更新后的值
}, [count]);
高级Hooks
18. useReducer的使用场景是什么?
答案: useReducer适用于复杂状态逻辑,尤其是状态之间有依赖关系或下一个状态依赖于之前的状态。
最佳实践:
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, {count: 0});
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
19. useRef的作用是什么?
答案: useRef主要有两个用途:
- 访问DOM节点或React元素
- 保存不触发重新渲染的可变值
最佳实践:
// 访问DOM节点
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus();
}, []);
return <input ref={inputRef} />;
// 保存可变值
const renderCountRef = useRef(0);
useEffect(() => {
renderCountRef.current += 1;
});
架构与设计模式
20. 什么是受控组件和非受控组件?
答案:
- 受控组件:表单数据由React组件控制,通过props和state实现
- 非受控组件:表单数据由DOM元素自身控制,通过ref获取值
最佳实践:
// 受控组件
function ControlledForm() {
const [value, setValue] = useState('');
return <input value={value} onChange={e => setValue(e.target.value)} />;
}
// 非受控组件
function UncontrolledForm() {
const inputRef = useRef(null);
const handleSubmit = () => {
console.log(inputRef.current.value);
};
return <input ref={inputRef} defaultValue="默认值" />;
}
21. 什么是高阶组件(HOC)?
答案: 高阶组件是接收一个组件并返回一个新组件的函数,用于复用组件逻辑。
最佳实践:
// 高阶组件示例 - 添加日志功能
function withLogging(Component) {
return function LoggedComponent(props) {
useEffect(() => {
console.log(`组件${Component.name}已挂载`);
return () => console.log(`组件${Component.name}已卸载`);
}, []);
return <Component {...props} />;
};
}
// 使用HOC
const EnhancedButton = withLogging(Button);
性能优化进阶
22. 如何处理React中的大型列表渲染?
答案: 使用虚拟滚动技术,只渲染可视区域内的元素,如使用react-window或react-virtualized库。
最佳实践:
import { FixedSizeList } from 'react-window';
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={500}
width={300}
itemCount={items.length}
itemSize={50}
>
{Row}
</FixedSizeList>
);
}
23. 如何避免不必要的重新渲染?
答案:
- 使用shouldComponentUpdate或React.memo
- 使用useCallback和useMemo
- 组件拆分,分离变化和不变的部分
- 使用React.lazy进行代码分割
最佳实践:
// 避免传递新对象导致的不必要渲染
const memoizedValue = useMemo(() => ({ id, name }), [id, name]);
return <ChildComponent data={memoizedValue} />;
服务端渲染与新特性
24. 什么是服务端渲染(SSR)?它有什么优势?
答案: SSR是在服务端生成HTML,然后发送到客户端。优势包括:
- 更好的SEO
- 更快的首屏加载
- 对低性能设备更友好
- 改善核心Web指标
最佳实践: 使用Next.js或Remix等框架实现SSR。
25. React中的错误边界是什么?
答案: 错误边界是捕获子组件树JavaScript错误的组件,可以记录错误并显示备用UI。
最佳实践:
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 记录错误到服务
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>出错了,请稍后重试。</h1>;
}
return this.props.children;
}
}
// 使用错误边界
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
TypeScript与React
26. React中如何使用TypeScript类型?
答案: 使用TypeScript定义组件props、state和事件处理函数的类型,提高代码质量。
最佳实践:
interface ButtonProps {
text: string;
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
variant?: 'primary' | 'secondary' | 'danger';
}
const Button: React.FC<ButtonProps> = ({
text,
onClick,
variant = 'primary'
}) => {
return (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{text}
</button>
);
};
27. 什么是React的Suspense?
答案: Suspense让组件在渲染前等待某些操作完成,比如数据获取。在等待期间可以显示fallback内容。
最佳实践:
// 代码分割
const LazyComponent = React.lazy(() => import('./LazyComponent'));
function MyComponent() {
return (
<Suspense fallback={<div>加载中...</div>}>
<LazyComponent />
</Suspense>
);
}
// React 18中数据获取
function ProfilePage() {
return (
<Suspense fallback={<Spinner />}>
<ProfileDetails />
</Suspense>
);
}
测试与调试
28. 如何测试React组件?
答案: 使用Jest和React Testing Library进行组件测试,Cypress或Playwright进行端到端测试。
最佳实践:
// 使用React Testing Library
import { render, screen, fireEvent } from '@testing-library/react';
test('点击按钮增加计数', () => {
render(<Counter />);
const button = screen.getByText('增加');
fireEvent.click(button);
expect(screen.getByText('计数: 1')).toBeInTheDocument();
});
29. useEffect的清理函数有什么作用?
答案: useEffect的清理函数用于防止内存泄漏,清理订阅、定时器等副作用。
最佳实践:
useEffect(() => {
const subscription = api.subscribe(data => {
setData(data);
});
// 清理函数
return () => {
subscription.unsubscribe();
};
}, []);
30. React中如何实现代码分割?
答案: 使用React.lazy和Suspense实现代码分割,按需加载组件,减小打包体积。
最佳实践:
const Home = React.lazy(() => import('./routes/Home'));
const About = React.lazy(() => import('./routes/About'));
function App() {
return (
<Router>
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
}
状态管理进阶
31. Redux中间件的作用是什么?
答案: Redux中间件提供了位于action被发起之后、到达reducer之前的第三方扩展点,用于处理异步操作、日志记录、崩溃报告等副作用。
最佳实践:
// redux-thunk中间件使用示例
const fetchUsers = () => {
return async (dispatch) => {
dispatch({ type: 'FETCH_USERS_REQUEST' });
try {
const response = await api.getUsers();
dispatch({ type: 'FETCH_USERS_SUCCESS', payload: response.data });
} catch (error) {
dispatch({ type: 'FETCH_USERS_FAILURE', error });
}
};
};
32. React中如何实现状态不可变性?
答案: 使用扩展运算符、Object.assign()、数组方法如map/filter/concat或使用Immer库确保状态不可变性。
最佳实践:
// 不可变更新对象
setState(prevState => ({
...prevState,
user: {
...prevState.user,
name: 'Zhang San'
}
}));
// 使用Immer库
import produce from 'immer';
setState(produce(draft => {
draft.user.name = 'Zhang San';
}));
33. Recoil与Redux有什么不同?
答案: Recoil是Facebook推出的状态管理库,相比Redux:
- API更简单,学习成本低
- 原生支持React Concurrent Mode
- 支持Atom级别的细粒度更新
- 使用Selector实现派生状态
最佳实践:
import { atom, selector, useRecoilState, useRecoilValue } from 'recoil';
// 定义atom
const counterState = atom({
key: 'counterState',
default: 0,
});
// 定义selector
const doubleCountState = selector({
key: 'doubleCountState',
get: ({get}) => {
return get(counterState) * 2;
},
});
// 组件中使用
function Counter() {
const [count, setCount] = useRecoilState(counterState);
const doubleCount = useRecoilValue(doubleCountState);
return (
<div>
<button onClick={() => setCount(count + 1)}>增加</button>
<div>计数: {count}</div>
<div>双倍: {doubleCount}</div>
</div>
);
}
React生态系统
34. React Hook Form和Formik的区别是什么?
答案: 两者都是React表单库,但有如下区别:
- React Hook Form更轻量,强调无控组件,性能更好
- Formik基于受控组件,API更直观
- React Hook Form使用ref获取值,减少重新渲染
- Formik提供更多的表单状态和辅助函数
最佳实践:
// React Hook Form
import { useForm } from "react-hook-form";
function HookForm() {
const { register, handleSubmit, errors } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name", { required: true })} />
{errors.name && <span>姓名必填</span>}
<button type="submit">提交</button>
</form>
);
}
35. 如何使用CSS-in-JS方案?
答案: 通过CSS-in-JS库如styled-components或emotion,可以在React组件内直接编写样式,实现样式的组件化和动态化。
最佳实践:
// styled-components
import styled from 'styled-components';
const Button = styled.button`
background: ${props => props.primary ? 'blue' : 'white'};
color: ${props => props.primary ? 'white' : 'blue'};
border-radius: 4px;
padding: 8px 16px;
transition: all 0.3s ease;
&:hover {
opacity: 0.8;
}
`;
function App() {
return <Button primary>点击我</Button>;
}
性能优化高级技巧
36. 什么是React.memo,它与PureComponent有什么区别?
答案:
- React.memo是高阶组件,用于函数组件的性能优化,通过浅比较props确定是否重新渲染
- PureComponent是类组件,通过浅比较props和state确定是否重新渲染
- 两者都通过浅比较实现优化,但适用于不同类型的组件
最佳实践:
// React.memo
const MyComponent = React.memo(function MyComponent(props) {
/* 渲染使用props */
return <div>{props.name}</div>;
});
// 自定义比较函数
const MyComponent = React.memo(
function MyComponent(props) {
/* 渲染使用props */
return <div>{props.name}</div>;
},
(prevProps, nextProps) => {
// 返回true表示不需要重新渲染
return prevProps.id === nextProps.id;
}
);
37. 如何解决React中的重渲染问题?
答案: 重渲染问题的解决方法包括:
- 使用React.memo包装纯组件
- 使用useCallback避免重复创建函数
- 使用useMemo避免重复计算
- 适当拆分组件,隔离变化
- 使用State提升精确控制更新范围
最佳实践:
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// 缓存回调函数
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return (
<>
<input value={text} onChange={(e) => setText(e.target.value)} />
<ExpensiveChild count={count} onClick={handleClick} />
</>
);
}
// 使用React.memo避免text改变时重渲染
const ExpensiveChild = React.memo(({ count, onClick }) => {
console.log('ExpensiveChild render');
return <button onClick={onClick}>点击了 {count} 次</button>;
});
React架构与设计
38. 如何实现React组件的按需加载?
答案: 使用React.lazy和动态import实现组件按需加载,配合Suspense显示加载状态。
最佳实践:
import React, { Suspense, lazy } from 'react';
// 按需加载路由组件
const UserProfile = lazy(() => import('./UserProfile'));
const UserSettings = lazy(() => import('./UserSettings'));
function App() {
return (
<div>
<Suspense fallback={<div>加载中...</div>}>
<Router>
<Routes>
<Route path="/profile" element={<UserProfile />} />
<Route path="/settings" element={<UserSettings />} />
</Routes>
</Router>
</Suspense>
</div>
);
}
39. React中如何实现组件通信?
答案: React组件通信方式包括:
- Props传递(父子组件)
- Context API(跨层级组件)
- 状态管理库(Redux/Mobx)
- 发布-订阅模式(EventEmitter)
- 自定义事件总线
最佳实践:
// 1. Props传递
function Parent() {
const [message, setMessage] = useState('');
const handleMessage = (msg) => {
setMessage(msg);
};
return <Child onSendMessage={handleMessage} parentMessage={message} />;
}
// 2. Context API
const MessageContext = createContext();
function MessageProvider({ children }) {
const [message, setMessage] = useState('');
return (
<MessageContext.Provider value={{ message, setMessage }}>
{children}
</MessageContext.Provider>
);
}
function DeepChild() {
const { message, setMessage } = useContext(MessageContext);
return (
<>
<div>{message}</div>
<button onClick={() => setMessage('新消息')}>发送消息</button>
</>
);
}
40. 什么是Compound Components设计模式?
答案: Compound Components是React中的一种设计模式,通过创建具有隐式状态共享的子组件集合,实现更灵活的组件组合。
最佳实践:
function Tabs({ children, defaultIndex = 0 }) {
const [activeIndex, setActiveIndex] = useState(defaultIndex);
const context = {
activeIndex,
setActiveIndex
};
return (
<TabsContext.Provider value={context}>
{children}
</TabsContext.Provider>
);
}
function TabList({ children }) {
return <div className="tab-list">{children}</div>;
}
function Tab({ children, index }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext);
return (
<div
className={activeIndex === index ? 'tab active' : 'tab'}
onClick={() => setActiveIndex(index)}
>
{children}
</div>
);
}
// 使用方式
<Tabs>
<TabList>
<Tab index={0}>标签一</Tab>
<Tab index={1}>标签二</Tab>
</TabList>
<TabPanels>
<TabPanel>内容一</TabPanel>
<TabPanel>内容二</TabPanel>
</TabPanels>
</Tabs>
React 18与新特性
41. React 18中的并发特性是什么?
答案: React 18引入了并发渲染机制,主要特性包括:
- 自动批处理:更智能地批量更新状态
- Transitions API:区分紧急和非紧急更新
- Suspense SSR:流式服务端渲染
- 并发特性:允许React中断、恢复和放弃渲染
最佳实践:
// 使用useTransition区分紧急和非紧急更新
const [isPending, startTransition] = useTransition();
// 紧急更新
setInputValue(input);
// 非紧急更新
startTransition(() => {
setSearchResults(filterItems(input));
});
// 显示加载状态
{isPending && <Spinner />}
// 使用useDeferredValue获取延迟值
const deferredQuery = useDeferredValue(query);
42. 什么是Server Components?
答案: React Server Components是一种新组件类型,完全在服务器上运行,不需要JavaScript bundle,可以访问服务器资源,减少客户端JavaScript负载。
最佳实践:
// 服务器组件(不使用useState/useEffect等客户端特性)
// ServerComponent.server.js
import { db } from './database';
async function ServerComponent({ id }) {
const data = await db.query(`SELECT * FROM items WHERE id = ${id}`);
return <div>{data.title}</div>;
}
// 客户端组件
// ClientComponent.client.js
'use client';
import { useState } from 'react';
export default function ClientComponent() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
实战问题
43. 如何管理React应用中的全局状态?
答案: 依据应用规模和复杂度选择合适的方案:
- 小型应用:使用React Context + useReducer
- 中型应用:考虑使用Zustand、Jotai等轻量库
- 大型应用:使用Redux、MobX等成熟方案
- 特定需求:考虑React Query、SWR处理服务端状态
最佳实践:
// Zustand示例
import create from 'zustand';
const useStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
function Counter() {
const { count, increment } = useStore();
return (
<div>
<p>计数: {count}</p>
<button onClick={increment}>增加</button>
</div>
);
}
44. 如何处理React中的表单验证?
答案: 表单验证可使用自定义Hooks、第三方库如Formik/React Hook Form,或使用HTML5内置验证API结合自定义逻辑。
最佳实践:
// 自定义Hook进行表单验证
function useFormValidation(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
useEffect(() => {
if (isSubmitting) {
const noErrors = Object.keys(errors).length === 0;
if (noErrors) {
// 提交表单
console.log('表单提交', values);
setIsSubmitting(false);
} else {
setIsSubmitting(false);
}
}
}, [errors, isSubmitting, values]);
const handleChange = (event) => {
setValues({
...values,
[event.target.name]: event.target.value
});
};
const handleSubmit = (event) => {
event.preventDefault();
const validationErrors = validate(values);
setErrors(validationErrors);
setIsSubmitting(true);
};
return {
values,
errors,
handleChange,
handleSubmit
};
}
45. 如何优化React应用的首屏加载时间?
答案: 优化首屏加载时间的方法包括:
- 代码分割和按需加载
- 路由级懒加载
- 预加载关键资源
- 使用服务端渲染
- 优化图片和媒体资源
- 开启Gzip/Brotli压缩
- 使用缓存策略
最佳实践:
// 预加载路由组件
const ProfilePage = lazy(() => import('./routes/Profile'));
// 在用户hover链接时预加载
function NavLink({ to, children }) {
const prefetchProfile = () => {
// 预加载组件
import('./routes/Profile');
};
return (
<Link
to={to}
onMouseEnter={prefetchProfile}
onFocus={prefetchProfile}
>
{children}
</Link>
);
}
React实际项目最佳设计方案
项目规模划分标准
| 规模 | 特征 | 团队规模 | 页面/组件数量 |
|---|---|---|---|
| 小型 | 单一功能、周期短 | 1-3人 | 10-20页面 |
| 中型 | 多个模块、周期中等 | 3-8人 | 20-100页面 |
| 大型 | 多业务线、长期维护 | 8人以上 | 100+页面 |
小型项目最佳设计
技术选型
- 框架:React + Vite (快速启动)
- 样式:Tailwind CSS/CSS Modules
- 状态管理:React Context + useReducer
- 路由:React Router v6
- 请求:React Query/SWR
- 表单:React Hook Form (轻量)
项目结构
src/
├── components/ # 共享组件
├── pages/ # 页面组件
├── hooks/ # 自定义hooks
├── context/ # Context状态
├── api/ # API请求
├── utils/ # 工具函数
└── assets/ # 静态资源
最佳实践
- 使用函数组件和Hooks
- 组件粒度适中,避免过度拆分
- 简单状态使用Context,避免引入Redux
- 使用Suspense和ErrorBoundary处理加载和错误状态
- 小型组件库如shadcn/ui满足UI需求
中型项目最佳设计
技术选型
- 框架:React + Next.js
- 样式:Styled Components/Emotion
- 状态管理:Zustand/Jotai/Redux Toolkit
- 路由:Next.js内置路由
- 请求:React Query + Axios拦截器
- 表单:Formik/React Hook Form + Yup/Zod验证
- 组件库:MUI/Ant Design
项目结构
src/
├── components/ # 组件(原子设计)
│ ├── atoms/ # 原子组件
│ ├── molecules/ # 分子组件
│ └── organisms/ # 有机体组件
├── features/ # 按功能模块组织
│ ├── auth/ # 认证功能
│ └── dashboard/ # 仪表盘功能
├── pages/ # 页面组件/路由
├── hooks/ # 自定义hooks
├── store/ # 状态管理
├── services/ # API服务
├── utils/ # 工具函数
└── assets/ # 静态资源
最佳实践
- 组件分层,实现UI与逻辑分离
- 采用特性驱动开发(FDD)模式,按功能模块组织文件
- 公共状态使用Zustand,局部状态使用useState/useReducer
- 实现权限控制与路由守卫
- API请求集中管理,使用适配器模式
- 组件文档与Storybook集成
大型项目最佳设计
技术选型
- 框架:React + Next.js/Remix
- 架构:微前端 (Module Federation/Qiankun)
- 样式:设计系统 + CSS-in-JS
- 状态管理:Redux Toolkit + RTK Query
- 路由:基于权限的动态路由
- 数据获取:GraphQL + Apollo Client/Relay
- 类型检查:TypeScript(严格模式)
- 测试:Jest + React Testing Library + Cypress
项目结构
apps/ # 多应用/微前端
├── main/ # 主应用
├── admin/ # 管理后台
└── marketing/ # 营销模块
packages/ # 共享包
├── ui/ # UI组件库
├── hooks/ # 业务Hooks库
├── api-client/ # API客户端
├── utils/ # 工具函数
└── eslint-config/ # 共享ESLint配置
最佳实践
- 采用Monorepo管理多个应用与共享库
- 实现自己的设计系统,确保品牌一致性
- 应用DDD领域驱动设计思想组织代码
- 中间件处理复杂异步流程(Redux-Saga/Redux-Observable)
- 强类型约束,减少运行时错误
- 完善的测试覆盖(单元测试、集成测试、E2E测试)
- 性能监控与错误追踪系统
- 国际化与多语言支持
- CI/CD自动化部署流水线
通用最佳实践
代码质量保障
- 代码规范:ESLint + Prettier + Husky
- 类型检查:TypeScript
- 测试:Jest/Vitest + React Testing Library
- Code Review:PR模板与代码审查清单
性能优化策略
- 代码分割:按路由/组件懒加载
- 自定义Hooks优化:避免重复渲染
- 使用memo/useCallback/useMemo:缓存优化
- 虚拟列表:处理长列表渲染
可访问性与SEO
- 可访问性:ARIA属性、语义化HTML、键盘支持
- SSR/SSG:提升首屏加载与SEO
- 元数据管理:动态Title与Meta标签
项目文档与知识管理
- 组件文档:Storybook展示组件用法
- API文档:Swagger/OpenAPI规范
- 架构决策记录:记录技术决策与演进
选型决策表
| 需求/规模 | 小型 | 中型 | 大型 |
|---|---|---|---|
| 构建工具 | Vite | Next.js | Turborepo + Next.js |
| 状态管理 | Context | Zustand/Jotai | Redux Toolkit |
| 样式方案 | Tailwind | Styled Components | 设计系统 |
| 数据获取 | SWR | React Query | GraphQL |
| 部署策略 | 静态部署 | Docker容器 | K8s集群 |
| 缓存策略 | 浏览器缓存 | 服务端缓存 | 多级缓存 |
根据项目复杂度和团队规模选择适合的技术栈,避免过度设计,从小做起,逐步演进。