Hook
react 提供的 hooks 没有比要全部熟知,这里只记录一些常用的必会hook。
第一梯队:
- useState
- useEffect
- useContext
- useRef
第二梯队:
- useMemo
- useCallback
第三梯队:
- useReducer
- useLayoutEffect
- useId
还有一个必须要知道的:
- 自定义Hook
useState
用于保存组件状态的hook,这是最常用,也是最重要的。
const [count, setCount] = useState(0)不同数据类型:
// 数字
const [age, setAge] = useState(28);
// 字符串
const [name, setName] = useState('Taylor');
// 对象
const [todos, setTodos] = useState(() => createTodos());
// 数组
const [todos, setTodos] = useState([]);
// 布尔
const [isLogged, setIsLogged] = useState(false);
// 复杂数组/对象
const [board, setBoard] = useState(() => [
[
{key: '1', value: 'xxx'},
{key: '2', value: 'xxx'},
{key: '3', value: 'xxx'},
],
[
{key: '4', value: 'xxx'},
{key: '5', value: 'xxx'},
{key: '6', value: 'xxx'},
],
[
{key: '7', value: 'xxx'},
{key: '8', value: 'xxx'},
{key: '9', value: 'xxx'},
]
])常见问题
- 调用
setState后发生什么? - 为什么不直接修改 state?
setCount(count + 1)连续调用两次为什么可能不是 + 2?- 函数式更新是什么?
- 为什么有时执行
setCount不会立刻改变值?
useEffect
用于处理副作用
useEffect(() => {
console.log("执行");
}, []);本质是在 React 完成渲染后,执行一些“额外的事情”(副作用)
既然是处理 副作用,那我们要先了解什么是副作用。
副作用
假设有这样一段代码
function App() {
const count = 10
return <div>{count}</div>
}这段代码非常存粹,只是将 count 显示在画面上,没有外部世界的影响。
但如果:
function App() {
fetch('/app/user')
return <div>Hello</div>
}这段代码中,fetch(),做了请求:
React组件
↓
请求服务器
↓
影响外部世界这就属于 副作用
其他典型的副作用还有:
API 请求
定时器
事件监听
WebSocket
订阅
组件初始化
状态变化后的处理
清理资源
修改 DOM
修改原生组件所以:
React 渲染 UI
↓
纯粹计算
useEffect
↓
处理副作用三种 useEffect
useEffect有三种使用方式
- 无依赖数组
- 空依赖数组
- 有依赖数组
无依赖数组
useEffect(() => {
// ...
});表示每次组件重新渲染都会执行这个 useEffect
空依赖数组
useEffect(() => {
// ...
}, []);由于没有依赖,因此只在组件第一次渲染完成后触发。
一般可以理解为 只执行一次
不过 React 开发环境的 Strict Mode 可能让你看到 Effect 执行两次。
有依赖数组
useEffect(() => {
// ...
}, [count]);这表示依赖于 count,第一次渲染组件后 或 每当 count 改变后 都会执行一次 useEffect。
还有一个十分常见的需求,第一次渲染组件后不执行,只在每次 count 改变后执行:
const isFirstRender = useRef(true);
useEffect(() => {
if (isFirstRender.current) {
isFirstRender.current = false;
return;
}
// 第一次渲染不会执行到这里
// count 后续改变才执行
console.log("count 改变了", count);
}, [count]);为什么要使用 useRef?
因为它可以保存数据,但修改 ref.current 不会导致组件重新渲染。
cleanup
Effect 在下一次重新执行之前,或者组件卸载之前,执行的清理函数。
useEffect(() => {
// Effect
return () => {
// Cleanup
};
}, []);这里的:
return () => {
// Cleanup
};如定时器:
useEffect(() => {
const timer = setInterval(() => {
console.log("Hello");
}, 1000);
return () => {
clearInterval(timer);
};
}, []);组件挂载时:
App 挂载
↓
useEffect
↓
创建 timer
↓
每秒打印 Hello组件卸载:
App 卸载
↓
cleanup
↓
clearInterval(timer)
↓
定时器停止useContext
用于跨组件共享数据
const user = useContext(UserContext);常见问题
- Context 和 Props 有什么区别
- Context 和 Redux/Zustand 有什么区别?
useRef
const inputRef = useRef(null);它有两个非常重要的用途
1.保存不会重新渲染的数据
const countRef = useRef(0);
countRef.current++;修改:
countRef.current = 10;不会导致组件重新渲染。
2.获取 DOM/原生组件引用
const inputRef = useRef(null);
inputRef.current?.focus();useMemo
缓存计算结果
const result = useMemo(() => {
return expensiveCalculation(data);
}, [data]);含义:
data 没变
↓
不用重新计算
↓
直接使用之前的 resultuseCallback
缓存函数
const handleClick = useCallback(() => {
console.log("click");
}, []);它和 useMemo 的区别是:
useMemo
↓
缓存计算结果
useCallback
↓
缓存函数本身useReducer
用于比较复杂的状态管理
const [state, dispatch] = useReducer(
reducer,
initialState
)例如:
登录
├── LOGIN
├── LOGOUT
└── UPDATE_USER当 useState 开始变得复杂是,可以考虑 useReducer
常见问题
- useState 和 useReducer 有什么区别
useLayoutEffect
和 useEffect 类似,但执行时机不同。
React Native 中也可能遇到,例如某些导航、布局相关场景。
useId
用于生成唯一 ID。
const id = useId();主要在 Web、无障碍等场景比较有用。
自定义Hook
function useUser() {
const [user, setUser] = useState(null);
return {
user,
setUser
};
}自定义 Hook 的名字通常以 use 开头,并且内部可以使用其他 Hook。