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

参考:

https://fullstackmark.com/post/17/building-a-graphql-api-with-aspnet-core-2-and-entity-framework-core

使用graphql和apollo client构建react web应用的更多相关文章

  1. 使用 Spring 3 MVC HttpMessageConverter 功能构建 RESTful web 服务

    原文地址:http://www.ibm.com/developerworks/cn/web/wa-restful/ 简介: Spring,构建 Java™ 平台和 Enterprise Edition ...

  2. 使用Nginx+CppCMS构建高效Web应用服务器

    使用Nginx+CppCMS构建高效Web应用服务器 1:Why当前,越来越多的网站使用了各种框架,大部分框架使用了脚本语言.半编译语言等.比如Java.Python.Php.C#.NET等.这些框架 ...

  3. 用webpack4从零开始构建react脚手架

    用webpack4从零开始构建react脚手架 使用脚手架 git clone git@github.com:xiehaitao0229/react-wepack4-xht.git` `cd reac ...

  4. [react001] 使用webpack自动构建react 项目

    1.react 简介 React 是一个Facebook出品的前端UI开发框架.react官方的tutorials 为了让人容易上手,并没有给在平常工作使用react的详细配置,随意学习的深入,你为了 ...

  5. SpringBoot实战(十)之使用Spring Boot Actuator构建RESTful Web服务

    一.导入依赖 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http ...

  6. 基于jersey和Apache Tomcat构建Restful Web服务(一)

    基于jersey和Apache Tomcat构建Restful Web服务(一) 现如今,RESTful架构已然成为了最流行的一种互联网软件架构,它结构清晰.符合标准.易于理解.扩展方便,所以得到越来 ...

  7. 使用 Jersey 和 Apache Tomcat 构建 RESTful Web 服务

    作者: Yi Ming Huang, 软件工程师, IBM Dong Fei Wu, 软件工程师, IBM Qing Guo, 软件工程师, IBM 出处: http://www.ibm.com/de ...

  8. 使用 Spring Boot Actuator 构建 RESTful Web 应用

    Spring Boot Actuator 是 Spring Boot 的一个子项目.通过它,可以很轻易地为应用提供多种生产级服务.本教程中,你将通过构建一个应用来学习如何添加这些服务. 1. 你需要构 ...

  9. 【读书笔记】2016.12.10 《构建高性能Web站点》

    本文地址 分享提纲: 1. 概述 2. 知识点 3. 待整理点 4. 参考文档 1. 概述 1.1)[该书信息] <构建高性能Web站点>: -- 百度百科 -- 本书目录: 第1章 绪论 ...

随机推荐

  1. el-upload怎么拿到上传的图片的base64格式

    这里只是本地上传,拿图片的base64,并不向后台直接上传,拿到base64后手动上传 上传前效果: 上传后效果: .vue <el-form-item label="礼品封面&quo ...

  2. 【bzoj1458】士兵占领 有上下界最小流

    题目描述 有一个M * N的棋盘,有的格子是障碍.现在你要选择一些格子来放置一些士兵,一个格子里最多可以放置一个士兵,障碍格里不能放置士兵.我们称这些士兵占领了整个棋盘当满足第i行至少放置了Li个士兵 ...

  3. Python 装饰器和抽象类

    #装饰器:对类或者函数进行功能的扩展 ''' #第一步:基本函数 def la(): print('脚踏黄河两岸,手拿机密文件,前面机枪扫射,后面炮火连天') #调用函数 la() la() #第二步 ...

  4. [洛谷P1801]黑匣子_NOI导刊2010提高(06)

    题目大意:两个操作:向一个可重集中加入一个元素:询问第$k$大的数($k$为之前询问的个数加一) 题解:离散化,权值线段树直接查询 卡点:无 C++ Code: #include <cstdio ...

  5. 移动开发:美团外卖Android Lint代码检查实践

    概述 Lint是Google提供的Android静态代码检查工具,可以扫描并发现代码中潜在的问题,提醒开发人员及早修正,提高代码质量.除了Android原生提供的几百个Lint规则,还可以开发自定义L ...

  6. LaTeX的图片插入及排版[转]

    LaTeX中一般只直接支持插入eps(Encapsulated PostScript)格式的图形文件, 因此在图片插入latex文档之前应先设法得到图片的eps格式的文件. UNIX下的各种应用软件都 ...

  7. bzoj 2425 [HAOI2010]计数 dp+组合计数

    [HAOI2010]计数 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 451  Solved: 289[Submit][Status][Discus ...

  8. java 构造函数问题

    1.构造函数什么时候被调用,被谁调用? 转摘:http://bbs.csdn.net/topics/350231037 当然,只有在NEW的时候,才会真正的创建这个对象,只有在创建时才会调用该类的构造 ...

  9. hihoCoder 1527 快速乘法

    #include<bits/stdc++.h> using namespace std; ; char a[N]; int main() { scanf(); ); ,r = n; ') ...

  10. 状压dp的题目列表 (一)

    状压dp的典型的例子就是其中某个数值较小. 但是某个数值较小也不一定是状压dp,需要另外区分的一种题目就是用暴力解决的题目,例如UVA818 紫书215 题目列表: ①校长的烦恼 UVA10817 紫 ...