关于前端:翻译-JavaScript-Everywhere第14章-使用Apollo-Client

38次阅读

共计 13540 个字符,预计需要花费 34 分钟才能阅读完成。

翻译 |《JavaScript Everywhere》第 14 章 应用 Apollo Client

写在最后面

大家好呀,我是毛小悠,是一位前端开发工程师。正在翻译一本英文技术书籍。

为了进步大家的浏览体验,对语句的构造和内容略有调整。如果发现本文中有存在瑕疵的中央,或者你有任何意见或者倡议,能够在评论区留言,或者加我的微信:code_maomao,欢送互相沟通交流学习。

(σ゚∀゚)σ..:*☆哎哟不错哦

第 14 章 应用 Apollo Client

我分明地记得我的第一个互联网连贯。我用计算机的调制解调器拨号上网,而后我能够收费上网。过后感觉如此神奇,与咱们明天应用的始终在线的连贯相距甚远。
流程如下所示:

  1. 坐在我的电脑上,而后关上 ISP 软件。
  2. 单击“连贯”,而后期待调制解调器拨打该号码。
  3. 如果连贯胜利,请听到“调制解调器声音”。

如果不是这样,例如在顶峰时段,此时线路可能会过载和忙碌,请重试。

  1. 建设连贯后,你将收到胜利告诉,并浏览所有充斥 GIF90年代辉煌的网页。

这个周期看起来很艰巨,然而它依然代表了服务彼此之间进行通信的形式:它们申请一个连贯,建设该连贯,发送一个申请,并失去一些回应。咱们的客户应用程序将以雷同的形式工作。咱们将首先连贯到服务器 API 应用程序,如果胜利,将向该服务器发出请求。

在本章中,咱们将应用 Apollo Client 连贯到咱们的 API。连贯之后,咱们将编写一个GraphQL 查问,该查问将用于在页面上显示数据。咱们还将在 API 查问和接口组件中引入分页。

在本地运行 API

Web客户端应用程序的开发将须要拜访咱们 API 的本地实例。如果你始终在浏览本书,那么你可能曾经在计算机上启动了 Notedly API 及其数据库。如果不是这样,我曾经在附录 A 增加了无关如何获取 API 并应用一些样本数据一起运行的正本。如果你曾经在运行 API,然而心愿应用其余数据,请从API 我的项目目录的根目录运行npm run seed

设置 Apollo 客户端

Apollo 服务器十分类似,Apollo客户端提供了许多有用的性能来简化在 JavaScript UI 应用程序中应用 GraphQL 的工作。Apollo客户端提供了用于将 Web 客户端连贯到 API、本地缓存、GraphQL 语法、本地状态治理等的库。咱们还将在 React 应用程序中应用 Apollo Client,但Apollo 还提供了 VueAngularMeteorEmberWeb Components的库。

首先,咱们要确保.env文件蕴含了对咱们本地 API URI 的援用,这将使咱们可能在开发中应用本地 API 实例,同时在将应用程序公布到公共 Web 服务器时指向咱们的产品 API。在咱们的.env 文件,咱们应该有一个 API_URI 变量保留咱们本地API 服务器的地址:

API_URI=http://localhost:4000/api

咱们的代码捆绑程序 Parcel 设置为主动解决.env文件。

任何时候咱们想在咱们的代码中援用一个.env变量,咱们能够应用 process.env.VARIABLE_NAME。这将使咱们可能在本地开发、生产以及咱们可能须要的任何其余环境(例如暂存或继续集成)中应用特有的变量值。

应用存储在环境变量中的地址,咱们能够将 Web 客户端连贯到 API 服务器。在 src/App.js 文件中工作,首先,咱们须要导入将要应用的 Apollo 软件包:

// import Apollo Client libraries
import {ApolloClient, ApolloProvider, InMemoryCache} from '@apollo/client';

导入这些内容后,咱们能够配置一个新的 Apollo Client 实例,将其传递给 API URI,启动缓存,并启用本地Apollo 开发者工具:

