React Hooks 深度解析面试题
React Hooks 是现代React开发的核心,涵盖状态管理、副作用处理和自定义逻辑封装等关键概念。
🔥 核心Hooks面试题
1. useState Hook 深度理解
问题:详细解释useState的工作原理,以及函数式更新和批处理机制。
参考答案:
jsx
import React, { useState, useEffect } from 'react';
function CounterComponent() {
const [count, setCount] = useState(0);
const [user, setUser] = useState({ name: '', email: '' });
// 函数式更新 - 避免闭包陷阱
const increment = () => {
setCount(prevCount => prevCount + 1);
};
// 批处理示例
const handleMultipleUpdates = () => {
// React 18+: 自动批处理
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);
// 只会触发一次重新渲染
};
// 对象状态更新
const updateUser = (field, value) => {
setUser(prevUser => ({
...prevUser,
[field]: value
}));
};
// 惰性初始状态
const [expensiveValue] = useState(() => {
console.log('计算昂贵的初始值');
return computeExpensiveValue();
});
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>增加</button>
<button onClick={handleMultipleUpdates}>批量更新</button>
<input
value={user.name}
onChange={(e) => updateUser('name', e.target.value)}
placeholder="Name"
/>
</div>
);
}
function computeExpensiveValue() {
// 模拟昂贵计算
let result = 0;
for (let i = 0; i < 1000000; i++) {
result += i;
}
return result;
}关键点:
- useState返回状态值和更新函数
- 函数式更新避免闭包问题
- React 18+ 自动批处理更新
- 惰性初始化优化性能
2. useEffect Hook 完全指南
问题:useEffect的执行时机、依赖数组和清理函数的正确使用方式?
参考答案:
jsx
import React, { useState, useEffect, useRef } from 'react';
function EffectExamples() {
const [count, setCount] = useState(0);
const [user, setUser] = useState(null);
const intervalRef = useRef();
// 1. 组件挂载时执行(componentDidMount)
useEffect(() => {
console.log('组件挂载');
fetchUserData();
// 清理函数(componentWillUnmount)
return () => {
console.log('组件卸载清理');
};
}, []); // 空依赖数组
// 2. count变化时执行(componentDidUpdate)
useEffect(() => {
console.log(`Count 更新为: ${count}`);
document.title = `Count: ${count}`;
}, [count]); // 依赖count
// 3. 订阅和取消订阅
useEffect(() => {
const subscription = subscribeToSomething();
return () => {
subscription.unsubscribe();
};
}, []);
// 4. 定时器管理
useEffect(() => {
intervalRef.current = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => {
clearInterval(intervalRef.current);
};
}, []); // 不依赖count,使用函数式更新
// 5. 异步操作处理
useEffect(() => {
let cancelled = false;
async function fetchData() {
try {
const response = await fetch('/api/user');
const userData = await response.json();
if (!cancelled) {
setUser(userData);
}
} catch (error) {
if (!cancelled) {
console.error('获取用户数据失败:', error);
}
}
}
fetchData();
return () => {
cancelled = true;
};
}, []);
// 6. 自定义Hook封装
const useWindowSize = () => {
const [windowSize, setWindowSize] = useState({
width: undefined,
height: undefined,
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
}
window.addEventListener('resize', handleResize);
handleResize(); // 立即调用一次
return () => window.removeEventListener('resize', handleResize);
}, []);
return windowSize;
};
const windowSize = useWindowSize();
return (
<div>
<p>Count: {count}</p>
<p>Window: {windowSize.width}x{windowSize.height}</p>
{user && <p>User: {user.name}</p>}
</div>
);
}
async function fetchUserData() {
// 模拟API调用
}
function subscribeToSomething() {
return {
unsubscribe: () => console.log('取消订阅')
};
}3. useContext Hook 和全局状态
问题:如何使用Context API进行全局状态管理,以及性能优化策略?
参考答案:
jsx
import React, { createContext, useContext, useReducer, useMemo } from 'react';
// 1. 创建Context
const AppStateContext = createContext();
const AppDispatchContext = createContext();
// 2. State和Action定义
const initialState = {
user: null,
theme: 'light',
notifications: [],
loading: false
};
function appReducer(state, action) {
switch (action.type) {
case 'SET_USER':
return { ...state, user: action.payload };
case 'SET_THEME':
return { ...state, theme: action.payload };
case 'ADD_NOTIFICATION':
return {
...state,
notifications: [...state.notifications, action.payload]
};
case 'SET_LOADING':
return { ...state, loading: action.payload };
default:
return state;
}
}
// 3. Provider组件
function AppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, initialState);
// 性能优化:分离state和dispatch context
const stateValue = useMemo(() => state, [state]);
const dispatchValue = useMemo(() => dispatch, [dispatch]);
return (
<AppStateContext.Provider value={stateValue}>
<AppDispatchContext.Provider value={dispatchValue}>
{children}
</AppDispatchContext.Provider>
</AppStateContext.Provider>
);
}
// 4. 自定义Hook封装
function useAppState() {
const context = useContext(AppStateContext);
if (context === undefined) {
throw new Error('useAppState必须在AppProvider内使用');
}
return context;
}
function useAppDispatch() {
const context = useContext(AppDispatchContext);
if (context === undefined) {
throw new Error('useAppDispatch必须在AppProvider内使用');
}
return context;
}
// 5. 业务Hook封装
function useAuth() {
const { user, loading } = useAppState();
const dispatch = useAppDispatch();
const login = async (credentials) => {
dispatch({ type: 'SET_LOADING', payload: true });
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials)
});
const userData = await response.json();
dispatch({ type: 'SET_USER', payload: userData });
} catch (error) {
dispatch({
type: 'ADD_NOTIFICATION',
payload: { type: 'error', message: '登录失败' }
});
} finally {
dispatch({ type: 'SET_LOADING', payload: false });
}
};
const logout = () => {
dispatch({ type: 'SET_USER', payload: null });
};
return { user, loading, login, logout };
}
// 6. 主题Hook
function useTheme() {
const { theme } = useAppState();
const dispatch = useAppDispatch();
const toggleTheme = () => {
const newTheme = theme === 'light' ? 'dark' : 'light';
dispatch({ type: 'SET_THEME', payload: newTheme });
};
return { theme, toggleTheme };
}
// 7. 组件使用
function App() {
return (
<AppProvider>
<Header />
<MainContent />
<Footer />
</AppProvider>
);
}
function Header() {
const { user } = useAuth();
const { theme, toggleTheme } = useTheme();
return (
<header className={`header ${theme}`}>
<h1>应用标题</h1>
{user ? (
<p>欢迎, {user.name}!</p>
) : (
<LoginForm />
)}
<button onClick={toggleTheme}>
切换主题
</button>
</header>
);
}
function LoginForm() {
const { login, loading } = useAuth();
const [credentials, setCredentials] = useState({
username: '',
password: ''
});
const handleSubmit = (e) => {
e.preventDefault();
login(credentials);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="用户名"
value={credentials.username}
onChange={(e) => setCredentials(prev => ({
...prev,
username: e.target.value
}))}
/>
<input
type="password"
placeholder="密码"
value={credentials.password}
onChange={(e) => setCredentials(prev => ({
...prev,
password: e.target.value
}))}
/>
<button type="submit" disabled={loading}>
{loading ? '登录中...' : '登录'}
</button>
</form>
);
}4. useReducer Hook 复杂状态管理
问题:什么时候使用useReducer而不是useState,以及如何设计复杂的reducer?
参考答案:
jsx
import React, { useReducer, useEffect, useCallback } from 'react';
// 1. 复杂表单状态管理
const formInitialState = {
values: {},
errors: {},
touched: {},
isSubmitting: false,
isValid: false
};
function formReducer(state, action) {
switch (action.type) {
case 'SET_FIELD_VALUE':
return {
...state,
values: {
...state.values,
[action.field]: action.value
}
};
case 'SET_FIELD_TOUCHED':
return {
...state,
touched: {
...state.touched,
[action.field]: true
}
};
case 'SET_FIELD_ERROR':
return {
...state,
errors: {
...state.errors,
[action.field]: action.error
}
};
case 'SET_SUBMITTING':
return {
...state,
isSubmitting: action.isSubmitting
};
case 'RESET_FORM':
return formInitialState;
default:
return state;
}
}
function useForm(validationRules = {}) {
const [state, dispatch] = useReducer(formReducer, formInitialState);
const setFieldValue = useCallback((field, value) => {
dispatch({ type: 'SET_FIELD_VALUE', field, value });
// 验证字段
if (validationRules[field]) {
const error = validationRules[field](value);
dispatch({ type: 'SET_FIELD_ERROR', field, error });
}
}, [validationRules]);
const setFieldTouched = useCallback((field) => {
dispatch({ type: 'SET_FIELD_TOUCHED', field });
}, []);
const handleSubmit = useCallback((onSubmit) => {
return async (e) => {
e.preventDefault();
dispatch({ type: 'SET_SUBMITTING', isSubmitting: true });
try {
await onSubmit(state.values);
} catch (error) {
console.error('表单提交失败:', error);
} finally {
dispatch({ type: 'SET_SUBMITTING', isSubmitting: false });
}
};
}, [state.values]);
return {
...state,
setFieldValue,
setFieldTouched,
handleSubmit
};
}
// 2. 异步数据获取状态
const asyncInitialState = {
data: null,
loading: false,
error: null
};
function asyncReducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return {
...state,
loading: true,
error: null
};
case 'FETCH_SUCCESS':
return {
...state,
loading: false,
data: action.payload,
error: null
};
case 'FETCH_ERROR':
return {
...state,
loading: false,
error: action.error
};
default:
return state;
}
}
function useAsyncData(fetchFunction, deps = []) {
const [state, dispatch] = useReducer(asyncReducer, asyncInitialState);
const fetchData = useCallback(async () => {
dispatch({ type: 'FETCH_START' });
try {
const data = await fetchFunction();
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_ERROR', error });
}
}, [fetchFunction]);
useEffect(() => {
fetchData();
}, deps);
return { ...state, refetch: fetchData };
}
// 3. 购物车状态管理
const cartInitialState = {
items: [],
total: 0,
discount: 0,
tax: 0
};
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
const existingItem = state.items.find(item => item.id === action.item.id);
if (existingItem) {
return {
...state,
items: state.items.map(item =>
item.id === action.item.id
? { ...item, quantity: item.quantity + 1 }
: item
)
};
}
return {
...state,
items: [...state.items, { ...action.item, quantity: 1 }]
};
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(item => item.id !== action.itemId)
};
case 'UPDATE_QUANTITY':
return {
...state,
items: state.items.map(item =>
item.id === action.itemId
? { ...item, quantity: action.quantity }
: item
)
};
case 'CLEAR_CART':
return cartInitialState;
default:
return state;
}
}
function useCart() {
const [state, dispatch] = useReducer(cartReducer, cartInitialState);
// 计算总价
const calculatedTotal = state.items.reduce(
(total, item) => total + (item.price * item.quantity), 0
);
const addItem = (item) => dispatch({ type: 'ADD_ITEM', item });
const removeItem = (itemId) => dispatch({ type: 'REMOVE_ITEM', itemId });
const updateQuantity = (itemId, quantity) =>
dispatch({ type: 'UPDATE_QUANTITY', itemId, quantity });
const clearCart = () => dispatch({ type: 'CLEAR_CART' });
return {
...state,
total: calculatedTotal,
addItem,
removeItem,
updateQuantity,
clearCart
};
}
// 使用示例
function ShoppingApp() {
const { items, total, addItem, removeItem } = useCart();
const { values, setFieldValue, handleSubmit } = useForm({
email: (value) => !value ? '邮箱必填' : null
});
return (
<div>
<h2>购物车</h2>
<div>总计: ¥{total}</div>
{items.map(item => (
<div key={item.id}>
{item.name} x {item.quantity}
<button onClick={() => removeItem(item.id)}>删除</button>
</div>
))}
</div>
);
}💡 自定义Hooks设计
5. 自定义Hooks最佳实践
问题:如何设计可复用的自定义Hooks,以及常见的设计模式?
参考答案:
jsx
import { useState, useEffect, useCallback, useRef } from 'react';
// 1. 防抖Hook
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
// 2. 本地存储Hook
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error reading localStorage key "${key}":`, error);
return initialValue;
}
});
const setValue = useCallback((value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error setting localStorage key "${key}":`, error);
}
}, [key, storedValue]);
return [storedValue, setValue];
}
// 3. 网络状态Hook
function useOnlineStatus() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOnline(true);
const handleOffline = () => setIsOnline(false);
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
};
}, []);
return isOnline;
}
// 4. API请求Hook
function useApi(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const fetchData = useCallback(async () => {
setLoading(true);
setError(null);
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, [url, options]);
useEffect(() => {
fetchData();
}, [fetchData]);
return { data, loading, error, refetch: fetchData };
}
// 5. 表单验证Hook
function useValidation(initialValues, validationRules) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const validateField = useCallback((name, value) => {
if (validationRules[name]) {
const error = validationRules[name](value, values);
setErrors(prev => ({ ...prev, [name]: error }));
return !error;
}
return true;
}, [validationRules, values]);
const handleChange = useCallback((name, value) => {
setValues(prev => ({ ...prev, [name]: value }));
if (touched[name]) {
validateField(name, value);
}
}, [touched, validateField]);
const handleBlur = useCallback((name) => {
setTouched(prev => ({ ...prev, [name]: true }));
validateField(name, values[name]);
}, [validateField, values]);
const validateAll = useCallback(() => {
const newErrors = {};
let isValid = true;
Object.keys(validationRules).forEach(name => {
const error = validationRules[name](values[name], values);
if (error) {
newErrors[name] = error;
isValid = false;
}
});
setErrors(newErrors);
setTouched(Object.keys(validationRules).reduce((acc, name) => ({
...acc,
[name]: true
}), {}));
return isValid;
}, [validationRules, values]);
return {
values,
errors,
touched,
handleChange,
handleBlur,
validateAll
};
}
// 使用示例
function UserForm() {
const isOnline = useOnlineStatus();
const [theme, setTheme] = useLocalStorage('theme', 'light');
const { values, errors, touched, handleChange, handleBlur, validateAll } = useValidation(
{ email: '', password: '' },
{
email: (value) => !value ? '邮箱必填' :
!/\S+@\S+\.\S+/.test(value) ? '邮箱格式错误' : null,
password: (value) => !value ? '密码必填' :
value.length < 6 ? '密码至少6位' : null
}
);
const handleSubmit = (e) => {
e.preventDefault();
if (validateAll()) {
console.log('表单提交:', values);
}
};
return (
<div className={theme}>
<div>网络状态: {isOnline ? '在线' : '离线'}</div>
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换主题
</button>
<form onSubmit={handleSubmit}>
<input
type="email"
placeholder="邮箱"
value={values.email}
onChange={(e) => handleChange('email', e.target.value)}
onBlur={() => handleBlur('email')}
/>
{touched.email && errors.email && <div>{errors.email}</div>}
<input
type="password"
placeholder="密码"
value={values.password}
onChange={(e) => handleChange('password', e.target.value)}
onBlur={() => handleBlur('password')}
/>
{touched.password && errors.password && <div>{errors.password}</div>}
<button type="submit">提交</button>
</form>
</div>
);
}这些React Hooks深度解析面试题涵盖了现代React开发的核心概念和高级应用,展示了对React生态系统的全面理解。
