我发现的关于GraphQL和Apollo的一切

In the older ages of front end development, fetching data from Web APIs was a very boring process. Indeed, the data model was frozen, meaning that you faced one of these situations:

在前端开发的较早年代,从Web API获取数据是一个非常无聊的过程。 实际上,数据模型已冻结,这意味着您面临以下情况之一:

  • The data model was too rich for your use case.

    对于您的用例而言,数据模型过于丰富。
  • Some pieces of data were missing.

    缺少一些数据。
  • You needed to chain requests to get the expected data structure.

    您需要链接请求以获取预期的数据结构。

To overcome these situations (and ease front-end developer tasks), GraphQL was created. It provides a new way to fetch data from the back end. Instead of dealing with frozen data structures, GraphQL offers a query language which allows us to get only what we need.

为了克服这些情况(并减轻前端开发人员的工作负担),创建了GraphQL。 它提供了一种从后端获取数据的新方法。 GraphQL无需处理冻结的数据结构,而是提供一种查询语言,该查询语言使我们仅能获得所需的内容。

Image for post
Photo by Federico Beccari on Unsplash
Federico Beccari Unsplash

In this article, I won’t explain how GraphQL works or how to set it up — Medium hosts many meaningful articles on GraphQL and Apollo, its client for web and mobile applications, and you can find documentation here:

在本文中,我不会解释GraphQL的工作方式或设置方式-Medium在GraphQL和Apollo(用于Web和移动应用程序的客户端)上托管了许多有意义的文章,您可以在这里找到文档:

And here:

和这里:

In this article, I will share some tips I discovered when using GraphQL and Apollo to ease development and make the Apollo-related code reusable.

在本文中,我将分享一些我在使用GraphQL和Apollo来简化开发并使Apollo相关代码可重用时发现的技巧。

Disclaimer: most of the code snippets concerns Vue.js and Vue Apollo. Anyway, as I introduce concepts and hints, I would guess that they can be applied to any other JavaScript Framework supported by GraphQL and Apollo.

免责声明: 大多数代码片段都涉及Vue.js和Vue Apollo。 无论如何,当我介绍概念和提示时,我猜想它们可以应用于GraphQL和Apollo支持的任何其他JavaScript框架。

第一件事首先... (First Things First…)

GraphQL (GraphQL)

As I said earlier, GraphQL is a new technology which modifies the relationships between back-end and front-end developers.

就像我之前说的,GraphQL是一项新技术,可修改后端和前端开发人员之间的关系。

Previously, both teams had to define a contract interface to ensure correct implementations. Sometimes, latencies could occur due to misunderstanding on object complexity or typings.

以前,两个团队都必须定义合同接口以确保正确实施。 有时,由于对对象复杂性或类型的误解可能会导致延迟。

Thanks to GraphQL, backends can provide all the data that front ends could need. Then, it’s up to them to “pick” the properties they require to build the interface.

借助GraphQL,后端可以提供前端可能需要的所有数据。 然后,由他们来“选择”构建接口所需的属性。

Moreover, GraphQL offers a web interface (named GraphiQL) to test queries and mutations (if you don’t understand what I’m writing about, please refer to documentation). It’s a clever tool to enable front ends to write requests, and browse the docs and typings.

此外,GraphQL提供了一个Web界面(名为GraphiQL)来测试查询和变异(如果您不了解我在说什么,请参阅文档 )。 这是一个使前端能够编写请求,浏览文档和打字的聪明工具。

查询语言 (A query language)

Using GraphQL implies understanding and mastering the query language included in the kit. It’s not a trivial one and is based on an object nesting syntax.

使用GraphQL意味着理解和掌握套件中包括的查询语言。 这不是一个简单的问题,它基于对象嵌套语法。

query GetProductInfo {
product {
id
name
price
}
}

When querying object seems simple, it’s not the case for the mutations. A mutation updates/inserts new data, potentially with some arguments.

当查询对象看起来很简单时,就不是这种突变了。 变异可能会带有一些参数来更新/插入新数据。

mutation ($label: String!) {
addTag(label: $label) {
id
label
}
}

Here, an argument is specified with its type on L1. Then, it’s used on the second line. The ending content between square brackets id and label defines the structure of the returned object, the result of the insertion.

在这里,参数的类型在L1上指定。 然后,将其用于第二行。 方括号idlabel之间的结尾内容定义了返回对象的结构,即插入的结果。

阿波罗 (Apollo)

Apollo is the client used to communicate with GraphQL. Whether you develop a web or mobile app, Apollo can support it.

Apollo是用于与GraphQL通信的客户端。 无论您是开发Web应用程序还是移动应用程序,Apollo都可以为其提供支持。

Image for post
https://www.apollographql.com/docs/intro/platform/ https://www.apollographql.com/docs/intro/platform/

