根据HOW TO GRAPHQL官网的例子,做了些对最新版的改动,适合最新框架的学习。
本系列文章注重前端方面的开发,对于node方面的放在下一个系列。在此过程中有任何问题,都欢迎在评论中提问,会及时反馈
系列目录:
第一章. Frontend开始
第二章. Queries组件编写(Loading Links)
第三章. Mutations组件编写(Creating Links)
第四章. 页面路由
第五章. 身份验证
为了照顾更加全面的读者,我写的会尽量详细,熟练的开发者可进行快速选择性的阅读
本节我们来学习如何使用Apollo实现身份验证功能,以便为用户提供注册和登录功能。
在components文件夹中创建Login.js文件
import React, { Component } from 'react'
import { AUTH_TOKEN } from '../constants'
class Login extends Component {
state = {
login: true, // switch between Login and SignUp
email: '',
password: '',
name: '',
}
render() {
const { login, email, password, name } = this.state
return (
<div>
<h4 className="mv3">{login ? 'Login' : 'Sign Up'}</h4>
<div className="flex flex-column">
{!login && (
<input
value={name}
onChange={e => this.setState({ name: e.target.value })}
type="text"
placeholder="Your name"
/>
)}
<input
value={email}
onChange={e => this.setState({ email: e.target.value })}
type="text"
placeholder="Your email address"
/>
<input
value={password}
onChange={e => this.setState({ password: e.target.value })}
type="password"
placeholder="Choose a safe password"
/>
</div>
<div className="flex mt3">
<div className="pointer mr2 button" onClick={() => this._confirm()}>
{login ? 'login' : 'create account'}
</div>
<div
className="pointer button"
onClick={() => this.setState({ login: !login })}
>
{login
? 'need to create an account?'
: 'already have an account?'}
</div>
</div>
</div>
)
}
_confirm = async () => {
// 接下来需要完成的
}
_saveUserData = token => {
localStorage.setItem(AUTH_TOKEN, token)
}
}
export default Login
该组件通过this.state.login状态来实现登录和注册页面的切换,但状态为true时,页面展示只有两个输入框的登录页面,当状态变为false时,展现三个输入框的注册页面。
_confirm 函数将用于实现我们需要为登录功能发送的变更请求。
接下来,我们还需要一个constants.js文件,我们用它来定义我们存储在浏览器的localStorage中的凭据的key。
在src目录下新建constants.js文件
export const AUTH_TOKEN = 'auth-token'
然后我们将新的Login组件加入到我们的路由当中,打开App.js
<Switch>
<Route exact path="/" component={LinkList} />
<Route exact path="/create" component={CreateLink} />
<Route exact path="/login" component={Login} />
</Switch>
并引入Login组件
import Login from './Login'
最后,我们再Header组件中加上可以跳转至Login的导航
render() {
const authToken = localStorage.getItem(AUTH_TOKEN)
return (
<div className="flex pa1 justify-between nowrap orange">
<div className="flex flex-fixed black">
<div className="fw7 mr1">Hacker News</div>
<Link to="/" className="ml1 no-underline black">
new
</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)
this.props.history.push(`/`)
}}
>
logout
</div>
) : (
<Link to="/login" className="ml1 no-underline black">
login
</Link>
)}
</div>
</div>
)
}
在Header的右侧,我们加入了前往Login组件的导航,并且我们会先判断用户登录与否,没登录的话不提供create页面的导航
别忘了要在Header中引入需要的依赖
import { AUTH_TOKEN } from '../constants'
注册和登录是两个常规的GraphQL变更,我们可以像使用之前的createLink变更一样使用它。
打开Login.js,在前面添加两个变更定义
const SIGNUP_MUTATION = gql`
mutation SignupMutation($email: String!, $password: String!, $name: String!) {
signup(email: $email, password: $password, name: $name) {
token
}
}
`
const LOGIN_MUTATION = gql`
mutation LoginMutation($email: String!, $password: String!) {
login(email: $email, password: $password) {
token
}
}
`
同时在className为flex mt3的div中添加以下代码
<div className="flex mt3">
<Mutation
mutation={login ? LOGIN_MUTATION : SIGNUP_MUTATION}
variables={{ email, password, name }}
onCompleted={data => this._confirm(data)}
>
{mutation => (
<div className="pointer mr2 button" onClick={mutation}>
{login ? 'login' : 'create account'}
</div>
)}
</Mutation>
<div
className="pointer button"
onClick={() => this.setState({ login: !login })}
>
{login ? 'need to create an account?' : 'already have an account?'}
</div>
</div>
并添加所需依赖
import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'
现在我们终于要开始实现_confirm函数了
_confirm = async data => {
const { token } = this.state.login ? data.login : data.signup
this._saveUserData(token)
this.props.history.push(`/`)
}
这样我们就已经基本实现了一个带有身份功能的Login组件了,接下来就是Apollo如何配合的事情了
既然我们已经可以拿到token了,那么我们就需要把token添加进我们的每一个请求中。
Apollo提供了一种很好的方法,通过使用中间件的概念来验证所有请求,实现为Apollo Link。
让我们来添加该依赖
yarn add apollo-link-context
然后我们需要改造我们的index.js,在创建httpLink和ApolloClient实例之间放置以下代码:
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem(AUTH_TOKEN)
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : ''
}
}
})
并且引入依赖
import { setContext } from 'apollo-link-context'
import { AUTH_TOKEN } from './constants'
每次ApolloClient向服务器发送请求时,都会调用此中间件。
Note: 如果你想了解更多关于Apollo关于身份验证方面的信息,点击这里
接下来我们要确保link被正确配置进apollo client
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache()
})
现在如果token存在的话,我们所发送的所有请求都会通过身份验证了
我们需要确保只有通过身份验证的用户才能发布新链接,所以需要在服务端做一点小更改
打开/server/src/resolvers/Mutation.js,找到post函数,修改为以下
function post(parent, args, context) {
const userId = getUserId(context)
return context.prisma.createLink({
url: args.url,
description: args.description,
postedBy: { connect: { id: userId } },
})
}
我们从请求的Authorization header中提取userId,并作为link的一个postedBy属性传出。
现在让我们重启两个服务,然后注册登录账户,发送一个create请求试试吧,目前发送create请求回到LinkList页面时并不会展示你刚才添加的那一条,需要手动刷新,这个我们之后会解决这个问题。
本节我们成功实现了页面的身份验证配置!马上开始下一步的旅程吧。
https://github.com/zust-hh/simple-hackernews/tree/Frontend-topic5