React中如何基于定时器仅触发特定组件重新渲染
方案对比结论
- 当项目中
MomentText实例数量少于10个时,第一种单组件独立定时器方案更合适:逻辑简单无额外依赖,维护成本极低,性能损耗可以忽略不计 - 当实例数量较多(比如列表页存在数十甚至上百个相对时间渲染项)时,第二种基于Context + 订阅分发的方案更优:全局仅需维护极少数定时器,资源开销比第一种低1~2个数量级
现有方案优化建议
你现在的订阅机制还可以进一步完善,解决两个潜在问题:
- 原
MomentText代码存在逻辑bug:formatDistanceToNow没有传入date参数,无论传入什么日期都会输出0分钟前类的错误结果 - 未适配组件自定义的
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
相关产品推荐
相关产品推荐