// configure our API URI & cache
const uri = process.env.API_URI;
const cache = new InMemoryCache();
// configure Apollo Client
const client = new ApolloClient({
uri,
cache,
connectToDevTools: true
});

最初,咱们能够将 React 应用程序打包到一个ApolloProvider。咱们将用替换空标签,并将咱们的客户端包含在内:

const App = () => {
return (<ApolloProvider client={client}>
<GlobalStyle />
<Pages />
</ApolloProvider>
);
};

总体而言,咱们的 src/App.js 文件当初将如下所示:

import React from 'react';
import ReactDOM from 'react-dom';
// import Apollo Client libraries
import {ApolloClient, ApolloProvider, InMemoryCache} from '@apollo/client';
// global styles
import GlobalStyle from '/components/GlobalStyle';
// import our routes
import Pages from '/pages';
// configure our API URI & cache
const uri = process.env.API_URI;
const cache = new InMemoryCache();
// configure Apollo Client
const client = new ApolloClient({
uri,
cache,
connectToDevTools: true
});
const App = () => (<ApolloProvider client={client}>
<GlobalStyle />
<Pages />
</ApolloProvider>
);
ReactDOM.render(<App />, document.getElementById('root'));

通过将客户端连贯到 API 服务器,咱们当初能够将 GraphQL 查问和批改集成到咱们的应用程序中。

查问 API

当咱们查问 API 时,咱们是在申请数据。在 UI 客户端中,咱们心愿可能查问该数据并将其显示给用户。Apollo使咱们可能编写查问以获取数据。而后,咱们能够更新 React 组件以将数据显示给最终用户。咱们能够通过编写 noteFeed 查问来摸索查问的用法,该查问将向用户返回最新笔记的摘要,并将其显示在应用程序的主页上。

当我第一次编写查问时,发现以下过程很有用:

  1. 思考查问须要返回什么数据。
  2. GraphQL Playground 中编写查问。
  3. 将查问集成到客户端应用程序。

让咱们在构思查问时遵循此过程。如果你持续浏览本书的 API 局部,你可能会记得,noteFeed查问返回一个蕴含 10 个笔记的列表,以及一个游标(批示最初返回的笔记的地位)和 hasNextPage 布尔值,该布尔值容许咱们确定是否有其余笔记要加载。咱们能够在 GraphQL Playground 中查看咱们的构造,从而能够查看所有可用的数据选项。对于咱们的查问,咱们很可能须要以下信息:

{
cursor
hasNextPage
notes {
id
createdAt
content
favoriteCount
author {
id
username
avatar
}
}
}

当初,在咱们的 GraphQLPlayground 中,咱们能够将其填充到 GraphQL 查问中。咱们将比服务器章节中的查问更加具体,通过命名查问并提供一个可选的名为 cursor 的变量,要应用 GraphQL Playground,请首先确保API 服务器正在运行,而后拜访 http://localhost:4000/api。在GraphQL Playground 中,增加以下查问:

query noteFeed($cursor: String) {noteFeed(cursor: $cursor) {
cursor
hasNextPage
notes {
id
createdAt
content
favoriteCount
author {
username
id
avatar
}
}
}
}

GraphQL Playground 中,增加一个“query variable”以测试该变量的应用:

{"cursor": ""}

要测试此变量,请将空字符串替换为数据库中任何笔记的 ID 值(图14-1)。

14-1 咱们在 GraphQL Playground 中的 noteFeed 查问

当初咱们晓得查问已正确编写,咱们能够释怀地将其集成到 Web 应用程序中。在 src/pages/home.js 文件中,通过 @apollo/client中的 gql 库导入 useQuery 库以及 GraphQL 语法:

// import the required libraries
import {useQuery, gql} from '@apollo/client';
// our GraphQL query, stored as a variable
const GET_NOTES = gql`
query NoteFeed($cursor: String) {noteFeed(cursor: $cursor) {
cursor
hasNextPage
notes {
id
createdAt
content
favoriteCount
author {
username
id
avatar
}
}
}
}
`;

当初咱们能够将查问集成到咱们的 React 应用程序中。为此,咱们将 GraphQL 查问字符串传递给 ApollouseQueryReact钩子。咱们的钩子将返回一个蕴含以下值之一的对象:

  • data数据

