GraphQL在react中的应用示例详解

目录
  • 什么是 GraphQL
    • GraphQL出现的意义
      • 传统API存在的主要问题:
      • GraphQL 如何解决问题
    • GraphQL基本语法
      • 标量类型
      • 对象类型
      • 枚举类型
      • GraphQL 内置指令
  • 什么是 Apollo
  • apollo-server
    • 处理流程
      • 1.解析阶段
      • 2.校验阶段
      • 3.执行阶段
    • Schema
    • 给server端带来的便利性
      • 创建client
      • 将client注入到react
      • 数据请求
      • 数据缓存
  • apollo-client
  • 总结
    • GraphQL 的优缺点
      • 优点
      • 缺点

什么是 GraphQL

GraphQL由Facebook发起,其手机客户端自2012年起,就全面采用了GraphQL查询语言, 2015年, Facebook全面开源了第一份GraphQL规范。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,是一种规范,使得客户端能够准确地获得它需要的数据,而且没有任何冗余.

GraphQL出现的意义

传统API存在的主要问题:

  • 接口数量众多维护成本高:接口的数量通常由业务场景的数量决定,为了尽量减少接口数量,服务端工程师通常会对业务做抽象,首先构建粒度较小的数据接口,再根据业务场景对数据接口进行组合,对外暴露业务接口,即便这样,服务端对前端暴露的接口数量还是非常多,因为业务总是多变的。
  • 接口扩展成本高:出于带宽的考虑移动端我们要求接口返回尽量少的字段,PC 端通常要展现更多字段;考虑首屏性能,我们又要求对接口做合并;传统 API 应对这些需求,前后端都面临改造,成本较高。
  • 接口响应的数据格式无法预知:由于接口文档几乎总是不能及时更新,前端工程师无法预知接口响应的数据格式,影响前端开发进度。

GraphQL 如何解决问题

请求参数在发送到服务端之前会先经过 GraphQL Client 转换成客户端 Schema,这段 Schema 其实是一段 query 开头的字符串,描述了客户端的对数据的述求:调用哪个方法,传递什么样的参数,返回哪些字段。服务端拿到这段 Schema 之后,通过事先定义好的服务端 Schema 接收请求参数并执行对应的 resolve 函数提供数据服务。

GraphQL基本语法

参考 [GraphQL][1] 官网文档

标量类型

GraphQL 自带一组默认标量类型: Int:有符号 32 位整数。 Float:有符号双精度浮点值。 String:UTF‐8 字符序列。 Boolean:true 或者 false。 ID:ID 标量类型表示一个唯一标识符,通常用以重新获取对象或者作为缓存中的键。ID 类型使用和 String 一样的方式序列化。

对象类型

一个 GraphQL schema 中的最基本的组件是对象类型,它就表示你可以从服务上获取到什么类型的对象,以及这个对象有什么字段

type Character {
    name: String!
    list: [Episode!]!
}
myField: [String!]
myField: null
myField: []
myField: ['a', 'b']
myField: ['a', null, 'b']
myField: [String]!
myField: null
myField: []
myField: ['a', 'b']
myField: ['a', null, 'b']

GraphQL 对象类型上的每一个字段都可能有零个或者多个参数,

type Starship {
    id: ID!
    name: String!
    length(unit: LengthUnit = METER): Float
}

枚举类型

enum Episode {
    NEWHOPE
    EMPIRE
    JEDI
}

这表示无论我们在 schema 的哪处使用了 Episode,都可以肯定它返回的是 NEWHOPE、EMPIRE 和 JEDI 之一。

对象类型、标量以及枚举是 GraphQL 中你唯一可以定义的类型种类。但是当你在 schema 的其他部分使用这些类型时,或者在你的查询变量声明处使用时,你可以给它们应用额外的类型修饰符来影响这些值的验证。

type Character {
    name: String!
    list: [Episode]!
}

GraphQL 内置指令

