Jacky's blog
首页
  • 学习笔记

    • web
    • android
    • iOS
    • vue
  • 分类
  • 标签
  • 归档
收藏
  • tool
  • algo
  • python
  • java
  • server
  • growth
  • frida
  • blog
  • SP
  • more
GitHub (opens new window)

Jack Yang

编程; 随笔
首页
  • 学习笔记

    • web
    • android
    • iOS
    • vue
  • 分类
  • 标签
  • 归档
收藏
  • tool
  • algo
  • python
  • java
  • server
  • growth
  • frida
  • blog
  • SP
  • more
GitHub (opens new window)
  • web
  • web concept
  • javascript

  • css

  • vue

  • react

    • React 完整学习指南
    • React Hooks 完整指南
      • 学习目标
      • 1. React Hooks 基础概念
        • 1.1 Hooks 简介
        • 1.2 Hooks 使用规则
      • 2. 基础 Hooks
        • 2.1 useState
        • 2.2 useEffect
        • 2.3 useContext
      • 3. 高级 Hooks
        • 3.1 useReducer
        • 3.2 useCallback
        • 3.3 useMemo
        • 3.4 useRef
        • 3.5 useImperativeHandle
      • 4. 自定义 Hooks
        • 4.1 创建自定义 Hooks
        • 4.2 常用自定义 Hooks
      • 5. Hooks 最佳实践
        • 5.1 性能优化
        • 5.2 依赖项管理
        • 5.3 错误处理
      • 6. 常见问题和解决方案
        • 6.1 useEffect 无限循环
        • 6.2 状态更新时机问题
        • 6.3 内存泄漏
      • 7. 实用技巧和常用模式
        • 7.1 状态管理模式
        • useComplexState 详细使用指南
        • 7.2 异步操作模式
        • 异步操作模式详细指南
        • 7.3 组件通信模式
        • 组件通信模式详细指南
    • REACT NATIVE
    • REACT 实用代码片段
    • REACT 高频问题
  • nextjs

  • module

  • web faq
  • web3

  • more

  • 《web》
  • react
Jacky
2023-05-24
目录

React Hooks 完整指南

# 学习目标

本指南旨在为 React 开发者提供系统性的 Hooks 学习路径,从基础概念到高级应用,逐步掌握 React Hooks 的使用技巧和最佳实践。

# 1. React Hooks 基础概念

# 1.1 Hooks 简介

React Hooks 是 React 16.8 引入的新特性,它让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

Hooks 的优势:

  • 在组件之间复用状态逻辑变得更容易
  • 将相关的逻辑放在一起,而不是分散在不同的生命周期方法中
  • 避免了 class 组件的复杂性(this 绑定、生命周期方法等)

# 1.2 Hooks 使用规则

// ✅ 正确:在函数组件的顶层调用 Hooks
function MyComponent() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  
  useEffect(() => {
    document.title = `Count: ${count}`;
  }, [count]);
  
  return <div>{count}</div>;
}

// ❌ 错误:在条件语句中调用 Hooks
function MyComponent() {
  if (condition) {
    const [count, setCount] = useState(0); // 错误!
  }
  
  // ❌ 错误:在循环中调用 Hooks
  for (let i = 0; i < 10; i++) {
    const [count, setCount] = useState(0); // 错误!
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

Hooks 使用规则:

  1. 只在函数组件的顶层调用 Hooks
  2. 不要在循环、条件或嵌套函数中调用 Hooks
  3. 只在 React 函数组件或自定义 Hooks 中调用 Hooks

# 2. 基础 Hooks

# 2.1 useState

useState 是最基础的 Hook,用于在函数组件中添加状态。

import { useState } from 'react';

function Counter() {
  // 基本用法
  const [count, setCount] = useState(0);
  
  // 对象状态
  const [user, setUser] = useState({
    name: 'Alice',
    age: 25
  });
  
  // 数组状态
  const [items, setItems] = useState([]);
  
  // 函数式更新
  const increment = () => {
    setCount(prevCount => prevCount + 1);
  };
  
  // 更新对象状态
  const updateUser = () => {
    setUser(prevUser => ({
      ...prevUser,
      age: prevUser.age + 1
    }));
  };
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <p>User: {user.name}, Age: {user.age}</p>
      <button onClick={updateUser}>Age +1</button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

useState 最佳实践:

// ✅ 正确:使用函数式更新避免依赖过期的状态
function Counter() {
  const [count, setCount] = useState(0);
  
  const increment = () => {
    setCount(prevCount => prevCount + 1);
  };
  
  const incrementAsync = () => {
    setTimeout(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);
  };
}

// ✅ 正确:使用多个 useState 而不是一个大的状态对象
function UserProfile() {
  const [name, setName] = useState('');
  const [email, setEmail] = useState('');
  const [age, setAge] = useState(0);
  
  // 而不是:
  // const [user, setUser] = useState({ name: '', email: '', age: 0 });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

# 2.2 useEffect

useEffect 用于处理组件中的副作用,如数据获取、订阅、手动修改 DOM 等。

import { useEffect, useState } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  
  // 基本用法:组件挂载时执行
  useEffect(() => {
    console.log('Component mounted');
  }, []);
  
  // 依赖项:当 userId 变化时重新执行
  useEffect(() => {
    const fetchUser = async () => {
      setLoading(true);
      try {
        const response = await fetch(`/api/users/${userId}`);
        const userData = await response.json();
        setUser(userData);
      } catch (error) {
        console.error('Failed to fetch user:', error);
      } finally {
        setLoading(false);
      }
    };
    
    fetchUser();
  }, [userId]);
  
  // 清理函数:组件卸载时执行
  useEffect(() => {
    const timer = setInterval(() => {
      console.log('Timer tick');
    }, 1000);
    
    return () => {
      clearInterval(timer);
    };
  }, []);
  
  if (loading) return <div>Loading...</div>;
  if (!user) return <div>User not found</div>;
  
  return <div>Hello, {user.name}!</div>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

useEffect 常见模式:

// 1. 只在组件挂载时执行
useEffect(() => {
  // 初始化逻辑
}, []);

// 2. 在特定依赖变化时执行
useEffect(() => {
  // 响应依赖变化的逻辑
}, [dependency1, dependency2]);

// 3. 每次渲染后都执行
useEffect(() => {
  // 每次渲染后的逻辑
});

// 4. 清理副作用
useEffect(() => {
  const subscription = subscribe();
  return () => {
    subscription.unsubscribe();
  };
}, []);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

useEffect 无限循环问题:

// ❌ 错误:导致无限循环
useEffect(() => {
  setCount(count + 1);
}, [count]); // count 变化导致重新渲染,重新渲染导致 count 变化

// ✅ 正确:使用函数式更新
useEffect(() => {
  setCount(prevCount => prevCount + 1);
}, []); // 只在挂载时执行一次

// ✅ 正确:使用 useCallback 避免函数重新创建
const fetchData = useCallback(async () => {
  const response = await fetch('/api/data');
  const data = await response.json();
  setData(data);
}, []);

useEffect(() => {
  fetchData();
}, [fetchData]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 2.3 useContext

useContext 用于在组件树中共享数据,避免 props 层层传递。

import { createContext, useContext, useState } from 'react';

// 创建 Context
const ThemeContext = createContext();
const UserContext = createContext();

// 提供者组件
function App() {
  const [theme, setTheme] = useState('light');
  const [user, setUser] = useState({ name: 'Alice' });
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <UserContext.Provider value={{ user, setUser }}>
        <Header />
        <Main />
      </UserContext.Provider>
    </ThemeContext.Provider>
  );
}

// 使用 Context 的组件
function Header() {
  const { theme, setTheme } = useContext(ThemeContext);
  const { user } = useContext(UserContext);
  
  return (
    <header className={theme}>
      <h1>Welcome, {user.name}!</h1>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
    </header>
  );
}

// 自定义 Hook 简化 Context 使用
function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within a ThemeProvider');
  }
  return context;
}

function useUser() {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUser must be used within a UserProvider');
  }
  return context;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

# 3. 高级 Hooks

# 3.1 useReducer

useReducer 用于管理复杂的状态逻辑,特别适合状态包含多个子值或下一个状态依赖于之前的状态。

import { useReducer } from 'react';

// 定义 action 类型
const ACTIONS = {
  INCREMENT: 'increment',
  DECREMENT: 'decrement',
  RESET: 'reset',
  SET_COUNT: 'set_count'
};

// reducer 函数
function counterReducer(state, action) {
  switch (action.type) {
    case ACTIONS.INCREMENT:
      return { count: state.count + 1 };
    case ACTIONS.DECREMENT:
      return { count: state.count - 1 };
    case ACTIONS.RESET:
      return { count: 0 };
    case ACTIONS.SET_COUNT:
      return { count: action.payload };
    default:
      throw new Error(`Unknown action: ${action.type}`);
  }
}

function Counter() {
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: ACTIONS.INCREMENT })}>
        Increment
      </button>
      <button onClick={() => dispatch({ type: ACTIONS.DECREMENT })}>
        Decrement
      </button>
      <button onClick={() => dispatch({ type: ACTIONS.RESET })}>
        Reset
      </button>
      <button onClick={() => dispatch({ 
        type: ACTIONS.SET_COUNT, 
        payload: 10 
      })}>
        Set to 10
      </button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

