8
头图

Getting started with react-query

write in front

Since there are few more systematic react-query tutorials in China, the author hopes to write a more comprehensive tutorial based on the official documents and the content of the official courses. This article will use various examples as entry points to explain relevant knowledge points as easily as possible. If there are any mistakes, please point them out in the comment area, and the author will correct them as soon as possible.

Table of contents

Imprint

This tutorial is based on the react-query@4 version, which is currently (2022-06-05) an alpha version.

Online demo based on stackblitz  platform

Start with requesting data from the backend in react

In daily development, it is inevitable to request the back-end interface. When requesting an interface, the following processing is often involved

  • loading state
  • Backend returns data store
  • If there is an error message on the interface, display the error message
  • refresh data
  • and many more

    Let's look at an example that satisfies the above processing
    Click me to see the example ①Online demo

PS: The following code will request a Zen-like (high-level) sentence from github and display it on the page

Example①👇🏻

 import * as React from 'react';

export default function App() {
  // 存储 后端返回数据
  const [zen, setZen] = React.useState('');
  // 存储 加载状态
  const [isLoading, setIsLoading] = React.useState(false);
  // 存储 是否请求成功
  const [isError, setIsError] = React.useState(false);
  // 存储 后端返回的错误数据
  const [errorMessage, setErrorMessage] = React.useState('');

  const fetchData = () => {
    // 开始获取数据,将isLoading置为true
    setIsLoading(true);

    fetch('https://api.github.com/zen')
      .then(async (response) => {
        // 如果请求返回status不为200 则抛出后端错误
        if (response.status !== 200) {
          const { message } = await response.json();

          throw new Error(message);
        }

        return response.text();
      })
      .then((text: string) => {
        // 请求完成将isLoading置为false
        setIsLoading(false);
        // 接口请求成功,将isError置为false
        setIsError(false);
        // 存储后端返回的数据
        setZen(text);
      })
      .catch((error) => {
        // 请求完成将isLoading置为false
        setIsLoading(false);
        // 接口请求错误,将isError置为true
        setIsError(true);
        // 存储后端返回的错误数据
        setErrorMessage(error.message);
      });
  };

  React.useEffect(() => {
    // 初始化请求数据
    fetchData();
  }, []);

  return (
    <div>
      <h1>Zen from Github</h1>
      <p>{isLoading ? '加载中...' : isError ? errorMessage : zen}</p>
      {!isLoading && (
        <button onClick={fetchData}>{isError ? '重试' : '刷新'}</button>
      )}
    </div>
  );
}

In the above example

  • Use isLoading to store 加载状态
  • Use isError  to store 接口是否有错误
  • Use errorMessage  to store 后端返回的报错信息
  • Use zen to store  后端返回数据存储 
  • Recall the fetchData  method to refresh the data

