跳转至

React深度实践

React深度实践

📚 章节目标

本章节将深入讲解React框架的核心概念和高级特性,包括Hooks、Context、状态管理、性能优化等,帮助学习者掌握React框架的深度应用。

学习目标

  1. 深入理解React Hooks原理和最佳实践
  2. 掌握React Context API的高级应用
  3. 熟练使用React Query进行数据获取
  4. 掌握React性能优化技巧
  5. 了解React Server Components

🎣 React Hooks深度解析

1. Hooks基础原理

1.1 Hooks的设计动机

JavaScript
// 类组件的问题:相关逻辑分散在不同生命周期方法中
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 }; // 类组件中状态必须是对象
  }

  // 生命周期问题:componentDidMount和componentDidUpdate中存在重复逻辑
  componentDidMount() {
    document.title = `Count: ${this.state.count}`;
  }

  componentDidUpdate() {
    document.title = `Count: ${this.state.count}`;
  }

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Increment
        </button>
      </div>
    );
  }
}

// Hooks解决方案:相关逻辑集中在一起,代码更简洁
function Counter() {
  const [count, setCount] = useState(0); // 声明式状态

  // useEffect统一处理挂载和更新,消除重复代码
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]); // 仅count变化时执行

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

1.2 Hooks的实现原理

JavaScript
// 简化的Hooks实现原理(React内部使用链表结构,此处用数组模拟)
let hooks = [];          // 存储所有Hook的状态值
let currentHookIndex = 0; // 当前正在执行的Hook索引

function useState(initialValue) {
  const index = currentHookIndex; // 闭包捕获当前索引,确保setState更新正确的状态

  if (hooks[index] === undefined) {
    hooks[index] = initialValue; // 仅首次渲染时初始化
  }

  const setState = (newValue) => {
    hooks[index] = newValue; // 通过闭包中的index定位到对应状态
    render(); // 触发重新渲染
  };

  currentHookIndex++; // 索引递增,为下一个Hook预留位置
  return [hooks[index], setState];
}

function useEffect(callback, deps) {
  const index = currentHookIndex;
  const prevDeps = hooks[index]; // 获取上一次渲染时的依赖数组

  // 浅比较依赖:首次渲染(prevDeps为空)或任一依赖变化时执行回调
  const hasChangedDeps = !prevDeps || !deps.every((dep, i) => dep === prevDeps[i]);

  if (hasChangedDeps) {
    callback();
    hooks[index] = deps; // 保存本次依赖用于下次比较
  }

  currentHookIndex++;
}

function render() {
  currentHookIndex = 0; // 每次渲染重置索引,保证Hooks调用顺序一致
  // React 18+ 使用 createRoot API
  const root = ReactDOM.createRoot(document.getElementById('root'));
  root.render(<App />);
}

2. 核心Hooks详解

2.1 useState

JavaScript
// 基础用法
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(count - 1)}>Decrement</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

// 函数式更新(当新状态依赖旧状态时,使用函数形式确保正确性)
function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    // 使用函数式更新,每次都基于最新的pending状态计算
    setCount(prev => prev + 1);
    setCount(prev => prev + 1); // 连续调用两次,最终count+2
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment Twice</button>
    </div>
  );
}

// 惰性初始化(传入函数而非值,避免每次渲染都执行昂贵计算)
function UserProfile({ userId }) {
  const [user, setUser] = useState(() => {
    // 只在初始渲染时执行一次,后续渲染跳过此函数
    return fetchUser(userId);
  });

  return <div>{user.name}</div>;
}

// 对象和数组状态(需要保持不可变性,每次更新返回新对象)
function Form() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    age: 0,
  });

  const handleChange = (field, value) => {
    // 展开运算符创建新对象,仅覆盖变化的字段(不可变更新模式)
    setFormData(prev => ({
      ...prev,
      [field]: value,
    }));
  };

  return (
    <form>
      <input
        value={formData.username}
        onChange={(e) => handleChange('username', e.target.value)}
      />
      <input
        value={formData.email}
        onChange={(e) => handleChange('email', e.target.value)}
      />
    </form>
  );
}

2.2 useEffect

JavaScript
// 基础用法
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]); // 依赖数组

  if (!user) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}

