You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native组件定时器卸载后仍运行问题求助

VisitTimer组件定时器卸载后仍更新state的问题排查与解决

我在维护一个React Native应用时,发现VisitTimer组件的定时器存在异常——组件设计为每秒更新state实现计时,但即使在componentWillUnmount中调用了clearInterval,用户离开组件后this.state.duration仍会每秒更新。

相关代码

_startTimer方法

async _startTimer() {
  if (this.timer) clearInterval(this.timer);

  let stateUpdate = {
    stopTime: null,
  };

  const newStartTime = moment(Date.serverTime() || Date.now());
  if (!this.state.startTime) {
    stateUpdate.startTime = newStartTime.format('YYYY-MM-DD HH:mm:ss');
    stateUpdate.startDuration = newStartTime.format('HH:mm');
  }

  this.setState(stateUpdate, () => {
    this.timer = setInterval(async () => {
      const duration = this._getDuration();
      this.setState({
        duration: duration.long,
        shortDuration: duration.short,
        stopDuration: duration.stopTime.format('HH:mm'),
      });
    }, 1000);

    if (this.props.onChange) this.props.onChange(this.state);
  });
}

componentWillUnmount方法

componentWillUnmount() {
  console.log('VisitTimer() component is unmounting...');
  if (this.timer) clearInterval(this.timer);
}

_getDuration方法

_getDuration() {
  let msDuration =
    (this.state.stopTime
      ? moment(this.state.stopTime).valueOf()
      : moment(Date.serverTime() - this.state.stopTimeOffset).valueOf()) -
    (this.state.startTime ? moment(this.state.startTime).valueOf() : moment(Date.serverTime()).valueOf());

  // detect maximum duration in milliseconds
  if (this.props.maxDuration && this.state.stopTime === null && msDuration >= this.props.maxDuration) {
    msDuration = this.props.maxDuration;

    this.setState(
      {
        stopTimeOffset:
          moment(Date.serverTime()).valueOf() - (moment(this.state.startTime).valueOf() + this.props.maxDuration),
        maxDurationReached: true,
      },
      () => {
        this._stopTimer();
        if (this.props.onMaxDurationReached) this.props.onMaxDurationReached();
      }
    );
  }

  if (msDuration < 0) msDuration = 0;
  const diff = moment.utc(msDuration);

  let long = diff.format('HH:mm:ss');
  let short = diff.format('HH:mm');

  return {
    long: long,
    short: short,
    stopTime: moment(moment(this.state.startTime).valueOf() + msDuration),
  };
}

VisitTimer组件调用代码

<VisitTimer
  started={this.props?.visit?.dates?.started?.value}
  stopped={this.props?.visit?.dates?.completed?.value}
  durationOverride={this.props?.visit?.duration?.total?.value}
  maxDuration={maxVisitTime(this.props?.visit?.type?.value)}
  visit={this.props?.visit}
  discipline={this.props?.patient?.discipline}
  therapistDisciplines={this.props?.therapistDisciplines}
  isChangeRequest={this.state.isChangeRequest}
  disabled={this.state.isCheckoutOutsideHomeGeoFence}
  isBlankLogSheet={this.state.isBlankLogSheet}
  isTest={this.props?.patient?.isTest}
  ref={(ref) => (this.visitTimer = ref)}
  onMaxDurationReached={() => {
    Alert.alert(
      'Maximum Duration Reached',
      `Please enter an accurate manual stop time in the field provided.`,
      [
        {
          text: 'OK',
          onPress: () => {},
          style: 'cancel',
        },
      ],
      { cancelable: false }
    );
    this.setState({ showManualStopTime: true });
  }}
  stopTimeOffset={this.props.visit?.duration?.stopOffset?.value}
  editable={statusCodeToEditableLevel(this.props.visit?.status?.value) === 2}
  onError={(error) => {
    reportError(error, 'components/VisitTimer');
  }}
  onChange={this._onVisitTimerChange}
/>

