为何useEffect添加curValue为依赖时每计时器tick触发清理,类组件无此问题
问题原因说明
1. 关于useEffect清理函数的执行时机
你对清理函数的认知存在偏差:useEffect的清理函数不只是在组件销毁时触发,只要Effect的依赖项发生变化,React就会先执行上一次Effect返回的清理函数,再执行本次的Effect逻辑。
2. 加入curValue后频繁触发清理的原因
你每次计数更新都会修改curValue状态,触发组件重渲染。如果把curValue加入依赖数组,React每次检测到curValue变化,就会重新执行Effect:先跑清理函数销毁上一次的定时器,再创建新的定时器,自然就会出现每次tick都触发清理的现象。
3. 为什么不需要把curValue加入依赖
教程要求把Effect用到的所有变量加入依赖,指的是在Effect回调内直接引用的、来自组件作用域的变量。你这段代码里更新状态用的是setCounter的函数式写法:
setCounter((current) => current + parseInt(props.increment))
这里的最新计数值current是React自动传入的,你根本没有在Effect里直接引用外部的curValue变量,所以curValue完全不属于这个Effect的依赖项,不需要加入数组。
补充优化建议
你当前代码的Effect依赖还缺了props.increment:因为你在Effect里直接引用了props.increment,如果后续父组件传入的增量发生变化,现有定时器里的增量还是旧值,建议补充到依赖数组里。
另外你清理函数里的日志打印componentWillUnmount是不准确的,这个日志大部分时候打印的是Effect更新前的清理操作,只有组件卸载时的那次才对应类组件的componentWillUnmount生命周期。
你给出的两个计数器实现代码(修正转义字符后):
// 每秒按props.increment指定的步长增加计数 function HookCounter(props) { const [curValue, setCounter] = useState(0); const [isOn, setIsOn] = useState(true); function toggleTimer() { setIsOn(!isOn); } useEffect(() => { let _timer = null; function _update(x,y) { setCounter((current) => current + parseInt(props.increment)); } if(isOn) { _timer = setInterval( _update, 1000 ); } else { clearInterval(_timer); } return () => { console.log('Effect清理执行'); clearInterval(_timer); }; }, [isOn, props.increment]); let buttonLabel = "Start"; if(isOn) { buttonLabel = "Stop"; } return <div>HookCounter {curValue} <button onClick={toggleTimer}>{buttonLabel}</button></div>; } class Counter extends React.Component { constructor(props) { super(props); this.incAmount = parseInt(props.increment); this.state = {curValue: 0, isOn: false}; this.toggleTimer = this.toggleTimer.bind(this); } componentDidMount() { this.startTimer(); } startTimer() { this.setState({isOn: true}); this._timer = setInterval( () => this.tick(), 1000 ); } stopTimer() { if(this.state.isOn) { clearInterval(this._timer); } this.setState({isOn: false}); } tick() { this.setState((state, props) => ({ curValue: (this.incAmount + state.curValue) })); } componentWillUnmount() { console.log("componentWillUnmount"); this.stopTimer(); } toggleTimer(x, y) { if(this.state.isOn) { this.stopTimer(); } else { this.startTimer(); } } render() { let buttonLabel = "Start"; if(this.state.isOn) buttonLabel = "Stop"; return <div>{this.state.curValue} <button onClick={this.toggleTimer}>{buttonLabel}</button></div>; } }
内容的提问来源于stack exchange,提问作者ohhayitskay
相关产品推荐
相关产品推荐

