React面试题目和答案大全
基础概念篇
1. 什么是React?它有什么特点?
答案: React是由Facebook开发的用于构建用户界面的JavaScript库。主要特点包括:
- 组件化:将UI拆分为独立、可复用的组件
- 虚拟DOM:通过虚拟DOM提高渲染性能
- 单向数据流:数据从父组件流向子组件,便于调试和维护
- 声明式编程:描述UI应该是什么样子,而不是如何操作DOM
- 跨平台:可用于Web、移动端(React Native)等
2. 解释虚拟DOM的工作原理
答案: 虚拟DOM是React在内存中维护的一个JavaScript对象树,它是真实DOM的抽象表示。
工作流程:
- 创建虚拟DOM:组件渲染时创建虚拟DOM树
- Diff算法:当状态改变时,React创建新的虚拟DOM树,并与旧树进行比较
- 计算差异:找出需要更新的最小变更集
- 批量更新:将变更批量应用到真实DOM上
优势:
- 减少直接操作DOM的次数
- 批量更新提高性能
- 跨浏览器兼容性好
3. JSX是什么?为什么要使用JSX?
答案: JSX(JavaScript XML)是React的语法扩展,允许在JavaScript中编写类似HTML的代码。
特点:
- 语法更直观,提高开发效率
- 编译时会转换为React.createElement()调用
- 支持JavaScript表达式嵌入
- 具备JavaScript的完整功能
示例:
// JSX
const element = <h1>Hello, {name}!</h1>;// 编译后
const element = React.createElement('h1', null, 'Hello, ', name, '!');
组件篇
4. 函数组件和类组件的区别
答案:
特性 | 函数组件 | 类组件 |
---|---|---|
定义方式 | 函数定义 | class定义 |
状态管理 | 使用useState Hook | 使用this.state |
生命周期 | 使用useEffect Hook | 内置生命周期方法 |
性能 | 更轻量,无实例开销 | 有实例开销 |
代码量 | 更简洁 | 相对冗长 |
推荐程度 | 官方推荐 | 逐渐被替代 |
5. 什么是React Hooks?常用的Hooks有哪些?
答案: Hooks是React 16.8引入的新特性,允许在函数组件中使用状态和其他React特性。
常用Hooks:
- useState:管理组件状态
- useEffect:处理副作用操作
- useContext:消费Context
- useReducer:复杂状态管理
- useMemo:缓存计算结果
- useCallback:缓存函数引用
- useRef:访问DOM元素或保存可变值
- useLayoutEffect:同步执行副作用
6. useState和useReducer的使用场景
答案:
useState适用场景:
- 简单的状态管理
- 独立的状态变量
- 状态更新逻辑简单
const [count, setCount] = useState(0);
const [name, setName] = useState('');
useReducer适用场景:
- 复杂的状态逻辑
- 多个子值的状态对象
- 状态更新依赖之前的状态
- 需要优化性能的场景
const [state, dispatch] = useReducer(reducer, initialState);
7. useEffect的用法和注意事项
答案:
基本用法:
// 副作用执行
useEffect(() => {// 副作用逻辑
}, [dependencies]);
使用场景:
- 数据获取
- 订阅/取消订阅
- 手动修改DOM
- 定时器操作
注意事项:
- 依赖数组控制执行时机
- 清理函数防止内存泄漏
- 避免无限循环
- 异步操作需要处理组件卸载情况
useEffect(() => {const timer = setInterval(() => {// 定时任务}, 1000);return () => clearInterval(timer); // 清理函数
}, []);
状态管理篇
8. React中状态提升是什么?
答案: 状态提升是指将多个组件需要共享的状态提升到它们最近的共同父组件中,通过props向下传递状态和更新函数。
使用场景:
- 兄弟组件间需要共享状态
- 多个组件需要响应同一状态变化
示例:
function Parent() {const [sharedState, setSharedState] = useState('');return (<div><ChildA value={sharedState} onChange={setSharedState} /><ChildB value={sharedState} /></div>);
}
9. Context API的使用方法
答案: Context API用于在组件树中传递数据,避免props drilling问题。
使用步骤:
// 1. 创建Context
const ThemeContext = React.createContext();// 2. 提供Provider
function App() {return (<ThemeContext.Provider value="dark"><Header /></ThemeContext.Provider>);
}// 3. 消费Context
function Header() {const theme = useContext(ThemeContext);return <h1>Current theme: {theme}</h1>;
}
10. Redux和Context API的区别
答案:
特性 | Redux | Context API |
---|---|---|
学习成本 | 较高 | 较低 |
样板代码 | 较多 | 较少 |
调试工具 | 强大 | 基础 |
性能 | 优秀 | 可能有性能问题 |
适用场景 | 复杂应用 | 简单到中等复杂度 |
中间件支持 | 丰富 | 无 |
性能优化篇
11. React.memo、useMemo、useCallback的区别和使用场景
答案:
React.memo:
- 高阶组件,用于优化函数组件
- 浅比较props,props未变化时跳过渲染
- 适用于props变化不频繁的组件
const MyComponent = React.memo(function MyComponent({ name }) {return <div>{name}</div>;
});
useMemo:
- 缓存计算结果
- 依赖项变化时重新计算
- 适用于昂贵的计算操作
const expensiveValue = useMemo(() => {return heavyCalculation(a, b);
}, [a, b]);
useCallback:
- 缓存函数引用
- 避免子组件不必要的重新渲染
- 适用于传递给子组件的回调函数
const memoizedCallback = useCallback(() => {doSomething(a, b);
}, [a, b]);
12. 如何避免不必要的渲染?
答案:
优化策略:
- 使用React.memo包装组件
- 合理使用useMemo和useCallback
- 状态结构扁平化
- 避免在render中创建新对象
- 使用状态提升和Context合理设计
代码示例:
// 避免在render中创建对象
const MyComponent = () => {const style = useMemo(() => ({ color: 'red' }), []);return <div style={style}>Content</div>;
};
13. 什么是代码分割?如何实现?
答案: 代码分割是将应用分割成多个bundle,实现按需加载,减少初始加载时间。
实现方法:
- 动态import():
const LazyComponent = React.lazy(() => import('./LazyComponent'));
- 路由级别分割:
const Home = React.lazy(() => import('./pages/Home'));
const About = React.lazy(() => import('./pages/About'));
- 使用Suspense:
<Suspense fallback={<div>Loading...</div>}><LazyComponent />
</Suspense>
高级特性篇
14. 什么是高阶组件(HOC)?
答案: 高阶组件是参数为组件,返回值为新组件的函数。它是基于React的组合特性而形成的设计模式。
特点:
- 复用组件逻辑
- 不修改原组件
- 返回新的增强组件
示例:
function withLoading(WrappedComponent) {return function WithLoadingComponent(props) {if (props.isLoading) {return <div>Loading...</div>;}return <WrappedComponent {...props} />;};
}const EnhancedComponent = withLoading(MyComponent);
15. Render Props模式是什么?
答案: Render Props是一种在React组件间使用一个值为函数的prop共享代码的简单技术。
特点:
- 组件接收一个返回React元素的函数
- 在渲染时调用该函数
- 实现逻辑复用
示例:
class DataProvider extends React.Component {state = { data: null };componentDidMount() {fetchData().then(data => this.setState({ data }));}render() {return this.props.render(this.state.data);}
}// 使用
<DataProvider render={data => (<h1>Hello {data ? data.name : 'Loading...'}</h1>
)} />
16. 什么是错误边界(Error Boundaries)?
答案: 错误边界是一种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) {console.log('Error caught:', error, errorInfo);}render() {if (this.state.hasError) {return <h1>Something went wrong.</h1>;}return this.props.children;}
}
17. Portal是什么?使用场景有哪些?
答案: Portal提供了一种将子节点渲染到存在于父组件以外的DOM节点的优秀方案。
使用场景:
- 模态框
- 提示工具
- 悬浮卡片
- 全局通知
示例:
import ReactDOM from 'react-dom';function Modal({ children }) {return ReactDOM.createPortal(children,document.getElementById('modal-root'));
}
生命周期篇
18. React类组件的生命周期方法有哪些?
答案:
挂载阶段:
- constructor()
- static getDerivedStateFromProps()
- render()
- componentDidMount()
更新阶段:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
卸载阶段:
- componentWillUnmount()
错误处理:
- static getDerivedStateFromError()
- componentDidCatch()
19. 函数组件如何模拟生命周期?
答案:
使用useEffect模拟:
function MyComponent() {// componentDidMountuseEffect(() => {console.log('Component mounted');}, []);// componentDidUpdateuseEffect(() => {console.log('Component updated');});// componentWillUnmountuseEffect(() => {return () => {console.log('Component will unmount');};}, []);// 依赖特定值的更新useEffect(() => {console.log('Value changed');}, [value]);
}
20. shouldComponentUpdate和React.memo的区别
答案:
特性 | shouldComponentUpdate | React.memo |
---|---|---|
适用组件 | 类组件 | 函数组件 |
比较方式 | 手动比较 | 自动浅比较 |
自定义比较 | 完全自定义 | 可传入比较函数 |
返回值 | boolean | React组件 |
// shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {return nextProps.value !== this.props.value;
}// React.memo
const MyComponent = React.memo(MyComponent, (prevProps, nextProps) => {return prevProps.value === nextProps.value;
});
事件处理篇
21. React事件系统的特点
答案:
SyntheticEvent(合成事件)特点:
- 跨浏览器兼容性
- 事件对象统一接口
- 事件代理机制
- 自动内存管理
与原生事件区别:
- 事件名称使用camelCase
- 传入函数而非字符串
- 不能通过返回false阻止默认行为
function Button() {const handleClick = (e) => {e.preventDefault(); // 阻止默认行为e.stopPropagation(); // 阻止冒泡console.log('Button clicked');};return <button onClick={handleClick}>Click me</button>;
}
22. 如何获取原生事件对象?
答案:
function MyComponent() {const handleClick = (e) => {// e是SyntheticEvent对象const nativeEvent = e.nativeEvent; // 获取原生事件console.log('Native event:', nativeEvent);};return <button onClick={handleClick}>Click</button>;
}
路由篇
23. React Router的基本使用
答案:
核心组件:
- BrowserRouter/HashRouter:路由器
- Route:路由匹配
- Switch:路由切换
- Link:导航链接
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';function App() {return (<Router><nav><Link to="/">Home</Link><Link to="/about">About</Link></nav><Switch><Route exact path="/" component={Home} /><Route path="/about" component={About} /></Switch></Router>);
}
24. 编程式导航如何实现?
答案:
使用useHistory Hook:
import { useHistory } from 'react-router-dom';function MyComponent() {const history = useHistory();const handleNavigation = () => {history.push('/new-page');// history.replace('/new-page'); // 替换当前历史记录// history.go(-1); // 后退};return <button onClick={handleNavigation}>Navigate</button>;
}
测试篇
25. React组件测试的常用方法
答案:
测试工具:
- Jest:测试框架
- React Testing Library:测试工具
- Enzyme:组件测试工具
测试示例:
import { render, screen, fireEvent } from '@testing-library/react';
import MyComponent from './MyComponent';test('renders component correctly', () => {render(<MyComponent name="Test" />);expect(screen.getByText('Test')).toBeInTheDocument();
});test('handles click event', () => {const handleClick = jest.fn();render(<MyComponent onClick={handleClick} />);fireEvent.click(screen.getByRole('button'));expect(handleClick).toHaveBeenCalledTimes(1);
});
26. 如何测试异步组件?
答案:
import { render, screen, waitFor } from '@testing-library/react';test('loads and displays data', async () => {render(<AsyncComponent />);// 等待异步操作完成await waitFor(() => {expect(screen.getByText('Data loaded')).toBeInTheDocument();});
});
最佳实践篇
27. React项目的文件组织结构
答案:
推荐结构:
src/
├── components/ # 通用组件
│ ├── Button/
│ │ ├── index.js
│ │ ├── Button.js
│ │ └── Button.test.js
├── pages/ # 页面组件
├── hooks/ # 自定义Hooks
├── utils/ # 工具函数
├── services/ # API服务
├── contexts/ # Context定义
├── styles/ # 样式文件
└── __tests__/ # 测试文件
28. 自定义Hook的设计原则
答案:
设计原则:
- 单一职责:每个Hook只负责一个功能
- 可复用性:能在多个组件中使用
- 命名规范:以use开头
- 返回值一致:返回数组或对象
- 依赖明确:正确声明依赖项
示例:
function useLocalStorage(key, initialValue) {const [storedValue, setStoredValue] = useState(() => {try {const item = window.localStorage.getItem(key);return item ? JSON.parse(item) : initialValue;} catch (error) {return initialValue;}});const setValue = (value) => {try {setStoredValue(value);window.localStorage.setItem(key, JSON.stringify(value));} catch (error) {console.error(error);}};return [storedValue, setValue];
}
29. React性能优化的最佳实践
答案:
优化策略:
- 使用React.memo包装纯组件
- 合理使用useMemo和useCallback
- 避免内联对象和函数
- 使用key优化列表渲染
- 懒加载和代码分割
- 状态结构优化
- 避免不必要的渲染
// 优化前
function BadExample({ items, onItemClick }) {return (<div>{items.map(item => (<Item key={item.id}item={item}onClick={() => onItemClick(item.id)} // 每次都创建新函数style={{ marginBottom: 10 }} // 每次都创建新对象/>))}</div>);
}// 优化后
const GoodExample = React.memo(function GoodExample({ items, onItemClick }) {const handleItemClick = useCallback((id) => {onItemClick(id);}, [onItemClick]);const itemStyle = useMemo(() => ({ marginBottom: 10 }), []);return (<div>{items.map(item => (<Item key={item.id}item={item}onItemClick={handleItemClick}style={itemStyle}/>))}</div>);
});
30. React + TypeScript的最佳实践
答案:
类型定义:
interface Props {name: string;age?: number;onClick: (id: string) => void;children: React.ReactNode;
}const MyComponent: React.FC<Props> = ({ name, age, onClick, children }) => {return (<div onClick={() => onClick('123')}><h1>{name}</h1>{age && <p>Age: {age}</p>}{children}</div>);
};// 使用泛型
interface ListProps<T> {items: T[];renderItem: (item: T) => React.ReactNode;
}function List<T>({ items, renderItem }: ListProps<T>) {return (<ul>{items.map((item, index) => (<li key={index}>{renderItem(item)}</li>))}</ul>);
}
面试技巧篇
31. 常见的React面试陷阱题
问题:为什么不能在循环、条件语句中使用Hooks? 答案: Hooks依赖于调用顺序来正确工作。React通过调用顺序来识别哪个state对应哪个useState调用。如果在条件语句中使用Hooks,调用顺序可能会改变,导致state混乱。
问题:setState是同步还是异步的? 答案: 在React 18之前,在合成事件和生命周期方法中setState是异步的,在原生事件和setTimeout中是同步的。React 18引入了自动批处理,所有的setState都会被批处理。
32. 项目经验相关问题
问题:描述一个你在React项目中遇到的性能问题及解决方案 答案框架:
- 问题描述:具体的性能问题现象
- 问题分析:使用的分析工具和方法
- 解决方案:采用的优化策略
- 效果评估:优化前后的性能对比
- 经验总结:从中学到的经验
问题:如何设计一个可复用的组件库? 答案要点:
- API设计简洁明了
- 组件功能单一且完整
- 良好的文档和示例
- 完善的测试覆盖
- 合理的主题定制
- TypeScript支持
- 无障碍访问支持
以上是React面试的核心知识点,建议结合实际项目经验进行深入理解和实践。