概述
下面是一个视频和一个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' }
export default graphql(TODO_QUERY,{ options: (props) => ({ }),})(MyComponent);
config.props
该属性用于定义一个映射函数. 传入组件自身的属性,和通过 graphql()
函数添加的属性(Query为,props.data
,Mutation 为 props.mutate
),这让我们能够构造一个新的属性对象,并把这个新的属性对象
传递给被graphql()
包装的组件.
config.props 的基本用途
从UI组件解耦 GraphQL 逻辑,让UI组件更简单,大体上讲就是UI组件只负责UI的渲染,
config.props
选项用于封装复杂的数据处理逻辑. UI组件和数据交互逻辑实现分离,并且通过config.props
关联.
config.name
我们方位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...