This example is just a request for an interface. If it is a real project, there will definitely be more than one request, so we will write a lot of repetitive code to meet the business needs (inner os: In fact, too much code is written to affect efficiency, so we can't get off work early. (¬_¬), and it is expensive to maintain)_

At this point, the introduction of react-query can reduce the code related to the request interface. The above example is rewritten using react-query as follows:
Click me to see the example ②Online demo
Example ②👇🏻

 import * as React from 'react';
import { useQuery } from 'react-query';

const fetchData = () => {
  return fetch('https://api.github.com/zen').then(async (response) => {
    // 如果请求返回status不为200 则抛出后端错误
    if (response.status !== 200) {
      const { message } = await response.json();

      throw new Error(message);
    }

    return response.text();
  });
};

export default function App() {
  const zenQuery = useQuery(['zen'], fetchData); // ①

  return (
    <div>
      <h1>Zen from Github</h1>
      <p>
        {zenQuery.isLoading || zenQuery.isFetching
          ? '加载中...'
          : zenQuery.isError
          ? zenQuery.error?.message
          : data}
      </p>
      {!zenQuery.isLoading && !zenQuery.isFetching && (
        <button
          onClick={() => {
            zenQuery.refetch();
          }}
        >
          {zenQuery.isError ? '重试' : '刷新'}
        </button>
      )}
    </div>
  );
}

In contrast, after the introduction of react-query, the amount of code visible to the naked eye is decreasing! !

There is no need to write useState to manage the additional state caused by the request interface (if you use state management libraries such as react-redux, mobx, etc., you will also encounter similar problems), and you also do not need to initialize in useEffect(() => {}, []) Call the interface, and react-query will handle it for us.

In the above example, you may not know much about the incoming parameters when introducing the useQuery hook in code ①. Next, we will introduce these incoming parameters.

Query Keys and Query Functions

What are Query Keys and Query Functions?

In the process of daily development, when requesting back-end data:

  • A function will be written first to request the data of the back-end interface (such as the fetchData function in the above example ①)
  • Then specify a variable (such as zen in example ① above) to store the data returned by the relevant backend. The variables of each interface will have different names for different interfaces to identify different data.

So how to distinguish different data obtained by different interfaces in react-query?

Going back to example ②, we use the useQuery hook to get back-end data. The code is as follows:

 const zenQuery = useQuery(['zen'], fetchData);
  • Wherein ['zen']  is the query key of react-query, react-query identifies (maps) the data returned by different interfaces (or requests with different parameters of the same interface) through different query keys. In react-query@4 the query key must be an array.
  • And fetchData is the function that we request the back-end interface, that is, the query function.

PS: The elements inside the query key can be nested arrays, objects, strings, numbers

For example: ['zen', { form: 'confucius' }] or ['zen', ['confucius', 'Lao Tzu']]

In order to facilitate memory, for example, you can regard the query key as the key when you store localStorage , and the value is to store the various state data we need after querying the data through the query function enter value

PS: Of course, the actual processing process and stored information will be very complicated, but the ideas are basically the same.

Some tips for writing query keys

After explaining the query key and query function, the author hopes that everyone will consider a question. This interface is relatively simple, so we can use zen as the query key. If I have a complex interface, how should I change it? What about good design query keys?

Taking the github interface as an example, if you want to get the issue list of a warehouse in github, you can call the interface like this

 https://api.github.com/repos/{owner}/{repo}/issues

Specifically, if you want to get the issue list of the react repository, you can call the following interface, you can open it in the browser and try it:

 https://api.github.com/repos/facebook/react/issues

At this point, you can get the issue list in the react repository through the request interface.

Take this interface for getting the warehouse issue list as an example, you can write the query key example like this ③👇🏻

 ['issues', owner, repo]

In this query key we follow a principle: from general to special

First of all, the data type we get is issue, we need to put a string at the beginning of the array to identify the data type, so we set the first parameter to issues . There are many repositories in github. These repositories usually use the user as the first-level identification, and the warehouse name is the second-level identification, as shown in the following figure
react仓库

So the second and third arguments are owner and repo in that order.

In the above example, the reason we did not use ['issues', 'facebook', 'react'] but used ['issues', owner, repo] is to introduce in react-query, when a variable is used as the element of the query key, when the value of the variable changes After that, react-query will call the fetchData method again to obtain new data and cache it in the cache with the corresponding variable value as key.

That is, when the following changes occur, react-query will call the fetchData method again, and cache the data obtained from the backend in the value corresponding to the query key ['issues', 'vuejs', 'vue'] , Similarly, when we initialize the calling interface, the obtained data is cached in the corresponding value of the query key ['issues', 'facebook', 'react'] :

 ['issues', 'facebook', 'react'] -> ['issues', 'vuejs', 'vue'] // 从查询react仓库的issue,变更为查询vue仓库的issue

The following example will get the latest issue in the react repository, you can view the online demo of example ④

Change the input box in the example: facebook to vuejs, react to vue, click the [View latest issue information] button, you can see the latest issue information of the vue warehouse (for the relevant data cache, you can think about the above example we said)

Click me to see the example ④Online demo

Example ④👇🏻

 import * as React from 'react';
import { useQuery } from 'react-query';

const fetchData = ({ queryKey }) => {
  const [, owner, repo] = queryKey;

  return fetch(`https://api.github.com/repos/${owner}/${repo}/issues`, {
    headers: {
      Authorization: '',
    },
  }).then(async (response) => {
    // 如果请求返回status不为200 则抛出后端错误
    if (response.status !== 200) {
      const { message } = await response.json();

      throw new Error(message);
    }

    return response.json();
  });
};

export default function App() {
  const [inputOwner, setInputOwner] = React.useState('facebook');
  const [inputRepo, setInputRepo] = React.useState('react');
  const [queryKey, setQueryKey] = React.useState([inputOwner, inputRepo]);
  const issueQuery = useQuery(['issues', ...queryKey], fetchData);

  return (
    <div>
      <span>仓库:</span>
      <input
        name={'owner'}
        value={inputOwner}
        onChange={(e) => setInputOwner(e.target.value)}
      />
      /
      <input
        name={'repo'}
        value={inputRepo}
        onChange={(e) => setInputRepo(e.target.value)}
      />
      <button
        onClick={() => {
          setQueryKey([inputOwner, inputRepo]);
        }}
      >
        查看最新issue信息
      </button>
      <div>
        <h1>
          仓库{queryKey[0]}/{queryKey[1]}最新一条issue信息
        </h1>
        <p>
          {issueQuery.isLoading
            ? '加载中...'
            : issueQuery.isError
            ? issueQuery.message
            : JSON.stringify(issueQuery.data[0])}
        </p>
      </div>
    </div>
  );
}

In this example, when the value of the query key variable changes, react-query will automatically request the corresponding data after the change, and in the parameters passed in by the query function, we can also get the value of the query key when calling the query function .

You can view the cache information of react-query in DevTool to help you understand:

使用DevTool

缓存信息


修仙大橙子
108 声望17 粉丝

前端工程师