glownight

返回

React 自定义 Hook 是什么?#

核心概念#

自定义Hook 是React的一种代码复用机制,让你可以:

  • 提取组件中的状态逻辑
  • 多个组件间共享这些逻辑
  • 保持组件简洁和可维护

简单类比#

类比说明
工具函数就像把常用工具放在工具箱里
乐高积木把常用功能封装成可复用的积木
食谱配方把做菜步骤写成配方,随时可用

基本语法#

1. 命名规则#

// 必须以 "use" 开头
function useMyHook() { }      // ✅ 正确
function myHook() { }         // ❌ 错误
javascript

2. 最简单的自定义Hook#

// useCounter.js - 计数器逻辑封装
import { useState } from 'react';

function useCounter(initialValue = 0) {
  // 使用React内置Hook
  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 };
}

export default useCounter;
javascript

3. 在组件中使用#

import useCounter from './useCounter';

function MyComponent() {
  // 使用自定义Hook,就像使用useState一样
  const { count, increment, decrement, reset } = useCounter(10);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}
javascript

项目中的实际应用#

1. useSession - 会话管理#

// src/hooks/useSession.ts
export function useSession(
    sessionManager: SessionManager,
    setSessionManager: (value: SessionManager) => void
) {
    // 路由处理
    const { sessionId } = useParams();
    const navigate = useNavigate();
    
    // 初始化逻辑
    useEffect(() => {
        if (sessionManager.sessions.length === 0) {
            // 创建默认会话...
        }
    }, []);
    
    // 会话操作
    const createNewSession = () => { /* ... */ };
    const switchSession = (id: string) => { /* ... */ };
    const deleteSession = (id: string) => { /* ... */ };
    
    return {
        currentSession,
        createNewSession,
        switchSession,
        deleteSession,
    };
}
typescript

使用场景

// ChatSidebar.tsx
function ChatSidebar() {
  const { currentSession, createNewSession, switchSession } = useSession(...);
  
  return (
    <div>
      {sessions.map(s => (
        <div key={s.id} onClick={() => switchSession(s.id)}>
          {s.title}
        </div>
      ))}
      <button onClick={createNewSession}>新建会话</button>
    </div>
  );
}
typescript

2. useRemoteChat - 远程聊天#

// src/hooks/useRemoteChat.ts
export function useRemoteChat(
  apiConfig: RemoteApiConfig,
  sessionMessages: Message[],
  updateCurrentSession: (messages: Message[]) => void
) {
  const [loading, setLoading] = useState(false);
  
  // 发送消息逻辑(包含批量更新、流式处理等)
  async function handleSend(input: SendMessageInput): Promise<void> {
    // 1. 构建请求
    // 2. 乐观更新UI
    // 3. 流式处理响应
    // 4. 批量更新机制
  }
  
  return { loading, handleSend };
}
typescript

使用场景

// ChatComposer.tsx
function ChatComposer() {
  const { loading, handleSend } = useRemoteChat(...);
  
  const onSubmit = async (text) => {
    if (loading) return;
    await handleSend({ text });
  };
  
  return (
    <form onSubmit={onSubmit}>
      <input disabled={loading} />
      <button disabled={loading}>发送</button>
    </form>
  );
}
typescript

3. useLocalStorage - 本地存储#

// src/hooks/useLocalStorage.ts
export function useLocalStorage<T>(key: string, initialValue: T) {
  // 从localStorage读取
  const [storedValue, setStoredValue] = useState<T>(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });
  
  // 更新时同步到localStorage
  const setValue = (value: T | ((val: T) => T)) => {
    const valueToStore = value instanceof Function ? value(storedValue) : value;
    setStoredValue(valueToStore);
    window.localStorage.setItem(key, JSON.stringify(valueToStore));
  };
  
  return [storedValue, setValue] as const;
}
typescript

使用场景

// 保存用户主题偏好
const [theme, setTheme] = useLocalStorage('theme', 'light');

// 保存会话数据
const [sessions, setSessions] = useLocalStorage('sessions', []);
typescript

自定义Hook的优势#

1. 代码复用#

// ❌ 不使用Hook - 重复代码
function ComponentA() {
  const [count, setCount] = useState(0);
  const increment = () => setCount(c => c + 1);
  // ...
}

function ComponentB() {
  const [count, setCount] = useState(0);
  const increment = () => setCount(c => c + 1);
  // ... 重复的逻辑
}

// ✅ 使用自定义Hook - 复用逻辑
function useCounter() {
  const [count, setCount] = useState(0);
  const increment = () => setCount(c => c + 1);
  return { count, increment };
}

function ComponentA() {
  const { count, increment } = useCounter();
}

function ComponentB() {
  const { count, increment } = useCounter();
}
javascript

2. 逻辑分离#

// ❌ 组件臃肿
function ChatComponent() {
  // 状态管理 - 50行
  // API调用 - 80行
  // 错误处理 - 30行
  // UI渲染 - 20行
  // 总共180行,难以维护
}

// ✅ 逻辑分离
function useChatLogic() {
  // 所有业务逻辑封装在这里
  return { messages, sendMessage, loading, error };
}

function ChatComponent() {
  const { messages, sendMessage, loading } = useChatLogic();
  // 只关注UI渲染,简洁清晰
}
javascript

3. 测试友好#

// 可以单独测试Hook逻辑
test('useCounter', () => {
  const { result } = renderHook(() => useCounter(10));
  
  act(() => result.current.increment());
  
  expect(result.current.count).toBe(11);
});
javascript

常用自定义Hook模式#

1. 数据获取#

function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, [url]);
  
  return { data, loading, error };
}
javascript

2. 表单处理#

function useForm(initialValues) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  
  const handleChange = (e) => {
    setValues({ ...values, [e.target.name]: e.target.value });
  };
  
  const handleSubmit = (onSubmit) => (e) => {
    e.preventDefault();
    onSubmit(values);
  };
  
  return { values, errors, handleChange, handleSubmit };
}
javascript

3. 防抖/节流#

function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  
  return debouncedValue;
}

// 使用
const debouncedSearch = useDebounce(searchTerm, 500);
javascript

总结#

特性说明
命名必须以 use 开头
本质函数,可以调用其他Hook
目的复用状态逻辑
优势代码复用、逻辑分离、测试友好
规则只能在函数组件或自定义Hook中使用

自定义Hook是React中最强大的代码组织工具之一,让复杂应用保持清晰和可维护!