原文:How to Consume REST APIs in React – a Beginner's Guide,作者:Joel Olawanle

React 是一个流行的前端库,开发者常常使用 React 来创建应用。如果想要将创建的应用投入使用,你需要将 API 集成到应用中。

如果想要使用 React 构建现代、强大的 Web 应用,你必须知道如何在 React中 使用 API 来获取数据。

在这份初学者指南中,你将学习如何在 React 中使用 RESTful API,其中包括获取、删除以及添加数据。同时,我们将讲解两种使用 RESTful API 的主要方法,以及如何搭配 React 钩子来使用。

什么是 REST API?

如果你接触过编程,就应该碰到过 API 这个术语。

API 代表应用程序接口(Application Programming Interface),是不同应用之间相互通讯以及实时返回响应的媒介。

Roy Fielding在 2000 年将 REST 定义为一种互联网服务(如分布式超媒体系统)开发的架构—风格和方法,REST是 REpresentational State Transfer(表现层状态转化)的缩写。

当通过 REST API 发出请求时,它会将资源的当前状态的表现发送给请求者或者终点。状态表现通常采用 JSON 格式(JSON 即 JavaScript Object Notation),或者 XML 和 HTML 格式。

JSON 格式之所以最受欢迎,是因为它和编程语言无关,人类和机器都可以理解。

以下是一个JSON示例:

[
   {
      "userId": 1,
      "id": 1,
      "title": "sunt excepturi",
      "body": "quia et suscipit\nsuscipit recusandae consequuntur "
   },
   {
      "userId": 1,
      "id": 2,
      "title": "qui est esse",
      "body": "est rerum tempore vitae\nsequi sint nihil"
   }
]

如何在 React 中使用 REST API?

你可以通过各种不同的方法在 React 中使用 REST API,但在这篇教程中我们只讲解两种主要的方法:Axios(基于 promise 的 HTTP 客户端)和 Fetch API(浏览器内置的 Web API)。

注意: 你必须熟悉 JavaScript、React 和 React 钩子才能完全理解本教程。

在我们正式开始在 React 中使用 REST API 之前,我必须强调在 React 中使用 API 与在 JavaScript 中完全不同,因为请求是在 React 组件中完成。

这篇教程使用 React 函数组件,所以会使用两大钩子:

  • useEffect Hook: 在 React 中,我们通常使用useEffect()来发送 API 请求。一般是页面渲染后马上执行钩子内的代码,或者当达到特定状态时执行。基本语法如下:
useEffect(() => {
    // 在这里获取数据
}, []);
  • useState Hook: 在请求数据的时候,我们必须准备一个状态(state)来存储返回的数据。你可以使用状态管理工具如 Redux 或者存储到一个上下文对象中。为了简化难度,这里我们将返回数据存储到 React 本地状态:
const [posts, setPosts] = useState([]);

让我们进入本文的正题,我们借助 JSONPlaceholder posts API 来学习如何获取、添加和删除数据。因为文章内容是针对初学者的,所以这里讲解的方法的应用面很广。

如何使用 Fetch API 来获取 API 数据?

Fetch API 是 JavaScript 内置从服务器或者 API 终点获取资源的方法。因为是内置的,所以使用 Fetch API 不需要安装任何依赖项或者包。

fetch()方法有一个强制参数,即你想要获取资源的路径或者 URL,该方法返回一个 promise,可以使用then()catch()方法来处理成功或者失败的情况。

一个基本的 fetch 请求简单易写,在下面示例中,我们从 URL 获取数据后,返回 JSON 格式的数据然后打印在控制台。

fetch('https://jsonplaceholder.typicode.com/posts?_limit=10')
   .then(response => response.json())
   .then(data => console.log(data));

默认响应是 HTTP 格式的,但是我们可以使用json()方法来把结果改成 JSON 对象。

在 React 中如何使用 Fetch API 来实现 GET 请求?

可以使用 HTTP GET 方法从终点请求数据。

如前文所述,Fetch API 有一个强制参数,但与此同时也接受一个选择参数,当使用 GET 方法的时候,可以选择写或者不写,但是如果使用 POST 和 DELETE 方法的话,就必须附上方法的名称及详情:

fetch(url, {
    method: "GET" //默认参数,可以忽略
})

在学习完原理之后,让我们学以致用,从 API 中获取数据。

我们将使用free online API JSONPlaceholder上的数据,获取一组帖子的数据到应用:

import React, { useState, useEffect } from 'react';

