React常见的Hooks

发布于:2025-08-15 ⋅ 阅读:(13) ⋅ 点赞:(0)


React Hooks 是 React 16.8 引入的特性,允许你在函数组件中使用状态和其他 React 特性。以下是 React 中主要的 Hooks 分类和具体用法:

一、基础Hooks

1、useState

  • 在函数中添加状态管理
  • 返回一个状态值和一个更新状态的函数
import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

2、useEffect

  • 处理副作用(数据获取、订阅、手动修改DOM等)
  • 可以模拟 componentDidMount、componentDidUpdate 和 componentWillUnmount
import { useState, useEffect } from 'react';

function Example() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('https://api.example.com/data')
      .then(res => res.json())
      .then(data => setData(data));
      
    return () => {
      // 清理函数(如取消订阅)
    };
  }, []); // 空数组表示只在组件挂载时运行一次
}

3、useContext

访问React中的上下文(Context),避免多层 props 传递

import { useContext } from 'react';
const ThemeContext = React.createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button style={{ background: theme === 'dark' ? '#333' : '#eee' }}>按钮</button>;
}

二、额外Hooks

4、useReducer

复杂状态逻辑管理(类似Redux)

useReducer 基本语法:

const [state, dispatch] = useReducer(reducer, initialState, initFunction);

参数说明:

  • reducer:一个函数,形式为(state,antion)=> newState
  • initialStata:状态初始值
  • initFunction(可选):用于惰性初始化状态的函数

工作原理:

  • 组件通过dispatch(action)发出动作
  • React调用reducer(currentState,action)计算新状态
  • 组件使用新状态重新渲染
import { useReducer } from 'react';

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

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
    </>
  );
}

5、useRef

访问DOM节点或保存可变值(不会触发重新渲染)

访问DOM节点:

const inputRef = useRef(null);
<input ref={inputRef} />

保存可变值:创建可变引用,修改其值不会触发更新

import { useRef } from 'react';

function Counter() {
  const countRef = useRef(0); // 初始化值为0
  
  const handleClick = () => {
    countRef.current += 1;    // 修改ref值不会触发重新渲染
    console.log(countRef.current);
  };

  return <button onClick={handleClick}>点击</button>;
}

特点:

  • 不会触发重新渲染:修改 .current属性,不会导致组件重新渲染
  • 跨渲染周期保持值:在组件的整个生命周期中保持同一个引用
  • 与实例变量类似:类似于类组件中的this.xxx属性

常见使用场景:

1、访问DOM元素

function TextInput() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus(); // 访问DOM元素
  };

  return (
    <>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>聚焦输入框</button>
    </>
  );
}

2、保存可变值(不触发重新渲染)

function Timer() {
  const countRef = useRef(0);
  const [dummy, setDummy] = useState(0); // 用于强制渲染

  useEffect(() => {
    const id = setInterval(() => {
      countRef.current += 1; // 修改ref值
      console.log(countRef.current);
      
      // 每5次更新一次UI
      if (countRef.current % 5 === 0) {
        setDummy(d => d + 1);
      }
    }, 1000);
    
    return () => clearInterval(id);
  }, []);

  return <div>计数: {countRef.current}</div>;
}

3、保存上一次值

function Counter() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();
  
  useEffect(() => {
    prevCountRef.current = count; // 在渲染后更新ref
  }); // 没有依赖数组,每次渲染后都执行

  return (
    <div>
      <p>当前: {count}, 之前: {prevCountRef.current}</p>
      <button onClick={() => setCount(c => c + 1)}>增加</button>
    </div>
  );
}

与useState区别:

特性 useRef useState
触发重新渲染
值存储位置 返回对象的.current属性 直接返回状态值
更新方式 直接修改.current 必须通过setState函数
使用场景 需要保持可变但不触发渲染的值 需要触发UI更新的状态

4、高级用法-在useCallback中使用ref

function Form() {
  const [value, setValue] = useState('');
  const valueRef = useRef(value);

  useEffect(() => {
    valueRef.current = value; // 同步value到ref
  }, [value]);

  const handleSubmit = useCallback(() => {
    console.log('提交的值:', valueRef.current); // 总是获取最新值
  }, []); // 不需要依赖value

  return (
    <form onSubmit={handleSubmit}>
      <input value={value} onChange={e => setValue(e.target.value)} />
    </form>
  );
}

5、实现组件实例方法

function FancyInput(props, ref) {
  const inputRef = useRef();
  
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    },
    scrollIntoView: () => {
      inputRef.current.scrollIntoView();
    }
  }));

  return <input ref={inputRef} />;
}

const ForwardedFancyInput = forwardRef(FancyInput);

