React入门(六)– 连接后端

​React是用于创建界面或前端的一个库,而应用程序还需要运行在服务器的后端支持,我们可以将后端理解成给前端提供动力的引擎,它提供业务逻辑、数据或安全验证等。

很多语言或框架可以做后端,比如:

Express.js、Django、Ruby on Rails、Spring、ASP.NET Core。

后端开发是另一个主题,如果想了解Django的话,可以查看本人之前写的《掌握Django》系列。作为React开发人员,需要知道如何连接后端。


理解Effect 回调钩子

在介绍连接后端之前,必须先了解Effect Hook。

本系列《管理组件状态》一文中,我们提到过React组件要求是纯的组件,即同样的输入,输出都是一致的,要做到这点,就要求将更改放在组件渲染阶段之外。

组件里是有些是不用返回任何jsx的更改代码的,比如:将数据存在本地缓存,调用服务器获取数据,手动修改DOM元素。这些更改的操作该放在何处呢?

这就要用到 Effect 钩子了!

利用Effect 钩子,我们可以在渲染完组件后执行一些代码片段。

看下面的例子。我们有一个Ref钩子 ref,与input关联,调用focus()设置焦点:…ref.current.focus(),这属于在组件内修改,组件就不纯了。我们可以使用Effect钩子useEffect 将这些代码包在里面,这样React就会在组件渲染完成后再执行这些代码。

使用Effect钩子,我们有机会执行一些操作,比如缓存到浏览器本地,获取数据库并保存,以及手动修改Dom元素等。

import { useEffect, useRef } from "react";
​
function App() {
  const ref = useRef<HTMLInputElement>(null);
  useEffect(() => {
    if (ref.current) ref.current.focus();
  });
  return (
    <div>
      <input ref={ref} type="text" className="form-control" />
    </div>
  );
}
​
export default App;

使用Effect钩子有一些注意项:

像State钩子和 Ref钩子一样,我们只能在组件的顶部调用,不能在循环语句或If语句中使用。

可以多次调用Effect钩子以满足不同的用途。比如我们再增加一个Effect钩子用于修改标题。