// 清理函数(组件卸载或依赖变化时执行,防止内存泄漏)
function ChatRoom({ roomId }) {
  useEffect(() => {
    const connection = createConnection(roomId);
    connection.connect();

    // 返回清理函数:roomId变化时先断开旧连接,再建立新连接
    return () => {
      connection.disconnect();
    };
  }, [roomId]); // roomId变化时重新执行Effect

  return <div>Connected to {roomId}</div>;
}

// 多个Effect
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);

  useEffect(() => {
    if (user) {
      fetchPosts(user.id).then(setPosts);
    }
  }, [user]);

  return (
    <div>
      <h1>{user?.name}</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

// 性能优化:防抖搜索,避免每次按键都发送请求
function SearchResults({ query }) {
  const [results, setResults] = useState([]);

  useEffect(() => {
    if (query.trim() === '') {
      setResults([]);
      return; // 空查询直接返回,不设置定时器
    }

    // 延迟500ms执行搜索,实现防抖效果
    const timer = setTimeout(() => {
      search(query).then(setResults);
    }, 500);

    // 依赖变化时清除上一个定时器,只执行最后一次输入的搜索
    return () => clearTimeout(timer);
  }, [query]);

  return (
    <ul>
      {results.map(result => (
        <li key={result.id}>{result.title}</li>
      ))}
    </ul>
  );
}

2.3 useContext

JavaScript
// 创建Context
const ThemeContext = React.createContext('light');
const UserContext = React.createContext(null);

// 提供者
function App() {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState({ name: 'John' });

  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <UserContext.Provider value={user}>
        <Toolbar />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

// 消费者
function Toolbar() {
  const { theme, setTheme } = useContext(ThemeContext);
  const user = useContext(UserContext);

  return (
    <div className={theme}>
      <p>User: {user.name}</p>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </div>
  );
}

// 自定义Hook简化Context使用,并添加安全校验
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    // 未在Provider内使用时抛出明确错误,便于调试
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

function Button() {
  const { theme } = useTheme();
  return <button className={theme}>Click me</button>;
}

2.4 useReducer

JavaScript
// 基础用法
const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return initialState;
    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>Reset</button>
    </div>
  );
}

// 复杂状态管理(当状态逻辑复杂时,useReducer比useState更清晰)
const initialState = {
  todos: [],
  filter: 'all',
  loading: false,  // 异步加载状态
  error: null,     // 错误信息
};

// 纯函数reducer:接收当前状态和action,返回新状态
function reducer(state, action) {
  switch (action.type) {
    case 'FETCH_TODOS_START':       // 开始请求:显示加载、清除错误
      return { ...state, loading: true, error: null };
    case 'FETCH_TODOS_SUCCESS':     // 请求成功:保存数据、关闭加载
      return { ...state, loading: false, todos: action.payload };
    case 'FETCH_TODOS_ERROR':       // 请求失败:保存错误、关闭加载
      return { ...state, loading: false, error: action.payload };
    case 'ADD_TODO':
      return { ...state, todos: [...state.todos, action.payload] };
    case 'REMOVE_TODO':
      return {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload),
      };
    case 'SET_FILTER':
      return { ...state, filter: action.payload };
    default:
      return state;
  }
}

function TodoApp() {
  // useReducer返回当前状态和dispatch函数(类似Redux模式)
  const [state, dispatch] = useReducer(reducer, initialState);

  // 组件挂载时获取待办数据
  useEffect(() => {
    dispatch({ type: 'FETCH_TODOS_START' });
    fetchTodos()
      .then(todos => dispatch({ type: 'FETCH_TODOS_SUCCESS', payload: todos }))
      .catch(error => dispatch({ type: 'FETCH_TODOS_ERROR', payload: error }));
  }, []); // 空依赖数组:仅在挂载时执行一次

  const addTodo = (text) => {
    const todo = { id: Date.now(), text, completed: false };
    dispatch({ type: 'ADD_TODO', payload: todo });
  };

  if (state.loading) return <div>Loading...</div>;
  if (state.error) return <div>Error: {state.error.message}</div>;

  return (
    <div>
      <TodoForm onSubmit={addTodo} />
      <TodoList todos={state.todos} filter={state.filter} />
    </div>
  );
}

2.5 useMemo

