概述
下面是一个视频和一个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' }),
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);
无别名
增加别名后
代码
本文所描述的代码放在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...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。