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

如何让React-Native(TypeScript)组件每分钟自动重新渲染?

React Native + TypeScript 组件每分钟强制重渲染解决方案

问题背景

我正在开发一个TypeScript的React-Native项目,需要组件每分钟重新渲染一次——即使Redux的useSelector派生状态本身未变化。原因是我的selector输出依赖dateProvider.getNow(),时间变化会导致ViewModel内容更新,但Redux不会自动触发重渲染。

现有代码片段

HomeScreen组件简化版

import { useSelector } from 'react-redux';
import { useEffect, useState } from 'react';

export function HomeScreen({ navigation }) {
  const viewModel = useSelector((rootState) => selectHomeViewModel(rootState, dateProvider))

  // rest of the component code
}

HomeViewModel代码

import { RootState } from '../../../../core/_redux_/createStore.ts'
import {
  BlockSession,
  blockSessionAdapter,
} from '../../../../core/block-session/block.session.ts'
import { formatDistance, isBefore } from 'date-fns'
import { createSelector } from '@reduxjs/toolkit'
import {
  Greetings,
  HomeViewModel,
  HomeViewModelType,
  SessionBoardMessage,
  SessionBoardTitle,
} from './home-view-model.types.ts'
import { DateProvider } from '../../../../infra/date-provider/port.date-provider.ts'

function greetUser(now: Date) {
  const hour = now.getHours()

  if (hour >= 6 && hour < 12) return Greetings.GoodMorning
  if (hour >= 12 && hour < 18) return Greetings.GoodAfternoon
  if (hour >= 18 && hour < 22) return Greetings.GoodEvening
  return Greetings.GoodNight
}

function generateTimeInfo(now: Date, start: Date, end: Date) {
  return isActive(now, start, end)
    ? 'Ends ' +
        formatDistance(end, now, {
          addSuffix: true,
        })
    : 'Starts at ' +
        start.getHours().toString().padStart(2, '0') +
        ':' +
        start.getMinutes().toString().padStart(2, '0')
}

function recoverDate(now: Date, time: string) {
  const [hours, minutes] = time.split(':').map(Number)

  const todayWithNewTime = new Date(now.getTime())
  todayWithNewTime.setHours(hours)
  todayWithNewTime.setMinutes(minutes)

  return todayWithNewTime
}

function formatToViewModel(blockSessions: BlockSession[], now: Date) {
  return blockSessions.map((session) => {
    const start = recoverDate(now, session.start)
    const end = recoverDate(now, session.end)

    const timeline = generateTimeInfo(now, start, end)

    return {
      id: session.id,
      name: session.name,
      minutesLeft: timeline,
      blocklists: session.blocklists.length,
      devices: session.devices.length,
    }
  })
}

function isActive(now: Date, start: Date, end: Date) {
  return !isBefore(now, start) && isBefore(now, end)
}

export const selectHomeViewModel = createSelector(
  [
    (rootState: RootState) => rootState.blockSession,
    (_state: RootState, dateProvider: DateProvider) => dateProvider,
  ],
  (blockSession, dateProvider): HomeViewModelType => {
    const blockSessions = blockSessionAdapter
      .getSelectors()
      .selectAll(blockSession)

    const greetings = greetUser(dateProvider.getNow())

    const NO_ACTIVE_SESSION = {
      title: SessionBoardTitle.NO_ACTIVE_SESSIONS as const,
      message: SessionBoardMessage.NO_ACTIVE_SESSIONS as const,
    }

    const NO_SCHEDULED_SESSION = {
      title: SessionBoardTitle.NO_SCHEDULED_SESSIONS as const,
      message: SessionBoardMessage.NO_SCHEDULED_SESSIONS as const,
    }

    if (!blockSessions.length)
      return {
        type: HomeViewModel.WithoutActiveNorScheduledSessions,
        greetings,
        activeSessions: NO_ACTIVE_SESSION,
        scheduledSessions: NO_SCHEDULED_SESSION,
      }

    const activeSessions = blockSessions.filter((session) => {
      const now = dateProvider.getNow()
      const start = recoverDate(now, session.start)
      const end = recoverDate(now, session.end)
      return isActive(now, start, end)
    })
    const formattedActiveSessions = formatToViewModel(
      activeSessions,
      dateProvider.getNow(),
    )

    const scheduledSessions = blockSessions.filter((session) => {
      const now = dateProvider.getNow()
      const start = recoverDate(now, session.start)
      const end = recoverDate(now, session.end)
      return !isActive(now, start, end)
    })
    const formattedScheduledSessions = formatToViewModel(
      scheduledSessions,
      dateProvider.getNow(),
    )

    if (!activeSessions.length)
      return {
        type: HomeViewModel.WithoutActiveWithScheduledSessions,
        greetings,
        activeSessions: NO_ACTIVE_SESSION,
        scheduledSessions: {
          title: SessionBoardTitle.SCHEDULED_SESSIONS,
          blockSessions: formattedScheduledSessions,
        },
      }

    if (!scheduledSessions.length)
      return {
        type: HomeViewModel.WithActiveWithoutScheduledSessions,
        greetings,
        activeSessions: {
          title: SessionBoardTitle.ACTIVE_SESSIONS,
          blockSessions: formattedActiveSessions,
        },
        scheduledSessions: NO_SCHEDULED_SESSION,
      }

    return {
      type: HomeViewModel.WithActiveAndScheduledSessions,
      greetings,
      activeSessions: {
        title: SessionBoardTitle.ACTIVE_SESSIONS as const,
        blockSessions: formattedActiveSessions,
      },
      scheduledSessions: {
        title: SessionBoardTitle.SCHEDULED_SESSIONS as const,
        blockSessions: formattedScheduledSessions,
      },
    }
  },
)

