Tengo un componente que representa el tiempo relativo
import React, { useEffect, useRef, useReducer } from 'react' import { Text, TextProps } from 'react-native'; import { formatDistanceToNow } from 'date-fns'; type MomentTextProps = Omit<TextProps, 'children'> & { date: Date | number; pollTimeMs?: number; } export function MomentText({ date, pollTimeMs = 60000, ...props }: MomentTextProps): JSX.Element { const [formatted, update] = useReducer( () => formatDistanceToNow(Date.now()), formatDistanceToNow(Date.now()) ); const timeoutRef = useRef<ReturnType<typeof setTimeout>>(); function doUpdate() { update(); timeoutRef.current = setTimeout(update, pollTimeMs); } useEffect(() => { doUpdate(); return () => { clearTimeout(timeoutRef.current!); }; }); return <Text {...props}>{formatted}</Text> }Lo cual funciona, pero parece exagerado que esté creando tantos tiempos de espera, me preguntaba si hay una manera de hacer esto a través de un "contexto de reacción" pero solo volver a renderizar este niño específico.
La forma en que lo pienso (mientras escribo esto) es tener un gancho de subscribe/notify como este...
/** * This hook provides a simple subscription semantic to React components. */ export function useSubscription<T = unknown>(): SubscriptionManager<T> { const subscribersRef = useRef<((data: T) => void)[]>([]); function subscribe(fn: (data: T) => void) { subscribersRef.current.push(fn); return () => { subscribersRef.current = subscribersRef.current.filter( (subscription) => !Object.is(subscription, fn) ); }; } function notify(data: T) { subscribersRef.current.forEach((fn) => fn(data)); } function useSubscribeEffect(fn: (data: T) => void) { useEffect(() => subscribe(fn), []); } return { subscribe, notify, useSubscribeEffect }; } y tenga un useRef y useEffect que llamará a notify en el componente MomentText .
Parece bastante exagerado, pero no estoy seguro de qué es peor.
El primer enfoque es fácil de entender, pero puede ser un golpe de rendimiento
El segundo es un poco más complicado, pero el uso está aislado ya que es solo un requisito de contexto y todo lo demás puede ser manejado por suscripciones dentro del componente.
Parece que la idea que tuve con el contexto funcionaría, pero tuve que crear un nuevo enlace llamado useClock que se implementa a continuación.
import { useEffect, useRef } from "react"; import { SubscriptionManager, useSubscription } from "../useSubscription"; /** * time to next full minute from the date * @param date * @returns milliseconds to next full minute from date */ function timeToNextFullMinute(date: number): number { return 60000 - (date % 60000); } /** * This hook notifies the subscribers with the current date. This * is useful for updating components that show moment in time without * a full rerender of the tree. * * It notifies at the following points: * - useEffect * - first minute at zero seconds * - every minute after at zero seconds * * However, due to the performance of the device, this may not be acurate. * * @returns subscription manager */ export function useClock(): SubscriptionManager<number> { const { subscribe, notify, useSubscribeEffect } = useSubscription<number>(); const timeoutRef = useRef<ReturnType<typeof setTimeout>>(); function doNotify() { notify(Date.now()); timeoutRef.current = setTimeout(doNotify, timeToNextFullMinute(Date.now())); } useEffect(() => { doNotify(); return () => { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion clearTimeout(timeoutRef.current!); }; }, []); return { subscribe, notify, useSubscribeEffect }; } Mi contexto luego proporcionaría el useSubscribeEffect específico para el useClock que luego uso de MomentText , que sería:
type MomentTextProps = Omit<TextProps, "children"> & { on: Date | number; }; export function MomentText({ on, ...props }: MomentTextProps): JSX.Element { const { useClockSubscribeEffect } = useDevhausUI(); const { formatRelativeToNow } = useLocalization(); const [text, setText] = useState(formatRelativeToNow(on)); useClockSubscribeEffect(() => setText(formatRelativeToNow(on))); return <Text {...props}>{text}</Text>; } Con este enfoque, el tiempo de espera se administra en el useClock() y es parte de un contexto, por lo que no hay demasiados corriendo.
Fuente y prueba disponibles en https://github.com/trajano/react-hooks-tests/tree/master/src/useClock