Головна / Статті / Що таке та як використовувати 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

Для процесу 2 «Завантаження даних зі стадією» необхідно визначити вхідні дані, власника кроку та критерії завершення ще до змін у коді. Оператори мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Необхідно документувати як шлях успішного виконання, так і шлях відновлення. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Стан слід розміщувати разом із компонентом, який керує змінами. Перенесення всього до глобального сховища ускладнює виявлення проблем із таймінгом. Для процесу 2 «Завантаження даних зі стадією» необхідно визначити вхідні дані, власника кроку та критерії завершення ще до змін у коді. Оператори мають мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан. Розглядайте цю стадію як контракт між вхідними даними та перевіреними результатами. Позначте елементи продукту, визначте критерії успіху та не допускайте безповідомного часткового завершення роботи.

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 працюють найкраще, якщо їх розглядати як вимірювану поверхню. Збережіть один ідеальний запис дії, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Тримайте конфігурацію окремо від коду додатку. Файли середовища, сховища конфіденційних даних та флаги функцій мають знаходитися в одному місці, де оператори можуть їх перевіряти, не читаючи весь код. Зробіть процес відображення дешевим та виконуйте складні обчислення лише після застосування мемоїзації після її вимірювання. Надмірна мемоїзація може приховати помилки старих значень параметрів.

Висновок

Етап Висновку працює найкраще, коли його розглядають як вимірювану поверхню. Зафіксуйте один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу роботи. Документуйте як успішний, так і відновлювальний сценарії. Повторні спроби, людський контроль та обробка некоректних повідомлень є частиною продукту, а не етапом подальшої оптимізації. Зберігайте витрати на обробку низькими та відкладайте дорогі операції на пізніший етап лише після вимірювань. Надмірне використання мемоайзерів може приховати баги, пов’язані з застарілими даними. Етап Висновку працює найкраще, коли його розглядають як вимірювану поверхню. Зафіксуйте один ідеальний запис, один випадок збою та примітку про скасування змін перед розширенням обсягу. Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового завершення роботи.

Коротко

На етапі TL DR необхідно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори повинні мати можливість перезапустити крок з відомої точки контролю, не намагаючись вгадати прихований стан.

Чек-лист для експлуатації

На етапі чек-листу для експлуатації також потрібно визначити вхідні дані, власника кроку та критерії завершення перед зміною коду. Оператори мають можливість перезапустити крок з відомої точки контролю, уникаючи необхідності вгадування прихованого стану.

Віддавайте перевагу малим, тестованим одиницям перед величезними скриптами. Коли якийсь крок зазнає невдачі, причина має вказувати на конкретну відповідальність, а не на заплутану послідовність операцій.

Розміщуйте стан разом із компонентом, який керує змінами. Перенесення всього до глобального сховища ускладнює виявлення проблем, пов’язаних із таймінгом.

Напишіть короткий посібник: як змінювати ключі, як спорожнювати чергу, як скасовувати останнє завантаження.

Розглядайте цей етап як контракт між вхідними даними та перевіреними результатами. Позначте всі елементи, визначте критерії успіху та не допускайте мовчазного часткового виконання.

Розміщуйте стан разом із компонентом, який керує змінами. Перенесення всього до глобального сховища ускладнює виявлення проблем, пов’язаних із таймінгом.

Перш ніж запускати стек у продакшн, заморозьте версії, створіть «золотий» запис для критичного шляху та підтвердьте кроки відкату. У спільних середовищах необхідні обмеження на частоту запитів, перевірки прав доступу та чіткий власник для зміни секретів. Віддавайте перевагу надійності перед креативними одноразовими демонстраціями.

Примітка для 1aa45226c385: не зберігайте ключі постачальника у репозиторії, встановіть ліміт токенів на сеанс та зберігайте записи поруч із фікстурами для оцінки, щоб подальша заміна моделей залишалася порівнянною.