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

132 阅读4分钟

本文是graphql极简入门教程的第四篇,本篇内容主要讲述为react添加路由导航、完成前后端搜索功能

graphql极简入门教程目录:

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

添加路由

react路由采用react-router6,因此需要安装react-router-dom依赖:

npm install react-router-dom@6.8

页面内添加顶部导航,在src/components文件夹下,新建Header.js文件,在文件内编写顶部导航代码:

import React from 'react';
import { Link } from 'react-router-dom';

const Header = () => {
  return (
    <div className="flex pa1 justify-between nowrap orange">
      <div className="flex flex-fixed black">
        <Link to="/" className="no-underline black">
          <div className="fw7 mr1">Hacker News</div>
        </Link>        
        <Link to="/" className="ml1 no-underline black">
          new
        </Link>
        <div className="ml1">|</div>
        <Link
          to="/create"
          className="ml1 no-underline black"
        >
          submit
        </Link>
      </div>
    </div>
  );
};

export default Header;

src/components/App.js文件中引入导航组件,并配置不同的路由:

由于react-routerv6.4后的版本对api进行了更新,导致用法与以前的版本有所不同,具体可以参考官网文档

import React from 'react';
import CreateLink from './CreateLink';
import Header from './Header';
import LinkList from './LinkList';
import {Outlet, createBrowserRouter, RouterProvider, createRoutesFromElements, Route} from 'react-router-dom';

const AppLayout = () => (
    <div className="center w85">
        <Header />
        <div className="ph3 pv1 background-gray">
            <Outlet />
        </div>
    </div>
)

const router = createBrowserRouter(
    createRoutesFromElements(
        <Route element={<AppLayout />}>
            <Route path="/" element={<LinkList />} />
            <Route path="/create" element={<CreateLink />} />
        </Route>
    )
);
const App = () => (
    <RouterProvider router={router} />
);

export default App;

此时打开http://localhost:3000页面,可以看到当前的展示效果:

改版后首页

完成了导航后,还需要完善创建链接页面:

如果创建链接成功后,跳回首页,需要对src/components/CreateLink.js文件做如下改动:

import React, { useState } from 'react';
import {useMutation} from "urql";
+ import { useNavigate } from 'react-router-dom';

export const createLinkMutation = `
  mutation PostMutation(
    $description: String!
    $url: String!
  ) {
    post(description: $description, url: $url) {
      id
      createdAt
      url
      description
    }
  }
`;

const CreateLink = () => {
      // ①
+     const navigate = useNavigate();

    const [createLinkResult, createLink] = useMutation(createLinkMutation);

    const [formState, setFormState] = useState({
        description: '',
        url: ''
    });

    const { data, fetching, error } = createLinkResult;

    return (
        <div>
            <form
                onSubmit={(e) => {
                    e.preventDefault();
-                    createLink(formState)
                     // ②
+                    createLink(formState).then(() => navigate('/'));
                }}
            >
                <div className="flex flex-column mt3">
                    <input
                        className="mb2"
                        value={formState.description}
                        onChange={(e) =>
                            setFormState({
                                ...formState,
                                description: e.target.value
                            })
                        }
                        type="text"
                        placeholder="A description for the link"
                    />
                    <input
                        className="mb2"
                        value={formState.url}
                        onChange={(e) =>
                            setFormState({
                                ...formState,
                                url: e.target.value
                            })
                        }
                        type="text"
                        placeholder="The URL for the link"
                    />
                </div>
                <button disabled={fetching} type="submit">Submit {fetching && '(sending...)'}</button>
            </form>
        </div>
    );
};

export default CreateLink;

在①中,使用react-router提供的useNavigate钩子跳转页面。

在②中,指定创建成功后,跳转到首页。

打开http://localhost:3000/create创建链接页面,测试一下效果:

创建链接

创建成功后,自动跳回了首页,并且最新的数据已经展示出来:

创建链接后的首页

添加搜索

前端搜索语句定义

在搜索时,需要将用户输入的搜索值透传给graphql,因此前端的graphql的类型定义如下:

const feedSearchQuery = `
  query FeedSearchQuery($filter: String!) {
    feed(filter: $filter) {
      links {
        id
        url
        description
        createdAt
      }
    }
  }
`;

在查询时,传入了一个不为空的filter字符串,后端根据该字符串进行搜索。

后端数据类型定义

前端定义完成后,现在开始定义后端的数据类型,修改src/server/schema.graphql文件

