组件prop变更时状态重置:为何无法使用getDerivedStateFromProps?
这问题我之前也碰到过,确实getDerivedStateFromProps在这里不太适用——它要求返回的状态必须是props的纯函数,而咱们的elapse是一直在计时的动态值,完全不是由name直接决定的;而且要是直接返回{elapse: 0},每次组件重渲染(比如计时器更新导致的)都会触发这个方法,直接把计时给搞崩了。
那换个思路,咱们可以通过监听name的变化来手动重置elapse,不管是函数组件还是类组件都有对应的靠谱实现:
函数组件方案(React Hooks)
用useEffect专门监听name的变化,一旦name更新就重置计时:
import { useState, useEffect, useRef } from 'react'; function Greeting({ name }) { const [elapse, setElapse] = useState(0); // 用ref保存计时器ID,方便后续清理 const timerRef = useRef(null); // 组件挂载时启动计时器 useEffect(() => { timerRef.current = setInterval(() => { // 用函数式更新确保拿到最新的state值 setElapse(prev => prev + 1); }, 1000); // 组件卸载时清理计时器,避免内存泄漏 return () => clearInterval(timerRef.current); }, []); // 监听name的变化,只要name变了就重置elapse useEffect(() => { setElapse(0); }, [name]); return <div>Hi {name}. It's been {elapse} seconds</div>; }
这个方案里两个useEffect各司其职:一个负责维持正常计时,另一个专门处理name变化的重置逻辑,完全避开了getDerivedStateFromProps的限制。
类组件方案
用componentDidUpdate对比前后的name props,只有当name确实变化时才重置状态:
class Greeting extends React.Component { state = { elapse: 0 }; timer = null; componentDidMount() { // 挂载时启动计时器 this.timer = setInterval(() => { this.setState(prevState => ({ elapse: prevState.elapse + 1 })); }, 1000); } componentDidUpdate(prevProps) { // 仅当name发生真实变化时,才重置elapse if (prevProps.name !== this.props.name) { this.setState({ elapse: 0 }); } } componentWillUnmount() { // 卸载时清理计时器 clearInterval(this.timer); } render() { return <div>Hi {this.props.name}. It's been {this.state.elapse} seconds</div>; } }
这里的关键是在componentDidUpdate里做了前后props的对比,只有当name真的改变了才执行重置,不会在组件自身state更新(比如计时变化)时误触发,完美符合需求。
为什么不推荐用getDerivedStateFromProps?
再强调下:getDerivedStateFromProps会在每次render前被调用,包括组件自身state变化导致的重渲染。如果我们在这里返回{elapse: 0},那每次计时器更新elapse时,这个方法都会把elapse重置为0,计时功能直接失效。而且它要求返回的状态是props的纯函数,咱们的elapse是动态计时的,根本不符合这个要求,所以这个API在这里完全不适用。
内容的提问来源于stack exchange,提问作者mq7