JavaScript
// 缓存计算结果
function ExpensiveComponent({ a, b }) {
  const expensiveValue = useMemo(() => {
    console.log('Computing expensive value...');
    return a * b + Math.sqrt(a + b);
  }, [a, b]);

  return <div>Result: {expensiveValue}</div>;
}

// 优化列表渲染(仅当users或filter变化时才重新过滤)
function UserList({ users, filter }) {
  const filteredUsers = useMemo(() => {
    return users.filter(user =>
      user.name.toLowerCase().includes(filter.toLowerCase())
    );
  }, [users, filter]); // 依赖数组:只有这两个值变化时才重新计算

  return (
    <ul>
      {filteredUsers.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

// 缓存复杂对象(避免每次渲染都创建新对象引用,导致子组件不必要的重渲染)
function Chart({ data, config }) {
  const chartOptions = useMemo(() => ({
    type: config.type,
    data: {
      labels: data.map(d => d.label),
      datasets: [{
        data: data.map(d => d.value),
        backgroundColor: config.colors,
      }],
    },
    options: {
      responsive: true,
      plugins: {
        legend: { position: 'top' },
      },
    },
  }), [data, config]);

  return <Chart options={chartOptions} />;
}

2.6 useCallback

JavaScript
// 缓存函数
function ParentComponent() {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []); // 空依赖数组,函数只创建一次

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

// 避免子组件不必要的渲染(useCallback + React.memo配合使用)
function ParentComponent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  // useCallback缓存函数引用,防止每次渲染创建新函数导致子组件重渲染
  const handleChange = useCallback((value) => {
    setText(value);
  }, []); // 空依赖:函数永远保持同一引用

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      {/* count变化时,ChildComponent不会重渲染,因为props未变 */}
      <ChildComponent value={text} onChange={handleChange} />
    </div>
  );
}

// React.memo:浅比较props,仅props变化时才重新渲染
const ChildComponent = React.memo(function ChildComponent({ value, onChange }) {
  console.log('ChildComponent rendered');
  return <input value={value} onChange={(e) => onChange(e.target.value)} />;
});

2.7 useRef

JavaScript
// 访问DOM元素
function TextInput() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus</button>
    </div>
  );
}

// 保存可变值(useRef在组件整个生命周期内保持同一引用)
function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null); // 用ref保存定时器ID,修改时不触发重渲染

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1); // 使用函数式更新,避免闭包引用过期的count
    }, 1000);

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

  return <div>Count: {count}</div>;
}

// 保存上一次的值(利用useEffect在渲染后执行的特性)
function PreviousValue({ value }) {
  const prevRef = useRef(); // 初始值为undefined

  useEffect(() => {
    // Effect在渲染完成后执行,此时DOM已展示当前值
    prevRef.current = value; // 保存当前值,下次渲染时作为"上一次的值"
  }, [value]);

  return (
    <div>
      <p>Current: {value}</p>
      <p>Previous: {prevRef.current}</p>
    </div>
  );
}

2.8 useLayoutEffect

JavaScript
// 同步执行,在DOM更新后、浏览器绘制前执行(适合DOM测量)
function MeasureExample() {
  const [height, setHeight] = useState(0);
  // 回调ref:当DOM节点挂载/卸载时调用
  const measuredRef = useCallback(node => {
    if (node !== null) {
      setHeight(node.getBoundingClientRect().height); // 获取元素实际高度
    }
  }, []);

  return (
    <>
      <h1 ref={measuredRef}>Hello, world!</h1>
      <h2>The above header is {Math.round(height)}px tall</h2>
    </>
  );
}

// 避免闪烁(useLayoutEffect同步执行,用户看不到中间状态)
function ScrollToBottom({ messages }) {
  const containerRef = useRef(null);

  // useLayoutEffect保证滚动在浏览器绘制前完成,避免闪烁
  useLayoutEffect(() => {
    if (containerRef.current) {
      containerRef.current.scrollTop = containerRef.current.scrollHeight;
    }
  }, [messages]); // 消息列表更新时自动滚动到底部

  return (
    <div ref={containerRef} style={{ overflow: 'auto', height: '300px' }}>
      {messages.map(msg => (
        <div key={msg.id}>{msg.text}</div>
      ))}
    </div>
  );
}

3. 自定义Hooks

