1. useState - 状态管理

场景:表单处理、UI 状态控制

import { useState } from 'react';

function LoginForm() {
  // 初始化表单状态
  const [formData, setFormData] = useState({
    username: '',
    password: '',
    rememberMe: false
  });

  // 处理输入变化
  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('提交表单:', formData);
    // 实际登录逻辑...
  };

  return (
    <form onSubmit={handleSubmit} className="login-form">
      <input
        type="text"
        name="username"
        value={formData.username}
        onChange={handleChange}
        placeholder="用户名"
      />
      <input
        type="password"
        name="password"
        value={formData.password}
        onChange={handleChange}
        placeholder="密码"
      />
      <label>
        <input
          type="checkbox"
          name="rememberMe"
          checked={formData.rememberMe}
          onChange={handleChange}
        />
        记住我
      </label>
      <button type="submit">登录</button>
    </form>
  );
}

2. useEffect - 副作用处理

场景:数据获取、事件监听

import { useState, useEffect } from 'react';

function UserActivityList({ userId }) {
  const [activities, setActivities] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  // 获取用户活动数据
  useEffect(() => {
    const controller = new AbortController();
    const signal = controller.signal;

    const fetchActivities = async () => {
      try {
        setLoading(true);
        const response = await fetch(
          `/api/users/${userId}/activities`,
          { signal }
        );
        
        if (!response.ok) throw new Error('数据获取失败');
        
        const data = await response.json();
        setActivities(data);
        setError(null);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchActivities();

    // 清理函数:取消请求和事件监听
    return () => {
      controller.abort();
    };
  }, [userId]); // 当userId变化时重新获取数据

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error}</div>;

  return (
    <ul className="activity-list">
      {activities.map(activity => (
        <li key={activity.id}>
          {activity.action} - {new Date(activity.time).toLocaleString()}
        </li>
      ))}
    </ul>
  );
}

3. useContext - 跨组件状态共享

场景:主题切换、用户认证状态

import { createContext, useContext, useState } from 'react';

// 创建上下文
const AuthContext = createContext();

// 提供器组件
export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);

  const login = (userData) => {
    setUser(userData);
    localStorage.setItem('user', JSON.stringify(userData));
  };

  const logout = () => {
    setUser(null);
    localStorage.removeItem('user');
  };

  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

// 自定义Hook简化使用
export function useAuth() {
  return useContext(AuthContext);
}

// 使用示例 - 登录按钮
function LoginButton() {
  const { user, login, logout } = useAuth();

  if (user) {
    return (
      <div>
        欢迎, {user.name}
        <button onClick={logout}>退出登录</button>
      </div>
    );
  }
  
  return (
    <button onClick={() => login({ id: 1, name: '用户' })}>
      登录
    </button>
  );
}

4. useReducer - 复杂状态管理

场景:购物车、多步骤表单

import { useReducer } from 'react';

// 初始状态
const initialState = {
  items: [],
  total: 0
};

