Мемоизированный селектор повторного выбора с RTKQuery selectFromResult по-прежнему повторно отображает компонент

Я работаю с конечной точкой API, которая возвращает случайные данные для определенного свойства (unstableProperty) каждый раз, когда оно вызывается для одного и того же объекта. Это поведение показано ниже:

// First API Call
GET /entities/3
{
  "name": "Entity Name",
  "message": "This is some entity description",
  "files": [
    { "fileName": "File1", "unstableProperty": "bbserg" },
    { "fileName": "File2", "unstableProperty": "thslkuygseaf" }
  ]
}

// Second API Call (5 seconds later)
GET /entities/3
{
  "name": "Entity Name",
  "message": "This is some entity description",
  "files": [
    { "fileName": "File1", "unstableProperty": "ystgrgazazrg" },
    { "fileName": "File2", "unstableProperty": "strhsryjarehaerh" }
  ]
}

Контекст

Бизнес-правило требует опроса этой конечной точки каждые X секунд на наличие изменений. Однако я не хочу, чтобы мой компонент обновлялся каждый раз, поскольку он включает в себя таблицу, потенциально содержащую сотни строк, и каждая строка содержит миниатюру. Каждый раз повторная обработка таблицы приводит к проблемам с производительностью и заставляет миниатюры мерцать.

Проблема

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

Я подготовил подробный пример на этом коде и ящике: https://codesandbox.io/p/sandbox/modest-snyder-vyxkxd?file=%252Fsrc%252Fapi%252FbaseApi.ts

Вот как я смоделировал нестабильное свойство для демонстрации:

let calls = 0;

export const baseApi = createApi({
  baseQuery: fetchBaseQuery(),
  endpoints: (build) => ({
    getTestData: build.query<TestDataResponse, void>({
      queryFn: () => ({
        data: {
          name: "Entity Name",
          message: "This is some entity description",
          files: [
            { fileName: "File1", unstableProperty: `value_${calls++}` },
            { fileName: "File2", unstableProperty: `value_${calls++}` },
          ],
        },
      }),
    }),
  }),
});

Мой компонент, который показывает проблему, использует этот запрос следующим образом:

  const selectOnlyStableData = useMemo(() => {
    return createSelector(
      (data: TestDataResponse | undefined) => data,
      (data: TestDataResponse | undefined) => {
        if (!data) return undefined;

        // Select everything from data except files' unstableProperty
        const stableData = {
          name: data.name,
          message: data.message,
          files: data.files.map((f) => ({
            fileName: f.fileName,
          })),
        };

        return stableData;
      }
    );
  }, []);

  const { data } = useGetTestDataQuery(undefined, {
    pollingInterval: 5000,
    selectFromResult: ({ data }) => ({
      data: selectOnlyStableData(data),
    }),
  });

Несмотря на селектор, компонент перерисовывается каждые 5 секунд. Все работает так, как задумано, если я полностью удалю нестабильное свойство из результата запроса.

Вопрос

Как предотвратить повторную отрисовку, вызванную изменениями нестабильного свойства, при этом опрашивая другие изменения данных с помощью запроса RTK?

Обратите внимание, что я не могу просто удалить это нестабильное свойство (например, в transformResponse), потому что мне нужно значение этого нестабильного свойства в других компонентах.


1
75
1

Ответ:

Решено

Если вы спросите меня, в документации есть немного спрятанная опция resultEqualityCheck. Это упоминается здесь в примере в самом низу страницы и кратко документировано здесь. Это свойство делает именно то, что мне нужно — сравнивает текущий и предыдущий выходные данные и возвращает новую ссылку только в том случае, если они не идентичны. Из документации:

Если предоставлено, используется для сравнения вновь созданного выходного значения с предыдущими значениями в кэше. Если совпадение найдено, возвращается старое значение. Это касается распространенного случая использования todos.map(todo => todo.id), когда обновление другого поля в исходных данных вызывает перерасчет из-за измененных ссылок, но выходные данные остаются фактически теми же.

Фиксированный селектор выглядит следующим образом:

const selectOnlyStableData = useMemo(() => {
    return createSelector(
      (data: TestDataResponse | undefined) => data.data,
      (data: TestDataResponse | undefined) => {
        if (!data) return undefined;

        // Select everything from data except files' unstableProperty
        const stableData = {
          name: data.name,
          message: data.message,
          files: data.files.map((f) => ({
            fileName: f.fileName,
          })),
        };

        return stableData;
      },
      {
        memoizeOptions: {
          resultEqualityCheck: deepEqual,
        },
      }
    );
  }, []);

Исправлены коды и ящик: https://codesandbox.io/p/sandbox/agitated-hugle-xtyt78?file=%252Fsrc%252FcomComponents%252FProblem.tsx