Hook 可以帮助在组件中使用不同的 React 功能。可以使用内置的 Hook 或使用自定义 Hook。本页列出了 React 中所有内置 Hook。
一. State Hook:状态hook
状态帮助组件** “记住”用户输入的信息**。例如,一个表单组件可以使用状态存储输入值,而一个图像库组件可以使用状态存储所选的图像索引。
1.使用 useState 声明可以直接更新的状态变量。
import React, { useState } from 'react';
function Counter() {
// 声明一个状态变量count,初始值为0
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>增加</button>
);
}
2.使用 useReducer 在 reducer 函数 中声明带有更新逻辑的 state 变量。
import React, { useReducer } from 'react';
// 定义reducer函数
function counterReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'SET':
return { count: action.payload };
default:
throw new Error('未知操作类型');
}
}
function Counter() {
// 使用useReducer,第一个参数是reducer函数,第二个参数是初始状态
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<div>
<p>当前计数: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>增加</button>
<button onClick={() => dispatch({ type: 'SET', payload: 10 })}>
设为10
</button>
</div>
);
}
二.Context Hook:上下文hook
上下文帮助组件从祖先组件接收信息,而无需将其作为 props 传递。例如,应用程序的顶层组件可以借助上下文将 UI 主题传递给所有下方的组件,无论这些组件层级有多深。
使用 useContext 读取订阅上下文。
import React, { createContext, useContext, useState } from 'react';
// 1. 创建Context对象
const ThemeContext = createContext();
function App() {
const [theme, setTheme] = useState('light');
return (
// 2. 使用Provider提供值
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
// 3. 在子组件中使用useContext获取值
const { theme, setTheme } = useContext(ThemeContext);
return (
<button
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
style={{
background: theme === 'light' ? '#fff' : '#333',
color: theme === 'light' ? '#000' : '#fff'
}}
>
当前主题: {theme} (点击切换)
</button>
);
}
三.Ref Hook
ref 允许组件 保存一些不用于渲染的信息,比如 DOM 节点或 timeout ID。与状态不同,更新 ref 不会重新渲染组件。ref 是从 React 范例中的“脱围机制”。当需要与非 React 系统如浏览器内置 API 一同工作时,ref 将会非常有用。
1.使用 useRef 声明 ref。可以在其中保存任何值,但最常用于保存 DOM 节点。
import React, { useRef } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载的 input 元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>聚焦输入框</button>
</>
);
}
2.使用 useImperativeHandle 自定义从组件中暴露的 ref,但是很少使用。
import React, { useRef, useImperativeHandle, forwardRef } from 'react';
// 子组件
const FancyInput = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
getValue: () => {
return inputRef.current.value;
},
setValue: (value) => {
inputRef.current.value = value;
}
}));
return <input ref={inputRef} />;
});
// 父组件
function Parent() {
const fancyInputRef = useRef();
const handleClick = () => {
fancyInputRef.current.focus();
fancyInputRef.current.setValue('Hello');
console.log(fancyInputRef.current.getValue());
};
return (
<div>
<FancyInput ref={fancyInputRef} />
<button onClick={handleClick}>操作输入框</button>
</div>
);
}
四.Effect Hook
useEffect是 React Hooks 中用于处理副作用的钩子函数。
import { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 类似于 componentDidMount 和 componentDidUpdate
useEffect(() => {
// 更新文档标题
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useEffect 有两个很少使用的变换形式,它们在执行时机有所不同:
useLayoutEffect在浏览器重新绘制屏幕前执行,可以在此处测量布局。useInsertionEffect在 React 对 DOM 进行更改之前触发,库可以在此处插入动态 CSS。
五.性能 Hook
优化重新渲染性能的一种常见方法是跳过不必要的工作。例如,可以告诉 React 重用缓存的计算结果,或者如果数据自上次渲染以来没有更改,则跳过重新渲染。
1.使用 useMemo 缓存计算代价昂贵的计算结果。
import React, { useState, useMemo } from 'react';
function ExpensiveCalculationComponent() {
const [count, setCount] = useState(0);
const [inputValue, setInputValue] = useState('');
// 一个昂贵的计算函数
const expensiveCalculation = (num) => {
console.log('Calculating...');
for (let i = 0; i < 1000000000; i++) {} // 模拟耗时操作
return num * 2;
};
// 使用 useMemo 缓存计算结果
const calculatedValue = useMemo(() => {
return expensiveCalculation(count);
}, [count]); // 只有 count 变化时才会重新计算
return (
<div>
<h2>useMemo 示例</h2>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>增加 Count</button>
<p>计算后的值: {calculatedValue}</p>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="输入一些内容"
/>
<p>输入的内容: {inputValue}</p>
</div>
);
}
export default ExpensiveCalculationComponent;
2.使用 useCallback 将函数传递给优化组件之前缓存函数定义。
import React, { useState, useCallback } from 'react';
function CounterWithCallback() {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(false);
// 不使用 useCallback - 每次渲染都会创建新函数
// const increment = () => {
// setCount(count + 1);
// };
// 使用 useCallback 缓存函数
const increment = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, []); // 空依赖数组表示函数不依赖任何外部值
const toggleOtherState = () => {
setOtherState(prev => !prev);
};
return (
<div>
<h2>useCallback 示例</h2>
<p>Count: {count}</p>
<button onClick={increment}>增加 Count</button>
<p>Other State: {otherState.toString()}</p>
<button onClick={toggleOtherState}>切换 Other State</button>
<MemoizedChildComponent onIncrement={increment} />
</div>
);
}
// 使用 React.memo 优化的子组件
const MemoizedChildComponent = React.memo(function ChildComponent({ onIncrement }) {
console.log('子组件渲染了');
return (
<div style={{ marginTop: '10px' }}>
<button onClick={onIncrement}>从子组件增加 Count</button>
</div>
);
});
export default CounterWithCallback;
useMemo缓存计算结果useCallback缓存函数本身
有时由于屏幕确实需要更新,无法跳过重新渲染。在这种情况下,可以通过将必须同步的阻塞更新(比如使用输入法输入内容)与不需要阻塞用户界面的非阻塞更新(比如更新图表)分离以提高性能。
useTransition 允许将状态转换标记为非阻塞,并允许其他更新中断它。
useDeferredValue 允许延迟更新 UI 的非关键部分,以让其他部分先更新。
六.资源 Hook
资源可以被组件访问,而无需将它们作为状态的一部分。例如,组件可以从 Promise 中读取消息,或从上下文中读取样式信息。
use 允许读取像 Promise 或 上下文 这样的资源的值。
function MessageComponent({ messagePromise }) {
const message = use(messagePromise);
const theme = use(ThemeContext);
// ...
}
七.其他 Hook
- 使用 useDebugValue 自定义 React 开发者工具为自定义 Hook 添加的标签。
- 使用 useId 将唯一的 ID 与组件相关联,其通常与可访问性 API 一起使用。
- 使用 useSyncExternalStore 订阅外部 store。

1279

被折叠的 条评论
为什么被折叠?