3.1 数据获取Hook

JavaScript
// useFetch - 通用数据获取Hook(封装loading/error/data三种状态)
function useFetch(url, options = {}) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  // 用ref保存options,避免options对象引用变化导致Effect重新执行
  const optionsRef = useRef(options);
  optionsRef.current = options;

  useEffect(() => {
    // AbortController用于在组件卸载或url变化时取消未完成的请求
    const controller = new AbortController();

    const fetchData = async () => {
      setLoading(true);
      setError(null);

      try {
        const response = await fetch(url, {
          ...optionsRef.current,
          signal: controller.signal, // 关联取消信号
        });

        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }

        const json = await response.json();
        setData(json);
      } catch (err) {
        // 忽略因组件卸载导致的AbortError
        if (err.name !== 'AbortError') {
          setError(err);
        }
      } finally {
        setLoading(false);
      }
    };

    fetchData();

    return () => {
      controller.abort(); // 清理:取消进行中的请求
    };
  }, [url]); // 仅url变化时重新请求

  return { data, loading, error };
}

// 使用示例
function UserProfile({ userId }) {
  const { data: user, loading, error } = useFetch(`/api/users/${userId}`);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!user) return null;

  return <div>{user.name}</div>;
}

3.2 本地存储Hook

JavaScript
// useLocalStorage - 将状态持久化到localStorage
function useLocalStorage(key, initialValue) {
  // 惰性初始化:首次渲染时从localStorage读取
  const [storedValue, setStoredValue] = useState(() => {
    try {  // try/catch捕获异常
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue; // 已存在则解析,否则用默认值
    } catch (error) {
      console.error(error);
      return initialValue;
    }
  });

  const setValue = (value) => {
    try {
      // 支持函数式更新(与useState行为一致)
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore)); // 同步写入localStorage
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue]; // API与useState完全一致
}

// 使用示例
function ThemeToggle() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      Switch to {theme === 'light' ? 'dark' : 'light'} mode
    </button>
  );
}

3.3 防抖Hook

JavaScript
// useDebounce - 防抖Hook(延迟更新值,适用于搜索输入等场景)
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);

  useEffect(() => {
    // 值变化后延迟delay毫秒再更新
    const timer = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    // 值再次变化时清除上一个定时器,实现防抖
    return () => {
      clearTimeout(timer);
    };
  }, [value, delay]);

  return debouncedValue; // 返回防抖后的值
}

// 使用示例
function SearchInput() {
  const [searchTerm, setSearchTerm] = useState('');
  const debouncedSearchTerm = useDebounce(searchTerm, 500);

  useEffect(() => {
    if (debouncedSearchTerm) {
      // 执行搜索
      search(debouncedSearchTerm);
    }
  }, [debouncedSearchTerm]);

  return (
    <input
      type="text"
      value={searchTerm}
      onChange={(e) => setSearchTerm(e.target.value)}
      placeholder="Search..."
    />
  );
}

3.4 窗口大小Hook

JavaScript
// useWindowSize - 响应式监听窗口尺寸变化
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); // 监听窗口resize事件
    return () => window.removeEventListener('resize', handleResize); // 卸载时移除监听
  }, []); // 空依赖:仅挂载/卸载时执行

  return size;
}

// 使用示例
function ResponsiveComponent() {
  const { width } = useWindowSize();

  return (
    <div>
      {width < 768 ? <MobileView /> : <DesktopView />}
    </div>
  );
}

3.5 表单Hook

JavaScript
// useForm - 通用表单管理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 }));

    // 仅对已触摸过的字段实时校验(避免用户还未输入就显示错误)
    if (touched[name] && validate) {
      setErrors(prev => ({
        ...prev,
        [name]: validate(values)[name],
      }));
    }
  };

  // 失焦时标记字段为已触摸,并触发校验
  const handleBlur = (e) => {
    const { name } = e.target;
    setTouched(prev => ({ ...prev, [name]: true }));

    if (validate) {
      setErrors(prev => ({
        ...prev,
        [name]: validate(values)[name],
      }));
    }
  };

  // 高阶函数:返回表单提交处理器
  const handleSubmit = (callback) => (e) => {
    e.preventDefault();

    // 提交时校验所有字段
    const validationErrors = validate ? validate(values) : {};
    setErrors(validationErrors);
    // 标记所有字段为已触摸(显示所有错误)
    setTouched(
      Object.keys(values).reduce((acc, key) => ({ ...acc, [key]: true }), {})
    );

    // 无错误时调用回调
    if (Object.keys(validationErrors).length === 0) {
      callback(values);
    }
  };

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

