大家好,我是小杨。作为一名有6年经验的前端开发,今天想和大家聊聊我在使用useEffect时遇到的一个经典坑点------引用类型依赖问题。
记得刚开始用React Hooks时,我写过这样的代码:
jsx
function UserProfile({ userId }) {
const [userData, setUserData] = useState(null);
const fetchConfig = {
method: 'GET',
headers: { 'Authorization': 'Bearer token' }
};
useEffect(() => {
const fetchUser = async () => {
const response = await fetch(`/api/users/${userId}`, fetchConfig);
const data = await response.json();
setUserData(data);
};
fetchUser();
}, [userId, fetchConfig]); // 这里埋了个坑!
return <div>{/* 用户信息展示 */}</div>;
}
看起来没问题?但实际上,这个useEffect会在每次渲染时都执行!为什么呢?
为什么引用类型依赖会出问题?
在JavaScript中,对象、数组、函数都是引用类型。每次组件重新渲染时,即使内容没变,它们都会被重新创建,内存地址不同。
jsx
function Example() {
const obj = { name: 'John' };
const arr = [1, 2, 3];
const func = () => {};
useEffect(() => {
console.log('Effect triggered');
}, [obj, arr, func]); // 每次都会触发!
return <div>Test</div>;
}
我是如何解决这个问题的?
方案一:将依赖移到useEffect内部
jsx
function UserProfile({ userId }) {
const [userData, setUserData] = useState(null);
useEffect(() => {
// 将配置移到effect内部
const fetchConfig = {
method: 'GET',
headers: { 'Authorization': 'Bearer token' }
};
const fetchUser = async () => {
const response = await fetch(`/api/users/${userId}`, fetchConfig);
const data = await response.json();
setUserData(data);
};
fetchUser();
}, [userId]); // 现在只需要依赖userId
return <div>{/* 用户信息展示 */}</div>;
}
方案二:使用useMemo缓存引用类型
jsx
function DataTable({ data, sortOptions }) {
const [sortedData, setSortedData] = useState([]);
// 使用useMemo缓存排序配置
const memoizedSortOptions = useMemo(() => ({
key: sortOptions.key,
direction: sortOptions.direction
}), [sortOptions.key, sortOptions.direction]);
useEffect(() => {
const result = sortData(data, memoizedSortOptions);
setSortedData(result);
}, [data, memoizedSortOptions]); // 现在依赖稳定了
return <Table data={sortedData} />;
}
方案三:使用useCallback缓存函数
jsx
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
// 使用useCallback缓存函数
const createConnection = useCallback(() => {
return {
connect: () => console.log(`连接到房间 ${roomId}`),
disconnect: () => console.log(`断开连接`)
};
}, [roomId]);
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createConnection]); // 依赖稳定的函数引用
return <div>{/* 聊天界面 */}</div>;
}
我在实际项目中的经验总结
场景一:API请求配置
错误写法:
jsx
function ProductsPage() {
const [products, setProducts] = useState([]);
const queryParams = { category: 'electronics', page: 1 };
useEffect(() => {
fetchProducts(queryParams).then(setProducts);
}, [queryParams]); // 依赖问题!
return <ProductList products={products} />;
}
正确写法:
jsx
function ProductsPage() {
const [products, setProducts] = useState([]);
const [category, setCategory] = useState('electronics');
const [page, setPage] = useState(1);
useEffect(() => {
const queryParams = { category, page };
fetchProducts(queryParams).then(setProducts);
}, [category, page]); // 依赖基本类型,安全!
return <ProductList products={products} />;
}
场景二:事件处理函数
错误写法:
jsx
function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleSearch = (searchQuery) => {
searchAPI(searchQuery).then(setResults);
};
useEffect(() => {
handleSearch(query);
}, [query, handleSearch]); // handleSearch每次都会变!
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
正确写法:
jsx
function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const handleSearch = useCallback((searchQuery) => {
searchAPI(searchQuery).then(setResults);
}, []);
useEffect(() => {
handleSearch(query);
}, [query, handleSearch]); // 现在handleSearch是稳定的
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
高级技巧:自定义Hook封装
在实际项目中,我经常这样封装:
jsx
function useDeepCompareEffect(callback, dependencies) {
const ref = useRef();
if (!deepEqual(dependencies, ref.current)) {
ref.current = dependencies;
}
useEffect(callback, [ref.current]);
}
// 使用示例
function ComplexComponent({ config }) {
useDeepCompareEffect(() => {
// 处理复杂的配置对象
initializeWithConfig(config);
}, [config]); // 深度比较config的变化
return <div>{/* 组件内容 */}</div>;
}
血泪教训:我踩过的坑
有一次在开发实时仪表板时,因为一个对象依赖没处理好,导致每秒发起几十次请求,差点把后端服务打挂!
jsx
// 错误代码
useEffect(() => {
const interval = setInterval(() => {
fetchData(currentFilters); // currentFilters每次都是新对象
}, 1000);
return () => clearInterval(interval);
}, [currentFilters]); // 每秒都在重新创建interval!
总结一下我的经验
- 尽量使用基本类型作为依赖
- useMemo和useCallback是你的好朋友
- 复杂的对象依赖考虑拆解为基本类型
- 必要时使用深度比较,但要谨慎性能影响
记住这些技巧,下次遇到useEffect的依赖问题时,你就能像老司机一样从容应对了!
⭐ 写在最后
请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.
✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式
✅ 认为我部分代码过于老旧,可以提供新的API或最新语法
✅ 对于文章中部分内容不理解
✅ 解答我文章中一些疑问
✅ 认为某些交互,功能需要优化,发现BUG
✅ 想要添加新功能,对于整体的设计,外观有更好的建议
✅ 一起探讨技术加qq交流群:906392632
最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!