导航菜单

  • 0.api
  • 0.Async
  • 0.module
  • 1.ES2015
  • 2.Promise
  • 3.Node
  • 4.NodeInstall
  • 5.REPL
  • 6.NodeCore
  • 7.module&NPM
  • 8.Encoding
  • 9.Buffer
  • 10.fs
  • 11.Stream-1
  • 11.Stream-2
  • 11.Stream-3
  • 11.Stream-4
  • 12-Network-2
  • 12.NetWork-3
  • 12.Network-1
  • 13.tcp
  • 14.http-1
  • 14.http-2
  • 15.compress
  • 16.crypto
  • 17.process
  • 18.yargs
  • 19.cache
  • 20.action
  • 21.https
  • 22.cookie
  • 23.session
  • 24.express-1
  • 24.express-2
  • 24.express-3
  • 24.express-4
  • 25.koa-1
  • 26.webpack-1-basic
  • 26.webpack-2-optimize
  • 26.webpack-3-file
  • 26.webpack-4.tapable
  • 26.webpack-5-AST
  • 26.webpack-6-sources
  • 26.webpack-7-loader
  • 26.webpack-8-plugin
  • 26.webpack-9-hand
  • 26.webpack-10-prepare
  • 28.redux
  • 28.redux-jwt-back
  • 28.redux-jwt-front
  • 29.mongodb-1
  • 29.mongodb-2
  • 29.mongodb-3
  • 29.mongodb-4
  • 29.mongodb-5
  • 29.mongodb-6
  • 30.cms-1-mysql
  • 30.cms-2-mysql
  • 30.cms-3-mysql
  • 30.cms-4-nunjucks
  • 30.cms-5-mock
  • 30.cms-6-egg
  • 30.cms-7-api
  • 30.cms-8-roadhog
  • 30.cms-9-yaml
  • 30.cms-10-umi
  • 30.cms-12-dva
  • 30.cms-13-dva-ant
  • 30.cms-14-front
  • 30.cms-15-deploy
  • 31.dva
  • 31.cms-13-dva-antdesign
  • 33.redis
  • 34.unittest
  • 35.jwt
  • 36.websocket-1
  • 36.websocket-2
  • 38.chat-api-1
  • 38.chat-api-2
  • 38.chat-3
  • 38.chat-api-3
  • 38.chat
  • 38.chat2
  • 38.chat2
  • 39.crawl-0
  • 39.crawl-1
  • 39.crawl-2
  • 40.deploy
  • 41.safe
  • 42.test
  • 43.nginx
  • 44.enzyme
  • 45.docker
  • 46.elastic
  • 47.oauth
  • 48.wxpay
  • index
  • 52.UML
  • 53.design
  • index
  • 54.linux
  • 57.ts
  • 56.react-ssr
  • 58.ts_react
  • 59.ketang
  • 59.ketang2
  • 61.1.devops-linux
  • 61.2.devops-vi
  • 61.3.devops-user
  • 61.4.devops-auth
  • 61.5.devops-shell
  • 61.6.devops-install
  • 61.7.devops-system
  • 61.8.devops-service
  • 61.9.devops-network
  • 61.10.devops-nginx
  • 61.11.devops-docker
  • 61.12.devops-jekins
  • 61.13.devops-groovy
  • 61.14.devops-php
  • 61.15.devops-java
  • 61.16.devops-node
  • 61.17.devops-k8s
  • 62.1.react-basic
  • 62.2.react-state
  • 62.3.react-high
  • 62.4.react-optimize
  • 62.5.react-hooks
  • 62.6.react-immutable
  • 62.7.react-mobx
  • 62.8.react-source
  • 63.1.redux
  • 63.2.redux-middleware
  • 63.3.redux-hooks
  • 63.4.redux-saga
  • 63.5.redux-saga-hand
  • 64.1.router
  • 64.2.router-connected
  • 65.1.typescript
  • 65.2.typescript
  • 65.3.typescript
  • 65.4.antd
  • 65.4.definition
  • 66-1.vue-base
  • 66-2.vue-component
  • 66-3.vue-cli3.0
  • 66-4.$message组件
  • 66-5.Form组件
  • 66-6.tree
  • 66-7.vue-router-apply
  • 66-8.axios-apply
  • 66-9.vuex-apply
  • 66-10.jwt-vue
  • 66-11.vue-ssr
  • 66-12.nuxt-apply
  • 66-13.pwa
  • 66-14.vue单元测试
  • 66-15.权限校验
  • 67-1-network
  • 68-2-wireshark
  • 7.npm2
  • 69-hooks
  • 70-deploy
  • 71-hmr
  • 72.deploy
  • 73.import
  • 74.mobile
  • 75.webpack-1.文件分析
  • 75.webpack-2.loader
  • 75.webpack-3.源码流程
  • 75.webpack-4.tapable
  • 75.webpack-5.prepare
  • 75.webpack-6.resolve
  • 75.webpack-7.loader
  • 75.webpack-8.module
  • 75.webpack-9.chunk
  • 75.webpack-10.asset
  • 75.webpack-11.实现
  • 76.react_optimize
  • 77.ts_ketang_back
  • 77.ts_ketang_front
  • 78.vue-domdiff
  • 79.grammar
  • 80.tree
  • 81.axios
  • 82.1.react
  • 82.2.react-high
  • 82.3.react-router
  • 82.4.redux
  • 82.5.redux_middleware
  • 82.6.connected
  • 82.7.saga
  • 82.8.dva
  • 82.8.dva-source
  • 82.9.roadhog
  • 82.10.umi
  • 82.11.antdesign
  • 82.12.ketang-front
  • 82.12.ketang-back
  • 83.upload
  • 84.graphql
  • 85.antpro
  • 86.1.uml
  • 86.2.design
  • 87.postcss
  • 88.react16-1
  • 89.nextjs
  • 90.react-test
  • 91.react-ts
  • 92.rbac
  • 93.tsnode
  • 94.1.JavaScript
  • 94.2.JavaScript
  • 94.3.MODULE
  • 94.4.EventLoop
  • 94.5.文件上传
  • 94.6.https
  • 94.7. nginx
  • 95.1. react
  • 95.2.react
  • 96.1.react16
  • 96.2.fiber
  • 96.3.fiber
  • 97.serverless
  • 98.websocket
  • 100.1.react-basic
  • 101.1.monitor
  • 101.2.monitor
  • 102.java
  • 103.1.webpack-usage
  • 103.2.webpack-bundle
  • 103.3.webpack-ast
  • 103.4.webpack-flow
  • 103.5.webpack-loader
  • 103.6.webpack-tapable
  • 103.7.webpack-plugin
  • 103.8.webpack-optimize1
  • 103.9.webpack-optimize2
  • 103.10.webpack-hand
  • 103.11.webpack-hmr
  • 103.11.webpack5
  • 103.13.splitChunks
  • 103.14.webpack-sourcemap
  • 103.15.webpack-compiler1
  • 103.15.webpack-compiler2
  • 103.16.rollup.1
  • 103.16.rollup.2
  • 103.16.rollup.3
  • 103.16.vite.basic
  • 103.16.vite.source
  • 103.16.vite.plugin
  • 103.16.vite.1
  • 103.16.vite.2
  • 103.17.polyfill
  • 104.1.binary
  • 104.2.binary
  • 105.skeleton
  • 106.1.react
  • 106.2.react_hooks
  • 106.3.react_router
  • 106.4.redux
  • 106.5.redux_middleware
  • 106.6.connected-react-router
  • 106.6.redux-first-history
  • 106.7.redux-saga
  • 106.8.dva
  • 106.9.umi
  • 106.10.ketang
  • 106.11.antdesign
  • 106.12.antpro
  • 106.13.router-6
  • 106.14.ssr
  • 106.15.nextjs
  • 106.16.1.cms
  • 106.16.2.cms
  • 106.16.3.cms
  • 106.16.4.cms
  • 106.16.mobx
  • 106.17.fomily
  • 107.fiber
  • 108.http
  • 109.1.webpack_usage
  • 109.2.webpack_source
  • 109.3.dll
  • 110.nest.js
  • 111.xstate
  • 112.Form
  • 113.redux-saga
  • 114.react+typescript
  • 115.immer
  • 116.pro5
  • 117.css-loader
  • 118.1.umi-core
  • 119.2.module-federation
  • 119.1.module-federation
  • 120.create-react-app
  • 121.react-scripts
  • 122.react-optimize
  • 123.jsx-runtime
  • 124.next.js
  • 125.1.linux
  • 125.2.linux-vi
  • 125.3.linux-user
  • 125.4.linux-auth
  • 125.5.linux-shell
  • 125.6.linux-install
  • 125.7.linux-system
  • 125.8.linux-service
  • 125.9.linux-network
  • 125.10.nginx
  • 125.11.docker
  • 125.12.ci
  • 125.13.k8s
  • 125.14.k8s
  • 125.15.k8s
  • 125.16.k8s
  • 126.11.react-1
  • 126.12.react-2
  • 126.12.react-3
  • 126.12.react-4
  • 126.12.react-5
  • 126.12.react-6
  • 126.12.react-7
  • 126.12.react-8
  • 127.frontend
  • 128.rollup
  • 129.px2rem-loader
  • 130.health
  • 131.hooks
  • 132.keepalive
  • 133.vue-cli
  • 134.react18
  • 134.2.react18
  • 134.3.react18
  • 135.function
  • 136.toolkit
  • 137.lerna
  • 138.create-vite
  • 139.cli
  • 140.antd
  • 141.react-dnd
  • 142.1.link
  • 143.1.gulp
  • 143.2.stream
  • 143.3.gulp
  • 144.1.closure
  • 144.2.v8
  • 144.3.gc
  • 145.react-router-v6
  • 146.browser
  • 147.lighthouse
  • 148.1.basic
  • 148.2.basic
  • 148.3.basic
  • 148.4.basic
  • 148.5.basic
  • 149.1.vite
  • 149.2.vite
  • 149.3.vite
  • 149.4.vite
  • 150.react-window
  • 151.react-query
  • 152.useRequest
  • 153.transition
  • 154.emotion
  • 155.1.formily
  • 155.2.formily
  • 155.3.formily
  • 155.3.1.mobx.usage
  • 155.3.2.mobx.source
  • 156.vue-loader
  • 103.11.mf
  • 157.1.react18
  • 158.umi4
  • 159.rxjs
  • 159.rxjs2
  • 160.bff
  • 161.zustand
  • 162.vscode
  • 163.emp
  • 164.cors
  • 1.React Query介绍
  • 2. 安装
  • 3.基本查询
    • 3.1 src\index.js
    • 3.2 src\App.js
    • 3.3 src\request.js
    • 3.4 api.js
  • 4.加载状态
    • 4.1 状态
    • 4.2 src\App.js
  • 5.开发工具
    • 5.1 src\App.js
  • 6.refetchOnWindowFocus
    • 6.1 src\App.js
  • 7.staleTime
    • 7.1 查询状态
    • 7.2 src\App.js
  • 8.cacheTime
    • 8.1 src\App.js
  • 9.轮询
    • 9.1 App.js
    • 9.2 api.js
  • 10.查询键去重
    • 10.1 App.js
  • 11.自定义hooks
    • 11.1 App.js
  • 12.QueryObserver
    • 12.1 App.js
  • 13.组合缓存键
    • 13.1 App.js
    • 13.2 api.js
  • 14.enabled
    • 14.1 查询状态
    • 14.2 App.js
  • 15.retry
    • 15.1 App.js
    • 15.2 api.js
  • 16.取消请求
    • 16.1 App.js
    • 16.2 request.js
    • 16.3 api.js
  • 17.依赖查询
    • 17.1 App.js
    • 17.2 api.js
  • 18.初始化数据
    • 18.1 App.js
  • 19.并发查询
    • 19.1 App.js
    • 19.2 api.js
  • 20.列表和详情
    • 20.1 App.js
  • 21.读取查询缓存
    • 21.1 App.js
  • 22.预缓存
    • 22.1 App.js
  • 23.副作用
    • 23.1 App.js
  • 23.预查询
    • 23.1 App.js
  • 24.变更操作
    • 24.1 src\App.js
    • 24.2 api.js
  • 25.乐观更新
    • 25.1 src\App.js
    • 25.2 api.js
  • 26.失败回滚
    • 26.1 src\App.js
  • 27.分页查询
    • 27.1 src\App.js
    • 27.2 api.js
  • 28.无限分页
    • 28.1 src\App.js

