React Hooks 一堆钩子,使得 函数组件 更强大!
React Hooks 是 React 16.8 引入的功能,它允许我们在 函数组件 中使用 类组件 的一些功能,如 state 和 生命周期方法。
在此之前,函数组件 仅仅是一个 接收 props
并 返回 UI 的普通 JavaScript 函数,但随着 React Hooks 的引入,函数组件 变得更强大,能够处理 状态管理、生命周期 等功能。
以下是 React 常用的 Hooks 及其用法:
1 useState
- 管理组件的状态
useState
是最常用的 Hook,用于在 函数组件 中添加 state
。useState
会返回一个 数组,其中包括当前的 状态值 和 更新状态的 函数。
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0); // count 是状态值,setCount 是更新该值的函数
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
useState(0)
:定义一个count
状态,初始值 为0
;setCount
:是一个 更新count
状态的 函数,每次点击按钮时,count
的值会增加。
对象或数组 作为 state
const [state, setState] = useState({ count: 0, name: 'Alice' });
const updateCount = () => {
setState(prevState => ({
...prevState, // 保留之前的状态
count: prevState.count + 1
}));
};
延迟初始化状态
useState
可以接收一个 函数 作为 初始值,React 会 延迟计算 该值,直到需要时才执行。
const [count, setCount] = useState(() => computeInitialState());
2 useEffect
- 管理副作用
useEffect
用于 执行 副作用 操作,例如 数据获取、订阅、手动 DOM 操作 或 清理操作。
它类似于 类组件 中的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
生命周期方法。
import React, { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const timer = setInterval(() => setSeconds(seconds => seconds + 1), 1000);
// Cleanup function: 在组件卸载时清理副作用
return () => clearInterval(timer);
}, []); // 空依赖数组,表示仅在组件挂载时运行一次
return <h1>Time: {seconds}</h1>;
}
useEffect
会在 组件 每次渲染后 执行,默认情况下,它在每次渲染后执行(类似于componentDidUpdate
);- 如果传递一个 空数组
[]
作为 第二个参数,useEffect
只会在 组件挂载时 执行一次(类似于componentDidMount
); useEffect
的 返回值 是一个 清理函数,在组件卸载时调用,类似于componentWillUnmount
。
useEffect
的依赖项(第二个参数)
你可以将 useEffect
的 第二个参数 设置为 依赖项 数组,这样只有当 依赖项数组 发生变化时,useEffect
才会 重新执行。
useEffect(() => {
console.log('name changed:', name);
}, [name]); // 只有 name 发生变化时才会重新执行
3 useContext
- 使用上下文
useContext
是 React 提供的 Hook,用于在 函数组件 中使用 React Context,它能让你 访问到 祖先组件 传递下来的 数据。
import React, { createContext, useContext } from 'react';
// 创建一个 Context 对象
const MyContext = createContext();
function Parent() {
return (
<MyContext.Provider value="Hello from context">
<Child />
</MyContext.Provider>
);
}
function Child() {
const contextValue = useContext(MyContext); // 使用 useContext 获取上下文的值
return <h1>{contextValue}</h1>;
}
MyContext.Provider
用于 提供 上下文的 值;useContext(MyContext)
用于在Child
组件中 获取 传递的 值。
4 useRef
- 获取 DOM 元素 或 保存值
useRef
用于 获取 DOM 元素的引用,或者 保持一个 不会触发 重新渲染 的 可变 值。
获取 DOM 元素引用
import React, { useRef } from 'react';
function FocusInput() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus the input</button>
</div>
);
}
useRef
返回一个 可变的 引用对象,该对象的current
属性 指向 DOM 元素;inputRef.current
就是对<input>
元素的 引用,调用focus()
可以让该输入框获取焦点。
保持一个不触发渲染的值
React 的 useState
用于 管理状态,并且每当我们调用 setState
更新状态时,React 会重新渲染组件。但是 useRef
用来 保存某些值,这些值可以在组件的多次渲染之间 持续存在,且更新 useRef
的值 不会触发组件的重新渲染。
import React, { useState, useRef, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0); // 每次点击时更新秒数
const prevTimeRef = useRef(); // 用来保存上次点击时的时间戳
// 每次点击按钮时都记录当前时间,并与上次的时间戳对比
const handleClick = () => {
const currentTime = Date.now(); // 获取当前时间戳
if (prevTimeRef.current) {
const timeDifference = (currentTime - prevTimeRef.current) / 1000; // 计算时间差,单位为秒
console.log(`Time difference: ${timeDifference} seconds`);
}
prevTimeRef.current = currentTime; // 更新上次点击的时间戳
setSeconds(seconds + 1); // 更新秒数
};
return (
<div>
<h1>Timer: {seconds} seconds</h1>
<button onClick={handleClick}>Increment Time</button>
</div>
);
}
export default Timer;
为什么不使用 useState
?
如果我们使用 useState
来保存上次点击的时间戳,每次 时间戳更新 时,组件就会 重新渲染,这显然是没有必要的,因为我们只关心 seconds
(显示的秒数),而 时间戳只需要保持在内存中,不需要影响 UI。
为什么会重新渲染?
React 的核心理念之一是 声明式编程:你声明 组件的 UI 如何基于 某些状态值 来 显示,React 会负责根据 状态值的变化 自动更新 UI。因此,任何通过 useState
更新的状态 都会触发渲染,以便确保 UI 的状态 与 最新的变量 同步。
如果你 更新的状态 与 当前状态 相同(即状态没有发生变化),React 会 跳过这次渲染,从而 提高性能。这是 React 的 “批量更新” 和 “浅比较” 优化的一个方面。
为什么不直接使用普通变量保存时间戳
在 React 中,组件的状态和渲染 是高度关联的。每次 组件重新渲染时,组件中的 所有局部变量 都会被 重新创建。换句话说,局部变量的值会丢失,而 useRef
是专门用于解决这个问题的。
5 useMemo
和 useCallback
- 性能优化
useMemo
和 useCallback
,这两个 Hook 用于 优化性能,避免 不必要的计算 或 函数重新创建。
useMemo
- 缓存”昂贵“计算结果
useMemo
是 React 中的一个 优化工具,它用于 缓存计算结果,从而避免 每次组件重新渲染时 都进行 重复的计算,提升性能。
- 避免不必要的计算:如果某些 计算非常耗时,且计算结果 依赖于 某些参数(状态),
useMemo
可以缓存这些结果,避免每次渲染时(其它状态变化)都重新计算; - 提高性能:在组件渲染过程中,如果存在 重复的 昂贵计算,
useMemo
可以提高性能,减少不必要的计算开销。
import React, { useState, useMemo } from 'react';
function ItemList() {
const [filter, setFilter] = useState('');
const items = [
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Orange' },
{ id: 4, name: 'Grape' },
];
// 使用 useMemo 缓存过滤后的结果,只有 filter 改变时才会重新计算
const filteredItems = useMemo(() => {
console.log('Filtering items...');
return items.filter(item => item.name.toLowerCase().includes(filter.toLowerCase()));
}, [filter]); // 依赖于 filter,只有 filter 变化时才重新计算
return (
<div>
<input
type="text"
value={filter}
onChange={(e) => setFilter(e.target.value)}
placeholder="Search items"
/>
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
export default ItemList;
- 在输入框输入内容时,每次
filter
改变时,Filtering items...
会打印到控制台; - 如果你在没有改变
filter
的情况下多次渲染(例如 修改其他状态 或 重新渲染整个组件),useMemo
会 返回之前缓存的结果,并不会重新执行filterItems
函数。
useCallback
- 缓存回调函数
useCallback
是 React 中的一个 Hook,用于 缓存回调函数,从而避免 在每次 组件渲染时 都 重新创建 新的函数实例。
在 React 中,每次组件渲染 都会 重新创建函数实例。这在多数情况下不会有问题,但如果这些函数 被传递到 子组件 作为 props,或者作为 某些依赖项(例如 useEffect
或 useMemo
)的依赖,就可能导致 不必要的 重新渲染 和 性能问题。
useCallback
通过返回一个 缓存的函数 来避免这种情况,从而提高性能,特别是在 传递回调函数 给 子组件时。
import React, { useState, useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// 使用 useCallback 缓存 handleClick 函数
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]); // 只有当 count 改变时,handleClick 才会重新创建
return (
<div>
<h1>Count: {count}</h1>
{/* 传递缓存的 handleClick 函数 */}
<Child onClick={handleClick} />
</div>
);
}
function Child({ onClick }) {
console.log('Child re-rendered!');
return <button onClick={onClick}>Increment</button>;
}
export default Parent;
useCallback
会缓存handleClick
函数,只有当count
发生变化时,handleClick
才会 重新创建。否则,React 会返回 上一次缓存的 函数实例;Child
组件 只有在handleClick
函数的引用 发生变化时 才会 重新渲染,而不是每次Parent
渲染时 都重新渲染。
6 useReducer
- 复杂状态管理
useReducer
是 useState
的一个 替代方案,适用于 状态变化 比较复杂的 场景,特别是涉及 多个子值 的 更新。
import React, { 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 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>Count: {state.count}</h1>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
useReducer
用于处理 复杂的 状态逻辑,接受一个 reducer 函数 和 初始状态。dispatch
用来触发 reducer
中定义的 状态更新 逻辑。