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. php静态文件缓存示例

    //开始缓冲区 ob_start(); $cache_file = "./cache/4.3-static.html"; $cache_time = 1; //设置缓存更新时间 i ...

  2. RxJS & Angular

    RxJS & Angular https://www.learnrxjs.io/ https://rxjs-cn.github.io/learn-rxjs-operators/ https:/ ...

  3. java 自定义序列化

    pom.xml 导包 创建自己的序列化类,继承 com.fasterxml.jackson.databind.JsonSerializer<T> 抽象类 重写 serialize() 方法 ...

  4. jcaptcha配置验证码

    准备开始 首先导入jar包:jcaptcha-my-1.0 /** * web 常量 * @author lx * */ public abstract class Constants { /** 用 ...

  5. 静态化技术Freemarker

    什么是Freemarker FreeMarker是一个用Java语言编写的模板引擎,它基于模板来生成文本输出.FreeMarker与Web容器无关,即在Web运行时,它并不知道Servlet或HTTP ...

  6. 二分查找 Binaryserach

    二分查找: 二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且插入删除困难.因此,折半查找方法适用于不经常变动而查找频繁的有序列表.首先,假设表中元素是按升 ...

  7. 51nod 1819 黑白树V2(树链剖分)

    第一次写如此复杂的树链剖分, 感觉自己代码能力还是挺不错的,没有调试太久(2个小时) 最后代码量高达11K orz(大部分都是重复的线段树代码,以后可以考虑优化一下代码量) 题解: 首先就是要进行一次 ...

  8. 【题解】ZOJ1420 Cashier Employment

    论文——冯威<浅析差分约束系统>. 论文讲得很详细,就不解释了.主要想记录一下对于差分约束的理解(感觉以前的学习真的是在囫囵吞枣啊……) 差分约束系统,同于解决线性的不等关系是否存在合法解 ...

  9. [Leetcode] subsets 求数组所有的子集

    Given a set of distinct integers, S, return all possible subsets. Note: Elements in a subset must be ...

  10. CMD批处理把txt文本中的每行写入一个新文件,第一列作文件名

    需求 现在有一个文件格式如图 ID 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17001 89.84 8.87 1.29 -0.0 0.0 68.99 0.0 0. ...