GraphQL 進階: Apollo Client 之 GraphQL Subscription 和 graphql容器

概述

下面是一個視頻和一個GIF動畫, 感覺一下基於Websocket, 經過GraphQL實現的即時聊天應用是個什麼鬼.html

視頻鏈接: https://v.qq.com/x/page/x0508...react

GIF動畫
圖片描述webpack

graphql() 函數是一個給組件增長數據邏輯(查詢, 修改, 刪除)的一個高階函數, 存在於 react-apollo 模塊中, 若是要使用它, 須要把它 import 進來. git

該函數接受一個React組件, 同時返回一個通過修改(增長數據邏輯)的React組件. 屬於設計模式中的裝飾器模式, 在不修改原組件的狀況下, 對組件增長額外的功能, 實現了「對修改關閉, 對擴展開放」的軟件工程原則.github

graphql 容器的基本形態以下:web

# 導入 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 提供的最重要的一個函數. 用這個函數能夠建立執行查詢何更新的高階組件. api

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...

相關文章
相關標籤/搜索