GraphQL 中内置了两款逻辑指令,指令跟在字段名后使用。

@include 当条件成立时,查询此字段

query {
    search {
        actors @include(if: $queryActor) {
            name
        }
    }
}

@skip 当条件成立时,不查询此字段

query {
    search {
        comments @skip(if: $noComments) {
            from
        }
    }
}

  • 操作类型:指定本请求体要对数据做什么操作,类似与 REST 中的 GET POST。GraphQL 中基本操作类型有 query 表示查询,mutation 表示对数据进行操作,例如增删改操作,subscription 订阅操作。
  • 操作名称:操作名称是个可选的参数,操作名称对整个请求并不产生影响,只是赋予请求体一个名字,可以作为调试的依据。
  • 变量定义:在 GraphQL 中,声明一个变量使用符号开头,冒号后面紧跟着变量的传入类型。如果要使用变量,直接引用即可,例如上面的movie就可以改写成movie(name:符号开头,冒号后面紧跟着变量的传入类型。如果要使用变量,直接引用即可,例如上面的 movie 就可以改写成 movie(name: 符号开头,冒号后面紧跟着变量的传入类型。如果要使用变量,直接引用即可,例如上面的movie就可以改写成movie(name:name)。
query Hero($episode: Int!, $withFriends: Boolean!) {
    hero(episode: $episode) {
        name
        friends @include(if: $withFriends) {
        name
        }
    }
}

什么是 Apollo

Meteor 团队有着很丰富的数据流控制经验,他们发现了 Relay 的不便之处,引领业界通过使用他们开发的 Apollo 享受到更简洁的接口,Apollo 是基于GraphQL的全栈解决方案集合。包括了 apollo-client 和 apollo-server ;从后端到前端提供了对应的 lib ,使开发使用 GraphQL 更加的方便。

apollo-server

apollo-server是一个在nodejs上构建grqphql服务端的web中间件。支持express,koa 等框架。 参考 [apollo-server][2] 官网文档

处理流程

主要是通过官方graphql-js库进行处理

1.解析阶段

为了识别客户端 Schema, graphql-js 定义了一系列的特征标识符:

export const TokenKind = Object.freeze({
    BANG: '!',
    DOLLAR: '$',
    PAREN_L: '(',
    PAREN_R: ')',
    SPREAD: '...',
    COLON: ':',
    EQUALS: '=',
    BRACKET_L: '[',
    BRACKET_R: ']',
    ...
});

并定义了 AST 语法树规范,规定语法树支持以下节点:

export const Kind = Object.freeze({
    // Name
    NAME: 'Name',
    // Document
    DOCUMENT: 'Document',
    OPERATION_DEFINITION: 'OperationDefinition',
    VARIABLE_DEFINITION: 'VariableDefinition',
    VARIABLE: 'Variable',
    // Values
    INT: 'IntValue',
    FLOAT: 'FloatValue',
    STRING: 'StringValue',
    BOOLEAN: 'BooleanValue',
    ...
});

有了特征字符串与 AST 语法树规范,GraphQL Server 对客户端 Schema 进行逐字符扫描,如果客户端 Schema 不符合服务端定义的 AST 规范,解析过程会直接抛出语法异常。

2.校验阶段

校验阶段用于验证客户端 Schema 是否按照服务端 Schema 定义好的方式获取数据,比如:获取数据的方法名是否有误,必填项是否有值等等,校验范围一共有几十种,不一一举例。

{
    "errors":[
        {
            "message":"Cannot query field "getU" on type "Query". Did you mean "getUser"?",
            "locations":[
                {
                    "line":3,
                    "column":9
                }
            ]
        }
    ]
}

不仅返回结构化的报错信息,还非常人性化的告诉你正确的调用方式是什么。校验阶段通过之后会进入执行阶段.

3.执行阶段

执行阶段依赖的输入为:解析阶段的产出物 document ,服务端 Schema;其中 document 准确描述了客户端对数据的述求:请求哪个方法,参数是什么,需要哪些字段;服务端 Schema 描述了提供数据的方式。执行服务端 Schema 中的 resolve 函数,得到执行阶段的输出。每个类型的每个字段都由一个 resolver 函数支持,该函数由 GraphQL 服务器开发人员提供。

Schema

Schema可以说是GraphQL最具核心的部分,其描述了整个接口向外暴露的形式;像Restful API,我们会定义一个查询所有人的接口url定义为:/api/v1/user/getUsers,而查询人具体信息的接口url为:/api/v1/user/getUserById,前端人员调用起来很直观。但是graphql是完全不一样的使用方式,其向前端暴露的url就一个像/api/graphql之类的,那这么多接口怎么区分呢?

一个graphql接口都有一个Schema定义,其定义三种操作方式:query(查询),mutation(变更)和subscription(监听)。再往下延伸,一个查询中包含多个field,也就是多种不同的查询,比如query user查询人,query message查询消息,query weather查询天气,通过这些就实现了Restful API使用多个url来达到不同操作的效果。

给server端带来的便利性

由于 GraphQL 通过客户端 Schema 而不是通过 URL 描述数据述求,所以理论上服务端只需要对客户端暴露一个地址即可, 解决了接口数量众多维护成本高的问题; 同时,服务端提供的是全量字段,客户端可按需获取,面对接口扩展的需求,服务端没有开发成本;

import express from 'express';
import { graphiqlExpress, graphqlExpress } from 'apollo-server-express';
const app = express();
app.use('/graphql', graphqlExpress({
    schema,
}));
app.use('/graphiql', graphiqlExpress({
    endpointURL: '/graphql'
}));

apollo-client

参考 [apollo-client][3] 官网文档

创建client

import ApolloClient from "apollo-boost";
const client = new ApolloClient({
  uri: "https://48p1r2roz4.sse.codesandbox.io"
});

在我们将Apollo Client连接到React之前,让我们先尝试发送查询。记住首先导入gql用于将查询字符串解析为查询文档。

import gql from "graphql-tag";
...
client.query({
    query: gql`
      {
        rates(currency: "USD") {
          currency
        }
      }
    `
  })
  .then(result => console.log(result));

将client注入到react

react-apollo提供ApolloProvider组件,ApolloProvider类似于redux的provider。它会把apollo客户端放入到React app的上下文里,以便在组件树的任何地方都是可以获取到apollo客户端。

import React from "react";
import { render } from "react-dom";
import { ApolloProvider } from "react-apollo";
const App = () => (
  <ApolloProvider client={client}>
    <div>
      <h3>My first Apollo app </h3>
    </div>
  </ApolloProvider>
);
render(<App />, document.getElementById("root"));

数据请求

1.通过react-apollo提供的graphql函数获取数据,并connect到组建中,就可以在组件的this.props中看到多了个data对象,

import React, { Component } from 'react';
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
export const USERS_QUERY = gql`
  query UserQuery($pageNum: Int,$pageSize:Int){
    users(pageNum:$pageNum,pageSize:$pageSize ) {
      pageNum
      pageSize
      total
      data {
        userName
      }
    }
  }
`;
const withQuery = graphql(USERS_QUERY, {
  options: () => ({
    variables: {
      pageNum: 3,
      pageSize: 8,
    },
  }),
});
class List extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    const { data: { loading, error, users } } = this.props;
    if (loading) {
      return <div className="loading">Loading...</div>;
    }
    if (error) return `Error! ${error.message}`;
    const { total } = users;
    };
    return (
        <div>
            <p className="total">总共<span>{total}</span>人</p>
        </div>
    );
  }
}
export default withQuery(List);