useReducer 与 useState 的选择:

// 使用 useState:简单状态
function SimpleCounter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

// 使用 useReducer:复杂状态逻辑
function ComplexCounter() {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
    step: 1,
    history: []
  });
  
  const increment = () => {
    dispatch({
      type: 'increment',
      payload: { step: state.step }
    });
  };
  
  return (
    <div>
      <p>Count: {state.count}</p>
      <input
        type="number"
        value={state.step}
        onChange={(e) => dispatch({
          type: 'set_step',
          payload: parseInt(e.target.value)
        })}
      />
      <button onClick={increment}>Increment</button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 3.2 useCallback

useCallback 用于缓存函数,避免在每次渲染时重新创建函数,优化子组件的性能。

import { useCallback, useState } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  // 基本用法:缓存函数
  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []); // 空依赖数组,函数永远不会重新创建
  
  // 带依赖的函数
  const handleIncrement = useCallback(() => {
    setCount(prevCount => prevCount + 1);
  }, []); // 不依赖任何值,可以缓存
  
  // 依赖外部值的函数
  const handleTextChange = useCallback((newText) => {
    setText(newText);
  }, []); // 不依赖任何值,可以缓存
  
  // 依赖 props 或 state 的函数
  const handleSubmit = useCallback(() => {
    console.log('Submitting:', { count, text });
  }, [count, text]); // 依赖 count 和 text
  
  return (
    <div>
      <input
        value={text}
        onChange={(e) => handleTextChange(e.target.value)}
      />
      <button onClick={handleIncrement}>Count: {count}</button>
      <button onClick={handleSubmit}>Submit</button>
      <ChildComponent onAction={handleClick} />
    </div>
  );
}