Apollo supports several platforms:

Apollo支持多种平台:

Its configuration allows to define specific aspects such as: cache-network strategy, pipelines (covered here), Server-Side Rendering (Vue.js version), local state (Vue.js version), performance, error handling (covered here) or internationalization (covered here).

它的配置允许定义特定方面,例如: 缓存网络策略管道 (在此处发现), 服务器端渲染 ( Vue.js版本 ), 本地状态 ( Vue.js版本 ), 性能错误处理 (在此处发现)或国际化(在此处找到)。

使查询可重用 (Make Queries Reusable)

ES6进行救援! (ES6 to the rescue!)

They are many ways to create Apollo queries (also called GraphQL documents). The most common one is to use the graphql-tag parser.

它们是创建Apollo查询(也称为GraphQL文档)的多种方法。 最常见的一种是使用graphql-tag 解析器。

Then, a query will look like this:

然后,查询将如下所示:

import gql from 'graphql-tag'const hello query = gql`query sayHello {
hello
}`

It’s a good habit to store such queries in a dedicated folder e.g ~/api/apollo-queries and separate them by features in a specific file: you will gather queries regarding user, product or contract concerns.

将此类查询存储在专用文件夹(例如 ~/api/apollo-queries 并按特定文件中的功能将它们分开 是一个好习惯 :您将收集有关 user product contract 问题的 查询

You might know that GraphQL documents have several properties:

您可能知道GraphQL文档具有几个属性

  • query: the query itself with gql parser.

    query :使用gql解析器查询本身。

  • variables: if you need to pass some arguments.

    variables :如果您需要传递一些参数。

  • update: a callback function to format the query response or compute extra operations.

    update :一个回调函数,用于格式化查询响应或计算额外的操作。

This latter property is our entry point to create reusable queries. Indeed, with a simple query, you might want to execute different operations.

后一个属性是我们创建可重用查询的入口。 实际上,通过一个简单的查询,您可能想要执行不同的操作。

ES6 destructuring assignment and spread operator features are powerful tools to reuse a single query at several places. Here are some examples.

ES6解构分配和传播运算符功能是强大的工具,可以在多个地方重用单个查询。 这里有些例子。

变量不同 (Variables are different)

import gql from 'graphql-tag'


export const GetProductById = {
  query: gql`
    query GetProductById($productId: Int) {
      store {
        product(productId: $productId) {
          name
          price
          description
        }
      }
    }
  `,
  update: data => data.store.product
}

Update 回调不同 (Update callback is different)

import gql from 'graphql-tag'


export const GetProductById = gql`
  query GetProductById($productId: Int) {
    store {
      product(productId: $productId) {
        name
        price
        description
      }
    }
  }
`

在商店更新中重用查询 (Reuse query in a store update)

import gql from 'graphql-tag'


export const GetProductById = {
  query: gql`
    query GetProductById($productId: Int) {
      store {
        product(productId: $productId) {
          name
          price
          description
        }
      }
    }
  `,
  update: data => data.store.product
}


export const AddProduct = undefined // Not implemented

条件查询和参数查询 (Conditional and parametrised query)

As described in this section of Vue Apollo documentation, we can create conditional queries — aka Reactive query definition — so we can do it for variables.

如Vue Apollo文档的本所述,我们可以创建条件查询(也称为响应式查询定义),因此可以对变量执行此操作。

<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  //...
  apollo: {
    products: {
      query () {
        if (this.$route.query.mode === 'dense') {
          return gql`{
            products {
              id
              name
              price
            }
          }`
        } else {
          return gql`{
            products {
              id
              name
              price
              description
              availability
              imgUrl
            }
          }`
        }
      }
    }
  }
})
</script>
<script lang="ts">
import Vue from 'vue'

import { GetProductById } from '@/api/apollo-queries'

export default Vue.extend({
  //...
  apollo: {
    contract: {
      ...GetProductById,
      variables() {
        return { productId: +this.$route.params.id }
      }
    }
  }
})
</script>

In addition, you can inject any piece of code you want in query template because gql parser is just a string interpolation. So, you can easily write this mutation:

此外,由于gql解析器只是一个字符串插值,因此您可以在查询模板中插入任何代码。 因此,您可以轻松编写此突变:

gql`
mutation UpdateBirthdate ($bd: String!) {
updateBirthdate(input: {
birthDate: ${'"' + moment($bd).format('YYYY-MM-DD') + '"'}
}){
ok
}
}
`

It’s not a clean practice, but it sometimes saves time and effort. It’s preferable to sanitize inputs before sending them to the mutation.

这不是一个干净的做法,但有时可以节省时间和精力。 最好在将输入发送到变异之前先清理输入。

Moment.js is a JS library to handle DateTimes.