data中包含loading, error, users等字段

当React安装Query组件时,Apollo Client会自动触发查询。如果想延迟触发查询,直到用户执行操作(例如单击按钮),该怎么办?对于这种情况,可以使用ApolloConsumer组件并直接调用client.query()。

import React, { Component } from 'react';
import { ApolloConsumer } from 'react-apollo';
class DelayedQuery extends Component {
  state = { dog: null };
  onDogFetched = dog => this.setState(() => ({ dog }));
  render() {
    return (
      <ApolloConsumer>
        {client => (
          <div>
            {this.state.dog && <img src={this.state.dog.displayImage} />}
            <button
              onClick={async () => {
                const { data } = await client.query({
                  query: GET_DOG_PHOTO,
                  variables: { breed: "bulldog" }
                });
                this.onDogFetched(data.dog);
              }}
            >
              Click me!
            </button>
          </div>
        )}
      </ApolloConsumer>
    );
  }
}

2.通过apollo提供的组件获取

import gql from "graphql-tag";
import { Query } from "react-apollo";
const GET_DOG_PHOTO = gql`
  query Dog($breed: String!) {
    dog(breed: $breed) {
      id
      displayImage
    }
  }
`;
const DogPhoto = ({ breed }) => (
  <Query query={GET_DOG_PHOTO} variables={{ breed }} pollInterval={500}>
    {({ loading, error, data, startPolling, stopPolling }) => {
      if (loading) return null;
      if (error) return `Error!: ${error}`;
      return (
        <img src={data.dog.displayImage} style={{ height: 100, width: 100 }} />
      );
    }}
  </Query>
);