// 子组件使用 React.memo 优化
const ChildComponent = React.memo(({ onAction }) => {
  console.log('ChildComponent rendered');
  return <button onClick={onAction}>Child Action</button>;
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

useCallback 最佳实践:

// ✅ 正确:缓存事件处理函数
function Form() {
  const [data, setData] = useState({});
  
  const handleSubmit = useCallback((formData) => {
    setData(formData);
  }, []);
  
  const handleReset = useCallback(() => {
    setData({});
  }, []);
  
  return (
    <form onSubmit={handleSubmit}>
      <button type="button" onClick={handleReset}>Reset</button>
    </form>
  );
}

// ✅ 正确:与 useMemo 结合使用
function DataTable({ data, onRowClick }) {
  const sortedData = useMemo(() => {
    return [...data].sort((a, b) => a.name.localeCompare(b.name));
  }, [data]);
  
  const handleRowClick = useCallback((row) => {
    onRowClick(row);
  }, [onRowClick]);
  
  return (
    <table>
      {sortedData.map(row => (
        <tr key={row.id} onClick={() => handleRowClick(row)}>
          <td>{row.name}</td>
        </tr>
      ))}
    </table>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 3.3 useMemo

useMemo 用于缓存计算结果,避免在每次渲染时重复进行昂贵的计算。

import { useMemo, useState } from 'react';

function ExpensiveComponent({ items, filter }) {
  const [count, setCount] = useState(0);
  
  // 缓存计算结果
  const filteredItems = useMemo(() => {
    console.log('Computing filtered items...');
    return items.filter(item => item.name.includes(filter));
  }, [items, filter]); // 只有当 items 或 filter 变化时才重新计算
  
  // 缓存排序结果
  const sortedItems = useMemo(() => {
    console.log('Computing sorted items...');
    return [...filteredItems].sort((a, b) => a.name.localeCompare(b.name));
  }, [filteredItems]);
  
  // 缓存统计信息
  const stats = useMemo(() => {
    console.log('Computing stats...');
    return {
      total: sortedItems.length,
      average: sortedItems.reduce((sum, item) => sum + item.value, 0) / sortedItems.length
    };
  }, [sortedItems]);
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>
        Re-render count: {count}
      </button>
      <p>Total items: {stats.total}</p>
      <p>Average value: {stats.average.toFixed(2)}</p>
      <ul>
        {sortedItems.map(item => (
          <li key={item.id}>{item.name}: {item.value}</li>
        ))}
      </ul>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

useMemo 使用场景:

// 1. 昂贵的计算
function Fibonacci({ n }) {
  const result = useMemo(() => {
    console.log('Computing fibonacci...');
    if (n <= 1) return n;
    let a = 0, b = 1;
    for (let i = 2; i <= n; i++) {
      [a, b] = [b, a + b];
    }
    return b;
  }, [n]);
  
  return <div>Fibonacci({n}) = {result}</div>;
}

// 2. 对象和数组的创建
function UserList({ users, selectedIds }) {
  const selectedUsers = useMemo(() => {
    return users.filter(user => selectedIds.includes(user.id));
  }, [users, selectedIds]);
  
  const userMap = useMemo(() => {
    return users.reduce((map, user) => {
      map[user.id] = user;
      return map;
    }, {});
  }, [users]);
  
  return (
    <div>
      {selectedUsers.map(user => (
        <div key={user.id}>{user.name}</div>
      ))}
    </div>
  );
}

// 3. 避免子组件不必要的重新渲染
function ParentComponent({ data }) {
  const processedData = useMemo(() => {
    return data.map(item => ({
      ...item,
      processed: item.value * 2
    }));
  }, [data]);
  
  return <ChildComponent data={processedData} />;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

# 3.4 useRef

useRef 用于保存可变值,不会触发组件重新渲染,常用于访问 DOM 元素或保存前一次的值。

import { useRef, useEffect, useState } from 'react';

function RefExample() {
  // 访问 DOM 元素
  const inputRef = useRef(null);
  const buttonRef = useRef(null);
  
  // 保存可变值
  const countRef = useRef(0);
  const prevCountRef = useRef(0);
  
  const [count, setCount] = useState(0);
  
  // 保存前一次的值
  useEffect(() => {
    prevCountRef.current = count;
  }, [count]);
  
  const handleClick = () => {
    countRef.current += 1;
    setCount(count + 1);
    
    // 访问 DOM 元素
    if (inputRef.current) {
      inputRef.current.focus();
    }
  };
  
  return (
    <div>
      <input ref={inputRef} placeholder="Focus me" />
      <button ref={buttonRef} onClick={handleClick}>
        Count: {count}
      </button>
      <p>Previous count: {prevCountRef.current}</p>
      <p>Ref count: {countRef.current}</p>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

useRef 常见用途:

// 1. 访问 DOM 元素
function AutoFocusInput() {
  const inputRef = useRef(null);
  
  useEffect(() => {
    inputRef.current?.focus();
  }, []);
  
  return <input ref={inputRef} placeholder="Auto focused" />;
}

// 2. 保存定时器 ID
function Timer() {
  const [time, setTime] = useState(0);
  const timerRef = useRef(null);
  
  const startTimer = () => {
    timerRef.current = setInterval(() => {
      setTime(prev => prev + 1);
    }, 1000);
  };
  
  const stopTimer = () => {
    if (timerRef.current) {
      clearInterval(timerRef.current);
      timerRef.current = null;
    }
  };
  
  useEffect(() => {
    return () => {
      if (timerRef.current) {
        clearInterval(timerRef.current);
      }
    };
  }, []);
  
  return (
    <div>
      <p>Time: {time}</p>
      <button onClick={startTimer}>Start</button>
      <button onClick={stopTimer}>Stop</button>
    </div>
  );
}

// 3. 保存前一次的值
function PreviousValue({ value }) {
  const prevValueRef = useRef();
  
  useEffect(() => {
    prevValueRef.current = value;
  });
  
  return (
    <div>
      <p>Current: {value}</p>
      <p>Previous: {prevValueRef.current}</p>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

# 3.5 useImperativeHandle

useImperativeHandle 用于自定义暴露给父组件的 ref 值,通常与 forwardRef 一起使用。

import { forwardRef, useImperativeHandle, useState } from 'react';

// 子组件
const FancyInput = forwardRef((props, ref) => {
  const [value, setValue] = useState('');
  
  // 自定义暴露的方法
  useImperativeHandle(ref, () => ({
    focus: () => {
      // 聚焦逻辑
      console.log('Input focused');
    },
    clear: () => {
      setValue('');
    },
    getValue: () => {
      return value;
    },
    setValue: (newValue) => {
      setValue(newValue);
    }
  }), [value]); // 依赖项
  
  return (
    <input
      {...props}
      value={value}
      onChange={(e) => setValue(e.target.value)}
    />
  );
});

// 父组件
function ParentComponent() {
  const inputRef = useRef();
  
  const handleFocus = () => {
    inputRef.current?.focus();
  };
  
  const handleClear = () => {
    inputRef.current?.clear();
  };
  
  const handleGetValue = () => {
    const value = inputRef.current?.getValue();
    console.log('Input value:', value);
  };
  
  return (
    <div>
      <FancyInput ref={inputRef} placeholder="Enter text" />
      <button onClick={handleFocus}>Focus</button>
      <button onClick={handleClear}>Clear</button>
      <button onClick={handleGetValue}>Get Value</button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

useImperativeHandle 最佳实践:

// 1. 暴露必要的方法
const Modal = forwardRef((props, ref) => {
  const [isOpen, setIsOpen] = useState(false);
  
  useImperativeHandle(ref, () => ({
    open: () => setIsOpen(true),
    close: () => setIsOpen(false),
    toggle: () => setIsOpen(!isOpen)
  }), [isOpen]);
  
  if (!isOpen) return null;
  
  return (
    <div className="modal">
      <div className="modal-content">
        {props.children}
        <button onClick={() => setIsOpen(false)}>Close</button>
      </div>
    </div>
  );
});

// 2. 与 useCallback 结合使用
const Toast = forwardRef((props, ref) => {
  const [toastText, setToastText] = useState('');
  
  const showToast = useCallback((text) => {
    setToastText(text);
    setTimeout(() => {
      setToastText('');
    }, 3000);
  }, []);
  
  useImperativeHandle(ref, () => ({
    show: showToast
  }), [showToast]);
  
  if (!toastText) return null;
  
  return (
    <div className="toast">
      {toastText}
    </div>
  );
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

# 4. 自定义 Hooks

# 4.1 创建自定义 Hooks

自定义 Hooks 是复用状态逻辑的函数,必须以 "use" 开头。

// 自定义 Hook:计数器
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);
  
  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);
  
  const decrement = useCallback(() => {
    setCount(prev => prev - 1);
  }, []);
  
  const reset = useCallback(() => {
    setCount(initialValue);
  }, [initialValue]);
  
  return {
    count,
    increment,
    decrement,
    reset
  };
}

// 使用自定义 Hook
function Counter() {
  const { count, increment, decrement, reset } = useCounter(10);
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>Reset</button>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 4.2 常用自定义 Hooks

// 1. 数据获取 Hook
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('Network response was not ok');
        }
        const result = await response.json();
        setData(result);
      } catch (err) {
        setError(err);
      } finally {
        setLoading(false);
      }
    };
    
    fetchData();
  }, [url]);
  
  return { data, loading, error };
}

// 2. 本地存储 Hook
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;
    }
  });
  
  const setValue = useCallback((value) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      window.localStorage.setItem(key, JSON.stringify(valueToStore));
    } catch (error) {
      console.error(error);
    }
  }, [key, storedValue]);
  
  return [storedValue, setValue];
}

// 3. 防抖 Hook
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);
  
  return debouncedValue;
}

// 4. 窗口大小 Hook
function useWindowSize() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });
  
  useEffect(() => {
    const handleResize = () => {
      setWindowSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };
    
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);
  
  return windowSize;
}

