使用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章 绪论 ...
随机推荐
- php静态文件缓存示例
//开始缓冲区 ob_start(); $cache_file = "./cache/4.3-static.html"; $cache_time = 1; //设置缓存更新时间 i ...
- RxJS & Angular
RxJS & Angular https://www.learnrxjs.io/ https://rxjs-cn.github.io/learn-rxjs-operators/ https:/ ...
- java 自定义序列化
pom.xml 导包 创建自己的序列化类,继承 com.fasterxml.jackson.databind.JsonSerializer<T> 抽象类 重写 serialize() 方法 ...
- jcaptcha配置验证码
准备开始 首先导入jar包:jcaptcha-my-1.0 /** * web 常量 * @author lx * */ public abstract class Constants { /** 用 ...
- 静态化技术Freemarker
什么是Freemarker FreeMarker是一个用Java语言编写的模板引擎,它基于模板来生成文本输出.FreeMarker与Web容器无关,即在Web运行时,它并不知道Servlet或HTTP ...
- 二分查找 Binaryserach
二分查找: 二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好:其缺点是要求待查表为有序表,且插入删除困难.因此,折半查找方法适用于不经常变动而查找频繁的有序列表.首先,假设表中元素是按升 ...
- 51nod 1819 黑白树V2(树链剖分)
第一次写如此复杂的树链剖分, 感觉自己代码能力还是挺不错的,没有调试太久(2个小时) 最后代码量高达11K orz(大部分都是重复的线段树代码,以后可以考虑优化一下代码量) 题解: 首先就是要进行一次 ...
- 【题解】ZOJ1420 Cashier Employment
论文——冯威<浅析差分约束系统>. 论文讲得很详细,就不解释了.主要想记录一下对于差分约束的理解(感觉以前的学习真的是在囫囵吞枣啊……) 差分约束系统,同于解决线性的不等关系是否存在合法解 ...
- [Leetcode] subsets 求数组所有的子集
Given a set of distinct integers, S, return all possible subsets. Note: Elements in a subset must be ...
- 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. ...