1.React Query介绍 #

  • react-query基于Hooks,能够智能管理请求的一切内容,包括数据、状态、缓存,更新等
  • 管理请求
    • 基本上axios等请求库封装,可以实现请求、轮询、失败重试、无限加载等功能
    • 可以在网络重连、窗口获得焦点等时机等向服务器发送请求同步状态
  • 状态管理
    • 可以把服务器端的状态缓存在客户端的内存中,从而让任意组件获取这些状态
  • comparison

2. 安装 #

npm install react-query axios --save
npm install express cors morgan --save

3.基本查询 #

  • React Query会在内存中缓存状态
  • 可以通过QueryClientProvider把QueryClient实例传递给下层组件
  • useQuery

3.1 src\index.js #

src\index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { QueryClient, QueryClientProvider } from 'react-query';
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <QueryClientProvider client={queryClient}>
    <App />
  </QueryClientProvider>);

3.2 src\App.js #

src\App.js

import { useQuery } from 'react-query';
import request from './request';
function App() {
  const { data } = useQuery('users', () => request.get('/users'))
  return (
    (<ul>
      {
        data?.map((user) => <li key={user.id}>{user.name}</li>)
      }
    </ul>)
  )
}
export default App;

3.3 src\request.js #

src\request.js

import axios from 'axios';
axios.interceptors.response.use(response => response.data);
axios.defaults.baseURL = 'http://localhost:8080';
export default axios;

3.4 api.js #

const express = require('express');
const cors = require('cors');
const logger = require('morgan');
const app = express();
app.use(express.json());
app.use(logger('dev'));
app.use(cors({
  allowedHeaders: ["Content-Type"],
  allowMethods: ["GET", 'POST', "PUT", "DELETE", "OPTIONS"]
}));
const users=new Array(10).fill(true).map((item, index) => ({ id: String(index + 1), name: `name${index + 1}` }))
app.use((req, res, next) => {
  setTimeout(next, 1000);
});
app.get('/users', (req, res) => {
  res.json(users);
});
app.listen(8080, () => console.log('started on port 8080'));