// 5. 表单 Hook
function useForm(initialValues = {}) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});
  
  const handleChange = useCallback((name, value) => {
    setValues(prev => ({
      ...prev,
      [name]: value
    }));
    
    // 清除错误
    if (errors[name]) {
      setErrors(prev => ({
        ...prev,
        [name]: ''
      }));
    }
  }, [errors]);
  
  const handleSubmit = useCallback((onSubmit) => {
    return (e) => {
      e.preventDefault();
      onSubmit(values);
    };
  }, [values]);
  
  const reset = useCallback(() => {
    setValues(initialValues);
    setErrors({});
  }, [initialValues]);
  
  return {
    values,
    errors,
    handleChange,
    handleSubmit,
    reset,
    setValues,
    setErrors
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

# 5. Hooks 最佳实践

# 5.1 性能优化

// 1. 使用 React.memo 优化子组件
const ExpensiveComponent = React.memo(({ data, onAction }) => {
  console.log('ExpensiveComponent rendered');
  
  const processedData = useMemo(() => {
    return data.map(item => ({
      ...item,
      processed: item.value * 2
    }));
  }, [data]);
  
  const handleClick = useCallback(() => {
    onAction(processedData);
  }, [processedData, onAction]);
  
  return (
    <div>
      {processedData.map(item => (
        <div key={item.id} onClick={handleClick}>
          {item.name}: {item.processed}
        </div>
      ))}
    </div>
  );
});

// 2. 避免在渲染中创建对象和函数
function BadExample({ items }) {
  // ❌ 错误:每次渲染都创建新对象
  const style = { color: 'red' };
  const handleClick = () => console.log('clicked');
  
  return (
    <div style={style} onClick={handleClick}>
      {items.map(item => <div key={item.id}>{item.name}</div>)}
    </div>
  );
}

function GoodExample({ items }) {
  // ✅ 正确:使用 useMemo 和 useCallback
  const style = useMemo(() => ({ color: 'red' }), []);
  const handleClick = useCallback(() => console.log('clicked'), []);
  
  return (
    <div style={style} onClick={handleClick}>
      {items.map(item => <div key={item.id}>{item.name}</div>)}
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

# 5.2 依赖项管理

// 1. 正确设置依赖项
function Example({ userId, onSuccess }) {
  const [user, setUser] = useState(null);
  
  // ✅ 正确:包含所有依赖项
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  // ✅ 正确:使用 useCallback 避免函数重新创建
  const handleSuccess = useCallback(() => {
    onSuccess(user);
  }, [onSuccess, user]);
  
  return <div>{user?.name}</div>;
}

// 2. 使用 useCallback 和 useMemo 减少依赖项
function OptimizedExample({ items, onItemClick }) {
  // 缓存计算结果
  const processedItems = useMemo(() => {
    return items.filter(item => item.active);
  }, [items]);
  
  // 缓存事件处理函数
  const handleItemClick = useCallback((item) => {
    onItemClick(item);
  }, [onItemClick]);
  
  return (
    <div>
      {processedItems.map(item => (
        <div key={item.id} onClick={() => handleItemClick(item)}>
          {item.name}
        </div>
      ))}
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 5.3 错误处理

// 自定义错误边界 Hook
function useErrorBoundary() {
  const [error, setError] = useState(null);
  
  const handleError = useCallback((error) => {
    console.error('Error caught by boundary:', error);
    setError(error);
  }, []);
  
  const resetError = useCallback(() => {
    setError(null);
  }, []);
  
  return { error, handleError, resetError };
}

// 使用示例
function App() {
  const { error, handleError, resetError } = useErrorBoundary();
  
  if (error) {
    return (
      <div>
        <h2>Something went wrong!</h2>
        <button onClick={resetError}>Try again</button>
      </div>
    );
  }
  
  return (
    <ErrorBoundary onError={handleError}>
      <YourComponent />
    </ErrorBoundary>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 6. 常见问题和解决方案

# 6.1 useEffect 无限循环

// ❌ 问题:无限循环
function BadExample() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    setCount(count + 1);
  }, [count]); // count 变化导致重新渲染,重新渲染导致 count 变化
  
  return <div>{count}</div>;
}

// ✅ 解决方案 1:使用函数式更新
function Solution1() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    setCount(prev => prev + 1);
  }, []); // 只在挂载时执行一次
  
  return <div>{count}</div>;
}

// ✅ 解决方案 2:移除不必要的依赖
function Solution2() {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState('');
  
  useEffect(() => {
    console.log('Count changed:', count);
  }, [count]); // 只依赖 count
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      <input value={otherState} onChange={(e) => setOtherState(e.target.value)} />
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 6.2 状态更新时机问题

// ❌ 问题:状态更新时机
function BadExample() {
  const [count, setCount] = useState(0);
  
  const handleClick = () => {
    setCount(count + 1);
    console.log('Count after update:', count); // 仍然是旧值
  };
  
  return <button onClick={handleClick}>Count: {count}</button>;
}

// ✅ 解决方案:使用 useEffect 监听状态变化
function GoodExample() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log('Count updated:', count);
  }, [count]);
  
  const handleClick = () => {
    setCount(count + 1);
  };
  
  return <button onClick={handleClick}>Count: {count}</button>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 6.3 内存泄漏

// ❌ 问题:内存泄漏
function BadExample() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    let isMounted = true;
    
    fetch('/api/data')
      .then(response => response.json())
      .then(result => {
        if (isMounted) {
          setData(result);
        }
      });
    
    // 缺少清理函数
  }, []);
  
  return <div>{data?.message}</div>;
}

