Graphql的全栈指南——掌握React客户端
这是GraphQL系列文章的最后一篇。
我们从GraphQl的介绍开始,实现了一个简单的NodeJs GraphQl服务,以了解Resolvers 、Schemas (typeDefs) 、Query 和Mutation 等概念,然后我们在elixir中用Absinthe构建了同样的GraphQL服务。
在这篇文章中,我们将使用ReactJs和Apollo graphql客户端为我们编写的菜单卡服务构建UI。让我们开始吧。
就像每个React例子的开始:
$ npx create-react-app menu_card_client
$ cd menu_card_client
$ yarn start
让我们添加我们需要的GraphQL依赖项:
$ yarn add @apollo/client graphql
- @apollo/client是一个GraphQL客户端库,通过GraphQL获取数据。
- graphql是apollo使用的一个核心GraphQL库。
我们准备好编码了!👨💻
编辑index.js :
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { ApolloProvider, ApolloClient, InMemoryCache } from "@apollo/client";
const client = new ApolloClient({
url: process.env.REACT_APP_API_URL,
cache: new InMemoryCache(),
});
ReactDOM.render(
<ApolloProvider client={client}>
<React.StrictMode>
<App />
</React.StrictMode>
</ApolloProvider>,
document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
更新你的.env ,以
REACT_APP_API_URL=https://localhost:4000
将REACT_APP_GRAPHQL_URL 设置为你正在运行的 graphql 服务端点。
我们添加的行:
const client = new ApolloClient({
url: process.env.REACT_APP_GRAPHQL_URL,
cache: new InMemoryCache(),
});
客户端是我们的React应用和GraphQL服务之间的接口,我们要告诉它使用所提供的url的服务,并使用可配置的缓存。
如果你还没有运行GraphQL节点服务器,请启动该服务并继续或克隆回购文件:
$ git clone https://github.com/jawakarD/node-graphql-menu.git && cd node-graphql-menu && yarn && yarn start.
ReactDOM.render(
<ApolloProvider client={client}>
<React.StrictMode>
<App />
</React.StrictMode>
</ApolloProvider>,
document.getElementById("root")
);
为了使client 被我们在应用程序中使用的钩子所消耗,将App 组件作为一个子节点传递给ApolloProvider ,它作为一个上下文提供者并将client 作为一个上下文传递。
这就是index.js 的全部内容。
让我们转到功能方面。
不要担心css,删除App.css 上的内容,复制并粘贴这个要点。
获取菜单项 - useQuery
useQuery 钩子允许我们查询graphql服务,就像我们在服务器端玩的那样,但在这里我们要使用react来调用这些查询。
基本用法:
const { loading, error, data } = useQuery(QUERY, options);
useQuery 需要两个参数
- GraphQL查询字符串,用于我们要进行的查询
- 选项将有变量和其他我们可以传递的选项
编辑你的App.js,有两个组件MenuItem 和App :
// App.js
const MenuItem = ({ name, id, price, rating }) => {
//just for fun
const COLORS = ["#ff00f6", "#00ff50", "#fff900", "#ff8300"];
const color = useMemo(
() => COLORS[Math.floor(Math.random() * COLORS.length)],
[]
);
return (
<div className="menu-item">
<div className="info">
<p className="name" style={{ color }}>
{`${name} *${rating}`}
</p>
<p className="item-price" style={{ color }}>
{price}
</p>
</div>
<div className="action">
<button
type="button"
style={{ color, borderColor: color }}
>
Delete
</button>
</div>
</div>
);
};
const GET_MENU_ITEMS = gql`
query {
menuItems {
id
name
price
rating
}
}
`;
const App = () => {
const { loading, error, data } = useQuery(GET_MENU_ITEMS);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>error</p>;
}
const { menuItems } = data;
return (
<div className="App">
<div className="App-body">
<div className="add-form">
<button>Add Item</button>
</div>
<div className="menu-box">
{menuItems.map(item => (
<MenuItem key={item.id} {...item} />
))}
</div>
</div>
</div>
);
};
在App 组件中,我们用GET_MENU_ITEMS 查询调用useQuery 钩子,以获取菜单项目的列表。
该钩子返回正在获取的数据的不同状态:加载、错误(如果查询中有的话)、查询的实际数据,以及其他值。
该查询也将被缓存起来。如果在另一个组件中再次调用,apollo将从缓存中获取数据,这将使后续的查询更快。另一个有趣的事情是,你也可以与存储的缓存进行交互。但不建议像redux那样把apollo缓存作为一个状态管理来使用,因为redux会比apollo缓存给我们更多的数据更新控制。
useQuery 也允许其他功能。轮询(在指定的时间间隔内定期执行查询)和重新获取(重新获取查询)。你可能想选择 hook,用于graphql查询的实际执行需要发生在一些其他的用户动作或事件上,而不是默认的执行行为。useLazyQuery 在这里阅读更多关于这方面的内容。
现在,如果你转入浏览器,你可以看到当前可用的菜单列表。如果它是空的,你可以等我们到useMutation 部分时再添加一些。
创建菜单项--useMutation
useMutation 允许我们用graphql服务器运行突变。
基本用法:
const [mutateFunction, {called, loading, data, error}] = useMutation(MUTATION, options);
像useQuery ,useMutation 需要两个参数:gql查询字符串和突变选项。但是像useLazyQuery ,它本身并不做任何API调用,而是返回一个包含的tuple:
- 一个突变函数,用于在任何时候执行突变
- 一个带有突变状态的对象。
called,loading,data和error
要使用用户界面添加一个新的菜单项,添加一个Form 组件,并将其纳入App 组件中。
编辑你的App.js,使其拥有Form 组件,并在App 组件中使用:
const CREATE_MENU_ITEM = gql`
mutation addMenuItem($name: String!, $price: Int!, $rating: Int) {
addMenuItem(params: { name: $name, price: $price, rating: $rating }) {
id
name
price
rating
}
}
`;
const Form = () => {
const [name, setName] = useState("");
const [price, setPrice] = useState();
const [rating, setRating] = useState();
const [addMenuItem] = useMutation(CREATE_MENU_ITEM, {
update(cache, { data: { addMenuItem } }) {
const { menuItems } = cache.readQuery({ query: GET_MENU_ITEMS });
cache.writeQuery({
query: GET_MENU_ITEMS,
data: { menuItems: [addMenuItem, ...menuItems] },
});
},
});
const onSubmit = (e) => {
e.preventDefault();
addMenuItem({
variables: {
name,
price: Number(price),
rating: Number(rating),
},
});
};
return (
<>
<form className="form">
<div className="form-group">
<label htmlFor="name">Name</label>
<input
type="text"
onChange={(e) => setName(e.target.value)}
id="name"
value={name}
/>
</div>
<div className="form-group">
<label htmlFor="price">Price</label>
<input
type="number"
onChange={(e) => setPrice(e.target.value)}
id="price"
value={price}
/>
</div>
<div className="form-group">
<label htmlFor="rating">Rating</label>
<input
type="number"
onChange={(e) => setRating(e.target.value)}
id="rating"
value={rating}
/>
</div>
</form>
<button type="button" onClick={onSubmit}>
Submit
</button>
</>
);
};
// Edit App component to:
const App = () => {
> const [formOpen, setFormOpen] = useState(false);
const { loading, error, data } = useQuery(GET_MENU_ITEMS);
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>error</p>;
}
const { menuItems } = data;
return (
<div className="App">
<div className="App-body">
<div className="add-form">
> <button onClick={() => setFormOpen((o) => !o)}>Add Item</button>
> {formOpen && <Form />}
</div>
<div className="menu-box">
{menuItems.map((item) => (
<MenuItem key={item.id} {...item} />
))}
</div>
</div>
</div>
);
};
update函数:
const [addMenuItem] = useMutation(CREATE_MENU_ITEM, {
update(cache, { data: { addMenuItem } }) {
const { menuItems } = cache.readQuery({ query: GET_MENU_ITEMS });
cache.writeQuery({
query: GET_MENU_ITEMS,
data: { menuItems: [addMenuItem, ...menuItems] },
});
},
});
这里我们将选项传递给useMutation ,但我们在调用addMutation 函数时也可以将选项传递给mutate 函数。
我们传递给mutate函数的选项将覆盖useMutation 中的选项。
update mutate函数是一个回调函数,在突变成功后会被调用,参数是突变返回的 ,以及当前的 接口。data cache
通过突变,你只是在更新服务器端的数据。
如果一个突变更新了一个现有的实体,当突变返回时,apollo-graphql会自动在其缓存中更新该实体的值,并将更新后的实体id 。
除了更新之外,如果我们使用突变做其他的更新(增加和删除),我们必须手动更新缓存以反映这些变化。
因此,理解我们在上述useMutation 中使用的更新函数。
- 获取查询的缓存
GET_MENU_ITEMS:
const { menuItems } = cache.readQuery({ query: GET_MENU_ITEMS });
cache.writeQuery用于将数据写入一个特定的查询,这里使用传播操作符将 查询的数据与新创建的数据进行更新。GET_MENU_ITEMS
cache.writeQuery({
query: GET_MENU_ITEMS,
data: { menuItems: [addMenuItem, ...menuItems] },
});
在提交时:
const onSubmit = (e) => {
e.preventDefault();
addMenuItem({
variables: {
name,
price: Number(price),
rating: Number(rating),
},
});
};
这里,我们正在传递选项中的变量,这些变量将用于突变mutation addMenuItem($name: String!, $price: Int!, $rating: Int) ,创建一个菜单项。
保存文件并到浏览器中添加一个菜单项。在你提交表单后,你会看到提交的菜单项会因为更新函数而被添加到列表中。
你可以通过练习来实现删除一个菜单项和更新缓存的删除功能。
query 和 ,这是graphql中需要了解的主要内容。还有一个主题叫 ,它使用WebSocket将变化推送到客户端,而不是我们查询或变异。mutation subscriptions