查问返回的数据(如果胜利)。

  • loading

加载状态,在获取数据时将其设置为true。这使咱们能够向用户显示加载指示器。

  • error

如果无奈获取咱们的数据,则会将谬误返回到咱们的应用程序。

咱们能够更新 Home 组件包含咱们的查问:

const Home = () => {
// query hook
const {data, loading, error, fetchMore} = useQuery(GET_NOTES);
// if the data is loading, display a loading message
if (loading) return <p>Loading...</p>;
// if there is an error fetching the data, display an error message
if (error) return <p>Error!</p>;
// if the data is successful, display the data in our UI
return (
<div>
{console.log(data)}
The data loaded!
</div>
);
};
export default Home;

如果胜利实现所有操作,则应该看到“数据已加载!”音讯呈现在应用程序主页上(图 14-2)。咱们还包含一个console.log 语句,它将打印咱们的数据到浏览器控制台。将数据集成到应用程序中时,查看数据后果的构造是有用的领导。

14-2。如果咱们的数据已胜利获取,则咱们的组件将显示“数据已加载!”音讯,数据将打印到控制台。

当初,让咱们将接管到的数据集成到应用程序中。为此,咱们将应用数据中返回的笔记数组。React要求为每个后果调配一个惟一的键,为此咱们将应用单个笔记的ID。首先,咱们将为每个笔记显示作者的用户名:

const Home = () => {
// query hook
const {data, loading, error, fetchMore} = useQuery(GET_NOTES);
// if the data is loading, display a loading message
if (loading) return <p>Loading...</p>;
// if there is an error fetching the data, display an error message
if (error) return <p>Error!</p>;
// if the data is successful, display the data in our UI
return (
<div>
{data.noteFeed.notes.map(note => (<div key={note.id}>{note.author.username}</div>
))}
</div>
);
};

应用 JavaScript 的 map()办法

如果你以前没有应用过 JavaScriptmap()办法,那么语法仿佛有些生疏。map()办法,能够在数组中执行遍历动作。当你解决从 API 返回的数据时,这十分有用,它容许你执行操作,例如以某种形式在模板中显示每个我的项目。要理解无关 map() 的更多信息,倡议浏览 MDN Web 文档指南。

如果咱们的数据库中有数据,你当初应该在页面上看到用户名列表(图14-3)。

14-3 来自咱们数据的用户名,打印到屏幕上。

当初咱们曾经胜利输入了咱们的数据,咱们能够编写其余的组件了。因为咱们的笔记是用 Markdown 编写的,因而咱们导入一个库,该库将容许咱们将 Markdown 出现到页面上。

src/pages/home.js 中:

import ReactMarkdown from 'react-markdown';

当初,咱们能够更新UI,包含作者的头像、作者的用户名、笔记的创立日期,笔记具备的收藏夹数量以及笔记本身的内容。

src/pages/home.js 中:

// if the data is successful, display the data in our UI
return (
<div>
{data.noteFeed.notes.map(note => (<article key={note.id}>
<img src={note.author.avatar} alt={`${note.author.username} avatar`} height="50px" />{' '}
{note.author.username} {note.createdAt} {note.favoriteCount}{' '}
<ReactMarkdown source={note.content} />
</article>
))}
</div>
);

React 中的空格

React会去除在新行上的元素之间的空格。在咱们的标记中应用 {”} 是一种手动增加空格的办法。

当初,你应该在浏览器中看到残缺的笔记列表。然而,在持续进行款式设计之前,能够进行一些小的重构。这是咱们显示笔记的第一页,然而咱们晓得咱们还会做更多事件。在其余页面上,咱们将须要显示单个笔记以及其余笔记类型的性能(例如“我的笔记”和“收藏夹”)。让咱们持续创立两个新组件:src/components/Note.jssrc/components/NoteFeed.js

src/components/Note.js 中,咱们将包含单个笔记的标记。为此,咱们将为每个组件函数传递一个蕴含内容的属性。

