Главная / Статьи / Что такое React Query и как им пользоваться в производственных системах?

Что такое React Query и как им пользоваться в производственных системах?

Пошаговое руководство по использованию React Query в производственных системах: контракты, проверки и готовые блоки кода для команд, внедряющих эту паттерн-архитектуру.

2930 слов

В следующих заметках описывается практический подход к изучению темы «Начало работы с 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: не храните ключи поставщика в репозитории, установите лимит токенов на сессию и сохраняйте отчеты рядом с фикстурами для оценки, чтобы последующие замены моделей оставались сопоставимыми.