// 父组件使用
function Parent() {
  const inputRef = useRef();
  
  const handleClick = () => {
    inputRef.current.focus(); // 调用子组件方法
  };
  
  return (
    <>
      <ForwardedFancyInput ref={inputRef} />
      <button onClick={handleClick}>聚焦输入框</button>
    </>
  );
}

注意事项:

1、不要在渲染期间写入ref

// ❌ 错误写法
function MyComponent() {
  const myRef = useRef();
  myRef.current = 123; // 渲染期间修改ref
  return ...;
}

// ✅ 正确写法
function MyComponent() {
  const myRef = useRef(123); // 初始化时设置
  useEffect(() => {
    myRef.current = 456; // 在effect中修改
  }, []);
  return ...;
}

2、与 useEffect 配合使用时注意时序:

useEffect(() => {
  // 这里的ref.current可能不是最新的
}, []);

useEffect(() => {
  // 更好的做法是在依赖数组中包含ref.current
}, [ref.current]);

6、useMemo

性能优化、缓存计算结果

useMemo 是 React 提供的一个性能优化 Hook,用于缓存计算结果,避免在每次渲染时都进行高开销的计算

基本语法:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

参数说明:

  • 计算函数:返回需要缓存的值
  • 依赖数组:只有依赖变化的时候,才会重新计算值

常见场景:

1、复杂计算优化

const sortedList = useMemo(() => {
  return largeList.sort((a, b) => a.value - b.value);
}, [largeList]);

2、避免子组件不必要的渲染

const childProps = useMemo(() => ({ value: props.value }), [props.value]);
return <Child {...childProps} />;

3、引用类型稳定性

const config = useMemo(() => ({
  color: theme === 'dark' ? 'white' : 'black',
  size: 'large'
}), [theme]);

错误写法:

1、缺少依赖项

const badExample = useMemo(() => a + b, [a]); // 缺少b依赖

2、副作用操作

// 不应该在useMemo中执行副作用
useMemo(() => {
  fetchData(); // ❌ 副作用应放在useEffect中
}, []);

3、总是重新计算

// 空依赖数组表示只计算一次
const once = useMemo(() => compute(a), []); 
// 如果a可能变化,这会导致使用过期值

注意:

  • 所有回调函数内部用到的变量都必须包含在依赖数组中
  • 故意省略依赖是危险的,会导致缓存失效或使用过期值
  • 如果发现需要省略某些依赖,通常意味着代码结构需要重构

7、useCallback

缓存函数,避免不必要的重新渲染 从而优化性能

useCallback 会返回一个记忆化(memoized)的回调函数,只有当依赖项发生变化时才会重新创建函数。它的主要用途是:

  • 避免子组件不必要的重新渲染(配合 React.memo 使用)
  • 保持函数引用稳定,避免作为依赖项触发不必要的 effect 执行
  • 优化性能敏感的场景(如高频触发的事件处理)

基本语法:

const memoizedCallback = useCallback(
() => {
// 函数逻辑
},
[dep1, dep2], // 依赖项数组
);

典型使用场景:

1、避免子组件不必要的重新渲染

// 子组件
const Child = React.memo(function Child({ onClick }) {
  console.log('Child 渲染');
  return <button onClick={onClick}>点击</button>;
});

// 父组件
function Parent() {
  const [count, setCount] = useState(0);
  
  // 使用useCallback缓存函数
  const handleClick = useCallback(() => {
    console.log('点击处理');
  }, []); // 空依赖表示函数永远不会改变

  return (
    <div>
      <Child onClick={handleClick} />
      <button onClick={() => setCount(c => c + 1)}>计数: {count}</button>
    </div>
  );
}

效果:当父组件状态更新时,Child 组件不会重新渲染,因为 onClick 的引用保持不变。

2、作为其他Hooks的依赖项

function Example({ id }) {
  const [data, setData] = useState(null);
  
  // 使用useCallback确保fetchData引用稳定
  const fetchData = useCallback(async () => {
    const result = await fetch(`/api/data/${id}`);
    setData(await result.json());
  }, [id]); // id变化时才重新创建函数

  useEffect(() => {
    fetchData();
  }, [fetchData]); // 安全地将函数作为依赖

  return <div>{data ? data.name : '加载中...'}</div>;
}

3、高频事件

function ScrollHandler() {
  const handleScroll = useCallback(() => {
    console.log('滚动位置:', window.scrollY);
  }, []);

  useEffect(() => {
    window.addEventListener('scroll', handleScroll);
    return () => window.removeEventListener('scroll', handleScroll);
  }, [handleScroll]);

  return <div style={{ height: '200vh' }}>滚动测试</div>;
}