// 使用示例
function LoginForm() {
  const { values, errors, handleChange, handleBlur, handleSubmit } = useForm(
    { username: '', password: '' },
    (values) => {
      const errors = {};
      if (!values.username) errors.username = 'Username is required';
      if (!values.password) errors.password = 'Password is required';
      return errors;
    }
  );

  const onSubmit = (values) => {
    console.log('Form submitted:', values);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input
        name="username"
        value={values.username}
        onChange={handleChange}
        onBlur={handleBlur}
        placeholder="Username"
      />
      {errors.username && <span>{errors.username}</span>}

      <input
        name="password"
        type="password"
        value={values.password}
        onChange={handleChange}
        onBlur={handleBlur}
        placeholder="Password"
      />
      {errors.password && <span>{errors.password}</span>}

      <button type="submit">Submit</button>
    </form>
  );
}

🌐 React Context API高级应用

1. Context最佳实践

1.1 分离Context

JavaScript
// 不好的做法:所有状态放在一个Context(任一状态变化会导致所有消费者重渲染)
const AppContext = React.createContext();

function AppProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState(null);
  const [language, setLanguage] = useState('en');

  // 问题:value对象每次渲染都是新引用,且包含所有状态
  return (
    <AppContext.Provider value={{ theme, setTheme, user, setUser, language, setLanguage }}>
      {children}
    </AppContext.Provider>
  );
}

// 好的做法:按职责分离Context,各自独立更新
const ThemeContext = React.createContext();
const UserContext = React.createContext();
const LanguageContext = React.createContext();

function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

function UserProvider({ children }) {
  const [user, setUser] = useState(null);
  return (
    <UserContext.Provider value={{ user, setUser }}>
      {children}
    </UserContext.Provider>
  );
}

1.2 优化Context性能

JavaScript
// 不好的做法:整个Context变化导致所有消费者重新渲染
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [colors, setColors] = useState({
    primary: '#007bff',
    secondary: '#6c757d',
  });

  return (
    <ThemeContext.Provider value={{ theme, setTheme, colors, setColors }}>
      {children}
    </ThemeContext.Provider>
  );
}

// 好的做法:将状态值和修改函数分到不同Context,减少不必要的重渲染
function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [colors, setColors] = useState({
    primary: '#007bff',
    secondary: '#6c757d',
  });

  return (
    // 只读数据和修改方法分离:读取theme的组件不会因setTheme引用变化而重渲染
    <ThemeContext.Provider value={{ theme, colors }}>
      <ThemeDispatchContext.Provider value={{ setTheme, setColors }}>
        {children}
      </ThemeDispatchContext.Provider>
    </ThemeContext.Provider>
  );
}

// 消费者Hook:按需订阅,只读组件用useTheme,修改组件用useThemeDispatch
function useTheme() {
  return useContext(ThemeContext);
}

function useThemeDispatch() {
  return useContext(ThemeDispatchContext);
}

2. 复杂场景应用

2.1 多层Context嵌套

JavaScript
function App() {
  return (
    <ThemeProvider>
      <AuthProvider>
        <LanguageProvider>
          <Router>
            <Routes>
              <Route path="/" element={<Home />} />
              <Route path="/dashboard" element={<Dashboard />} />
            </Routes>
          </Router>
        </LanguageProvider>
      </AuthProvider>
    </ThemeProvider>
  );
}

function Dashboard() {
  const { theme } = useTheme();
  const { user } = useAuth();
  const { language } = useLanguage();

  return (
    <div className={theme}>
      <h1>Welcome, {user.name}</h1>
      <p>Language: {language}</p>
    </div>
  );
}

2.2 Context组合

JavaScript
// 创建组合Hook
function useApp() {
  const theme = useTheme();
  const auth = useAuth();
  const language = useLanguage();

  return {
    theme,
    auth,
    language,
  };
}

