react:组件基础

差别

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

到此差别页面的链接

两侧同时换到之前的修订记录 前一修订版
后一修订版
前一修订版
react:组件基础 [2026/04/07 09:52] 张叶安react:组件基础 [2026/04/07 11:03] (当前版本) – [5.19 useReducer - 复杂状态管理] 张叶安
行 1: 行 1:
-====== 第五章:组件基础 ======+====== 第五章:React 组件基础 ======
  
 ===== 5.1 函数组件 ===== ===== 5.1 函数组件 =====
行 429: 行 429:
 组件是 React 的核心概念,理解组件的各种用法是掌握 React 的关键。 组件是 React 的核心概念,理解组件的各种用法是掌握 React 的关键。
  
-====== useEffect 副作用管理 ======+===== 5.12 React Hooks 简介 =====
  
-===== 6.1 useEffect 简介 =====+Hooks 是 React 16.8 引入的新特性,它让我们在函数组件中使用 state 和其他 React 特性,而无需编写类组件。
  
-useEffect 是 React Hooks 中最重、最基础的 Hook 之一,用于在函数组件中执行副作用操作。所谓副作用,是指那些不直接参与 UI 渲染、但会对组件外部产生影响的操作,例如数据获取、订阅、手动修改 DOM 等。+**为什么需要 Hooks**:
  
-类组件时代,我们通常使用生命周期方法(如 componentDidMount、componentDidUpdate、componentWillUnmount)来处理副作用。而 useEffect 出现让我们能够函数组件中以更简洁、更统一的方式来处理这些场景。+  * 类组件中的 this 指向难以理解 
 +  * 生命周期方法中常常包含不相关逻辑而相关逻辑分散不同生命周期中 
 +  * 高阶组件和 render props 会导致组件嵌套地狱 
 +  * 复用状态逻辑困难
  
-**基本法**:+**Hooks 使用规则**: 
 + 
 +  * 只在最顶层调用 Hooks,不要在循环、条件或嵌套函数中调用 
 +  * 只在 React 函数组件或自定义 Hooks 中调用 Hooks 
 + 
 +===== 5.13 useState - 状态管理 ===== 
 + 
 +useState 是最常用的 Hook,用于在函数组件中添加状态。 
 + 
 +**基本法**:
  
 <code javascript> <code javascript>
-import { useEffect } from 'react';+import { useState } from 'react';
  
-function Example() { +function Counter() { 
-  useEffect(() => { +  // 声明一个叫 "count" 的 state 变量,初始值为 0 
-    // 副作用逻辑 +  const [count, setCount] = useState(0); 
-    console.log('组件挂载或更新'); + 
-     +  return ( 
-    // 可选的清理函数 +    <div> 
-    return () => +      <p>你点击了 {count} 次</p> 
-      console.log('清理工作')+      <button onClick={() => setCount(count + 1)}> 
-    }; +        点击我 
-  }, [/* 依赖数组 */]); +      </button> 
-   +    </div> 
-  return <div>示例组件</div>;+  );
 } }
 </code> </code>
  
-useEffect 接收两个参数第一个参数是一个执行副作用的函数,该函数可以返回一个清理函数(可选);第二个参数是依赖数组,用于控制 effect 的执行时机。+**解构赋值说明**
  
-===== 6.2 useEffect 执行时机 =====+`const [count, setCount] useState(0)` 是数组解构: 
 +- count: 当前状态值 
 +- setCount: 更新状态函数
  
-理解 useEffect 的执行时机是掌握它的关键。默认情况下,React 会在每次渲染完成后执行 useEffect 中的副作用函数+**函数式更新**:
  
-**挂载执行**:+当新状态依赖于旧状态,使用函数式更新可以避免闭包问题。
  