与普通函数区别:

特性 普通函数 useCallback 记忆化函数
创建时机 每次渲染都重新创建 依赖变化时才重新创建
引用稳定性 每次渲染引用不同 依赖不变时引用相同
性能影响 可能导致子组件不必要渲染 减少不必要的子组件渲染
适用场景 简单场景 作为prop传递或hook依赖时使用

注意事项:

  • 不要过度使用:简单组件不需要使用useCallback,过早优化可能适得其反
  • 正确设置依赖:遗漏依赖可能导致闭包问题(获取过时的值)
  • 与useMemo区别:useCallback是缓存函数本身,useMemo是缓存计算结果
  • 配合 React.memo 使用:单独使用 useCallback 而没有 React.memo 通常不会带来性能提升

替代写法比较:
直接在effect中定义函数(不推荐)

useEffect(() => {
  const fetchData = async () => {
    const result = await fetch(`/api/data/${id}`);
    setData(await result.json());
  };
  fetchData();
}, [id]); // 需要禁用eslint警告

问题:

每次渲染都会创建新函数(尽管可能不会执行)
如果函数体复杂,可能造成不必要的性能开销

写法2:使用 useCallback + useEffect(推荐)

const fetchData = useCallback(async () => {
  // ...
}, [id]);

useEffect(() => {
  fetchData();
}, [fetchData]);

优点:

函数只在必要时重新创建
依赖关系明确
适合复杂函数逻辑

关键点:

useEffect 执行规则总结表:

依赖项情况 注册时机 注册时机 典型用途
无依赖数组 每次渲染都重新注册 每次渲染后都执行 极少使用(通常需要lint禁用)
[] 空数组 每次渲染都重新注 仅首次渲染后执行(挂载时) 组件挂载时的初始化操作
[dep] 有依赖 每次渲染都重新注册 首次渲染 + 依赖项变化时执行 数据获取、订阅管理等

这里的重点: 注册 ≠ 执行,每次渲染都会注册(可以理解为新回调函数放入React的待处理列表),但是是否“执行”取决于依赖项是否变化

三、自定义Hooks

8、自定义 Hook 示例

作用:复用状态逻辑

import { useState, useEffect } from 'react';

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return width;
}

function MyComponent() {
  const width = useWindowWidth();
  return <div>Window width: {width}</div>;
}

四、其他Hooks

9、useLayoutEffect

作用:与 useEffect 类似,但在DOM更新后同步触发、而不是异步执行

import { useLayoutEffect } from 'react';

function MeasureExample() {
  const [size, setSize] = useState({});
  const divRef = useRef();
  
  useLayoutEffect(() => {
    setSize(divRef.current.getBoundingClientRect());
  }, []);
  
  return <div ref={divRef}>{JSON.stringify(size)}</div>;
}

与 useEffect 的区别

1、执行时机

  • useLayoutEffect:在 DOM 更新后,浏览器绘制前同步执行
  • useEffect:在 DOM 更新后,浏览器绘制后异步执行

2、使用场景

  • useLayoutEffect:当你需要读取或修改 DOM 布局,并且不希望用户看到闪烁时使用
  • useEffect:适用于大多数副作用场景,如数据获取、订阅等

场景案例

1、测量DOM元素

function MeasureExample() {
  const [size, setSize] = useState({});
  const ref = useRef();
  
  useLayoutEffect(() => {
    setSize(ref.current.getBoundingClientRect());
  }, []);
  
  return <div ref={ref}>{JSON.stringify(size)}</div>;
}

2、同步更新 DOM

function Tooltip({ children, targetRef }) {
  const [position, setPosition] = useState({ top: 0, left: 0 });
  
  useLayoutEffect(() => {
    const targetRect = targetRef.current.getBoundingClientRect();
    setPosition({
      top: targetRect.bottom,
      left: targetRect.left
    });
  }, [targetRef]);
  
  return (
    <div style={{ position: 'fixed', ...position }}>
      {children}
    </div>
  );
}

3、防止闪烁:当你需要修改 DOM 样式或布局时,使用 useLayoutEffect 可以确保在浏览器绘制前完成,避免用户看到中间状态。

注意事项:

1、性能影响:由于 useLayoutEffect 是同步执行的,复杂的逻辑可能会阻塞浏览器渲染。所以,只在必要时使用,大多数情况下应优先使用 useEffect
2、服务器渲染 (SSR):在服务器端渲染时,useLayoutEffect 会导致警告,因为它不能在服务器端执行,如果需要在 SSR 中使用,可以考虑使用 useEffect 或动态加载组件
3、执行顺序:组件内多个 useLayoutEffect 按声明顺序执行,清理函数按相反顺序执行
4、将 DOM 读取和写入操作放在同一个 useLayoutEffect 中,避免不必要的重排
5、避免在 useLayoutEffect 中执行耗时操作

