下面是一個視頻和一個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)有不一樣的行爲.
options
該對象能夠是一個純對象
, 或者是一個函數
. 用於定製GraphQL查詢的行爲, 好比, 給一個Mutation傳遞輸入對象參數:
options: ({ params }) => ({ variables: { text: '我是一個粉刷匠, 粉刷本領強4' }, }),
純對象很簡單, 形式爲:
const config = { name: 'getTodos' }
options 函數, 接收一個組件屬性做爲參數, 形式爲:
export default graphql(TODO_QUERY, { options: (props) => ({ }), })(MyComponent);
該屬性用於定義一個映射函數. 傳入組件自身的屬性, 和經過 graphql()
函數添加的屬性(Query爲, props.data
, Mutation 爲 props.mutate
), 這讓咱們可以構造一個新的屬性對象, 並把這個新的屬性對象
傳遞給被graphql()
包裝的組件.
config.props 的基本用途
config.props
可讓咱們把複雜的函數調用抽離成單獨的模塊, 而且做爲簡單的屬性傳遞給組件.
從UI組件解耦 GraphQL 邏輯, 讓UI組件更簡單, 大致上講就是UI組件只負責UI的渲染, config.props
選項用於封裝複雜的數據處理邏輯. UI組件和數據交互邏輯實現分離, 而且經過 config.props
關聯.
這選項的做用是避免一個組件中有多個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
爲 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(); }} /> ); } }
組件別名, 主要是給 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...