import React from 'react'; import ReactMarkdown from 'react-markdown';
const Note = ({note}) => { return (
<article>
<img
src={note.author.avatar}
alt="{note.author.username} avatar" height="50px" />{' '}
{note.author.username} {note.createdAt} {note.favoriteCount}{' '}
<ReactMarkdown source={note.content} />
</article>
);
}; export default Note;

当初,对于 src/components/NoteFeed.js 组件:

import React from 'react';
import Note from './Note';
const NoteFeed = ({notes}) => {
return (
<div>
{notes.map(note => (<div key={note.id}>
<Note note={note} />
</div>
))}
</div>
);
};
export default NoteFeed;

最初,咱们能够更新 src/pages/home.js 组件援用咱们的NoteFeed

import React from 'react';
import {useQuery, gql} from '@apollo/client';
import Button from '../components/Button';
import NoteFeed from '../components/NoteFeed';
const GET_NOTES = gql`
query NoteFeed($cursor: String) {noteFeed(cursor: $cursor) {
cursor
hasNextPage
notes {
id
createdAt
content
favoriteCount
author {
username
id
avatar
}
}
}
}
`;
const Home = () => {
// query hook
const {data, loading, error, fetchMore} = useQuery(GET_NOTES);
// if the data is loading, display a loading message
if (loading) return <p>Loading...</p>;
// if there is an error fetching the data, display an error message
if (error) return <p>Error!</p>;
// if the data is successful, display the data in our UI
return <NoteFeed notes={data.noteFeed.notes} />;
};
export default Home;

通过这种重构,咱们当初能够轻松地在咱们的应用程序中从新创立笔记和笔记摘要的实例。

一些格调

当初咱们曾经编写了组件并能够查看数据,咱们能够增加一些款式。最显著的改良机会之一就是咱们的“创立日期”显示方式。
为了解决这个问题,咱们将应用 date-fns 库,它提供了一些小的组件来解决 JavaScript 中的日期。在 src/components/Note.js 中,导入库并更新日期标记以利用转换,如下所示:

// import the format utility from `date-fns`
import {format} from 'date-fns';
// update the date markup to format it as Month, Day, and Year
{format(note.createdAt, 'MMM Do YYYY')} Favorites:{' '}

格式化日期后,咱们能够应用款式化组件库来更新笔记布局:

import React from 'react';
import ReactMarkdown from 'react-markdown';
import {format} from 'date-fns';
import styled from 'styled-components';
// Keep notes from extending wider than 800px
const StyledNote = styled.article` max-width: 800px;
margin: 0 auto; `;
// Style the note metadata
const MetaData = styled.div` @media (min-width: 500px) { display: flex;
align-items: top;
} `;
// add some space between the avatar and meta info
const MetaInfo = styled.div` padding-right: 1em; `;
// align 'UserActions' to the right on large screens
const UserActions = styled.div` margin-left: auto; `;
const Note = ({note}) => {
return (
<StyledNote>
<MetaData>
<MetaInfo>
<img
src={note.author.avatar}
alt="{note.author.username} avatar"
height="50px"
/>
</MetaInfo>
<MetaInfo>
<em>by</em> {note.author.username} <br />
{format(note.createdAt, 'MMM Do YYYY')}
</MetaInfo>
<UserActions>
<em>Favorites:</em> {note.favoriteCount}
</UserActions>
</MetaData>
<ReactMarkdown source={note.content} />
</StyledNote>
);
};
export default Note;

咱们还能够在 NoteFeed.js 组件中的笔记之间增加一些空间和浅边框:

import React from 'react';
import styled from 'styled-components';
const NoteWrapper = styled.div`
max-width: 800px;
margin: 0 auto;
margin-bottom: 2em;
padding-bottom: 2em;
border-bottom: 1px solid #f5f4f0;
`;
import Note from './Note';
const NoteFeed = ({notes}) => {
return (
<div>
{notes.map(note => (<NoteWrapper key={note.id}>
<Note note={note} />
</NoteWrapper>
))}
</div>
);
};
export default NoteFeed;

