Что такое React Query и как им пользоваться в производственных системах?
Пошаговое руководство по использованию React Query в производственных системах: контракты, проверки и готовые блоки кода для команд, внедряющих эту паттерн-архитектуру.
В следующих заметках описывается практический подход к изучению темы «Начало работы с React Query». Основное внимание уделяется контрактам, проверкам и шаблонам кода, которые можно использовать сразу, а не мотивирующим формулировкам. Во время этапа обзора сначала запишите контракт: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия элементам кода, определите критерии успеха и не допускайте безответственного частичного выполнения задач.
Состояние сервера против состояния клиента
Наилучшим образом подходит модель состояния сервера и этапов клиента, когда она рассматривается как измеримая характеристика. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе помогает избежать неожиданных счетов при переходе от демо-среды к общедоступным средам. Старайтесь сохранять низкие затраты на обработку данных и откладывайте дорогостоящие вычисления с использованием мемоизации только после их измерения; преждевременное использование мемоизации может скрыть ошибки, связанные с устаревшими данными.
Традиционно мы получаем данные именно так
Традиционно так работает сценарий наилучшим образом, когда он рассматривается как измеримая поверхность. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь граф зависимостей. Сделайте процесс отрисовки дешёвым и откладывайте затратные вычисления с использованием мемоизации только после их измерения. Преждевременная мемоизация может скрыть ошибки, связанные с устаревшими данными.
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
async function fetchUsers() {
try {
setLoading(true) const res = await fetch(
"https://jsonplaceholder.typicode.com/users"
) const data = await res.json() setUsers(data)
} catch (err) {
setError(err)
} finally {
setLoading(false)
}
} fetchUsers()
С использованием React Query код выше преобразуется в следующее:
С использованием React Query этот этап работает наилучшим образом, когда его рассматривают как измеримую среду. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Документируйте как успешный, так и восстановительный пути работы. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не последующими улучшениями. Сохраняйте низкую стоимость процесса отрисовки и откладывайте дорогостоящие вычисления с использованием мемоизации только после их измерения. Преждевременная мемоизация может скрыть ошибки связанные с устаревшими параметрами.
function Users() {
const {
data,
isLoading,
error
} = useQuery({
queryKey: ["users"],
queryFn: fetchUsers
})
if (isLoading) {
return <p>Loading...</p>
} if (error) {
return <p>Something went wrong</p>
} return (
<ul>
{data.map(user => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
)
}
С использованием React Query этот этап работает наилучшим образом, когда его рассматривают как измеримую среду. Соберите один идеальный пример работы, один случай сбоя и запись о возврате к предыдущему состоянию перед расширением объёма работ. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия соответствующим элементам, определите критерии успеха и не допускайте беззвучного частичного завершения работы.
Как же нам на самом деле им пользоваться?
Как же нам организовать процесс, определить входные данные, ответственного за шаг и критерии завершения перед изменением кода? Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Записывайте время выполнения и стоимость токенов или запросов рядом с функциональными результатами. Отображение стоимости заранее предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Размещайте состояние рядом с компонентом, ответственным за его изменение. Если всё хранить в глобальном хранилище, будет сложнее обнаруживать ошибки во времени выполнения.
1. Настройка
На этапе настройки 1 необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Конфигурацию следует хранить отдельно от кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Состояние следует хранить рядом с компонентом, ответственным за его изменение. Хранение всего в глобальном хранилище затрудняет выявление ошибок, связанных с временем выполнения.
npm install @tanstack/react-query
const queryClient = new QueryClient();
root.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
2. Получение данных с помощью useQuery
Для этапа «Загрузка данных с фазой» необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Необходимо одновременно задокументировать успешный сценарий работы и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Храните состояние вместе с компонентом, ответственным за его изменение. Хранение всего в глобальном хранилище затрудняет выявление проблем с временем выполнения. Для этапа «Загрузка данных с фазой» необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия элементам данных, определите критерии успеха и не допускайте безусловного частичного завершения работы.
const { data,
isPending,
error } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers
})
2.1. Функция запроса
При работе над этапом 2.1 «Функция запроса» сначала запишите условия использования: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет избежать ошибок при последующих изменениях кода. Рядом с результатами работы запишите время выполнения и стоимость токенов или запросов. Отображение затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды. Рассматривайте эффекты как синхронизацию с внешним миром, а не как замену вычисляемых значений во время отрисовки.
async function fetchUsers() {
const res = await fetch("/api/users")
if (!res.ok) {
throw new Error("Failed to fetch users")
}
return res.json()
}
useQuery({
queryKey: ["users"],
queryFn: fetchUsers
})
2.2. Ключ запроса
При работе над этапом 2 2 Query Key сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой чек-лист поможет сохранять честность при последующих изменениях кода. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Рассматривайте эффекты как синхронизацию с внешним миром, а не как замену вычисляемых значений во время отрисовки.
3. Кэширование
При работе над тремя этапами кэширования сначала запишите контракт: необходимые входные данные, сигнал о успехе и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Документируйте как успешный путь, так и путь восстановления одновременно. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не последующими улучшениями. Рассматривайте эффекты как синхронизацию с внешним миром, а не как замену вычисляемых значений во время отрисовки. При работе над тремя этапами кэширования сначала запишите контракт: необходимые входные данные, сигнал о успехе и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Рассматривайте этот этап как контракт между входными данными и проверенными выходными данными. Дайте названия результатам работы, определите критерии успеха и не допускайте безусловного частичного завершения.
4. staleTime
Этап 4 staleTime работает наилучшим образом, если рассматривать его как измеримую величину. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Старайтесь сохранять низкие затраты на обработку данных и откладывайте дорогостоящие вычисления с использованием мемоизации только после их измерения. Преждевременное применение мемоизации может скрыть ошибки, связанные с устаревшими данными.
useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
staleTime: 60,000 // 60 seconds
})
Зачем нам нужен staletime??
Подход «Почему нам нужны этапы» работает наилучшим образом, когда его рассматривают как измеримую структуру. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объема работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Сделайте процесс отрисовки дешевым и откладывайте затратные вычисления с использованием мемоизации только после их измерения. Преждевременная мемоизация может скрыть ошибки, связанные с устаревшими данными.
Понимание staleTime
Этап Understanding staleTime работает наилучшим образом, когда его рассматривают как измеримую характеристику. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию до расширения объёма работ. Документируйте как успешный, так и восстановительный пути выполнения. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не этапом последующей доработки. Сохраняйте низкую стоимость процесса генерации результатов и откладывайте дорогостоящие вычисления с использованием мемоизации только после их измерения. Преждевременная мемоизация может скрыть ошибки, связанные с устаревшими данными. Этап Understanding staleTime работает наилучшим образом, когда его рассматривают как измеримую характеристику. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию до расширения объёма работ. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия соответствующим элементам, определите критерии успеха и не допускайте молчаливого частичного завершения работы.
User visits page
↓
Fetch users
↓
User navigates away
↓
User comes back
↓
Fetch users again
staleTime: 5 * 60 * 1000
5. gcTime
Для этапа 5 gcTime необходимо заранее определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг, исходя из известной точки контроля, без необходимости угадывать скрытое состояние. Регистрируйте время выполнения, а также стоимость токенов или запросов рядом с функциональными результатами. Отображение стоимости на раннем этапе предотвращает неожиданные счета при переходе от демо-среды к общедоступным средам. Храните состояние вместе с компонентом, ответственным за его изменение. Хранение всего в глобальном хранилище затрудняет выявление ошибок, связанных с временем выполнения.
useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
gcTime: 60000
})
В чём разница между staleTime и gcTime?
На этапе «В чем разница?» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Храните состояние рядом с компонентом, ответственным за его изменение. Хранение всего в глобальном хранилище затрудняет выявление ошибок, связанных с временем выполнения.
6. Перезагрузка данных
На этапе 6 «Повторное загрузчение» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Необходимо задокументировать как успешный, так и восстановительный сценарии работы. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Состояние должно храниться вместе с компонентом, ответственным за его изменение. Хранение всего в глобальном хранилище затрудняет выявление проблем с временем выполнения. На этапе 6 «Повторное загрузчение» необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия элементам, определите критерии успеха и не допускайте безусловного частичного завершения работы.
const { refetch } = useQuery(...)
refetch()
useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
refetchInterval: 30,000
})
7. Мутации — создание, обновление, удаление
При работе над этапом создания и обновления в рамках 7 мутаций сначала запишите условия использования: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Рядом с функциональными результатами записывайте время выполнения и стоимость токенов или запросов. Отображение затрат с самого начала предотвращает неожиданные расходы при переходе с демо-среды в общедоступные среды. Рассматривайте эффекты как синхронизацию с внешним миром, а не как замену вычисляемых значений во время отрисовки.
async function createUser(user) {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(user),
}
);
if (!response.ok) {
throw new Error("Failed to create user");
}
return response.json();
}
import { useMutation } from "@tanstack/react-query";
function CreateUser() {
const mutation = useMutation({
mutationFn: createUser,
});
return (
<button
onClick={() =>
mutation.mutate({
name: "John Doe",
email: "john@example.com",
})
}
disabled={mutation.isPending}
>
{mutation.isPending ? "Creating..." : "Create User"}
</button>
);
}
export default CreateUser;
Button click
↓
mutation.mutate(user)
↓
createUser(user)
↓
POST request
↓
Server
7.1. Состояния мутаций
При работе над этапом 7 1 Mutation States сначала запишите условия работы: необходимые входные данные, сигнал о успешном выполнении и действия при частичной неудаче. Такой список поможет сохранять честность при последующих изменениях кода. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Рассматривайте эффекты как синхронизацию с внешним миром, а не как замену вычисляемых значений во время отрисовки.
const {
mutate,
isPending,
isSuccess,
isError,
error,
data,
} = useMutation({
mutationFn: createUser,
});
<button
onClick={() => mutate({ userName: "delfina ghimire" })}
disabled={isPending}
>
{isPending ? "Creating..." : "Create user"}
</button>
8. Клиент запросов
При работе над 8 этапом Query Client сначала запишите контракт: необходимые входные данные, сигнал о успехе и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Документируйте одновременно успешный сценарий и сценарий восстановления. Повторные попытки, проверки человеком и обработка неработоспособных сообщений являются частью продукта, а не элементами последующей доработки. Рассматривайте эффекты как синхронизацию с внешним миром, а не как замену вычисляемых значений во время отрисовки. При работе над 8 этапом Query Client сначала запишите контракт: необходимые входные данные, сигнал о успехе и действия при частичной неудаче. Такой список помогает сохранять честность при последующих изменениях кода. Рассматривайте этот этап как контракт между входными данными и проверенными выходными данными. Дайте названия результатам работы, определите критерии успеха и не допускайте безусловного частичного завершения.
9. Аннулирование запроса
Этап 9 «Аннулирование запросов» работает наилучшим образом, если рассматривать его как измеримую характеристику. Соберите один эталонный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Записывайте временные показатели, а также стоимость токенов или запросов рядом с функциональными результатами. Отслеживание затрат на раннем этапе предотвращает неожиданные счёты при переходе с демо-среды в общедоступные среды. Старайтесь сохранять низкие затраты на обработку данных и откладывайте дорогостоящие вычисления с использованием мемоизации только после их измерения. Преждевременное применение мемоизации может скрыть ошибки, связанные с устаревшими данными.
[
{ id: 1, userName: "delfina ghimire" },
{ id: 2, userName: "spiderman ghimire" },
]
mutation.mutate({
userName: "ironman ghimire",
});
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["users"],
});
},
});
Create Users (Mutation)
↓
Server data changes
↓
Invalidate ["users"]
↓
Query becomes stale
↓
Refetch
↓
UI gets fresh data
Аннулирование vs. Перезагрузка
Этапы Invalidate и Refetch работают наилучшим образом, если рассматривать их как измеримые компоненты системы. Соберите один эталонный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Храните конфигурацию вне кода приложения. Файлы среды, хранилища секретов и флаги функций должны находиться в одном месте, чтобы операторы могли их проверять, не читая весь код. Сделайте процесс отрисовки простым и откладывайте ресурсоёмкие вычисления с использованием мемоизации только после их измерения. Преждевременное применение мемоизации может скрыть ошибки, связанные с устаревшими данными.
Заключение
Этап Заключения работает наилучшим образом, когда его рассматривают как измеримую область. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Документируйте одновременно успешный и восстановительный пути выполнения. Повторные попытки, проверки человеком и обработка неработающих сообщений являются частью продукта, а не этапом последующей доработки. Сохраняйте низкую стоимость процесса генерации результатов и откладывайте дорогостоящие операции на этап мемоизации только после проведения измерений. Преждевременная мемоизация может скрыть ошибки, связанные с устаревшими данными. Этап Заключения работает наилучшим образом, когда его рассматривают как измеримую область. Соберите один идеальный пример работы, один случай сбоя и записку о возврате к предыдущему состоянию перед расширением объёма работ. Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия создаваемым элементам, определите критерии успеха и не допускайте молчаливого частичного завершения работы.
Кратко
На этапе краткого обзора необходимо определить входные данные, ответственного за выполнение шага и критерии завершения перед внесением изменений в код. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии. Рядом с функциональными результатами следует записывать время выполнения и стоимость токенов или запросов. Отображение затрат с самого начала помогает избежать неожиданных счетов при переходе от демо-среды к общедоступным средам. Состояние следует хранить в том же компоненте, который отвечает за его изменение. Хранение всего в глобальном хранилище затрудняет выявление ошибок, связанных с временем выполнения.
Чек-лист операционной работы
На этапе чек-листа операционной работы также необходимо определить входные данные, ответственного за шаг и критерии завершения перед изменением кода. Операторы должны иметь возможность перезапустить шаг с известной точки контроля, не догадываясь о скрытом состоянии.
Лучше использовать небольшие, тестируемые единицы вместо обширных скриптов. Когда какой-то шаг терпит неудачу, ошибка должна указывать на конкретную ответственность, а не на запутанную цепочку операций.
Размещайте состояние рядом с компонентом, ответственным за его изменение. Перенос всего в глобальный хранилище затрудняет обнаружение ошибок, связанных с временем выполнения операций.
Напишите краткий руководство: как обновлять ключи, как опустошать очередь, как откатить последнюю операцию ввода.
Рассматривайте этот этап как контракт между входными данными и проверенными выходными результатами. Дайте названия элементам, определите критерии успеха и не соглашайтесь на молчаливое частичное выполнение задачи.
Размещайте состояние рядом с компонентом, ответственным за его изменение. Перенос всего в глобальный хранилище затрудняет обнаружение ошибок, связанных с временем выполнения операций.
Перед внедрением стека заморозьте версии, сделайте копию «золотого» отчета для критической цепочки операций и уточните шаги возврата к предыдущему состоянию. В совместных средах необходимы ограничения на частоту запросов, проверки принадлежности пользователя и четко определенный ответственный за обновление секретов. Лучше выбирать надежность, даже если она кажется скучной, чем умные одноразовые демонстрации.
Примечание для 1aa45226c385: не храните ключи поставщика в репозитории, установите лимит токенов на сессию и сохраняйте отчеты рядом с фикстурами для оценки, чтобы последующие замены моделей оставались сопоставимыми.