如何让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
相关产品推荐
相关产品推荐

