为何React推荐在componentDidMount中发起AJAX请求?该实践有何局限?
你提的这个点真的戳中了很多React开发者刚接触类组件时的困惑——当初我刚学React的时候,也对着这个问题卡了好一阵子!
首先得澄清:早期推荐在componentDidMount里发起AJAX,是有特定场景和前提的,它并不是“唯一正确”的实践,更不是忽略props更新的解决方案。
为什么最初会推荐componentDidMount?
在类组件的生命周期里,componentDidMount是组件挂载完成后第一个能安全执行副作用(比如AJAX请求)的钩子:
- 它只会在组件第一次渲染完成后执行一次,避免了在
render里发起请求导致的无限循环(因为请求更新state会触发render,render又请求,死循环); - 此时组件已经挂载到DOM上,如果你需要基于DOM的状态做请求(比如获取某个元素的尺寸),这个时机是安全的。
你说的问题确实存在——props更新时怎么办?
完全同意你的观察:如果请求依赖父组件传入的props,当props变化时,componentDidMount不会再次执行,这时候组件就无法获取新的数据。这不是这个实践的“缺陷”,而是它只覆盖了组件挂载的场景,没覆盖组件更新的场景。
解决这个问题,在类组件里我们需要配合componentDidUpdate钩子:
class UserProfile extends React.Component { componentDidMount() { // 初始挂载时发起请求 this.fetchUser(this.props.userId); } componentDidUpdate(prevProps) { // 对比前后props,只有当userId变化时才重新请求 if (prevProps.userId !== this.props.userId) { this.fetchUser(this.props.userId); } } fetchUser = async (userId) => { const response = await fetch(`/api/users/${userId}`); const userData = await response.json(); this.setState({ user: userData }); } // ...render逻辑 }
这里一定要加prevProps和当前props的对比判断,不然每次组件更新(比如state变化、父组件其他props变化)都会发起请求,既浪费资源又可能导致不必要的重渲染。
现在更灵活的方案:函数组件+useEffect
如果是用现代React(函数组件),useEffect钩子已经完美解决了这个问题——你只需要把依赖的props放进依赖数组,React就会在props变化时自动重新执行副作用:
function UserProfile({ userId }) { const [user, setUser] = useState(null); useEffect(() => { const fetchUser = async () => { const response = await fetch(`/api/users/${userId}`); const userData = await response.json(); setUser(userData); }; fetchUser(); }, [userId]); // 依赖userId,当它变化时重新执行请求 // ...render逻辑 }
这种方式把挂载和更新的逻辑合并到了一起,不需要分开写两个钩子,逻辑更简洁。
总结
“在componentDidMount发起AJAX”是类组件时代的常见基础实践,但它需要配合componentDidUpdate来处理props更新的场景。而随着React的演进,函数组件+useEffect已经成为更推荐的方案,它能更优雅地处理依赖变化的副作用。
内容的提问来源于stack exchange,提问作者Deng Zhebin

