在我们开始这个项目之前,让我们回顾一下 GraphQL 的用例,以及它与 REST API 的不同之处。
(React教程:
GraphQL 的用例是什么?
GraphQL 用于构建需要实时数据同步的应用程序,例如聊天应用程序。它允许开发人员获取必要的数据,减少网络上的数据传输,并提高应用程序性能。
微服务处理应用程序的特定功能或特性,这对开发人员单独使用多个 API 提出了挑战。
GraphQL 提供了一种自文档化模式,使开发人员可以轻松理解数据模型和数据之间的关系。它还简化了创建、测试和维护 API 的过程,从而减少了时间和成本。
最后,GraphQL 提供了版本控制功能,允许在不破坏现有客户端的情况下改进 API 模式。版本控制是可能的,因为客户指定了他们需要的确切数据,这使得添加新字段和删除折旧字段变得容易,而不会影响现有客户。
GraphQL 和 REST API 之间有什么区别?
使用 GraphQL,客户端发送一个带有它需要的数据的查询,服务器只用该数据响应。另一方面,使用REST API,客户端向端点发送请求,服务器使用与端点相关的所有数据/响应进行响应。
REST API 是基于资源的,其中端点表示可以访问、创建、更新或删除的数据。另一方面,GraphQL 是基于图的,其中每个节点代表对象之间的关系。
REST API,以 JSON(JavaScript 对象表示法)或 XML(可扩展标记语言)格式返回数据。同时,GraphQL 允许客户端指定他们需要的数据,并用匹配查询的 JSON 对象进行响应。
GraphQL 提供版本控制以在不中断现有客户端的情况下实现 API 的发展,而 REST API 为每个版本创建新的端点。
在某些情况下,REST API 可能会出现过度获取或获取不足的情况,服务器可能会发送过多或较少的数据。GraphQL 通过允许客户端请求他们需要的数据来解决这个问题,从而减少通过网络传输的数据量。
项目设置
现在您已经熟悉了可以使用 GraphQL 做什么,让我们开始构建项目。
先决条件
-
React基础知识
-
了解
依赖安装
创建一个名为“rickandmorty”的新 React App。
npm init [React](https://react.dev/)-app rickandmorty
或者
npx create-[React](https://react.dev/)-app rickandmorty
安装 Apollo 客户端和 GraphQL。下面的代码安装了两个依赖项:
-
@apollo/client 包含您需要的一切,例如内存缓存、本地状态管理、错误处理和基于 React 的视图层。
-
GraphQL 提供解析查询的逻辑。
npm install @apollo/client [GraphQL](https://graphql.org/)
Rick & Morty API 和 Apollo 客户端设置
设置项目后,我们需要开始在我们的文件中使用它。接下来,index.js
使用命令导航到您的文件cd
,并添加以下代码:
import [React](https://react.dev/)DOM from '[React](https://react.dev/)-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://rickandmortyapi.com/[GraphQL](https://graphql.org/)',
cache: new InMemoryCache(),
});
const root = [React](https://react.dev/)DOM.createRoot(document.getElementById('root'));
root.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
);
上面的代码使用瑞克和莫蒂 API GraphQL 端点的 URL(统一资源定位器)创建了一个 Apollo 客户端实例。
App 组件与 Apollo 提供程序组件一起包装,以将客户端传递给所有子组件。
查询实现
characters.js
现在,在文件夹内创建一个名为的文件src
。该文件将包含查询和您要添加的任何其他函数。
在文件中,添加以下代码:
import { gql } from '@apollo/client';
export const GET_CHARACTERS = gql`
query Characters{
characters{
results {
name
species
status
type
gender
origin{name}
location {name}
image
},
},
}
`;
在上面的代码中,我们gql
从中导入@apollo/client
来定义我们的查询。
我们创建变量并将其导出GET_CHARACTERS
为带有大写字母的字符串。在 GraphQL 中定义查询时,大写被认为是最佳实践。使用模板文字包装字符串也被认为是最佳实践。
Javascript 中的对象是用键值对填充的集合或容器。键值对称为属性。
在我们的例子中,查询搜索瑞克和莫蒂中的角色。它返回一个具有该results
属性的对象,该属性是一个字符对象数组。
每个角色都有名称、物种、状态、类型、性别和图像等属性——您可以选择要获取的内容?。
其他属性 origin 和 location 是具有每个角色的 origin 和 location 的 name 属性的对象。
字符函数定义
在该character.js
文件中,修改后的查询下方添加以下代码,GET_CHARACTERS
如下所示:
import { useQuery, gql } from '@apollo/client';
import { useState } from "[React](https://react.dev/)";
import { RandomCharacter } from './randomcharacters';
import './App.css';
export const GET_CHARACTERS = gql`
query Characters($name: String){
characters ( filter: {name: $name}){
results {
name
species
status
type
gender
origin{name}
location {name}
image
},
},
}
`;
export function CharacterList() {
const [searchTerm, setSearchTerm] = useState("");
const {loading, error, data } = useQuery(GET_CHARACTERS, {variables: {name: searchTerm}});
const handleChange = (event) => {
setSearchTerm(event.target.value);
};
return (
<div>
<input type="text" name="search" placeholder="search for Rick and Morty characters..." value={searchTerm} onChange={handleChange} className="search-input" />
{loading && (
<div className="loader-container">
<div className="loader"></div>
</div>
)}
{error && <p> error </p> }
{data?.characters.results.length === 0 && (<> <RandomCharacter/> </>)}
{data && data.characters.results.map((character) => (
<div className="card" key={character.name} style={{ backgroundImage: `url(${character.image})`,backgroundRepeat: 'no-repeat'}}>
<div className="info">
<h2 className="h3"> {character.name}</h2>
<p> Status: {character.status}</p>
<p> Species: {character.species} </p>
<p> Type: {character.type}</p>
<p> Gender: {character.gender}</p>
<p> Origin: {character.origin.name}</p>
<p> Location: {character.location.name}</p>
</div>
</div>
))}
</div>
);
}
创建export function CharacterList()
一个函数,该函数也导出到代码的其他部分并且可以被代码的其他部分使用。
该searchTerm
变量将搜索状态初始化为空字符串,并创建一个函数setSearchTerm
来更新值。
useQuery
来自库的钩子从@apollo/client
API 获取数据。
查询传递GET_CHARACTERS
一个名为searchTerm
which 的变量,该变量用于保存要搜索的字符名称。
该handleChange
变量将searchTerm
的值设置为输入字段的当前值。该input
字段是用户将用于搜索他们想要查看的字符的名称的搜索栏。状态由 处理handleChange
。
我们还需要考虑加载站点的问题,以及可能发生的任何错误。
loading
如果加载设置为 ,则使用微调器动态呈现True
。如果将错误设置为 ,则会显示一条错误消息True
。
当用户正在搜索一个不存在的角色时,我们想要显示一条消息和一个他们可以找到更多信息的替代角色——这就是进来的地方。我们稍后会定义它RandomCharacter
。现在,让我们保持原样。
获取数据后,我们将data.characters.results
数组映射到每个角色的卡片。
我们还想更改卡片的背景以代表信息所针对的角色。属性backgroundImage
中的style
处理图像的动态变化。其余项目在卡片上显示为文本。
如何显示数据
现在我们有一个功能可以工作,我们需要查看浏览器中显示的内容,以及我们是否可以进行查询并获取我们需要的数据。
在您的App.js
文件中,添加以下代码:
function App() {
return (
<div>
<h1 style={{ textAlign: 'center' }} >Rick and Morty Characters</h1>
<CharacterList />
</div>
);
}
<CharacterList />
组件显示有关我们从 API 获取的字符的信息。
如何随机化字符
请记住,我们调用了RandomCharacter
组件,但尚未定义它。
randomcharacters.js
创建一个调用的文件src
并添加以下代码:
import { useQuery } from "@apollo/client";
import { gql } from '@apollo/client';
import { useState } from "[React](https://react.dev/)";
import './App.css';
export const GET_SINGLE_CHARACTER = gql`
query Character($id: ID!){
character (id: $id) {
name
species
status
type
gender
origin{name}
location {name}
image
},
},
`;
export const RandomCharacter = () => {
const [randomNumber, setRandomNumber] = useState(Math.floor(Math.random() * 200));
const { loading, error, data } = useQuery(GET_SINGLE_CHARACTER, {variables: {id: randomNumber } });
return (
<div>
<p className="intro" >
Sorry, we couldn't find that character ?
<br/>
<br/>
How about this one instead? ? </p>
{/* {loading && <p>loading...</p>} */}
{loading && (
<div className="loader-container">
<div className="loader"></div>
</div>
)}
{error && <p> error </p> }
{data && (<>
<div className="card" key={data.character.name} style={{ backgroundImage: `url(${data.character.image})`,backgroundRepeat: 'no-repeat'}}>
<div className="info">
<h2 className="h3">{data.character.name}</h2>
<p>Status: {data.character.status}</p>
<p>Species: {data.character.species}</p>
<p>Type: {data.character.type}</p>
<p>Gender: {data.character.gender}</p>
<p>Origin: {data.character.origin.name}</p>
<p>Location: {data.character.location.name}</p>
</div>
</div>
</>
)}
</div>
);
};
我们将复制我们在文件中创建的查询characters.js
,并将其重命名为GET_SINGLE_CHARACTER
. 我们将寻找 s 而不是寻找名字ID
。
我们寻找ID
s 是因为它们是唯一的,并且我们希望在用户没有找到他们正在寻找的字符时随机选择字符。
randomNumber
将状态初始化为Math.floor
生成 0 到 199 之间的随机数的函数,使用该Math.random()
方法并将其乘以 200。
该Math.floor
函数将表达式的结果四舍五入为最接近的整数。每次randomNumber
需要更新时,该 setRandomNumber
函数都会将新值作为其参数并更新状态。
我们有一条消息提醒用户他们正在寻找的角色没有找到,但他们可以签出一个新角色。
加载微调器也在此组件中实现,如果出现任何问题,错误就在那里。图像和卡片类似于格式,characters.js
因为我们希望与所有内容的外观保持一致。
如何设置显示样式
我们将使用 CSS 来设置卡片的外观样式,以及搜索栏和一般页面。
定义了功能和组件后,我们将向className
需要设置样式的内容添加属性。
添加以下代码:
@import url(https://fonts.googleapis.com/css?family=Roboto:400,500,700);
body{
background: navajowhite;
font-family: Roboto, veranda;
padding-bottom: 4em;
}
.card{
position: relative;
width: 22em;
height: 30em;
background-size: 22em 30em;
box-shadow: 3px 3px 20px rgba(0,0,0,0.5);
margin: auto;
overflow: hidden;
margin-bottom: 2em;
}
.card *{
position: relative;
z-index: 2;
}
.card:hover .info{
bottom: -3em;
opacity: 1;
padding: 2px 1px;
background-color: navajowhite;
}
.info{
font-family: 'Droid Serif', serif;
font-size: 1.2em;
color: black;
line-height: 1.1em;
padding: 0 2em;
position: relative;
bottom: -4em;
opacity: 0;
background: transparent;
transition: opacity 0.3s, bottom 0.3s;
text-align: center;
}
/* search bar*/
input[type="text"] {
border: none;
border-radius: 10px;
background-color: #f2f2f2;
padding: 10px;
width: 500px;
margin: 0 auto;
display: block;
font-size: 16px;
font-family: 'Roboto', sans-serif;
box-shadow: 2px 2px 5px rgba(0,0,0,0.2);
margin-bottom: 2em;
}
input[type="text"]::placeholder {
color: #999;