sudo-iのBlog

  • 🍟首页
  • 🍊目录
    • 技术分享
    • vps教程
    • 软件分享
    • 干货分享
  • 🍎链接
  • 🍓工具
    • 🌽IP路由追踪
    • 域名被墙检测
    • KMS激活
    • 域名whois查询
  • 🍕联系
  • 🍌登录
Sudo-i
关注互联网,生活,音乐,乐此不疲
  1. 首页
  2. 干货分享
  3. 正文

React Hooks深度解析与最佳实践:从入门到精通

6 3 月, 2026 49点热度 0人点赞 0条评论

引言

自React 16.8引入Hooks以来,函数组件已经成为React开发的主流方式。Hooks让代码更简洁、逻辑复用更优雅,但使用不当也会带来性能问题和难以追踪的bug。本文将深入解析常用Hooks的原理,分享实战中的最佳实践和常见陷阱。

一、useState:状态管理的基石

1.1 基本用法

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  
  return (
    <button onClick={() => setCount(count + 1)}>
      点击次数:{count}
    </button>
  );
}

1.2 函数式更新(推荐)

当新状态依赖于旧状态时,使用函数式更新可以避免闭包陷阱:

// ❌ 可能有问题的写法
const handleClick = () => {
  setCount(count + 1);
  setCount(count + 1);  // 这里的count还是旧值
};

// ✓ 推荐:函数式更新
const handleClick = () => {
  setCount(prev => prev + 1);
  setCount(prev => prev + 1);  // 正确累加两次
};

1.3 状态初始化优化

// ❌ 每次渲染都执行expensiveCalculation
const [state, setState] = useState(expensiveCalculation());

// ✓ 推荐:惰性初始化(只执行一次)
const [state, setState] = useState(() => expensiveCalculation());

二、useEffect:副作用处理

2.1 依赖数组的正确使用

import { useEffect, useState } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  // ✓ 正确:userId在依赖数组中
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  
  // ❌ 错误:缺少依赖,可能获取到旧数据
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, []);  // userId变化时不会重新执行
}

2.2 清理函数(Cleanup)

useEffect(() => {
  const subscription = api.subscribe(handleData);
  
  // 清理函数:组件卸载或依赖变化时执行
  return () => {
    subscription.unsubscribe();
  };
}, [dependencies]);

2.3 避免无限循环

// ❌ 无限循环:对象引用每次都是新的
const [options, setOptions] = useState({ page: 1 });
useEffect(() => {
  fetchData(options);
  setOptions({ page: options.page + 1 });  // 新对象,触发重新渲染
}, [options]);

// ✓ 优化:使用状态更新函数
useEffect(() => {
  fetchData(options);
}, [options]);

const loadMore = () => {
  setOptions(prev => ({ ...prev, page: prev.page + 1 }));
};

三、useCallback: memoized函数

3.1 使用场景

useCallback用于缓存函数引用,主要在以下场景使用:

  1. 传递给React.memo优化的子组件
  2. 作为useEffect的依赖
  3. 作为其他Hooks的依赖
import { useCallback, useState } from 'react';

function Parent() {
  const [count, setCount] = useState(0);
  
  // ✓ 缓存函数引用,避免子组件不必要的重渲染
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);
  
  return (
    <>
      <Child onClick={handleClick} />
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
    </>
  );
}

// 子组件使用React.memo优化
const Child = React.memo(({ onClick }) => {
  console.log('Child rendered');
  return <button onClick={onClick}>Click me</button>;
});

3.2 常见误区

// ❌ 过度使用useCallback(增加复杂度,收益小)
const handleChange = useCallback((e) => {
  setValue(e.target.value);
}, []);

// ✓ 普通函数即可(除非有特定优化需求)
const handleChange = (e) => {
  setValue(e.target.value);
};

四、useMemo:缓存计算结果

4.1 性能优化场景

import { useMemo, useState } from 'react';

function ProductList({ products, filter }) {
  const [search, setSearch] = useState('');
  
  // ✓ 缓存昂贵的过滤和排序操作
  const filteredProducts = useMemo(() => {
    console.log('过滤和排序执行');
    return products
      .filter(p => p.name.includes(search))
      .filter(p => p.category === filter)
      .sort((a, b) => b.price - a.price);
  }, [products, search, filter]);
  
  return (
    <ul>
      {filteredProducts.map(p => (
        <li key={p.id}>{p.name} - ${p.price}</li>
      ))}
    </ul>
  );
}

4.2 不要过早优化

// ❌ 不必要的useMemo(简单计算开销很小)
const fullName = useMemo(() => `${firstName} ${lastName}`, [firstName, lastName]);

// ✓ 直接计算即可
const fullName = `${firstName} ${lastName}`;

五、useRef:持久化引用

5.1 访问DOM元素

import { useRef, useEffect } from 'react';

function FocusInput() {
  const inputRef = useRef(null);
  
  useEffect(() => {
    inputRef.current?.focus();
  }, []);
  
  return <input ref={inputRef} type="text" />;
}