//  reducer函数
function cartReducer(state, action) {
  switch (action.type) {
    case 'ADD_ITEM': {
      const existingItem = state.items.find(
        item => item.id === action.payload.id
      );
      
      let newItems;
      if (existingItem) {
        newItems = state.items.map(item =>
          item.id === action.payload.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
      } else {
        newItems = [...state.items, { ...action.payload, quantity: 1 }];
      }
      
      const total = newItems.reduce(
        (sum, item) => sum + item.price * item.quantity, 
        0
      );
      
      return { items: newItems, total };
    }
    
    case 'REMOVE_ITEM': {
      const newItems = state.items.filter(item => item.id !== action.payload);
      const total = newItems.reduce(
        (sum, item) => sum + item.price * item.quantity, 
        0
      );
      return { items: newItems, total };
    }
    
    case 'CLEAR_CART':
      return initialState;
      
    default:
      return state;
  }
}

// 购物车组件
function ShoppingCart() {
  const [state, dispatch] = useReducer(cartReducer, initialState);

  return (
    <div className="cart">
      <h2>购物车</h2>
      <button onClick={() => dispatch({
        type: 'ADD_ITEM',
        payload: { id: 1, name: '商品A', price: 99 }
      })}>
        添加商品A
      </button>
      
      <ul>
        {state.items.map(item => (
          <li key={item.id}>
            {item.name} × {item.quantity} - ¥{item.price * item.quantity}
            <button onClick={() => dispatch({
              type: 'REMOVE_ITEM',
              payload: item.id
            })}>
              移除
            </button>
          </li>
        ))}
      </ul>
      
      <p>总计: ¥{state.total}</p>
      <button onClick={() => dispatch({ type: 'CLEAR_CART' })}>
        清空购物车
      </button>
    </div>
  );
}

5. useRef - DOM 访问与状态存储

场景:输入框焦点、定时器管理

import { useRef, useState, useEffect } from 'react';

function Timer() {
  const [time, setTime] = useState(0);
  const timerRef = useRef(null); // 存储定时器ID
  const lastTimeRef = useRef(0); // 存储上一次时间

  // 开始计时
  const startTimer = () => {
    if (!timerRef.current) {
      lastTimeRef.current = Date.now() - time * 1000;
      timerRef.current = setInterval(() => {
        const now = Date.now();
        setTime(Math.floor((now - lastTimeRef.current) / 1000));
      }, 1000);
    }
  };

  // 停止计时
  const stopTimer = () => {
    clearInterval(timerRef.current);
    timerRef.current = null;
  };

  // 重置计时
  const resetTimer = () => {
    stopTimer();
    setTime(0);
  };

  // 组件卸载时清理定时器
  useEffect(() => {
    return () => clearInterval(timerRef.current);
  }, []);

  return (
    <div className="timer">
      <h2>计时器: {time}秒</h2>
      <button onClick={startTimer}>开始</button>
      <button onClick={stopTimer}>停止</button>
      <button onClick={resetTimer}>重置</button>
    </div>
  );
}

6. useCallback 与 useMemo - 性能优化

场景:优化子组件渲染、避免重复计算

import { useState, useCallback, useMemo } from 'react';
import { memo } from 'react';

// 子组件 - 使用memo避免不必要的渲染
const ExpensiveComponent = memo(({ onCalculate, items }) => {
  console.log('ExpensiveComponent 渲染了');
  return (
    <div>
      <h3>商品列表</h3>
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
      <button onClick={onCalculate}>计算总价</button>
    </div>
  );
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [products, setProducts] = useState([
    { id: 1, name: '商品1', price: 100 },
    { id: 2, name: '商品2', price: 200 }
  ]);

  // 使用useCallback记忆化函数
  const calculateTotal = useCallback(() => {
    console.log('计算总价...');
    return products.reduce((sum, product) => sum + product.price, 0);
  }, [products]); // 只有products变化时才重新创建函数

  // 使用useMemo记忆化计算结果
  const filteredProducts = useMemo(() => {
    console.log('过滤商品...');
    return products.filter(product => product.price > 150);
  }, [products]); // 只有products变化时才重新计算

  return (
    <div>
      <p>计数器: {count}</p>
      <button onClick={() => setCount(count + 1)}>增加计数</button>
      
      {/* 传递记忆化的函数和值 */}
      <ExpensiveComponent
        onCalculate={calculateTotal}
        items={filteredProducts}
      />
    </div>
  );
}

除了前面介绍的常用 Hooks,React 还提供了一些其他实用的 Hooks,以及社区中常用的自定义 Hooks 模式。以下是一些补充的 Hooks 及其用法示例:

7. useLayoutEffect - 同步 DOM 操作

特点:与 useEffect 类似,但会在 DOM 更新后同步执行,适合需要立即读取布局的场景。

适用场景:测量 DOM 尺寸、避免布局抖动

import { useState, useRef, useLayoutEffect } from 'react';

function Tooltip() {
  const [tooltipStyle, setTooltipStyle] = useState({ opacity: 0 });
  const targetRef = useRef(null);
  const tooltipRef = useRef(null);

  useLayoutEffect(() => {
    if (!targetRef.current || !tooltipRef.current) return;

    // 获取目标元素位置信息(DOM 已更新)
    const targetRect = targetRef.current.getBoundingClientRect();
    const tooltipRect = tooltipRef.current.getBoundingClientRect();

    // 计算tooltip位置(确保不超出视口)
    const left = targetRect.left + window.scrollX;
    const top = targetRect.top + window.scrollY - tooltipRect.height - 10;

    setTooltipStyle({
      opacity: 1,
      left: `${left}px`,
      top: `${top}px`,
      position: 'absolute'
    });
  }, []);

  return (
    <div style={{ position: 'relative' }}>
      <button ref={targetRef}>Hover me</button>
      <div ref={tooltipRef} style={tooltipStyle}>
        This is a tooltip
      </div>
    </div>
  );
}

8. useImperativeHandle - 自定义暴露接口

特点:自定义子组件通过 ref 暴露给父组件的方法,控制暴露的接口。

适用场景:封装组件时限制外部访问范围

import { useRef, useImperativeHandle, forwardRef } from 'react';

// 子组件 - 使用forwardRef接收ref
const TextEditor = forwardRef((props, ref) => {
  const textareaRef = useRef(null);
  
  // 自定义暴露给父组件的方法
  useImperativeHandle(ref, () => ({
    // 只暴露需要的方法
    focus: () => {
      textareaRef.current.focus();
    },
    getValue: () => {
      return textareaRef.current.value;
    }
    // 不暴露内部的textareaRef
  }));

  return <textarea ref={textareaRef} {...props} />;
});

// 父组件使用
function EditorContainer() {
  const editorRef = useRef(null);

  return (
    <div>
      <TextEditor ref={editorRef} />
      <button onClick={() => editorRef.current.focus()}>
        聚焦编辑器
      </button>
      <button onClick={() => {
        console.log('内容:', editorRef.current.getValue());
      }}>
        获取内容
      </button>
    </div>
  );
}

9. useDebugValue - 调试自定义 Hook

特点:在 React DevTools 中为自定义 Hook 显示标签,提高调试体验。

适用场景:开发共享的自定义 Hook 时

import { useState, useEffect, useDebugValue } from 'react';

// 自定义Hook - 处理数据获取
function useDataFetching(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  
  // 在DevTools中显示状态和URL
  useDebugValue(loading ? 'Loading...' : `Fetched: ${url}`);

  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      });
  }, [url]);

  return { data, loading };
}

