Как лениво загружать и аннулировать данные в контексте React на основе вызовов функций?

Вопрос или проблема

Я работаю над приложением на React, в котором мне нужно управлять контекстом, предоставляющим доступ к структуре данных. Я хочу заполнить эту структуру только когда вызывается определенная функция. Кроме того, если исходные данные изменятся, мне необходимо аннулировать кэшированные данные и убедиться, что следующий вызов функции получит обновленные данные.

Вот упрощенная версия моего сценария:


export const DataContext = createContext({
  getDataById: (id) => undefined,
});

export const DataProvider = ({ children }) => {
  const sourceData = useFetchData(); // Пользовательский хук для получения исходных данных
  const [dataMap, setDataMap] = useState({});

  const populateDataMap = useCallback(() => {
    if (sourceData) {
      const newMap = sourceData.reduce((acc, item) => {
        acc[item.id] = item;
        return acc;
      }, {});
      setDataMap(newMap);
    }
  }, [sourceData]);

  const getDataById = useCallback(
    (id) => {
      if (Object.keys(dataMap).length === 0) {
        populateDataMap();
      }
      return dataMap[id]; // Это может не сработать, так как dataMap не будет обновлен еще в ходе итерации
    },
    [dataMap, populateDataMap]
  );

  // Аннулировать и сбросить dataMap всегда, когда меняются исходные данные
  useEffect(() => {if (sourceData) {setDataMap({})}}, [sourceData]);

  return (
    <DataContext.Provider value={{ getDataById }}>
      {children}
    </DataContext.Provider>
  );
};

// Пример использования
const ExampleComponent = () => {
  const { getDataById } = React.useContext(DataContext);

  const handleGetData = (id) => {
    const data = getDataById(id);
    console.log(data);
  };

  return <button onClick={() => handleGetData('some-id')}>Получить данные</button>;
};

Что я хочу достичь:

  • dataMap должен заполняться только когда getDataById вызывается в первый раз.
  • Если sourceData изменится (например, если я его повторно получу), я хочу аннулировать текущий dataMap, чтобы следующий вызов getDataById пересчитал его.

Есть ли лучший способ обработки этой схемы в React? Любые предложения или улучшения будут очень приветствоваться!

Ответ или решение

Как реализовать ленивую загрузку и инвалидировать данные в контексте React на основе вызовов функций

Работа с контекстом в React может быть довольно сложной, особенно когда необходимо реализовать такие сценарии, как ленивое население данных и их инвалидизация при изменении исходных данных. В этом ответе мы рассмотрим, как добиться желаемого результата на примере, который вы предоставили.

Проблема

Вы хотите, чтобы dataMap заполнялся только при первом вызове функции getDataById, а также чтобы этот кэш сбрасывался при изменении sourceData.

Решение

Для достижения такой функциональности, мы можем использовать хук состояния и эффектов в React. Рассмотрим, как можно улучшить ваш код с акцентом на читаемость, эффективность и предотвращение потенциальных багов.

import React, { createContext, useState, useEffect, useCallback } from 'react';

// Создаем контекст данных
export const DataContext = createContext({
  getDataById: (id) => undefined,
});

// Провайдер данных
export const DataProvider = ({ children }) => {
  const sourceData = useFetchData(); // Кастомный хук для получения исходных данных
  const [dataMap, setDataMap] = useState({});
  const [isDataLoaded, setIsDataLoaded] = useState(false); // Флаг для отслеживания загрузки данных

  // Функция для заполнения dataMap
  const populateDataMap = useCallback(() => {
    if (sourceData && Array.isArray(sourceData)) {
      const newMap = sourceData.reduce((acc, item) => {
        acc[item.id] = item;
        return acc;
      }, {});
      setDataMap(newMap);
      setIsDataLoaded(true); // Устанавливаем флаг после загрузки данных
    }
  }, [sourceData]);

  // Функция для получения данных по ID
  const getDataById = useCallback((id) => {
    // Если данные еще не загружены, запускаем загрузку
    if (!isDataLoaded) {
      populateDataMap();
    }
    return dataMap[id]; // Возвращаем данные по ID
  }, [dataMap, isDataLoaded, populateDataMap]);

  // Инвалидируем и сбрасываем dataMap, когда sourceData изменяется
  useEffect(() => {
    if (sourceData) {
      setDataMap({});
      setIsDataLoaded(false); // Сбрасываем флаг загрузки
    }
  }, [sourceData]);

  return (
    <DataContext.Provider value={{ getDataById }}>
      {children}
    </DataContext.Provider>
  );
};

Объяснение кода

  1. Создание контекста: Используем createContext для создания контекста данных и определения его начального состояния.

  2. Использование состояния: dataMap хранит данные, а isDataLoaded отслеживает, были ли данные загружены.

  3. populateDataMap: Эта функция позаботится о заполнении dataMap только при наличии исходных данных и обновит флаг isDataLoaded. Это гарантирует, что данные будут загружены только один раз и только по необходимости.

  4. getDataById: Простая функция для получения данных по идентификатору. Если данные еще не загружены (isDataLoaded равно false), вызывается populateDataMap, что делает код более управляемым.

  5. Инвалидизация данных: Используем useEffect для автоматического сброса dataMap и флага загрузки при изменении sourceData, что гарантирует актуальность кэша.

Заключение

Приведенное решение оптимизирует процесс получения и управления данными в вашем контексте. Ленивое население данных, внедренное вместе с системой инвалидизации, поможет избежать проблем с неправильными данными и улучшить производительность приложения.

Это подход обеспечит чистоту и эффективность вашего React-приложения, а также позволит легко управлять состоянием данных. Если у вас есть дополнительные вопросы или требования, не стесняйтесь их задавать!

Оцените материал
Добавить комментарий

Капча загружается...