3

概述

下面是一个视频和一个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传递输入对象参数:

clipboard.png

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' }),
  graphql(gql`mutation (...) { ... }`, { 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);

无别名
clipboard.png

增加别名后
clipboard.png

代码

本文所描述的代码放在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...


developerworks
1.7k 声望266 粉丝