// 使用自定义Hook
function UserList() {
  // 在React DevTools中可以看到useDataFetching的调试信息
  const { data, loading } = useDataFetching('/api/users');
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <ul>
      {data?.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

10. 自定义 Hook - 封装复用逻辑

特点:将组件逻辑提取到可重用的函数中,是 Hooks 最强大的模式之一。

适用场景:表单处理、数据订阅、权限控制等可复用逻辑

示例 1: 表单处理自定义 Hook
import { useState } from 'react';

// 自定义表单处理Hook
function useForm(initialValues, validate) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  const [touched, setTouched] = useState({});

  const handleChange = (e) => {
    const { name, value } = e.target;
    setValues(prev => ({ ...prev, [name]: value }));
  };

  const handleBlur = (e) => {
    const { name } = e.target;
    setTouched(prev => ({ ...prev, [name]: true }));
    // 验证当前字段
    if (validate) {
      const fieldErrors = validate(values);
      setErrors(prev => ({ ...prev, [name]: fieldErrors[name] }));
    }
  };

  const handleSubmit = (callback) => (e) => {
    e.preventDefault();
    // 标记所有字段为已触碰
    setTouched(Object.keys(values).reduce((acc, key) => {
      acc[key] = true;
      return acc;
    }, {}));
    
    // 完整验证
    const formErrors = validate ? validate(values) : {};
    setErrors(formErrors);
    
    // 如果没有错误,执行回调
    if (Object.keys(formErrors).length === 0) {
      callback(values);
    }
  };

  return {
    values,
    errors,
    touched,
    handleChange,
    handleBlur,
    handleSubmit
  };
}

// 使用示例
function RegistrationForm() {
  const {
    values,
    errors,
    touched,
    handleChange,
    handleBlur,
    handleSubmit
  } = useForm(
    { email: '', password: '' },
    (values) => {
      const errors = {};
      if (!values.email) errors.email = '邮箱不能为空';
      else if (!/\S+@\S+\.\S+/.test(values.email)) {
        errors.email = '邮箱格式不正确';
      }
      if (!values.password) errors.password = '密码不能为空';
      else if (values.password.length < 6) {
        errors.password = '密码长度不能少于6位';
      }
      return errors;
    }
  );

  const onSubmit = (data) => {
    console.log('提交:', data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <input
          type="email"
          name="email"
          value={values.email}
          onChange={handleChange}
          onBlur={handleBlur}
          placeholder="邮箱"
        />
        {touched.email && errors.email && (
          <span style={{ color: 'red' }}>{errors.email}</span>
        )}
      </div>
      
      <div>
        <input
          type="password"
          name="password"
          value={values.password}
          onChange={handleChange}
          onBlur={handleBlur}
          placeholder="密码"
        />
        {touched.password && errors.password && (
          <span style={{ color: 'red' }}>{errors.password}</span>
        )}
      </div>
      
      <button type="submit">注册</button>
    </form>
  );
}
示例 2: 窗口尺寸监听 Hook
import { useState, useEffect } from 'react';

// 监听窗口尺寸的自定义Hook
function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    };

    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

// 使用示例
function ResponsiveComponent() {
  const { width, height } = useWindowSize();
  const isMobile = width < 768;

  return (
    <div>
      <p>窗口尺寸: {width}x{height}</p>
      {isMobile ? (
        <p>移动端视图</p>
      ) : (
        <p>桌面端视图</p>
      )}
    </div>
  );
}
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