面试题答案
一键面试1. useEffect 深度优化 - 精准控制依赖数组
- 理解依赖数组原理:依赖数组决定了
useEffect
的触发时机。当依赖数组中的任何值发生变化时,useEffect
会重新执行。如果依赖数组为空,useEffect
只会在组件挂载和卸载时执行。如果省略依赖数组,useEffect
会在每次组件渲染时都执行。 - 精准确定依赖:
- 仔细分析逻辑:在编写
useEffect
时,需要明确副作用逻辑中到底依赖了哪些组件的状态或属性。例如,在一个根据用户ID获取用户信息的useEffect
中:
- 仔细分析逻辑:在编写
import React, { useState, useEffect } from'react';
const UserInfo = () => {
const [userId, setUserId] = useState(1);
const [userInfo, setUserInfo] = useState(null);
useEffect(() => {
const fetchUserInfo = async () => {
const response = await fetch(`https://example.com/api/users/${userId}`);
const data = await response.json();
setUserInfo(data);
};
fetchUserInfo();
}, [userId]); // 这里只依赖 userId,所以依赖数组中只放 userId
return (
<div>
{userInfo && <p>{userInfo.name}</p>}
<button onClick={() => setUserId(2)}>Change User</button>
</div>
);
};
export default UserInfo;
- 避免遗漏依赖:如果在
useEffect
中使用了某个变量,但没有将其放入依赖数组,可能会导致该变量的值在useEffect
外部发生变化时,useEffect
不会重新执行,从而产生逻辑错误。比如在上述例子中,如果在useEffect
内部使用了一个全局配置变量apiBaseUrl
,并且这个变量可能会改变,那么就需要将其放入依赖数组。 - 使用ESLint规则:可以启用
eslint-plugin-react-hooks
中的exhaustive-deps
规则,它会在编译时检查useEffect
的依赖数组是否遗漏了必要的依赖。
2. useEffect 替代类组件生命周期方法的差异
- 组件性能:
- 类组件:在类组件中,
componentDidMount
、componentDidUpdate
和componentWillUnmount
生命周期方法会在特定时机触发。componentDidUpdate
可能会因为不必要的状态或属性更新而频繁触发,导致性能问题,因为它没有像useEffect
那样通过依赖数组精准控制。例如,一个类组件展示商品列表,当商品列表数据没有变化,但组件因为父组件的某些不相关状态更新而重新渲染时,componentDidUpdate
仍然会执行,可能会导致不必要的数据请求。
- 类组件:在类组件中,
import React, { Component } from'react';
class ProductList extends Component {
componentDidMount() {
this.fetchProducts();
}
componentDidUpdate(prevProps) {
if (prevProps.category!== this.props.category) {
this.fetchProducts();
}
}
fetchProducts = () => {
// 数据请求逻辑
}
render() {
return (
<div>
{/* 商品列表渲染 */}
</div>
);
}
}
export default ProductList;
- 函数组件(useEffect):通过精准控制依赖数组,
useEffect
可以避免不必要的渲染和副作用执行。在上述商品列表的例子中,如果使用useEffect
,可以这样写:
import React, { useState, useEffect } from'react';
const ProductList = ({ category }) => {
const [products, setProducts] = useState([]);
useEffect(() => {
const fetchProducts = async () => {
// 数据请求逻辑
setProducts(data);
};
fetchProducts();
}, [category]);
return (
<div>
{/* 商品列表渲染 */}
</div>
);
};
export default ProductList;
这样只有当 category
变化时,useEffect
才会重新执行,减少了不必要的性能开销。
- 代码维护性:
- 类组件:类组件的生命周期方法分散在不同的位置,对于复杂组件,代码可能变得冗长和难以维护。例如,一个具有多个副作用逻辑的类组件,
componentDidMount
可能包含初始化数据请求,componentDidUpdate
可能有数据更新后的处理逻辑,componentWillUnmount
有清理操作,这些逻辑分散在不同的生命周期方法中,增加了维护成本。 - 函数组件(useEffect):
useEffect
将副作用逻辑集中在一起,并且每个useEffect
可以通过依赖数组独立控制。例如,在一个聊天组件中,可能有连接WebSocket的副作用(在挂载时执行)和接收新消息更新UI的副作用(当新消息到达时执行),可以分别用两个useEffect
来实现,代码结构更加清晰,维护起来更容易。
- 类组件:类组件的生命周期方法分散在不同的位置,对于复杂组件,代码可能变得冗长和难以维护。例如,一个具有多个副作用逻辑的类组件,
import React, { useState, useEffect } from'react';
const ChatComponent = () => {
const [messages, setMessages] = useState([]);
useEffect(() => {
const socket = new WebSocket('ws://example.com/socket');
socket.onopen = () => {
// 连接成功处理
};
return () => {
socket.close();
};
}, []);
useEffect(() => {
const handleNewMessage = (message) => {
setMessages([...messages, message]);
};
// 假设这里有一个全局的消息接收事件绑定
window.addEventListener('newMessage', handleNewMessage);
return () => {
window.removeEventListener('newMessage', handleNewMessage);
};
}, [messages]);
return (
<div>
{/* 聊天消息渲染 */}
</div>
);
};
export default ChatComponent;
- 逻辑理解:
- 类组件:类组件的生命周期方法基于面向类的编程思维,对于不熟悉类继承和生命周期概念的开发者来说,理解起来可能有一定难度。例如,
componentWillReceiveProps
(已废弃,新的方式是在componentDidUpdate
中比较prevProps
和nextProps
)需要开发者理解组件在接收新属性时的特殊处理逻辑。 - 函数组件(useEffect):
useEffect
基于函数式编程思维,更加直观。开发者可以将useEffect
看作是在组件渲染后执行的副作用函数,依赖数组决定了函数的执行时机,更容易理解和掌握。例如,在一个倒计时组件中,使用useEffect
实现倒计时逻辑,开发者可以很清晰地看到倒计时开始和更新的逻辑以及依赖关系。
- 类组件:类组件的生命周期方法基于面向类的编程思维,对于不熟悉类继承和生命周期概念的开发者来说,理解起来可能有一定难度。例如,
import React, { useState, useEffect } from'react';
const Countdown = () => {
const [timeLeft, setTimeLeft] = useState(60);
useEffect(() => {
const timer = setInterval(() => {
setTimeLeft(timeLeft - 1);
if (timeLeft === 0) {
clearInterval(timer);
}
}, 1000);
return () => {
clearInterval(timer);
};
}, [timeLeft]);
return (
<div>
<p>Time left: {timeLeft} seconds</p>
</div>
);
};
export default Countdown;