useEffect
每个Fiber节点都会为该组件的所有effec对象维护一个链表,
场景 | 类组件方法 | 函数组件等效写法 | 差异说明 |
---|---|---|---|
挂载时执行 | componentDidMount() | useEffect(fn, []) | useEffect 副作用在浏览器绘制后异步执行;componentDidMount 是同步的。 |
更新时执行 | componentDidUpdate() | useEffect(fn, [deps]) | useEffect 可精确控制依赖项,避免不必要的更新;componentDidUpdate 需手动比较 prevProps/prevState。 |
卸载时清理 | componentWillUnmount() | useEffect(() => { return cleanupFn }, []) | 清理逻辑通过返回值实现,更集中 |
同步 DOM 操作 | componentDidMount/Update | useLayoutEffect | useLayoutEffect 同步执行,类似类组件生命周期; useEffect 是异步的,不会阻塞页面渲染 |
性能优化 | shouldComponentUpdate() | React.memo + useMemo/useCallback | 函数组件通过缓存值和函数减少不必要的渲染,而非直接控制更新 |
useEffect vs useLayoutEffect
hook | 执行时机 | 适用场景 |
---|---|---|
useEffect | 在浏览器完成本次页面布局和绘制后 ,消费effect链表。 | 大多数副作用(如 API 请求、事件订阅)。 |
useLayoutEffect | 在DOM更新后、浏览器绘制前 ,消费effect链表,会阻塞本次渲染。 | 需同步读取或修改 DOM(如调整元素尺寸/位置)。 |
常见场景
useEffect(() => { return cleanupFn }, [])
仅挂载时执行, cleanupFn 清理函数 ,会下一次挂载会 调用 cleanupFn
- 事件监听与清理
useEffect(() => {const handleResize = () => {setWindowSize(window.innerWidth);};window.addEventListener('resize', handleResize);return () => window.removeEventListener('resize', handleResize);
}, []);
- 关闭第三方库实例
useEffect(() => {const chart = d3.select('#chart').append('svg')// 初始化图表...// 清理函数:销毁图表实例return () => {chart.remove();console.log('图表已销毁');};
}, []);
ref
useMemo
用于缓存属性,避免重新渲染时不必要的计算,从而优化性能。
有点像vue的计算属性
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
-
参数:
- 计算函数:返回需要缓存的值(函数内部包含高开销计算)。
- 依赖数组:当数组中的值发生变化时,重新执行计算函数。
-
返回值:缓存的值(依赖未变化时直接返回上一次结果)。
使用场景:
- 高开销计算,(如数据转换、大数组遍历),使用 useMemo 避免每次渲染重复计算
- 当父组件传递对象/数组给子组件时,用 useMemo 保持引用稳定,避免子组件不必要重渲染
误用场景
缓存简单计算,(字符串拼接、简单数学运算)无需缓存,useMemo 自身开销可能超过计算成本。
useCallback
用于缓存函数实例,避免渲染时的重新创建,从而优化性能。
const memoizedCallback = useCallback(() => {// 需要缓存的函数逻辑doSomething(a, b);},[a, b]) // 依赖项数组;
- 参数:
- 第一个参数:需要缓存的函数。
- 第二个参数:依赖项数组。当依赖项变化时,函数会重新创建;否则复用之前的实例。
- 返回值:一个持久化(缓存)的函数引用
使用场景
- 优化子组件渲染(配合 React.memo)
// 父组件
const Parent = () => {const [count, setCount] = useState(0);// 使用 useCallback 缓存回调函数const handleClick = useCallback(() => {console.log("按钮被点击");}, []); // 空依赖数组:函数不依赖任何变量,始终保持同一引用return (<div><button onClick={() => setCount(c => c + 1)}>父组件计数:{count}</button>{/* 子组件因 handleClick的 引用不变,避免了因setCount的变化而重新渲染 */}<Child onClick={handleClick} /></div>);
};// 子组件用 React.memo 优化,依赖 props 的浅比较
const Child = React.memo(({ onClick }) => {console.log("子组件渲染");return <button onClick={onClick}>子组件按钮</button>;
});
⚠️ 🆘:在简单组件中,函数创建的开销可能微乎其微,优化可能得不偿失。极少特定场景适用。
React.memo()
React.memo 有点像类组件的 shouldComponentUpdata 函数,只有当props
变化时,才会重新渲染组件,否则复用。
const MyComponent = React.memo(function MyComponent(props) {// 组件逻辑
}, arePropsEqual?);
- MyComponent:需要优化的函数组件。
- arePropsEqual(可选):自定义的 props 比较函数。默认情况下,React.memo 会对 props 进行浅比较。如果提供了此函数,React 会使用它来判断 props 是否相等
⚠️:注意
- 默认浅比较: 如果 props 中包含复杂对象或数组,可能需要自定义比较函数。
- useMemo 和 useCallback :如果 props 中包含函数或复杂对象,建议配合 useMemo 或 useCallback 使用,以避免不必要的重新渲染
- 并不总是必要的,过度使用可能会增加代码复杂度。只有在性能确实成为问题时才使用。
HOC 高阶组件
是一种设计模式,它通过接受一个组件并返回一个增强后的新组件来实现逻辑复用。
- 控制渲染流程
- 逻辑复用
- 增强组件功能
- 类组件转函数组件
定义
// js高阶函数示例:接受一个函数,返回一个新函数
const withLogger = (func) => {return (...args) => {console.log("函数被调用,参数:", args);return func(...args);};
};
使用
const add = (a, b) => a + b;
const addWithLog = withLogger(add);
addWithLog(2, 3); // 输出日志并返回 5
react中的HOC
- 权限控制
const withAuth = (WrappedComponent) => {return (props) => {const isAuthenticated = true; // 权限校验逻辑return isAuthenticated ? <WrappedComponent {...props} /> : <Redirect to="/login" />;};
};export default withAuth(UserDashboard); //导出给其他组件用
- 类组件转函数组件。
class child extends React.Component {return div
}const ProxyComponent = (comp) => {// Do somethingreturn function HOC(props) {// 代理为函数组件后就能 函数式接受参数return <comp {...props}>}
}export default ProxyComponent(child)