react:组件基础

差别

这里会显示出您选择的修订版和当前版本之间的差别。

到此差别页面的链接

后一修订版
前一修订版
react:组件基础 [2026/03/13 15:50] – 创建 张叶安react:组件基础 [2026/04/07 11:03] (当前版本) – [5.19 useReducer - 复杂状态管理] 张叶安
行 1: 行 1:
-====== 第五章:组件基础 ======+====== 第五章:React 组件基础 ======
  
 ===== 5.1 函数组件 ===== ===== 5.1 函数组件 =====
行 428: 行 428:
  
 组件是 React 的核心概念,理解组件的各种用法是掌握 React 的关键。 组件是 React 的核心概念,理解组件的各种用法是掌握 React 的关键。
 +
 +===== 5.12 React Hooks 简介 =====
 +
 +Hooks 是 React 16.8 引入的新特性,它让我们在函数组件中使用 state 和其他 React 特性,而无需编写类组件。
 +
 +**为什么需要 Hooks**:
 +
 +  * 类组件中的 this 指向难以理解
 +  * 生命周期方法中常常包含不相关的逻辑,而相关逻辑分散在不同生命周期中
 +  * 高阶组件和 render props 会导致组件嵌套地狱
 +  * 复用状态逻辑困难
 +
 +**Hooks 使用规则**:
 +
 +  * 只在最顶层调用 Hooks,不要在循环、条件或嵌套函数中调用
 +  * 只在 React 函数组件或自定义 Hooks 中调用 Hooks
 +
 +===== 5.13 useState - 状态管理 =====
 +
 +useState 是最常用的 Hook,用于在函数组件中添加状态。
 +
 +**基本用法**:
 +
 +<code javascript>
 +import { useState } from 'react';
 +
 +function Counter() {
 +  // 声明一个叫 "count" 的 state 变量,初始值为 0
 +  const [count, setCount] = useState(0);
 +
 +  return (
 +    <div>
 +      <p>你点击了 {count} 次</p>
 +      <button onClick={() => setCount(count + 1)}>
 +        点击我
 +      </button>
 +    </div>
 +  );
 +}
 +</code>
 +
 +**解构赋值说明**:
 +
 +`const [count, setCount] = useState(0)` 是数组解构:
 +- count: 当前状态值
 +- setCount: 更新状态的函数
 +
 +**函数式更新**:
 +
 +当新状态依赖于旧状态时,使用函数式更新可以避免闭包问题。
 +
 +<code javascript>
 +function Counter() {
 +  const [count, setCount] = useState(0);
 +
 +  // 正确:使用函数式更新
 +  const increment = () => {
 +    setCount(prevCount => prevCount + 1);
 +  };
 +
 +  return (
 +    <div>
 +      <p>{count}</p>
 +      <button onClick={increment}>增加</button>
 +    </div>
 +  );
 +}
 +</code>
 +
 +**对象状态**:
 +
 +当 state 是对象时,更新时需要展开旧值。
 +
 +<code javascript>
 +function UserForm() {
 +  const [user, setUser] = useState({
 +    name: '',
 +    age: 0,
 +    email: ''
 +  });
 +
 +  const handleChange = (field, value) => {
 +    setUser(prevUser => ({
 +      ...prevUser,  // 展开旧值
 +      [field]: value // 更新特定字段
 +    }));
 +  };
 +
 +  return (
 +    <div>
 +      <input 
 +        value={user.name} 
 +        onChange={e => handleChange('name', e.target.value)}
 +        placeholder="姓名"
 +      />
 +    </div>
 +  );
 +}
 +</code>
 +
 +**延迟初始化**:
 +
 +当初始值计算开销较大时,可以传入函数进行延迟初始化。
 +
 +<code javascript>
 +function ExpensiveComponent() {
 +  // 只会在首次渲染时执行 computeExpensiveValue
 +  const [value, setValue] = useState(() => computeExpensiveValue());
 +  
 +  // ...
 +}
 +</code>
 +
 +===== 5.14 useEffect - 副作用管理 =====
 +
 +useEffect 是 React Hooks 中最重要、最基础的 Hook 之一,用于在函数组件中执行副作用操作。
 +
 +**基本语法**:
 +
 +<code javascript>
 +import { useEffect } from 'react';
 +
 +function Example() {
 +  useEffect(() => {
 +    // 副作用逻辑
 +    console.log('组件挂载或更新');
 +    
 +    // 可选的清理函数
 +    return () => {
 +      console.log('清理工作');
 +    };
 +  }, [/* 依赖数组 */]);
 +  
 +  return <div>示例组件</div>;
 +}
 +</code>
 +
 +**执行时机**:
 +
 +<code javascript>
 +// 1. 每次渲染后都执行
 +useEffect(() => {
 +  console.log('每次渲染都会执行');
 +});
 +
 +// 2. 只在挂载时执行(空依赖数组)
 +useEffect(() => {
 +  console.log('只在挂载时执行');
 +  fetchData();
 +}, []);
 +
 +// 3. 依赖变化时执行
 +useEffect(() => {
 +  console.log(`count 变化为: ${count}`);
 +  document.title = `点击了 ${count} 次`;
 +}, [count]);
 +</code>
 +
 +**清理函数(Cleanup)**:
 +
 +<code javascript>
 +function ChatRoom({ roomId }) {
 +  useEffect(() => {
 +    // 建立连接
 +    const connection = createConnection(roomId);
 +    connection.connect();
 +    console.log(`连接到房间: ${roomId}`);
 +    
 +    // 返回清理函数
 +    return () => {
 +      connection.disconnect();
 +      console.log(`断开房间: ${roomId} 的连接`);
 +    };
 +  }, [roomId]);
 +  
 +  return <div>聊天房间: {roomId}</div>;
 +}
 +</code>
 +
 +**数据获取模式**:
 +
 +<code javascript>
 +function UserProfile({ userId }) {
 +  const [user, setUser] = useState(null);
 +  const [loading, setLoading] = useState(true);
 +  const [error, setError] = useState(null);
 +  
 +  useEffect(() => {
 +    let isCancelled = false;
 +    const controller = new AbortController();
 +    
 +    async function fetchUser() {
 +      try {
 +        setLoading(true);
 +        const response = await fetch(`/api/users/${userId}`, {
 +          signal: controller.signal
 +        });
 +        const data = await response.json();
 +        
 +        if (!isCancelled) {
 +          setUser(data);
 +          setError(null);
 +        }
 +      } catch (err) {
 +        if (!isCancelled && err.name !== 'AbortError') {
 +          setError(err.message);
 +        }
 +      } finally {
 +        if (!isCancelled) {
 +          setLoading(false);
 +        }
 +      }
 +    }
 +    
 +    fetchUser();
 +    
 +    return () => {
 +      isCancelled = true;
 +      controller.abort();
 +    };
 +  }, [userId]);
 +  
 +  if (loading) return <div>加载中...</div>;
 +  if (error) return <div>错误: {error}</div>;
 +  
 +  return (
 +    <div>
 +      <h2>{user.name}</h2>
 +      <p>{user.email}</p>
 +    </div>
 +  );
 +}
 +</code>
 +
 +**useEffect 与 useLayoutEffect**:
 +
 +<code javascript>
 +import { useLayoutEffect } from 'react';
 +
 +function MeasureExample() {
 +  const divRef = useRef(null);
 +  const [width, setWidth] = useState(0);
 +  
 +  useLayoutEffect(() => {
 +    // 在浏览器绘制前测量 DOM
 +    const { width } = divRef.current.getBoundingClientRect();
 +    setWidth(width);
 +  }, []);
 +  
 +  return (
 +    <div ref={divRef}>
 +      宽度: {width}px
 +    </div>
 +  );
 +}
 +</code>
 +
 +- useEffect:在浏览器绘制完成后异步执行,不会阻塞视觉更新
 +- useLayoutEffect:在浏览器绘制之前同步执行,会阻塞视觉更新
 +
 +绝大多数情况下应该使用 useEffect,只有在需要同步测量/修改 DOM 时才使用 useLayoutEffect。
 +
 +
 +===== 5.15 useRef - DOM 引用与持久化值 =====
 +
 +useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。这个对象在组件的整个生命周期内保持不变。
 +
 +**访问 DOM 元素**:
 +
 +<code javascript>
 +import { useRef } from 'react';
 +
 +function TextInput() {
 +  const inputRef = useRef(null);
 +
 +  const focusInput = () => {
 +    // 直接访问 DOM 元素
 +    inputRef.current.focus();
 +  };
 +
 +  return (
 +    <div>
 +      <input ref={inputRef} type="text" placeholder="输入内容" />
 +      <button onClick={focusInput}>聚焦输入框</button>
 +    </div>
 +  );
 +}
 +</code>
 +
 +**保存上一次的值**:
 +
 +<code javascript>
 +function Counter() {
 +  const [count, setCount] = useState(0);
 +  const prevCountRef = useRef();
 +
 +  useEffect(() => {
 +    // 在渲染后保存当前值,供下一次渲染使用
 +    prevCountRef.current = count;
 +  });
 +
 +  const prevCount = prevCountRef.current;
 +
 +  return (
 +    <div>
 +      <p>当前: {count}, 上一次: {prevCount}</p>
 +      <button onClick={() => setCount(c => c + 1)}>增加</button>
 +    </div>
 +  );
 +}
 +</code>
 +
 +**保存定时器 ID**:
 +
 +<code javascript>
 +function Timer() {
 +  const [seconds, setSeconds] = useState(0);
 +  const [isRunning, setIsRunning] = useState(false);
 +  const intervalRef = useRef(null);
 +
 +  const start = () => {
 +    if (!isRunning) {
 +      setIsRunning(true);
 +      intervalRef.current = setInterval(() => {
 +        setSeconds(s => s + 1);
 +      }, 1000);
 +    }
 +  };
 +
 +  const stop = () => {
 +    if (isRunning) {
 +      setIsRunning(false);
 +      clearInterval(intervalRef.current);
 +    }
 +  };
 +
 +  const reset = () => {
 +    stop();
 +    setSeconds(0);
 +  };
 +
 +  useEffect(() => {
 +    // 组件卸载时清理定时器
 +    return () => {
 +      if (intervalRef.current) {
 +        clearInterval(intervalRef.current);
 +      }
 +    };
 +  }, []);
 +
 +  return (
 +    <div>
 +      <p>{seconds} 秒</p>
 +      <button onClick={start}>开始</button>
 +      <button onClick={stop}>停止</button>
 +      <button onClick={reset}>重置</button>
 +    </div>
 +  );
 +}
 +</code>
 +
 +**转发 ref(forwardRef)**:
 +
 +<code javascript>
 +import { forwardRef, useRef } from 'react';
 +
 +// 子组件转发 ref 到内部 DOM 元素
 +const FancyInput = forwardRef((props, ref) => {
 +  return <input ref={ref} className="fancy-input" {...props} />;
 +});
 +
 +// 父组件使用
 +function Parent() {
 +  const inputRef = useRef(null);
 +
 +  const focusInput = () => {
 +    inputRef.current.focus();
 +  };
 +
 +  return (
 +    <div>
 +      <FancyInput ref={inputRef} placeholder="输入内容" />
 +      <button onClick={focusInput}>聚焦</button>
 +    </div>
 +  );
 +}
 +</code>
 +
 +**useImperativeHandle 自定义暴露的实例值**:
 +
 +<code javascript>
 +import { forwardRef, useRef, useImperativeHandle } from 'react';
 +
 +const FancyInput = forwardRef((props, ref) => {
 +  const inputRef = useRef(null);
 +
 +  // 自定义暴露给父组件的方法
 +  useImperativeHandle(ref, () => ({
 +    focus: () => {
 +      inputRef.current.focus();
 +    },
 +    clear: () => {
 +      inputRef.current.value = '';
 +    },
 +    getValue: () => {
 +      return inputRef.current.value;
 +    }
 +  }));
 +
 +  return <input ref={inputRef} {...props} />;
 +});
 +
 +// 父组件使用
 +function Parent() {
 +  const fancyInputRef = useRef(null);
 +
 +  const handleClick = () => {
 +    fancyInputRef.current.focus();
 +    console.log(fancyInputRef.current.getValue());
 +    fancyInputRef.current.clear();
 +  };
 +
 +  return (
 +    <div>
 +      <FancyInput ref={fancyInputRef} />
 +      <button onClick={handleClick}>操作输入框</button>
 +    </div>
 +  );
 +}
 +</code>
 +
 +===== 5.16 useCallback - 缓存回调函数 =====
 +
 +useCallback 返回一个 memoized 回调函数。只有当依赖项发生变化时,才会返回新的函数。
 +
 +**基本用法**:
 +
 +<code javascript>
 +import { useCallback } from 'react';
 +
 +function Parent() {
 +  const [count, setCount] = useState(0);
 +  const [text, setText] = useState('');
 +
 +  // 有 useCallback:只在 count 变化时创建新函数
 +  const handleClick = useCallback(() => {
 +    console.log('count:', count);
 +  }, [count]);
 +
 +  return (
 +    <div>
 +      <button onClick={() => setCount(c => c + 1)}>增加</button>
 +      <input value={text} onChange={e => setText(e.target.value)} />
 +      <Child onClick={handleClick} />
 +    </div>
 +  );
 +}
 +</code>
 +
 +**配合 React.memo 使用**:
 +
 +<code javascript>
 +import { memo, useCallback, useState } from 'react';
 +
 +// 子组件使用 React.memo 进行浅比较
 +const Child = memo(({ onClick, label }) => {
 +  console.log(`${label} 渲染`);
 +  return <button onClick={onClick}>{label}</button>;
 +});
 +
 +function Parent() {
 +  const [count1, setCount1] = useState(0);
 +  const [count2, setCount2] = useState(0);
 +
 +  // 使用 useCallback 缓存函数
 +  const handleClick1 = useCallback(() => {
 +    setCount1(c => c + 1);
 +  }, []);
 +
 +  const handleClick2 = useCallback(() => {
 +    setCount2(c => c + 1);
 +  }, []);
 +
 +  return (
 +    <div>
 +      <p>Count1: {count1}, Count2: {count2}</p>
 +      <Child onClick={handleClick1} label="增加 Count1" />
 +      <Child onClick={handleClick2} label="增加 Count2" />
 +    </div>
 +  );
 +}
 +</code>
 +
 +**何时使用 useCallback**:
 +
 +  * 函数作为 props 传递给子组件,且子组件使用了 React.memo
 +  * 函数作为 useEffect 的依赖项
 +  * 函数被其他 Hooks(如 useMemo)依赖
 +  * 函数是一个复杂计算或创建成本较高
 +
 +**避免过度使用**:
 +
 +<code javascript>
 +// 不需要 useCallback 的情况:简单函数直接内联
 +function SimpleComponent() {
 +  const [count, setCount] = useState(0);
 +
 +  // 简单函数,不需要 useCallback
 +  const increment = () => setCount(c => c + 1);
 +
 +  return <button onClick={increment}>{count}</button>;
 +}
 +</code>
 +
 +
 +===== 5.17 useMemo - 缓存计算结果 =====
 +
 +useMemo 返回一个 memoized 值。只有当依赖项发生变化时,才会重新计算。
 +
 +**基本用法**:
 +
 +<code javascript>
 +import { useMemo } from 'react';
 +
 +function ExpensiveComponent({ data, filter }) {
 +  // 使用 useMemo 缓存昂贵的计算结果
 +  const filteredData = useMemo(() => {
 +    console.log('计算过滤数据');
 +    return data.filter(item => item.name.includes(filter));
 +  }, [data, filter]);
 +
 +  return (
 +    <ul>
 +      {filteredData.map(item => <li key={item.id}>{item.name}</li>)}
 +    </ul>
 +  );
 +}
 +</code>
 +
 +**对象/数组的稳定性**:
 +
 +<code javascript>
 +function Chart({ data, options }) {
 +  // 使用 useMemo 保持对象的引用稳定
 +  const chartOptions = useMemo(() => ({
 +    responsive: true,
 +    scales: {
 +      y: { beginAtZero: options.startFromZero }
 +    }
 +  }), [options.startFromZero]);
 +
 +  // chartOptions 的引用只在依赖变化时改变
 +  useEffect(() => {
 +    console.log('options 变化,重新初始化图表');
 +    initChart(chartOptions);
 +  }, [chartOptions]);
 +
 +  return <canvas id="chart" />;
 +}
 +</code>
 +
 +**复杂数据的处理**:
 +
 +<code javascript>
 +function DataTable({ rows, sortKey, sortOrder }) {
 +  const sortedRows = useMemo(() => {
 +    return [...rows].sort((a, b) => {
 +      const aVal = a[sortKey];
 +      const bVal = b[sortKey];
 +      
 +      if (sortOrder === 'asc') {
 +        return aVal > bVal ? 1 : -1;
 +      } else {
 +        return aVal < bVal ? 1 : -1;
 +      }
 +    });
 +  }, [rows, sortKey, sortOrder]);
 +
 +  return (
 +    <table>
 +      <tbody>
 +        {sortedRows.map(row => (
 +          <tr key={row.id}>
 +            <td>{row.name}</td>
 +            <td>{row.value}</td>
 +          </tr>
 +        ))}
 +      </tbody>
 +    </table>
 +  );
 +}
 +</code>
 +
 +**useMemo vs useCallback**:
 +
 +<code javascript>
 +// useCallback(fn, deps) 等同于 useMemo(() => fn, deps)
 +
 +// useCallback - 缓存函数
 +const memoizedCallback = useCallback(() => {
 +  doSomething(a, b);
 +}, [a, b]);
 +
 +// useMemo - 缓存值
 +const memoizedValue = useMemo(() => {
 +  return computeExpensiveValue(a, b);
 +}, [a, b]);
 +</code>
 +
 +===== 5.18 useContext - 跨组件状态共享 =====
 +
 +useContext 让我们无需为每层组件手动添加 props,就能在组件树间进行数据传递。
 +
 +**创建和使用 Context**:
 +
 +<code javascript>
 +import { createContext, useContext, useState } from 'react';
 +
 +// 1. 创建 Context
 +const ThemeContext = createContext('light');
 +
 +// 2. 提供 Context
 +function App() {
 +  const [theme, setTheme] = useState('light');
 +
 +  return (
 +    <ThemeContext.Provider value={{ theme, setTheme }}>
 +      <Toolbar />
 +    </ThemeContext.Provider>
 +  );
 +}
 +
 +// 3. 消费 Context
 +function Toolbar() {
 +  return (
 +    <div>
 +      <ThemeButton />
 +    </div>
 +  );
 +}
 +
 +function ThemeButton() {
 +  // 使用 useContext 获取上下文值
 +  const { theme, setTheme } = useContext(ThemeContext);
 +
 +  return (
 +    <button
 +      style={{ background: theme === 'dark' ? '#333' : '#fff' }}
 +      onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
 +    >
 +      当前主题: {theme}
 +    </button>
 +  );
 +}
 +</code>
 +
 +**多 Context 使用**:
 +
 +<code javascript>
 +const ThemeContext = createContext('light');
 +const UserContext = createContext(null);
 +
 +function App() {
 +  const [theme, setTheme] = useState('light');
 +  const [user, setUser] = useState({ name: '张三' });
 +
 +  return (
 +    <ThemeContext.Provider value={{ theme, setTheme }}>
 +      <UserContext.Provider value={{ user, setUser }}>
 +        <Layout />
 +      </UserContext.Provider>
 +    </ThemeContext.Provider>
 +  );
 +}
 +
 +function Layout() {
 +  const { theme } = useContext(ThemeContext);
 +  const { user } = useContext(UserContext);
 +
 +  return (
 +    <div className={theme}>
 +      <h1>欢迎, {user.name}</h1>
 +    </div>
 +  );
 +}
 +</code>
 +
 +**Context 分割优化**:
 +
 +<code javascript>
 +// 不好的做法:整个 context 变化导致所有消费者重新渲染
 +const AppContext = createContext({
 +  theme: 'light',
 +  user: null,
 +  setTheme: () => {},
 +  setUser: () => {}
 +});
 +
 +// 更好的做法:将经常变化和很少变化的数据分开
 +const ThemeContext = createContext({ theme: 'light', setTheme: () => {} });
 +const UserContext = createContext({ user: null, setUser: () => {} });
 +
 +// 最佳做法:使用自定义 Hook 封装 context
 +function useTheme() {
 +  const context = useContext(ThemeContext);
 +  if (!context) {
 +    throw new Error('useTheme 必须在 ThemeProvider 内部使用');
 +  }
 +  return context;
 +}
 +</code>
 +
 +
 +===== 5.19 useReducer - 复杂状态管理 =====
 +
 +useReducer 是 useState 的替代方案,适用于 state 逻辑较复杂或包含多个子值的情况。
 +
 +**基本用法**:
 +
 +<code javascript>
 +import { useReducer } from 'react';
 +
 +// 定义 reducer 函数
 +function reducer(state, action) {
 +  switch (action.type) {
 +    case 'increment':
 +      return { count: state.count + 1 };
 +    case 'decrement':
 +      return { count: state.count - 1 };
 +    case 'reset':
 +      return { count: 0 };
 +    default:
 +      throw new Error('未知 action');
 +  }
 +}
 +
 +function Counter() {
 +  const [state, dispatch] = useReducer(reducer, { count: 0 });
 +
 +  return (
 +    <div>
 +      <p>Count: {state.count}</p>
 +      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
 +      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
 +      <button onClick={() => dispatch({ type: 'reset' })}>重置</button>
 +    </div>
 +  );
 +}
 +</code>
 +
 +**复杂状态管理**:
 +
 +<code javascript>
 +const initialState = {
 +  loading: false,
 +  data: null,
 +  error: null
 +};
 +
 +function dataReducer(state, action) {
 +  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 };
 +    default:
 +      return state;
 +  }
 +}
 +
 +function DataComponent() {
 +  const [state, dispatch] = useReducer(dataReducer, initialState);
 +
 +  const fetchData = async () => {
 +    dispatch({ type: 'FETCH_START' });
 +    try {
 +      const response = await fetch('/api/data');
 +      const data = await response.json();
 +      dispatch({ type: 'FETCH_SUCCESS', payload: data });
 +    } catch (error) {
 +      dispatch({ type: 'FETCH_ERROR', payload: error.message });
 +    }
 +  };
 +
 +  return (
 +    <div>
 +      {state.loading && <p>加载中...</p>}
 +      {state.error && <p>错误: {state.error}</p>}
 +      {state.data && <p>数据: {JSON.stringify(state.data)}</p>}
 +      <button onClick={fetchData}>获取数据</button>
 +    </div>
 +  );
 +}
 +</code>
 +
 +**useReducer vs useState**:
 +
 +| 场景 | 推荐方案 |
 +| 简单状态(单个值) | useState |
 +| 复杂状态(多个子值相互依赖) | useReducer |
 +| 需要优化性能,state 更新逻辑复杂 | useReducer |
 +| 需要复用状态逻辑 | useReducer + 自定义 Hook |
 +
 +===== 5.20 其他常用 Hooks =====
 +
 +**useId - 生成唯一 ID**:
 +
 +<code javascript>
 +import { useId } from 'react';
 +
 +function Form() {
 +  const id = useId();
 +  
 +  return (
 +    <div>
 +      <label htmlFor={id + '-name'}>姓名:</label>
 +      <input id={id + '-name'} type="text" />
 +      
 +      <label htmlFor={id + '-email'}>邮箱:</label>
 +      <input id={id + '-email'} type="email" />
 +    </div>
 +  );
 +}
 +</code>
 +
 +**useTransition - 非紧急更新**:
 +
 +<code javascript>
 +import { useTransition, useState } from 'react';
 +
 +function TabContainer() {
 +  const [isPending, startTransition] = useTransition();
 +  const [tab, setTab] = useState('home');
 +
 +  const selectTab = (nextTab) => {
 +    // 将状态更新标记为低优先级
 +    startTransition(() => {
 +      setTab(nextTab);
 +    });
 +  };
 +
 +  return (
 +    <div>
 +      {isPending && <p>加载中...</p>}
 +      <TabButton onClick={() => selectTab('home')}>首页</TabButton>
 +      <TabButton onClick={() => selectTab('about')}>关于</TabButton>
 +      {tab === 'home' && <HomeTab />}
 +      {tab === 'about' && <AboutTab />}
 +    </div>
 +  );
 +}
 +</code>
 +
 +**useDeferredValue - 延迟更新值**:
 +
 +<code javascript>
 +import { useDeferredValue, useState } from 'react';
 +
 +function SearchResults() {
 +  const [query, setQuery] = useState('');
 +  const deferredQuery = useDeferredValue(query);
 +
 +  return (
 +    <div>
 +      <input 
 +        value={query} 
 +        onChange={e => setQuery(e.target.value)}
 +        placeholder="搜索..."
 +      />
 +      {/* 使用 deferredQuery 显示结果,保持输入流畅 */}
 +      <SlowList query={deferredQuery} />
 +    </div>
 +  );
 +}
 +</code>
 +
 +
 +===== 5.21 自定义 Hooks =====
 +
 +自定义 Hooks 是提取组件逻辑到可复用函数的方式。自定义 Hook 是一个函数,其名称以 "use" 开头,内部可以调用其他 Hooks。
 +
 +**useFetch - 数据获取**:
 +
 +<code javascript>
 +import { useState, useEffect } from 'react';
 +
 +function useFetch(url) {
 +  const [data, setData] = useState(null);
 +  const [loading, setLoading] = useState(true);
 +  const [error, setError] = useState(null);
 +
 +  useEffect(() => {
 +    const controller = new AbortController();
 +    
 +    async function fetchData() {
 +      try {
 +        setLoading(true);
 +        const response = await fetch(url, { signal: controller.signal });
 +        if (!response.ok) {
 +          throw new Error(`HTTP 错误: ${response.status}`);
 +        }
 +        const result = await response.json();
 +        setData(result);
 +        setError(null);
 +      } catch (err) {
 +        if (err.name !== 'AbortError') {
 +          setError(err.message);
 +          setData(null);
 +        }
 +      } finally {
 +        setLoading(false);
 +      }
 +    }
 +    
 +    fetchData();
 +    
 +    return () => controller.abort();
 +  }, [url]);
 +
 +  return { data, loading, error };
 +}
 +
 +// 使用
 +function UserList() {
 +  const { data: users, loading, error } = useFetch('/api/users');
 +  
 +  if (loading) return <div>加载中...</div>;
 +  if (error) return <div>错误: {error}</div>;
 +  
 +  return (
 +    <ul>
 +      {users.map(user => <li key={user.id}>{user.name}</li>)}
 +    </ul>
 +  );
 +}
 +</code>
 +
 +**useLocalStorage - 本地存储同步**:
 +
 +<code javascript>
 +import { useState, useEffect } from 'react';
 +
 +function useLocalStorage(key, initialValue) {
 +  // 获取初始值
 +  const [storedValue, setStoredValue] = useState(() => {
 +    try {
 +      const item = window.localStorage.getItem(key);
 +      return item ? JSON.parse(item) : initialValue;
 +    } catch (error) {
 +      console.error(error);
 +      return initialValue;
 +    }
 +  });
 +
 +  // 当值变化时更新 localStorage
 +  useEffect(() => {
 +    try {
 +      window.localStorage.setItem(key, JSON.stringify(storedValue));
 +    } catch (error) {
 +      console.error(error);
 +    }
 +  }, [key, storedValue]);
 +
 +  return [storedValue, setStoredValue];
 +}
 +
 +// 使用
 +function App() {
 +  const [name, setName] = useLocalStorage('name', '');
 +  
 +  return (
 +    <input 
 +      value={name} 
 +      onChange={e => setName(e.target.value)}
 +      placeholder="输入你的名字"
 +    />
 +  );
 +}
 +</code>
 +
 +**useDebounce - 防抖**:
 +
 +<code javascript>
 +import { useState, useEffect } from 'react';
 +
 +function useDebounce(value, delay) {
 +  const [debouncedValue, setDebouncedValue] = useState(value);
 +
 +  useEffect(() => {
 +    const timer = setTimeout(() => {
 +      setDebouncedValue(value);
 +    }, delay);
 +
 +    return () => {
 +      clearTimeout(timer);
 +    };
 +  }, [value, delay]);
 +
 +  return debouncedValue;
 +}
 +
 +// 使用
 +function SearchInput() {
 +  const [input, setInput] = useState('');
 +  const debouncedInput = useDebounce(input, 500);
 +
 +  useEffect(() => {
 +    if (debouncedInput) {
 +      performSearch(debouncedInput);
 +    }
 +  }, [debouncedInput]);
 +
 +  return (
 +    <input
 +      value={input}
 +      onChange={(e) => setInput(e.target.value)}
 +      placeholder="搜索..."
 +    />
 +  );
 +}
 +</code>
 +
 +**usePrevious - 获取上一次的值**:
 +
 +<code javascript>
 +import { useRef, useEffect } from 'react';
 +
 +function usePrevious(value) {
 +  const ref = useRef();
 +  
 +  useEffect(() => {
 +    ref.current = value;
 +  });
 +  
 +  return ref.current;
 +}
 +
 +// 使用
 +function Counter() {
 +  const [count, setCount] = useState(0);
 +  const prevCount = usePrevious(count);
 +  
 +  return (
 +    <div>
 +      <p>当前: {count}, 上一次: {prevCount}</p>
 +      <button onClick={() => setCount(c => c + 1)}>增加</button>
 +    </div>
 +  );
 +}
 +</code>
 +
 +**useOnClickOutside - 点击外部关闭**:
 +
 +<code javascript>
 +import { useEffect, useRef } from 'react';
 +
 +function useOnClickOutside(ref, handler) {
 +  useEffect(() => {
 +    const listener = (event) => {
 +      // 如果点击的是 ref 元素内部,不执行 handler
 +      if (!ref.current || ref.current.contains(event.target)) {
 +        return;
 +      }
 +      handler(event);
 +    };
 +
 +    document.addEventListener('mousedown', listener);
 +    document.addEventListener('touchstart', listener);
 +
 +    return () => {
 +      document.removeEventListener('mousedown', listener);
 +      document.removeEventListener('touchstart', listener);
 +    };
 +  }, [ref, handler]);
 +}
 +
 +// 使用
 +function Dropdown() {
 +  const ref = useRef();
 +  const [isOpen, setIsOpen] = useState(false);
 +  
 +  useOnClickOutside(ref, () => setIsOpen(false));
 +  
 +  return (
 +    <div ref={ref}>
 +      <button onClick={() => setIsOpen(!isOpen)}>切换</button>
 +      {isOpen && <div className="dropdown">下拉内容</div>}
 +    </div>
 +  );
 +}
 +</code>
 +
 +===== 5.22 Hooks 最佳实践 =====
 +
 +**1. 只在最顶层调用 Hooks**:
 +
 +<code javascript>
 +// 正确
 +function Example() {
 +  const [count, setCount] = useState(0);
 +  useEffect(() => { });
 +  
 +  if (condition) {  // 条件在 Hooks 之后
 +    return;
 +  }
 +}
 +
 +// 错误 - 条件中使用 Hook
 +function Example() {
 +  if (condition) {
 +    const [count, setCount] = useState(0);  // 错误!
 +  }
 +}
 +</code>
 +
 +**2. 只在 React 函数中调用 Hooks**:
 +
 +<code javascript>
 +// 正确 - 在函数组件中
 +function MyComponent() {
 +  const [state, setState] = useState(0);
 +}
 +
 +// 正确 - 在自定义 Hook 中
 +function useMyHook() {
 +  const [state, setState] = useState(0);
 +}
 +</code>
 +
 +**3. 使用 ESLint 插件**:
 +
 +<code json>
 +{
 +  "plugins": ["react-hooks"],
 +  "rules": {
 +    "react-hooks/rules-of-hooks": "error",
 +    "react-hooks/exhaustive-deps": "warn"
 +  }
 +}
 +</code>
 +
 +**4. 分离不相关的副作用**:
 +
 +<code javascript>
 +// 好的做法:分离关注点
 +function Example() {
 +  // 处理订阅
 +  useEffect(() => {
 +    const subscription = subscribe();
 +    return () => subscription.unsubscribe();
 +  }, []);
 +  
 +  // 处理 DOM 操作
 +  useEffect(() => {
 +    document.title = '新标题';
 +  }, []);
 +}
 +</code>
 +
 +**5. 避免过度优化**:
 +
 +<code javascript>
 +// 不需要 useMemo 的简单计算
 +const doubled = count * 2;
 +
 +// 不需要 useCallback 的简单函数
 +const handleClick = () => setCount(c => c + 1);
 +</code>
 +
 +===== 5.23 常见陷阱与解决方案 =====
 +
 +**陷阱 1:useEffect 无限循环**
 +
 +<code javascript>
 +// 错误:setState 导致重新渲染,形成无限循环
 +useEffect(() => {
 +  setCount(count + 1);
 +}, [count]);
 +
 +// 正确:使用函数式更新
 +useEffect(() => {
 +  const timer = setTimeout(() => {
 +    setCount(c => c + 1);
 +  }, 1000);
 +  return () => clearTimeout(timer);
 +}, []);
 +</code>
 +
 +**陷阱 2:遗漏依赖**
 +
 +<code javascript>
 +// 错误:遗漏了 count 依赖
 +useEffect(() => {
 +  console.log(`当前计数: ${count}`);
 +}, []); // 警告:count 应该在依赖数组中
 +
 +// 正确:添加所有依赖
 +useEffect(() => {
 +  console.log(`当前计数: ${count}`);
 +}, [count]);
 +</code>
 +
 +**陷阱 3:对象和数组依赖**
 +
 +<code javascript>
 +// 问题:options 每次都是新对象,导致 effect 每次都执行
 +useEffect(() => {
 +  fetchData(options);
 +}, [options]);
 +
 +// 方案1:展开为原始值
 +useEffect(() => {
 +  fetchData({ page, size });
 +}, [page, size]);
 +
 +// 方案2:使用 useMemo
 +const options = useMemo(() => ({ page, size }), [page, size]);
 +</code>
 +
 +===== 5.24 总结 =====
 +
 +本章全面介绍了 React 组件和 Hooks:
 +
 +**组件基础**:
 +  * 函数组件和类组件的定义与区别
 +  * Props 传递、默认值和类型检查
 +  * 组件组合、children 和 render props
 +  * 高阶组件和组件组织方式
 +
 +**核心 Hooks**:
 +  * useState:状态管理
 +  * useEffect:副作用处理
 +  * useRef:DOM 引用和持久化值
 +  * useCallback:缓存回调函数
 +  * useMemo:缓存计算结果
 +  * useContext:跨组件状态共享
 +  * useReducer:复杂状态管理
 +
 +**其他 Hooks**:
 +  * useId:生成唯一 ID
 +  * useTransition:非紧急更新
 +  * useDeferredValue:延迟更新值
 +
 +**自定义 Hooks**:
 +  * 封装可复用逻辑
 +  * 常见的自定义 Hooks 模式
 +
 +**最佳实践**:
 +  * Hooks 使用规则
 +  * 性能优化策略
 +  * 常见陷阱与解决方案
 +
 +掌握这些知识后,你可以使用函数组件和 Hooks 构建高质量的 React 应用。记住 Hooks 的核心原则:只在最顶层调用、只在 React 函数中调用、保持依赖数组完整。
  

该主题尚不存在

您访问的页面并不存在。如果允许,您可以使用创建该页面按钮来创建它。

  • react/组件基础.1773388234.txt.gz
  • 最后更改: 2026/03/13 15:50
  • 张叶安