通过这些更新,咱们将布局款式引入了咱们的应用程序。

动静查问

以后,咱们的应用程序蕴含三个路由,每个路由都是动态的。这些路由位于动态 URL,并且将始终收回雷同的数据申请。然而,应用程序通常须要动静路由和基于这些路由的查问。例如,Twitter.com 上的每条推文调配了惟一的 URL :twitter.com//status/. 这使用户能够在 Twitter 生态系统内以及网络上的任何中央链接和共享单个推文。

以后,在咱们的利用笔记中只能在摘要中拜访,然而咱们心愿容许用户查看并链接到各个笔记。为此,咱们将在 React 应用程序中设置动静路由以及 GraphQL 查问单个笔记。咱们的指标是使用户可能拜访 /note/处的路由。

首先,咱们将在 src/pages/note.js 中创立一个新的页面组件。咱们将把 props(属性)对象传递给组件,该对象包含通过React Routermatch属性。此属性蕴含无关路由门路如何与 URL 匹配的信息。这将使咱们可能通过以下形式拜访 URL 参数。

import React from 'react';
const NotePage = props => {
return (
<div>
<p>ID: {props.match.params.id}</p>
</div>
);
};
export default NotePage;

当初,咱们能够向 src/pages/index.js 文件增加相应的路由。该路由将蕴含一个 ID 参数,以:id示意:

// import React and routing dependencies
import React from 'react';
import {BrowserRouter as Router, Route} from 'react-router-dom';
// import shared layout component
import Layout from '../components/Layout';
// import routes
import Home from './home';
import MyNotes from './mynotes';
import Favorites from './favorites';
import NotePage from './note';
// define routes
const Pages = () => {
return (
<Router>
<Layout>
<Route exact path="/" component={Home} />
<Route path="/mynotes" component={MyNotes} />
<Route path="/favorites" component={Favorites} />
<Route path="/note/:id" component={NotePage} />
</Layout>
</Router>
);
};
export default Pages;

当初,拜访 http://localhost:1234/note/123将在咱们的页面上打印 ID:123。要对其进行测试,请应用你抉择的任何内容替换ID 参数,例如 /note/pizza或 /note/GONNAPARTYLIKE1999

这很酷,但不是很有用。让咱们更新咱们的 src/pages/note.js 组件,以对在 URL 中找到 ID 的笔记进行 GraphQL 查问。为此,咱们将应用 APINoteReact组件中的 note 查问:

import React from 'react';
// import GraphQL dependencies
import {useQuery, gql} from '@apollo/client';
// import the Note component
import Note from '../components/Note';
// the note query, which accepts an ID variable
const GET_NOTE = gql`
query note($id: ID!) {note(id: $id) {
id
createdAt
content
favoriteCount
author {
username
id
avatar
}
}
}
`;
const NotePage = props => {
// store the id found in the url as a variable
const id = props.match.params.id;
// query hook, passing the id value as a variable
const {loading, error, data} = useQuery(GET_NOTE, { variables: { id} });
// if the data is loading, display a loading message
if (loading) return <p>Loading...</p>;
// if there is an error fetching the data, display an error message
if (error) return <p>Error! Note not found</p>;
// if the data is successful, display the data in our UI
return <Note note={data.note} />;
};
export default NotePage;

当初,导航到带有 ID 参数的 URL 将出现相应的笔记或谬误音讯。最初,让咱们更新 src/components/NoteFeed.js 组件,以在 UI 中显示指向单个笔记的链接。

首先,在文件顶部,从 React Router 导入{Link}:

import {Link} from 'react-router-dom';

而后,更新JSX,使其蕴含指向笔记页面的链接,如下所示:

<NoteWrapper key={note.id}>
<Note note={note} />
<Link to={`note/${note.id}`}>Permalink</Link>
</NoteWrapper>

这样,咱们就在应用程序中应用了动静路由,并使用户可能查看各个笔记。

分页