...
function App() {
  const ref = useRef<HTMLInputElement>(null);
  useEffect(() => {
    if (ref.current) ref.current.focus();
  });
​
  useEffect(() => {
    document.title = "My App";
  });
...

Effect 的依赖

前面介绍了Effect钩子里包含的函数默认每次在组件渲染完成时执行,我们可以修改控制函数的执行时机。看一个例子。

src–components下新建ProductList.tsx组件。

组件里有名为products的State,初始为空列表,这里我们调用useState时给定了<string[]>的类型。

假设需要从后端获取ProductList,获取之后再设置State,代码中我们简单在控制台输出一句表示在Fetching products,然后设置State,我们模拟获取到了2个产品。我们将这个操作用Effect钩子useEffect()包起来。

import React, { useEffect, useState } from "react";
​
const ProductList = () => {
  const [products, setProducts] = useState<string[]>([]);
  useEffect(() => {
    console.log("Fetching products");
    setProducts(["Clothing", "Household"]);
  });
  return <div>ProductList</div>;
};
​
export default ProductList;

接着在App组件中调用ProductList,保存执行,检查网页控制台,发现有无限循环的错误。原因是:

组件渲染完成之后React执行Effect钩子,而Effect里面有一条setProduct语句会导致再次渲染,然后再执行Effect,再渲染,造成了无限循环。

解决这个问题是用Effect的依赖,useEffect()还有第2个可选参数,是个列表,列表元素可以是各个State或Props,表示Effect依赖这些State和Props的变化,有变动才执行Effect。

这里我们指供了空列表 [],表示不依赖任何State和Effect,这样Effect就只执行一次,不会再次执行造成无限循环。

...
  useEffect(() => {
    ...
  }, []);
...

再进一步,我们希望在App组件中添加下拉选项框,选中的category将作为Props传到ProductList展示。

App.tsx如下,没什么新鲜的东西。

...
function App() {
  const [category, setCategory] = useState("");
  return (
    <div>
      <select
        className="form-control"
        onChange={(event) => setCategory(event.target.value)}
      >
        <option value=""></option>
        <option value="Clothing">Clothing</option>
        <option value="Household">Household</option>
      </select>
      <ProductList category={category} />
    </div>
  );
}
​
export default App;
​

接着修改ProductList.tsx。需要定义一个Props的interface,属性是category,这里我们就使用简写方式,直接写在组件参数里面,对于单行简单的Props都可以类似这样简化处理。

在useEffect()的第2个参数的列表项里,我们提供了Props的category,这样当category有变化时,就会执行该Effect里的代码。如果在这里不加category,下拉选项框变化时将不会执行。

...
const ProductList = ({ category }: { category: string }) => {
...
  useEffect(() => {
    console.log("Fetching products in", category);
   ...
  }, [category]);
...
​

Effect 清理

Effect有时需要清理,比如一个聊天组件连接服务器,不展示时就要断开服务器的连接。

下面的代码,先定义两个函数connect和disconnect分别表示连接服务器和断开服务器,在Effect中,先调用connect连接服务器,然后做一些工作,最后可选返回一个函数,该函数里可以做些Effect清理工作,这里就是调用disconnect。

const connect = () => console.log("Connecting");
const disconnect = () => console.log("Disconnecting");
​
function App() {
  useEffect(() => {
    connect();
    // 一些代码
    return () => disconnect();
  });
  return <div></div>;
}
​
export default App;

一般而言,我们在Effect中做了什么就要停止或撤回什么。我们连接或订阅了什么,清理函数就应该断开或退订。或者,我们显示某个对话框,清理时就得隐藏掉。再或者我们在Effect中我们从服务器获取数据,清理函数应该中断数据的获取或忽略掉数据。

我们保存运行一下,在控制台能看到3条信息。

你知道为什么是3条信息吗?

前面我们说过在开发模式下,React是在Strict模式运行,该模式需要执行2次,第1次执行时运行了connect,由于要执行第2次,就要先unmount(卸载)组件再mount,unmount时就触发了清理代码,执行了disconnect。


获取数据

了解了Effect,现在开始介绍后端的连接。

我们需要一个模拟的后端。打开网址:

https://jsonplaceholder.typicode.com/

我们能看一些免费使用的访问端点。点击可以查看相应的数据。

有两个方法可以获取后端数据。

大多数现代浏览器都支持fetch方法,不过很多人可能更喜欢axios,本节也使用它。

第一步安装:

npm i axios@1.3.4

编辑App.tsx:

首先导入axios, 在useEffect中调用axios.get方法,获取数据并不是马上就能得到的,这是异步的过程,我们会得到一个Promise对象,该对象在异步调用完成后给到我们一个结果或失败,有一个then方法。在then方法中,我们可以对结果response(这里简写res)进行处理。特别注意useEffect的第2个参数我们设了空列表 [] ,这非常重要,否则就像前面介绍的那样出现无限循环。

我们可以查看一下response的结构,

有config,data,headers,request,status,statusText等属性,data的数据就是我们调用得到的数据,假设我们只关心id和name,可以定义一个User的interface,在axios.get<User[]>(“….”)… 时指定返回的数据结构为User[]。

返回的数据我们要用State保存,定义users的State,并且也指定类型:

… = useState<User[]>([]),初始为空列表。在axios.get返回Promise的then方法中,我们调用setUsers。

最后一步,我们数据展示在页面,用users.map方法列出名字。

...
import axios from "axios";
​
interface User {
  id: number;
  name: string;
}
function App() {
  const [users, setUsers] = useState<User[]>([]);
  useEffect(() => {
    axios
      .get<User[]>("https://jsonplaceholder.typicode.com/users")
      .then((res) => setUsers(res.data));
  }, []);
  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
​
export default App;
​

保存,效果如下:


理解HTTP请求

HTTP是一个协议,是构成WEB网站的基础。客户端浏览器发送请求,WEB服务返回响应。

检查网页,“网络”-筛选”Fetch/XHR”,其中XHR是XML HTTP request的缩写。

如图我们能看到请求包的大小,以及花费时间。

点击可以进一步查看详细。

任何一个请求和响应都至少有两部分Header和Body。Header是一些元数据,而Body则是提供或响应的数据。

预览标签下可以查看响应结果的漂亮的格式,而响应标签下则显示文本格式。


处理错误

HTTP请求过程中,因为网络及服务器等原因,可能会出现各种错误,React程序员需要捕捉处理。

axios.get方法得到一个Promise,它除了then方法,还有catch方法,就是用于提供处理错误的函数。我们故意设置一个错误的xusers端点,并添加error的State,在catch的箭头函数中调用setError。

再到jsx渲染页面中判断是否有error,有的话就显示错误信息。

import { useEffect, useState } from "react";
import axios from "axios";
​
interface User {
  id: number;
  name: string;
}
function App() {
  const [users, setUsers] = useState<User[]>([]);
  const [error, setError] = useState("");
  useEffect(() => {
    axios
      .get<User[]>("https://jsonplaceholder.typicode.com/xusers")
      .then((res) => setUsers(res.data))
      .catch((err) => setError(err.message));
  }, []);
  return (
    <>
      {error && <p className="text-danger">{error}</p>}
      <ul>
        {users.map((user) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </>
  );
}
​
export default App;

效果如图。


使用Async和Await

Axios.get方法得到一个Promise,promise要么得到一个正确的响应res,要么是被拒绝了而得到错误err,相应地我们可以用then和catch来处理,非常简洁。

get –> promise –> res / err 

有些人可能不喜欢用Promise,不习惯用then和catch,这里就来介绍另外一种方式,Async和Await。

在axios.get前加上await,会在Promise之前拦截处理,这样可以返回结果res,使用await的函数需要加上async,由于不能直接加Effect上,这里添加一个函数fetchUsers。

fetchUsers函数里,await需要手动捕捉错误,得加上try…catch结构,在catch类型这里,指定类型为AxiosError。

最后还要明确指示执行fetchUsers。

import axios, { AxiosError } from "axios";
......
  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const res = await axios.get<User[]>(
          "https://jsonplaceholder.typicode.com/xusers"
        );
        setUsers(res.data);
      } catch (err) {
        setError((err as AxiosError).message);
      }
    };
    fetchUsers();
  }, []);
......

await 和 async已经存在了很长时间了,非常可靠,但与Promise相比,代码明显更长,结构也更丑陋,建议用Promise的then、catch处理。当然你如果就是喜欢用await和async,那也完全没有问题。


取消获取数据请求

想象一下这个场景,组件正从后端获取数据,但用户已导航到了其他页面,这时我们就不必等到数据获取完毕并进行渲染,而是应该取消这个获取过程。作为最佳实践,我们在Effect中进行数据获取时,需返回一个Effect清理函数。

修改下代码,先创建一个AbortController对象,该对象用于终止异步操作,异步操作主要是指类似获取后端数据需要花较长时间的操作。再在axios.get方法添加第2个参数,这里提供键值 为signal的对象为前面建的AboutController的signal:

{signal:controller.signal}

再在Effect返回时设置箭头函数,中断该controller指示(signal)的操作:

()=>controller.abort()

取消数据请求也会被捕捉到CanceledError类型的错误,需要判断并忽略掉。

......
  useEffect(() => {
    const controller = new AbortController();
    axios
      .get<User[]>("https://jsonplaceholder.typicode.com/users", {
        signal: controller.signal,
      })
      .then((res) => setUsers(res.data))
      .catch((err) => {
        if (err instanceof CanceledError) return;
        setError(err.message);
      });
    return () => controller.abort();
  }, []);
......

保存并检查网页,在网络标签下,我们能看到取消的请求。

开发模式下,React启用了strict模式运行2次组件,第2次运行相当于导航到其他页面,这样我们就能看到取消的信息,使用这种Effect清除方式,我们在strict模式下也不必担心会执行2次从后端获取数据的操作。


显示加载指示

在获取数据时,需要给出加载图标,提示用户耐心等待。

添加isLoading的State,默认是false,在准备执行axios.get之时设置为false,然后在then和catch中分别设置回false,这稍微有些重复,按理只需在finally中执行就行,比如 .finally(()=>setLoading(false)),但在strict模式中,仅在finally中执行不能正确工作,这里我们就分别在then和catch中执行。

接着在页面上展示出来,使用了bootstrap的 spinner-border。代码如下。

......
function App() {
......
  const [isLoading, setLoading] = useState(false);
  useEffect(() => {
    ......
    setLoading(true);
    axios
      .get<User[]>(......)
      .then((res) => {
        ......
        setLoading(false);
      })
      .catch((err) => {
        ......
        setLoading(false);
      });
    ......
  }, []);
  return (
      ......
      {isLoading && <div className="spinner-border"></div>}
     ......

由于数据量不大,执行速度很快,我们没机会看到加载图标,可以模拟慢速网络进行查看。


删除数据

为了删除记录,我们添加删除按钮。按钮的使用btn-outline-danger。为了排版美观,在ul里使用 list-group,每项 li 使用 list-group-item,这些都是比较常用的bootstrap的css。为了名字靠左侧,而按钮靠右侧,使用 d-flex 和 justify-content-between,d-flext表示变成flex弹性容器,而justify-content-between 表示以合适的间距排版,这里2个元素,就是一个靠左一个靠右,这也是常用的bootstrap的css,要学会使用。

......
function App() {
......
  const deleteUser = (user: User) => {
    const originalUsers = [...users];
    setUsers(users.filter((u) => u.id !== user.id));
    axios
      .delete("https://jsonplaceholder.typicode.com/xusers/" + user.id)
      .catch((err) => {
        setError(err.message);
        setUsers(originalUsers);
      });
  };
  return (
    <>
      ......
      <ul className="list-group">
        {users.map((user) => (
          <li
            className="list-group-item d-flex justify-content-between"
            key={user.id}
          >
            {user.name}
            <button
              className="btn btn-outline-danger"
              onClick={() => deleteUser(user)}
            >
              Delete
            </button>
          </li>
        ))}
      </ul>
    </>
  );
}
​
export default App;
​

接着处理删除事件,一般有两种方式:乐观更新和悲观更新。乐观更新假设后端连接处理会成功,先更新组件,再连后端服务器;悲观方式则假设服务器处理会失败,先连接后端服务器处理,成功后再更新组件。

乐观更新用户体验更好,因为反馈是即时的,而悲观更新则稍有些延迟。一般而言,在可能的情况下尽量使用乐观更新方式。

上面我们的代码就是采用乐观更新方式,点删除按钮时,先更新页面setUsers(),再调用服务器 axios.delete()。delete()方法删除成功后不需要做什么,所以then方法就不调用,只捕捉错误用catch。

在catch中做两件事情,一件是显示错误信息,另一件则是恢复原先的页面,所以事先用originalUsers保存了原来的users。

删除出错的效果如下。


创建数据

为在列表上添加按钮Add,使用btn-primary和mb-3,mb-3是为了间隔美观,处理函数是addUser,也使用乐观更新方式先更新组件后调用服务器。

一般而言,创建数据需要一个form,但这会分散本节要介绍内容的注意力,所以就直接硬编码一个newUser,而不是从form获取。

创建数据使用 axios.post 方法并携带 数据 newUser,Promise返回调用then,这里我们直接解构了res的data并重命名为savedUser,这样代码可读性更强。

同样的,调用失败也恢复原有的State,这与前面删除的操作类似,不再赘述。

......
function App() {
  ......
  const addUser = () => {
    const originalUser = [...users];
    const newUser = { id: 0, name: "kelemi" };
    setUsers([newUser, ...users]);
​
    axios
      .post("https://jsonplaceholder.typicode.com/xusers/", newUser)
      .then(({ data: savedUser }) => setUsers([savedUser, ...users]))
      .catch((err) => {
        setError(err.message);
        setUsers(originalUser);
      });
  };
  return (
    <>
      ......
      <button className="btn btn-primary mb-3" onClick={addUser}>
        Add
      </button>
      ......
    </>
  );
}
​
export default App;
​

效果如下:


更新数据

在每个User行中,添加Update按钮,为了不使排版混乱,需要将 Update按钮和Delete按钮包在一个div里,添加Update按钮的 mx-1 是为了设置两个按钮间的水平间隔为1。

接着处理更新操作,为了不分心,也没用form直接硬编码在原有name上添加”!”。更新有两个方法:patch 和 put。put是更新整个对象,而patch是只更新1个或多个属性,这也要看后端服务器的支持,有些后端很可能只支持 put 而不支持 patch,这里我们使用 patch。

patch操作使用乐观更新方式,与前面类似。

......
​
function App() {
......
​
  const updateUser = (user: User) => {
    const originalUsers = [...users];
    const updatedUser = { ...user, name: user.name + "!" };
    setUsers(users.map((u) => (u.id === user.id ? updatedUser : u)));
    axios
      .patch(
        "https://jsonplaceholder.typicode.com/xusers/" + user.id,
        updatedUser
      )
      .catch((err) => {
        setError(err.message);
        setUsers(originalUsers);
      });
  };
  return (
    <>
      ......
      
      <ul className="list-group">
        {users.map((user) => (
          <li ......>
            {user.name}
            <div>
              <button
                className="btn btn-outline-secondary mx-1"
                onClick={() => updateUser(user)}
              >
                Update
              </button>
              <button ...... >
                Delete
              </button>
            </div>
          </li>
        ))}
      </ul>
    </>
  );
}
​
export default App;

如下:


提取可重用的apiClient

目前我们的App.tsx稍有点规模了,我们来优化下。

首先我们看到,每次调用axios时都需要写完整的API端点 ,这显然是重复的,我们来提取优化。

在src文件夹下新建文件夹services,再在其下新建 api-client.ts。

将axios访问后端服务器端点相关的都放在该模块下,axios.create方法创建API访问端点的基础信息,我们设置baseURL,实际的生产环境中可能需要设置headers,比如有api-key信息等,这里暂且不设。并默认导出 export default。

另外导出在APP组件中使用到的axios的 CanceledError。

import axios,{CanceledError} from "axios";
​
export default axios.create({
    baseURL:'https://jsonplaceholder.typicode.com',
    headers:{
        'api-key':'...'
    }
})
​
export {CanceledError};

再回App.tsc,删除导入axios 的语句。添加导入api-client。

将原先使用axios的地方,比如axios.get改成apiClient.get,且端点信息不用写全,因为baseURL已在apiClient中设置了,只需写后面的部分如“/users/” 即可。

......
import apiClient, { CanceledError } from "./services/api-client";
......
​
    apiClient
      .get<User[]>("/users/",......)
......
​

这样修改后,代码就简洁了一点。


提取用户服务

再看App.tsx组件,我们发现它太了解Http细节了,比如AbortController这完全是Http的东西,它也了解各个API端点细节,以及get、post、patch、delete等操作方法,这个其实不应该是App组件关心的细节,它只需关心与用户的交互即可。在其他场景下,也可能用到这个users信息,更好的方式是将用户服务提取出来。

src/services下新建文件 user-service.ts,先将获取用户数据提取到该文件,函数名为getAllUsers,我们将原在App.tsx里Effect里的逻辑移到这里,由于用到User的数据结构,也将该interface移到这里并export以便App组件其他逻辑用。

AbortController完全属于HTTP层面的事情,我们也移到这里。getAllUsers返回request和cancel,request是一个Promise,调用它的程序可以继续用then或catch等,cancel用于清除Effect。

import apiClient from "./api-client";
​
export interface User {
    id: number;
    name: string;
}
class UserService{
    getAllUsers(){
        const controller = new AbortController();
        const request = apiClient.get<User[]>("/users/", {
            signal: controller.signal,
        })
        return {request, cancle:() => controller.abort()}
    }
}
​
export default new UserService();

回到App.tsx,修改useEffect部分。

......
​
useEffect(() => {
    setLoading(true);
    const { request, cancle } = userService.getAllUsers();
    request
      .then((res) => {
        setUsers(res.data);
        setLoading(false);
      })
      .catch((err) => {
        if (err instanceof CanceledError) return;
        setError(err.message);
        setLoading(false);
      });
    return () => cancle();
  }, []);
  
......

其他的deleteUser, createUser, updateUser也作类似重构,不详述了,user-service.ts如下。

......
​
class UserService{
    ......
    
    deleteUser(id:number){
        return apiClient.delete("/users/" + id);
    }
​
    createUser(user:User){
        return apiClient.post("/users/", user);
    }
​
    updateUser(user:User){
        return apiClient.patch("/users/" + user.id, user)
    }
}
​
......

App.tsx调用userService的相应方法即可,这样就消除了在App组件中HTTP细节处理的代码,最后App组件也不再依赖 apiClient,可以取消导入。


创建通用的HTTP服务

我们已创建了useService服务,这使我们的App完全跟Http实现细节脱离,这很好。但还有些问题,比如后续我们需要实现 /posts 端点,类似地它也有获取所有posts、新增、删除、更新等操作,唯一与 /users 不同的就是API端点的不同。本节我们借助Typescript的魔法,实现一个通用的HTTP服务。

src/services文件夹下新建 http-service.ts文件。

先将user-service.ts文件的内容全部复制到http-service.ts里,然后逐行进行修改。

先删除 interface User,因为这是特定的对User的使用,在通用实现里要么删除要么改成通用化。

按F2将类UserService改名为HttpService,再将getAllUsers改名为getAll这样通用的名字。getAll里用到 User这个数据结构,我们将其改成T,函数也变成 getAll<T>,T 相当于占位符,这样可以由调用者提供结构。

数据结构解决了,还有一个就是端点名,我们可以让调用者在调用方法时提供端点名,类似 httpService.getAll(‘/users’),但这稍点麻类烦,更好的方法是使用构造函数,在创建类的实例时提供。这里设置endpoint属性,构造函数 constructor获取该字符串参数并设置endpoint。

同样的,deleteUser, createUser也改成 delete, create,也利用 <T> 占位符代表数据结构。

update稍有些不同,因为它有2个参数,第1个是实体的id,而实体不一定有id属性,所以要限制数据实体必须有 id ,创建Entity的interface,定义一个属性id,update方法的里点位符 <T> 继承于 Entity。

最后是导出类的实例给其他模块调用,自然不能硬编码写明端点,我们创建一个箭头函数 create,该函数需要有一个endpoint参数,接着export default create 。

import apiClient from "./api-client";
​
interface Entity{
    id:number;
}
​
class HttpService{
    endpoint:string;
    constructor(endpoint:string){
        this.endpoint = endpoint;
    }
    getAll<T>(){
        const controller = new AbortController();
        const request = apiClient.get<T[]>(this.endpoint, {
            signal: controller.signal,
        })
        return {request, cancle:() => controller.abort()}
    }
    delete(id:number){
        return apiClient.delete(this.endpoint +"/" + id);
    }
​
    create<T>(entity:T){
        return apiClient.post(this.endpoint, entity);
    }
​
    update<T extends Entity>(entity:T){
        return apiClient.patch(this.endpoint + '/' + entity.id, entity)
    }
}
​
const create = (endpoint:string)=>new HttpService(endpoint);
export default create;

回到 user-service.ts,保留原定义的User的数据结构,调用create函数并提供端点名称 “/users”,其他代码均删除,然后导出create即可。另外apiClient已用不到了,不用再导入。

import create from "./http-service"
​
export interface User {
    id: number;
    name: string;
}
​
export default create('/users');

回到App.tsx,修改userService的方法比如deleteUser改delete,以及在userService.getAll<User>() 提供数据结构 <User>即可。这里不再详列。

按这种方法提取后,后续需要处理 posts 等其他API端点时,也只需提供数据结构和端点字符串即可,可重用性非常强。


创建自定义的数据获取钩子

目前我们的组件很不错,不过也有问题。假设我们要创建另外一个组件也需要获取users信息,我们要创建 users, error, isLoading三个State钩子,在获取数据时也需要用到Effect钩子,显然这些代码与App组件的代码重复了。

钩子(Hook)其实就是一些功能性逻辑,我们可以将它放在自定义钩子或自定义函数里,这样就可以在各个组件间重用。看看我们怎么做?

src文件夹下新建hooks文件夹,再在hooks下新建 useUsers.ts文件,按照惯例,Hook 都以use开头,像我们学过的useState, useEffect, useRef一样,这是个普通typescript文件,以ts结尾。

我们将App组件的相关Hook全移到useUsers中,并导入必要的模块,可以在vscode错误提示处按 【ctrl+.】 自动导入相关模块。在uerUsers最后return 相关 Hook, 比如users, error, isLoading, setUsers, setError等。

import { useEffect, useState } from "react";
import userService, { User } from "../services/user-service";
import { CanceledError } from "../services/api-client";
​
const useUsers = () =>{
    const [users, setUsers] = useState<User[]>([]);
    const [error, setError] = useState("");
    const [isLoading, setLoading] = useState(false);
    useEffect(() => {
      setLoading(true);
      const { request, cancle } = userService.getAll<User>();
      request
        .then((res) => {
          setUsers(res.data);
          setLoading(false);
        })
        .catch((err) => {
          if (err instanceof CanceledError) return;
          setError(err.message);
          setLoading(false);
        });
      return () => cancle();
    }, []);
    return {users, error, isLoading, setUsers, setError} 
}
export default useUsers;

回到App组件,使用useUsers钩子并解构相应Hook使用即可。

......
​
function App() {
  const { users, error, isLoading, setUsers, setError } = useUsers();
  
......

其他组件需要用到Users钩子时,也可按这个方法使用。


小结

本文详细介绍了React连接后端的相关知识。下一篇开始将分几篇从头开始创建一个游戏网站项目。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注