const App = () => {
   const [posts, setPosts] = useState([]);
   useEffect(() => {
      fetch('https://jsonplaceholder.typicode.com/posts?_limit=10')
         .then((response) => response.json())
         .then((data) => {
            console.log(data);
            setPosts(data);
         })
         .catch((err) => {
            console.log(err.message);
         });
   }, []);

return (
   // ... 在这里使用数据
);
};

我们在代码的一开始创建了一个状态来存储从 API 获取的数据,以便之后可以在应用中使用。同时,我们将状态的默认值设置为空数组。

const [posts, setPosts] = useState([]);

主要的操作发生在 useEffect 中,一旦应用加载成功,就获取数据(帖子)。fetch 请求得到一个 promise,我们可以接受或者拒绝:

useEffect(() => {
   fetch('https://jsonplaceholder.typicode.com/posts?_limit=10').then(
      (response) => console.log(response)
   );
}, []);

response包含了大量的数据,如:状态码、文本以及其他我们之后要去处理的错误信息。

目前我们使用 .then()来处理 promise 决议,但此刻只返回一个响应对象,并不是我们想要的。 所以我们要使用 json() 方法来修改决议格式为 JSON 格式。此时也会返回一个 promise 需要我们使用第二个.then()来获取真正的数据。

useEffect(() => {
   fetch('https://jsonplaceholder.typicode.com/posts?_limit=10')
      .then((response) => response.json())
      .then((data) => {
         console.log(data);
         setPosts(data);
      });
}, []);

查看控制台,会发现我们已经用 API 获取了 10 条帖子,并且将状态设置为我们之前计划的样子。

还没完,因为我们只处理了决议没有处理拒绝,此时需要.catch()方法:

useEffect(() => {
   fetch('https://jsonplaceholder.typicode.com/posts?_limit=10')
      .then((response) => response.json())
      .then((data) => {
         console.log(data);
         setPosts(data);
      })
      .catch((err) => {
         console.log(err.message);
      });
}, []);

我们已经知道如何使用GET请求,数据可以轻松通过数组循环在应用内使用:

const App = () => {
// ...

   return (
   <div className="posts-container">
      {posts.map((post) => {
         return (
            <div className="post-card" key={post.id}>
               <h2 className="post-title">{post.title}</h2>
               <p className="post-body">{post.body}</p>
               <div className="button">
               <div className="delete-btn">Delete</div>
               </div>
            </div>
         );
      })}
   </div>
   );
};

export default App;

在 React 中如何使用 Fetch API 发送 POST 请求?

可以使用HTTPPOST方法从终点发送数据。该方法和GET请求类似,主要的区别在于需要添加方法名称以及两个额外的参数到选填对象中:

const addPosts = async (title, body) => {
await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify({
   title: title,
   body: body,
   userId: Math.random().toString(36).slice(2),
}),
headers: {
   'Content-type': 'application/json; charset=UTF-8',
},
})
.then((response) => response.json())
.then((data) => {
   setPosts((posts) => [data, ...posts]);
   setTitle('');
   setBody('');
})
.catch((err) => {
   console.log(err.message);
});
};

代码中的body(请求体)和header(请求头)对于你来说可能有点陌生。

body部分的数据是我们想要传给 API 的数据,在传输给服务器之前必须先字符串化。header告知数据类型,通常和我们使用的 REST API 一致的类型。同时我们也使用状态保存新的数据,并将剩下的数据分配到上文所述的循环数组中。

在我们创建的addPost()方法中期望从表单或者其他地方获取数据。在我们的例子中,我创建了一个表单<form>,从状态的变化获得表单数据,然后在提交表单的时候将数据添加到方法。

import React, { useState, useEffect } from 'react';
const App = () => {
const [title, setTitle] = useState('');
const [body, setBody] = useState('');
// ...
const addPosts = async (title, body) => {
   await fetch('https://jsonplaceholder.typicode.com/posts', {
      method: 'POST',
      body: JSON.stringify({
         title: title,
         body: body,
         userId: Math.random().toString(36).slice(2),
      }),
      headers: {
         'Content-type': 'application/json; charset=UTF-8',
      },
   })
      .then((response) => response.json())
      .then((data) => {
         setPosts((posts) => [data, ...posts]);
         setTitle('');
         setBody('');
      })
      .catch((err) => {
         console.log(err.message);
      });
};

const handleSubmit = (e) => {
   e.preventDefault();
   addPosts(title, body);
};    

return (
   <div className="app">
      <div className="add-post-container">
         <form onSubmit={handleSubmit}>
            <input type="text" className="form-control" value={title}
               onChange={(e) => setTitle(e.target.value)}
            />
            <textarea name="" className="form-control" id="" cols="10" rows="8" 
               value={body} onChange={(e) => setBody(e.target.value)} 
            ></textarea>
            <button type="submit">Add Post</button>
         </form>
      </div>
      {/* ... */}
   </div>
);
};