// 使用
function MyComponent() {
  const { theme, auth, language } = useApp();

  return (
    <div>
      <p>Theme: {theme}</p>
      <p>User: {auth.user?.name}</p>
      <p>Language: {language}</p>
    </div>
  );
}

📊 React Query数据获取

1. 基础使用

JavaScript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

// 获取数据(useQuery自动管理缓存、重试、后台刷新)
function UserProfile({ userId }) {
  const { data, isLoading, error } = useQuery({
    queryKey: ['user', userId],  // 查询键:用于缓存标识和自动重获取
    queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
  });

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return <div>{data.name}</div>;
}

// 更新数据(useMutation处理写操作,配合缓存失效实现数据同步)
function UpdateUser({ userId }) {
  const queryClient = useQueryClient(); // 获取查询客户端实例

  const mutation = useMutation({
    mutationFn: (userData) =>
      fetch(`/api/users/${userId}`, {
        method: 'PUT',
        body: JSON.stringify(userData),
      }).then(res => res.json()),
    onSuccess: () => {
      // 更新成功后使相关缓存失效,触发自动重新获取
      queryClient.invalidateQueries({ queryKey: ['user', userId] });
    },
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    mutation.mutate(Object.fromEntries(formData));
  };

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="Name" />
      <button type="submit" disabled={mutation.isPending}>
        {mutation.isPending ? 'Saving...' : 'Save'}
      </button>
    </form>
  );
}

2. 高级特性

2.1 缓存和重新验证

JavaScript
// 自动重新验证
function UserProfile({ userId }) {
  const { data } = useQuery({
    queryKey: ['user', userId],
    queryFn: () => fetch(`/api/users/${userId}`).then(res => res.json()),
    staleTime: 5 * 60 * 1000, // 5分钟内不重新获取
    gcTime: 10 * 60 * 1000, // 10分钟后清除缓存(TanStack Query v5 已将 cacheTime 重命名为 gcTime)
    refetchOnWindowFocus: true, // 窗口聚焦时重新获取
    refetchOnReconnect: true, // 网络重连时重新获取
  });

  return <div>{data?.name}</div>;
}

// 手动重新验证
function RefreshButton() {
  const queryClient = useQueryClient();

  const handleClick = () => {
    queryClient.invalidateQueries({ queryKey: ['user'] });
  };

  return <button onClick={handleClick}>Refresh</button>;
}

2.2 分页和无限滚动

JavaScript
// 分页
function UserList() {
  const [page, setPage] = useState(1);

  const { data, isLoading } = useQuery({
    queryKey: ['users', page],
    queryFn: () => fetch(`/api/users?page=${page}`).then(res => res.json()),
  });

  return (
    <div>
      {isLoading ? (
        <div>Loading...</div>
      ) : (
        <>
          <ul>
            {data.users.map(user => (
              <li key={user.id}>{user.name}</li>
            ))}
          </ul>
          <button
            onClick={() => setPage(p => p - 1)}
            disabled={page === 1}
          >
            Previous
          </button>
          <button
            onClick={() => setPage(p => p + 1)}
            disabled={!data.hasMore}
          >
            Next
          </button>
        </>
      )}
    </div>
  );
}

// 无限滚动(结合useInfiniteQuery和IntersectionObserver实现)
function InfiniteUserList() {
  const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } =
    useInfiniteQuery({
      queryKey: ['users'],
      queryFn: ({ pageParam }) =>
        fetch(`/api/users?page=${pageParam}`).then(res => res.json()),
      initialPageParam: 1, // TanStack Query v5 必须显式指定初始页码
      // 根据上一页响应决定下一页参数,返回undefined表示没有更多数据
      getNextPageParam: (lastPage) => lastPage.hasMore ? lastPage.nextPage : undefined,
    });

  const observerRef = useRef(); // 目标元素ref,用于触发加载更多

  useEffect(() => {
    // IntersectionObserver:当目标元素进入视口时触发回调
    const observer = new IntersectionObserver(
      (entries) => {
        // 元素可见 && 还有下一页 && 当前未在加载中
        if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
          fetchNextPage();
        }
      },
      { threshold: 1 } // 完全可见时触发
    );

    if (observerRef.current) {
      observer.observe(observerRef.current);
    }

    return () => {
      if (observerRef.current) {
        observer.unobserve(observerRef.current);
      }
    };
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);

  return (
    <div>
      {data?.pages.map((page) => (
        <ul key={page.page}>
          {page.users.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
      ))}
      <div ref={observerRef} />
      {isFetchingNextPage && <div>Loading more...</div>}
    </div>
  );
}

