架构视角:Hooks的设计哲学

React Hooks的引入是React历史上最重要的变革之一。它彻底改变了状态管理和副作用处理的方式,让函数组件拥有了类组件的所有能力,同时保持了更好的可复用性和可测试性。

Hooks核心设计原则

  • 组合优于继承:通过自定义Hook组合逻辑,而非高阶组件嵌套
  • 声明式副作用:用useEffect等Hook声明副作用,而非生命周期方法
  • 闭包捕获:每次渲染都有独立的props和state,避免this指向问题
  • 可复用逻辑:自定义Hook让状态逻辑复用变得简单直观

类组件 vs Hooks对比

// 类组件实现
class UserProfile extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      user: null,
      loading: true,
      error: null
    };
  }
  
  componentDidMount() {
    this.fetchUser();
  }
  
  componentDidUpdate(prevProps) {
    if (prevProps.userId !== this.props.userId) {
      this.fetchUser();
    }
  }
  
  componentWillUnmount() {
    this.isCancelled = true;
  }
  
  async fetchUser() {
    this.setState({ loading: true });
    try {
      const user = await api.getUser(this.props.userId);
      if (!this.isCancelled) {
        this.setState({ user, loading: false });
      }
    } catch (error) {
      if (!this.isCancelled) {
        this.setState({ error, loading: false });
      }
    }
  }
  
  render() {
    const { user, loading, error } = this.state;
    // 渲染逻辑...
  }
}

// Hooks实现
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let cancelled = false;
    
    async function fetchUser() {
      setLoading(true);
      try {
        const user = await api.getUser(userId);
        if (!cancelled) {
          setUser(user);
          setLoading(false);
        }
      } catch (error) {
        if (!cancelled) {
          setError(error);
          setLoading(false);
        }
      }
    }
    
    fetchUser();
    
    return () => { cancelled = true; };
  }, [userId]);
  
  // 渲染逻辑...
}

核心Hooks深度剖析

useState:状态管理基础

// useState工作原理与最佳实践

// 1. 基础用法
const [count, setCount] = useState(0);

// 2. 函数式更新(避免闭包问题)
setCount(prev => prev + 1);

// 3. 延迟初始化(性能优化)
const [state, setState] = useState(() => {
  // 只在初始渲染执行
  return expensiveComputation();
});

// 4. 复杂状态管理
// ❌ 多个useState
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [age, setAge] = useState(0);

// ✅ 使用useReducer或合并状态
const [user, setUser] = useState({
  firstName: '',
  lastName: '',
  age: 0
});

// 更新时保持不可变性
setUser(prev => ({ ...prev, age: prev.age + 1 }));

// 5. useState实现原理(简化版)
let hookIndex = 0;
const hooks = [];

function useState(initialValue) {
  const state = hooks[hookIndex] !== undefined 
    ? hooks[hookIndex] 
    : initialValue;
  hooks[hookIndex] = state;
  
  const _index = hookIndex;
  hookIndex++;
  
  function setState(newValue) {
    hooks[_index] = newValue;
    reRender(); // 触发重新渲染
  }
  
  return [state, setState];
}

useEffect:副作用管理

// useEffect深度理解

// 1. 依赖数组的三种模式
// 每次渲染后执行
useEffect(() => {
  console.log('Every render');
});

// 只在挂载时执行
useEffect(() => {
  console.log('Mount only');
}, []);

// 依赖变化时执行
useEffect(() => {
  console.log('UserId changed:', userId);
}, [userId]);

// 2. 清理函数
useEffect(() => {
  const subscription = api.subscribe(userId);
  
  return () => {
    // 组件卸载或依赖变化前执行
    subscription.unsubscribe();
  };
}, [userId]);

// 3. 常见副作用模式
// 数据获取
function useData(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const controller = new AbortController();
    
    fetch(url, { signal: controller.signal })
      .then(res => res.json())
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false));
    
    return () => controller.abort();
  }, [url]);
  
  return { data, loading, error };
}

// 订阅
function useSubscription(topic) {
  const [message, setMessage] = useState(null);
  
  useEffect(() => {
    const ws = new WebSocket(`wss://api.example.com/${topic}`);
    ws.onmessage = (event) => setMessage(JSON.parse(event.data));
    
    return () => ws.close();
  }, [topic]);
  
  return message;
}

// DOM操作
function useDocumentTitle(title) {
  useEffect(() => {
    document.title = title;
  }, [title]);
}

// 4. useEffect执行时机
/*
渲染阶段: render()
提交阶段: React更新DOM
浏览器绘制: 浏览器渲染页面
useEffect执行: 异步执行,不阻塞绘制

useLayoutEffect: 在浏览器绘制前同步执行
*/

// 5. 避免无限循环
// ❌ 错误:setState导致重渲染,形成循环
useEffect(() => {
  setCount(count + 1);
}, [count]);

// ✅ 正确:使用函数式更新
useEffect(() => {
  const timer = setInterval(() => {
    setCount(c => c + 1);
  }, 1000);
  return () => clearInterval(timer);
}, []); // 空依赖,只在挂载时设置

useRef:持久化引用

// useRef使用场景

