GraphQL 进阶: Apollo Client 之 GraphQL Subscription 和 graphql容器

概述

下面是一个视频和一个GIF动画,感受一下基于Websocket,通过GraphQL实现的即时聊天应用是个什么鬼.

视频连接: https://v.qq.com/x/page/x0508...

GIF动画

graphql() 函数是一个给组件增加数据逻辑(查询,修改,删除)的一个高阶函数,存在于 react-apollo 模块中,如果要使用它,需要把它 import 进来.

该函数接受一个React组件,同时返回一个经过修改(增加数据逻辑)的React组件. 属于设计模式中的装饰器模式,在不修改原组件的情况下,对组件增加额外的功能,实现了「对修改关闭,对扩展开放」的软件工程原则.

graphql 容器的基本形态如下:

# 导入 graphql 函数
import { graphql } from 'react-apollo';
# 函数签名,参数分别为GraphQL查询(通过gql 模板标签进行构造,一个可选的配置对象,以及一个被包装的React组件)
graphql(query,[config])(component)

graphql() 函数有两个参数

第一个参数为通过 gql 包裹的查询字符串,如:

const TODO_QUERY = gql`query Todo {
  todos: {
    id
    text
  }
`}

第二个参数为一个配置对象,方括号表示其是可选的,可省略
第三个参数为被包装的React组件.

graphql() 函数是 react-apollo 提供的最重要的一个函数. 用这个函数可以创建执行查询何更新的高阶组件.

graphql() 函数可以这样用:

function TodoApp({ data: { todos } }) {
  return (
    <ul>
      {todos.map(({ id,text }) => (
        <li key={id}>{text}</li>
      ))}
    </ul>
  );
}
export default graphql(gql`
  query TodoAppQuery {
    todos {
      id
      text
    }
  }
`)(TodoApp);

也可以定义中间函数

# 中间函数
const withTodoAppQuery = graphql(gql`query { ... }`);
# 传入React组件给这个中间函数
const TodoAppWithData = withTodoAppQuery(TodoApp);
# 导出这个组件
export default TodoAppWithData;

graphql() 函数也可以作为装饰器使用:

@graphql(gql`
  query TodoAppQuery {
    todos {
      id
      text
    }
  }
`)
export default class TodoApp extends Component {
  render() {
    const { data: { todos } } = this.props;
    return (
      <ul>
        {todos.map(({ id,text }) => (
          <li key={id}>{text}</li>
        ))}
      </ul>
    );
  }
}

graphql() 函数的使用依赖于在React组件树的根外层再包装一个 <ApolloProvider/> 组件. <ApolloProvider /> 在其属性上提供了一个 ApolloClient 实例用于访问数据.

通过 graphql() 函数增强的组件依据GraphQL的查询类型(Query,Mutation,Subscription)有不同的行为.

配置对象

config.options

options 该对象可以是一个纯对象,或者是一个函数. 用于定制GraphQL查询的行为,比如,给一个Mutation传递输入对象参数:

options: ({ params }) => ({
  variables: {
    text: '我是一个粉刷匠,粉刷本领强4'
  },}),

纯对象很简单,形式为:

const config = {
  name: 'getTodos'
}

options 函数,接收一个组件属性作为参数,形式为:

export default graphql(TODO_QUERY,{
  options: (props) => ({
  }),})(MyComponent);

config.props

该属性用于定义一个映射函数. 传入组件自身的属性,和通过 graphql() 函数添加的属性(Query为,props.data,Mutation 为 props.mutate),这让我们能够构造一个新的属性对象,并把这个新的属性对象传递给被graphql()包装的组件.

config.props 的基本用途

  • config.props 可以让我们把复杂的函数调用抽离成单独的模块,并且作为简单的属性传递给组件.

  • 从UI组件解耦 GraphQL 逻辑,让UI组件更简单,大体上讲就是UI组件只负责UI的渲染,config.props 选项用于封装复杂的数据处理逻辑. UI组件和数据交互逻辑实现分离,并且通过 config.props 关联.

config.name

这选项的作用是避免一个组件中有多个GraphQL查询,或者Mutation名称上的冲突.

我们方位GraphQL查询结果的数据通常是通过this.props.data返回数据的,data 属性是通过graphql() 高阶函数注入到我们的组件属性中的,这个名字有时候会和我们组件本身的属性名称冲突,为了解决冲突问题,可以在graphql()函数第二个参数配置对象中设置一个 name,然后通过 this.props.${name} 来访问这个属性.

export default compose(
  graphql(gql`mutation (...) { ... }`,{ name: 'createTodo' }),graphql(gql`mutation (...) { ... }`,{ name: 'updateTodo' }),{ name: 'deleteTodo' }),)(MyComponent);