DateProvider代码

export class RealDateProvider implements DateProvider {
  getISOStringNow(): string {
    return new Date().toISOString()
  }

  getNow(): Date {
    return new Date()
  }
}

可行解决方案

方案1:本地状态触发重渲染(简单直接)

利用useState保存时间戳,每分钟更新一次,让useSelector感知到依赖变化,从而重新计算ViewModel。

修改HomeScreen组件

import { useSelector } from 'react-redux';
import { useEffect, useState } from 'react';

export function HomeScreen({ navigation }) {
  // 用本地状态存储当前时间戳,作为重渲染触发点
  const [refreshTrigger, setRefreshTrigger] = useState(Date.now());

  useEffect(() => {
    // 每分钟更新一次时间戳
    const interval = setInterval(() => {
      setRefreshTrigger(Date.now());
    }, 60 * 1000);

    // 组件卸载时清理定时器
    return () => clearInterval(interval);
  }, []);

  // 将refreshTrigger传入selector,让createSelector检测到依赖变化
  const viewModel = useSelector((rootState) => 
    selectHomeViewModel(rootState, dateProvider, refreshTrigger)
  );

  // rest of the component code
}

修改selector

export const selectHomeViewModel = createSelector(
  [
    (rootState: RootState) => rootState.blockSession,
    (_state: RootState, dateProvider: DateProvider) => dateProvider,
    // 添加时间戳作为依赖,不需要实际使用,仅触发重新计算
    (_state: RootState, _dateProvider: DateProvider, trigger: number) => trigger,
  ],
  (blockSession, dateProvider): HomeViewModelType => {
    // 统一获取当前时间,减少重复调用
    const now = dateProvider.getNow();
    const blockSessions = blockSessionAdapter.getSelectors().selectAll(blockSession);

    // 原有逻辑保持不变,仅替换所有dateProvider.getNow()为now变量
    // ... 原有代码
  }
);

方案2:Redux全局时间状态(多组件场景)

如果多个组件需要时间驱动的重渲染,可将时间戳放入Redux store,统一管理更新逻辑。

创建time slice

// core/_redux_/features/time/timeSlice.ts
import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  currentTimestamp: Date.now(),
};

const timeSlice = createSlice({
  name: 'time',
  initialState,
  reducers: {
    updateTimestamp: (state) => {
      state.currentTimestamp = Date.now();
    },
  },
});

export const { updateTimestamp } = timeSlice.actions;
export default timeSlice.reducer;

注册到store

// core/_redux_/createStore.ts
import { configureStore } from '@reduxjs/toolkit';
import blockSessionReducer from '../block-session/block.session.ts';
import timeReducer from './features/time/timeSlice';

export const store = configureStore({
  reducer: {
    blockSession: blockSessionReducer,
    time: timeReducer,
  },
});

export type RootState = ReturnType<typeof store.getState>;

修改HomeScreen组件

import { useSelector, useDispatch } from 'react-redux';
import { useEffect } from 'react';
import { updateTimestamp } from '../../core/_redux_/features/time/timeSlice';

export function HomeScreen({ navigation }) {
  const dispatch = useDispatch();
  const currentTimestamp = useSelector((state) => state.time.currentTimestamp);

  useEffect(() => {
    const interval = setInterval(() => {
      dispatch(updateTimestamp());
    }, 60 * 1000);

    return () => clearInterval(interval);
  }, [dispatch]);

  const viewModel = useSelector((rootState) => 
    selectHomeViewModel(rootState, dateProvider, currentTimestamp)
  );

  // rest of the component code
}

修改selector依赖

export const selectHomeViewModel = createSelector(
  [
    (rootState: RootState) => rootState.blockSession,
    (_state: RootState, dateProvider: DateProvider) => dateProvider,
    (rootState: RootState) => rootState.time.currentTimestamp,
  ],
  (blockSession, dateProvider): HomeViewModelType => {
    const now = dateProvider.getNow();
    // 原有逻辑不变
    // ... 原有代码
  }
);

方案3:优化selector性能

原selector中多次调用dateProvider.getNow(),统一获取一次可减少重复计算,提升性能:

export const selectHomeViewModel = createSelector(
  [
    (rootState: RootState) => rootState.blockSession,
    (_state: RootState, dateProvider: DateProvider) => dateProvider,
    (_state: RootState, _dateProvider: DateProvider, trigger: number) => trigger,
  ],
  (blockSession, dateProvider): HomeViewModelType => {
    // 统一获取当前时间,后续逻辑复用
    const now = dateProvider.getNow();
    const blockSessions = blockSessionAdapter.getSelectors().selectAll(blockSession);

    const greetings = greetUser(now);

    // 替换所有dateProvider.getNow()为now变量
    const activeSessions = blockSessions.filter((session) => {
      const start = recoverDate(now, session.start);
      const end = recoverDate(now, session.end);
      return isActive(now, start, end);
    });
    const formattedActiveSessions = formatToViewModel(activeSessions, now);

    const scheduledSessions = blockSessions.filter((session) => {
      const start = recoverDate(now, session.start);
      const end = recoverDate(now, session.end);
      return !isActive(now, start, end);
    });
    const formattedScheduledSessions = formatToViewModel(scheduledSessions, now);

    // 后续分支逻辑保持不变
    // ... 原有代码
  }
);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 19:19:51