export default App;

在 React 中如何使用 Fetch API 执行 DELETE 请求?

可以使用 HTTPDELETE方法从终点删除数据。这和GET请求类似,主要的区别在与这个方法附加的一些条件:

const deletePost = async (id) => {
await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
   method: 'DELETE',
}).then((response) => {
   if (response.status === 200) {
      setPosts(
         posts.filter((post) => {
            return post.id !== id;
         })
      );
   } else {
      return;
   }
});
};

一旦点击按钮就触发这个行为,我们获得按钮所在的具体帖子的id,然后从整个返回数据中删除这个数据。

这样操作会将数据从 API 删除,但是不从 UI 删除,所以我们添加了一个filter方法来在 UI 上删除响应的数据。每一个在循环中的删除键代码如下:

const App = () => {
// ...

   return (
   <div className="posts-container">
      {posts.map((post) => {
         return (
            <div className="post-card" key={post.id}>
               {/* ... */}
               <div className="button">
                  <div className="delete-btn" onClick={() => deletePost(post.id)}>
                     Delete
                  </div>
               </div>    
            </div>
         );
      })}
   </div>
   );
};

export default App;

如何在 Fetch API 中使用 Async/Await?

目前我们使用 promise 语法来发送 fetch 请求,这个语法有时会造成理解上的困惑,特别是后续紧跟着链式调用。我们可以使用 async/await 取代.then()从而编写更易读的代码。

使用 async/await 的第一步是在函数中添加async标识,然后在需要等待响应的部分添加await语法,等待 promise 决议。

使用 async/await 后我们的 fetch 请求会变成这个样子:

import React, { useState, useEffect } from 'react';

const App = () => {
   const [title, setTitle] = useState('');
   const [body, setBody] = useState('');
   const [posts, setPosts] = useState([]);

   // fetch API 中的 GET 方法
   useEffect(() => {
      const fetchPost = async () => {
         const response = await fetch(
            'https://jsonplaceholder.typicode.com/posts?_limit=10'
         );
         const data = await response.json();
         console.log(data);
         setPosts(data);
      };
      fetchPost();
   }, []);

   //fetch API 中的 DELETE 方法
   const deletePost = async (id) => {
      let response = await fetch(
         `https://jsonplaceholder.typicode.com/posts/${id}`,
         {
            method: 'DELETE',
         }
      );
      if (response.status === 200) {
         setPosts(
            posts.filter((post) => {
               return post.id !== id;
            })
         );
      } else {
         return;
      }
   };

   // fetch API 中的 POST 方法
   const addPosts = async (title, body) => {
      let response = await fetch('https://jsonplaceholder.typicode.com/posts', {
         method: 'POST',
         body: JSON.stringify({
            title: title,
            body: body,
            userId: Math.random().toString(36).slice(2),
         }),
         headers: {
            'Content-type': 'application/json; charset=UTF-8',
         },
      });
      let data = await response.json();
      setPosts((posts) => [data, ...posts]);
      setTitle('');
      setBody('');
   };

   const handleSubmit = (e) => {
      e.preventDefault();
      addPosts(title, body);
   };

   return (
      // ...
   );
};

export default App;

如何使用 Fetch API 处理错误

在这个部分我们将讨论如何使用传统和 async/await 的方式来处理错误。

我们可以使用响应数据来处理 Fetch API 中的错误,或者在 async/await 中使用 try/catch 声明来处理错误。

让我们先看看如果使用传统方式:

const fetchPost = () => {
fetch('https://jsonplaceholder.typicode.com/posts?_limit=10')
   .then((response) => {
      if (!response.ok) {
         throw Error(response.statusText);
      }
      return response.json();
   })
   .then((data) => {
      console.log(data);
      setPosts(data);
   })
   .catch((err) => {
      console.log(err.message);
   });
};

更多相关内容可以阅读这篇文章

在 async/await 中可以这样使用trycatch

const fetchPost = async () => {
   try {
      const response = await fetch(
         'https://jsonplaceholder.typicode.com/posts?_limit=10'
      );
      const data = await response.json();
      setPosts(data);
   } catch (error) {
      console.log(error);
   }
};

如何使用 Axios 来使用 API?

Axios 是基于 promise 的 HTTP 客户端库,使得向 REST 终点发送异步 HTTP 请求变得更容易。本文使用的终点是 JSONPlaceholder Posts API,我们会对它发出GETPOSTDELETE请求。

如何安装和配置 Axios 实例?

Axios 不像 Fetch API 一样是内置的,所以需要我们在使用之前将其安装在项目里。

