BlockCoder
前端开发··16 分钟

React Hooks 完全指南:从入门到精通

深入理解 React Hooks 的工作原理,掌握常用 Hooks 的使用技巧,学会创建自定义 Hooks 来提升代码复用性。

#React#Hooks#JavaScript#前端框架

React Hooks 是 React 16.8 引入的新特性,它让我们可以在函数组件中使用状态和其他 React 特性。本文将深入探讨 Hooks 的使用方法和最佳实践。

🎯 为什么需要 Hooks?

在 Hooks 出现之前,函数组件被称为"无状态组件",只能接收 props 并返回 JSX。如果需要状态管理或生命周期方法,就必须使用类组件。

类组件的问题

  • 代码复杂度高
  • 难以复用状态逻辑
  • 生命周期方法中逻辑分散
  • this 绑定问题

📚 常用 Hooks 详解

useState - 状态管理

jsx
1import React, { useState } from 'react';
2
3function Counter() {
4  const [count, setCount] = useState(0);
5  
6  return (
7    <div>
8      <p>当前计数: {count}</p>
9      <button onClick={() => setCount(count + 1)}>
10        增加
11      </button>
12    </div>
13  );
14}

关键要点:

  • 初始状态只在首次渲染时使用
  • setState 是异步的
  • 可以传入函数来基于前一个状态更新

useEffect - 副作用处理

jsx
1import React, { useState, useEffect } from 'react';
2
3function UserProfile({ userId }) {
4  const [user, setUser] = useState(null);
5  const [loading, setLoading] = useState(true);
6
7  useEffect(() => {
8    async function fetchUser() {
9      setLoading(true);
10      try {
11        const response = await fetch(`/api/users/${userId}`);
12        const userData = await response.json();
13        setUser(userData);
14      } catch (error) {
15        console.error('获取用户信息失败:', error);
16      } finally {
17        setLoading(false);
18      }
19    }
20
21    fetchUser();
22  }, [userId]); // 依赖数组
23
24  if (loading) return <div>加载中...</div>;
25  if (!user) return <div>用户不存在</div>;
26
27  return (
28    <div>
29      <h2>{user.name}</h2>
30      <p>{user.email}</p>
31    </div>
32  );
33}

useContext - 上下文消费

jsx
1import React, { useContext, createContext } from 'react';
2
3const ThemeContext = createContext();
4
5function App() {
6  return (
7    <ThemeContext.Provider value="dark">
8      <Header />
9    </ThemeContext.Provider>
10  );
11}
12
13function Header() {
14  const theme = useContext(ThemeContext);
15  return (
16    <header className={`theme-${theme}`}>
17      <h1>我的应用</h1>
18    </header>
19  );
20}

useReducer - 复杂状态管理

jsx
1import React, { useReducer } from 'react';
2
3const initialState = {
4  todos: [],
5  filter: 'all'
6};
7
8function todoReducer(state, action) {
9  switch (action.type) {
10    case 'ADD_TODO':
11      return {
12        ...state,
13        todos: [...state.todos, {
14          id: Date.now(),
15          text: action.text,
16          completed: false
17        }]
18      };
19    case 'TOGGLE_TODO':
20      return {
21        ...state,
22        todos: state.todos.map(todo =>
23          todo.id === action.id
24            ? { ...todo, completed: !todo.completed }
25            : todo
26        )
27      };
28    case 'SET_FILTER':
29      return {
30        ...state,
31        filter: action.filter
32      };
33    default:
34      return state;
35  }
36}
37
38function TodoApp() {
39  const [state, dispatch] = useReducer(todoReducer, initialState);
40
41  const addTodo = (text) => {
42    dispatch({ type: 'ADD_TODO', text });
43  };
44
45  const toggleTodo = (id) => {
46    dispatch({ type: 'TOGGLE_TODO', id });
47  };
48
49  return (
50    <div>
51      {/* Todo 应用 UI */}
52    </div>
53  );
54}

🔧 自定义 Hooks

自定义 Hooks 让我们可以提取组件逻辑到可复用的函数中。

useLocalStorage

jsx
1import { useState, useEffect } from 'react';
2
3function useLocalStorage(key, initialValue) {
4  const [storedValue, setStoredValue] = useState(() => {
5    try {
6      const item = window.localStorage.getItem(key);
7      return item ? JSON.parse(item) : initialValue;
8    } catch (error) {
9      console.error('读取 localStorage 失败:', error);
10      return initialValue;
11    }
12  });
13
14  const setValue = (value) => {
15    try {
16      setStoredValue(value);
17      window.localStorage.setItem(key, JSON.stringify(value));
18    } catch (error) {
19      console.error('写入 localStorage 失败:', error);
20    }
21  };
22
23  return [storedValue, setValue];
24}
25
26// 使用示例
27function Settings() {
28  const [theme, setTheme] = useLocalStorage('theme', 'light');
29  
30  return (
31    <div>
32      <p>当前主题: {theme}</p>
33      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
34        切换主题
35      </button>
36    </div>
37  );
38}

useFetch