Moment.js是用于处理DateTimes的JS库。

上下文化GraphQL查询 (Contextualizing GraphQL queries)

为查询命名 (Name your query)

If you inspect the HTTP Request sent when querying your GraphQL server, you can see an operationName property:

如果检查查询GraphQL服务器时发送的HTTP请求,则可以看到operationName属性:

Image for post
Source: Author
资料来源:作者

It may seem pointless to add a name to Apollo queries but it can be very useful when debugging or if you want to track REST APIs calls. Indeed, in my company we want to track these requests for performance and server efficiency.

在Apollo查询中添加名称似乎没有意义,但是在调试或要跟踪REST API调用时,它可能非常有用 。 确实,在我公司中,我们希望跟踪这些对性能和服务器效率的要求。

Naming queries is really easy and handy:

命名查询确实非常容易和方便:

query GetProductInfo { 
product {
id
name
price
}
}

国际化 (Internationalization)

Internationalization (or i18n) is about adapting the language and culture in your app. However it might be annoying to provide translations for each language — it weighs the bundle down and requires updates.

国际化(或i18n)与适应应用程序中的语言和文化有关。 但是,为每种语言提供翻译可能会很烦人-压缩了软件包的重量并需要更新。

Did you know that GraphQL server can support internationalization thanks to a query parameter? It just requires some modifications server side.

您是否知道GraphQL服务器可以通过查询参数支持国际化? 它只需要在服务器端进行一些修改。

Regarding the front end, an additional configuration must be done. It involves specifying a pipeline on the HttpLink setup of the Apollo client — nothing difficult:

关于前端,必须完成其他配置。 它涉及在Apollo客户端的HttpLink设置上指定管道-没什么困难的:

import ApolloClient from 'apollo-client'
import { createHttpLink } from 'apollo-link-http'


const fetchByLang = (uri: string, options: any) => {
  const lang = localStorage.getItem('lang') || 'en'
  return fetch(`${uri}?lang=${lang}`, options)
}


export const apolloClient = new ApolloClient({
  link: createHttpLink({
    uri: 'http://graphql-server-url.com/v1',
    fetch: fetchByLang
  })
})

You can chain operations in the pipeline, more details here.

你可以在管道连锁经营,更多的细节 在这里

改善编码体验 (Improve the Coding Experience)

You may have noted that the GraphQL query response contains a __typename property:

您可能已经注意到GraphQL查询响应包含__typename属性:

Image for post
Source: Author
资料来源:作者

When using the same object in a mutation, the following error will appear:

在突变中使用同一对象时,将出现以下错误:

"Variable "$data" got invalid value {
"__typename": "UserSchema",
//...
"phone": "06 07 08 09 11"
}.In field "__typename": Unknown field."

Indeed, the __typename property is still present and to make the mutation work it has to be removed. Digging into the InMemoryCache documentation, we can remove this property from query response:

实际上, __typename属性仍然存在,并且为了使突变起作用,必须将其删除。 深入研究InMemoryCache 文档 ,我们可以从查询响应中删除此属性:

import ApolloClient from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { createHttpLink } from 'apollo-link-http'


export const apolloClient = new ApolloClient({
  link: createHttpLink({
    uri: `http://graphql-server-url.com/`,
    //...
  }),
  cache: new InMemoryCache({ addTypename: false })
})

订购查询 (Ordering queries)

At some points in the code, several queries must be fired in the same time. Sometimes, according to the response of a query, we want to fire a new one or skip a request.

在代码中的某些点上,必须同时触发多个查询。 有时,根据查询的响应,我们想触发一个新查询或跳过一个请求。

Luckily, Apollo offers a way to ease these tasks! Indeed, on the query object, a skip property exists:

幸运的是,阿波罗提供了一种减轻这些任务的方法! 实际上,在query对象上,存在一个skip属性:

<script lang="ts">
// QUERY_A is an object like { query: GraphQL query, variables: ..., etc }
// QUERY_B is a GraphQL query

