开始本文前一定要先学习# GraphQL 的 正确打开方式 (apollo-client前戏)
前面我们学习了graphql
的基础知识,知道如果你要在express
里面启动grapgql
服务,你就要用schema
定义类型,用root
定义数据,就能启动一个基础服务了。
咱们在前端js里面可以用fetch``和axios
启动http服务
,把gql
语句以参数的形式传入就好了。
这里先使用apollo-server
看看。
一. apollo-server
从之前的案例里面可以看出来,你启动的graphql服务是建立在express基础上的。
我们按照官方的案例建立一个apollo-server
案例对比看看
1.创建项目并且初始化
bash
mkdir my-apollo-server
cd my-apollo-server
npm init -y
//顺便把package.json里面的type修改成module模式,支持esModule
npm pkg set type="module"
2.安装包
js
npm install @apollo/server graphql
3.创建graphql服务
js
import { ApolloServer } from '@apollo/server'; // preserve-line
import { startStandaloneServer } from '@apollo/server/standalone'; // preserve-line
const typeDefs = `
type Query {
hello: String
}
`;
const resolvers = {
Query: {
hello: () => {
return "hello world";
},
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
listen: { port: 3009 },
});
console.log(`🚀 Server ready at: ${url}`);
启动服务:node server
访问下http://localhost:3009/ 就会进入这个页面
它是一个gql
可视化页面。有没有觉得apollo-server
瞬间比express + graphql
高大上了很多。光这个可视化页面就减轻了很多我们写gql
的痛苦。
4.增删改查
js
const { ApolloServer, gql } = require('apollo-server');
// 定义GraphQL的类型
const typeDefs = gql`
type Book {
title: String
author: String
}
type Query {
books可控性Book]
}
input BookInput {
title: String
author: String
}
type Mutation {
createBook(input: BookInput): Book
deleteBook(title: String): Boolean
updateBook(title: String, author: String): Book
}
`;
// 模拟数据库
let books = [];
// 解析器
const resolvers = {
Query: {
books: () => books,
},
Mutation: {
createBook: (_, { input }) => {
books.push({ title: input.title, author: input.author });
return input;
},
deleteBook: (_, { title }) => {
const index = books.findIndex(book => book.title === title);
if (index !== -1) {
books.splice(index, 1);
return true;
}
return false;
},
updateBook: (_, { title, author }) => {
const book = books.find(book => book.title === title);
if (book) {
book.author = author;
return book;
}
return null;
},
},
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`Server ready at ${url}`);
});
注意: 带参数的是函数写法一定要注意,参数是在第二个参数上,不在第一个上
5.对比并总结
对比文档:spec.graphql.org/June2018/#s... www.apollographql.com/docs/apollo...
1.apollo-server
相比于express + graphql
而言,语法更加简单。graphqlHTTP
利用schema,root
创建服务。apollo
利用 typeDefs,resolvers
创建服务。内核一样,一个定义的是类型,另一个定义的是数据。
2.它还提供了一个可视化界面,帮助你写gql
。
3.如果appolo-server
要进行跨域请求,还是少不了exprss
的帮助。
4.graphql
和 apollo-server
都支持的类型:基础类型:String,Int, Boolean,Float, ID,高级类型:对象,数组,查询Query,突变Mutation,联合union,枚举enum, 关键字有:type和input,指令。
5.graphql
可空性和Apollo
雷同
6.apollo-server
的缓存分为静态缓存和动态缓存两种,静态缓存是利用 @cacheControl
指令实现,直接把指令跟在类型的后面就好了,这个和浏览器的缓存一样,设置maxAge,scope,inheritMaxAge
。动态缓存是利用 @apollo/cache-control-types
包的cacheControlFromInfo方
法实现的。
7.参数处理上也是不一样的。一定要注意下。
8.如果它要和next
集成,利用@as-integrations/next
二. appolo-client
之前的express+graphql
启动的服务,我用前端访问接口的时候,直接用的html
文件,我用live-server启动一个前端服务,然后调用了接口,相互连接。
现在 appolo-client 集成了react。
1.初始化项目
利用vite创建一个react项目
js
npx create vite my-project
2.安装依赖
js
npm install @apollo/client graphql
3.在main文件集成apollo-client
我们在上面apollo-server
里面已经创建了一个graphql
服务,现在就启动它,然后在main
文件里面使用它
js
import { createRoot } from 'react-dom/client';
import { ApolloClient, InMemoryCache, ApolloProvider, gql } from '@apollo/client';
import App from './App.tsx';
const client = new ApolloClient({
uri: ' http://localhost:3009/',
cache: new InMemoryCache(),
});
createRoot(document.getElementById('root')!).render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
解释代码:new ApolloClient
创建一个实例,然后利用ApolloProvider
传递给他的子孙。ApolloProvider
它是react Context Api
的封装
4.在组件里面使用
在app.tsx里面
js
// Import everything needed to use the `useQuery` hook
import { useQuery, gql } from '@apollo/client';
const GET_LOCATIONS = gql`
query {
hello
}
`;
export default function App() {
const { loading, error, data } = useQuery(GET_LOCATIONS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error : {error.message}</p>;
console.log(data, 888);
return <div>{data.hello}</div>;
}
解释代码: GET_LOCATIONS
就是grqphql
的gql
语句,用来获取数据,以前获取数据都用的是fetch
请求,现在不用了,直接一个hooks
就解决了,你说方便不方便?
apollo-client
提供了2个hooks
,useQuery和useLazyQuery
5.测试
6.react-hooks
1.useQuery
-- 返回data,loading,error,不返回执行函数,用于页面一进来就获取数据的场景
2.useLazyQuery
-- 返回执行函数和对应的值data,如果你想在组件里面,点击了按钮以后才去查询数据,就用useLazyQuery,在使用的时候,记得加 fetchPolicy: 'network-only' 才能立即更新 如下:
6.总结
apollo-client
完全秒杀了原始js
利用http
请求获取数据的解决方案。首先它将graphql
服务地址集中管理,将获取到的数据集中由context
管理,按照 ContextApi的方式将所有的值在父组件里面传递出去。在子组件里面,使用 useQuery和useLazyQuery
配合 gql
获取数据。
三.一个简单的增删改查的案例
1.apollo-server实现服务端
后端服务利用apollo-server
实现如下:
初始化项目后,npm init -y,然后创建一个js文件:server.js,复制以下代码。
js
import { ApolloServer } from '@apollo/server'; // preserve-line
import { startStandaloneServer } from '@apollo/server/standalone'; //preserve-line
import crypto from 'crypto';
const typeDefs = `
type User {
name: String
sex: String
age: Int
id: ID
}
type Query {
getList: [User]
getUser(id: ID): User
}
type Mutation {
createUser(name: String, sex: String, age: Int): User
updateUser(id: ID, name: String, sex: String, age: Int): User
deleteUser(id: ID): User
}
`;
class User {
constructor({ id, name, sex, age }) {
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
}
var fakeDatabase = [];
const resolvers = {
Query: {
getList() {
return fakeDatabase;
},
getUser({ id }) {
const user = fakeDatabase.find((item) => item.id === id);
return user;
},
},
Mutation: {
createUser(_, { name, sex, age }) {
var id = crypto.randomBytes(10).toString("hex");
const user = new User({
id,
name,
sex,
age
});
fakeDatabase.push(user);
return user;
},
updateUser(_, { id, name, sex, age }) {
const user = new User({ id, name, sex, age });
const userList = fakeDatabase.map((item) => {
if (item.id === id) {
return user;
}
return item;
});
fakeDatabase = [...userList];
return user;
},
deleteUser(_, { id }) {
const userList = fakeDatabase.filter((item) => item.id !== id);
fakeDatabase = [...userList];
return userList;
},
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
});
const { url } = await startStandaloneServer(server, {
listen: { port: 3009 },
});
console.log(`🚀 Server ready at: ${url}`);
利用 node
启动服务,一般你直接用node启动的服务是不能帮你打印console.log
的,你需要用debug
模式开启服务,可以帮你在前端访问接口的时候,node
打印console.log
2.apollo-client实现前端
利用vite初始化项目
js
npx create vite my-project
安装包
main.tsx
graphql服务的端口号是3009,就是apollo- server的服务。
js
import { createRoot } from 'react-dom/client';
import { ApolloClient, InMemoryCache, ApolloProvider, gql } from '@apollo/client';
import App from './App.tsx';
const client = new ApolloClient({
uri: ' http://localhost:3009/',
cache: new InMemoryCache(),
});
createRoot(document.getElementById('root')!).render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
gql.ts
js
import { gql } from '@apollo/client';
export const GET_LIST = gql`query {
getList {
id
name
sex
age
}
}
`;
export const CREATE_USER = gql`mutation CreateUser($name: String, $sex: String, $age: Int) {
createUser(name: $name, sex: $sex, age: $age){
id
name
sex
age
}
}`;
export const UPDATE_USER = gql`mutation UpdateUser($id: ID, $name: String, $sex: String, $age: Int) {
updateUser(id: $id, name: $name, sex: $sex, age: $age){
id
name
sex
age
}
}`;
export const DELETE_USER = gql`mutation DeleteUser($id: ID) {
deleteUser(id: $id){
id
name
sex
age
}
}`;
app.tsx
js
import { useLazyQuery } from '@apollo/client';
import UserList from './UserList';
import CreateUser from './CreateUser';
import { Button } from 'antd';
import { GET_LIST } from './gql';
import './App.css';
import { useEffect } from 'react';
export default function App() {
const [getList, { loading, data }] = useLazyQuery(GET_LIST, {
fetchPolicy: 'network-only' //用于实时更新
});
const getData = () => {
getList();
};
useEffect(() => {
getList();
}, []);
return <div>
<div className='user-list'>
<h1>人员信息表</h1>
<div className='btn-container'>
<CreateUser mode="add" refresh={getList}></CreateUser>
<Button type="primary" onClick={getData} style={{ marginLeft: "8px" }}>查询</Button>
</div>
<UserList {...{ loading, data, refresh: getList }}></UserList>
</div>
</div >;
}
CreateUser.tsx
js
import React, { useState, useRef } from 'react';
import { useMutation } from '@apollo/client';
import { Button, Modal, Form, Input, Radio, InputNumber } from 'antd';
import { CREATE_USER, UPDATE_USER } from './gql';
interface IProps {
mode?: string;
record?: Record<string, any>;
refresh?: () => void;
}
const CreateUser: React.FC = ({ mode, record, refresh }: IProps) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const formRef = useRef(null) as any;
const [createUser] = useMutation(CREATE_USER);
const [updateUser] = useMutation(UPDATE_USER);
const showModal = () => {
setIsModalOpen(true);
};
const handleOk = () => {
const formVal = formRef.current;
if (formVal) {
const values = formVal?.getFieldsValue();
if (mode === 'add') {
createUser({
variables: values
}).finally(() => {
setIsModalOpen(false);
refresh && refresh();
});
return;
}
updateUser({
variables: {
id: record?.id,
...values
}
}).finally(() => {
setIsModalOpen(false);
refresh && refresh();
});
}
};
const handleCancel = () => {
setIsModalOpen(false);
};
return (
<>
{mode === 'add' ? <Button type="primary" onClick={showModal}>
新增
</Button> : <a onClick={showModal}>修改</a>}
<Modal
title={mode === 'add' ? "新增人员" : "修改人员"}
cancelText="取消"
okText="保存"
open={isModalOpen}
onOk={handleOk}
onCancel={handleCancel}>
<Form
name="basic"
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
initialValues={{ remember: true }}
// onFinish={onFinish}
// onFinishFailed={onFinishFailed}
autoComplete="off"
ref={formRef}
>
<Form.Item
label="姓名"
name="name"
rules={[{ required: true, message: '请输入姓名!' }]}
initialValue={record?.name}
>
<Input />
</Form.Item>
<Form.Item
label="性别"
name="sex"
rules={[{ required: true, message: '请输入性别' }]}
initialValue={record?.sex}
>
<Radio.Group name="radiogroup" >
<Radio value='male'>男</Radio>
<Radio value='female'>女</Radio>
</Radio.Group>
</Form.Item>
<Form.Item
label="年龄"
name="age"
rules={[{ required: true, message: '请输入年龄!' }]}
initialValue={record?.age}
>
<InputNumber />
</Form.Item>
</Form>
</Modal>
</>
);
};
export default CreateUser;
UserList.tsx
js
import { Table, Space, Modal } from 'antd';
import CreateUser from './CreateUser';
import { DELETE_USER } from './gql';
import { useMutation } from '@apollo/client';
import { ExclamationCircleOutlined } from '@ant-design/icons';
const { confirm } = Modal;
export default function UserList({ loading, data, refresh }: any) {
const dataSource = data?.getList;
const [deleteUser] = useMutation(DELETE_USER);
const handleDelete = (id: string) => {
confirm({
title: '删除',
icon: <ExclamationCircleOutlined />,
content: '你确定要删除这个人员吗?',
okText: '确定',
okType: 'danger',
cancelText: '取消',
onOk() {
deleteUser({
variables: { id }
});
refresh();
},
onCancel() {
console.log('Cancel');
},
});
};
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
},
{
title: '姓名',
dataIndex: 'name',
key: 'name',
},
{
title: '性别',
dataIndex: 'sex',
key: 'sex',
},
{
title: '年龄',
dataIndex: 'age',
key: 'age',
},
{
title: 'Action',
key: 'action',
render: (_: any, record: any) => (
<Space size="middle">
<CreateUser mode="edit" record={record} refresh={refresh}></CreateUser>
<a onClick={() => handleDelete(record?.id)}>删除</a>
</Space>
),
},
];
return <Table dataSource={dataSource} columns={columns} loading={loading} />;
}
项目目录
总结
这是一个简单的增删改查的小页面,它包含了apollo-server
和apollo-client
所有的基础知识。通过这个小项目你能充分的理解apollo-server
和apollo-client
组合而成的项目。