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

1,888 阅读4分钟

本文是graphql极简入门教程的第七篇,内容主要讲述前端对接已有的用户系统,实现前后端联调。

graphql极简入门教程目录:

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

前端接入登录及注册功能

前端登录组件

在前面的章节中已经定义登录/注册的graphql语句,请同学们先不要看截图后面的代码,想一想你应该怎么写前端的语句:

这里放上之前在playground的截图,方便回忆: 注册 登录

下面就来揭晓答案,注册功能👇🏻

const signupMutation = `
    mutation SignupMutation(
        $email: String!
        $password: String!
        $name: String!
    ) {
        signup(
            email: $email
            password: $password
            name: $name
        ) {
            token
        }
    }
`;

登录功能👇🏻

const loginMutation = `
    mutation LoginMutation(
        $email: String!
        $password: String!
    ) {
        login(email: $email, password: $password) {
            token
        }
    }
`;

由于每次请求都会携带token信息,为了方便前端会将后端返回的token存入localstorage中,在每次请求时获取完后添加到请求的headers中。

因此需要新建一个constants.js文件,把localstoragekey值放到该文件中:

// 文件路径:src/constants.js
export const AUTH_TOKEN = 'auth-token';

接下来实现具体的登录/注册功能组件逻辑,在src/components文件夹下,新增Login.js组件:

// 文件路径:src/components/Login.js
import React, {useState} from 'react';
import {useNavigate} from 'react-router-dom';
import {AUTH_TOKEN} from "../constants";
import {useMutation} from "urql";

const signupMutation = `
    mutation SignupMutation(
        $email: String!
        $password: String!
        $name: String!
    ) {
        signup(
            email: $email
            password: $password
            name: $name
        ) {
            token
        }
    }
`;

const loginMutation = `
    mutation LoginMutation(
        $email: String!
        $password: String!
    ) {
        login(email: $email, password: $password) {
            token
        }
    }
`;

const Login = () => {
    const navigate = useNavigate();
    const [formState, setFormState] = useState({
        login: true,
        email: '',
        password: '',
        name: ''
    });

    const [,login] = useMutation(loginMutation);

    const [,signup] = useMutation(signupMutation);

    return (
        <div>
            <h4 className="mv3">
                {formState.login ? 'Login' : 'Sign Up'}
            </h4>
            <div className="flex flex-column">
                {!formState.login && (
                    <input
                        value={formState.name}
                        onChange={(e) =>
                            setFormState({
                                ...formState,
                                name: e.target.value
                            })
                        }
                        type="text"
                        placeholder="Your name"
                    />
                )}
                <input
                    value={formState.email}
                    onChange={(e) =>
                        setFormState({
                            ...formState,
                            email: e.target.value
                        })
                    }
                    type="text"
                    placeholder="Your email address"
                />
                <input
                    value={formState.password}
                    onChange={(e) =>
                        setFormState({
                            ...formState,
                            password: e.target.value
                        })
                    }
                    type="password"
                    placeholder="Choose a safe password"
                />
            </div>
            <div className="flex mt3">
                <button
                    className="pointer mr2 button"
                    onClick={() => {
                        if (formState.login) {
                            // ①
                            login({
                                email: formState.email,
                                password: formState.password
                            }).then(({ data: { login } }) => {
                                localStorage.setItem(AUTH_TOKEN, login.token);
                                navigate('/');
                            });
                        } else {
                            // ①
                            signup({
                                name: formState.name,
                                email: formState.email,
                                password: formState.password
                            }).then(({ data: { signup } }) => {
                                localStorage.setItem(AUTH_TOKEN, signup.token);
                                navigate('/');
                            })
                        }
                    }}
                >
                    {formState.login ? 'login' : 'create account'}
                </button>
                <button
                    className="pointer button"
                    onClick={(e) =>
                        setFormState({
                            ...formState,
                            login: !formState.login
                        })
                    }
                >
                    {formState.login
                        ? 'need to create an account?'
                        : 'already have an account?'}
                </button>
            </div>
        </div>
    );
};

export default Login;

相信经过前面使用useMutation钩子的经验,你应该不难理解上面的代码。

①中在执行登录/注册时,前端将用户输入的信息传递给useMutation钩子,在操作成功后将后端返回的token写入localstorage中,并跳转至首页。

现在,在src/components/App.js中,添加登录页面的路由:

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

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 path="/login" element={<Login/>} />
        </Route>
    )
);
// ...

同时还需要在导航栏中,添加登录入口:

  • 如果用户未登录,那么就不显示提交链接(submit)的入口,并展示登录(login)入口;
  • 如果用户已经登录了,就展示登出(logout)按钮,点击后会清除本地token缓存,并重定向到首页(/)。

打开src/components/Header.js文件,输入以下内容,完成上面的需求:

import React from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { AUTH_TOKEN } from '../constants';

const Header = () => {
    const navigate = useNavigate();
    const authToken = localStorage.getItem(AUTH_TOKEN);

    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="/search"
                    className="ml1 no-underline black"
                >
                    search
                </Link>
                {authToken && (
                    <div className="flex">
                        <div className="ml1">|</div>
                        <Link
                            to="/create"
                            className="ml1 no-underline black"
                        >
                            submit
                        </Link>
                    </div>
                )}
            </div>
            <div className="flex flex-fixed">
                {authToken ? (
                    <div
                        className="ml1 pointer black"
                        onClick={() => {
                            localStorage.removeItem(AUTH_TOKEN);
                            navigate(`/`);
                        }}
                    >
                        logout
                    </div>
                ) : (
                    <Link
                        to="/login"
                        className="ml1 no-underline black"
                    >
                        login
                    </Link>
                )}
            </div>
        </div>
    );
};

export default Header;

urql发送请求的headers中,需要额外添加authorization字段,方便后端识别用户信息。

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';
import {AUTH_TOKEN} from "./constants";

const client = createClient({
    url: 'http://localhost:4000/graphql',
    fetchOptions: () => {
        const token = localStorage.getItem(AUTH_TOKEN);
        return {
            // ①
            headers: { authorization: token ? `Bearer ${token}` : '' },
        };
    },
});
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();

在①中,将token以Bearer ${token}形式回传给后端。如果有忘记如何使用jwt的同学,也请参见阮一峰老师的入门链接👉🏻JSON Web Token 入门教程

到这里就已经搞定了前端对接用户信息,接下来发送一条数据,来验证一下用户信息是否正确记录上。

创建带有用户信息的链接

打开http://localhost:3000/create页面,创建一条数据:

image-20230210231435573

接下来,修改src/components/LinkList.js文件,添加请求postedBy字段的数据结构描述

const feedQuery = `
    query FeedQuery($take: Int, $skip: Int, $orderBy: LinkOrderByInput) {
        feed(take: $take, skip: $skip, orderBy: $orderBy) {
            count
            links {
                id
                createdAt
                url
                description
+                postedBy {
+                    id
+                    name
+                }
            }
        }
    }
`;

此时刷新页面,打开控制台,就可以看到后端回传的postedBy字段的具体信息啦!

image-20230210232721652

至此我们关于用户系统的接入已经全部完成!恭喜你已经更进一步了解了graphql了

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