function MyComponent(props) {
  console.log(props.createTodo);
  console.log(props.updateTodo);
  console.log(props.deleteTodo);
  return null;

这样,我们就可以通过 this.props.createTodo,this.props.updateTodo,this.props.deleteTodo 来访问我们需要的数据了.

config.withRef

设置 config.withRef 为 true 时,可以通过graphql() 返回的高阶组件上调用 getWrappedInstance 方法获取被包装组件的实例. 通常我们需要访问被包装组件 的属性和方法是需要把这个选项设置为true,默认为false

# 创建一个UI组件

class HelloWorld extends Component {
  saySomething() {
    console.log('Hello,world!');
  }
  render() {
  }
}

# 添加数据逻辑,编程一个支持GraphQL的组件,我们简称GraphQL组件

const HelloWorldWithData = graphql(
  gql`{ ... }`,{ withRef: true },)(HelloWorld);

# 使用 GraphQL 组件
# 通过该组件的ref属性,我们能够访问到原始的 HelloWorld 组件实例

class Container extends Component {
  render() {
    return (
      <HelloWorldWithData ref={component => {
          assert(component.getWrappedInstance() instanceof HelloWorld);
          component.saySomething();
        }}
      />
    );
  }
}

config.alias

组件别名,主要是给 React Devtools 使用,用于区分多个不同的高阶组件

export default compose(
  graphql(gql`{ ... }`,{ alias: 'withCurrentUser' }),graphql(gql`{ ... }`,{ alias: 'withList' }),)(MyComponent);

看一下别名的效果,我们实际的组件名称为FeedbackList,通过graphql()高阶组件包装后的组件名称为Apollo(FeedbackList),如果组件不多的情况下,我们很好分辨不同的组件,如果一个单页应用中使用了大量的graphql()高阶组件,这样的名字容易引其混乱,因此我们通过alias能够避免名称上的混乱,让组件更容易识别. 下面我们看一下代码:

import React,{ Component } from 'react'
import PropTypes from 'prop-types'
import { graphql,gql,withApollo,compose } from 'react-apollo'

// 查询文本
import QUERY_FEEDBACKS from './graphql/ListFeedback.graphql'
import SUBSCRIPTION_NEW_FEEDBACKS from './graphql/SubscribeAddFeedback.graphql'

// 反馈列表组件
class FeedbackList extends Component {
  // constructor(props) {
  //   super(props)
  // }
  componentWillMount() {
    this.props.subscribeToNewFeedback();
  }
  render() {
    if(this.props.data.loading == true) {
      return <div>Loading...</div>
    } else {
      return (
        <ul>{
          this.props.data.feedbacks.map((item,index) => {
            console.log(item.id)
            return (
              <div key={item.id}>{item.text}</div>
            )
          })
        }</ul>
      )
    }
  }
}
// 属性验证
FeedbackList.propTypes = {
  subscribeToNewFeedback: PropTypes.func.isRequired
}
// 高阶组件
export default graphql(QUERY_FEEDBACKS,{
  // name: 'FeedbackList',options: ({ params }) => ({
    variables: {
      key: 'value'
    },// 无别名时的效果
  // alias: 'FeedbackListWithData' 
})(FeedbackList);

无别名

增加别名后

代码

本文所描述的代码放在Github上,可以Clone下来进行学习和测试,代码中的数据是通过一个内存数组存储的,服务器重启后数据丢失. 如果需要持久化,可以改为使用数据库.

示例代码实现了GraphQL的订阅模式,客户端通过 Websocket 建立到服务器的长连接. 可以一次作为「使用GraphQL实现即时聊天应用」的基础,示例代码包含完整的服务器和客户端代码,可通过下面两行命令启动服务器和客户端.

# 启动GraphQL服务器 
# GraphQL服务器,提供GraphQL查询接口: http://localhost:7001/api
# 订阅服务器,订阅功能: http://localhost:7003/feedback

yarn server 

# 启动 webpack dev server,提供Web界面: http://localhost:7001

yarn client

GraphiQL 查询工具,可以通过 http://localhost:7001/graphiql 访问. 启动服务器和客户端后,可以通过在 GraphiQL 工具中执行如下的查询看到效果:

查询

mutation AddFeedback($data: FeedbackInput!) {
  addFeedback(data: $data) {
    id
    text
  }
}

变量

{
  "data": {
    "text": "我是一个粉刷匠,粉刷本领强"
  }
}

这个连接是订阅

http://localhost:7001/graphiq...

这个连接是添加一条反馈(Feedback)记录,以及对应的变量. 执行此Mutation,会在客户端和订阅窗口看到数据的实时更新.

http://localhost:7001/graphiq...

参考资料

http://dev.apollodata.com/rea...
https://github.com/apollograp...
https://github.com/apollograp...
http://dev.apollodata.com/rea...
https://webpack.js.org/config...

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom