React训练营:GraphQL 与CRUD的故事
概述
本文为React学习过程中的一些探索实践,主要的目的为用GraphQL实现一个React的CRUD应用。
重要
- 本文将给出一些GraphQL的一些用法
- 通过GraphQL实现一个简单的React应用
前置条件
- 构建React项目
- React中基本组件的构建以及单项数据流的使用
- 数据库表格设计与CRUD的简易实现思路
GraphQL
GraphQL主要用于数据的接口,类似SQL语句,后端的程序可以通过一些规范的语法直接获取数据存储层中数据。根据官网的介绍,只要三个步骤就能获取所需要的数据:
1)描述数据,
2)请求数据,
3)得到可预测的结果。
官方网站说明如下
一种用于 API 的查询语言
GraphQL 既是一种用于 API 的查询语言也是一个满足你数据查询的运行时。 GraphQL 对你的 API 中的数据提供了一套易于理解的完整描述,使得客户端能够准确地获得它需要的数据,而且没有任何冗余,也让 API 更容易地随着时间推移而演进,还能用于构建强大的开发者工具。
作为一门类似SQL的语言,学习的套路并非是读官网的一些概念:查询和变更、schema和类型...,而是直接上手做一个简单的应用,至于应用内数据的权限、分页、对象、缓存等等,待应用进一步演化之后再来考虑复杂的实践,最开始的应用系统设计应当足够简单、合适(满足当前的需求),之后才是应用的演化和迭代。
GraphQL的演示
graphQL演示案例网站
https://graphql.org/swapi-graphql
该案例中使用的数据与 Star Wars这部电影有关。
1.为了查询电影的标题和导演,输入以下的命令,点击执行
{
allFilms{
films{
id
title
director
}
}
}
在该网站的右侧,有一个DOC,其中记录了GraphQL的一些用法: 字段的类型及API接口
例如上如所示的film(id:ID,filmID:Id):Film
根据ID查询
{
film(id:"ZmlsbXM6MQ=="){
id
title
director
}
}
得到如下的结果
{
"data": {
"film": {
"id": "ZmlsbXM6MQ==",
"title": "A New Hope",
"director": "George Lucas"
}
}
}
通过hasura平台使用GraphQL
为了更方便地使用GraphQL,可以使用hasura托管平台(https://cloud.hasura.io/),用户只需要注册一个账号,填入自己的数据库参数,拥有更大的自由度,只需要在网站上定义数据库中的表格,然后就可以通过GraphQL的语法实现数据库的表格的增删改查。
上面Star Wars的例子只有简单的查询、为了实现更多的需求,考虑在自己的数据库中建立一张表,通过GraphQL语句进行查询、修改。
连接数据库之后新建一张表格Todos
id | text | done |
---|---|---|
UUID | text | boolean, default: false |
GraphQL中三个核心概念
- query: query_root
- mutation: mutation_root
- subscription: subscription_root
Query 主要是查询,其中也有分页、聚合等功能
todos(
distinct_on: [todos_select_column!]
limit: Int
offset: Int
order_by: [todos_order_by!]
where: todos_bool_exp
): [todos!]!
fetch data from the table: "todos"
todos_aggregate(
distinct_on: [todos_select_column!]
limit: Int
offset: Int
order_by: [todos_order_by!]
where: todos_bool_exp
): todos_aggregate!
fetch aggregated fields from the table: "todos"
todos_by_pk(id: uuid!): todos
fetch data from the table: "todos" using primary key columns
mutation 主要是对数据库中的表格进行修改
delete_todos(where: todos_bool_exp!): todos_mutation_response
delete data from the table: "todos"
delete_todos_by_pk(id: uuid!): todos
delete single row from the table: "todos"
insert_todos(objects: [todos_insert_input!]!on_conflict: todos_on_conflict): todos_mutation_response
insert data into the table: "todos"
insert_todos_one(object: todos_insert_input!on_conflict: todos_on_conflict): todos
insert a single row into the table: "todos"
update_todos(_set: todos_set_inputwhere: todos_bool_exp!): todos_mutation_response
update data of the table: "todos"
update_todos_by_pk(_set: todos_set_inputpk_columns: todos_pk_columns_input!): todos
update single row of the table: "todos"
在React中使用GraphQL
1.新建一个项目
npx create-react-app graphql-checklist
2.删除无关内容仅保留App.js
和index.js
3.安装
npm install @apollo/client graphql
使用ApolloClient
const client = new ApolloClient({
uri: 'https://todos-holy-bedbug-22.hasura.app/v1/graphql',
cache: new InMemoryCache(),
headers: {
authorization: localStorage.getItem('token'),
'content-type':'application/json',
'x-hasura-admin-secret': `这里输入hasura平台的给的密钥`
}
});
使用钩子将数据勾出来
index.js
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById('root')
);
App.js
import React from "react";
import {useQuery,gql} from '@apollo/client';
const GET_TODOS = gql`
query getTodos{
todos{
done
id
text
}
}
`
function App() {
const {data,loading,error} = useQuery(GET_TODOS);//hook
if(loading) return <div>Loading...</div>;
if(error) return <div>Error fetching todos</div>
return <div>
{data.todos.map(todo=>(<p key={todo.id}>
<span>
{todo.text}
</span>
<button>×</button>
</p>))}
</div>;
}
export default App;
React项目中实现增删改查
ToDo应用:前端使用 @apollo/client
import React from "react";
import {useQuery,gql,useMutation} from '@apollo/client';
//query
const GET_TODOS = gql`
query getTodos{
todos{
done
id
text
}
}
`
//execute mutation
const TOGGLE_TODO=gql`
mutation toggleTodo($id: uuid!, $done: Boolean!) {
update_todos(where: {id: {_eq:$id}}, _set:{done: $done}){
returning {
done
id
text
}
}
}
`
//add todos
const ADD_TODO=gql`
mutation addTodo($text:String!) {
insert_todos(objects:{text:$text}){
returning{
done
id
text
}
}
}
`
//
const DELTE_TODO=gql`
mutation deleteTodo($id:uuid!) {
delete_todos(where: {id: {_eq: $id}}){
returning{
done
id
text
}
}
}
`
function App() {
const [todoText,setTodoText] = React.useState('');
const {data,loading,error} = useQuery(GET_TODOS);//hook
const [toggleTodo]=useMutation(TOGGLE_TODO);
const [addTodo] = useMutation(ADD_TODO,{
onCompleted: ()=>setTodoText('')
});
const [deleteTodo] = useMutation(DELTE_TODO);
if(loading) return <div>Loading...</div>;
if(error) return <div>Error fetching todos</div>
async function handleToggleTodo({ id, done }){
const data = await toggleTodo({ variables:{id:id, done:!done}});
console.log(data)
}
async function handleDeleteTodo({id}){
const isComfirmed = window.confirm('Do you want to delete this todo?');
if(isComfirmed){
const data = await deleteTodo({
variables:{id},
update: cache=>{
const prevData = cache.readQuery({query:GET_TODOS});
const newTodos = prevData.todos.filter(todo=>todo.id!==id);
cache.writeQuery({query:GET_TODOS,data:{todos:newTodos}});
}
});
console.log('delete todo',data)
}
}
async function handleAddTodos(event){
event.preventDefault();
if(!todoText.trim()) return;
const data = await addTodo({
variables:{text:todoText},
refetchQueries:[
{ query: GET_TODOS }
]
});
setTodoText("");
}
return <div className="vh-100 code flex-column items-center bg-purple white pa5">
<h1 className="f2-l flex items-center justify-center">GraphQL CheckList{" "}<span role="img" aria-label="Checkmark">✔</span></h1>
<div className="flex items-center justify-center flex-column">
<form onSubmit={handleAddTodos} className="mb3">
<input className="pa2 f4 b--dashed items-center"
type = "text"
placeholder ="Write your todo"
onChange={event=>setTodoText(event.target.value)}
value={todoText}
/>
<button type="submit" className="pa2 f4 bg-green">Create</button>
</form>
</div>
<div className="flex items-center justify-center flex-column">
{data.todos.map(todo=>(<p onDoubleClick={()=>handleToggleTodo(todo)} key={todo.id}>
<span className={`pointer list pa1 f3 ${todo.done&&"strike"}`}>
{todo.text}
</span>
<button className="bg-transparent bn f4" onClick={()=>handleDeleteTodo(todo)}>
<span className="red">
×
</span>
</button>
</p>))}
</div>
</div>;
}
export default App;
参考文献
https://www.apollographql.com/docs/react
https://swapi.dev/
https://graphql.org/swapi-graphql/