Я работаю с конечной точкой 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
), потому что мне нужно значение этого нестабильного свойства в других компонентах.
Возможно, опрос — не лучшее решение для вас, если вы не можете предоставить в данных какое-то стабильное поле «updateHash» или «lastUpdatedAt». Как часто данные на самом деле меняются? Рассматривали ли вы сокеты или публикацию/подписку и «рассылку» обновлений клиентам или проверку связи с ними для получения?
@DrewReese Я не являюсь владельцем API, поэтому имею ограниченное влияние на его дизайн. Я поговорил с владельцем API и, насколько я понимаю, нестабильное свойство — это хэш свойств файла. Даже если значения, используемые для хеширования, остаются прежними, алгоритм хеширования все равно каждый раз создает другой хэш. Поэтому я уверен, что значение нестабильного свойства будет меняться каждый раз, когда я вызываю конечную точку API.
@DrewReese API требует, чтобы я передал эти нестабильные свойства для операций переименования и удаления. Вот почему мне нужно иметь их в хранилище и найти способ повторно отображать мой компонент только при изменении любых других свойств, за исключением нестабильных. Спасибо за ваше время, я очень ценю ваш вклад!
Спасибо за подробности, этот «хеш» больше похож на идентификатор транзакции/синхронизации/токен, что не очень полезно для рендеринга React. Думаете, вы могли бы отредактировать , включив в него пример последующего кода, в котором слишком часто возникают проблемы? Если бы мы могли увидеть более полный и репрезентативный минимально воспроизводимый пример проблемного кода, мы могли бы быть лучше готовы прийти к другому или более оптимальному решению.
@DrewReese, конечно, я могу отредактировать и предоставить более подробную информацию. Сейчас я потрачу на это несколько часов, и если не найду решения, я обновлю свой код. Просто обновление: я попробовал обернуть мемоизированный селектор другим селектором, чтобы добавить еще один уровень мемоизации, но это не помогло. Я не уверен, что мне здесь не хватает — второй селектор теперь получает на вход стабильные данные: codeandbox.io/p/sandbox/…
@DrewReese У меня получилось! Думаю, опубликую это как ответ: codeandbox.io/p/sandbox/…
Если вы спросите меня, в документации есть немного спрятанная опция 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
Хорошие находки, спасибо, что поделились.
Есть ли у вас какой-либо способ определить, когда полученные данные на самом деле отличаются? Что-нибудь вообще, что можно было бы использовать в качестве зависимости для запоминания? Я довольно долго возился с вашей песочницей и не смог заставить ничего работать так, как вы пытаетесь. Технически вы получаете новые результаты каждые 5 секунд, поэтому мемоизация в вашей реализации довольно неудачна. Я бы посоветовал подумать о запоминании свойства
data
, которое вы передаете... но опять же, вам понадобится что-то вроде стабильной ссылки на данные для запоминания.