5.2 保存可变值(不触发重渲染)

function Timer() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);
  
  // ✓ 使用ref保存定时器ID,不触发重渲染
  const start = () => {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);
  };
  
  const stop = () => {
    clearInterval(intervalRef.current);
  };
  
  return (
    <>
      <p>{count}</p>
      <button onClick={start}>开始</button>
      <button onClick={stop}>停止</button>
    </>
  );
}

六、自定义Hooks:逻辑复用

6.1 创建自定义Hook

// hooks/useFetch.js
import { useState, useEffect } from 'react';

export function useFetch(url, options = {}) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    let cancelled = false;
    
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(url, options);
        if (!response.ok) throw new Error('Request failed');
        const result = await response.json();
        if (!cancelled) setData(result);
      } catch (err) {
        if (!cancelled) setError(err);
      } finally {
        if (!cancelled) setLoading(false);
      }
    };
    
    fetchData();
    
    return () => {
      cancelled = true;
    };
  }, [url, JSON.stringify(options)]);
  
  return { data, loading, error };
}

6.2 使用自定义Hook

import { useFetch } from './hooks/useFetch';

function UserList() {
  const { data: users, loading, error } = useFetch('/api/users');
  
  if (loading) return <div>加载中...</div>;
  if (error) return <div>错误:{error.message}</div>;
  
  return (
    <ul>
      {users?.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

6.3 实用的自定义Hooks示例

// hooks/useLocalStorage.js
export function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      return initialValue;
    }
  });
  
  const setValue = (value) => {
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      console.error(error);
    }
  };
  
  return [storedValue, setValue];
}

// hooks/useDebounce.js
export function useDebounce(value, delay) {
  const [debouncedValue, setDebouncedValue] = useState(value);
  
  useEffect(() => {
    const handler = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);
    
    return () => clearTimeout(handler);
  }, [value, delay]);
  
  return debouncedValue;
}

七、useReducer:复杂状态管理

import { useReducer } from 'react';

const initialState = { count: 0 };

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

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  
  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'reset' })}>重置</button>
    </>
  );
}

八、性能优化最佳实践

8.1 组件拆分

// ❌ 大组件,任何状态变化都导致全部重渲染
function Parent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  return (
    <>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <input value={text} onChange={e => setText(e.target.value)} />
      <ExpensiveComponent count={count} />
    </>
  );
}

// ✓ 拆分为小组件,独立优化
function Parent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');
  
  return (
    <>
      <CountButton count={count} onIncrement={() => setCount(c => c + 1)} />
      <TextInput value={text} onChange={setText} />
      <ExpensiveComponent count={count} />
    </>
  );
}

8.2 使用React.memo

const ExpensiveComponent = React.memo(({ data }) => {
  console.log('ExpensiveComponent rendered');
  return <div>{data}</div>;
});

九、常见陷阱与解决方案

9.1 闭包陷阱

// ❌ 闭包陷阱:setTimeout中的count是旧值
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setTimeout(() => {
      console.log(count);  // 始终是0
    }, 3000);
    return () => clearTimeout(timer);
  }, []);
}

// ✓ 解决方案1:使用ref
function Counter() {
  const [count, setCount] = useState(0);
  const countRef = useRef(count);
  countRef.current = count;
  
  useEffect(() => {
    const timer = setTimeout(() => {
      console.log(countRef.current);  // 最新值
    }, 3000);
    return () => clearTimeout(timer);
  }, []);
}

// ✓ 解决方案2:使用函数式更新
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setTimeout(() => {
      setCount(prev => {
        console.log(prev);  // 最新值
        return prev;
      });
    }, 3000);
    return () => clearTimeout(timer);
  }, []);
}

9.2 依赖数组遗漏

使用ESLint插件自动检查:

// .eslintrc.js
{
  "plugins": ["react-hooks"],
  "rules": {
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "warn"
  }
}

十、Hooks规则

  1. 只在最顶层使用Hooks - 不要在循环、条件或嵌套函数中调用
  2. 只在React函数中调用Hooks - 不要在普通JavaScript函数中调用
  3. 使用ESLint插件 - 自动检查规则遵守情况

总结

React Hooks让函数组件更强大,但需要正确使用:

  • ✅ useState:简单状态用直接更新,依赖旧状态用函数式更新
  • ✅ useEffect:正确设置依赖数组,记得清理副作用
  • ✅ useCallback/useMemo:按需使用,不要过早优化
  • ✅ useRef:保存可变值或访问DOM
  • ✅ 自定义Hooks:提取可复用逻辑
  • ✅ 遵守Hooks规则,使用ESLint检查

掌握这些最佳实践,让你的React代码更优雅、更高效!


欢迎在评论区分享你的Hooks使用经验!

无关联文章

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:6 3 月, 2026

李炫炫

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2025 sudo-iのBlog. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

鲁ICP备2024054662号

鲁公网安备37108102000450号