使用React和GraphQL進行CRUD:完整教程與示例
在本教程中,我們將向您展示如何使用GraphQL和React實現(xiàn)簡單的端到端CRUD操作。我們將介紹使用React Hooks讀取和修改數(shù)據(jù)的簡單示例。我們還將演示如何使用Apollo Client實現(xiàn)身份驗證、錯誤處理、緩存和樂觀UI。
什么是React?
React是一個用于構(gòu)建用戶界面的JavaScript庫。它旨在幫助構(gòu)建應(yīng)用程序的前端部分,包括處理Web和移動應(yīng)用的視圖層。
React是基于組件的,這意味著React應(yīng)用程序的各個部分被分解成較小的組件,然后在更高級別的組件中組織。這些更高級別的組件定義了應(yīng)用程序的最終結(jié)構(gòu)。
React支持可重用組件,因此您可以創(chuàng)建一個組件,并在應(yīng)用程序的不同部分多次使用。這有助于減少冗余代碼,使代碼更易于維護,遵循DRY原則。
什么是GraphQL?
GraphQL是一種用于API的查詢語言,也是一個用現(xiàn)有數(shù)據(jù)來實現(xiàn)查詢的運行時。簡單來說,GraphQL是一種描述如何請求數(shù)據(jù)的語法。它通常用于從服務(wù)器加載數(shù)據(jù)到客戶端。
GraphQL通過將所有請求抽象到一個端點來簡化API的構(gòu)建。與傳統(tǒng)的REST API不同,它是聲明式的,這意味著請求的內(nèi)容會被返回。
何時使用GraphQL
當然,并不是所有項目都需要GraphQL——它只是一個用于整合數(shù)據(jù)的工具。GraphQL有定義良好的模式,因此我們可以確定不會過度獲取數(shù)據(jù)。但是,如果我們已經(jīng)有一個穩(wěn)定的RESTful API系統(tǒng),并且只依賴單一數(shù)據(jù)源的數(shù)據(jù),那么我們不需要GraphQL。
例如,假設(shè)我們正在為自己創(chuàng)建一個博客,并決定在單一的MongoDB數(shù)據(jù)庫中存儲、檢索和通信數(shù)據(jù)。在這種情況下,我們沒有做任何復雜的架構(gòu)設(shè)計,不需要GraphQL。
另一方面,假設(shè)我們有一個依賴多個數(shù)據(jù)源(如MongoDB、MySQL、Postgres和其他API)的完整產(chǎn)品。在這種情況下,我們應(yīng)該使用GraphQL。
例如,如果我們在設(shè)計一個作品集網(wǎng)站,并希望從社交媒體和GitHub獲取數(shù)據(jù)(以顯示貢獻),并且我們還有自己的數(shù)據(jù)庫來維護博客,我們可以使用GraphQL來編寫業(yè)務(wù)邏輯和模式。它將數(shù)據(jù)整合為單一的真實來源。
一旦我們有了解決函數(shù)來將正確的數(shù)據(jù)分發(fā)到前端,我們將能夠輕松地在單一來源中管理數(shù)據(jù)。
什么是CRUD?
在構(gòu)建API時,您希望您的模型提供四個基本功能:它應(yīng)該能夠創(chuàng)建、讀取、更新和刪除資源。這一組基本操作通常被稱為CRUD。
RESTful API通常使用HTTP請求。在REST環(huán)境中,四個最常見的HTTP方法是GET、POST、PUT和DELETE,這是開發(fā)者可以用來創(chuàng)建CRUD系統(tǒng)的方法。
使用graphql-server進行CRUD
在本節(jié)中,我們將介紹一些GraphQL CRUD示例,以幫助您了解在React和GraphQL應(yīng)用程序中CRUD操作的工作方式。
設(shè)置服務(wù)器
我們將使用express-graphql啟動一個簡單的GraphQL服務(wù)器,并將其連接到MySQL數(shù)據(jù)庫。源代碼和MySQL文件在這個倉庫中。
GraphQL服務(wù)器是基于模式和解析器構(gòu)建的。首先,我們構(gòu)建一個模式(定義類型、查詢、變更和訂閱)。該模式描述了整個應(yīng)用程序結(jié)構(gòu)。
其次,對于模式中定義的內(nèi)容,我們構(gòu)建相應(yīng)的解析器來計算和分發(fā)數(shù)據(jù)。解析器將動作映射到函數(shù);對于在類型定義中聲明的每個查詢,我們創(chuàng)建一個解析器來返回數(shù)據(jù)。
最后,通過定義端點并傳遞配置來完成服務(wù)器設(shè)置。我們將/graphql初始化為應(yīng)用程序的端點。對于graphqlHTTP中間件,我們傳遞構(gòu)建的模式和根解析器。
除了模式和根解析器外,我們還啟用了GraphiQL開發(fā)工具。GraphiQL是一個交互式的瀏覽器內(nèi)GraphQL IDE,可以幫助我們玩轉(zhuǎn)構(gòu)建的GraphQL查詢。
var express = require('express');
var graphqlHTTP = require('express-graphql');
var { buildSchema } = require('graphql');
var schema = buildSchema(`
type Query {
hello: String
}
`);
var root = {
hello: () => "World"
};
var app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
一旦服務(wù)器準備就緒,運行node index.js將啟動服務(wù)器在http://localhost:4000/graphql。我們可以查詢hello并獲得字符串“World”作為響應(yīng)。
連接數(shù)據(jù)庫
我要建立與MySQL數(shù)據(jù)庫的連接,如下所示:
var mysql = require('mysql');
app.use((req, res, next) => {
req.mysqlDb = mysql.createConnection({
host : 'localhost',
user : 'root',
password : '',
database : 'userapp'
});
req.mysqlDb.connect();
next();
});
我們可以連接多個數(shù)據(jù)庫/數(shù)據(jù)源,并在解析器中整合它們。我在這里連接了一個MySQL數(shù)據(jù)庫。本文中使用的數(shù)據(jù)庫轉(zhuǎn)儲在GitHub倉庫中。
使用GraphQL讀取和寫入數(shù)據(jù)
我們使用查詢和變更來讀取和修改數(shù)據(jù)源中的數(shù)據(jù)。在這個例子中,我定義了一個通用的queryDB函數(shù)來幫助查詢數(shù)據(jù)庫。
查詢
所有的SELECT語句(或讀取操作)用于列出和查看數(shù)據(jù),放入type Query類型定義中。我們在這里定義了兩個查詢:一個用于列出數(shù)據(jù)庫中的所有用戶,另一個用于按ID查看單個用戶。
- 1. 列出數(shù)據(jù):為了列出用戶,我們定義了一個GraphQL模式對象類型User,它表示我們可以從getUsers查詢中獲取或期望的內(nèi)容。然后我們定義getUsers查詢來返回一個用戶數(shù)組。
- 2. 查看單個記錄:為了查看單個記錄,我們使用getUserInfo查詢定義一個參數(shù)id。它查詢數(shù)據(jù)庫中的特定ID并將數(shù)據(jù)返回到前端。
圖片
現(xiàn)在我們已經(jīng)組合了查詢來獲取所有記錄并按ID查看記錄,當我們嘗試從GraphiQL查詢用戶時,它將在屏幕上列出一個用戶數(shù)組!
查詢
var schema = buildSchema(`
type User {
id: String
name: String
job_title: String
email: String
}
type Query {
getUsers: [User],
getUserInfo(id: Int) : User
}
`);
const queryDB = (req, sql, args) => new Promise((resolve, reject) => {
req.mysqlDb.query(sql, args, (err, rows) => {
if (err)
return reject(err);
rows.changedRows || rows.affectedRows || rows.insertId ? resolve(rows) : resolve(rows[0]);
});
});
var root = {
getUsers: (args, req) => {
return queryDB(req, `SELECT * FROM user`);
},
getUserInfo: (args, req) => {
return queryDB(req, `SELECT * FROM user WHERE id=?`, [args.id]);
}
};
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true
}));
變更
在變更部分中,我們將執(zhí)行以下操作:創(chuàng)建、更新和刪除記錄。變更按類型定義,對應(yīng)于數(shù)據(jù)庫中用戶模式的對象類型。
type Mutation {
createUser(name: String!, job_title: String!, email: String!): User
updateUser(id: Int!, name: String, job_title: String, email: String): String
deleteUser(id: Int!): String
}
我定義了一個帶有三個參數(shù)的createUser變更來將數(shù)據(jù)插入數(shù)據(jù)庫中。updateUser使用id標識符來修改表中的用戶記錄,deleteUser使用id從數(shù)據(jù)庫中刪除記錄。
const queryDB = (req, sql, args) => new Promise((resolve, reject) => {
req.mysqlDb.query(sql, args, (err, rows) => {
if (err)
return reject(err);
rows.changedRows || rows.affectedRows || rows.insertId ? resolve(rows) : resolve(rows[0]);
});
});
const createDB = (req, sql, args) => new Promise((resolve, reject) => {
req.mysqlDb.query(sql, args, (err, rows) => {
if (err)
return reject(err);
args[0].id = rows.insertId;
resolve(args[0]);
});
});
var root = {
getUsers: (args, req) => {
return queryDB(req, `SELECT * FROM user`);
},
getUserInfo: (args, req) => {
return queryDB(req, `SELECT * FROM user WHERE id=?`, [args.id]);
},
createUser: (args, req) => {
return createDB(req, `INSERT INTO user SET ?`, [args]);
},
updateUser: (args, req) => {
return queryDB(req, `UPDATE user SET ? WHERE id=?`, [args, args.id]).then((res) => "Successfully updated user").catch((err) => "Cannot update user");
},
deleteUser: (args, req) => {
return queryDB(req, `DELETE FROM user WHERE id=?`, [args.id]).then((res) => "Successfully deleted user").catch((err) => "Cannot delete user");
}
};
app.use('/graphql', graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true
}));
在React中使用graphql-client進行CRUD
這節(jié)將使用React與GraphQL客戶端整合。我們將使用Apollo客戶端和GraphQL查詢來從GraphQL API中讀取數(shù)據(jù)并更新UI。Apollo客戶端的作用類似于瀏覽器的Fetch API,但它是為GraphQL設(shè)計的。
設(shè)置Apollo客戶端
首先,確保安裝了以下包:
npm i react-apollo graphql-tag apollo-boost apollo-client apollo-cache-inmemory apollo-link-http
初始化Apollo客戶端并為應(yīng)用程序提供客戶端對象:
import ApolloClient from "apollo-boost";
import { ApolloProvider } from "react-apollo";
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
const client = new ApolloClient({
uri: "http://localhost:4000/graphql"
});
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById("root")
);
列出數(shù)據(jù)
為了列出數(shù)據(jù),我們首先創(chuàng)建一個GraphQL查詢,然后將其傳遞給React組件。我們可以使用useQuery鉤子來執(zhí)行查詢并返回結(jié)果。
import React from "react";
import gql from "graphql-tag";
import { useQuery } from "react-apollo";
const GET_USERS = gql`
query {
getUsers {
id
name
job_title
email
}
}
`;
const UserList = () => {
const { loading, error, data } = useQuery(GET_USERS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error :(</p>;
return (
<div>
{data.getUsers.map(user => (
<div key={user.id}>
<p>{user.name}</p>
<p>{user.job_title}</p>
<p>{user.email}</p>
</div>
))}
</div>
);
};
export default UserList;
創(chuàng)建用戶
為了創(chuàng)建新用戶,我們創(chuàng)建一個變更并將其傳遞給組件。我們可以使用useMutation鉤子來執(zhí)行變更并返回結(jié)果。
import React from "react";
import gql from "graphql-tag";
import { useMutation } from "react-apollo";
const CREATE_USER = gql`
mutation CreateUser($name: String!, $job_title: String!, $email: String!) {
createUser(name: $name, job_title: $job_title, email: $email) {
id
name
job_title
email
}
}
`;
const CreateUser = () => {
let name, job_title, email;
const [createUser] = useMutation(CREATE_USER);
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
createUser({ variables: { name: name.value, job_title: job_title.value, email: email.value } });
name.value = "";
job_title.value = "";
email.value = "";
}}
>
<input ref={node => { name = node; }} placeholder="Name" />
<input ref={node => { job_title = node; }} placeholder="Job Title" />
<input ref={node => { email = node; }} placeholder="Email" />
<button type="submit">Add User</button>
</form>
</div>
);
};
export default CreateUser;
更新和刪除用戶
類似地,您可以創(chuàng)建更新和刪除用戶的組件。使用useMutation鉤子傳遞GraphQL變更并返回結(jié)果。
import React from "react";
import gql from "graphql-tag";
import { useMutation } from "react-apollo";
const UPDATE_USER = gql`
mutation UpdateUser($id: Int!, $name: String, $job_title: String, $email: String) {
updateUser(id: $id, name: $name, job_title: $job_title, email: $email)
}
`;
const DELETE_USER = gql`
mutation DeleteUser($id: Int!) {
deleteUser(id: $id)
}
`;
const UpdateUser = () => {
let id, name, job_title, email;
const [updateUser] = useMutation(UPDATE_USER);
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
updateUser({ variables: { id: parseInt(id.value), name: name.value, job_title: job_title.value, email: email.value } });
id.value = "";
name.value = "";
job_title.value = "";
email.value = "";
}}
>
<input ref={node => { id = node; }} placeholder="ID" />
<input ref={node => { name = node; }} placeholder="Name" />
<input ref={node => { job_title = node; }} placeholder="Job Title" />
<input ref={node => { email = node; }} placeholder="Email" />
<button type="submit">Update User</button>
</form>
</div>
);
};
const DeleteUser = () => {
let id;
const [deleteUser] = useMutation(DELETE_USER);
return (
<div>
<form
onSubmit={e => {
e.preventDefault();
deleteUser({ variables: { id: parseInt(id.value) } });
id.value = "";
}}
>
<input ref={node => { id = node; }} placeholder="ID" />
<button type="submit">Delete User</button>
</form>
</div>
);
};
export { UpdateUser, DeleteUser };
總結(jié)
在本教程中,我們展示了如何使用React和GraphQL進行CRUD操作。我們設(shè)置了GraphQL服務(wù)器,連接到MySQL數(shù)據(jù)庫,定義了查詢和變更,并使用Apollo客戶端在React應(yīng)用中執(zhí)行CRUD操作。希望這能幫助您更好地理解和實現(xiàn)React和GraphQL的集成。