// ✅ 解决方案:使用 AbortController
function GoodExample() {
  const [data, setData] = useState(null);
  
  useEffect(() => {
    const abortController = new AbortController();
    
    fetch('/api/data', { signal: abortController.signal })
      .then(response => response.json())
      .then(result => {
        setData(result);
      })
      .catch(error => {
        if (error.name !== 'AbortError') {
          console.error('Fetch error:', error);
        }
      });
    
    return () => {
      abortController.abort();
    };
  }, []);
  
  return <div>{data?.message}</div>;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 7. 实用技巧和常用模式

# 7.1 状态管理模式

// 1. 复杂状态管理
function useComplexState(initialState) {
  const [state, setState] = useState(initialState);
  
  const updateState = useCallback((updates) => {
    setState(prev => ({
      ...prev,
      ...updates
    }));
  }, []);
  
  const resetState = useCallback(() => {
    setState(initialState);
  }, [initialState]);
  
  return [state, updateState, resetState];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# useComplexState 详细使用指南

工作原理:

useComplexState 是一个自定义 Hook,用于管理复杂的状态对象。它提供了三个功能:

  • state: 当前状态
  • updateState: 部分更新状态的函数
  • resetState: 重置状态到初始值的函数

使用场景:

  • 管理复杂的表单状态
  • 应用配置管理
  • 游戏状态管理
  • 任何需要频繁部分更新对象状态的场景

基础使用示例:

function UserProfileForm() {
  // 使用 useComplexState 管理用户数据
  const [userData, updateUserData, resetUserData] = useComplexState({
    name: '',
    email: '',
    age: 0,
    address: {
      street: '',
      city: '',
      country: ''
    },
    preferences: {
      newsletter: false,
      notifications: true,
      theme: 'light'
    }
  });

  // 更新单个字段
  const handleNameChange = (name) => {
    updateUserData({ name });
  };

  // 更新嵌套对象
  const handleAddressChange = (address) => {
    updateUserData({ address });
  };

  // 更新嵌套对象的特定字段
  const handleStreetChange = (street) => {
    updateUserData({
      address: { ...userData.address, street }
    });
  };

  // 更新多个字段
  const handleProfileUpdate = (updates) => {
    updateUserData(updates);
  };

  return (
    <form>
      <input
        value={userData.name}
        onChange={(e) => handleNameChange(e.target.value)}
        placeholder="姓名"
      />
      <input
        value={userData.email}
        onChange={(e) => updateUserData({ email: e.target.value })}
        placeholder="邮箱"
      />
      <input
        value={userData.address.street}
        onChange={(e) => handleStreetChange(e.target.value)}
        placeholder="街道"
      />
      <button type="button" onClick={resetUserData}>重置表单</button>
    </form>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

高级使用示例:

function AppSettings() {
  const [settings, updateSettings, resetSettings] = useComplexState({
    theme: 'light',
    language: 'zh-CN',
    notifications: {
      email: true,
      push: false,
      sms: false
    },
    privacy: {
      profileVisibility: 'public',
      dataSharing: false,
      analytics: true
    },
    performance: {
      autoSave: true,
      cacheEnabled: true,
      compression: false
    }
  });

  // 切换主题
  const toggleTheme = () => {
    updateSettings({
      theme: settings.theme === 'light' ? 'dark' : 'light'
    });
  };

  // 更新通知设置
  const updateNotificationSettings = (notificationType, enabled) => {
    updateSettings({
      notifications: {
        ...settings.notifications,
        [notificationType]: enabled
      }
    });
  };

  // 批量更新隐私设置
  const updatePrivacySettings = (privacySettings) => {
    updateSettings({
      privacy: { ...settings.privacy, ...privacySettings }
    });
  };

  // 保存设置到本地存储
  const saveSettings = () => {
    localStorage.setItem('appSettings', JSON.stringify(settings));
  };

  // 从本地存储加载设置
  const loadSettings = () => {
    const saved = localStorage.getItem('appSettings');
    if (saved) {
      updateSettings(JSON.parse(saved));
    }
  };

  return (
    <div className={`app ${settings.theme}`}>
      <h2>应用设置</h2>
      
      <section>
        <h3>主题设置</h3>
        <button onClick={toggleTheme}>
          当前主题: {settings.theme}
        </button>
      </section>

      <section>
        <h3>通知设置</h3>
        <label>
          <input
            type="checkbox"
            checked={settings.notifications.email}
            onChange={(e) => updateNotificationSettings('email', e.target.checked)}
          />
          邮件通知
        </label>
        <label>
          <input
            type="checkbox"
            checked={settings.notifications.push}
            onChange={(e) => updateNotificationSettings('push', e.target.checked)}
          />
          推送通知
        </label>
      </section>

      <section>
        <h3>隐私设置</h3>
        <select
          value={settings.privacy.profileVisibility}
          onChange={(e) => updatePrivacySettings({ profileVisibility: e.target.value })}
        >
          <option value="public">公开</option>
          <option value="friends">仅好友</option>
          <option value="private">私密</option>
        </select>
      </section>

      <div className="actions">
        <button onClick={saveSettings}>保存设置</button>
        <button onClick={loadSettings}>加载设置</button>
        <button onClick={resetSettings}>恢复默认</button>
      </div>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

游戏状态管理示例:

function GameComponent() {
  const [gameState, updateGameState, resetGameState] = useComplexState({
    player: {
      name: 'Player1',
      health: 100,
      level: 1,
      experience: 0,
      inventory: []
    },
    game: {
      currentLevel: 1,
      score: 0,
      timeElapsed: 0,
      isPaused: false
    },
    enemies: [],
    items: [],
    environment: {
      weather: 'sunny',
      timeOfDay: 'day',
      difficulty: 'normal'
    }
  });

  // 更新玩家生命值
  const updatePlayerHealth = (health) => {
    updateGameState({
      player: { ...gameState.player, health }
    });
  };

  // 增加经验值并检查升级
  const addExperience = (exp) => {
    const newExp = gameState.player.experience + exp;
    const newLevel = Math.floor(newExp / 100) + 1;
    
    updateGameState({
      player: {
        ...gameState.player,
        experience: newExp,
        level: newLevel
      }
    });
  };

  // 添加物品到背包
  const addItemToInventory = (item) => {
    updateGameState({
      player: {
        ...gameState.player,
        inventory: [...gameState.player.inventory, item]
      }
    });
  };

  // 更新游戏进度
  const updateGameProgress = (progress) => {
    updateGameState({
      game: { ...gameState.game, ...progress }
    });
  };

  // 生成敌人
  const spawnEnemy = (enemy) => {
    updateGameState({
      enemies: [...gameState.enemies, enemy]
    });
  };

  // 移除敌人
  const removeEnemy = (enemyId) => {
    updateGameState({
      enemies: gameState.enemies.filter(enemy => enemy.id !== enemyId)
    });
  };

  return (
    <div className="game">
      <div className="player-info">
        <h3>{gameState.player.name}</h3>
        <p>生命值: {gameState.player.health}</p>
        <p>等级: {gameState.player.level}</p>
        <p>经验: {gameState.player.experience}</p>
        <p>背包物品: {gameState.player.inventory.length} 个</p>
      </div>

      <div className="game-info">
        <p>关卡: {gameState.game.currentLevel}</p>
        <p>分数: {gameState.game.score}</p>
        <p>时间: {gameState.game.timeElapsed}s</p>
        <p>敌人数量: {gameState.enemies.length}</p>
      </div>

      <div className="controls">
        <button onClick={() => updatePlayerHealth(Math.max(0, gameState.player.health - 10))}>
          受到伤害 (-10)
        </button>
        <button onClick={() => addExperience(50)}>
          获得经验 (+50)
        </button>
        <button onClick={() => addItemToInventory({ id: Date.now(), name: '药水' })}>
          获得物品
        </button>
        <button onClick={() => updateGameProgress({ score: gameState.game.score + 100 })}>
          增加分数 (+100)
        </button>
        <button onClick={() => spawnEnemy({ id: Date.now(), name: '怪物', health: 50 })}>
          生成敌人
        </button>
        <button onClick={resetGameState}>
          重新开始
        </button>
      </div>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116

最佳实践:

// 1. 使用 TypeScript 提供类型安全
interface UserState {
  name: string;
  email: string;
  age: number;
  address: {
    street: string;
    city: string;
    country: string;
  };
}

function useTypedComplexState<T>(initialState: T) {
  const [state, setState] = useState<T>(initialState);
  
  const updateState = useCallback((updates: Partial<T>) => {
    setState(prev => ({
      ...prev,
      ...updates
    }));
  }, []);
  
  const resetState = useCallback(() => {
    setState(initialState);
  }, [initialState]);
  
  return [state, updateState, resetState] as const;
}

// 2. 与 useCallback 结合使用优化性能
function OptimizedForm() {
  const [formData, updateFormData, resetFormData] = useComplexState({
    name: '',
    email: '',
    age: 0
  });

  const handleNameChange = useCallback((name: string) => {
    updateFormData({ name });
  }, [updateFormData]);

  const handleEmailChange = useCallback((email: string) => {
    updateFormData({ email });
  }, [updateFormData]);

  return (
    <form>
      <input
        value={formData.name}
        onChange={(e) => handleNameChange(e.target.value)}
        placeholder="姓名"
      />
      <input
        value={formData.email}
        onChange={(e) => handleEmailChange(e.target.value)}
        placeholder="邮箱"
      />
    </form>
  );
}

// 3. 与 useEffect 结合使用
function FormWithValidation() {
  const [formData, updateFormData, resetFormData] = useComplexState({
    name: '',
    email: '',
    age: 0
  });

  const [errors, setErrors] = useState({});

  // 表单验证
  useEffect(() => {
    const newErrors = {};
    
    if (!formData.name.trim()) {
      newErrors.name = '姓名不能为空';
    }
    
    if (!formData.email.includes('@')) {
      newErrors.email = '邮箱格式不正确';
    }
    
    if (formData.age < 0) {
      newErrors.age = '年龄不能为负数';
    }
    
    setErrors(newErrors);
  }, [formData]);

  const isFormValid = Object.keys(errors).length === 0;

  return (
    <form>
      <input
        value={formData.name}
        onChange={(e) => updateFormData({ name: e.target.value })}
        placeholder="姓名"
      />
      {errors.name && <span className="error">{errors.name}</span>}
      
      <input
        value={formData.email}
        onChange={(e) => updateFormData({ email: e.target.value })}
        placeholder="邮箱"
      />
      {errors.email && <span className="error">{errors.email}</span>}
      
      <button type="submit" disabled={!isFormValid}>
        提交
      </button>
    </form>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

优势总结:

  1. 简化状态更新:不需要手动展开对象
  2. 类型安全:保持状态对象的完整性
  3. 性能优化:使用 useCallback 避免不必要的重新渲染
  4. 代码复用:可以在多个组件中使用相同的状态管理逻辑
  5. 易于测试:状态更新逻辑集中在一个地方
  6. 维护性好:统一的状态更新模式

注意事项:

  1. 深度嵌套更新:对于深层嵌套的对象,需要手动展开每一层
  2. 数组更新:更新数组时需要创建新数组
  3. 性能考虑:对于大型状态对象,考虑使用 useMemo 优化
  4. 依赖项:确保在 useCallback 中包含正确的依赖项

# 7.2 异步操作模式

// 1. 异步状态管理
function useAsync(asyncFn, deps = []) {
  const [state, setState] = useState({
    loading: false,
    data: null,
    error: null
  });
  
  const execute = useCallback(async (...args) => {
    setState(prev => ({ ...prev, loading: true, error: null }));
    
    try {
      const data = await asyncFn(...args);
      setState({ loading: false, data, error: null });
      return data;
    } catch (error) {
      setState({ loading: false, data: null, error });
      throw error;
    }
  }, [asyncFn]);
  
  useEffect(() => {
    execute();
  }, deps);
  
  return { ...state, execute };
}

// 2. 防抖搜索
function useDebounceSearch(searchFn, delay = 300) {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, delay);
  
  const { loading, data, error } = useAsync(
    () => searchFn(debouncedQuery),
    [debouncedQuery]
  );
  
  return {
    query,
    setQuery,
    loading,
    data,
    error
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

# 异步操作模式详细指南

1. 基础异步状态管理

// 使用 useAsync 管理数据获取
function UserList() {
  const fetchUsers = useCallback(async () => {
    const response = await fetch('/api/users');
    if (!response.ok) {
      throw new Error('Failed to fetch users');
    }
    return response.json();
  }, []);

  const { loading, data: users, error, execute: refetch } = useAsync(fetchUsers);

  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误: {error.message}</div>;
  if (!users) return <div>暂无数据</div>;

  return (
    <div>
      <button onClick={refetch}>刷新</button>
      <ul>
        {users.map(user => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

// 带参数的异步操作
function UserProfile({ userId }) {
  const fetchUser = useCallback(async (id) => {
    const response = await fetch(`/api/users/${id}`);
    if (!response.ok) {
      throw new Error('Failed to fetch user');
    }
    return response.json();
  }, []);

  const { loading, data: user, error } = useAsync(
    () => fetchUser(userId),
    [userId]
  );

  if (loading) return <div>加载用户信息...</div>;
  if (error) return <div>加载失败: {error.message}</div>;
  if (!user) return <div>用户不存在</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>邮箱: {user.email}</p>
      <p>年龄: {user.age}</p>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

2. 请求取消和清理

// 使用 AbortController 取消请求
function useCancellableAsync(asyncFn, deps = []) {
  const [state, setState] = useState({
    loading: false,
    data: null,
    error: null
  });

  const execute = useCallback(async (...args) => {
    const abortController = new AbortController();
    
    setState(prev => ({ ...prev, loading: true, error: null }));
    
    try {
      const data = await asyncFn(...args, abortController.signal);
      setState({ loading: false, data, error: null });
      return data;
    } catch (error) {
      if (error.name !== 'AbortError') {
        setState({ loading: false, data: null, error });
      }
      throw error;
    }
  }, [asyncFn]);

  useEffect(() => {
    execute();
  }, deps);

  return { ...state, execute };
}

// 使用示例
function SearchResults({ query }) {
  const searchAPI = useCallback(async (searchQuery, signal) => {
    const response = await fetch(`/api/search?q=${searchQuery}`, { signal });
    if (!response.ok) {
      throw new Error('Search failed');
    }
    return response.json();
  }, []);

  const { loading, data: results, error } = useCancellableAsync(
    () => searchAPI(query),
    [query]
  );

  if (loading) return <div>搜索中...</div>;
  if (error) return <div>搜索失败: {error.message}</div>;

  return (
    <div>
      <h3>搜索结果 ({results?.length || 0})</h3>
      {results?.map(result => (
        <div key={result.id}>{result.title}</div>
      ))}
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

3. 防抖和节流模式

// 防抖 Hook
function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);
  
  return debouncedValue;
}

// 节流 Hook
function useThrottle(value, delay) {
  const [throttledValue, setThrottledValue] = useState(value);
  const lastRun = useRef(Date.now());
  
  useEffect(() => {
    const handler = setTimeout(() => {
      if (Date.now() - lastRun.current >= delay) {
        setThrottledValue(value);
        lastRun.current = Date.now();
      }
    }, delay - (Date.now() - lastRun.current));
    
    return () => {
      clearTimeout(handler);
    };
  }, [value, delay]);
  
  return throttledValue;
}

// 防抖搜索组件
function DebouncedSearch() {
  const [query, setQuery] = useState('');
  const debouncedQuery = useDebounce(query, 500);
  
  const { loading, data: results, error } = useAsync(
    async () => {
      if (!debouncedQuery.trim()) return [];
      const response = await fetch(`/api/search?q=${debouncedQuery}`);
      return response.json();
    },
    [debouncedQuery]
  );

  return (
    <div>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        placeholder="搜索..."
      />
      {loading && <div>搜索中...</div>}
      {error && <div>搜索失败: {error.message}</div>}
      {results && (
        <ul>
          {results.map(result => (
            <li key={result.id}>{result.title}</li>
          ))}
        </ul>
      )}
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

4. 并发请求管理

// 并发请求 Hook
function useConcurrentRequests() {
  const [requests, setRequests] = useState(new Map());
  
  const addRequest = useCallback((id, asyncFn) => {
    setRequests(prev => new Map(prev).set(id, { status: 'pending' }));
    
    asyncFn()
      .then(data => {
        setRequests(prev => new Map(prev).set(id, { status: 'success', data }));
      })
      .catch(error => {
        setRequests(prev => new Map(prev).set(id, { status: 'error', error }));
      });
  }, []);
  
  const removeRequest = useCallback((id) => {
    setRequests(prev => {
      const newMap = new Map(prev);
      newMap.delete(id);
      return newMap;
    });
  }, []);
  
  return { requests, addRequest, removeRequest };
}

// 使用示例
function MultiDataLoader() {
  const { requests, addRequest, removeRequest } = useConcurrentRequests();
  
  const loadUserData = useCallback(() => {
    addRequest('user', async () => {
      const response = await fetch('/api/user');
      return response.json();
    });
  }, [addRequest]);
  
  const loadPostsData = useCallback(() => {
    addRequest('posts', async () => {
      const response = await fetch('/api/posts');
      return response.json();
    });
  }, [addRequest]);
  
  const loadAllData = useCallback(() => {
    loadUserData();
    loadPostsData();
  }, [loadUserData, loadPostsData]);
  
  return (
    <div>
      <button onClick={loadAllData}>加载所有数据</button>
      
      {Array.from(requests.entries()).map(([id, request]) => (
        <div key={id}>
          <h3>{id}</h3>
          {request.status === 'pending' && <div>加载中...</div>}
          {request.status === 'success' && (
            <div>数据: {JSON.stringify(request.data)}</div>
          )}
          {request.status === 'error' && (
            <div>错误: {request.error.message}</div>
          )}
          <button onClick={() => removeRequest(id)}>移除</button>
        </div>
      ))}
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

5. 乐观更新模式

// 乐观更新 Hook
function useOptimisticUpdate(initialData, updateFn) {
  const [data, setData] = useState(initialData);
  const [pendingUpdates, setPendingUpdates] = useState(new Map());
  
  const optimisticUpdate = useCallback(async (id, optimisticData, actualUpdate) => {
    // 立即应用乐观更新
    setData(prev => ({ ...prev, ...optimisticData }));
    setPendingUpdates(prev => new Map(prev).set(id, optimisticData));
    
    try {
      // 执行实际更新
      const result = await actualUpdate();
      
      // 更新成功,移除待处理状态
      setPendingUpdates(prev => {
        const newMap = new Map(prev);
        newMap.delete(id);
        return newMap;
      });
      
      return result;
    } catch (error) {
      // 更新失败,回滚到原始状态
      setData(prev => {
        const newData = { ...prev };
        Object.keys(optimisticData).forEach(key => {
          delete newData[key];
        });
        return newData;
      });
      
      setPendingUpdates(prev => {
        const newMap = new Map(prev);
        newMap.delete(id);
        return newMap;
      });
      
      throw error;
    }
  }, []);
  
  return { data, pendingUpdates, optimisticUpdate };
}

// 使用示例
function TodoList() {
  const { data: todos, pendingUpdates, optimisticUpdate } = useOptimisticUpdate(
    [],
    async (newTodos) => {
      const response = await fetch('/api/todos', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(newTodos)
      });
      return response.json();
    }
  );
  
  const addTodo = useCallback(async (text) => {
    const newTodo = { id: Date.now(), text, completed: false };
    
    await optimisticUpdate(
      'add-todo',
      { [newTodo.id]: newTodo },
      async () => {
        const response = await fetch('/api/todos', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(newTodo)
        });
        return response.json();
      }
    );
  }, [optimisticUpdate]);
  
  return (
    <div>
      <button onClick={() => addTodo('新任务')}>添加任务</button>
      
      <ul>
        {Object.values(todos).map(todo => (
          <li key={todo.id}>
            {todo.text}
            {pendingUpdates.has(todo.id) && <span> (保存中...)</span>}
          </li>
        ))}
      </ul>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

6. 错误边界和重试机制

// 重试 Hook
function useRetry(asyncFn, maxRetries = 3, delay = 1000) {
  const [state, setState] = useState({
    loading: false,
    data: null,
    error: null,
    retryCount: 0
  });
  
  const execute = useCallback(async (...args) => {
    setState(prev => ({ ...prev, loading: true, error: null }));
    
    for (let i = 0; i <= maxRetries; i++) {
      try {
        const data = await asyncFn(...args);
        setState({ loading: false, data, error: null, retryCount: i });
        return data;
      } catch (error) {
        if (i === maxRetries) {
          setState({ loading: false, data: null, error, retryCount: i });
          throw error;
        }
        
        // 等待后重试
        await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
        setState(prev => ({ ...prev, retryCount: i + 1 }));
      }
    }
  }, [asyncFn, maxRetries, delay]);
  
  return { ...state, execute };
}

// 使用示例
function ResilientDataLoader() {
  const fetchData = useCallback(async () => {
    const response = await fetch('/api/unreliable-endpoint');
    if (!response.ok) {
      throw new Error('Network error');
    }
    return response.json();
  }, []);
  
  const { loading, data, error, retryCount, execute } = useRetry(fetchData, 3, 1000);
  
  if (loading) {
    return (
      <div>
        加载中... (重试次数: {retryCount})
      </div>
    );
  }
  
  if (error) {
    return (
      <div>
        <div>加载失败: {error.message}</div>
        <button onClick={execute}>重试</button>
      </div>
    );
  }
  
  return (
    <div>
      <h3>数据加载成功</h3>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

最佳实践总结:

  1. 请求取消:使用 AbortController 避免内存泄漏
  2. 防抖节流:优化用户输入和频繁操作
  3. 乐观更新:提升用户体验,减少等待时间
  4. 错误处理:提供重试机制和友好的错误提示
  5. 并发管理:合理控制同时进行的请求数量
  6. 状态管理:统一管理异步操作的状态

注意事项:

  1. 内存泄漏:确保在组件卸载时取消未完成的请求
  2. 错误边界:使用 React Error Boundary 捕获异步错误
  3. 性能优化:避免不必要的重新渲染
  4. 用户体验:提供加载状态和错误反馈
  5. 网络优化:合理使用缓存和请求合并

# 7.3 组件通信模式

// 1. 事件总线模式
function useEventBus() {
  const listeners = useRef(new Map());
  
  const subscribe = useCallback((event, callback) => {
    if (!listeners.current.has(event)) {
      listeners.current.set(event, new Set());
    }
    listeners.current.get(event).add(callback);
    
    return () => {
      const eventListeners = listeners.current.get(event);
      if (eventListeners) {
        eventListeners.delete(callback);
      }
    };
  }, []);
  
  const publish = useCallback((event, data) => {
    const eventListeners = listeners.current.get(event);
    if (eventListeners) {
      eventListeners.forEach(callback => callback(data));
    }
  }, []);
  
  return { subscribe, publish };
}

// 2. 状态提升模式
function useSharedState(initialState) {
  const [state, setState] = useState(initialState);
  
  const updateState = useCallback((updates) => {
    setState(prev => ({
      ...prev,
      ...updates
    }));
  }, []);
  
  return [state, updateState];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 组件通信模式详细指南

1. 事件总线模式

事件总线模式允许组件之间进行松耦合的通信,特别适合跨层级组件通信。

// 创建全局事件总线
const EventBus = createContext();

function EventBusProvider({ children }) {
  const eventBus = useEventBus();
  
  return (
    <EventBus.Provider value={eventBus}>
      {children}
    </EventBus.Provider>
  );
}

// 使用事件总线的组件
function Publisher() {
  const { publish } = useContext(EventBus);
  
  const handleClick = () => {
    publish('user-action', { type: 'button-click', timestamp: Date.now() });
  };
  
  return <button onClick={handleClick}>发布事件</button>;
}

function Subscriber() {
  const { subscribe } = useContext(EventBus);
  const [events, setEvents] = useState([]);
  
  useEffect(() => {
    const unsubscribe = subscribe('user-action', (data) => {
      setEvents(prev => [...prev, data]);
    });
    
    return unsubscribe;
  }, [subscribe]);
  
  return (
    <div>
      <h3>接收到的事件:</h3>
      <ul>
        {events.map((event, index) => (
          <li key={index}>
            {event.type} - {new Date(event.timestamp).toLocaleTimeString()}
          </li>
        ))}
      </ul>
    </div>
  );
}

// 使用示例
function App() {
  return (
    <EventBusProvider>
      <div>
        <Publisher />
        <Subscriber />
      </div>
    </EventBusProvider>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

2. 状态提升模式

状态提升是将共享状态提升到最近的共同父组件中,通过 props 传递给子组件。

// 共享状态 Hook
function useSharedCounter() {
  const [count, setCount] = useState(0);
  
  const increment = useCallback(() => {
    setCount(prev => prev + 1);
  }, []);
  
  const decrement = useCallback(() => {
    setCount(prev => prev - 1);
  }, []);
  
  const reset = useCallback(() => {
    setCount(0);
  }, []);
  
  return { count, increment, decrement, reset };
}

// 父组件管理共享状态
function CounterApp() {
  const { count, increment, decrement, reset } = useSharedCounter();
  
  return (
    <div>
      <h2>共享计数器: {count}</h2>
      <CounterDisplay count={count} />
      <CounterControls 
        onIncrement={increment}
        onDecrement={decrement}
        onReset={reset}
      />
      <CounterHistory count={count} />
    </div>
  );
}

// 子组件接收状态
function CounterDisplay({ count }) {
  return (
    <div className="counter-display">
      <h3>当前值: {count}</h3>
      <div className={`status ${count > 0 ? 'positive' : count < 0 ? 'negative' : 'zero'}`}>
        {count > 0 ? '正数' : count < 0 ? '负数' : '零'}
      </div>
    </div>
  );
}

function CounterControls({ onIncrement, onDecrement, onReset }) {
  return (
    <div className="counter-controls">
      <button onClick={onIncrement}>+1</button>
      <button onClick={onDecrement}>-1</button>
      <button onClick={onReset}>重置</button>
    </div>
  );
}

function CounterHistory({ count }) {
  const [history, setHistory] = useState([]);
  
  useEffect(() => {
    setHistory(prev => [...prev, { value: count, timestamp: Date.now() }]);
  }, [count]);
  
  return (
    <div className="counter-history">
      <h3>历史记录</h3>
      <ul>
        {history.slice(-5).reverse().map((item, index) => (
          <li key={index}>
            {item.value} - {new Date(item.timestamp).toLocaleTimeString()}
          </li>
        ))}
      </ul>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

3. Context 模式

使用 React Context 进行深层组件通信。

// 创建 Context
const UserContext = createContext();
const ThemeContext = createContext();

// Context Provider
function AppProvider({ children }) {
  const [user, setUser] = useState(null);
  const [theme, setTheme] = useState('light');
  
  const updateUser = useCallback((userData) => {
    setUser(userData);
  }, []);
  
  const toggleTheme = useCallback(() => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  }, []);
  
  return (
    <UserContext.Provider value={{ user, updateUser }}>
      <ThemeContext.Provider value={{ theme, toggleTheme }}>
        {children}
      </ThemeContext.Provider>
    </UserContext.Provider>
  );
}

// 自定义 Hook 简化 Context 使用
function useUser() {
  const context = useContext(UserContext);
  if (!context) {
    throw new Error('useUser must be used within UserProvider');
  }
  return context;
}

function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// 使用 Context 的组件
function UserProfile() {
  const { user, updateUser } = useUser();
  const { theme, toggleTheme } = useTheme();
  
  const handleLogin = () => {
    updateUser({ id: 1, name: 'Alice', email: '[email protected]' });
  };
  
  const handleLogout = () => {
    updateUser(null);
  };
  
  return (
    <div className={`profile ${theme}`}>
      <h2>用户信息</h2>
      {user ? (
        <div>
          <p>姓名: {user.name}</p>
          <p>邮箱: {user.email}</p>
          <button onClick={handleLogout}>退出登录</button>
        </div>
      ) : (
        <button onClick={handleLogin}>登录</button>
      )}
      <button onClick={toggleTheme}>
        切换主题 (当前: {theme})
      </button>
    </div>
  );
}

function App() {
  return (
    <AppProvider>
      <UserProfile />
    </AppProvider>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82

4. 发布-订阅模式

实现一个简单的发布-订阅系统。

// 发布-订阅 Hook
function usePubSub() {
  const subscribers = useRef(new Map());
  
  const subscribe = useCallback((topic, callback) => {
    if (!subscribers.current.has(topic)) {
      subscribers.current.set(topic, new Set());
    }
    subscribers.current.get(topic).add(callback);
    
    // 返回取消订阅函数
    return () => {
      const topicSubscribers = subscribers.current.get(topic);
      if (topicSubscribers) {
        topicSubscribers.delete(callback);
        if (topicSubscribers.size === 0) {
          subscribers.current.delete(topic);
        }
      }
    };
  }, []);
  
  const publish = useCallback((topic, data) => {
    const topicSubscribers = subscribers.current.get(topic);
    if (topicSubscribers) {
      topicSubscribers.forEach(callback => {
        try {
          callback(data);
        } catch (error) {
          console.error('Subscriber error:', error);
        }
      });
    }
  }, []);
  
  const unsubscribe = useCallback((topic, callback) => {
    const topicSubscribers = subscribers.current.get(topic);
    if (topicSubscribers) {
      topicSubscribers.delete(callback);
    }
  }, []);
  
  return { subscribe, publish, unsubscribe };
}

// 使用示例
function ChatRoom() {
  const { subscribe, publish } = usePubSub();
  const [messages, setMessages] = useState([]);
  const [users, setUsers] = useState([]);
  
  // 订阅消息
  useEffect(() => {
    const unsubscribeMessage = subscribe('message', (message) => {
      setMessages(prev => [...prev, message]);
    });
    
    const unsubscribeUser = subscribe('user-join', (user) => {
      setUsers(prev => [...prev, user]);
    });
    
    return () => {
      unsubscribeMessage();
      unsubscribeUser();
    };
  }, [subscribe]);
  
  const sendMessage = (text) => {
    const message = {
      id: Date.now(),
      text,
      timestamp: new Date(),
      user: 'CurrentUser'
    };
    publish('message', message);
  };
  
  const joinRoom = () => {
    const user = {
      id: Date.now(),
      name: 'User' + Math.floor(Math.random() * 1000)
    };
    publish('user-join', user);
  };
  
  return (
    <div className="chat-room">
      <div className="users">
        <h3>在线用户 ({users.length})</h3>
        <ul>
          {users.map(user => (
            <li key={user.id}>{user.name}</li>
          ))}
        </ul>
        <button onClick={joinRoom}>加入房间</button>
      </div>
      
      <div className="messages">
        <h3>消息 ({messages.length})</h3>
        <div className="message-list">
          {messages.map(message => (
            <div key={message.id} className="message">
              <span className="user">{message.user}:</span>
              <span className="text">{message.text}</span>
              <span className="time">
                {message.timestamp.toLocaleTimeString()}
              </span>
            </div>
          ))}
        </div>
        <button onClick={() => sendMessage('Hello!')}>发送消息</button>
      </div>
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

5. 状态机模式

使用状态机管理复杂的组件状态。

// 状态机 Hook
function useStateMachine(initialState, transitions) {
  const [state, setState] = useState(initialState);
  
  const transition = useCallback((event, payload) => {
    const currentTransitions = transitions[state];
    if (!currentTransitions || !currentTransitions[event]) {
      console.warn(`No transition found for event "${event}" in state "${state}"`);
      return;
    }
    
    const { target, action } = currentTransitions[event];
    setState(target);
    
    if (action) {
      action(payload);
    }
  }, [state, transitions]);
  
  return [state, transition];
}

// 使用示例:表单状态机
function FormWithStateMachine() {
  const transitions = {
    idle: {
      start: { target: 'editing' }
    },
    editing: {
      save: { target: 'saving' },
      cancel: { target: 'idle' },
      validate: { target: 'validating' }
    },
    validating: {
      valid: { target: 'editing' },
      invalid: { target: 'editing' }
    },
    saving: {
      success: { target: 'idle' },
      error: { target: 'editing' }
    }
  };
  
  const [state, transition] = useStateMachine('idle', transitions);
  const [formData, setFormData] = useState({ name: '', email: '' });
  const [errors, setErrors] = useState({});
  
  const handleStart = () => {
    transition('start');
  };
  
  const handleSave = async () => {
    transition('save');
    try {
      await saveForm(formData);
      transition('success');
    } catch (error) {
      transition('error');
    }
  };
  
  const handleCancel = () => {
    setFormData({ name: '', email: '' });
    setErrors({});
    transition('cancel');
  };
  
  const handleValidate = () => {
    transition('validate');
    const newErrors = {};
    
    if (!formData.name.trim()) {
      newErrors.name = '姓名不能为空';
    }
    
    if (!formData.email.includes('@')) {
      newErrors.email = '邮箱格式不正确';
    }
    
    setErrors(newErrors);
    transition(Object.keys(newErrors).length === 0 ? 'valid' : 'invalid');
  };
  
  return (
    <div className={`form-state-${state}`}>
      <h3>表单状态: {state}</h3>
      
      {state === 'idle' && (
        <button onClick={handleStart}>开始编辑</button>
      )}
      
      {(state === 'editing' || state === 'validating') && (
        <form>
          <input
            value={formData.name}
            onChange={(e) => setFormData(prev => ({ ...prev, name: e.target.value }))}
            placeholder="姓名"
          />
          {errors.name && <span className="error">{errors.name}</span>}
          
          <input
            value={formData.email}
            onChange={(e) => setFormData(prev => ({ ...prev, email: e.target.value }))}
            placeholder="邮箱"
          />
          {errors.email && <span className="error">{errors.email}</span>}
          
          <div className="actions">
            <button type="button" onClick={handleValidate}>验证</button>
            <button type="button" onClick={handleSave}>保存</button>
            <button type="button" onClick={handleCancel}>取消</button>
          </div>
        </form>
      )}
      
      {state === 'saving' && (
        <div>保存中...</div>
      )}
    </div>
  );
}

// 模拟保存函数
async function saveForm(data) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.5) {
        resolve(data);
      } else {
        reject(new Error('保存失败'));
      }
    }, 1000);
  });
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134

最佳实践总结:

  1. 选择合适的模式:根据组件关系选择合适的通信模式
  2. 避免过度使用:不要为了通信而通信,优先考虑组件设计
  3. 性能优化:使用 useCallback 和 useMemo 优化性能
  4. 类型安全:使用 TypeScript 提供类型安全
  5. 错误处理:为异步通信提供错误处理机制
  6. 测试友好:设计易于测试的通信接口

注意事项:

  1. 避免过度耦合:组件间通信应该保持松耦合
  2. 内存泄漏:及时清理事件监听器和订阅
  3. 性能考虑:避免不必要的重新渲染
  4. 调试困难:复杂的状态流可能难以调试
  5. 维护成本:过度使用全局状态可能增加维护成本
#React#Hooks
上次更新: 2025/10/09, 23:53:03
React 完整学习指南
REACT NATIVE

← React 完整学习指南 REACT NATIVE→

最近更新
01
npx 使用指南
10-12
02
cursor
09-28
03
inspect
07-20
更多文章>
Theme by Vdoing | Copyright © 2019-2025 Jacky | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式