4.加载状态 #

  • useQuery

4.1 状态 #

字段 含义 取值
status 状态 loading、error、success
isLoading 是否首次加载中 true、false
isError 是否获取失败
isSuccess 是否获取成功 true、false

4.2 src\App.js #

src\App.js

import { useQuery } from 'react-query';
import request from './request';
function App() {
+  const { data, isLoading, isError } = useQuery('users', () => {
+   throw new Error('用户列表加载失败!');
+   return request.get('/users');
  })
+ if (isLoading) return <div>加载中.......</div>
+ if (isError) return <div>加载失败</div>
  return (
    (<ul>
      {
        data?.map((user) => <li key={user.id}>{user.name}</li>)
      }
    </ul>)
  )
}
export default App;

5.开发工具 #

  • devtools是React Query带有专用的开发工具,它们有助于可视化React Query的所有内部工作

5.1 src\App.js #

src\App.js

import { useQuery } from 'react-query';
+import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
+function Users() {
+  const { data, isLoading, isError } = useQuery('users', () => request.get('/users'))
+  if (isLoading) return <div>加载中.......</div>
+  if (isError) return <div>加载失败</div>
+  return (
+    (<ul>
+      {
+        data?.map((user) => <li key={user.id}>{user.name}</li>)
+      }
+    </ul>)
+  )
+}
function App() {
  return (
+   <>
+     <Users />
+     <ReactQueryDevtools initialIsOpen={true} />
+   </>
  )
}
export default App;

6.refetchOnWindowFocus #

  • refetchOnWindowFocus
字段 含义 取值
isFetching 是否正在请求 true、false

6.1 src\App.js #

src\App.js