备注:useLayoutEffect 的清理和effect清理一样
清理时机:
组件卸载时,react会清理函数
依赖项变化时候,React 会先执行上一次的清理函数

11、useId

useId 是一个用于生成唯一 ID 的 Hook,主要用于解决服务端渲染(SSR)和客户端渲染(CSR)之间的 ID 不一致问题

import { useId } from 'react';

function Form() {
  const id = useId(); // 生成唯一的 ID(如 ":r1:")

  return (
    <>
      <label htmlFor={id}>Username</label>
      <input id={id} type="text" />
    </>
  );
}

12、useDeferredValue

useDeferredValue 是一个用于延迟更新低优先级状态的 Hook,类似于防抖(debounce)或节流(throttle),但更智能。

使用场景

  • 优化输入框联想搜索(用户输入时立即显示输入内容,联想结果稍后更新)
  • 非紧急的渲染(如大型列表、图表等)

示例

import { useState, useDeferredValue } from 'react';

function Search() {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query); // 延迟的 query

  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      {/* 延迟渲染结果,避免阻塞输入 */}
      <Results query={deferredQuery} />
    </>
  );
}

特点:

  • 延迟的值会“滞后”于原始值,但 React 会优先处理高优先级更新(如用户输入)
  • 与 useTransition 类似,但更适用于直接延迟某个状态值,而非标记整个更新过程

与Transition详细区别:

1、useDeferredValue

作用:

  • 延迟一个状态的更新,使其不阻塞高优先级的渲染(如用户输入
  • 本质是对值的延迟,React 会在后台先处理高优先级更新,再处理这个延迟的值

适用场景:

  • 当需要延迟派生状态(如根据输入过滤列表)时
  • 希望保持 UI 响应性(如输入框即时响应,结果稍后显示)

示例

function Search() {
  const [query, setQuery] = useState("");
  const deferredQuery = useDeferredValue(query); // 延迟的 query

  return (
    <>
      <input value={query} onChange={(e) => setQuery(e.target.value)} />
      {/* 结果延迟更新 */}
      <Results query={deferredQuery} />
    </>
  );
}

特点:

  • 被动触发:依赖其他状态的变化(如 query 变化后延迟更新 deferredQuery)
  • 针对单个值:直接延迟某个状态值,不控制更新过程

2、useTransition

作用:

  • 标记一个更新为低优先级,允许 React 中断其渲染以处理更紧急的任务(如用户交互)
  • 本质是对更新过程的控制,可以手动决定哪些更新应该被延迟。

适用场景:

  • 当需要手动控制非紧急更新(如页面导航、批量操作)时
  • 需要显示加载状态(如切换页面时的加载动画)

示例:

function App() {
  const [isPending, startTransition] = useTransition();
  const [tab, setTab] = useState("home");

  function switchTab(nextTab) {
    startTransition(() => {
      setTab(nextTab); // 低优先级更新
    });
  }

  return (
    <>
      <button onClick={() => switchTab("home")}>Home</button>
      <button onClick={() => switchTab("settings")}>Settings</button>
      {isPending && <Spinner />} {/* 过渡期间的加载状态 */}
      <Content tab={tab} />
    </>
  );
}

特点:

  • 主动触发:通过 startTransition 显式包裹低优先级更新。
  • 控制更新过程:可以管理多个状态的更新,并获取过渡状态(isPending)
  • 更灵活:适合复杂场景(如路由切换、批量状态更新)

核心区别对比

特性 useDeferredValue useTransition
用途 延迟某个值的更新 延迟一组状态的更新
触发方式 被动(自动延迟派生值) 主动(手动包裹低优先级更新)
返回值 延迟后的值 [isPending, startTransition]
适用场景 输入联想、派生状态优化 页面导航、批量操作、需要加载状态的场景
性能优化角度 对值的优化 对更新过程的优化

结合使用的例子

function HybridExample() {
  const [query, setQuery] = useState("");
  const [isPending, startTransition] = useTransition();
  const deferredQuery = useDeferredValue(query);

  function handleInput(e) {
    setQuery(e.target.value); // 高优先级更新(输入框即时响应)
    startTransition(() => {
      // 低优先级更新(如联想搜索)
      // 可以在这里处理其他非紧急状态
    });
  }

  return (
    <>
      <input value={query} onChange={handleInput} />
      {isPending && <Spinner />}
      <Results query={deferredQuery} />
    </>
  );
}

网站公告

今日签到

点亮在社区的每一天
去签到