通过设置pollInterval为500,每隔0.5秒看到一个新的小狗图像。 当我们从Query组件中获取数据时,看看Apollo Client幕后发生的事情。

1.当Query组件安装时,Apollo Client会为我们的查询创建一个observable。我们的组件通过Apollo Client缓存订阅查询结果。

2.首先,我们尝试从Apollo缓存加载查询结果。如果它不在那里,我们将请求发送到服务器。

3.数据恢复后,我们将其标准化并将其存储在Apollo缓存中。由于Query组件订阅了结果,因此它会自动更新数据。

数据缓存

创建本地缓存

import { ApolloClient } from 'apollo-client'
import { withClientState } from 'apollo-link-state'
import { HttpLink } from 'apollo-link-http'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { resolvers, typeDefs, defaults } from '../client/index'
const cache = new InMemoryCache()
const client = new ApolloClient({
  cache, // 本地数据存储
  link: withClientState({ resolvers, defaults, cache, typeDefs }).concat(
    new HttpLink({
      uri: 'http://localhost:4001/graphql',
      opts: {
        credentials: 'cross-origin',
      },
    })
  ),
})

要直接与缓存交互,可以使用Apollo Client方法readQuery,readFragment,writeQuery和writeFragment。

总结

如果使用 GraphQL,那么后端将不再产出 API,而是将 Controller 层维护为 Resolver,和前端约定一套 Schema,这个 Schema 将用来生成接口文档,前端直接通过 Schema 或生成的接口文档来进行自己期望的请求。

GraphQL 的优缺点

优点

所见即所得:所写请求体即为最终数据结构 减少网络请求:复杂数据的获取也可以一次请求完成 Schema 即文档:定义的 Schema 也规定了请求的规则 类型检查:严格的类型检查能够消除一定的认为失误

缺点

增加了服务端实现的复杂度:一些业务可能无法迁移使用 GraphQL,虽然可以使用中间件的方式将原业务的请求进行代理,这无疑也将增加复杂度和资源的消耗

以上就是GraphQL在react中的应用示例详解的详细内容,更多关于GraphQL react应用的资料请关注我们其它相关文章!

(0)

