Hooks的作用
在没有hooks之前,函数组件只能够接收props 渲染UI,以及触发父组件传过来的事件。所有的逻辑都要在类组件中实现。这样会使类组件内部错综复杂,每一个类组件都有一套独特的状态,相互之间不能复用。即便React之前出现过mixins等方案,但是minxins会伴随着隐式依赖和代码冲突等问题。
另外函数组件要比类组件轻巧很多,同时更加灵活
hooks的目的:
- 让函数组件也能做类组件的事情,有自己的状态,可以处理一些副作用,能获取ref,也能做数据缓存
- 解决逻辑复用的问题
- 放弃面向对象编程 拥抱函数式编程
常见的hooks
数据驱动型:
- useState
管理组件的状态,适用于简单状态的管理。
const [state, setState] = useState(initialState);
- useReducer
用于管理复杂的状态逻辑或涉及多个子状态的情况,替代 useState 的一种方式。
const [state, dispatch] = useReducer(reducer, initialState);
状态获取/传递型:
- useContext
用于获取上层组件通过 Context 提供的全局状态,避免层层传递 props。
const value = useContext(MyContext);
- useRef
用于获取和存储 DOM 节点或其他持久值,组件重新渲染时不会改变。
const ref = useRef(initialValue);
- useImperativeHandle
与 forwardRef 配合使用,允许自定义暴露给父组件的实例值。
import React, { useImperativeHandle, forwardRef, useRef } from 'react';
// Step 1: 使用 forwardRef 包裹子组件
const CustomInput = forwardRef((props, ref) => {
const inputRef = useRef();
// Step 2: 使用 useImperativeHandle 暴露自定义方法
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
},
clear: () => {
inputRef.current.value = '';
}
}));
return <input ref={inputRef} {...props} />;
});
// 父组件
export default function ParentComponent() {
const inputRef = useRef();
return (
<div>
<CustomInput ref={inputRef} placeholder="Type here..." />
<button onClick={() => inputRef.current.focus()}>Focus Input</button>
<button onClick={() => inputRef.current.clear()}>Clear Input</button>
</div>
);
}
副作用型:
- useEffect
用于处理副作用(例如数据获取、订阅、手动修改 DOM 等)。它在组件渲染后执行,依赖数组变化时触发。
useEffect(() => {
// Effect
return () => {
// Cleanup
};
}, [dependencies]);
- useLayoutEffect
与 useEffect 类似,但在所有 DOM 变更后同步执行,适用于需要在绘制前进行 DOM 操作的情况。
useLayoutEffect(() => {
// Effect
return () => {
// Cleanup
};
}, [dependencies]);
- useInsertionEffect
用于在 CSS-in-JS 库中注入样式。它在 DOM 元素渲染之前同步运行,通常比 useLayoutEffect 更早,可以避免布局抖动
useInsertionEffect(() => {
// 样式注入逻辑
return () => {
// 清除逻辑
};
}, [dependencies]);
状态派生/状态保存
- useMemo
用于缓存计算结果,仅在依赖变化时重新计算,优化性能。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- useCallback
用于缓存函数,使其在依赖未变更的情况下不会重新创建,适用于传递给子组件的回调函数。
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
React 18新增的hooks
useSyncExternalStore
useTranstion
useDeferredValue
useInsertionEffect
useId
除此之外React 18还新增了 concurrent模式
concurrent模式指的是在 React 18 中,组件的渲染过程可以同时进行多个任务,而不仅仅是一个任务。
提高应用的性能和用户体验。它使 React 能够在更新 UI 时更灵活地管理渲染资源,从而有效应对复杂界面的卡顿问题。Concurrent 模式引入了一种“打断式渲染”的机制,使 React 可以根据任务的重要性和优先级分配渲染时间
可中断渲染:在传统的同步渲染中,一次渲染过程是不可中断的,一旦开始,整个更新必须完成。在 Concurrent 模式中,渲染过程是可中断的。React 可以在合适的时候暂停或放弃某些任务,让高优先级的任务(如用户输入、动画)优先渲染。
时间分片(Time Slicing):Concurrent 模式将渲染任务分割成小块,并在空闲时逐步执行这些小块。通过这种方式,React 可以在处理复杂渲染的同时保持页面的响应性,不会因为渲染耗时任务而出现卡顿。
自动调节优先级:React 能够根据任务的类型动态调整优先级。例如,当用户与应用交互时,React 会自动将用户输入的处理设为高优先级,并暂缓其他不那么紧急的渲染任务。这种动态调整确保了应用能够在不同情况下保持流畅。
优点:
更流畅的用户体验:减少大规模渲染任务带来的卡顿,使动画和交互更加平滑。
优先级管理:高优先级任务(如用户输入)能够迅速响应,而非阻塞在渲染队列中。
更智能的资源分配:复杂组件在渲染时会自动优化渲染顺序,提高页面性能
React 的 Concurrent 模式在底层通过 ReactDOM.createRoot 实现,并且是向后兼容的。只需使用 ReactDOM.createRoot 代替传统的 ReactDOM.render
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
useEffect 与 useLayoutEffect 区别
执行时机:
useEffect:在浏览器完成绘制之后执行。这意味着 React 会先更新 DOM 和页面布局,然后执行 useEffect 内的副作用。这是异步执行的,通常不会阻塞页面的绘制。
useLayoutEffect:在 DOM 更新后、浏览器绘制之前执行。它会阻塞浏览器的绘制,确保在布局和绘制之前完成所有同步的 DOM 操作。这是同步执行的,适合需要在渲染前计算布局或进行 DOM 操作的情况。
用途:
useEffect:适用于不影响布局的副作用,例如数据获取、事件监听、日志记录等。这些操作无需在浏览器渲染之前执行,因此可以在 DOM 更新后异步执行。
useLayoutEffect:适合在浏览器绘制前需要直接访问和操作 DOM 的场景。例如,读取或调整 DOM 元素的位置、尺寸等,以避免页面的闪烁或布局跳动。这些操作需要在渲染前完成。
useMemo 与 useCallback memo 区别
useMemo:
- 缓存计算结果
- 用于缓存计算结果,以避免不必要的重复计算。当依赖项没有变化时,useMemo 会返回缓存的值,而不是重新计算。主要适用于需要进行复杂或昂贵计算的场景。
- 使用场景:适合计算属性、生成列表、数据过滤等需要缓存结果的场景。
useCallback:
- 缓存函数引用
- 用于缓存函数的引用,以避免在子组件的 props 变化时触发不必要的重渲染。当依赖项没有变化时,useCallback 返回相同的函数引用。
- 使用场景:适合传递给子组件的回调函数,特别是在依赖项不变时,避免重新创建函数导致子组件重复渲染。
React.memo:缓存组件
- React.memo 是一个高阶组件,用于缓存组件的渲染结果。当传递给组件的 props 没有变化时,React.memo 会跳过重新渲染。
- 适合纯展示组件或在 props 不变时无需重新渲染的组件,以提升性能
使用原则:
- useMemo:用于避免昂贵的计算操作。
- useCallback:用于避免回调函数引用的变化引起子组件重渲染。
- React.memo:用于避免在 props 相同的情况下重新渲染组件。
总结:
useMemo 缓存计算结果 计算后的值 复杂计算,避免重复计算
useCallback 缓存函数引用 缓存的函数 避免传递给子组件的回调重复渲染
React.memo 缓存整个组件 缓存的组件 纯展示组件,props 不变时跳过渲染