使用graphql和apollo client构建react web应用
graphql是一种用于 API 的查询语言(摘自官网)。
我们为什么要用graphql?
相信大家在开发web应用的时候常常会遇到以下这些问题:后端更新了接口却没有通知前端,从而导致各种报错;后端修改接口字段名或者数据类型,前端也要跟着改,同时还要重新测试;项目涉及的接口数量繁多,如果是使用typescript的话还要手动的一个接口一个接口的去写interface。如果项目中使用了graphql的话,以上这些问题都会改善很多。利用插件,graphql能够自动化的生成接口的相应typescript interface,需要的字段以及数据结构都由前端编写的graphql代码决定,不用实际请求就可以知道服务器会返回什么数据。
举一个简单的例子:向部署了graphql的服务器发送以下graphql代码:
query:query DroidById($id: ID!) { // 调用名为DroidById的Query
droid(id: $id) { // 调用DroidById这个query的查询字段droid(相当于方法),传入id
name // 要求服务器返回实体中的name字段
}
}
variables:{
"id": 1
}
如果id为1的相应数据存在,就会获得这样的响应:
{
"data": {
“droid”:{
“name”:”his name”
}
}
}
这个是查询操作,修改操作也很简单:
query:mutation NHLMutation($id:Int!){ // 调用名为NHLMutation的Mutation,变量id类型为int,必传
deletePlayer(id:$id) // 调用NHLMutation下的deletePlayer方法,传入id
}
variables:{
id:1
}
如果成功的话,服务器则返回:
{
"data": {
"deletePlayer": true // 具体的返回数据类型可用内省(introspection)功能查到
}
}
更多graphql的相关功能和语法,见官方教程:http://graphql.cn/learn
了解完了graphql,接下来介绍一个基于graphql的框架:apollo(官网:https://www.apollographql.com/docs/react/)。它集成了状态管理、错误处理、Loading效果等功能,在react中如果数据由apollo来管理的话,基本上就没redux什么事了。apollo会尽可能地帮你解决技术上的问题,让你专心于业务。
官网的文档和教程已经写的很详细了,但如果有具体的案例的话应该会理解得更深刻。以下就是一个针对于Player类的一个增删改查小应用。

应用的后台是使用.net core编写的,地址:https://github.com/axel10/graphql-demo-backend 安装完.net core sdk后cd到NHLStats.Api目录下运行dotnet run即可在localhost:5000端口上启动服务器。localhost:5000/graphql为graphql endpoint,可调试graphql。
在开始编写业务代码之前,先用graphql-code-generator来生成graphql服务器提供的接口(types.d.ts),这一步由于按照官网上提供的教程来就行,过程十分简单,这里就直接略过。详见https://graphql-code-generator.com/docs/getting-started/
首先是查询:
先编写graphql语句:(query/player.ts)
export const CREATE_PLAYER = gql`
mutation ($player: PlayerInput!) {
createPlayer(player: $player) {
id name birthDate
}
}
`
然后是具体逻辑(index.tsx)
import React from 'react'
import { ApolloProvider, Query } from 'react-apollo'
import ReactDOM from 'react-dom'
import { Create } from 'src/components/createPlayerForm'
import { GET_PLAYER } from 'src/querys/player'
import { NhlMutation, NhlQuery, PlayerType } from 'src/types'
import { client } from 'src/utils/apolloClient'
import './base.less' class PlayerList extends React.Component { public render () {
return (
<div>
<Query query={GET_PLAYER}>
{
({ loading, error, data }) => {
if (loading) return <p>Loading...</p>
if (error) return <p>Error :(</p>
const players: PlayerType[] = data.players
return players.map((o, i) => (
<div key={i}}>
{o.name} {o.birthDate}
</div>
))
}
}
</Query>
</div>
)
}
}
接着渲染组件:
import ApolloClient from 'apollo-boost'
const client = new ApolloClient({
uri: 'http://localhost:5000/graphql' //graphql服务器的endpoint
})
ReactDOM.render(
<div>
<ApolloProvider client={client}>
<PlayerList/>
</ApolloProvider>
</div>, document.getElementById('root'))
这样我们就完成了取出数据并渲染这一步。接下来我们来试着创建player。
先编写graphql:(querys/player.ts)
export const CREATE_PLAYER = gql`
mutation ($player: PlayerInput!) {
createPlayer(player: $player) {
id name birthDate
}
}
`
新建components/createPlayerForm/index.tsx:
import React from 'react'
import { Mutation, MutationFunc } from 'react-apollo'
import { CREATE_PLAYER, GET_PLAYER } from 'src/querys/player'
import { NhlMutation, NhlQuery, PlayerInput } from 'src/types'
import { FormUtils } from 'src/utils/formUtils'
import styles from './style.less' interface IState {
form: PlayerInput
} const initState: IState = {
form: { name: '' }
} const formUtils = new FormUtils<IState>({
initState
}) export class Create extends React.Component { public handleCreateSubmit = (createPlayer: MutationFunc, data) => (e: React.FormEvent) => {
e.preventDefault()
const form = e.target as HTMLFormElement
createPlayer({ variables: { player: formUtils.state[form.getAttribute('name')] } }) // 取出表单数据并提交
} public handleUpdate = (cache, { data }: { data: NhlMutation }) => { // 服务器相应成功后更新本地数据
const createdPlayer = data.createPlayer
const { players } = cache.readQuery({ query: GET_PLAYER }) as NhlQuery // 先读取本地数据
cache.writeQuery({ query: GET_PLAYER, data: { players: players.concat(createdPlayer) } }) // 写入处理后的数据
} public render () {
return (
<div className={styles.CreatePlayer}>
新增player
<Mutation mutation={CREATE_PLAYER}
update={this.handleUpdate}
>
{
(createPlayer, { data }) => (
<form name='form' onSubmit={this.handleCreateSubmit(createPlayer, data)}>
<div>
<label>
姓名
<input type='text' name='name' onChange={formUtils.bindField}/>
</label>
</div>
<div>
<label>
身高
<input type='number' name='height' onChange={formUtils.bindField}/>
</label>
</div>
<div>
<label>
出生日期
<input type='date' name='birthDate' onChange={formUtils.bindField}/>
</label>
</div>
<div>
<label>
体重
<input type='number' name='weightLbs' onChange={formUtils.bindField}/>
</label>
</div>
<button type='submit'>提交</button>
</form>
)
}
</Mutation>
</div>
)
}
}
完成后渲染:
ReactDOM.render(
<div>
<ApolloProvider client={client}>
<PlayerList/>
<Create/>
</ApolloProvider>
</div>, document.getElementById('root')) 这样我们就可以看到新增player的表单了。 接下来是修改模态框:(components/editPlayerModal/index.tsx) import * as React from 'react'
import { Mutation, MutationFunc } from 'react-apollo'
import { EDIT_PLAYER, GET_PLAYER } from 'src/querys/player'
import { NhlQuery, PlayerInput, PlayerType } from 'src/types'
import { removeTypename } from 'src/utils/utils'
import { FormUtils } from '../../utils/formUtils'
import styles from './style.less' interface IState {
form: PlayerInput
} const initState: IState = {
form: { name: '' }
} const formUtils = new FormUtils<IState>({
initState
}) export default class EditPlayerModal extends React.Component<{ player: PlayerType, onCancel: () => void }> { public formName = 'edit' constructor (props) {
super(props)
formUtils.state[this.formName] = this.props.player
} public handleEditSubmit = (editPlayer: MutationFunc, data) => (e: React.FormEvent) => {
const player = removeTypename(formUtils.state[this.formName]) // 删除apollo为了进行状态管理而添加的__typename字段,否则报错
editPlayer({
variables: { player },
update (cache, { data }) {
const { players } = cache.readQuery({ query: GET_PLAYER }) as NhlQuery
Object.assign(players.find(o => o.id === player.id), player) // 提交修改
cache.writeQuery({ query: GET_PLAYER, data: { players } }) // 写入
}
}) // 提交
this.props.onCancel()
} public render () {
const { player, onCancel } = this.props
console.log(player) return (
<div className={styles.wrap}>
<div className='form-content'>
<Mutation mutation={EDIT_PLAYER}
>
{
(editPlayer, { data }) => {
return (
<div>
<span className={styles.cancel} onClick={onCancel}>取消</span>
<form name={this.formName} onReset={formUtils.resetForm}
onSubmit={this.handleEditSubmit(editPlayer, data)}>
<div>
<label>
姓名
<input defaultValue={player.name} type='text' name='name' onChange={formUtils.bindField}/>
</label>
</div>
<div>
<label>
身高
<input defaultValue={player.height} type='text' name='height'
onChange={formUtils.bindField}/>
</label>
</div>
<div>
<label>
出生日期
<input defaultValue={player.birthDate} type='text' name='birthDate'
onChange={formUtils.bindField}/>
</label>
</div>
<div>
<label>
体重
<input defaultValue={player.weightLbs ? player.weightLbs.toString() : ''} type='number'
name='weightLbs'
onChange={formUtils.bindField}/>
</label>
</div>
<button type='submit'>提交</button>
</form>
</div>
)
}
}
</Mutation>
</div>
</div>
)
}
}
然后利用showEditPlayerModal方法显示模态框(utils/utils.ts)
import gql from 'graphql-tag'
import React from 'react'
import { ApolloProvider } from 'react-apollo'
import ReactDOM from 'react-dom'
import EditPlayerModal from 'src/components/editPlayerModal'
import { PlayerType } from 'src/types'
import { client } from 'src/utils/apolloClient'
import { PlayerFragement } from 'src/utils/graphql/fragements' export function showEditPlayerModal (player: PlayerType) {
client.query<{ player: PlayerType }>({
query: gql`
query ($id:Int!){
player(id:$id){
...PlayerFragment
}
}
${PlayerFragement}
`,
variables: {
id: player.id
}
}).then(o => {
console.log(o)
document.body.appendChild(container)
ReactDOM.render(
<ApolloProvider client={client}>
<EditPlayerModal player={o.data.player} onCancel={onCancel}/>
</ApolloProvider>,
container)
})
const container = document.createElement('div')
container.className = 'g-mask'
container.id = 'g-mask' function onCancel () {
ReactDOM.unmountComponentAtNode(container)
document.body.removeChild(container)
}
} function omitTypename (key, val) {
return key === '__typename' ? undefined : val
} export function removeTypename (obj) {
return JSON.parse(JSON.stringify(obj), omitTypename)
}
其中的代码片段PlayerFragement:(utils/graphql/fragements.ts)
import gql from 'graphql-tag' export const PlayerFragement = gql`
fragment PlayerFragment on PlayerType{
id
birthDate
name
birthPlace
weightLbs
height
}
`
完成后修改PlayList的render方法,使每一次点击条目都会弹出修改模态框:
import { showEditPlayerModal } from 'src/utils/utils'
...
class PlayerList extends React.Component {
public showEditModal = (player: PlayerType) => () => {
showEditPlayerModal(player)
}
public render () {
return (
<div>
<Query query={GET_PLAYERS}>
{
({ loading, error, data }) => {
if (loading) return <p>Loading...</p>
if (error) return <p>Error :(</p>
const players: PlayerType[] = data.players
return players.map((o, i) => (
<div key={i} onClick={this.showEditModal(o)}>
{o.name} {o.birthDate}
</div>
))
}
}
</Query>
</div>
)
}
}
这样修改功能也完成了。最后是删除:
修改PlayerList的render方法:
public render () {
return (
<div>
<Query query={GET_PLAYERS}>
{
({ loading, error, data }) => {
if (loading) return <p>Loading...</p>
if (error) return <p>Error :(</p>
const players: PlayerType[] = data.players
return players.map((o, i) => (
<div key={i} onClick={this.showEditModal(o)}>
{o.name} {o.birthDate} <span style={{ color: 'red' }} onClick={this.deletePlayer(o.id)}>删除</span>
</div>
))
}
}
</Query>
</div>
)
}
添加删除方法:
public deletePlayer = (id) => (e: React.MouseEvent) => {
e.stopPropagation()
client.mutate({
mutation: DELETE_PLAYER,
variables: {
id
},
update (cache, { data }: { data: NhlMutation }) {
console.log(data)
const { players } = cache.readQuery({ query: GET_PLAYERS }) as NhlQuery
cache.writeQuery({ query: GET_PLAYERS, data: { players: players.filter(item => item.id !== id) } })
}
})
}
删除Player的graphql语句:
export const DELETE_PLAYER = gql`
mutation NHLMutation($id:Int!){
deletePlayer(id:$id)
}
`
这样增删改查就全部完成了。
graphql是一个比较新的概念,学习曲线可能略显陡峭,不过总体来说不会太难。
项目地址:https://github.com/axel10/graphql-demo-frontend
参考:
使用graphql和apollo client构建react web应用的更多相关文章
- 使用 Spring 3 MVC HttpMessageConverter 功能构建 RESTful web 服务
原文地址:http://www.ibm.com/developerworks/cn/web/wa-restful/ 简介: Spring,构建 Java™ 平台和 Enterprise Edition ...
- 使用Nginx+CppCMS构建高效Web应用服务器
使用Nginx+CppCMS构建高效Web应用服务器 1:Why当前,越来越多的网站使用了各种框架,大部分框架使用了脚本语言.半编译语言等.比如Java.Python.Php.C#.NET等.这些框架 ...
- 用webpack4从零开始构建react脚手架
用webpack4从零开始构建react脚手架 使用脚手架 git clone git@github.com:xiehaitao0229/react-wepack4-xht.git` `cd reac ...
- [react001] 使用webpack自动构建react 项目
1.react 简介 React 是一个Facebook出品的前端UI开发框架.react官方的tutorials 为了让人容易上手,并没有给在平常工作使用react的详细配置,随意学习的深入,你为了 ...
- SpringBoot实战(十)之使用Spring Boot Actuator构建RESTful Web服务
一.导入依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http ...
- 基于jersey和Apache Tomcat构建Restful Web服务(一)
基于jersey和Apache Tomcat构建Restful Web服务(一) 现如今,RESTful架构已然成为了最流行的一种互联网软件架构,它结构清晰.符合标准.易于理解.扩展方便,所以得到越来 ...
- 使用 Jersey 和 Apache Tomcat 构建 RESTful Web 服务
作者: Yi Ming Huang, 软件工程师, IBM Dong Fei Wu, 软件工程师, IBM Qing Guo, 软件工程师, IBM 出处: http://www.ibm.com/de ...
- 使用 Spring Boot Actuator 构建 RESTful Web 应用
Spring Boot Actuator 是 Spring Boot 的一个子项目.通过它,可以很轻易地为应用提供多种生产级服务.本教程中,你将通过构建一个应用来学习如何添加这些服务. 1. 你需要构 ...
- 【读书笔记】2016.12.10 《构建高性能Web站点》
本文地址 分享提纲: 1. 概述 2. 知识点 3. 待整理点 4. 参考文档 1. 概述 1.1)[该书信息] <构建高性能Web站点>: -- 百度百科 -- 本书目录: 第1章 绪论 ...
随机推荐
- vue2.0 vue-cli项目中路由之间的参数传递
1.首先配置路由, import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new R ...
- Jekyll 使用入门
Jekyll 是一个网站生成工具,可以用来将带有一定格式的文本(如:MarkDown)转换成静态的HTML页面, 并提供了Liquid模板引擎进行页面渲染,然后可以将生成的静态网站发布到如 Githu ...
- 大并发量订单处理的 KafKa部署
大并发量订单处理的 KafKa部署总结 今天要介绍的是消息中间件KafKa,应该说是一个很牛的中间件吧,背靠Apache 与很多有名的中间件搭配起来用效果更好哦 ,为什么不用RabbitMQ,因为公司 ...
- JS判断页面是否加载完成
用 document.readyState == "complete" 判断页面是否加载完成 传回XML 文件资料的目前状况. 基本语法intState = xmlDocument ...
- 【ZJ选讲·BZOJ 5073】
小A的咒语 给出两个字符串A,B (len<=105) 现在可以把A串拆为任意段,然后取出不超过 x 段,按在A串中的前后顺序拼接起来 问是否可以拼出B串. [题解] ①如果遇 ...
- BZOJ3223: Tyvj 1729 文艺平衡树 无旋Treap
一开始光知道pushdown却忘了pushup......... #include<cstdio> #include<iostream> #include<cstring ...
- finally return 执行顺序问题
网上有很多人探讨Java中异常捕获机制try...catch...finally块中的finally语句是不是一定会被执行?很多人都说不是,当然他们的回答是正确的,经过我试验,至少有两种情况下fina ...
- 【poj3693-重复次数最多的连续重复子串】后缀数组
题意:给定一个串,长度<=10^5,求它重复次数最多的连续重复子串(输出字典序最小的那个). 例如ccabcabc,答案就是abcabc 一开始没想清楚,结果调了好久. 原理: 按照L划分,因为 ...
- 【Foreign】猜测 [费用流]
猜测 Time Limit: 10 Sec Memory Limit: 256 MB Description Input Output Sample Input 3 1 1 1 2 2 1 Samp ...
- 【BZOJ2326】【HNOI2011】数学作业 [矩阵乘法][DP]
数学作业 Time Limit: 10 Sec Memory Limit: 128 MB[Submit][Status][Discuss] Description Input 输入文件只有一行为用空 ...