grahql极简入门教程(基于react+graphql-yoga+urql)①

1,638 阅读6分钟

本文基于howtographql教程,使用下面较为容易上手的技术栈,为大家做一个极简入门

  • 前端
    • react@18.2.0(基于create-react-app@5.0.1版本创建项目)
    • react-router-dom@6.8.0
    • urql@3.0.3
    • graphql@16.6.0
  • 后端
    • graphql-yoga@3.5.1
    • @prisma/client@4.9.0
    • graphql@16.6.0

由于不知道你看到本文时,相关技术库的版本迭代到了多少,因此列出本文内使用的相关包版本号,如果遇到不可知的问题,可以与上面的版本号保持一致。(成文于2023-02-03)

本文是该系列的第一篇,为大家初步搭建前后端,以及生成一个hello world的demo。

graphql极简入门教程目录:

👉🏻点击进入本教程github仓库

项目准备

创建react项目

在命令行中基于create-react-app快速创建react项目:

npx create-react-app hackernews-react-graphql-yoga

打开刚刚创建的项目,在src目录下新建componentsstyles文件夹,并将App.tsxApp.test.tsx移动到src/components文件夹下,index.cssApp.css移动到src/styles文件夹下,调整后的目录结构如下:

├── README.md
├── package-lock.json
├── package.json
├── public
└── src
    ├── components
    │   ├── App.js
    │   └── App.test.js
    ├── index.js
    ├── logo.svg
    ├── reportWebVitals.js
    ├── setupTests.js
    └── styles
        ├── App.css
        └── index.css

打开src/index.tsx文件,将文件内的代码替换为(主要是修改一下文件的路径):

import React from 'react';
import ReactDOM from 'react-dom/client';
import './styles/index.css';
import App from './components/App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

由于本文着重于讲述如何使用graph搭建服务,为了加速开发速度,这里引入一个css库tachyons

打开public/index.html,添加tachyons:

    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <!--
      manifest.json provides metadata used when your web app is installed on a
      user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
    -->
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->

+    <link
+      rel="stylesheet"
+      href="https://unpkg.com/tachyons@4.12.0/css/tachyons.min.css"
+    />

接着打开src/styles/index.css文件,将文件内样式替换为

body {
  margin: 0;
  padding: 0;
  font-family: Verdana, Geneva, sans-serif;
}

input {
  max-width: 500px;
}

.gray {
  color: #828282;
}

.orange {
  background-color: #ff6600;
}

.background-gray {
  background-color: rgb(246, 246, 239);
}

.f11 {
  font-size: 11px;
}

.w85 {
  width: 85%;
}

.button {
  font-family: monospace;
  font-size: 10pt;
  color: black;
  background-color: buttonface;
  text-align: center;
  padding: 2px 6px 3px;
  border-width: 2px;
  border-style: outset;
  border-color: buttonface;
  cursor: pointer;
  max-width: 250px;
}

到这里我们已经搭建起了一个简单的前端页面,接下来我们创建后端服务。

基于graphql-yoga创建后端服务

在命令行中,键入以下指令安装graphql-yoga

npm install graphql graphql-yoga

并安装nodemon(检测到文件变更,自动重启服务)

npm install nodemon --save-dev

让我们创建一个server目录来存放后端的内容,并添加一个index.js文件,在文件中增加如下的内容:

// 路径:src/server/index.js
import { createServer } from 'node:http'
import { createYoga } from 'graphql-yoga'
import { createSchema } from 'graphql-yoga'

const schema = createSchema({
    // ①
    typeDefs: `
        //②
        type Query {
          // ③
          hello: String!
        }
    `,
    // ④
    resolvers: {
        Query: {
            hello: () => 'world'
        }
    }
})

// 基于graphql的scheme,创建一个graphql-yoga的实例
const yoga = createYoga({ schema })

// 基于实例化后的yoga,创建一个server
const server = createServer(yoga)

// 后端将在http://localhost:4000/graphql地址上启动server
server.listen(4000, () => {
    console.info('Server is running on http://localhost:4000/graphql')
})

①中typeDefs字段传入的内容,定义了GraphQL Schema(笔者保持与官方英文名称,暂不翻译)。它其实就像你在写typescript时,会预先定义类型一样。

示例中的内容含义是:②是一个查询类型,③有一个叫做hello的字段,这个字段是一个string类型,!代表这个类型一定会返回一个不为null的字符串。

④中的resolvers是针对GraphQL Schema的数据定义,具体执行相关的方法,返回与GraphQL Schema定义数据类型一致的数据

用上面的代码举例就是:我之前定义了一个查询类型会返回一个不为null的hello字段,他是字符串类型,但这个只是我对于数据类型的定义,具体怎么获取这个数据,就需要通过resolvers里的设定来实现。

如果还是不太明白,可以结合下面的具体的返回数据来理解。

请注意观察resolvers.Query中的结构与typeDefs中的type Query定义结构之间相同的地方。

在package.json文件内添加:

{
  ...,
    "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
+   "start:server": "nodemon --exec node src/server/index.js"
  },
  ...
}

在项目根目录下,执行下面命令

npm run start:server

并在浏览器中打开:http://localhost:4000/graphql,进入play ground页面

在左侧的输入框,输入下面的内容:

query {
  hello
}

graphql playground

执行后我们可以看到,返回的结果与我们在resolvers中定义的一致

安装urql

urql是我们在react中请求graphql服务的工具,类似于axios

在项目根目录下,键入以下指令,安装urql

npm install --save urql

下面我们在react中引入urql,将src/index.js的内容替换为:

import React from 'react';
import ReactDOM from 'react-dom/client';
import './styles/index.css';
import App from './components/App';
import reportWebVitals from './reportWebVitals';

import { createClient, Provider } from 'urql';

// ①
const client = createClient({
    url: 'http://localhost:4000/graphql',
});
const root = ReactDOM.createRoot(document.getElementById('root'));

root.render(
    <React.StrictMode>
        {/*②*/}
        <Provider value={client}>
            <App />
        </Provider>
    </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

①中我们创建了一个客户端,graphql服务端的地址设置为http://localhost:4000/graphql

②中我们提供了一个上下文,供组件内使用

完成hello world

接下来将src/components/App.js的内容替换为:

import React from 'react';
import { useQuery } from "urql";

// ①
const HelloQuery = `
  query {
    hello
  }
`;

function App() {
  // ②
  const [result] = useQuery({
    query: HelloQuery,
  });

  // ③
  const { data, fetching, error } = result;

  // ④
  if (fetching) return <p>Loading...</p>;
  // ⑤
  if (error) return <p>Oh no... {error.message}</p>;

  // ⑥
  return (
      <span>{JSON.stringify(data)}</span>
  );
}

export default App;

①中,我们定义了查询的内容,与之前我们在play ground里输入的一致

graphql playground

②中我们在useQuery的hook中,传入定义好的查询内容,解构出result结果

③由于result中包含

  • data:返回的数据
  • fetching:当前是否正在从后端获取数据中
  • error:错误信息

因此需要解构后,方便后续使用

④如果fetching为true,意味着当前正在获取数据中,此时展示loading文案

⑤如果请求失败,展示错误信息

⑥展示返回的数据

启动react服务

npm run start

打开 http://localhost:3000 后你在页面会看到下面的数据:

hello world数据

对比一下之前在play ground返回的数据,两个的内容是一致的

恭喜!你已经成功完成了最经典的hello world入门!接下来我们将会一步步搭建一个hackernews的简化版前后端程序。

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 1 天,点击查看活动详情