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

React中如何基于定时器仅触发特定组件重新渲染

方案对比结论
  • 当项目中MomentText实例数量少于10个时,第一种单组件独立定时器方案更合适:逻辑简单无额外依赖,维护成本极低,性能损耗可以忽略不计
  • 当实例数量较多(比如列表页存在数十甚至上百个相对时间渲染项)时,第二种基于Context + 订阅分发的方案更优:全局仅需维护极少数定时器,资源开销比第一种低1~2个数量级
现有方案优化建议

你现在的订阅机制还可以进一步完善,解决两个潜在问题:

  1. 原MomentText代码存在逻辑bug:formatDistanceToNow没有传入date参数,无论传入什么日期都会输出0分钟前类的错误结果
  2. 未适配组件自定义的pollTimeMs属性,不同更新间隔的组件无法共用同一套订阅体系

完整实现代码

1. 定义Context与Provider

import React, { createContext, useContext, useEffect, useRef } from 'react';
import { useSubscription } from './useSubscription'; // 就是你提供的useSubscription钩子

type PollingSubscription = Record<number, ReturnType<typeof useSubscription<number>>>;
const MomentTextContext = createContext<{
  subscribe: (pollTimeMs: number, callback: (now: number) => void) => () => void;
} | null>(null);

export function MomentTextProvider({ children }: { children: React.ReactNode }) {
  const subscriptionsRef = useRef<PollingSubscription>({});
  const timerRef = useRef<Record<number, NodeJS.Timeout>>({});

  const subscribe = (pollTimeMs: number, callback: (now: number) => void) => {
    // 不存在对应间隔的订阅器则新建
    if (!subscriptionsRef.current[pollTimeMs]) {
      subscriptionsRef.current[pollTimeMs] = useSubscription<number>();
      // 启动对应间隔的定时器
      timerRef.current[pollTimeMs] = setInterval(() => {
        subscriptionsRef.current[pollTimeMs].notify(Date.now());
      }, pollTimeMs);
    }
    return subscriptionsRef.current[pollTimeMs].subscribe(callback);
  };

  // 组件卸载时清空所有定时器
  useEffect(() => {
    return () => {
      Object.values(timerRef.current).forEach(clearInterval);
    };
  }, []);

  return (
    <MomentTextContext.Provider value={{ subscribe }}>
      {children}
    </MomentTextContext.Provider>
  );
}

export const useMomentTextContext = () => {
  const ctx = useContext(MomentTextContext);
  if (!ctx) throw new Error('请在MomentTextProvider内使用MomentText组件');
  return ctx;
};

2. 重构MomentText组件

import React, { useCallback, useState, useEffect } from 'react'
import { Text, TextProps } from 'react-native';
import { formatDistanceToNow } from 'date-fns';
import { useMomentTextContext } from './MomentTextProvider';

type MomentTextProps = Omit<TextProps, 'children'> & {
  date: Date | number;
  pollTimeMs?: number;
}

export function MomentText({
  date,
  pollTimeMs = 60000,
  ...props 
}: MomentTextProps): JSX.Element {
  const { subscribe } = useMomentTextContext();
  const [formatted, setFormatted] = useState(() => formatDistanceToNow(date));

  const update = useCallback((now: number) => {
    setFormatted(formatDistanceToNow(date, { now }));
  }, [date]);

  // 订阅对应更新间隔的事件
  useEffect(() => {
    return subscribe(pollTimeMs, update);
  }, [pollTimeMs, update, subscribe]);

  return <Text {...props}>{formatted}</Text>
}
使用方式

在应用根组件包裹MomentTextProvider即可,所有子组件内的MomentText会自动共用定时器,只有收到更新通知时才会重渲染,不会触发无关组件更新。如果你的应用支持不同用户时区,可以在Provider的notify时传入统一校准后的时间戳,避免不同组件时间计算不一致的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 20:06:07