import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function Users() {
+ const { data, isLoading, isError, isFetching } = useQuery('users', () => request.get('/users'), {
+   refetchOnWindowFocus: true
  })
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>加载失败</div>
  return (
    (
      <>
        <ul>
          {
            data?.map((user) => <li key={user.id}>{user.name}</li>)
          }
        </ul>
+       {isFetching && <div>更在更新数据...</div>}
      </>
    )
  )
}
function App() {
  return (
    <>
      <Users />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

7.staleTime #

  • staleTime可以理解为数据保质期,在保质期内遇到同 key 的请求,不会去再次获取数据,也就是从缓存中取,瞬间切换展示,isFetching 也一直为 false

7.1 查询状态 #

字段 含义 取值
isStale 是否已经过期 0 立刻过期,Infinity 永不过期

7.2 src\App.js #

src\App.js

import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function Users() {
  const { data, isLoading, isError, isFetching } = useQuery('users', () => request.get('/users'), {
    refetchOnWindowFocus: true,
+   staleTime: 3000
  })
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>加载失败</div>
  return (
    (
      <>
        <ul>
          {
            data?.map((user) => <li key={user.id}>{user.name}</li>)
          }
        </ul>
        {isFetching && <div>更在更新数据...</div>}
      </>
    )
  )
}
function App() {
  return (
    <>
      <Users />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

8.cacheTime #

  • cacheTime是指未使用/非活动缓存数据保留在内存中的时间(以毫秒为单位)
  • 当查询的缓存变得未使用或不活动时,该缓存数据将在此持续时间后被垃圾收集。当指定不同的缓存时间时,将使用最长的一个

8.1 src\App.js #

src\App.js

import { useState } from 'react';
import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function Users() {
  const { data, isLoading, isError, isFetching } = useQuery('users', () => request.get('/users'), {
    refetchOnWindowFocus: true,
+   staleTime: Infinity,
+   cacheTime: 5000
  })
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>加载失败</div>
  return (
    (
      <>
        <ul>
          {
            data?.map((user) => <li key={user.id}>{user.name}</li>)
          }
        </ul>
        {isFetching && <div>更在更新数据...</div>}
      </>
    )
  )
}
function App() {
+ const [show, setShow] = useState(true);
  return (
    <>
+     <button onClick={() => setShow(!show)}>{show ? '隐藏' : '显示'}</button>
+     {show && <Users />}
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

9.轮询 #

9.1 App.js #

src\App.js

import { useState } from 'react';
import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function Users() {
  const { data, isLoading, isError, isFetching } = useQuery('users', () => request.get('/users'), {
    refetchOnWindowFocus: true,
    staleTime: Infinity,
    cacheTime: 5000,
+   refetchInterval: 1000
  })
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>加载失败</div>
  return (
    (
      <>
        <ul>
          {
            data?.map((user) => <li key={user.id}>{user.name}</li>)
          }
        </ul>
        {isFetching && <div>更在更新数据...</div>}
      </>
    )
  )
}
function App() {
  const [show, setShow] = useState(true);
  return (
    <>
      <button onClick={() => setShow(!show)}>{show ? '隐藏' : '显示'}</button>
      {show && <Users />}
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

9.2 api.js #

api.js

const express = require('express');
const cors = require('cors');
const logger = require('morgan');
const app = express();
app.use(express.json());
app.use(cors({
  allowedHeaders: ["Content-Type"],
  allowMethods: ["GET", 'POST', "PUT", "DELETE", "OPTIONS"]
}));
app.use(logger('dev'));
const users = new Array(10).fill(true).map((item, index) => ({ id: String(index + 1), name: `name${index + 1}` }))
app.use((req, res, next) => {
  setTimeout(next, 1000);
});
app.get('/users', (req, res) => {
   res.json(users.map(user => ({ ...user, name: user.name + '#' + new Date().toLocaleString() })));
});
app.listen(8080, () => console.log('started on port 8080'));

10.查询键去重 #

  • Deduping

10.1 App.js #

src\App.js

import { useState } from 'react';
import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
+function Users({ queryKey }) {
+ const { data, isLoading, isError, isFetching } = useQuery(queryKey, () => request.get('/users'))
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>加载失败</div>
  return (
    (
      <>
        <ul>
          {
            data?.map((user) => <li key={user.id}>{user.name}</li>)
          }
        </ul>
        {isFetching && <div>更在更新数据...</div>}
      </>
    )
  )
}
function App() {
  return (
    <>
+     <Users queryKey="users" />
+     <Users queryKey="users" />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

11.自定义hooks #

  • Deduping

11.1 App.js #

src\App.js

import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
+function useUsers() {
+  return useQuery('users', () => request.get('/users'));
+}
+function Stats() {
+  const { data } = useUsers();
+  return data && <h1>共计{data.length}用户</h1>
+}
function Users() {
+ const { data, isLoading, isError, isFetching } = useUsers();
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>加载失败</div>
  return (
    (
      <>
        <ul>
          {
            data?.map((user) => <li key={user.id}>{user.name}</li>)
          }
        </ul>
        {isFetching && <div>更在更新数据...</div>}
      </>
    )
  )
}
function App() {
  return (
    <>
      <Users />
+     <Stats />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

12.QueryObserver #

  • QueryObserver可实现在任意组件中订阅状态

12.1 App.js #

src\App.js

import { useEffect, useState } from 'react';
+import { useQuery, QueryObserver, useQueryClient } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function Stats() {
+ const [data, setData] = useState();
+ const queryClient = useQueryClient();
+ useEffect(() => {
+   const observer = new QueryObserver(queryClient, { queryKey: 'users' })
+   const unsubscribe = observer.subscribe(result => setData(result.data))
+   return unsubscribe;
+ }, []);
  return data && <h1>共计{data.length}用户</h1>
}
function Users() {
+ const { data, isLoading, isError, isFetching } = useQuery('users', () => request.get('/users'))
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>加载失败</div>
  return (
    (
      <>
        <ul>
          {
            data?.map((user) => <li key={user.id}>{user.name}</li>)
          }
        </ul>
        {isFetching && <div>更在更新数据...</div>}
      </>
    )
  )
}
function App() {
  return (
    <>
      <Users />
      <Stats />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

13.组合缓存键 #

13.1 App.js #

src\App.js

import React from 'react';
import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
+function User({ userId }) {
+  const { data, isLoading, isError, error, isFetching } = useQuery(['user', userId], () => request.get('/user', {
+    params: {
+      userId
+    }
+  }))
+  if (isLoading) return <div>加载中.......</div>
+  if (isError) return <div>{error.message}</div>
+  return (
+    (
+      <>
+        {data.id ? <p>{data.id}:{data.name}</p> : <p>{userId}对应的用户不存在</p>}
+        {isFetching && <div>更在更新数据...</div>}
+      </>
+    )
+  )
+}
function App() {
+ const [userId, setUserId] = React.useState('');
  return (
    <>
+     <input value={userId} onChange={(event) => setUserId(event.target.value)} />
+     <User userId={userId} />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

13.2 api.js #

api.js

const express = require('express');
const cors = require('cors');
const logger = require('morgan');
const app = express();
app.use(express.json());
app.use(cors({
  allowedHeaders: ["Content-Type"],
  allowMethods: ["GET", 'POST', "PUT", "DELETE", "OPTIONS"]
}));
app.use(logger('dev'));
const users = new Array(10).fill(true).map((item, index) => ({ id: String(index + 1), name: `name${index + 1}` }))
app.use((req, res, next) => {
  setTimeout(next, 1000);
});
app.get('/users', (req, res) => {
  res.json(users.map(user => ({ ...user, name: user.name + '#' + new Date().toLocaleString() })));
});
+app.get('/user', (req, res) => {
+  const userId = req.query.userId;
+  const user = users.find(user => user.id === userId);
+  if (user)
+    res.json(user);
+  else
+    res.json({})
+});
app.listen(8080, () => console.log('started on port 8080'));

14.enabled #

14.1 查询状态 #

字段 含义 取值
isIdle 是否空闲 true、false

14.2 App.js #

src\App.js

import React from 'react';
import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';

function User({ userId }) {
  const { data, isLoading, isError, error, isFetching, isIdle } = useQuery(['user', userId], () => request.get('/user', {
    params: {
      userId
    }
+ }), { enabled: !!userId })
  if (isIdle) return null;
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>{error.message}</div>
  return (
    (
      <>
        {data.id ? <p>{data.id}:{data.name}</p> : <p>{userId}对应的用户不存在</p>}
        {isFetching && <div>更在更新数据...</div>}
      </>
    )
  )
}
function App() {
  const [userId, setUserId] = React.useState('');
  return (
    <>
      <input value={userId} onChange={(event) => setUserId(event.target.value)} />
      <User userId={userId} />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

15.retry #

15.1 App.js #

src\App.js

import React from 'react';
import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function User({ userId }) {
  const { data, isLoading, isError, error, isFetching, isIdle } = useQuery(['user', userId], () => request.get('/user', {
    params: {
      userId
    }
  }), {
    enabled: !!userId,
+   retry: 1,
+   retryDelay: 1000,
+   retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
  })
  if (isIdle) return null;
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>{error.message}</div>
  return (
    (
      <>
        {data.id ? <p>{data.id}:{data.name}</p> : <p>{userId}对应的用户不存在</p>}
        {isFetching && <div>更在更新数据...</div>}
      </>
    )
  )
}
function App() {
  const [userId, setUserId] = React.useState('');
  return (
    <>
      <input value={userId} onChange={(event) => setUserId(event.target.value)} />
      <User userId={userId} />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;
`

15.2 api.js #

api.js

const express = require('express');
const cors = require('cors');
const logger = require('morgan');
const app = express();
app.use(express.json());
app.use(cors({
  allowedHeaders: ["Content-Type"],
  allowMethods: ["GET", 'POST', "PUT", "DELETE", "OPTIONS"]
}));
app.use(logger('dev'));
const users = new Array(10).fill(true).map((item, index) => ({ id: String(index + 1), name: `name${index + 1}` }))
app.use((req, res, next) => {
  setTimeout(next, 1000);
});
app.get('/users', (req, res) => {
  res.json(users.map(user => ({ ...user, name: user.name + '#' + new Date().toLocaleString() })));
});
app.get('/user', (req, res) => {
  const userId = req.query.userId;
  const user = users.find(user => user.id === userId);
  if (user)
    res.json(user);
  else
+   res.sendStatus(404)
});
app.listen(8080, () => console.log('started on port 8080'));

16.取消请求 #

  • axios发送请求时,添加cancelToken配置项可以用于取消请求
  • CancelToken有一个source静态方法,调用之后返回一个对象,该对象包含一个token属性,用于标记请求和一个cancel方法,用于取消请求
  • cancel取消请求方法在调用取消请求的时候可以将取消原因message字符串传递进去,这样请求在被取消之后会被catch捕获,你可以在这里将取消原因打印出来或者提示给用户,比如提示用户不要频繁点击发送请求
  • get的cancelToken放置在第二个参数的对象里面,post的cancelToken放置在第三个参数对象里面

16.1 App.js #

src\App.js

import React from 'react';
import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
+import request, { CancelToken } from './request';

function User({ userId }) {
  const { data, isLoading, isError, error, isFetching, isIdle } = useQuery(['user', userId], () => {
+   const source = CancelToken.source();
+   const promise = request.get('/user', {
+     params: { userId }, cancelToken: source.token
+   }).catch(error => {
+     if (request.isCancel(error)) {
+       console.log(error.message);
+     }
+   });
+   promise.cancel = () => source.cancel('请求被React Query取消');
+   return promise;
  }, {
    enabled: !!userId,
    retry: 3,
    retryDelay: 1000,
    retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
  })
  if (isIdle) return null;
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>{error.message}</div>
  return (
    (
      <>
        {data.id ? <p>{data.id}:{data.name}</p> : <p>{userId}对应的用户不存在</p>}
        {isFetching && <div>更在更新数据...</div>}
      </>
    )
  )
}
function App() {
  const [userId, setUserId] = React.useState('');
  return (
    <>
      <input value={userId} onChange={(event) => setUserId(event.target.value)} />
      <User userId={userId} />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

16.2 request.js #

src\request.js

+import axios, { CancelToken } from 'axios';
axios.interceptors.response.use(response => response.data);
axios.defaults.baseURL = 'http://localhost:8080';
export default axios;
+export { CancelToken }

16.3 api.js #

api.js

const express = require('express');
const cors = require('cors');
const logger = require('morgan');
const app = express();
app.use(express.json());
app.use(cors({
  allowedHeaders: ["Content-Type"],
  allowMethods: ["GET", 'POST', "PUT", "DELETE", "OPTIONS"]
}));
app.use(logger('dev'));
const users = new Array(10).fill(true).map((item, index) => ({ id: String(index + 1), name: `name${index + 1}` }))
app.use((req, res, next) => {
+ setTimeout(next, 1000 * req.query.userId);
});
app.get('/users', (req, res) => {
  res.json(users.map(user => ({ ...user, name: user.name + '#' + new Date().toLocaleString() })));
});
app.get('/user', (req, res) => {
  const userId = req.query.userId;
  const user = users.find(user => user.id === userId);
  if (user)
    res.json(user);
  else
    res.sendStatus(404)
});
app.listen(8080, () => console.log('started on port 8080'));

17.依赖查询 #

17.1 App.js #

src\App.js

import React from 'react';
import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request, { CancelToken } from './request';

function User({ userId }) {
  const { data, isLoading, isError, error, isFetching, isIdle } = useQuery(['user', userId], () => {
    const source = CancelToken.source();
    const promise = request.get('/user', {
      params: { userId }, cancelToken: source.token
    }).catch(error => {
      if (request.isCancel(error)) {
        console.log(error.message);
      }
    });
    promise.cancel = () => source.cancel('请求被React Query取消');
    return promise;
  }, {
    enabled: !!userId,
    retry: 3,
    retryDelay: 1000,
    retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
  })
+ const postsResult = useQuery(['posts', data?.id],
+   () => request.get(`/posts`, { params: { userId: data?.id } }),
+   {
+     enabled: !!(data?.id)
+   });
  if (isIdle) return null;
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>{error.message}</div>
  return (
    (
      <>
        {data.id ? <p>{data.id}:{data.name}</p> : <p>{userId}对应的用户不存在</p>}
        {isFetching && <div>更在更新数据...</div>}
+       {postsResult.data && <p>帖子数:{postsResult.data.length}</p>}
+       <p>{postsResult.isFetching && '正在更新贴子数据...'}</p>
      </>
    )
  )
}
function App() {
  const [userId, setUserId] = React.useState('');
  return (
    <>
      <input value={userId} onChange={(event) => setUserId(event.target.value)} />
      <User userId={userId} />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

17.2 api.js #

const express = require('express');
const cors = require('cors');
const logger = require('morgan');
const app = express();
app.use(express.json());
app.use(cors({
  allowedHeaders: ["Content-Type"],
  allowMethods: ["GET", 'POST', "PUT", "DELETE", "OPTIONS"]
}));
app.use(logger('dev'));
const users = new Array(10).fill(true).map((item, index) => ({ id: String(index + 1), name: `name${index + 1}` }))
+const posts = new Array(10).fill(true).map((user, index) => ({ id: String(index + 1), title: `title${index + 1}`, userId: String(index + 1) }))
app.use((req, res, next) => {
  setTimeout(next, 1000 * req.query.userId);
});
app.get('/users', (req, res) => {
  res.json(users.map(user => ({ ...user, name: user.name + '#' + new Date().toLocaleString() })));
});
app.get('/user', (req, res) => {
  const userId = req.query.userId;
  const user = users.find(user => user.id === userId);
  if (user)
    res.json(user);
  else
    res.sendStatus(404)
});
+app.get('/posts', (req, res) => {
+  const userId = req.query.userId;
+  res.json(posts.filter(post => post.userId === userId));
+});
app.listen(8080, () => console.log('started on port 8080'));

18.初始化数据 #

18.1 App.js #

src\App.js

import React from 'react';
import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
const initialUser = { id: "1" }
const initialPosts = [];
function User({ userId }) {
  const { data, isLoading, isError, error, isFetching, isIdle } = useQuery(['user', userId], () => request.get('/user', {
    params: { userId }
  }), {
    enabled: !!userId,
    retry: 3,
    retryDelay: 1000,
    retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
    initialData: initialUser,
    initialStale: false,
    staleTime: 5000
  })
  const postsResult = useQuery(['posts', data?.id],
    () => request.get(`/posts`, { params: { userId: data?.id } }),
    {
      enabled: !!(data?.id),
      initialData: initialPosts,
      initialStale: false,
      staleTime: 5000
    });
  if (isIdle) return null;
  if (isLoading) return <div>加载中.......</div>
  if (isError) return <div>{error.message}</div>
  return (
    (
      <>
        {data.id ? <p>{data.id}:{data.name}</p> : <p>{userId}对应的用户不存在</p>}
        {isFetching && <div>更在更新数据...</div>}
        {postsResult.data && <p>帖子数:{postsResult.data.length}</p>}
        <p>{postsResult.isFetching && '正在更新贴子数据...'}</p>
      </>
    )
  )
}
function App() {
  const [userId, setUserId] = React.useState('');
  return (
    <>
      <input value={userId} onChange={(event) => setUserId(event.target.value)} />
      <User userId={userId} />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

19.并发查询 #

19.1 App.js #

src\App.js

import React from 'react';
+import { useQuery, useQueries } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function User() {
+ const [usersQuery, postsQuery] = useQueries([
+   { queryKey: ['users'], queryFn: () => request.get('/users') },
+   { queryKey: ['posts'], queryFn: () => request.get(`/posts`) },
+ ]);
  return (
    (
      <>
+       {usersQuery.data && <p>用户数:{usersQuery.data.length}</p>}
+       {postsQuery.data && <p>帖子数:{postsQuery.data.length}</p>}
      </>
    )
  )
}
function App() {
  return (
    <>
      <User />
      <ReactQueryDevtools initialIsOpen={true} />
    </>
  )
}
export default App;

19.2 api.js #

api.js

const express = require('express');
const cors = require('cors');
const logger = require('morgan');
const app = express();
app.use(express.json());
app.use(cors({
  allowedHeaders: ["Content-Type"],
  allowMethods: ["GET", 'POST', "PUT", "DELETE", "OPTIONS"]
}));
app.use(logger('dev'));
const users = new Array(10).fill(true).map((item, index) => ({ id: String(index + 1), name: `name${index + 1}` }))
const posts = new Array(10).fill(true).map((user, index) => ({ id: String(index + 1), title: `title${index + 1}`, userId: String(index + 1) }))
app.use((req, res, next) => {
  setTimeout(next, 1000 * 1);
});
app.get('/users', (req, res) => {
  res.json(users.map(user => ({ ...user, name: user.name + '#' + new Date().toLocaleString() })));
});
app.get('/user', (req, res) => {
  const userId = req.query.userId;
  const user = users.find(user => user.id === userId);
  if (user)
    res.json(user);
  else
    res.sendStatus(404)
});
app.get('/posts', (req, res) => {
+  res.json(posts);
});
app.listen(8080, () => console.log('started on port 8080'));

20.列表和详情 #

20.1 App.js #

src\App.js

import React from 'react';
import { useQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
+function Users({ setUserId }) {
+  const usersResult = useQuery('users', () => request.get('/users'), { staleTime: 5000 });
+  if (usersResult.isLoading) {
+    return '用户列表加载中......';
+  }
+  return (
+    <>
+      <h3>用户列表</h3>
+      <ul>
+        {
+          usersResult.data?.map(user => <li key={user.id} onClick={() => setUserId(user.id)}>{user.name}</li>)
+        }
+      </ul>
+    </>
+  )
+}

+function User({ userId, setUserId }) {
+  const userResult = useQuery(['user', userId], () => request.get('/user', {
+    params: { userId }
+  }), { staleTime: 5000 });
+  if (userResult.isLoading) {
+    return '单个用户加载中......';
+  }
+  return (
+    <div>
+      <button onClick={() => setUserId(-1)}>返回</button>
+      {userResult.data && <p>ID:{userResult.data.id},NAME:{userResult.data.name}</p>}
+    </div>
+  )
+}
function App() {
+ const [userId, setUserId] = React.useState(-1);
+ return (
+   <>
+     {
+       userId > -1 ? (
+         <User userId={userId} setUserId={setUserId} />
+       ) : <Users setUserId={setUserId} />
+     }
+     <ReactQueryDevtools initialIsOpen={false} />
+   </>
+ )
}
export default App;

21.读取查询缓存 #

  • QueryCache是 React Query 的存储机制。它存储它包含的所有数据、元信息和查询状态
  • 通常,您不会直接与 QueryCache 交互,而是使用QueryClient

21.1 App.js #

src\App.js

import React from 'react';
+import { useQuery, useQueryClient } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function Users({ setUserId }) {
  const usersResult = useQuery('users', () => request.get('/users'), { staleTime: 5000 });
  if (usersResult.isLoading) {
    return '用户列表加载中......';
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {
          usersResult.data?.map(user => <li key={user.id} onClick={() => setUserId(user.id)}>{user.name}</li>)
        }
      </ul>
    </>
  )
}

function User({ userId, setUserId }) {
  const queryClient = useQueryClient();
  const userResult = useQuery(['user', userId], () => request.get('/user', {
    params: { userId }
  }), {
    staleTime: 5000,
+   initialData: () => queryClient.getQueryData('users')?.find(user => user.id === userId),
+   initialStable: false
  });
  if (userResult.isLoading) {
    return '单个用户加载中......';
  }
  return (
    <div>
      <button onClick={() => setUserId(-1)}>返回</button>
      {userResult.data && <p>ID:{userResult.data.id},NAME:{userResult.data.name}</p>}
    </div>
  )
}
function App() {
  const [userId, setUserId] = React.useState(-1);
  return (
    <>
      {
        userId > -1 ? (
          <User userId={userId} setUserId={setUserId} />
        ) : <Users setUserId={setUserId} />
      }
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  )
}
export default App;

22.预缓存 #

22.1 App.js #

src\App.js

import React from 'react';
import { useQuery, useQueryClient } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function Users({ setUserId }) {
+  const queryClient = useQueryClient();
+ const usersResult = useQuery('users', async () => {
+   const users = await request.get('/users');
+   users.forEach(user => {
+     queryClient.setQueryData(['user', user.id], user);
+   });
+   return users;
  }, { staleTime: 5000 });
  if (usersResult.isLoading) {
    return '用户列表加载中......';
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {
          usersResult.data?.map(user => <li key={user.id} onClick={() => setUserId(user.id)}>{user.name}</li>)
        }
      </ul>
    </>
  )
}

function User({ userId, setUserId }) {
- const queryClient = useQueryClient();
  const userResult = useQuery(['user', userId], () => request.get('/user', {
    params: { userId }
  }), {
-    staleTime: 5000,
-    initialData: () => queryClient.getQueryData('users')?.find(user => user.id === userId),
-    initialStable: false
  });
  if (userResult.isLoading) {
    return '单个用户加载中......';
  }
  return (
    <div>
      <button onClick={() => setUserId(-1)}>返回</button>
      {userResult.data && <p>ID:{userResult.data.id},NAME:{userResult.data.name}</p>}
    </div>
  )
}
function App() {
  const [userId, setUserId] = React.useState(-1);
  return (
    <>
      {
        userId > -1 ? (
          <User userId={userId} setUserId={setUserId} />
        ) : <Users setUserId={setUserId} />
      }
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  )
}
export default App;

23.副作用 #

23.1 App.js #

src\App.js

import React from 'react';
import { useQuery, useQueryClient } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
+import { ToastContainer, toast } from 'react-toastify';
+import 'react-toastify/dist/ReactToastify.css';
import request from './request';
function Users({ setUserId }) {
  const queryClient = useQueryClient();
  const usersResult = useQuery('users', async () => {
    const users = await request.get('/users');
    users.forEach(user => {
      queryClient.setQueryData(['user', user.id], user);
    });
    return users;
  }, {
+   onSuccess(data) {
+     //console.log('查询成功', data);
+     toast("查询成功!")
+   },
+   onError(error) {
+     //console.log('查询失败', error);
+     toast("查询失败!")
+   },
+   onSettled(data, error) {
+     console.log('查询结束');
+   }
  });
  if (usersResult.isLoading) {
    return '用户列表加载中......';
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {
          usersResult.data?.map(user => <li key={user.id} onClick={() => setUserId(user.id)}>{user.name}</li>)
        }
      </ul>
    </>
  )
}

function User({ userId, setUserId }) {
  //  const queryClient = useQueryClient();
  const userResult = useQuery(['user', userId], () => request.get('/user', {
    params: { userId }
  }));
  if (userResult.isLoading) {
    return '单个用户加载中......';
  }
  return (
    <div>
      <button onClick={() => setUserId(-1)}>返回</button>
      {userResult.data && <p>ID:{userResult.data.id},NAME:{userResult.data.name}</p>}
    </div>
  )
}
function App() {
  const [userId, setUserId] = React.useState(-1);
  return (
    <>
      {
        userId > -1 ? (
          <User userId={userId} setUserId={setUserId} />
        ) : <Users setUserId={setUserId} />
      }
+     <ToastContainer />
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  )
}
export default App;

23.预查询 #

  • prefetchQuery是一种异步方法,可用于在需要查询或与useQuery一起呈现之前预取查询

23.1 App.js #

src\App.js

import React from 'react';
import { useQuery, useQueryClient } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import { ToastContainer, toast } from 'react-toastify';
import 'react-toastify/dist/ReactToastify.css';
import request from './request';
+function fetchUsers() {
+  return request.get('/users');
+}
function Users({ setUserId }) {
  const usersResult = useQuery('users', fetchUsers);
  if (usersResult.isLoading) {
    return '用户列表加载中......';
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {
          usersResult.data?.map(user => <li key={user.id} onClick={() => setUserId(user.id)}>{user.name}</li>)
        }
      </ul>
    </>
  )
}

function App() {
+ const queryClient = useQueryClient();
+ const [show, setShow] = React.useState(false);
+ React.useEffect(() => {
+   queryClient.prefetchQuery('users', fetchUsers, { staleTime: 5000 });
+ })
  return (
    <>
+     <button onClick={() => setShow(!show)} onMouseOver={() =>
+       queryClient.prefetchQuery('users', fetchUsers, { staleTime: 5000 })}>show</button>
+     {
+       show && <Users />
+     }
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  )
}
export default App;

24.变更操作 #

24.1 src\App.js #

src\App.js

import React from 'react';
+import { useQuery, useQueryClient, useMutation } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function fetchUsers() {
  return request.get('/users');
}
function Users({ setUserId }) {
  const usersResult = useQuery('users', fetchUsers);
  if (usersResult.isLoading) {
    return '用户列表加载中......';
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {
          usersResult.data?.map(user => <li key={user.id} onClick={() => setUserId(user.id)}>{user.name}</li>)
        }
      </ul>
    </>
  )
}

function App() {
+ const nameRef = React.useRef();
+ const queryClient = useQueryClient();
+ const { mutate, isLoading, isError, isSuccess, error } = useMutation(
+   (values) => request.post('/users', values), {
+   onSuccess() {
+     //queryClient.invalidateQueries('users');
+   },
+   onError(error) {
+     //alert(error.response.data.message);
+   },
+   onSettled(data, error) {
+     queryClient.invalidateQueries('users');
+   }
+ });
+ const handleSubmit = (event) => {
+   event.preventDefault();
+   const name = nameRef.current.value;
+   const user = { name };
+   mutate(user);
+ }
  return (
    <>
      <Users />
+      <form onSubmit={handleSubmit}>
+        <input ref={nameRef} />
+        <input type="submit" value={isLoading ? '保存中...' : isError ? '保存失败' : isSuccess ? "保存成功" : "保存"} />
+     </form>
+     {isError && <pre style={{ color: 'red' }}>{error.response.data.message}</pre>}
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  )
}
export default App;

24.2 api.js #

api.js

const express = require('express');
const cors = require('cors');
const logger = require('morgan');
const app = express();
app.use(express.json());
app.use(cors({
  allowedHeaders: ["Content-Type"],
  allowMethods: ["GET", 'POST', "PUT", "DELETE", "OPTIONS"]
}));
app.use(logger('dev'));
const users = new Array(10).fill(true).map((item, index) => ({ id: String(index + 1), name: `name${index + 1}` }))
const posts = new Array(10).fill(true).map((user, index) => ({ id: String(index + 1), title: `title${index + 1}`, userId: String(index + 1) }))
app.use((req, res, next) => {
  setTimeout(next, 1000 * 1);
});
app.get('/users', (req, res) => {
  res.json(users.map(user => ({ ...user, name: user.name + '#' + new Date().toLocaleString() })));
});
app.get('/user', (req, res) => {
  const userId = req.query.userId;
  const user = users.find(user => user.id === userId);
  if (user)
    res.json(user);
  else
    res.sendStatus(404)
});
+app.post('/users', (req, res) => {
+  let user = req.body;
+  if (user.name) {
+    user.id = (users.length + 1) + '';
+    user.createdAt = new Date().toLocaleDateString();
+    users.push(user);
+    res.json(user);
+  } else {
+    res.status(400).send({ message: '用户名不能为空!' });
+  }
+});
app.get('/posts', (req, res) => {
  res.json(posts);
});
app.listen(8080, () => console.log('started on port 8080'));

25.乐观更新 #

  • useMutation
  • onMutate函数将在突变函数被触发之前触发,并传递突变函数将接收的相同变量
  • 用于对资源执行乐观更新以希望突变成功
  • 如果发生突变失败,从此函数返回的值将传递给onError和onSettled函数,并且可用于回滚乐观更新

25.1 src\App.js #

src\App.js

import React from 'react';
import { useQuery, useQueryClient, useMutation } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function fetchUsers() {
  return request.get('/users');
}
function Users({ setUserId }) {
  const usersResult = useQuery('users', fetchUsers);
  if (usersResult.isLoading) {
    return '用户列表加载中......';
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {
          usersResult.data?.map(user => <li key={user.id} onClick={() => setUserId(user.id)}>{user.name}</li>)
        }
      </ul>
    </>
  )
}

function App() {
  const nameRef = React.useRef();
  const queryClient = useQueryClient();
  const { mutate:saveUser, isLoading, isError, isSuccess, error } = useMutation(
    (values) => request.post('/users', values), {
+   onMutate(values) {
+     queryClient.setQueryData('users', oldUsers => [...oldUsers, { ...values, id: String(Date.now()) }]);
+   },
    onSuccess() {
      //queryClient.invalidateQueries('users');
    },
    onError(error) {
      //alert(error.response.data.message);
    },
    onSettled(data, error) {
      queryClient.invalidateQueries('users');
    }
  });
  const handleSubmit = (event) => {
    event.preventDefault();
    const name = nameRef.current.value;
    const user = { name };
    saveUser(user);
  }
  return (
    <>
      <Users />
      <form onSubmit={handleSubmit}>
        <input ref={nameRef} />
        <input type="submit" value={isLoading ? '保存中...' : isError ? '保存失败' : isSuccess ? "保存成功" : "保存"} />
      </form>
      {isError && <pre style={{ color: 'red' }}>{error.response.data.message}</pre>}
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  )
}
export default App;

25.2 api.js #

const express = require('express');
const cors = require('cors');
const logger = require('morgan');
const app = express();
app.use(express.json());
app.use(cors({
  allowedHeaders: ["Content-Type"],
  allowMethods: ["GET", 'POST', "PUT", "DELETE", "OPTIONS"]
}));
app.use(logger('dev'));
const users = new Array(10).fill(true).map((item, index) => ({ id: String(index + 1), name: `name${index + 1}` }))
const posts = new Array(10).fill(true).map((user, index) => ({ id: String(index + 1), title: `title${index + 1}`, userId: String(index + 1) }))
app.use((req, res, next) => {
  setTimeout(next, 1000 * 1);
});
app.get('/users', (req, res) => {
+ res.json(users);
});
app.get('/user', (req, res) => {
  const userId = req.query.userId;
  const user = users.find(user => user.id === userId);
  if (user)
    res.json(user);
  else
    res.sendStatus(404)
});
app.post('/users', (req, res) => {
  let user = req.body;
  if (user.name) {
    user.id = (users.length + 1) + '';
    user.createdAt = new Date().toLocaleDateString();
    users.push(user);
    res.json(user);
  } else {
    res.status(400).send({ message: '用户名不能为空!' });
  }
});
app.get('/posts', (req, res) => {
  res.json(posts);
});
app.listen(8080, () => console.log('started on port 8080'));

26.失败回滚 #

26.1 src\App.js #

src\App.js

import React from 'react';
import { useQuery, useQueryClient, useMutation } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
function fetchUsers() {
  return request.get('/users');
}
function Users() {
  const usersResult = useQuery('users', fetchUsers);
  if (usersResult.isLoading) {
    return '用户列表加载中......';
  }
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {
          usersResult.data?.map(user => <li key={user.id}>{user.name}</li>)
        }
      </ul>
    </>
  )
}

function App() {
  const nameRef = React.useRef();
  const queryClient = useQueryClient();
+ const { mutate: saveUser, reset, isLoading, isError, isSuccess, error } = useMutation(
    (values) => request.post('/users', values), {
+   retry: false,
+   onMutate(values) {
+     queryClient.cancelQueries('users');
+     const oldUsers = queryClient.getQueryData('users');
+     queryClient.setQueryData('users', oldUsers => [...oldUsers, { ...values, id: String(Date.now+)) }]);
+     //return oldUsers;
+     return () => queryClient.setQueryData('users', oldUsers)
+   },
+   onSuccess(data) {
+     queryClient.setQueryData('users', oldUsers => oldUsers.map((user, index) => {
+       if (index === oldUsers.length - 1) {
+         return data;
+       }
+       return user;
+     }));
+     //queryClient.invalidateQueries('users');
+   },
+   onError(error, values, rollback) {
+     //alert(error.response.data.message);
+     //queryClient.setQueryData('users', rollbackValue);
+     console.log(rollback);
+     rollback && rollback();
+   },
+   onSettled(data, error) {
+     queryClient.invalidateQueries('users');
+     setTimeout(() => {
+       nameRef.current.value = ''
+       reset();
+     }, 3000)
+   }
  });
  const handleSubmit = (event) => {
    event.preventDefault();
    const name = nameRef.current.value;
    const user = { name };
    saveUser(user);
  }
  return (
    <>
      <Users />
      <form onSubmit={handleSubmit}>
        <input ref={nameRef} />
        <input type="submit" value={isLoading ? '保存中...' : isError ? '保存失败' : isSuccess ? "保存成功" : "保存"} />
      </form>
      {isError && <pre style={{ color: 'red' }}>{error.response.data.message}</pre>}
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  )
}
export default App;

27.分页查询 #

27.1 src\App.js #

src\App.js

import React from 'react';
import { useQuery, useQueryClient, useMutation } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
+function fetchUsers({ queryKey: [_, { pageNumber }] }) {
+  return request.get('/users', {
+    params: {
+      pageNumber
+    }
+  });
+}
function Users() {
+ const queryClient = useQueryClient();
+ const [pageNumber, setPageNumber] = React.useState(1);
+ const usersResult = useQuery(['users', { pageNumber }], fetchUsers, {
+   onSuccess() {
+     queryClient.prefetchQuery(['users', { pageNumber: pageNumber + 1 }], fetchUsers);
+   }
+ });
  return (
    <>
      <h3>用户列表</h3>
      <ul>
        {
+         usersResult.data?.data.map(user => <li key={user.id}>{user.name}</li>)
        }
      </ul>
+      {<button disabled={pageNumber <= 1} onClick={() => setPageNumber(pageNumber => pageNumber - 1)}>上一页</button>}
+      <span>{pageNumber}</span>
+      {<button disabled={usersResult.data?.pageNumber >= usersResult.data?.totalPage} onClick={() => setPageNumber(pageNumber => pageNumber + 1)}>下一页</button>}
    </>
  )
}

function App() {
  const nameRef = React.useRef();
  const queryClient = useQueryClient();
  const { mutate: saveUser, reset, isLoading, isError, isSuccess, error } = useMutation(
    (values) => request.post('/users', values), {
    retry: false,
    onMutate(values) {
      queryClient.cancelQueries('users');
      const oldUsers = queryClient.getQueryData('users');
      queryClient.setQueryData('users', oldUsers => [...oldUsers, { ...values, id: String(Date.now()) }]);
      //return oldUsers;
      return () => queryClient.setQueryData('users', oldUsers)
    },
    onSuccess(data) {
      queryClient.setQueryData('users', oldUsers => oldUsers.map((user, index) => {
        if (index === oldUsers.length - 1) {
          return data;
        }
        return user;
      }));
      //queryClient.invalidateQueries('users');
    },
    onError(error, values, rollback) {
      //alert(error.response.data.message);
      //queryClient.setQueryData('users', rollbackValue);
      console.log(rollback);
      rollback && rollback();
    },
    onSettled(data, error) {
      queryClient.invalidateQueries('users');
      setTimeout(() => {
        nameRef.current.value = ''
        reset();
      }, 3000)
    }
  });
  const handleSubmit = (event) => {
    event.preventDefault();
    const name = nameRef.current.value;
    const user = { name };
    saveUser(user);
  }
  return (
    <>
      <Users />
      <form onSubmit={handleSubmit}>
        <input ref={nameRef} />
        <input type="submit" value={isLoading ? '保存中...' : isError ? '保存失败' : isSuccess ? "保存成功" : "保存"} />
      </form>
      {isError && <pre style={{ color: 'red' }}>{error.response.data.message}</pre>}
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  )
}
export default App;

27.2 api.js #

api.js

const express = require('express');
const cors = require('cors');
const logger = require('morgan');
const app = express();
app.use(express.json());
app.use(cors({
  allowedHeaders: ["Content-Type"],
  allowMethods: ["GET", 'POST', "PUT", "DELETE", "OPTIONS"]
}));
app.use(logger('dev'));
+const users = new Array(30).fill(true).map((item, index) => ({ id: String(index + 1), name: `name${index + 1}` }))
const posts = new Array(30).fill(true).map((user, index) => ({ id: String(index + 1), title: `title${index + 1}`, userId: String(index + 1) }))
app.use((req, res, next) => {
  setTimeout(next, 1000 * 1);
});
app.get('/users', (req, res) => {
+ const pageNumber = Number(req.query.pageNumber);
+ const totalPage = Math.floor(users.length / 10);
+ const offset = (pageNumber - 1) * 10;
+ res.json({
+   data: users.slice(offset, offset + 10),
+   pageNumber,
+   totalPage
+ });
});
app.get('/user', (req, res) => {
  const userId = req.query.userId;
  const user = users.find(user => user.id === userId);
  if (user)
    res.json(user);
  else
    res.sendStatus(404)
});
app.post('/users', (req, res) => {
  let user = req.body;
  if (user.name) {
    user.id = (users.length + 1) + '';
    user.createdAt = new Date().toLocaleDateString();
    users.push(user);
    res.json(user);
  } else {
    res.status(400).send({ message: '用户名不能为空!' });
  }
});
app.get('/posts', (req, res) => {
  res.json(posts);
});
app.listen(8080, () => console.log('started on port 8080'));

28.无限分页 #

  • useInfiniteQuery

28.1 src\App.js #

src\App.js

import React from 'react';
+import { useInfiniteQuery } from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools'
import request from './request';
+function fetchUsers({ pageParam = 1 }) {
+  return request.get('/users', {
+    params: {
+      pageNumber: pageParam
+    }
+  });
+}
function Users() {
+ const { data, hasNextPage, fetchNextPage } = useInfiniteQuery(['users'], fetchUsers, {
+   getNextPageParam: (lastPageData) => {
+     console.log(lastPageData);
+     return lastPageData.pageNumber < lastPageData.totalPage ? lastPageData.pageNumber + 1 : false;
+   }
+ });
  return (
    <>
      <h3>用户列表</h3>
      <ul>
+       {
+         data?.pages?.map((page, index) => {
+           return (
+             <React.Fragment key={index}>
+               {
+                 page.data?.map(user => <li key={user.id}>{user.id}:{user.name}</li>)
+               }
+             </React.Fragment>
+           )
+         })
+       }
      </ul>
+     <button disabled={!hasNextPage} onClick={() => fetchNextPage()}>加载更多</button>
    </>
  )
}

function App() {
  return (
    <>
      <Users />
      <ReactQueryDevtools initialIsOpen={false} />
    </>
  )
}
export default App;

访问验证

请输入访问令牌

Token不正确,请重新输入