// 1. 访问DOM元素
function TextInput() {
  const inputRef = useRef(null);
  
  const focus = () => {
    inputRef.current?.focus();
  };
  
  return ;
}

// 2. 保存上一次的值
function usePrevious(value: T): T | undefined {
  const ref = useRef();
  
  useEffect(() => {
    ref.current = value;
  });
  
  return ref.current;
}

// 3. 保存不触发渲染的状态
function useInterval(callback: () => void, delay: number | null) {
  const savedCallback = useRef(callback);
  
  // 记住最新的callback
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);
  
  // 设置interval
  useEffect(() => {
    if (delay === null) return;
    
    const id = setInterval(() => savedCallback.current(), delay);
    return () => clearInterval(id);
  }, [delay]);
}

// 4. 强制重新渲染
function useForceUpdate() {
  const [, setState] = useState({});
  return useCallback(() => setState({}), []);
}

// 5. 测量DOM
function useMeasure() {
  const ref = useRef(null);
  const [rect, setRect] = useState();
  
  useEffect(() => {
    if (!ref.current) return;
    
    const observer = new ResizeObserver(([entry]) => {
      setRect(entry.contentRect);
    });
    
    observer.observe(ref.current);
    return () => observer.disconnect();
  }, []);
  
  return { ref, rect };
}

useMemo与useCallback:性能优化

// 记忆化Hooks

// 1. useMemo:缓存计算结果
const expensiveValue = useMemo(() => {
  return data.filter(item => item.active)
    .map(item => processItem(item))
    .sort((a, b) => b.score - a.score);
}, [data]);

// 2. useCallback:缓存函数引用
const handleSubmit = useCallback((values: FormValues) => {
  api.submitForm(values).then(onSuccess);
}, [onSuccess]);

// 3. 何时使用?
// ✅ 使用场景:
// - 计算成本高的值
// - 传递给子组件的函数(配合React.memo)
// - 依赖数组用于其他Hooks

// ❌ 避免过度使用:
// - 简单计算
// - 不传递给子组件的函数
// - 过早优化

// 4. 与React.memo配合
const ChildComponent = React.memo(function Child({ 
  data, 
  onUpdate 
}: ChildProps) {
  // 只有data或onUpdate变化时才重新渲染
  return 
{/* ... */}
; }); function Parent() { const [count, setCount] = useState(0); const [data, setData] = useState([]); // 不使用useCallback,每次渲染都是新函数 // Child会随Parent一起渲染 const badHandleUpdate = () => { console.log('update'); }; // 使用useCallback,函数引用稳定 // 只有data变化时Child才重新渲染 const goodHandleUpdate = useCallback(() => { console.log('update', data); }, [data]); return (
); } // 5. 自定义比较函数 const MemoizedComponent = React.memo( Component, (prevProps, nextProps) => { // 返回true表示不重新渲染 return prevProps.id === nextProps.id; } );

高级Hooks与模式

useReducer:复杂状态管理

// useReducer模式

type State = {
  loading: boolean;
  data: User | null;
  error: Error | null;
};

type Action =
  | { type: 'FETCH_START' }
  | { type: 'FETCH_SUCCESS'; payload: User }
  | { type: 'FETCH_ERROR'; payload: Error }
  | { type: 'RESET' };

const initialState: State = {
  loading: false,
  data: null,
  error: null
};

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'FETCH_START':
      return { ...state, loading: true, error: null };
    case 'FETCH_SUCCESS':
      return { ...state, loading: false, data: action.payload };
    case 'FETCH_ERROR':
      return { ...state, loading: false, error: action.payload };
    case 'RESET':
      return initialState;
    default:
      return state;
  }
}

function UserProfile({ userId }: { userId: string }) {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  useEffect(() => {
    dispatch({ type: 'FETCH_START' });
    
    api.getUser(userId)
      .then(user => dispatch({ type: 'FETCH_SUCCESS', payload: user }))
      .catch(error => dispatch({ type: 'FETCH_ERROR', payload: error }));
  }, [userId]);
  
  if (state.loading) return ;
  if (state.error) return ;
  return ;
}

// 使用immer简化不可变更新
import { useImmerReducer } from 'use-immer';

function reducer(draft: State, action: Action) {
  switch (action.type) {
    case 'FETCH_START':
      draft.loading = true;
      draft.error = null;
      break;
    case 'FETCH_SUCCESS':
      draft.loading = false;
      draft.data = action.payload;
      break;
  }
}

useContext:跨组件状态共享

// Context优化模式

// 1. 拆分Context避免不必要的重渲染
// ❌ 一个Context包含所有状态
const AppContext = createContext({
  theme: {},
  user: {},
  settings: {}
});

// ✅ 拆分为独立的Context
const ThemeContext = createContext(null);
const UserContext = createContext(null);
const SettingsContext = createContext(null);

// 2. 自定义Hook封装
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) throw new Error('useTheme must be used within ThemeProvider');
  return context;
}