你可以使用以下命令来安装 Axios:

npm install axios

成功安装 Axios 之后,可以马上创建实例,这一步是选择性的,但是为了避免不必要的重复,我建议你这样操作。

我们使用.create()方法来创建实例,我们可以通过这个方法确定如 URL 和 header 等具体信息:

import axios from "axios";

const client = axios.create({
   baseURL: "https://jsonplaceholder.typicode.com/posts" 
});

在 React 中如何使用 Axios 执行 GET 请求?

我们将使用创建好的实例来执行 GET 请求。只需要设置好参数,就可以获取默认 JSON 格式的响应。

和 Fetch API 方法不同的是,在 Axios 中没有额外的选择性参数。我们只需将方法添加到实例上,然后发出请求:

useEffect(() => {
   client.get('?_limit=10').then((response) => {
      setPosts(response.data);
   });
}, []);

在 React 中如何使用 Axios 执行 POST 请求?

如上文所述,可以通过POST方法将数据发送到终点,运行方式和GET请求类似,唯一的不同是在POST请求中需要指明请求方式以及添加我们需要发送的数据:

const addPosts = (title, body) => {
   client
      .post('', {
         title: title,
         body: body,
      })
      .then((response) => {
         setPosts((posts) => [response.data, ...posts]);
      });
};

在 React 中如何使用 Axios 执行 DELETE 请求?

我们可以通过delete方法来发送删除请求,获取id后,删除 API 上对应的内容。和在 Fetch API 中的操作一样,我们同样需要使用filter方法来更新 UI:

const deletePost = (id) => {
   client.delete(`${id}`);
   setPosts(
      posts.filter((post) => {
         return post.id !== id;
      })
   );
};

如何在 Axios 中使用 Async/Await?

目前为止我们都是使用 promise 的语法来发送 Axios 请求,现在让我们看看如何使用 async/await 语法来避免使用.then()链式调用。

使用 async/await 后,Axios 请求可以改写如下:

import React, { useState, useEffect } from 'react';

const App = () => {
   const [title, setTitle] = useState('');
   const [body, setBody] = useState('');
   const [posts, setPosts] = useState([]);

   // Axios 中的 GET
   useEffect(() => {
      const fetchPost = async () => {
         let response = await client.get('?_limit=10');
         setPosts(response.data);
      };
      fetchPost();
   }, []);

   // Axios 中的 DELETE
   const deletePost = async (id) => {
      await client.delete(`${id}`);
      setPosts(
         posts.filter((post) => {
            return post.id !== id;
         })
      );
   };

   // Axios 中的 POST
   const addPosts = async (title, body) => {
      let response = await client.post('', {
         title: title,
         body: body,
      });
      setPosts((posts) => [response.data, ...posts]);
   };

   const handleSubmit = (e) => {
      e.preventDefault();
      addPosts(title, body);
   };

   return (
      // ...
   );
};

export default App;

如何使用 Axios 处理错误?

如果是基于 promise 语法的 Axios 请求,我们使用.then().catch () 方法,如果是 async/await,我们使用try...catch代码块。这和使用 Fetch API 非常类似,try...catch代码块的示例如下:

const fetchPost = async () => {
   try {
      let response = await client.get('?_limit=10');
      setPosts(response.data);
   } catch (error) {
      console.log(error);
   }
};

更多相关内容可以阅读这篇文章

Fetch API vs Axios

你可以已经发现了一些不同,这里我制作了一张表格列举了两个方法之间的不同。

两者之间的区别可以帮助你决定在你的项目中采取哪种方法,两者之间的区别是:

Axios Fetch
Axios 是一个便于安装的第三方独立包 Fetch 是许多浏览器内置的,所以不需要安装
Axios 使用 data(数据)属性 Fetch 使用 body(请求体)属性
Axios 数据包含对象 Fetch 的请求体需要 字符串化
状态码为 200 以及状态文本为'OK'时,Axios 请求成功 Fetch 请求成功的标准是 响应对象包含 ok 属性
Axios 自动转换数据为 JSON 格式 Fetch 处理 JSON 格式请求需要两步操作:先发送请求,再对响应调用.json()方法
Axios 允许取消请求和请求超时 Fetch 不允许
Axios 内置下载进度支持 Fetch 不支持上传进度获取
Axios 被大部分浏览器支持 Fetch 仅兼容 Chrome 42+、Firefox 39+、Edge 14+、 and Safari 10.1+(向后兼容)

总结

在这篇文章中我们学习了如何使用 Fetch API 和 Axios 在 React 中使用 REST API。

本文将开启你的 React 使用 API 之旅,之后你将自己选择 API 并进行更为复杂的数据处理。