jsx
1import { useState, useEffect } from 'react';
2
3function useFetch(url) {
4  const [data, setData] = useState(null);
5  const [loading, setLoading] = useState(true);
6  const [error, setError] = useState(null);
7
8  useEffect(() => {
9    const fetchData = async () => {
10      try {
11        setLoading(true);
12        setError(null);
13        const response = await fetch(url);
14        if (!response.ok) {
15          throw new Error(`HTTP error! status: ${response.status}`);
16        }
17        const result = await response.json();
18        setData(result);
19      } catch (err) {
20        setError(err.message);
21      } finally {
22        setLoading(false);
23      }
24    };
25
26    fetchData();
27  }, [url]);
28
29  return { data, loading, error };
30}
31
32// 使用示例
33function UserList() {
34  const { data: users, loading, error } = useFetch('/api/users');
35
36  if (loading) return <div>加载中...</div>;
37  if (error) return <div>错误: {error}</div>;
38
39  return (
40    <ul>
41      {users.map(user => (
42        <li key={user.id}>{user.name}</li>
43      ))}
44    </ul>
45  );
46}

⚡ 性能优化

useMemo

jsx
1import React, { useMemo } from 'react';
2
3function ExpensiveList({ items, filter }) {
4  const filteredItems = useMemo(() => {
5    console.log('过滤计算执行');
6    return items.filter(item => 
7      item.name.toLowerCase().includes(filter.toLowerCase())
8    );
9  }, [items, filter]);
10
11  return (
12    <ul>
13      {filteredItems.map(item => (
14        <li key={item.id}>{item.name}</li>
15      ))}
16    </ul>
17  );
18}

useCallback

jsx
1import React, { useCallback, useState } from 'react';
2
3function TodoList({ todos }) {
4  const [filter, setFilter] = useState('');
5
6  const handleToggle = useCallback((id) => {
7    // 处理切换逻辑
8  }, []);
9
10  const handleDelete = useCallback((id) => {
11    // 处理删除逻辑
12  }, []);
13
14  return (
15    <div>
16      {todos.map(todo => (
17        <TodoItem
18          key={todo.id}
19          todo={todo}
20          onToggle={handleToggle}
21          onDelete={handleDelete}
22        />
23      ))}
24    </div>
25  );
26}

📋 Hooks 使用规则

1. 只在顶层调用 Hooks

jsx
1// ✅ 正确
2function MyComponent() {
3  const [count, setCount] = useState(0);
4  
5  return <div>{count}</div>;
6}
7
8// ❌ 错误
9function MyComponent() {
10  if (someCondition) {
11    const [count, setCount] = useState(0); // 不要在条件语句中调用
12  }
13}

2. 只在 React 函数中调用 Hooks

jsx
1// ✅ 正确 - 在函数组件中
2function MyComponent() {
3  const [state, setState] = useState();
4  return <div />;
5}
6
7// ✅ 正确 - 在自定义 Hook 中
8function useCustomHook() {
9  const [state, setState] = useState();
10  return state;
11}
12
13// ❌ 错误 - 在普通函数中
14function regularFunction() {
15  const [state, setState] = useState(); // 不要在普通函数中调用
16}

🎯 最佳实践

1. 合理拆分状态

jsx
1// ❌ 不推荐 - 将所有状态放在一个对象中
2const [state, setState] = useState({
3  name: '',
4  email: '',
5  age: 0,
6  loading: false
7});
8
9// ✅ 推荐 - 按逻辑分组拆分状态
10const [userInfo, setUserInfo] = useState({ name: '', email: '', age: 0 });
11const [loading, setLoading] = useState(false);

2. 优化依赖数组

jsx
1// ❌ 缺少依赖
2useEffect(() => {
3  fetchUser(userId);
4}, []); // userId 变化时不会重新执行
5
6// ✅ 包含所有依赖
7useEffect(() => {
8  fetchUser(userId);
9}, [userId]);

3. 避免不必要的重渲染

jsx
1// 使用 React.memo 包装子组件
2const ChildComponent = React.memo(({ title, onClick }) => {
3  return <button onClick={onClick}>{title}</button>;
4});
5
6function ParentComponent() {
7  const [count, setCount] = useState(0);
8  
9  // 使用 useCallback 缓存函数
10  const handleClick = useCallback(() => {
11    console.log('按钮被点击');
12  }, []);
13
14  return (
15    <div>
16      <p>{count}</p>
17      <ChildComponent title="点击我" onClick={handleClick} />
18    </div>
19  );
20}

🚀 总结

React Hooks 为函数组件带来了强大的能力:

  • useState - 简单状态管理
  • useEffect - 处理副作用
  • useContext - 消费上下文
  • useReducer - 复杂状态管理
  • useMemo/useCallback - 性能优化
  • 自定义 Hooks - 逻辑复用

掌握这些 Hooks 的使用技巧和最佳实践,能够让你写出更简洁、更易维护的 React 代码。

记住 Hooks 的两个基本规则,合理组织状态和副作用,你就能充分发挥 Hooks 的威力!

评论讨论

使用 GitHub 账号登录即可参与讨论