Skip to content

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生态系统的全面理解。

正在精进