// 3. Context Selector模式(减少重渲染)
function useUserSelector(selector: (state: UserState) => T): T {
  const store = useContext(UserContext);
  const [selected, setSelected] = useState(() => selector(store.getState()));
  
  useEffect(() => {
    return store.subscribe(() => {
      const newSelected = selector(store.getState());
      setSelected(prev => {
        // 浅比较,避免不必要的更新
        return shallowEqual(prev, newSelected) ? prev : newSelected;
      });
    });
  }, [store, selector]);
  
  return selected;
}

// 使用
const userName = useUserSelector(state => state.user.name);

自定义Hooks:逻辑复用

// 实用的自定义Hooks

// 1. 防抖
function useDebounce(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delay);
    return () => clearTimeout(timer);
  }, [value, delay]);
  
  return debouncedValue;
}

// 2. 本地存储同步
function useLocalStorage(key: string, initialValue: T): [T, (val: T) => void] {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch {
      return initialValue;
    }
  });
  
  const setValue = (value: T) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue];
}

// 3. 媒体查询
function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false);
  
  useEffect(() => {
    const media = window.matchMedia(query);
    setMatches(media.matches);
    
    const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
    media.addEventListener('change', listener);
    
    return () => media.removeEventListener('change', listener);
  }, [query]);
  
  return matches;
}

// 4. 异步操作管理
function useAsync(asyncFunction: () => Promise) {
  const [state, setState] = useState<{
    data: T | null;
    loading: boolean;
    error: Error | null;
  }>({
    data: null,
    loading: false,
    error: null
  });
  
  const execute = useCallback(async () => {
    setState(prev => ({ ...prev, loading: true }));
    
    try {
      const data = await asyncFunction();
      setState({ data, loading: false, error: null });
      return data;
    } catch (error) {
      setState({ data: null, loading: false, error: error as Error });
      throw error;
    }
  }, [asyncFunction]);
  
  return { ...state, execute };
}

// 5. 无限滚动
function useInfiniteScroll(callback: () => void) {
  const observer = useRef();
  
  const lastElementRef = useCallback((node: HTMLElement | null) => {
    if (observer.current) observer.current.disconnect();
    
    observer.current = new IntersectionObserver(entries => {
      if (entries[0].isIntersecting) {
        callback();
      }
    });
    
    if (node) observer.current.observe(node);
  }, [callback]);
  
  return lastElementRef;
}

Hooks规则与陷阱

// Hooks使用规则

// 1. 只在最顶层调用Hooks
// ❌ 错误:在条件语句中调用
function BadComponent({ condition }) {
  if (condition) {
    const [state, setState] = useState(0); // 错误!
  }
}

// ✅ 正确
function GoodComponent({ condition }) {
  const [state, setState] = useState(0);
  
  useEffect(() => {
    if (condition) {
      // 在effect中处理条件逻辑
    }
  }, [condition]);
}

// 2. 只在React函数中调用Hooks
// ✅ 正确的地方:
// - React函数组件
// - 自定义Hooks

// ❌ 错误的地方:
// - 普通JavaScript函数
// - 循环或条件语句中
// - class组件中

// 3. 闭包陷阱
function ClosureTrap() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      // ❌ 错误:count永远是0
      console.log(count);
    }, 1000);
    
    return () => clearInterval(timer);
  }, []); // 空依赖数组
  
  // ✅ 解决方案1:使用函数式更新
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
    
    return () => clearInterval(timer);
  }, []);
  
  // ✅ 解决方案2:使用useRef
  const countRef = useRef(count);
  countRef.current = count;
  
  useEffect(() => {
    const timer = setInterval(() => {
      console.log(countRef.current);
    }, 1000);
    
    return () => clearInterval(timer);
  }, []);
}

// 4. 依赖数组完整性
// ❌ 错误:缺少依赖
useEffect(() => {
  console.log(userId);
}, []); // userId变化时不会重新执行

// ✅ 正确:包含所有依赖
useEffect(() => {
  console.log(userId);
}, [userId]);

// 使用eslint-plugin-react-hooks自动检查
// .eslintrc
{
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

常见Hooks陷阱

  • 忘记清理副作用:忘记return清理函数导致内存泄漏
  • 过度使用useMemo/useCallback:增加代码复杂度,收益不明显
  • useEffect依赖数组不完整:导致闭包问题
  • 在useEffect中直接调用async函数:effect不能返回Promise
  • 使用自定义Hook提取重复逻辑:提高代码复用性

架构决策总结

场景 推荐方案 说明
简单状态 useState 单个或少量相关状态
复杂状态逻辑 useReducer 多状态联动,状态转换复杂
数据获取 useEffect + 自定义Hook 或使用React Query/SWR
跨组件共享状态 useContext + useReducer 或使用Zustand/Redux
DOM引用 useRef 访问DOM或保存可变值
性能优化 useMemo/useCallback 谨慎使用,避免过早优化

总结

React Hooks彻底改变了React组件的编写方式。通过理解Hooks的工作原理、遵循使用规则、掌握常见模式,可以编写出更简洁、可复用、易测试的React应用。

Hooks的学习曲线虽然存在,但一旦掌握,将极大提升开发效率和代码质量。记住:Hooks的设计初衷是简化状态逻辑复用,善用自定义Hook,避免过度工程化。