import Vue from 'vue'
export default Vue.extend({
  //...
data: () => ({
  canTriggerQueryA: false
}),
apollo: {
    queryA: {
      ...QUERY_A,
      skip() { return !this.canTriggerQueryA }
    },
    queryB: {
      query: QUERY_B,
      update: function(data: any) {
        if (data.ok) { // Or any relevant condition
        this.canTriggerQueryA = true
        
        return data
      }
    }
  }
})
</script>

On the other hand, to skip requests:

另一方面,要跳过请求:

<script lang="ts">
// QUERY_A is an object like { query: GraphQL query, variables: ..., etc }
// QUERY_B is a GraphQL query
import Vue from 'vue'
export default Vue.extend({
  //...
  apollo: {
    queryA: QUERY_A,
    queryB: {
      query: QUERY_B,
      update: function(data: any) {
        if (data.ok) // Or any relevant condition
          this.$apollo.queries.queryA.skip = true
        
        return data
      }
    }
  }
})
</script>

打字稿支持 (Typescript Support)

Vue Apollo does not support Typescript directly. However, you can add some typings and integrate some Typescript features in your code with any problem.

Vue Apollo不直接支持Typescript。 但是,您可以添加一些类型并将代码中的某些Typescript功能集成到任何问题中。

Code generators exist to generate typings based on GraphQL schemas. Personally, I didn’t use one of them so if you are interested in, please refer to:

存在代码生成器以基于GraphQL模式生成类型。 就个人而言,我没有使用其中之一,因此如果您对此感兴趣,请参阅:

改善用户体验 (Improve the User Experience)

使用加载状态 (Use the loading state)

As response time from server is uncertain, we must warn the user something is processing. The current trend is to use a Skeleton:

由于来自服务器的响应时间不确定,因此我们必须警告用户正在处理的东西。 当前的趋势是使用Skeleton

Image for post
Example of a Skeleton (Source: Author)
骨架示例(来源:作者)

GraphQL query Object is super developer-friendly when it deals with managing loading state. Indeed, it offers a boolean property loading. Thus, we can use it in the template:

GraphQL查询对象在管理加载状态时超级友好。 确实,它提供了布尔属性 loading 。 因此,我们可以在模板中使用它:

<template>
  <my-custom-skeleton
    v-if="$apollo.queries.myQuery.loading"
    size="L"
  />
</template>

使用Apollo Store可以缩短响应时间 (Use Apollo Store for a reduced response time)

The Apollo Store acts like a cache. It save HTTP Requests and allows us to get data almost instantaneously. If you use the cache-and-network strategy (by default), the Apollo Client will get data from the Store first and, if data is missing, on the GraphQL server.

阿波罗商店的行为就像一个缓存。 它保存了HTTP请求,并允许我们几乎立即获取数据。 如果您使用cache-and-network策略(默认情况下),则Apollo客户端将首先从存储中获取数据,如果缺少数据,将在GraphQL服务器上获取。

I recommend using the Apollo Client Developer Tools Chrome extension to easily manipulate and debug the Apollo Store.

我建议使用Apollo客户端开发人员工具Chrome扩展程序来轻松操纵和调试Apollo商店。

If mutations are correctly implemented on back end side, they will return the updated resource in the response. This behaviour allows to update the Apollo Store, so when you navigate elsewhere in your app, the data will be already available — you won’t need to do other queries.

如果在后端正确实施了变异,则它们将在响应中返回更新的资源。 此行为可以更新Apollo商店,因此,当您在应用程序中的其他位置导航时,数据将已经可用-无需执行其他查询。

// Update the cache with the created product
update: (store, { data: { newProduct } }) => {
  // Read the data from cache for the query
  const data = store.readQuery({ query: GET_PRODUCTS })
  // Add our product from the mutation to the end
  data.products.push(newProduct)
  // Write our data back to the cache.
  store.writeQuery({ query: GET_PRODUCTS, data })
}


// GET_PRODUCTS is a GraphQL query returning the list of products

错误处理 (Error handling)

Various errors can appear during the navigation on your app. Whether they come from an internal server error or network failure, they must be handled.

在您的应用上导航期间,可能会出现各种错误。 无论是内部服务器错误还是网络故障,都必须对其进行处理。

This is managed at the Apollo Provider level. Here, we handle a network error in case of the session token (a cookie) is missing:

这是在Apollo提供程序级别进行管理的。 在此,如果会话令牌(cookie)丢失,我们将处理网络错误:

import VueApollo from 'vue-apollo'
import { apolloClient } from '@/api/apollo_client'


Vue.use(VueApollo)


const apolloProvider = new VueApollo({
  defaultClient: apolloClient,
  errorHandler: async (err) => {
    // If a network error occurred because no cookie
    // Auto-Login again
    if (err.message.includes('Missing Session token')) {
      // Auto-Login logic here...
    }
  }
})

最后的话 (Final Words)

There’s no doubt: GraphQL is super powerful. It eases coding experience and interactions between back end and front end teams. However, it involves some drawbacks, especially when it deals with Typescript support. Plus, ordering queries is not that easy and can become more complex depending on your data model and the way you want to build your page.

毫无疑问:GraphQL超级强大。 它简化了编码经验以及后端团队与前端团队之间的交互。 但是,它具有一些缺点,尤其是在处理Typescript支持时。 另外,对查询进行排序并不是那么容易,并且可能会变得更加复杂,具体取决于您的数据模型和构建页面的方式。

资源资源 (Resources)

翻译自: https://medium.com/better-programming/everything-i-discovered-about-graphql-and-apollo-e774d1e11638

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值