- Published on
- · 45 min read
React面试题完全指南
- Authors
- Name
- felixDu
Table of Contents
React面试题完全指南
目录
React基础概念
1. 什么是React?它的主要特点是什么?
面试官提问: "请简要介绍一下React,以及它的核心特点是什么?"
参考答案: React是由Facebook开发的用于构建用户界面的JavaScript库。它的主要特点包括:
- 组件化:将UI拆分成独立、可复用的组件
- 虚拟DOM:通过虚拟DOM提高性能
- 单向数据流:数据从父组件流向子组件
- JSX语法:在JavaScript中编写类似HTML的代码
- 声明式编程:描述UI应该是什么样子,而不是如何改变它
// 组件示例
function Welcome(props) {
return <h1>Hello, {props.name}!</h1>;
}
// 使用组件
function App() {
return (
<div>
<Welcome name="Alice" />
<Welcome name="Bob" />
</div>
);
}
面试官追问: "虚拟DOM是如何工作的?"
深入回答: 虚拟DOM是React在内存中维护的一个JavaScript对象树,它是真实DOM的抽象表示。工作流程如下:
- 当状态改变时,React创建新的虚拟DOM树
- 将新旧虚拟DOM树进行比较(Diff算法)
- 计算出最小的变更集
- 批量更新真实DOM
// 虚拟DOM的简化表示
const vdom = {
type: 'div',
props: {
className: 'container',
children: [
{
type: 'h1',
props: {
children: 'Hello World'
}
}
]
}
};
2. 类组件和函数组件的区别
面试官提问: "请比较一下React中的类组件和函数组件,它们有什么区别?"
参考答案:
// 类组件
class ClassComponent extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
componentDidMount() {
console.log('组件已挂载');
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.handleClick}>增加</button>
</div>
);
}
}
// 函数组件
function FunctionComponent(props) {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('组件已挂载');
}, []);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>增加</button>
</div>
);
}
主要区别:
- 语法:类组件使用ES6类语法,函数组件使用函数
- 状态管理:类组件使用this.state,函数组件使用useState
- 生命周期:类组件有生命周期方法,函数组件使用useEffect
- 性能:函数组件通常更轻量
- 代码简洁性:函数组件更简洁
面试官追问: "在什么情况下你会选择使用类组件?"
深入回答: 虽然React推荐使用函数组件,但以下情况可能需要类组件:
- 需要使用Error Boundaries(错误边界)
- 需要特定的生命周期方法如getSnapshotBeforeUpdate
- 维护旧代码库
3. JSX是什么?它是如何工作的?
面试官提问: "解释一下JSX,以及它在React中的作用。"
参考答案: JSX(JavaScript XML)是React的语法扩展,允许在JavaScript中编写类似HTML的代码。
// JSX代码
const element = <h1 className="greeting">Hello, world!</h1>;
// 编译后的JavaScript
const element = React.createElement(
'h1',
{ className: 'greeting' },
'Hello, world!'
);
JSX特点:
- 可以嵌入JavaScript表达式
- 属性使用驼峰命名(如className而非class)
- 必须有一个根元素
- 可以防止XSS攻击
// JSX示例
function TodoList({ items }) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>
{item.done ? <s>{item.text}</s> : item.text}
</li>
))}
</ul>
);
}
4. Props和State的区别
面试官提问: "请解释Props和State的区别,以及它们的使用场景。"
参考答案:
Props(属性):
- 从父组件传递给子组件的数据
- 只读,不能修改
- 用于组件间通信
State(状态):
- 组件内部管理的数据
- 可以修改
- 改变时会触发重新渲染
// Props示例
function Parent() {
const [parentData, setParentData] = useState('父组件数据');
return <Child message={parentData} onUpdate={setParentData} />;
}
function Child({ message, onUpdate }) {
// message是props,只读
const [localState, setLocalState] = useState('子组件状态');
return (
<div>
<p>来自父组件的Props: {message}</p>
<p>本地State: {localState}</p>
<button onClick={() => setLocalState('更新的状态')}>
更新State
</button>
<button onClick={() => onUpdate('子组件更新了父组件')}>
更新父组件
</button>
</div>
);
}
面试官追问: "如何进行子组件向父组件的通信?"
深入回答: 通过回调函数实现:
function Parent() {
const [data, setData] = useState('');
const handleChildData = (childData) => {
setData(childData);
};
return (
<div>
<p>从子组件接收的数据: {data}</p>
<Child onSendData={handleChildData} />
</div>
);
}
function Child({ onSendData }) {
const [input, setInput] = useState('');
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
/>
<button onClick={() => onSendData(input)}>
发送给父组件
</button>
</div>
);
}
React Hooks
5. useState Hook详解
面试官提问: "请详细解释useState Hook的工作原理和使用注意事项。"
参考答案:
useState是React最基础的Hook,用于在函数组件中添加状态。
function Counter() {
// 基本用法
const [count, setCount] = useState(0);
// 惰性初始化
const [expensiveState, setExpensiveState] = useState(() => {
return computeExpensiveValue();
});
// 函数式更新
const increment = () => {
setCount(prevCount => prevCount + 1);
};
// 批量更新
const handleMultipleUpdates = () => {
setCount(c => c + 1);
setCount(c => c + 1);
setCount(c => c + 1);
// 最终count增加3
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+1</button>
<button onClick={handleMultipleUpdates}>+3</button>
</div>
);
}
注意事项:
- 不要在条件语句中调用useState
- 状态更新是异步的
- 对象和数组需要创建新的引用
// 对象状态更新
const [user, setUser] = useState({ name: 'John', age: 30 });
// 错误方式
user.age = 31;
setUser(user); // 不会触发重新渲染
// 正确方式
setUser({ ...user, age: 31 });
// 或
setUser(prevUser => ({ ...prevUser, age: 31 }));
6. useEffect Hook详解
面试官提问: "解释useEffect的作用,以及如何正确使用依赖数组。"
参考答案:
useEffect用于处理副作用,如数据获取、订阅、手动DOM操作等。
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
// 基本用法 - 每次渲染后执行
useEffect(() => {
console.log('组件渲染了');
});
// 带依赖数组 - 依赖变化时执行
useEffect(() => {
const fetchUser = async () => {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
} catch (error) {
console.error('获取用户失败:', error);
} finally {
setLoading(false);
}
};
fetchUser();
}, [userId]); // 只在userId变化时重新获取
// 清理函数
useEffect(() => {
const timer = setTimeout(() => {
console.log('定时器触发');
}, 1000);
// 清理函数
return () => {
clearTimeout(timer);
};
}, []);
// 空依赖数组 - 仅在挂载时执行
useEffect(() => {
console.log('组件挂载');
return () => {
console.log('组件卸载');
};
}, []);
if (loading) return <div>加载中...</div>;
return (
<div>
<h1>{user?.name}</h1>
<p>{user?.email}</p>
</div>
);
}
面试官追问: "useEffect和useLayoutEffect有什么区别?"
深入回答:
function LayoutEffectExample() {
const [value, setValue] = useState(0);
const ref = useRef();
// useEffect - 异步执行,不阻塞浏览器绘制
useEffect(() => {
console.log('useEffect执行');
// 可能会看到闪烁
if (ref.current) {
ref.current.style.transform = `translateX(${value}px)`;
}
});
// useLayoutEffect - 同步执行,阻塞浏览器绘制
useLayoutEffect(() => {
console.log('useLayoutEffect执行');
// 不会看到闪烁
if (ref.current) {
ref.current.style.transform = `translateX(${value}px)`;
}
});
return <div ref={ref}>移动的元素</div>;
}
7. 自定义Hook
面试官提问: "如何创建自定义Hook?请给出一个实际的例子。"
参考答案:
自定义Hook是复用状态逻辑的函数,必须以"use"开头。
// 自定义Hook - 本地存储
function useLocalStorage(key, initialValue) {
// 从localStorage读取初始值
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error('读取localStorage失败:', error);
return initialValue;
}
});
// 更新localStorage的函数
const setValue = (value) => {
try {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('写入localStorage失败:', error);
}
};
return [storedValue, setValue];
}
// 使用自定义Hook
function Settings() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
const [language, setLanguage] = useLocalStorage('language', 'zh-CN');
return (
<div>
<select value={theme} onChange={(e) => setTheme(e.target.value)}>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
<select value={language} onChange={(e) => setLanguage(e.target.value)}>
<option value="zh-CN">中文</option>
<option value="en-US">英文</option>
</select>
</div>
);
}
// 更复杂的例子 - 数据获取Hook
function useFetch(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortController = new AbortController();
const fetchData = async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, {
...options,
signal: abortController.signal
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const jsonData = await response.json();
setData(jsonData);
} catch (err) {
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
setLoading(false);
}
};
fetchData();
return () => {
abortController.abort();
};
}, [url]);
return { data, loading, error, refetch: () => {} };
}
8. useContext Hook
面试官提问: "解释useContext的使用场景,以及如何避免性能问题。"
参考答案:
useContext用于消费React Context,避免props drilling问题。
// 创建Context
const ThemeContext = createContext();
const UserContext = createContext();
// Context Provider
function App() {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState({ name: 'John' });
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<UserContext.Provider value={{ user, setUser }}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
// 使用useContext
function DeepChildComponent() {
const { theme, setTheme } = useContext(ThemeContext);
const { user } = useContext(UserContext);
return (
<div className={`theme-${theme}`}>
<h1>欢迎, {user.name}!</h1>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
</div>
);
}
// 性能优化 - 拆分Context
const ThemeStateContext = createContext();
const ThemeDispatchContext = createContext();
function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
return (
<ThemeStateContext.Provider value={theme}>
<ThemeDispatchContext.Provider value={setTheme}>
{children}
</ThemeDispatchContext.Provider>
</ThemeStateContext.Provider>
);
}
// 自定义Hook封装
function useTheme() {
const theme = useContext(ThemeStateContext);
const setTheme = useContext(ThemeDispatchContext);
if (theme === undefined) {
throw new Error('useTheme必须在ThemeProvider内使用');
}
return { theme, setTheme };
}
面试官追问: "Context的性能问题如何解决?"
深入回答:
// 1. 使用memo优化子组件
const ExpensiveChild = memo(({ data }) => {
console.log('ExpensiveChild渲染');
return <div>{/* 复杂的UI */}</div>;
});
// 2. 使用useMemo缓存Context值
function OptimizedProvider({ children }) {
const [state, setState] = useState({ count: 0, name: 'John' });
const value = useMemo(
() => ({
state,
increment: () => setState(s => ({ ...s, count: s.count + 1 })),
updateName: (name) => setState(s => ({ ...s, name }))
}),
[state]
);
return (
<MyContext.Provider value={value}>
{children}
</MyContext.Provider>
);
}
9. useReducer Hook
面试官提问: "什么时候应该使用useReducer而不是useState?"
参考答案:
useReducer适合管理复杂的状态逻辑,特别是当状态更新依赖于之前的状态时。
// 复杂状态管理示例
const initialState = {
count: 0,
history: [],
error: null
};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {
...state,
count: state.count + 1,
history: [...state.history, `+1 at ${new Date().toLocaleTimeString()}`]
};
case 'decrement':
if (state.count <= 0) {
return {
...state,
error: '计数不能小于0'
};
}
return {
...state,
count: state.count - 1,
history: [...state.history, `-1 at ${new Date().toLocaleTimeString()}`],
error: null
};
case 'reset':
return initialState;
case 'clearError':
return { ...state, error: null };
default:
throw new Error(`未知的action类型: ${action.type}`);
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<p>计数: {state.count}</p>
{state.error && <p style={{ color: 'red' }}>{state.error}</p>}
<button onClick={() => dispatch({ type: 'increment' })}>+1</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
<h3>历史记录:</h3>
<ul>
{state.history.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
// 结合Context使用
const StateContext = createContext();
const DispatchContext = createContext();
function AppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, initialAppState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
React性能优化
10. React.memo和useMemo的区别
面试官提问: "请解释React.memo和useMemo的区别和使用场景。"
参考答案:
React.memo - 用于优化组件重渲染
// 未优化的组件
function ExpensiveComponent({ data, id }) {
console.log('ExpensiveComponent渲染');
return <div>{/* 复杂的渲染逻辑 */}</div>;
}
// 使用React.memo优化
const MemoizedComponent = React.memo(ExpensiveComponent);
// 自定义比较函数
const MemoizedComponentWithCompare = React.memo(
ExpensiveComponent,
(prevProps, nextProps) => {
// 返回true表示相等,不需要重新渲染
return prevProps.id === nextProps.id;
}
);
// 父组件
function Parent() {
const [count, setCount] = useState(0);
const data = { value: 'static data' };
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<MemoizedComponent data={data} id="123" />
</div>
);
}
useMemo - 用于缓存计算结果
function DataProcessor({ items, filter }) {
// 缓存计算结果
const filteredItems = useMemo(() => {
console.log('执行过滤计算');
return items.filter(item => item.category === filter);
}, [items, filter]);
// 缓存组件
const expensiveComponent = useMemo(() => {
return <ExpensiveChild data={filteredItems} />;
}, [filteredItems]);
// 缓存对象,避免子组件不必要的重渲染
const config = useMemo(() => ({
theme: 'dark',
language: 'zh-CN'
}), []); // 空依赖,永远不变
return (
<div>
<ChildComponent config={config} />
{expensiveComponent}
</div>
);
}
面试官追问: "什么时候不应该使用这些优化?"
深入回答:
- 过早优化是万恶之源
- 简单组件不需要memo
- 缓存本身也有成本
// 不需要优化的例子
function SimpleComponent({ text }) {
return <span>{text}</span>; // 太简单,memo反而增加开销
}
// 需要优化的例子
function ComplexList({ items }) {
return (
<ul>
{items.map(item => (
<MemoizedListItem key={item.id} item={item} />
))}
</ul>
);
}
11. useCallback的使用
面试官提问: "解释useCallback的作用,以及它如何帮助性能优化。"
参考答案:
useCallback用于缓存函数引用,避免子组件不必要的重渲染。
function TodoList() {
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');
// 未优化 - 每次渲染都创建新函数
const handleAddBad = () => {
setTodos([...todos, { id: Date.now(), text: inputValue }]);
setInputValue('');
};
// 使用useCallback优化
const handleAdd = useCallback(() => {
setTodos(prevTodos => [...prevTodos, { id: Date.now(), text: inputValue }]);
setInputValue('');
}, [inputValue]); // 只在inputValue变化时更新函数
// 传递给子组件的回调
const handleDelete = useCallback((id) => {
setTodos(prevTodos => prevTodos.filter(todo => todo.id !== id));
}, []); // 空依赖,函数永不变化
// 结合useMemo使用
const handleToggle = useCallback((id) => {
setTodos(prevTodos =>
prevTodos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
)
);
}, []);
const stats = useMemo(() => ({
total: todos.length,
completed: todos.filter(t => t.done).length
}), [todos]);
return (
<div>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<button onClick={handleAdd}>添加</button>
<TodoStats stats={stats} />
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onDelete={handleDelete}
onToggle={handleToggle}
/>
))}
</div>
);
}
// 子组件使用React.memo优化
const TodoItem = React.memo(({ todo, onDelete, onToggle }) => {
console.log('TodoItem渲染:', todo.id);
return (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>删除</button>
</li>
);
});
12. 虚拟列表/无限滚动
面试官提问: "如何实现一个高性能的长列表?"
参考答案:
对于大量数据的列表,使用虚拟滚动技术只渲染可见区域的元素。
// 简化的虚拟列表实现
function VirtualList({ items, itemHeight, containerHeight }) {
const [scrollTop, setScrollTop] = useState(0);
const scrollElementRef = useRef();
// 计算可见区域
const startIndex = Math.floor(scrollTop / itemHeight);
const endIndex = Math.min(
items.length - 1,
Math.floor((scrollTop + containerHeight) / itemHeight)
);
// 只渲染可见项
const visibleItems = items.slice(startIndex, endIndex + 1);
// 计算偏移量
const offsetY = startIndex * itemHeight;
// 总高度,用于显示滚动条
const totalHeight = items.length * itemHeight;
const handleScroll = (e) => {
setScrollTop(e.target.scrollTop);
};
return (
<div
ref={scrollElementRef}
style={{
height: containerHeight,
overflow: 'auto',
position: 'relative'
}}
onScroll={handleScroll}
>
{/* 占位元素,撑开滚动条 */}
<div style={{ height: totalHeight }} />
{/* 可见项容器 */}
<div
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
transform: `translateY(${offsetY}px)`
}}
>
{visibleItems.map((item, index) => (
<div
key={startIndex + index}
style={{ height: itemHeight }}
>
{item.content}
</div>
))}
</div>
</div>
);
}
// 使用react-window库的示例
import { FixedSizeList } from 'react-window';
function OptimizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
);
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
);
}
13. 代码分割和懒加载
面试官提问: "如何在React中实现代码分割?"
参考答案:
// 1. 使用React.lazy进行组件懒加载
const HeavyComponent = React.lazy(() => import('./HeavyComponent'));
function App() {
const [showHeavy, setShowHeavy] = useState(false);
return (
<div>
<button onClick={() => setShowHeavy(true)}>
加载重型组件
</button>
{showHeavy && (
<Suspense fallback={<div>加载中...</div>}>
<HeavyComponent />
</Suspense>
)}
</div>
);
}
// 2. 路由级别的代码分割
import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function AppRouter() {
return (
<BrowserRouter>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// 3. 条件加载
function ConditionalLoad() {
const [Component, setComponent] = useState(null);
const loadComponent = async (componentName) => {
try {
const module = await import(`./components/${componentName}`);
setComponent(() => module.default);
} catch (error) {
console.error('组件加载失败:', error);
}
};
return (
<div>
<button onClick={() => loadComponent('Chart')}>加载图表</button>
<button onClick={() => loadComponent('Table')}>加载表格</button>
{Component && <Component />}
</div>
);
}
// 4. 预加载组件
const PreloadableComponent = lazy(() => import('./PreloadableComponent'));
// 预加载函数
export const preloadComponent = () => {
import('./PreloadableComponent');
};
// 在合适的时机预加载
function Navigation() {
return (
<nav>
<Link
to="/heavy-page"
onMouseEnter={preloadComponent}
>
重型页面(悬停预加载)
</Link>
</nav>
);
}
React状态管理
14. 状态提升和组件通信
面试官提问: "在React中,如何实现兄弟组件之间的通信?"
参考答案:
// 1. 状态提升到共同父组件
function Parent() {
const [sharedData, setSharedData] = useState('');
return (
<div>
<SiblingA onDataChange={setSharedData} />
<SiblingB data={sharedData} />
</div>
);
}
function SiblingA({ onDataChange }) {
return (
<input
onChange={(e) => onDataChange(e.target.value)}
placeholder="在A组件输入"
/>
);
}
function SiblingB({ data }) {
return <div>B组件接收到: {data}</div>;
}
// 2. 使用Context进行跨层级通信
const MessageContext = createContext();
function MessageProvider({ children }) {
const [messages, setMessages] = useState([]);
const sendMessage = (message) => {
setMessages(prev => [...prev, message]);
};
return (
<MessageContext.Provider value={{ messages, sendMessage }}>
{children}
</MessageContext.Provider>
);
}
// 3. 使用自定义事件总线
class EventBus {
constructor() {
this.events = {};
}
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
}
emit(event, data) {
if (this.events[event]) {
this.events[event].forEach(callback => callback(data));
}
}
off(event, callback) {
if (this.events[event]) {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
}
const eventBus = new EventBus();
// 使用自定义Hook封装
function useEventBus(event, handler) {
useEffect(() => {
eventBus.on(event, handler);
return () => eventBus.off(event, handler);
}, [event, handler]);
return eventBus.emit.bind(eventBus, event);
}
// 组件中使用
function ComponentA() {
const emit = useEventBus('dataUpdate', () => {});
return (
<button onClick={() => emit({ data: 'from A' })}>
发送数据
</button>
);
}
function ComponentB() {
const [data, setData] = useState('');
useEventBus('dataUpdate', (payload) => {
setData(payload.data);
});
return <div>接收到: {data}</div>;
}
15. Redux vs Context API
面试官提问: "什么时候应该使用Redux,什么时候Context API就足够了?"
参考答案:
Context API适用场景:
- 中小型应用
- 状态变化不频繁
- 状态结构简单
- 不需要时间旅行调试
// Context API示例
const AppContext = createContext();
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [theme, setTheme] = useState('light');
const [notifications, setNotifications] = useState([]);
const value = {
user,
theme,
notifications,
login: async (credentials) => {
const user = await api.login(credentials);
setUser(user);
},
logout: () => setUser(null),
toggleTheme: () => setTheme(t => t === 'light' ? 'dark' : 'light'),
addNotification: (notification) => {
setNotifications(prev => [...prev, notification]);
}
};
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}
Redux适用场景:
- 大型应用
- 复杂的状态逻辑
- 需要可预测的状态更新
- 需要强大的开发工具
// Redux示例
// store.js
import { configureStore } from '@reduxjs/toolkit';
import userReducer from './userSlice';
import notificationReducer from './notificationSlice';
export const store = configureStore({
reducer: {
user: userReducer,
notifications: notificationReducer,
},
});
// userSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
export const loginUser = createAsyncThunk(
'user/login',
async (credentials) => {
const response = await api.login(credentials);
return response.data;
}
);
const userSlice = createSlice({
name: 'user',
initialState: {
data: null,
loading: false,
error: null,
},
reducers: {
logout: (state) => {
state.data = null;
},
},
extraReducers: (builder) => {
builder
.addCase(loginUser.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(loginUser.fulfilled, (state, action) => {
state.loading = false;
state.data = action.payload;
})
.addCase(loginUser.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message;
});
},
});
export const { logout } = userSlice.actions;
export default userSlice.reducer;
// 组件中使用
import { useSelector, useDispatch } from 'react-redux';
function LoginComponent() {
const dispatch = useDispatch();
const { data: user, loading, error } = useSelector(state => state.user);
const handleLogin = (credentials) => {
dispatch(loginUser(credentials));
};
return (
// UI代码
);
}
16. 状态管理最佳实践
面试官提问: "分享一些React状态管理的最佳实践。"
参考答案:
// 1. 状态正规化
// 不好的做法
const [posts, setPosts] = useState([
{
id: 1,
title: 'Post 1',
author: { id: 1, name: 'John', avatar: '...' },
comments: [
{ id: 1, text: 'Nice!', author: { id: 2, name: 'Jane' } }
]
}
]);
// 好的做法 - 正规化数据
const [state, setState] = useState({
posts: {
byId: {
1: { id: 1, title: 'Post 1', authorId: 1, commentIds: [1] }
},
allIds: [1]
},
users: {
byId: {
1: { id: 1, name: 'John', avatar: '...' },
2: { id: 2, name: 'Jane' }
},
allIds: [1, 2]
},
comments: {
byId: {
1: { id: 1, text: 'Nice!', authorId: 2 }
},
allIds: [1]
}
});
// 2. 派生状态 vs 存储状态
function TodoList() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
// 派生状态 - 不要存储在state中
const filteredTodos = useMemo(() => {
switch (filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
}, [todos, filter]);
// 不好的做法
// const [filteredTodos, setFilteredTodos] = useState([]);
// useEffect(() => {
// setFilteredTodos(/* 过滤逻辑 */);
// }, [todos, filter]);
}
// 3. 状态分组
// 相关状态组合在一起
const [formData, setFormData] = useState({
name: '',
email: '',
phone: ''
});
// 而不是
// const [name, setName] = useState('');
// const [email, setEmail] = useState('');
// const [phone, setPhone] = useState('');
// 4. 使用reducer管理复杂状态
function useComplexState() {
const [state, dispatch] = useReducer(
(state, action) => {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true, error: null };
case 'FETCH_SUCCESS':
return {
...state,
loading: false,
data: action.payload,
lastFetch: Date.now()
};
case 'FETCH_ERROR':
return { ...state, loading: false, error: action.error };
default:
return state;
}
},
{ data: null, loading: false, error: null, lastFetch: null }
);
return [state, dispatch];
}
React高级特性
17. 高阶组件(HOC)
面试官提问: "解释什么是高阶组件,以及如何实现一个HOC?"
参考答案:
高阶组件是接受组件作为参数并返回新组件的函数。
// 基础HOC示例 - 添加日志功能
function withLogging(WrappedComponent) {
return function LoggingComponent(props) {
useEffect(() => {
console.log(`${WrappedComponent.name} mounted`);
return () => console.log(`${WrappedComponent.name} unmounted`);
}, []);
return <WrappedComponent {...props} />;
};
}
// 使用HOC
const Button = ({ onClick, children }) => (
<button onClick={onClick}>{children}</button>
);
const LoggedButton = withLogging(Button);
// 更复杂的HOC - 权限控制
function withAuth(WrappedComponent, requiredRole) {
return function AuthComponent(props) {
const { user } = useContext(AuthContext);
const navigate = useNavigate();
useEffect(() => {
if (!user || (requiredRole && user.role !== requiredRole)) {
navigate('/login');
}
}, [user, navigate]);
if (!user) {
return <div>加载中...</div>;
}
if (requiredRole && user.role !== requiredRole) {
return <div>无权访问</div>;
}
return <WrappedComponent {...props} user={user} />;
};
}
// 使用权限HOC
const AdminDashboard = withAuth(Dashboard, 'admin');
// HOC组合
function compose(...hocs) {
return (Component) =>
hocs.reduceRight((acc, hoc) => hoc(acc), Component);
}
const EnhancedComponent = compose(
withAuth,
withLogging,
withErrorBoundary
)(BaseComponent);
// 传递ref的HOC
function withForwardRef(Component) {
const ForwardRefComponent = React.forwardRef((props, ref) => {
return <Component {...props} forwardedRef={ref} />;
});
ForwardRefComponent.displayName = `withForwardRef(${Component.displayName || Component.name})`;
return ForwardRefComponent;
}
18. Render Props模式
面试官提问: "什么是Render Props模式?请给出一个实例。"
参考答案:
Render Props是一种在组件之间共享逻辑的技术。
// 鼠标位置追踪器
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
};
render() {
return (
<div onMouseMove={this.handleMouseMove} style={{ height: '100vh' }}>
{this.props.render(this.state)}
</div>
);
}
}
// 使用render prop
function App() {
return (
<MouseTracker
render={({ x, y }) => (
<div>
<h1>鼠标位置: ({x}, {y})</h1>
<Cat x={x} y={y} />
</div>
)}
/>
);
}
// 使用children作为render prop
function MouseTrackerWithChildren({ children }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (event) => {
setPosition({
x: event.clientX,
y: event.clientY
});
};
return (
<div onMouseMove={handleMouseMove} style={{ height: '100vh' }}>
{children(position)}
</div>
);
}
// 使用
<MouseTrackerWithChildren>
{({ x, y }) => <div>坐标: {x}, {y}</div>}
</MouseTrackerWithChildren>
// 更实用的例子 - 数据获取
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then(res => res.json())
.then(data => {
setData(data);
setLoading(false);
})
.catch(error => {
setError(error);
setLoading(false);
});
}, [url]);
return render({ data, loading, error });
}
// 使用DataFetcher
function UserProfile({ userId }) {
return (
<DataFetcher
url={`/api/users/${userId}`}
render={({ data: user, loading, error }) => {
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}}
/>
);
}
19. Error Boundaries错误边界
面试官提问: "如何在React中优雅地处理错误?"
参考答案:
Error Boundaries是React组件,可以捕获子组件树中的JavaScript错误。
// 错误边界组件
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
errorCount: 0
};
}
static getDerivedStateFromError(error) {
// 更新state使下一次渲染显示fallback UI
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 记录错误到错误报告服务
console.error('错误边界捕获到错误:', error, errorInfo);
// 记录到状态中
this.setState(prevState => ({
error,
errorInfo,
errorCount: prevState.errorCount + 1
}));
// 上报错误
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
// 发送到错误追踪服务
// logErrorToService(error, errorInfo);
}
resetError = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null
});
};
render() {
if (this.state.hasError) {
// 自定义fallback UI
if (this.props.fallback) {
return this.props.fallback(
this.state.error,
this.state.errorInfo,
this.resetError
);
}
// 默认fallback UI
return (
<div className="error-boundary-default">
<h2>哎呀,出错了!</h2>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}
<br />
{this.state.errorInfo && this.state.errorInfo.componentStack}
</details>
<button onClick={this.resetError}>重试</button>
</div>
);
}
return this.props.children;
}
}
// 使用错误边界
function App() {
return (
<ErrorBoundary
fallback={(error, errorInfo, reset) => (
<div className="custom-error-ui">
<h1>应用出现错误</h1>
<p>{error.message}</p>
<button onClick={reset}>重新加载</button>
</div>
)}
onError={(error, errorInfo) => {
// 发送错误报告
sendErrorReport({ error, errorInfo, timestamp: Date.now() });
}}
>
<Header />
<MainContent />
<Footer />
</ErrorBoundary>
);
}
// 使用Hook实现错误处理
function useErrorHandler() {
const [error, setError] = useState(null);
const resetError = () => setError(null);
const captureError = useCallback((error) => {
setError(error);
// 记录错误
console.error('捕获到错误:', error);
}, []);
// 用于异步错误
useEffect(() => {
if (error) {
throw error;
}
}, [error]);
return { error, resetError, captureError };
}
// 在组件中使用
function RiskyComponent() {
const { captureError } = useErrorHandler();
const fetchData = async () => {
try {
const data = await api.getData();
// 处理数据
} catch (error) {
captureError(error);
}
};
return <button onClick={fetchData}>获取数据</button>;
}
20. Portal传送门
面试官提问: "什么是React Portal?它解决了什么问题?"
参考答案:
Portal提供了一种将子节点渲染到存在于父组件DOM层次之外的DOM节点的方式。
// 基础Portal示例
import { createPortal } from 'react-dom';
function Modal({ isOpen, onClose, children }) {
if (!isOpen) return null;
return createPortal(
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<button className="modal-close" onClick={onClose}>×</button>
{children}
</div>
</div>,
document.getElementById('modal-root') // 目标DOM节点
);
}
// 使用Modal
function App() {
const [showModal, setShowModal] = useState(false);
return (
<div>
<button onClick={() => setShowModal(true)}>打开模态框</button>
<Modal isOpen={showModal} onClose={() => setShowModal(false)}>
<h2>模态框标题</h2>
<p>这是模态框内容</p>
</Modal>
</div>
);
}
// 更复杂的例子 - 工具提示
function Tooltip({ children, content, position = 'top' }) {
const [isVisible, setIsVisible] = useState(false);
const [coords, setCoords] = useState({ x: 0, y: 0 });
const triggerRef = useRef();
const show = () => {
const rect = triggerRef.current.getBoundingClientRect();
const coords = calculatePosition(rect, position);
setCoords(coords);
setIsVisible(true);
};
const hide = () => setIsVisible(false);
const calculatePosition = (rect, position) => {
const offset = 10;
switch (position) {
case 'top':
return {
x: rect.left + rect.width / 2,
y: rect.top - offset
};
case 'bottom':
return {
x: rect.left + rect.width / 2,
y: rect.bottom + offset
};
// 其他位置...
default:
return { x: 0, y: 0 };
}
};
return (
<>
<span
ref={triggerRef}
onMouseEnter={show}
onMouseLeave={hide}
>
{children}
</span>
{isVisible && createPortal(
<div
className={`tooltip tooltip-${position}`}
style={{
position: 'fixed',
left: coords.x,
top: coords.y,
transform: 'translate(-50%, -100%)'
}}
>
{content}
</div>,
document.body
)}
</>
);
}
// 自定义Hook封装Portal逻辑
function usePortal(id) {
const [portalElement, setPortalElement] = useState(null);
useEffect(() => {
let element = document.getElementById(id);
let created = false;
if (!element) {
created = true;
element = document.createElement('div');
element.setAttribute('id', id);
document.body.appendChild(element);
}
setPortalElement(element);
return () => {
if (created && element.parentNode) {
element.parentNode.removeChild(element);
}
};
}, [id]);
return portalElement;
}
// 使用自定义Hook
function Notification({ message, type }) {
const portalElement = usePortal('notification-root');
if (!portalElement) return null;
return createPortal(
<div className={`notification notification-${type}`}>
{message}
</div>,
portalElement
);
}
21. Ref和forwardRef
面试官提问: "解释React中Ref的使用场景和forwardRef的作用。"
参考答案:
// 1. 基础Ref使用
function TextInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
const selectText = () => {
inputRef.current.select();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>聚焦</button>
<button onClick={selectText}>选中文本</button>
</div>
);
}
// 2. 存储可变值
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => {
clearInterval(intervalRef.current);
};
}, []);
const stopTimer = () => {
clearInterval(intervalRef.current);
};
return (
<div>
<p>计时: {count}秒</p>
<button onClick={stopTimer}>停止</button>
</div>
);
}
// 3. forwardRef传递ref
const FancyInput = React.forwardRef((props, ref) => {
const localRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
localRef.current.focus();
},
clear: () => {
localRef.current.value = '';
},
getValue: () => {
return localRef.current.value;
}
}));
return (
<div className="fancy-input-wrapper">
<input
ref={localRef}
className="fancy-input"
{...props}
/>
</div>
);
});
// 使用forwardRef组件
function Parent() {
const inputRef = useRef();
const handleSubmit = () => {
const value = inputRef.current.getValue();
console.log('输入值:', value);
inputRef.current.clear();
};
return (
<div>
<FancyInput ref={inputRef} placeholder="请输入..." />
<button onClick={handleSubmit}>提交</button>
</div>
);
}
// 4. 回调Ref
function MeasuredComponent() {
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const measureRef = useCallback(node => {
if (node !== null) {
const { width, height } = node.getBoundingClientRect();
setDimensions({ width, height });
}
}, []);
return (
<div>
<div ref={measureRef} style={{ background: 'lightblue', padding: 20 }}>
测量这个元素
</div>
<p>宽度: {dimensions.width}px, 高度: {dimensions.height}px</p>
</div>
);
}
// 5. 多个ref的处理
function MultiRefComponent() {
const refs = useRef({});
const setRef = (id) => (el) => {
refs.current[id] = el;
};
const focusById = (id) => {
refs.current[id]?.focus();
};
return (
<div>
{['input1', 'input2', 'input3'].map(id => (
<div key={id}>
<input ref={setRef(id)} placeholder={id} />
<button onClick={() => focusById(id)}>聚焦{id}</button>
</div>
))}
</div>
);
}
React Router
22. React Router基础
面试官提问: "介绍一下React Router的核心概念和使用方法。"
参考答案:
// 1. 基础路由设置
import {
BrowserRouter,
Routes,
Route,
Link,
NavLink,
Navigate,
Outlet
} from 'react-router-dom';
function App() {
return (
<BrowserRouter>
<nav>
<NavLink to="/" className={({ isActive }) => isActive ? 'active' : ''}>
首页
</NavLink>
<NavLink to="/about">关于</NavLink>
<NavLink to="/users">用户</NavLink>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users" element={<Users />}>
<Route index element={<UserList />} />
<Route path=":userId" element={<UserDetail />} />
<Route path="new" element={<NewUser />} />
</Route>
<Route path="/old-path" element={<Navigate to="/new-path" replace />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
// 2. 嵌套路由
function Users() {
return (
<div>
<h1>用户管理</h1>
<nav>
<Link to="/users">用户列表</Link>
<Link to="/users/new">新建用户</Link>
</nav>
<Outlet /> {/* 子路由渲染位置 */}
</div>
);
}
// 3. 动态路由和参数
import { useParams, useSearchParams, useLocation } from 'react-router-dom';
function UserDetail() {
const { userId } = useParams();
const [searchParams, setSearchParams] = useSearchParams();
const location = useLocation();
const tab = searchParams.get('tab') || 'profile';
return (
<div>
<h2>用户ID: {userId}</h2>
<p>当前路径: {location.pathname}</p>
<div>
<button onClick={() => setSearchParams({ tab: 'profile' })}>
资料
</button>
<button onClick={() => setSearchParams({ tab: 'posts' })}>
帖子
</button>
</div>
{tab === 'profile' && <UserProfile userId={userId} />}
{tab === 'posts' && <UserPosts userId={userId} />}
</div>
);
}
// 4. 编程式导航
import { useNavigate } from 'react-router-dom';
function LoginForm() {
const navigate = useNavigate();
const location = useLocation();
const handleLogin = async (credentials) => {
try {
await api.login(credentials);
// 登录成功后跳转
const from = location.state?.from?.pathname || '/dashboard';
navigate(from, { replace: true });
} catch (error) {
console.error('登录失败:', error);
}
};
return (
<form onSubmit={handleLogin}>
{/* 表单内容 */}
</form>
);
}
// 5. 路由守卫
function ProtectedRoute({ children }) {
const { user } = useAuth();
const location = useLocation();
if (!user) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 使用路由守卫
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
// 6. 路由配置数组
const routes = [
{
path: '/',
element: <Layout />,
children: [
{ index: true, element: <Home /> },
{
path: 'courses',
element: <Courses />,
children: [
{ index: true, element: <CourseList /> },
{ path: ':courseId', element: <Course /> }
]
},
{
path: 'dashboard',
element: <ProtectedRoute><Dashboard /></ProtectedRoute>,
children: [
{ path: 'stats', element: <Stats /> },
{ path: 'settings', element: <Settings /> }
]
}
]
}
];
// 使用useRoutes Hook
import { useRoutes } from 'react-router-dom';
function App() {
const element = useRoutes(routes);
return element;
}
面试官追问: "如何实现路由级别的代码分割?"
深入回答:
// 路由懒加载
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
const Profile = lazy(() => import('./pages/Profile'));
function App() {
return (
<BrowserRouter>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</Suspense>
</BrowserRouter>
);
}
// 带错误边界的路由懒加载
function LazyBoundary({ children }) {
return (
<ErrorBoundary fallback={<ErrorPage />}>
<Suspense fallback={<LoadingSpinner />}>
{children}
</Suspense>
</ErrorBoundary>
);
}
// 预加载路由组件
const preloadDashboard = () => import('./pages/Dashboard');
// 在合适的时机预加载
<Link to="/dashboard" onMouseEnter={preloadDashboard}>
Dashboard
</Link>
React生态系统
23. 服务端渲染(SSR)
面试官提问: "解释React SSR的原理和优势。"
参考答案:
服务端渲染是在服务器上将React组件渲染成HTML字符串,然后发送给客户端。
SSR的优势:
- 更好的SEO
- 更快的首屏加载
- 更好的用户体验
基础SSR实现:
// server.js
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom/server';
import App from './App';
const app = express();
app.get('*', (req, res) => {
const context = {};
const html = renderToString(
<StaticRouter location={req.url} context={context}>
<App />
</StaticRouter>
);
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>React SSR</title>
</head>
<body>
<div id="root">${html}</div>
<script src="/client.js"></script>
</body>
</html>
`);
});
// client.js
import { hydrateRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
hydrateRoot(
document.getElementById('root'),
<BrowserRouter>
<App />
</BrowserRouter>
);
// Next.js示例
// pages/index.js
export default function Home({ posts }) {
return (
<div>
<h1>博客列表</h1>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</div>
);
}
// 服务端数据获取
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
return {
props: {
posts
}
};
}
// 静态生成
export async function getStaticProps() {
const posts = await fetchPosts();
return {
props: {
posts
},
revalidate: 3600 // 每小时重新生成
};
}
24. 测试React组件
面试官提问: "如何测试React组件?分享一些测试最佳实践。"
参考答案:
// 1. 使用React Testing Library进行单元测试
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
// 组件
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (input.trim()) {
setTodos([...todos, { id: Date.now(), text: input }]);
setInput('');
}
};
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="添加待办事项"
/>
<button onClick={addTodo}>添加</button>
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
}
// 测试
describe('TodoList', () => {
test('添加新的待办事项', async () => {
const user = userEvent.setup();
render(<TodoList />);
const input = screen.getByPlaceholderText('添加待办事项');
const button = screen.getByText('添加');
// 输入文本
await user.type(input, '学习React测试');
// 点击添加按钮
await user.click(button);
// 验证待办事项已添加
expect(screen.getByText('学习React测试')).toBeInTheDocument();
// 验证输入框已清空
expect(input).toHaveValue('');
});
test('不添加空的待办事项', async () => {
const user = userEvent.setup();
render(<TodoList />);
const button = screen.getByText('添加');
await user.click(button);
const listItems = screen.queryAllByRole('listitem');
expect(listItems).toHaveLength(0);
});
});
// 2. 测试异步组件
const server = setupServer(
rest.get('/api/users/:id', (req, res, ctx) => {
const { id } = req.params;
return res(
ctx.json({
id,
name: 'John Doe',
email: 'john@example.com'
})
);
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, [userId]);
if (loading) return <div>加载中...</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
test('加载并显示用户信息', async () => {
render(<UserProfile userId="123" />);
// 初始显示加载状态
expect(screen.getByText('加载中...')).toBeInTheDocument();
// 等待用户信息加载
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeInTheDocument();
});
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
// 3. 测试自定义Hook
import { renderHook, act } from '@testing-library/react';
function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue);
const increment = () => setCount(c => c + 1);
const decrement = () => setCount(c => c - 1);
const reset = () => setCount(initialValue);
return { count, increment, decrement, reset };
}
test('useCounter Hook', () => {
const { result } = renderHook(() => useCounter(10));
expect(result.current.count).toBe(10);
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(11);
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(10);
act(() => {
result.current.reset();
});
expect(result.current.count).toBe(10);
});
// 4. 集成测试
test('完整的用户流程', async () => {
const user = userEvent.setup();
const { container } = render(<App />);
// 登录
const loginButton = screen.getByText('登录');
await user.click(loginButton);
const usernameInput = screen.getByLabelText('用户名');
const passwordInput = screen.getByLabelText('密码');
await user.type(usernameInput, 'testuser');
await user.type(passwordInput, 'password123');
const submitButton = screen.getByText('提交');
await user.click(submitButton);
// 验证登录成功
await waitFor(() => {
expect(screen.getByText('欢迎, testuser')).toBeInTheDocument();
});
// 创建快照
expect(container).toMatchSnapshot();
});
25. React未来趋势
面试官提问: "你如何看待React的未来发展趋势?"
参考答案:
1. React Server Components
// 服务器组件示例
// Note.server.js
import db from './db';
async function Note({ id }) {
const note = await db.notes.get(id);
return (
<div>
<h1>{note.title}</h1>
<p>{note.content}</p>
</div>
);
}
// 客户端组件
// LikeButton.client.js
'use client';
function LikeButton({ noteId }) {
const [likes, setLikes] = useState(0);
return (
<button onClick={() => setLikes(likes + 1)}>
点赞 ({likes})
</button>
);
}
2. Concurrent Features
// 并发特性
import { useTransition, useDeferredValue } from 'react';
function SearchResults({ query }) {
const [isPending, startTransition] = useTransition();
const [results, setResults] = useState([]);
const handleSearch = (newQuery) => {
// 紧急更新 - 立即显示输入
setQuery(newQuery);
// 非紧急更新 - 可以延迟
startTransition(() => {
setResults(performSearch(newQuery));
});
};
const deferredQuery = useDeferredValue(query);
return (
<div>
<input onChange={(e) => handleSearch(e.target.value)} />
{isPending && <div>搜索中...</div>}
<ResultsList results={results} />
</div>
);
}
3. 新的Hook和API
// useId - 生成唯一ID
function FormField() {
const id = useId();
return (
<>
<label htmlFor={id}>名称:</label>
<input id={id} type="text" />
</>
);
}
// useSyncExternalStore - 订阅外部数据源
function useOnlineStatus() {
return useSyncExternalStore(
subscribe,
getSnapshot,
getServerSnapshot
);
}
面试官追问: "你认为React未来会如何发展?"
深入回答:
更好的开发体验
- 更智能的错误提示
- 更好的TypeScript支持
- 更强大的开发工具
性能持续优化
- 自动批处理
- 更智能的代码分割
- 更小的包体积
全栈框架整合
- Next.js、Remix等框架的深度整合
- 更好的SSR/SSG支持
- 边缘计算支持
新的编程模式
- 服务器组件成为主流
- 更多的并发特性
- 更好的状态管理方案
总结
这份React面试指南涵盖了从基础到高级的各个方面,包括:
- 基础概念 - React核心原理、组件、JSX、Props/State
- Hooks - 所有重要的Hook及其使用场景
- 性能优化 - 各种优化技术和最佳实践
- 状态管理 - 从简单到复杂的状态管理方案
- 高级特性 - HOC、Render Props、Error Boundaries等
- 路由 - React Router的完整使用
- 生态系统 - SSR、测试、未来趋势
掌握这些知识点,不仅能帮助你通过React面试,更重要的是能让你成为一个优秀的React开发者。记住,理解原理比死记硬背更重要,能够根据实际场景选择合适的解决方案才是真正的能力体现。