相关推荐

  • JS前端可视化GraphQL使用详解

    目录 正文 什么是 GraphQL? 什么是 GraphiQL? 开始 结尾 正文 了解事物幕后的运作方式往往有好处,但并非总是如此. 因为不必使事情过于复杂.而可视化图形界面在处理这么一个场景中,是首当其冲的. 在本文中,我将带你了解如何使用 GraphiQL 来辅助 GraphQL 的开发. 什么是 GraphQL? 在我们谈论 GraphiQL 之前,让我们先谈谈 GraphQL. GraphQL 是一种用于应用程序编程接口 (API) 的开源数据查询和操作语言,也是一种使用现有数据完成查

  • vue中对接Graphql接口的实现示例

    说明: 本文是本人正在搞nestjs+graphql+serverless训练营中对Graphql讲解的基础知识点,可能有点前后没对接上,文中提到的Graphql授权也是下小节介绍的 一.对原来的Express返回Graphql项目修改 本章节使用的代码是express返回Graphql的代码,在使用前要先对代码进行基本的配置,比如处理跨域问题(Graphql本质也是发送一个http请求,既然是这样在vue项目中自然存在跨域的问题,需要先处理) 1.安装跨域的包,并且配置中间件 npm inst

  • 新建的React Native就遇到vscode报警解除方法

    目录 新建的RN项目有警告 直接删除vscode报警的部分 禁掉vscode内置的TypeScript插件 引入Flow Language Support解除报警 新建的RN项目有警告 我相信AwesomeProject是很多人的第一个RN项目,包括我在内. npx react-native init AwesomeProject 但是当利用RN的脚手架搭建起来后,在vscode里打开项目,直接就会遇到如下这个vscode的警告: 'import type' declarations can o

  • React Native系列之Recyclerlistview使用详解

    目录 recyclerlistview的介绍与使用 1.安装 2.概述和功能 3. RecyclerListView的使用 1.dataProvider 2.LayoutProvider 3.rowRenderer 4.onEndReached 5.onEndReachedThreshold 6.extendedState 7.scrollViewProps RecyclerListView所有属性 recyclerlistview的介绍与使用 1.安装 npm install --save r

  • GraphQL入门总体创建教程

    目录 简介 简单示例 maven依赖 Schema 解析schema并关联对应的fetchers DataFetchers Default DataFetchers 总体创建过程 资料 简介 因为目前做的项目查询提供的接口都使用GraphQL替代典型的REST API,所以有必要去对它进行了解和源码的阅读.本篇主要大致了解下GraphQL. 一种用于API的查询语言,让你的请求数据不多不少.前端按需获取,后端动态返回(不需要的数据不会返回甚至不会查库),对比起典型的REST API将更加灵活,后

  • GraphQL在react中的应用示例详解

    目录 什么是 GraphQL GraphQL出现的意义 传统API存在的主要问题: GraphQL 如何解决问题 GraphQL基本语法 标量类型 对象类型 枚举类型 GraphQL 内置指令 什么是 Apollo apollo-server 处理流程 1.解析阶段 2.校验阶段 3.执行阶段 Schema 给server端带来的便利性 创建client 将client注入到react 数据请求 数据缓存 apollo-client 总结 GraphQL 的优缺点 优点 缺点 什么是 Graph

  • React.memo函数中的参数示例详解

    目录 React.memo?这是个啥? React.memo的第一个参数 父组件 子组件 React.memo优化 React.memo的第二个参数 父组件 子组件 React.memo优化 父组件 子组件 小结 React.memo?这是个啥? 按照官方文档的解释: 如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现.这意味着在这种情况下,React 将跳过渲染组件的操作并直

  • ThinkPHP Where 条件中常用表达式示例(详解)

    Where 条件表达式格式为: $map['字段名'] = array('表达式', '操作条件'); 其中 $map 是一个普通的数组变量,可以根据自己需求而命名.上述格式中的表达式实际是运算符的意义: ThinkPHP运算符 与 SQL运算符 对照表 TP运算符 SQL运算符 例子 实际查询条件 eq = $map['id'] = array('eq',100); 等效于:$map['id'] = 100; neq != $map['id'] = array('neq',100); id !

  • Vue中的vue-resource示例详解

    vue-resource特点 vue-resource插件具有以下特点: 1. 体积小 vue-resource非常小巧,在压缩以后只有大约12KB,服务端启用gzip压缩后只有4.5KB大小,这远比jQuery的体积要小得多. 2. 支持主流的浏览器 和Vue.js一样,vue-resource除了不支持IE 9以下的浏览器,其他主流的浏览器都支持. 3. 支持Promise API和URI Templates Promise是ES6的特性,Promise的中文含义为"先知",Pro

  • django中使用memcached示例详解

    目录 什么是memcached: 安装和启动memcached: windows linux(ubuntu) 启动memcached: telnet操作memcached: 添加数据: 获取数据: 删除数据: 通过python操作memcached: memcached的安全性: 在Django中使用memcached: 什么是memcached: memcached之前是danga的一个项目,最早是为LiveJournal服务的,当初设计师为了加速LiveJournal访问速度而开发的,后来被

  • Go Java算法之从英文中重建数字示例详解

    目录 从英文中重建数字 Java实现 Go实现 从英文中重建数字 给你一个字符串 s ,其中包含字母顺序打乱的用英文单词表示的若干数字(0-9).按 升序 返回原始的数字. 示例 1: 输入:s = "owoztneoer" 输出:"012" 示例 2: 输入:s = "fviefuro" 输出:"45" 提示: 1 <= s.length <= 105 s[i] 为 ["e","g&

  • React中父子组件通信详解

    目录 父组件向子组件通信 存在期 父组件向子组件通信 在父组件中,为子组件添加属性数据,即可实现父组件向子组件通信.传递的数据可以分成两类 子组件是作为属性来接收这些数据的 第一类就是数据:变量,对象,属性数据,状态数据等等 这些数据发生改变,子组件接收的属性数据就发生了改变. 第二类就是方法:父组件可以向子组件传递属性方法,子组件接收方法,并可以在组件内执行,有两种执行方式 注意:父组件传给子组件的方法是不能执行的,执行了相当于将方法的返回值传递给子组件. 第一种 作为事件回调函数执行 参数默

  • React中react-redux和路由详解

    目录 观察者模式解决组件间通信问题 react-redux connect方法 Provider组件 路由 使用路由 默认路由 路由重定向 获取路由参数 路由导航 观察者模式解决组件间通信问题 使用观察者解决组件间通信,分成两步 在一个组件中,订阅消息 在另一个组件中,发布消息 发布消息之后,订阅的消息回调函数会执行,在函数中,我们修改状态,这样就可以实现组件间通信了.这就是reflux框架的实现. react-redux redux早期被设计成可以在各个框架中使用,因此在不同的框架中使用的时候

  • threejs中使用drawbufferss示例详解

    目录 原因 历程 原生的使用 基本流程 灵光乍现 使用WebGLMultipleRenderTargets 原因 深度剥离实现之后,似乎会使得走样严重起来. 我意识到,这是因为 剥离这个过程,并没有什么讲究,只要是深度小于等于就剔除了,这样很可能就导致了,原本平滑的差值过渡出现了断层,突变. 简单的解决办法就是增顶点数. 一番搜寻weight oit算法的demo,但是只找到了用原生webgl写的,传送门.在费了几个日夜之后,终于看懂了,但是,还要把它用three实现.一般来说,没有使用编译的框

  • React中编写CSS实例详解

    目录 正文 内联样式 普通的CSS css modules css in js 样式组件 引入外部变量 默认值 引入全局样式 provider 样式继承 动态添加class 正文 目前,前端最流行的开发方式是组件化,而CSS的设计本身就不是为组件化而生的,所以在目前组件化的框架中都在需要一种合适的CSS解决方案 在组件化开发环境下的CSS,应该满足如下需求: 可以编写局部css: css具备自己的具备作用域,不会随意污染其他组件内的元素 可以编写动态的css: 可以获取当前组件的一些状态,根据状

随机推荐