目前,咱们仅检索应用程序主页中的 10 个最新笔记。如果要显示其余笔记,则须要启用分页。你可能会从本章的结尾以及咱们的 API 服务器的开发中回想起,咱们的 API 返回了游标,这是后果页面中返回的最初一个笔记的 ID。此外,API 返回 hasNextPage 布尔值,如果在咱们的数据库中找到其余笔记,则为 true。向咱们的API 发出请求时,咱们能够向其传递一个游标参数,该参数将返回接下来的 10 个我的项目。

换句话说,如果咱们有 25 个对象的列表(对应的 ID125),则在收回初始申请时,它将返回我的项目 110,游标值10hasNextPagetrue。如果咱们发送一个游标值为10 的申请,咱们将收到 11-20,游标值为20hasNextPage值为 true。最初,如果咱们做的第三申请,传递一个指针的20,咱们会在收到物品21-25,用游标的值25hasNextPage的值为 false。这正是咱们将在noteFeed 查问中实现的逻辑。

为此,让咱们更新 src/pages/home.js 文件以进行分页查问。在咱们的用户界面中,当用户单击“查看更多”按钮时,接下来的 10 条笔记应加载到页面上。咱们心愿这种状况下不会刷新任何页面。为此,咱们须要在查问组件中蕴含 fetchMore 参数,并仅在 hasNextPagetrue时显示 Button 组件。

当初,咱们将其间接写到咱们的主页组件中,能够很容易地将其隔离到本人的组件中或成为 NoteFeed 组件的一部分。

// if the data is successful, display the data in our UI
return (
// add a <React.Fragment> element to provide a parent element
<React.Fragment>
<NoteFeed notes={data.noteFeed.notes} />
{/* Only display the Load More button if hasNextPage is true */}
{data.noteFeed.hasNextPage && (<Button>Load more</Button>)}
</React.Fragment>
);

React 中的条件

在后面的示例中,咱们应用带有 && 运算符的内联 if 语句有条件地显示“加载更多”按钮。如果 hasNextPagetrue,则显示按钮。你能够在官网 React 文档中浏览无关条件渲染的更多信息。

当初咱们能够应用 onClick 处理程序更新组件。

当用户单击按钮时,咱们将要应用 fetchMore 办法进行附加查问,并将返回的数据附加到咱们的页面。

{data.noteFeed.hasNextPage && (
// onClick peform a query, passing the current cursor as a variable
<Button
onClick={() =>
fetchMore({
variables: {cursor: data.noteFeed.cursor},
updateQuery: (previousResult, { fetchMoreResult}) => {
return {
noteFeed: {
cursor: fetchMoreResult.noteFeed.cursor,
hasNextPage: fetchMoreResult.noteFeed.hasNextPage,
// combine the new results and the old
notes: [
...previousResult.noteFeed.notes,
...fetchMoreResult.noteFeed.notes
],
__typename: 'noteFeed'
}
};
}
})
}
>
Load more
</Button>
)}

先前的代码可能看起来有些毛糙,所以让咱们对其进行合成。咱们的组件包含一个 onClick 处理程序。单击该按钮时,将应用 fetchMore 办法执行新查问,并传递前一个查问中返回的游标值。返回后,updateQuery被执行,它更新咱们的光标和 hasNextPage 值,并将后果组合到一个数组中。

该__typename 是查问名称,它蕴含在 Apollo 的后果中。

进行此更改后,咱们便能够从笔记摘要中查看所有笔记。滚动到笔记底部,尝试一下。如果你的数据库蕴含 10 个以上的笔记,则该按钮将可见。单击“加载更多”将下一个 noteFeed 后果增加到页面。

论断

在本章中,咱们曾经介绍了很多基础知识。咱们曾经设置了 Apollo Client 来与咱们的 React 应用程序一起应用,并将多个 GraphQL 查问集成到咱们的 UI 中。GraphQL的弱小能力体现在编写单个查问的能力,这些查问恰好返回 UI 所需的数据。在下一章中,咱们将用户身份验证集成到咱们的应用程序中,容许用户登录并查看其笔记和收藏夹。

如果有了解不到位的中央,欢送大家纠错。如果感觉还能够,麻烦您点赞珍藏或者分享一下,心愿能够帮到更多人。

正文完
 0