排查思路

  • 定时器引用丢失:检查this.timer是否被正确赋值和清理,比如多次调用_startTimer是否导致旧定时器未被彻底清除,或是setState回调中的赋值时机引发重复创建。
  • 卸载后异步操作仍触发state更新:_getDuration方法内部存在setState操作,即使组件已卸载,定时器回调若已进入事件队列,仍会执行该方法并触发state更新,闭包还会保留旧的state引用。
  • ref引用导致组件未被正确回收:父组件通过ref持有VisitTimer实例,若父组件未在自身卸载时清理该ref,会导致组件实例被意外保留,定时器持续运行。
  • setInterval回调延迟执行:调用clearInterval后,已经进入事件队列的回调仍会执行,此时组件已卸载,执行setState会引发异常的state更新。

解决方案

1. 添加组件挂载状态标记,阻止卸载后的state更新

在组件state中新增isMounted标记,在组件挂载时设为true,卸载时设为false,所有setState操作前先检查该标记:

componentDidMount() {
  this.setState({ isMounted: true });
}

componentWillUnmount() {
  console.log('VisitTimer() component is unmounting...');
  if (this.timer) clearInterval(this.timer);
  this.setState({ isMounted: false });
}

// 修改_startTimer中的定时器回调
this.timer = setInterval(async () => {
  if (!this.state.isMounted) return; // 新增挂载状态检查
  const duration = this._getDuration();
  this.setState({
    duration: duration.long,
    shortDuration: duration.short,
    stopDuration: duration.stopTime.format('HH:mm'),
  });
}, 1000);

// 修改_getDuration中的setState
if (this.state.isMounted) {
  this.setState(
    {
      stopTimeOffset: moment(Date.serverTime()).valueOf() - (moment(this.state.startTime).valueOf() + this.props.maxDuration),
      maxDurationReached: true,
    },
    () => {
      this._stopTimer();
      if (this.props.onMaxDurationReached) this.props.onMaxDurationReached();
    }
  );
}

2. 确保定时器引用唯一且被彻底清除

新增_stopTimer方法统一处理定时器清除逻辑,在_startTimer和组件卸载时调用,同时置空定时器引用避免残留:

_stopTimer() {
  if (this.timer) {
    clearInterval(this.timer);
    this.timer = null; // 置空引用,防止内存泄漏
  }
}

async _startTimer() {
  this._stopTimer(); // 先清除已有定时器

  let stateUpdate = {
    stopTime: null,
  };

  const newStartTime = moment(Date.serverTime() || Date.now());
  if (!this.state.startTime) {
    stateUpdate.startTime = newStartTime.format('YYYY-MM-DD HH:mm:ss');
    stateUpdate.startDuration = newStartTime.format('HH:mm');
  }

  this.setState(stateUpdate, () => {
    this._stopTimer(); // 再次清除,避免setState回调延迟导致重复创建
    this.timer = setInterval(async () => {
      if (!this.state.isMounted) return;
      const duration = this._getDuration();
      this.setState({
        duration: duration.long,
        shortDuration: duration.short,
        stopDuration: duration.stopTime.format('HH:mm'),
      });
    }, 1000);

    if (this.props.onChange) this.props.onChange(this.state);
  });
}

3. 清理父组件的ref引用

父组件在自身的componentWillUnmount中清理对VisitTimer的ref引用,避免组件实例被意外保留:

componentWillUnmount() {
  this.visitTimer = null;
}

4. 迁移至React最新生命周期(可选)

如果应用使用较新的React版本,建议将类组件改为函数式组件,使用useEffect钩子管理定时器,其返回的清理函数会在组件卸载时自动执行,更可靠:

import { useEffect, useState } from 'react';

function VisitTimer(props) {
  const [isMounted, setIsMounted] = useState(true);
  const [timer, setTimer] = useState(null);

  useEffect(() => {
    return () => {
      setIsMounted(false);
      if (timer) clearInterval(timer);
    };
  }, [timer]);

  // 替代_startTimer的逻辑
  const startTimer = () => {
    if (timer) clearInterval(timer);
    // ...原有state更新逻辑
    const newTimer = setInterval(() => {
      if (!isMounted) return;
      // ...原有定时器回调逻辑
    }, 1000);
    setTimer(newTimer);
  };

  return (
    // ...组件渲染内容
  );
}

内容的提问来源于stack exchange,提问作者Muirik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 03:21:00