React Hooks 深入理解

React Hooks 深入理解:原理与最佳实践

React Hooks是React 16.8引入的革命性特性,让函数组件拥有了状态和副作用管理能力,彻底改变了React的编程方式。本文深入讲解核心Hooks的工作原理和使用最佳实践。

一、useState:状态管理

import { useState } from 'react';

function Counter() {
    // useState返回[state, setState]
    const [count, setCount] = useState(0);
    
    // 函数式更新(推荐,避免闭包陷阱)
    const increment = () => setCount(prev => prev + 1);
    
    // 状态初始化:传入函数避免重复计算
    const [data, setData] = useState(() => {
        return expensiveComputation(); // 只在首次渲染时执行
    });
    
    return <button onClick={increment}>{count}</button>;
}

// 复杂状态使用对象
const [form, setForm] = useState({ name: '', email: '' });
// 更新时必须展开合并,否则会丢失其他字段
const handleChange = (field, value) => {
    setForm(prev => ({ ...prev, [field]: value }));
};

二、useEffect:副作用处理

import { useEffect, useState } from 'react';

function UserProfile({ userId }) {
    const [user, setUser] = useState(null);
    
    useEffect(() => {
        let cancelled = false;
        
        async function fetchUser() {
            const data = await api.getUser(userId);
            if (!cancelled) {
                setUser(data);
            }
        }
        
        fetchUser();
        
        // 清理函数(组件卸载或依赖变化时执行)
        return () => {
            cancelled = true;  // 防止内存泄漏
        };
    }, [userId]);  // 依赖数组:userId变化时重新执行
    
    return user ? <div>{user.name}</div> : <Loading />;
}

// 常见useEffect使用模式
// 1. 只在挂载时执行(空依赖数组)
useEffect(() => { /* 只执行一次 */ }, []);
// 2. 每次渲染后都执行(无依赖数组)
useEffect(() => { /* 每次渲染后执行 */ });
// 3. 依赖变化时执行
useEffect(() => { /* 依赖变化时执行 */ }, [dep1, dep2]);

三、useCallback 和 useMemo:性能优化

import { useCallback, useMemo } from 'react';

function ExpensiveComponent({ items, onSelect }) {
    // useMemo:缓存计算结果
    const sortedItems = useMemo(() => {
        console.log('Sorting items...');
        return [...items].sort((a, b) => a.name.localeCompare(b.name));
    }, [items]);  // 只在items变化时重新计算
    
    // useCallback:缓存函数引用
    const handleClick = useCallback((id) => {
        onSelect(id);
    }, [onSelect]);  // 避免子组件不必要的重渲染
    
    return (
        <ul>
            {sortedItems.map(item => (
                <Item key={item.id} item={item} onClick={handleClick} />
            ))}
        </ul>
    );
}

四、useRef:引用与可变值

import { useRef, useEffect } from 'react';

function TextInput() {
    // 1. 访问DOM元素
    const inputRef = useRef(null);
    
    useEffect(() => {
        inputRef.current?.focus();  // 组件挂载后自动聚焦
    }, []);
    
    // 2. 存储不触发重渲染的可变值
    const renderCount = useRef(0);
    renderCount.current++;  // 不会触发重渲染
    
    // 3. 保存定时器ID(清理时使用)
    const timerRef = useRef(null);
    const startTimer = () => {
        timerRef.current = setInterval(() => console.log('tick'), 1000);
    };
    const stopTimer = () => clearInterval(timerRef.current);
    
    return <input ref={inputRef} />;
}

五、useContext:跨层级状态传递

// 创建Context
const ThemeContext = createContext({ theme: 'light', toggleTheme: () => {} });

// Provider组件
function ThemeProvider({ children }) {
    const [theme, setTheme] = useState('light');
    const toggleTheme = useCallback(() => {
        setTheme(t => t === 'light' ? 'dark' : 'light');
    }, []);
    
    return (
        <ThemeContext.Provider value={{ theme, toggleTheme }}>
            {children}
        </ThemeContext.Provider>
    );
}

// 消费Context(任意嵌套深度的子组件)
function Button() {
    const { theme, toggleTheme } = useContext(ThemeContext);
    return (
        <button className={theme} onClick={toggleTheme}>
            Toggle Theme
        </button>
    );
}

六、自定义Hook:逻辑复用

// 自定义Hook:封装数据获取逻辑
function useAsync(asyncFn, deps = []) {
    const [state, setState] = useState({ data: null, loading: false, error: null });
    
    useEffect(() => {
        setState(prev => ({ ...prev, loading: true }));
        
        asyncFn()
            .then(data => setState({ data, loading: false, error: null }))
            .catch(error => setState({ data: null, loading: false, error }));
    }, deps);
    
    return state;
}

// 使用自定义Hook
function UserList() {
    const { data, loading, error } = useAsync(() => api.getUsers(), []);
    
    if (loading) return <Spinner />;
    if (error) return <Error message={error.message} />;
    return <ul>{data?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;
}

七、Hooks使用规则

  • 只能在React函数组件或自定义Hook中调用Hooks
  • 只能在函数顶层调用Hooks,不能在条件语句、循环或嵌套函数中调用
  • 自定义Hook命名必须以”use”开头
  • 依赖数组必须包含所有在effect中使用的响应式变量(使用ESLint exhaustive-deps规则)

八、总结

React Hooks让代码组织从”生命周期”转变为”关注点”,相关逻辑内聚在一个Hook中,大幅提升代码可读性和复用性。建议从useState和useEffect入手,逐步掌握useCallback、useMemo等优化Hooks,最终能够封装自定义Hook实现业务逻辑复用。