2.3 乐观更新

JavaScript
function TodoList() {
  const queryClient = useQueryClient();

  const addTodoMutation = useMutation({
    mutationFn: (text) =>
      fetch('/api/todos', {
        method: 'POST',
        body: JSON.stringify({ text }),
      }).then(res => res.json()),
    // onMutate在请求发出前执行,用于乐观更新
    onMutate: async (newTodo) => {
      // 取消正在进行的查询,避免覆盖乐观更新的数据
      await queryClient.cancelQueries({ queryKey: ['todos'] });

      // 保存之前的数据(用于回滚)
      const previousTodos = queryClient.getQueryData(['todos']);

      // 乐观更新:立即在UI上显示新数据,无需等待服务器响应
      queryClient.setQueryData(['todos'], (old) => [
        ...old,  // ...展开运算符:展开数组/对象
        { id: Date.now(), text: newTodo, completed: false },
      ]);

      return { previousTodos }; // 传递给onError用于回滚
    },
    onError: (err, newTodo, context) => {
      // 请求失败时回滚到之前的数据
      queryClient.setQueryData(['todos'], context.previousTodos);
    },
    onSettled: () => {
      // 无论成功或失败,都重新获取以确保数据一致性
      queryClient.invalidateQueries({ queryKey: ['todos'] });
    },
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    const text = e.target.elements.text.value;
    addTodoMutation.mutate(text);
    e.target.reset();
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input name="text" placeholder="Add todo" />
        <button type="submit" disabled={addTodoMutation.isPending}>
          Add
        </button>
      </form>
    </div>
  );
}

⚡ React性能优化

1. 组件优化

1.1 React.memo

JavaScript
// 避免不必要的渲染
const ExpensiveComponent = React.memo(function ExpensiveComponent({ data }) {
  console.log('ExpensiveComponent rendered');
  return <div>{data.map(item => <div key={item.id}>{item.name}</div>)}</div>;
});

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [data, setData] = useState([]);

  useEffect(() => {
    fetchData().then(setData);
  }, []);

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <ExpensiveComponent data={data} />
    </div>
  );
}

// 自定义比较函数(默认浅比较不够用时,手动控制重渲染条件)
const MemoizedComponent = React.memo(
  function MyComponent({ user, onClick }) {
    return <div onClick={() => onClick(user.id)}>{user.name}</div>;
  },
  (prevProps, nextProps) => {
    // 返回true表示props相同(不重渲染),false表示需要重渲染
    return prevProps.user.id === nextProps.user.id;
  }
);

1.2 useMemo和useCallback

JavaScript
function ParentComponent() {
  const [count, setCount] = useState(0);
  const [items, setItems] = useState([]);

  // 缓存计算结果
  const expensiveValue = useMemo(() => {
    console.log('Computing expensive value...');
    return items.reduce((sum, item) => sum + item.value, 0);
  }, [items]);

  // 缓存函数
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Total: {expensiveValue}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

2. 列表优化

2.1 虚拟列表

JavaScript
import { FixedSizeList } from 'react-window'; // 虚拟列表库,仅渲染可见区域的DOM

function VirtualizedList({ items }) {
  // 行渲染组件:style由react-window计算,包含定位信息
  const Row = ({ index, style }) => (
    <div style={style}>
      {items[index].name}
    </div>
  );

  return (
    <FixedSizeList
      height={400}           // 容器高度
      itemCount={items.length} // 总数据量
      itemSize={35}           // 每行固定高度(px)
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

2.2 key优化

JavaScript
// 不好的做法:使用index作为key
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo, index) => (
        <li key={index}>{todo.text}</li>
      ))}
    </ul>
  );
}

// 好的做法:使用唯一ID作为key
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

3. 代码分割

3.1 React.lazy

JavaScript
import { lazy, Suspense } from 'react';

// lazy()实现动态导入,每个页面组件会被打包为独立chunk
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
const Contact = lazy(() => import('./Contact'));

