面试题答案
一键面试依赖数组设置
- 仅依赖商品ID:
- 由于该自定义Hook的功能是根据商品ID获取商品详情数据,那么依赖数组应只包含商品ID。例如:
import { useEffect, useState } from'react'; const useProductDetails = (productId) => { const [product, setProduct] = useState(null); useEffect(() => { const fetchProduct = async () => { // 这里模拟异步请求获取商品详情 const response = await fetch(`/api/products/${productId}`); const data = await response.json(); setProduct(data); }; fetchProduct(); }, [productId]); return product; };
- 这样设置,只有当
productId
发生变化时,useEffect
内的回调函数才会重新执行,避免了不必要的重复请求。如果依赖数组为空[]
,那么只会在组件挂载时执行一次,商品ID变化时不会重新获取数据;如果依赖数组包含了其他不必要的变量,可能会导致在这些变量变化时也触发不必要的请求。
排查和优化因依赖数组设置不当导致的性能问题
- 排查:
- 控制台日志法:在
useEffect
回调函数内部添加console.log
语句,输出依赖数组中的变量值以及useEffect
的执行次数。例如:
useEffect(() => { console.log('useEffect执行,productId:', productId); const fetchProduct = async () => { const response = await fetch(`/api/products/${productId}`); const data = await response.json(); setProduct(data); }; fetchProduct(); }, [productId]);
- 通过观察控制台输出,可以判断
useEffect
是否按照预期的频率执行,以及依赖数组中的变量变化是否导致了意外的执行。 - React DevTools:使用React DevTools的性能面板,记录组件的渲染情况。可以看到组件的重新渲染次数以及每个渲染阶段的耗时。如果
useEffect
执行过于频繁,会导致组件重新渲染次数增加,通过性能面板可以直观地发现这种异常情况,并定位到对应的组件和useEffect
。
- 控制台日志法:在
- 优化:
- 正确调整依赖数组:根据排查结果,确保依赖数组只包含真正影响
useEffect
回调函数逻辑的变量。如果发现某些不必要的变量导致了useEffect
频繁执行,将其从依赖数组中移除。 - 使用Memoization:对于传递给自定义Hook的复杂数据结构(如对象或数组),如果它们在组件更新过程中没有实质性变化,但却导致了
useEffect
不必要的执行,可以使用React.memo
(对于函数组件)或shouldComponentUpdate
(对于类组件)来避免不必要的重新渲染,进而减少useEffect
的不必要执行。例如,如果商品ID是通过一个对象传递的,而对象中的其他属性不影响商品详情的获取,可以对包含商品ID的对象进行React.memo
处理:
const ProductIdContainer = React.memo(({ productId }) => { const product = useProductDetails(productId); return ( // 组件渲染逻辑 ); });
- 节流或防抖:如果
productId
变化非常频繁,例如在一个搜索输入框中实时输入商品ID进行搜索,可以使用节流(throttle)或防抖(debounce)技术。节流限制useEffect
在一定时间间隔内只能执行一次,防抖则是在productId
停止变化后的一定时间再执行useEffect
。可以通过自定义节流或防抖函数,或者使用lodash
等库中的throttle
和debounce
函数来实现。例如,使用lodash
的debounce
:
import { useEffect, useState } from'react'; import debounce from 'lodash/debounce'; const useProductDetails = (productId) => { const [product, setProduct] = useState(null); const fetchProductDebounced = debounce(async () => { const response = await fetch(`/api/products/${productId}`); const data = await response.json(); setProduct(data); }, 300); useEffect(() => { fetchProductDebounced(); return () => { fetchProductDebounced.cancel(); }; }, [productId]); return product; };
- 这样在
productId
变化时,不会立即发起请求,而是等待300毫秒(这里的时间可以根据实际需求调整),如果在这300毫秒内productId
又发生了变化,之前的请求会被取消,避免了过多不必要的请求。
- 正确调整依赖数组:根据排查结果,确保依赖数组只包含真正影响