type Query {
-    feed: Feed!
+    feed(filter: String): Feed!
}

type Link {
    id: ID!
    url: String!
    description: String!
    createdAt: DateTime!
}

type Feed {
    links: [Link!]!
}

scalar DateTime

type Mutation {
    post(url: String!, description: String!): Link!
}

在这里filter没有强制传入参数的原因是,有可能用户并没有输入搜索,此时还需要能够正常查询,因此在后端不进行强行校验。

添加后端执行逻辑

接着添加具体的搜索逻辑,对src/server/resolvers/Query.js文件进行更改:

async function feed(parent, args, context) {
+    const where = args.filter
+        ? {
+            OR: [
+                { description: { contains: args.filter } },
+                { url: { contains: args.filter } }
+            ]
+        }
+        : {};

-    const links = await context.prisma.link.findMany();
+    const links = await context.prisma.link.findMany({ where });

    return {
        links,
    };
}

module.exports = {
    feed
};

在上面的代码中,添加了搜索语句,针对descriptionurl两个字段进行过滤,如果任意(OR)一个字段包含(contains)用户输入的内容(args.filter),就返回该结果。

前端添加搜索组件

src/components目录下,新建Search.js文件,添加如下内容:

// 文件路径:src/components/Search.js
import React, { useState } from 'react';
import Link from './Link';
import {useQuery} from "urql";

export const feedSearchQuery = `
  query FeedSearchQuery($filter: String!) {
    feed(filter: $filter) {
      links {
        id
        url
        description
        createdAt
      }
    }
  }
`;
const Search = () => {
    // ①
    const [userInput, setUserInput] = useState('');
    // ②
    const [searchFilter, setSearchFilter] = useState('');
    const [result] = useQuery({
        query: feedSearchQuery,
        // ③
        variables: { filter: searchFilter },
    })

    const { data } = result;

    return (
        <>
            <div>
                Search
                <input
                    type="text"
                    // ①
                    onChange={(e) => setUserInput(e.target.value)}
                />
                // ②
                <button onClick={() => { setSearchFilter(userInput) }}>OK</button>
            </div>
            {data &&
                data.feed.links.map((link, index) => (
                    <Link key={link.id} link={link} index={index} />
                ))}
        </>
    );
};

export default Search;

在①中,输入框监听用户输入的数据并进行存储

当用户点击了OK按钮,就将用户输入的最终内容同步到searchFilter

在③中,接收到更新的搜索内容后,urql将会自动重新请求数据,将用户需要搜索的内容查询出来。

前端添加路由及导航

src/components/App.js文件中添加路由,改动如下:

import React from 'react';
import CreateLink from './CreateLink';
import Header from './Header';
import LinkList from './LinkList';
import {Outlet, createBrowserRouter, RouterProvider, createRoutesFromElements, Route} from 'react-router-dom';
+ import Search from "./Search";

const AppLayout = () => (
    <div className="center w85">
        <Header />
        <div className="ph3 pv1 background-gray">
            <Outlet />
        </div>
    </div>
)

const router = createBrowserRouter(
    createRoutesFromElements(
        <Route element={<AppLayout />}>
            <Route path="/" element={<LinkList />} />
            <Route path="/create" element={<CreateLink />} />
+            <Route path="/search" element={<Search />} />
        </Route>
    )
);
const App = () => (
    <RouterProvider router={router} />
);

export default App;

src/components/Header.js文件中添加导航,改动如下:

import React from 'react';
import { Link } from 'react-router-dom';

const Header = () => {
    return (
        <div className="flex pa1 justify-between nowrap orange">
            <div className="flex flex-fixed black">
                <Link to="/" className="no-underline black">
                    <div className="fw7 mr1">Hacker News</div>
                </Link>
                <Link to="/" className="ml1 no-underline black">
                    new
                </Link>
                <div className="ml1">|</div>
                <Link
                    to="/create"
                    className="ml1 no-underline black"
                >
                    submit
                </Link>
+                <div className="ml1">|</div>
+                <Link
+                    to="/search"
+                    className="ml1 no-underline black"
+                >
+                    search
+                </Link>
            </div>
        </div>
    );
};

export default Header;

打开http://localhost:4000/search页面,输入搜索内容juejin,你可以得到下面的结果:

搜索内容

本章我们完成了添加前端路由导航及前后端搜索功能,接下来的章节中我们将介绍如何针对列表数据进行分页。

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