function App() {
  return (
    // Suspense包裹懒加载组件,在加载期间显示fallback内容
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </Suspense>
  );
}

3.2 路由级代码分割

JavaScript
import { lazy, Suspense } from 'react';

// 路由配置数组:每个路由对应一个懒加载组件,访问时才按需下载
const routes = [
  {
    path: '/',
    component: lazy(() => import('./pages/Home')),
  },
  {
    path: '/dashboard',
    component: lazy(() => import('./pages/Dashboard')),
  },
  {
    path: '/settings',
    component: lazy(() => import('./pages/Settings')),
  },
];

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        {routes.map(route => (  // map转换每个元素;filter筛选;reduce累积
          <Route
            key={route.path}
            path={route.path}
            element={<route.component />}
          />
        ))}
      </Routes>
    </Suspense>
  );
}

🚀 React Server Components

1. 基础概念

JavaScript
// 服务器组件(默认):在服务器端执行,可直接访问数据库,不增加客户端JS体积
async function BlogPost({ id }) {
  const post = await db.post.findUnique({ where: { id } }); // 服务端直接查询DB
  return <article>{post.content}</article>;
}

// 客户端组件:需要'use client'指令声明,支持交互和浏览器API
'use client';
import { useState } from 'react';

function LikeButton() {
  const [likes, setLikes] = useState(0); // 客户端状态,服务器组件中不可用
  return <button onClick={() => setLikes(likes + 1)}>{likes}</button>;
}

2. 混合使用

JavaScript
// 服务器组件(可嵌入客户端组件作为子组件)
async function BlogPage({ params }) {  // async定义异步函数;await等待Promise完成
  const post = await db.post.findUnique({ where: { id: params.id } });  // await等待异步操作完成
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
      {/* 服务器组件中使用客户端组件,通过props传递序列化数据 */}
      <LikeButton postId={post.id} />
    </div>
  );
}

// 客户端组件:处理用户交互逻辑
'use client';
import { useState } from 'react';

function LikeButton({ postId }) {
  const [likes, setLikes] = useState(0);

  // 客户端发起API请求(不能直接访问数据库)
  const handleLike = async () => {  // const不可重新赋值;let块级作用域变量
    const response = await fetch(`/api/posts/${postId}/like`, {
      method: 'POST',
    });
    const data = await response.json();
    setLikes(data.likes);
  };

  return <button onClick={handleLike}>{likes} likes</button>;
}

📝 练习题

1. 基础题

题目1:实现一个useCounter Hook

JavaScript
function useCounter(initialValue = 0) {
  // 实现计数器Hook
  // 返回 { count, increment, decrement, reset }
}

// 使用示例
function Counter() {
  const { count, increment, decrement, reset } = useCounter(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}

2. 进阶题

题目2:实现一个useAsync Hook

JavaScript
function useAsync(asyncFunction, immediate = true) {
  // 实现异步Hook
  // 返回 { data, error, loading, run }
}

// 使用示例
function UserProfile({ userId }) {
  const { data, error, loading } = useAsync(  // 解构赋值:从对象/数组提取值
    () => fetch(`/api/users/${userId}`).then(res => res.json()),  // 箭头函数:简洁的函数语法
    !!userId
  );

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  return <div>{data?.name}</div>;  // ?.可选链对象为null/undefined时安全返回undefined
}

3. 面试题

题目3:解释React的渲染机制和优化策略

JavaScript
// 答案要点:
// 1. React的渲染流程:状态变化 → 虚拟DOM计算 → diff算法 → 真实DOM更新
// 2. 优化策略:
//    - 使用React.memo避免不必要的渲染
//    - 使用useMemo缓存计算结果
//    - 使用useCallback缓存函数
//    - 代码分割和懒加载
//    - 虚拟列表优化长列表
//    - 避免内联对象和函数

🎯 本章总结

本章节深入讲解了React框架的核心概念和高级特性,包括Hooks、Context、React Query、性能优化等。关键要点:

  1. Hooks原理:理解Hooks的设计动机和实现原理
  2. 自定义Hooks:掌握自定义Hooks的编写技巧
  3. Context API:掌握Context的高级应用和性能优化
  4. React Query:掌握数据获取和状态管理
  5. 性能优化:掌握React性能优化的各种技巧

下一步将深入学习Vue框架的深度实践。