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

动态定时重计算派生数据:useEffect/useState与useMemo方案探讨

Answer

First, let's break down your questions and address the issues in your current code:

1. Is useMemo a better fit here?

useMemo is designed to memoize derived values that depend on component state/props, not to trigger recalculations based on time events. That said, you can combine it with a trigger state to achieve your goal—more on that below. Your current approach using useState + useEffect isn't inherently bad, but it has some critical issues we need to fix.

2. Fixing Your Current Implementation

Your existing code has two major problems:

  • The useEffect has no dependencies, so it runs on every render, spawning multiple timers and causing unnecessary recalculations.
  • When apiData updates, your filtered data won't refresh until the next timer tick, leading to stale data.

Here's a corrected version of your original approach:

import React from "react";
import { useQuery } from "@tanstack/react-query";

// Mock functions (fixed the filter logic—original was broken)
const getMillisecondsToNextCalculation = () => Math.round(Math.random() * 10000);
const filterData = (data) => data.filter(() => Math.random() > 0.5);
const fetchApiData = async () => { /* Your actual fetch logic */ };

const useFilteredData = () => {
  const { data: apiData } = useQuery("apiData", fetchApiData);
  const [filteredData, setFilteredData] = React.useState([]);

  React.useEffect(() => {
    let timeoutId;

    const recalculate = () => {
      if (!apiData) return;
      // Update filtered data with the latest apiData
      setFilteredData(filterData(apiData));
      
      const delay = getMillisecondsToNextCalculation();
      if (delay) {
        timeoutId = setTimeout(recalculate, delay);
      }
    };

    // Run initial calculation when apiData loads or changes
    recalculate();

    // Clean up timer to prevent memory leaks
    return () => clearTimeout(timeoutId);
  }, [apiData]); // Add apiData as a dependency to react to new data

  return { filteredData };
};

export default useFilteredData;

Key improvements:

  • Added apiData as an useEffect dependency, so filtered data updates immediately when new API data arrives.
  • Fixed the filterData function (your original code passed a boolean to filter() instead of a predicate function).
  • Ensured timers are cleaned up properly to avoid memory leaks.

3. Using useMemo with a Trigger State

If you want to use useMemo (to align with React's "derive state from props/state" pattern), you can introduce a trigger state variable that increments on each timer tick. This change will force useMemo to recompute the filtered data:

import React from "react";
import { useQuery } from "@tanstack/react-query";

// Mock functions
const getMillisecondsToNextCalculation = () => Math.round(Math.random() * 10000);
const filterData = (data) => data.filter(() => Math.random() > 0.5);
const fetchApiData = async () => { /* Your actual fetch logic */ };

const useFilteredData = () => {
  const { data: apiData } = useQuery("apiData", fetchApiData);
  // Trigger state: increments each time we want to recalculate
  const [refreshTrigger, setRefreshTrigger] = React.useState(0);

  // Derive filtered data using useMemo—recomputes when apiData or refreshTrigger changes
  const filteredData = React.useMemo(() => {
    if (!apiData) return [];
    return filterData(apiData);
  }, [apiData, refreshTrigger]);

  React.useEffect(() => {
    let timeoutId;

    const scheduleNextRefresh = () => {
      const delay = getMillisecondsToNextCalculation();
      if (delay) {
        timeoutId = setTimeout(() => {
          // Increment trigger to force useMemo recalculation
          setRefreshTrigger(prev => prev + 1);
          scheduleNextRefresh();
        }, delay);
      }
    };

    // Start the refresh cycle on mount
    scheduleNextRefresh();

    return () => clearTimeout(timeoutId);
  }, []); // No dependencies needed here—we're just managing the timer cycle

  return { filteredData };
};

export default useFilteredData;

Which Approach Should You Choose?

  • Use the useState + useEffect approach if you need more control (e.g., adding loading states, handling errors during filtering).
  • Use the useMemo approach if filteredData is a pure derived value and you want to follow React's recommended pattern for memoizing computed values. Keep in mind: useMemo is a performance optimization, not a guarantee—React may choose to recompute it even if dependencies haven't changed.

Critical Pitfalls to Avoid

  • Stale closures: Always include all dependencies in useEffect/useMemo. Use React's linting warnings to catch missing dependencies.
  • Expensive filter functions: If filterData is computationally heavy, memoize it with useCallback to prevent unnecessary re-runs.
  • Memory leaks: Never forget to clean up timers in the useEffect cleanup function.

内容的提问来源于stack exchange,提问作者Simon Eggert

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 12:53:18