React训练营:GraphQL 与CRUD的故事

概述

本文为React学习过程中的一些探索实践,主要的目的为用GraphQL实现一个React的CRUD应用。

重要

  • 本文将给出一些GraphQL的一些用法
  • 通过GraphQL实现一个简单的React应用

前置条件

  • 构建React项目
  • React中基本组件的构建以及单项数据流的使用
  • 数据库表格设计与CRUD的简易实现思路

GraphQL

image
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
    }
  }
}

image

在该网站的右侧,有一个DOC,其中记录了GraphQL的一些用法: 字段的类型及API接口
image
例如上如所示的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语句进行查询、修改。

image

image

image

连接数据库之后新建一张表格Todos

id text done
UUID text boolean, default: false

GraphQL中三个核心概念

  1. query: query_root
  2. mutation: mutation_root
  3. 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.jsindex.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>&times;</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">
          &times;
        </span>  
      </button>
    </p>))}

    </div>

  </div>;
}

export default App;


参考文献

https://www.apollographql.com/docs/react
https://swapi.dev/
https://graphql.org/swapi-graphql/

posted @ 2022-04-04 14:37  adminmttt  阅读(145)  评论(0编辑  收藏  举报