免责声明:我们不会详细描述“魔术精髓”和乐器起源的历史(对此已有很多论述)。我们希望那些已经熟悉GraphQL的人将从我们的实践经验中受益。
我们最近参与了照明设备的Internet目录的创建。要创建广告页面,站点管理员可以使用设计器:选择必要的块(例如,横幅或列表),在其中填充数据,定义显示顺序和其他设置。同时,该应用程序呈现了为每种块类型预先布局的组件。
在在线目录的每个类别中,显示了各个产品组的卡,并且当您将鼠标悬停在卡上时,将显示附加产品的属性列表。
我们需要以树形结构显示商品的属性,并提供足够高的处理请求速度。
我们根据请求选择了以下工作顺序:
- 请求特定类别的所有产品组(通常约50个组)。
- 索取每个组的产品列表。
- 请求每个产品的属性列表。
由于我们正在开发基于GraphQL的应用程序,因此我们已经为某些数据具有相当复杂的嵌套结构这一事实做好了准备。尽管设计这种结构对于后端开发是合乎逻辑的,但根据设计,必须在前端编写一些“额外”逻辑来处理数据并将其输出到组件。
由于GraphQL构造函数的特殊性,我们决定不在背面而是在正面收集属性和唯一值,然后以特定顺序渲染它们。但是,请求的处理速度太慢-最多20秒,这当然不适合我们。
因此,我们开始将每个请求划分为一些小的子查询,并分批加载数据。结果,应用程序的速度显着提高-请求花费的时间不超过2秒。尽管请求的数量增加了,但是系统的负载却减少了,加载未使用的数据的需求也消失了。
接下来,让我们直接更详细地讨论使用GraphQL。
使用GraphQL的功能
产品要求是我们使用Facebook开发的GraphQL查询语言。因此,我们不会无休止地争论GraphQL或REST哪个更好,而是考虑到其所有优势,决定以最有效的方式使用正确的技术。
我们考虑到GraphQL旨在主要通过具有单个端点来简化API的开发和维护。
GET /news
GET /posts
POST /news
POST /post
GraphQL具有单个端点。这意味着我们不需要发出两个单独的请求即可从两个不同的资源获取数据。 GraphQL将所有请求和变异合并到一个端点中,并可供参考,并且避免了REST API固有的版本控制。
GraphQL提供了使用特殊的查询语法优化性能并准确获取当前所需数据的能力:必填字段必须在查询中列出。
const FETCH_USER_DATA = gql`
query FetchUserData {
user {
firstName
lastName
date
}
}
`;
GraphQL使用强类型实体和类型架构,这与TypeScript和前端类型生成结合使用非常方便。
当然,其中许多功能都可以使用REST API来实现,但是GraphQL提供了开箱即用的功能。
为了与GraphQL进行客户端交互,我们选择了具有最佳文档的最受欢迎的解决方案-Apollo客户端库,该库可让您获取,缓存和修改应用程序数据。 Apollo Client使您能够使用请求和突变挂钩以及工具轻松跟踪下载/错误状态。
同样在前端,我们使用了NextJS框架,并考虑了以下因素:预先渲染(NextJS提供了非常简单的机制来实现静态生成和SSR的实现),对所有现有的CSS-in-js解决方案的支持,动态路由,对静态文件的支持(例如图片)在React组件中。
最后,选择了技术之后,我们继续进行开发。乍一看,一切看起来都不错:现代的库,完善的文档以及许多不同的用例。每种技术都经过单独设计,以促进舒适,快速的开发。通过创建必不可少的样板和布局UI组件,我们逐渐进入了库之间有效交互的阶段。在这里,所有的乐趣开始了。
深入研究NextJS的机制,我们可以看到它使用两种形式的预渲染器:静态生成和SSR。这两种策略都使用特殊的数据预加载功能实现:
`getInitialProps`(SSR)-首次加载时,它在服务器上运行,请求数据,然后将其作为道具传递给组件。
function Component ({data}) {
...
}
Component.getInitialProps = async (ctx) => {
const res = await fetch('https://...')
const json = await res.json()
return { data: json.data }
}
getStaticProps(静态生成)-在构建阶段运行。NextJS使用此函数返回的道具来预渲染页面。
export async function getStaticProps(context) {
return {
props: {}, // props
}
}
getServerSideProps(服务器端渲染)-NextJS在每个请求上使用该函数返回的数据作为道具预渲染页面。
export async function getServerSideProps(context) {
return {
props: {}, // props
}
}
因此,所有列出的函数都在组件外部声明,接收一些数据并将其传递给组件。这导致与Apollo客户互动的问题之一。该库提供了诸如“ useQuery”钩子和“ Query”组件之类的查询机制,并且所提出的方法均不能在该组件之外使用。考虑到这一点,在我们的项目中,我们决定使用next-with-apollo库,最后我们对结果感到满意。
我们也遇到过,Apollo Client的另一个已知问题是来自useQuery挂钩的请求无限循环的可能性。问题的症结在于,Apollo客户端会继续无限期地发送请求,直到成功接收数据为止。如果服务器由于某种原因无法返回数据,则可能导致组件在无限请求中“挂起”的情况。
在我们的案例中,原因之一是背面方案的更改。Apollo会自行生成类型,因此在前面编写查询时,我们必须遵循生成的类型以避免错误。例如,我们有一个工作要求,没有任何类型问题。但是,在后端更改架构时,类型会同时更改,这可能导致正在运行的查询停止运行。鉴于此,最好立即实施日志记录和清晰的错误处理系统,以节省团队的精力和时间。
我们认为,在graphql查询中可以准确指定应接收哪些数据非常有用。当发送大量请求时,这避免了处理不必要的数据。反过来,随着数据量的增长,这会严重影响性能。
值得注意的是,GraphQL的优势之一被认为是开发和API支持的便利,但是此属性对于后端开发更为重要,并且根据我们的观察,对前端没有显着影响。由于类型的生成,每次在背板上更改架构时,我们都会重写所有受影响的查询。如果我们需要在前端实现尚未实施的内容,贝克还必须完善该计划。同时,使用TypeScript时类型的生成使在编写代码的阶段捕获许多错误成为可能。
加起来
根据我们的观察,GraphQL被广泛用于各种类型的IT解决方案中,并为开发团队提供了一定的优势。让我们总结一下在开发项目时遇到的GraphQL的主要功能:
- . graphql , TypeScript .
- . GraphQL - . , ( , ). graphql «» ,
- . graphql- , . .
- . GraphQL , .
! , .