Graphql的全栈指南——掌握React客户端

405 阅读2分钟

Graphql的全栈指南——掌握React客户端

这是GraphQL系列文章的最后一篇。

我们从GraphQl的介绍开始,实现了一个简单的NodeJs GraphQl服务,以了解ResolversSchemas (typeDefs)QueryMutation 等概念,然后我们在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 需要两个参数

  1. GraphQL查询字符串,用于我们要进行的查询
  2. 选项将有变量和其他我们可以传递的选项

编辑你的App.js,有两个组件MenuItemApp

// 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);

useQueryuseMutation 需要两个参数:gql查询字符串和突变选项。但是像useLazyQuery ,它本身并不做任何API调用,而是返回一个包含的tuple:

  1. 一个突变函数,用于在任何时候执行突变
  2. 一个带有突变状态的对象。called,loading,dataerror

要使用用户界面添加一个新的菜单项,添加一个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