-当依赖数组为空数组 `[]时,effect 只在组件挂载执行一次,类似于类组件的 componentDidMount+<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> <code javascript>
-function UserList() { +function UserForm() { 
-  useEffect(() =+  const [user, setUser] useState(
-    console.log('组件挂载,开始获取用户数据'); +    name: '', 
-    fetchUsers(); +    age: 0, 
-  }, []); // 空依赖数组,只在挂载时执行 +    email: '' 
-   +  }); 
-  return <div>用户列表</div>;+ 
 +  const handleChange = (fieldvalue) => { 
 +    setUser(prevUser => ({ 
 +      ...prevUser,  // 展开旧值 
 +      [field]: value // 更新特定字段 
 +    })); 
 +  }; 
 + 
 +  return 
 +    <div> 
 +      <input  
 +        value={user.name}  
 +        onChange={e => handleChange('name', e.target.value)} 
 +        placeholder="姓名" 
 +      /> 
 +    </div> 
 +  );
 } }
 </code> </code>
  
-**依赖变时执行**:+**延迟初始化**:
  
-依赖数组中包含特定的 state 或 props 时,只有当这些依赖项发生变时,effect 才会重新执行+初始值计算开销较大时,可以传入函数进行延迟初始化。
  
 <code javascript> <code javascript>
-function Counter({ userId }) { +function ExpensiveComponent() { 
-  const [countsetCount] = useState(0);+  // 只会在首次渲染时执行 computeExpensiveValue 
 +  const [valuesetValue] = useState(() => computeExpensiveValue());
      
-  // 当 userId 变化时执行+  // ... 
 +
 +</code> 
 + 
 +===== 5.14 useEffect - 副作用管理 ===== 
 + 
 +useEffect 是 React Hooks 中最重要、最基础的 Hook 之一,用于在函数组件中执行副作用操作。 
 + 
 +**基本语法**: 
 + 
 +<code javascript> 
 +import { useEffect } from 'react'; 
 + 
 +function Example() {
   useEffect(() => {   useEffect(() => {
-    console.log(`用户ID变化为: ${userId}`); +    // 副作用逻辑 
-    fetchUserData(userId); +    console.log('组件挂载或更新'); 
-  }, [userId]);+     
 +    // 可选的清理函数 
 +    return () => { 
 +      console.log('清理工作'); 
 +    }
 +  }, [/* 依赖数组 */]);
      
-  // 当 count 变化时执行 +  return <div>示例组件</div>;
-  useEffect(() => { +
-    console.log(`计数变化为: ${count}`); +
-    document.title = `点击了 ${count} 次`; +
-  }, [count]); +
-   +
-  return <button onClick={() => setCount(count + 1)}>点击 {count}</button>;+
 } }
 </code> </code>
  
-**每次渲染都执行**: +**执行时机**:
- +
-如果不传递依赖数组,useEffect 会在每次组件渲染后都执行。这种方式在实际开发中较少使用,因为可能导致性能问题和无限循环。+
  
 <code javascript> <code javascript>
 +// 1. 每次渲染后都执行
 useEffect(() => { useEffect(() => {
   console.log('每次渲染都会执行');   console.log('每次渲染都会执行');
 }); });
-</code> 
  
-===== 6.3 清理函(Cleanup) =====+// 2只在挂载时执行(空依赖 
 +useEffect(() => { 
 +  console.log('只在挂载时执行'); 
 +  fetchData(); 
 +}, []);
  
-某些副作用需要清理,例如订阅外部数据源、设置定器、添加事件监听器等。如果在组件卸载时不清理这些副作用,可能会导致内存泄漏。 +// 3. 依赖变化执行 
- +useEffect(() => { 
-useEffect 允许通过返回一个函数来指定清理逻辑。React 会在组件卸载时执行这个清理函数,也会在重新执行 effect 之前调用它来清理上一个 effect。+  console.log(`count 变化为: ${count}`); 
 +  document.title = `点击了 ${count} 次`; 
 +}, [count]); 
 +</code>
  
-**订阅和取消订阅**:+**清理函数(Cleanup)**:
  
 <code javascript> <code javascript>
行 539: 行 607:
 </code> </code>
  
-当 roomId 从 "general" 变为 "random" 时,React 会依次执行: +**数据获取模式**:
-1. 使用 "general" 运行的 effect 的清理函数,断开旧连接 +
-2. 使用 "random" 运行的新 effect,建立新连接 +
- +
-**定时器的清理**: +
- +
-<code javascript> +
-function Timer() { +
-  const [seconds, setSeconds] = useState(0); +
-   +
-  useEffect(() => { +
-    const intervalId = setInterval(() => { +
-      setSeconds(s => s + 1); +
-    }, 1000); +
-     +
-    return () => { +
-      clearInterval(intervalId); +
-      console.log('定时器已清理'); +
-    }; +
-  }, []); +
-   +
-  return <div>已运行: {seconds} 秒</div>; +
-+
-</code> +
- +
-**事件监听器的清理**: +
- +
-<code javascript> +
-function WindowSize() { +
-  const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight }); +
-   +
-  useEffect(() => { +
-    const handleResize = () => { +
-      setSize({ width: window.innerWidth, height: window.innerHeight }); +
-    }; +
-     +
-    window.addEventListener('resize', handleResize); +
-     +
-    return () => { +
-      window.removeEventListener('resize', handleResize); +
-    }; +
-  }, []); +
-   +
-  return ( +
-    <div> +
-      窗口宽度: {size.width}px<br /> +
-      窗口高度: {size.height}px +
-    </div> +
-  ); +
-+
-</code> +
- +
-===== 6.4 数据获取模式 ===== +
- +
-数据获取是 useEffect 最常见的使用场景之一。掌握正确的数据获取模式对于避免竞态条件和内存泄漏至关重要。 +
- +
-**基本的数据获取**:+
  
 <code javascript> <code javascript>
行 605: 行 617:
   useEffect(() => {   useEffect(() => {
     let isCancelled = false;     let isCancelled = false;
 +    const controller = new AbortController();
          
     async function fetchUser() {     async function fetchUser() {
       try {       try {
         setLoading(true);         setLoading(true);
-        const response = await fetch(`/api/users/${userId}`);+        const response = await fetch(`/api/users/${userId}`, { 
 +          signal: controller.signal 
 +        });
         const data = await response.json();         const data = await response.json();
                  
-        // 检查组件是否已卸载或 userId 是否已改变 
         if (!isCancelled) {         if (!isCancelled) {
           setUser(data);           setUser(data);
行 618: 行 632:
         }         }
       } catch (err) {       } catch (err) {
-        if (!isCancelled) {+        if (!isCancelled && err.name !== 'AbortError') {
           setError(err.message);           setError(err.message);
-          setUser(null); 
         }         }
       } finally {       } finally {
行 633: 行 646:
     return () => {     return () => {
       isCancelled = true;       isCancelled = true;
 +      controller.abort();
     };     };
   }, [userId]);   }, [userId]);
行 638: 行 652:
   if (loading) return <div>加载中...</div>;   if (loading) return <div>加载中...</div>;
   if (error) return <div>错误: {error}</div>;   if (error) return <div>错误: {error}</div>;
-  if (!user) return null; 
      
   return (   return (
行 649: 行 662:
 </code> </code>
  
-**处理竞态条件**: +**useEffect 与 useLayoutEffect**:
- +
-当依赖项快速变化时(如用户快速切换选项),可能会出现竞态条件——先发出的请求后返回,导致显示错误的数据。使用取消标志或 AbortController 可以解决这个问题。+
  
 <code javascript> <code javascript>
-function SearchResults(query }) { +import useLayoutEffect from 'react'; 
-  const [resultssetResults] = useState([]);+ 
 +function MeasureExample() { 
 +  const divRef = useRef(null); 
 +  const [widthsetWidth] = 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(() => {   useEffect(() => {
-    const controller new AbortController(); +    // 在渲染后保存当前值,供下一次渲染使用 
-     +    prevCountRef.current count; 
-    async function search() { +  }); 
-      try + 
-        const response await fetch(`/api/search?q=${query}`, { +  const prevCount = prevCountRef.current; 
-          signal: controller.signal + 
-        }); +  return ( 
-        const data await response.json(); +    <div> 
-        setResults(data); +      <p>当前: {count}, 上一次: {prevCount}</p> 
-      } catch (error) { +      <button onClick={(=> setCount(c => c + 1)}>增加</button> 
-        if (error.name === 'AbortError') { +    </div> 
-          console.log('请求被取消'); +  ); 
-          return; +} 
-        +</code> 
-        console.error('搜索失败:', error); + 
-      }+**保存定时器 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);
     }     }
-     +  }; 
-    if (query) { + 
-      search(); +  const stop = () => { 
-    } else { +    if (isRunning) { 
-      setResults([]);+      setIsRunning(false); 
 +      clearInterval(intervalRef.current);
     }     }
-    +  }; 
 + 
 +  const reset = () => { 
 +    stop(); 
 +    setSeconds(0); 
 +  }; 
 + 
 +  useEffect(() => { 
 +    // 组件卸载时清理定时器
     return () => {     return () => {
-      controller.abort();+      if (intervalRef.current) { 
 +        clearInterval(intervalRef.current); 
 +      }
     };     };
-  }, [query]); +  }, []); 
-  +
   return (   return (
-    <ul+    <div
-      {results.map(item => <li key={item.id}>{item.name}</li>)+      <p>{seconds} 秒</p> 
-    </ul>+      <button onClick={start}>开始</button> 
 +      <button onClick={stop}>停止</button> 
 +      <button onClick={reset}>重置</button> 
 +    </div>
   );   );
 } }
 </code> </code>
  
-===== 6.5 多个 useEffect 的分离 =====+**转发 ref(forwardRef)**:
  
-与类组件的生命周期方法不同,useEffect 鼓励我们将相关的逻辑放在一起,而不是按生命周期阶段组织代码。这样可以更好地分离关注点,使代码更易读、更易维护。+<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> <code javascript>
-function Example() {+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 [count, setCount] = useState(0);
-  const [usersetUser] = useState(null); +  const [textsetText] = useState(''); 
-   + 
-  // 处理计相关的副作用 +  // 有 useCallback:只在 count 变化时创建新函数 
-  useEffect(() => { +  const handleClick = useCallback(() => { 
-    document.title = `点击了 ${count} 次`;+    console.log('count:', count);
   }, [count]);   }, [count]);
-   + 
-  // 处理用户数据获取 +  return ( 
-  useEffect(() => { +    <div> 
-    fetchUser().then(data => setUser(data));+      <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(() => { 
-  useEffect(() => { +    setCount2(=> c + 1);
-    const subscription = subscribeToNotifications(); +
-    return () => subscription.unsubscribe();+
   }, []);   }, []);
-   + 
-  return <div>示例</div>;+  return 
 +    <div> 
 +      <p>Count1: {count1}, Count2: {count2}</p> 
 +      <Child onClick={handleClick1} label="增加 Count1" /> 
 +      <Child onClick={handleClick2} label="增加 Count2" /> 
 +    </div> 
 +  );
 } }
 </code> </code>
  
-相比之下,如果使用类组件,这些逻辑都会混杂在 componentDidMount 和 componentDidUpdate 中,难以追踪和维护。+**何时使用 useCallback**:
  
-===== 6.useEffect 的执行顺序 =====+  * 函数作为 props 传递给子组件,且子组件使用了 React.memo 
 +  * 函数作为 useEffect 的依赖项 
 +  * 函数被其他 Hooks(如 useMemo)依赖 
 +  * 函数是一个复杂计算或创建成本较高
  
-在同一个组件中,多个 useEffect 会按照它们在代码中出现的顺序依次执行。+**避免过度使用**:
  
 <code javascript> <code javascript>
-function OrderExample() { +// 不需要 useCallback 的情况:简单函数直接内联 
-  useEffect(() => { +function SimpleComponent() { 
-    console.log('Effect 1'); +  const [count, setCount] useState(0); 
-  }, []); + 
-   +  // 简单函数,不需要 useCallback 
-  useEffect(() => +  const increment = () => setCount(=> c + 1); 
-    console.log('Effect 2'); + 
-  }, []); +  return <button onClick={increment}>{count}</button>;
-   +
-  useEffect(() => +
-    console.log('Effect 3'); +
-  }, []); +
-   +
-  return <div>检查控制台输出顺序</div>;+
 } }
-// 输出顺序: Effect 1, Effect 2, Effect 3 
 </code> </code>
  
-清理函数的执行顺序与 effect 相反:先声明的 effect 后清理+ 
 +===== 5.17 useMemo - 缓存计算结果 ===== 
 + 
 +useMemo 返回一个 memoized 值只有当依赖项发生变化时,才会重新计算。 
 + 
 +**基本用法**:
  
 <code javascript> <code javascript>
-function CleanupOrderExample() {+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(() => {   useEffect(() => {
-    console.log('Effect 1 启动'); +    console.log('options 变化,重新初始化图表'); 
-    return () => console.log('Effect 1 清理'); +    initChart(chartOptions); 
-  }, []); +  }, [chartOptions]); 
-   + 
-  useEffect(() => { +  return <canvas id="chart" />;
-    console.log('Effect 2 启动'); +
-    return () => console.log('Effect 2 清理'); +
-  }, []); +
-   +
-  // 组件卸载时输出: +
-  // Effect 2 清理 +
-  // Effect 1 清理 +
-   +
-  return <div>组件</div>;+
 } }
 </code> </code>
  
-===== 6.7 常见依赖问题 =====+**复杂数据处理**:
  
-依赖数组是 useEffect 中最容易出错的部分。遗漏依赖或错误地添加依赖都会导致 bug。+<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> <code javascript>
-// 错误示例遗漏了 count 依赖 +// useCallback(fn, deps) 等同于 useMemo(() => fn, deps) 
-function Counter() { + 
-  const [countsetCount] = useState(0); +// useCallback - 缓存函数 
-   +const memoizedCallback = useCallback(() => { 
-  useEffect(() =+  doSomething(a, b); 
-    console.log(`当前计数: ${count}`); +}, [a, b]); 
-  }, [])// 警告:count 应该在依赖数组中 + 
-   +// useMemo - 缓存值 
-  return <button onClick={() => setCount(=> c + 1)}>增加</button>;+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 [themesetTheme] = 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> </code>
  
-上述代码中,effect 只在挂载时执行一次,之后 count 变化时不会重新执行,导致输出始终是初始值 0。+**多 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> <code javascript>
-useEffect(() => { +// 不好的做法:整个 context 变化导致所有消费者重新渲染 
-  console.log(`当前计数: ${count}`); +const AppContext = createContext(
-}[count]);+  theme: 'light', 
 +  user: null, 
 +  setTheme: () => {}, 
 +  setUser: () => {} 
 +}); 
 + 
 +// 更好的做法:将经常变化和很少变化的据分开 
 +const ThemeContext = createContext({ theme'light', setTheme: () => {} }); 
 +const UserContext = createContext({ user: nullsetUser: () => {} }); 
 + 
 +// 最佳做法:使用自定义 Hook 封装 context 
 +function useTheme() { 
 +  const context = useContext(ThemeContext); 
 +  if (!context) { 
 +    throw new Error('useTheme 必须在 ThemeProvider 内部使用'); 
 +  } 
 +  return context; 
 +}
 </code> </code>
  
-**函数依赖的处理**: 
  
-当 effect 中使了函数时,需要将函数也加入依赖数组。但如果函数在每次渲染时都重新定义,会导致 effect 频繁执行+===== 5.19 useReducer - 复杂状态管理 ===== 
 + 
 +useReducer 是 useState 的替代方案,适于 state 逻辑较复杂或包含多个子值的情况 
 + 
 +**基本用法**:
  
 <code javascript> <code javascript>
-// 问题:fetchData 每次渲染都重新定义,导致 effect 频繁执行 +import { useReducer } from 'react'; 
-function UserList({ apiUrl }) { + 
-  const [users, setUsers] = useState([]); +// 定义 reducer 函数 
-   +function reducer(state, action) { 
-  const fetchData = async () => +  switch (action.type{ 
-    const response = await fetch(apiUrl)+    case 'increment': 
-    const data = await response.json()+      return count: state.count + 1 }; 
-    setUsers(data); +    case 'decrement': 
-  }; +      return { count: state.count - 1 }
-   +    case 'reset': 
-  useEffect(() => { +      return { count: 0 }
-    fetchData(); +    default: 
-  }[fetchData]); // fetchData 每次都变,导致无限循环 +      throw new Error('未知 action'); 
-   +  } 
-  return <ul>{users.map(=> <li key={u.id}>{u.name}</li>)}</ul>;+} 
 + 
 +function Counter() { 
 +  const [statedispatch= 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>
  
-解决方案是将函数定义在 useEffect 内部,或使用 useCallback+**复杂状态管理**
  
 <code javascript> <code javascript>
-// 方案1:函数定义在 effect 内部 +const initialState = { 
-useEffect(() => {+  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 () => {   const fetchData = async () => {
-    const response = await fetch(apiUrl); +    dispatch({ type: 'FETCH_START' }); 
-    const data = await response.json(); +    try { 
-    setUsers(data);+      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 }); 
 +    }
   };   };
-  fetchData(); 
-}, [apiUrl]); 
- 
-// 方案2:使用 useCallback 
-const fetchData = useCallback(async () => { 
-  const response = await fetch(apiUrl); 
-  const data = await response.json(); 
-  setUsers(data); 
-}, [apiUrl]); 
  
-useEffect(() => { +  return ( 
-  fetchData(); +    <div> 
-}, [fetchData]);+      {state.loading && <p>加载中...</p>
 +      {state.error && <p>错误: {state.error}</p>
 +      {state.data && <p>数据: {JSON.stringify(state.data)}</p>
 +      <button onClick={fetchData}>获取数据</button> 
 +    </div> 
 +  ); 
 +}
 </code> </code>
  
-===== 6.8 useEffect 与 useLayoutEffect =====+**useReducer vs useState**:
  
-React 提供了两类似的 Hook:useEffect 和 useLayoutEffect。它们的签名完全但执行时机不同。+| 场景 | 推荐方案 | 
 +| 简单状态(单值) | useState | 
 +| 复杂状态(多个子值互依赖) | useReducer | 
 +| 需要优化性能state 更新逻辑复杂 | useReducer | 
 +| 需要复用状态逻辑 | useReducer + 自定义 Hook |
  
-**执行时机差异**:+===== 5.20 其他常用 Hooks =====
  
-useEffect:在浏览器绘制完后异步执行,不会阻塞视觉更新 +**useId 唯一 ID**
-- useLayoutEffect在浏览器绘制之前同步执行,会阻塞视觉更新+
  
 <code javascript> <code javascript>
-import { useLayoutEffect } from 'react';+import { useId } from 'react';
  
-function MeasureExample() { +function Form() { 
-  const divRef useRef(null); +  const id useId();
-  const [width, setWidth] = useState(0); +
-   +
-  useLayoutEffect(() => { +
-    // 在浏览器绘制前测量 DOM +
-    const { width } = divRef.current.getBoundingClientRect(); +
-    setWidth(width); +
-  }, []);+
      
   return (   return (
-    <div ref={divRef}> +    <div
-      宽度: {width}px+      <label htmlFor={id + '-name'}>姓名:</label
 +      <input id={id + '-name'} type="text" /> 
 +       
 +      <label htmlFor={id + '-email'}>邮箱:</label> 
 +      <input id={id + '-email'type="email" />
     </div>     </div>
   );   );
行 880: 行 1255:
 </code> </code>
  
-**何时使用 useLayoutEffect**:+**useTransition - 非紧急更新**:
  
-绝大多数情况下,应该使用 useEffect。只有当出现以下情况时才考虑 useLayoutEffect:+<code javascript> 
 +import { useTransition, useState } from 'react';
  
-- 需要在浏览器绘制前测量 DOM 元素(如获取元素尺寸、位置) +function TabContainer() { 
-- 需要根据测量结果同步修改 DOM,避免视觉闪烁 +  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>
  
-在服务端渲染(SSR)环境中,useLayoutEffect 会产生警告,因为服务端无法测量 DOM。解决方法是使用 useEffect 代替,或使用动态导入在客户端渲染。+**useDeferredValue - 延迟更新值**:
  
 <code javascript> <code javascript>
-import { useEffectuseLayoutEffect } from 'react';+import { useDeferredValueuseState } from 'react';
  
-const useIsomorphicLayoutEffect = typeof window !== 'undefined? useLayoutEffect : useEffect;+function SearchResults() { 
 +  const [query, setQuery] useState(''); 
 +  const deferredQuery = useDeferredValue(query);
  
-function Component(+  return ( 
-  useIsomorphicLayoutEffect(() => { +    <div> 
-    // 在服务端使用 useEffect在客户端使用 useLayoutEffect +      <input  
-  }, []); +        value={query}  
-   +        onChange={e => setQuery(e.target.value)
-  return <div>组件</div>;+        placeholder="搜索..." 
 +      /
 +      {/使用 deferredQuery 显示结果保持输入流畅 */
 +      <SlowList query={deferredQuery} /> 
 +    </div> 
 +  );
 } }
 </code> </code>
  
-===== 6.9 自定义 Hook 封装 useEffect ===== 
  
-将常用的 useEffect 逻辑封装成自定义 Hook 是一种良好的实践,可以提高代码的可复用性和可维护性。+===== 5.21 自定义 Hooks =====
  
-**useFetch Hook**:+自定义 Hooks 是提取组件逻辑到可复用函数的方式。自定义 Hook 是一个函数,其名称以 "use" 开头,内部可以调用其他 Hooks。 
 + 
 +**useFetch - 数据获取**:
  
 <code javascript> <code javascript>
 +import { useState, useEffect } from 'react';
 +
 function useFetch(url) { function useFetch(url) {
   const [data, setData] = useState(null);   const [data, setData] = useState(null);
   const [loading, setLoading] = useState(true);   const [loading, setLoading] = useState(true);
   const [error, setError] = useState(null);   const [error, setError] = useState(null);
-  +
   useEffect(() => {   useEffect(() => {
     const controller = new AbortController();     const controller = new AbortController();
行 945: 行 1348:
     return () => controller.abort();     return () => controller.abort();
   }, [url]);   }, [url]);
-  +
   return { data, loading, error };   return { data, loading, error };
 } }
行 964: 行 1367:
 </code> </code>
  
-**useDebounce Hook**:+**useLocalStorage - 本地存储同步**:
  
 <code javascript> <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) { function useDebounce(value, delay) {
   const [debouncedValue, setDebouncedValue] = useState(value);   const [debouncedValue, setDebouncedValue] = useState(value);
-  +
   useEffect(() => {   useEffect(() => {
     const timer = setTimeout(() => {     const timer = setTimeout(() => {
       setDebouncedValue(value);       setDebouncedValue(value);
     }, delay);     }, delay);
-    +
     return () => {     return () => {
       clearTimeout(timer);       clearTimeout(timer);
     };     };
   }, [value, delay]);   }, [value, delay]);
-  +
   return debouncedValue;   return debouncedValue;
 } }
行 987: 行 1435:
   const [input, setInput] = useState('');   const [input, setInput] = useState('');
   const debouncedInput = useDebounce(input, 500);   const debouncedInput = useDebounce(input, 500);
-  +
   useEffect(() => {   useEffect(() => {
     if (debouncedInput) {     if (debouncedInput) {
行 993: 行 1441:
     }     }
   }, [debouncedInput]);   }, [debouncedInput]);
-  +
   return (   return (
     <input     <input
行 1004: 行 1452:
 </code> </code>
  
-===== 6.10 常见陷阱与最佳实践 =====+**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> <code javascript>
行 1023: 行 1618:
 </code> </code>
  
-**对象和数组依赖的处理**+**陷阱 2:遗漏依赖**
  
-对象和数组是引用类型,每次渲染都会创建新的引用,即使内容相同。+<code javascript> 
 +// 错误:遗漏了 count 依赖 
 +useEffect(() => { 
 +  console.log(`当前计数: ${count}`); 
 +}, []); // 警告:count 应该在依赖数组中 
 + 
 +// 正确:添加所有依赖 
 +useEffect(() => { 
 +  console.log(`当前计数: ${count}`); 
 +}, [count]); 
 +</code> 
 + 
 +**陷阱 3:对象和数组依赖**
  
 <code javascript> <code javascript>
行 1040: 行 1647:
 // 方案2:使用 useMemo // 方案2:使用 useMemo
 const options = useMemo(() => ({ page, size }), [page, size]); const options = useMemo(() => ({ page, size }), [page, size]);
- 
-useEffect(() => { 
-  fetchData(options); 
-}, [options]); 
 </code> </code>
  
-**依赖检查工具**: +===== 5.24 总结 =====
- +
-使用 ESLint 的 react-hooks/exhaustive-deps 规则可以自动检查依赖数组是否完整。 +
- +
-<code json> +
-+
-  "plugins": ["react-hooks"], +
-  "rules":+
-    "react-hooks/rules-of-hooks": "error", +
-    "react-hooks/exhaustive-deps": "warn" +
-  } +
-+
-</code> +
- +
-**使用 ref 跳过首次渲染**: +
- +
-有时候我们只想在依赖更新时执行 effect,而不是在挂载时。 +
- +
-<code javascript> +
-function useUpdateEffect(effect, deps) { +
-  const isFirst useRef(true); +
-   +
-  useEffect(() => { +
-    if (isFirst.current) { +
-      isFirst.current false; +
-      return; +
-    } +
-    return effect(); +
-  }, deps); +
-+
- +
-// 使用 +
-function Component() { +
-  const [count, setCount] useState(0); +
-   +
-  useUpdateEffect(() => { +
-    console.log('count 更新了:', count); +
-  }, [count]); +
-   +
-  return <button onClick={() => setCount(c => c + 1)}>增加</button>; +
-+
-</code>+
  
-===== 6.11 总结 =====+本章全面介绍了 React 组件和 Hooks:
  
-useEffect 是 React 函数组件中处理副作用核心工具。本章详细介绍了:+**组件基础**: 
 +  * 函数组件和类组件定义与区别 
 +  * Props 传递、默认值和类型检查 
 +  * 组件组合、children 和 render props 
 +  * 高阶组件和组件组织方式
  
-- useEffect 的基本语法和执行时机 +**核心 Hooks**: 
-- 清函数的编写和作用 +  * useState:状态管 
-- 数据获取的最佳实践和竞态条件处理 +  * useEffect:副作用处理 
-- 多个 useEffect 的分离原则 +  * useRef:DOM 引用和持久化值 
-- 依赖组的正确使用 +  * useCallback:缓存回调函数 
-- useEffect 与 useLayoutEffect 的区别 +  * useMemo:缓存计算结果 
-- 自定义 Hook 的封装 +  * useContext:跨组件状态共享 
-- 常见陷阱和最佳实践+  * useReducer:复杂状态管理
  
-掌握 useEffect 需要理解 React 的渲染流程和数据流。建议在开发时+**其他 Hooks** 
 +  * useId:生成唯一 ID 
 +  * useTransition:非紧急更新 
 +  * useDeferredValue:延迟更新值
  
-- 保持依赖数组的完整性,使用 ESLint 辅助检查 +**自定义 Hooks**: 
-- 将不相关的副作分离到不同的 useEffect 中 +  * 封装可复用逻辑 
-- 合理处理清理逻辑,避免内存泄漏 +  * 常见的自定义 Hooks 模式
-- 对于复杂逻辑,考虑封装成自定义 Hook +
-- 优先使用 useEffect,只在必要时使用 useLayoutEffect+
  
-useEffect 的强大之处在于它将副作逻辑按照功组织在一起,而不是分散在不同的生命周期法中,这使得代码更加清晰、易于维护。随着实践的深入,你会越来越熟练地运用这一强大的工具来构建高质量的 React 应用。+**最佳实践**: 
 +  * Hooks 使规则 
 +  * 性优化策略 
 +  * 常见陷阱与解决
  
 +掌握这些知识后,你可以使用函数组件和 Hooks 构建高质量的 React 应用。记住 Hooks 的核心原则:只在最顶层调用、只在 React 函数中调用、保持依赖数组完整。
  

该主题尚不存在

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

  • react/组件基础.1775526769.txt.gz
  • 最后更改: 2026/04/07 09:52
  • 张叶安