翻译 | 《JavaScript Everywhere》第15章 Web身份验证和状态

写在最后面

大家好呀,我是毛小悠,是一位前端开发工程师。正在翻译一本英文技术书籍。

为了进步大家的浏览体验,对语句的构造和内容略有调整。如果发现本文中有存在瑕疵的中央,或者你有任何意见或者倡议,能够在评论区留言,或者加我的微信:code_maomao,欢送互相沟通交流学习。

(゚∀゚)..:*☆哎哟不错哦

第15章 Web身份验证和状态

最近我和我的家人搬家了。填写并签订了几种表格(我的手依然很累)后,咱们就用钥匙进了前门。每次咱们回到家,咱们都能够应用这些钥匙来解锁并进入门。我很快乐我每次回家都不须要填写表格,但也感激领有一把锁,这样咱们就不会有任何不请自来到来了。

客户端Web身份验证的工作形式简直与这雷同。

咱们的用户将填写表格,并以明码和存储在他们的浏览器中的令牌的模式交给网站。当他们返回站点时,他们将应用令牌主动进行身份验证,或者可能应用其明码从新登录。

在本章中,咱们将应用GraphQL API构建一个Web身份验证零碎。

为此,咱们将构建表单,将JWT存储在浏览器中,随每个申请发送令牌,并跟踪应用程序的状态。

创立注册表格

开始应用咱们的应用程序的客户端身份验证,咱们能够创立一个用户注册React组件。在这样做之前,让咱们先确定组件的工作形式。

首先,用户将导航到咱们应用程序中的/signup路由。在此页面上,他们将看到一个表单,能够在其中输出电子邮件地址、用户名和明码。提交表单将执行咱们APIsignUp申请 。如果申请胜利,将创立一个新的用户帐户,API将返回一个JWT。如果有谬误,咱们能够告诉用户。咱们将显示一条通用谬误音讯,但咱们能够更新API以返回特定的谬误音讯,例如事后存在的用户名或反复的电子邮件地址。

让咱们开始创立新路由。首先,咱们将在src/pages/signup.js创立一个新的React组件 。

import React, { useEffect } from 'react';// include the props passed to the component for later useconst SignUp = props => {useEffect(() => {// update the document titledocument.title = 'Sign Up — Notedly';});return (<div> <p>Sign Up</p> </div>);};export default SignUp;

当初,咱们将在src/pages/index.js中更新路由列表,包含注册路由:

// import the signup routeimport SignUp from './signup';// within the Pages component add the route<Route path="/signup" component={SignUp} />

通过增加路由,咱们将可能导航到 http:// localhost:1234/signup 来查看(大部分为空)注册页面。当初,让咱们为表单增加标记:

import React, { useEffect } from 'react';const SignUp = props => {useEffect(() => {// update the document titledocument.title = 'Sign Up — Notedly';});return (<div><form><label htmlFor="username">Username:</label><input required type="text" id="username" name="username" placeholder="username" /><label htmlFor="email">Email:</label><input required type="email" id="email" name="email" placeholder="Email" /><label htmlFor="password">Password:</label><input required type="password" id="password" name="password" placeholder="Password" /><button type="submit">Submit</button></form></div>);};export default SignUp;

htmlFor

如果你只是在学习React,那么常见的陷阱之一就是与HTML对应的JSX属性的不同。在这种状况下,咱们应用JSX htmlFor 代替HTMLfor 属性来防止任何JavaScript抵触。你能够在以下页面中看到这些属性的残缺列表(尽管简短)

React DOM Elements文档。

当初,咱们能够通过导入Button 组件并将款式设置为款式化组件来增加某种款式 :

import React, { useEffect } from 'react';import styled from 'styled-components';import Button from '../components/Button';const Wrapper = styled.div` border: 1px solid #f5f4f0;max-width: 500px;padding: 1em;margin: 0 auto; `;const Form = styled.form` label,input { display: block;line-height: 2em;}input { width: 100%;margin-bottom: 1em;} `;const SignUp = props => {useEffect(() => {// update the document titledocument.title = 'Sign Up — Notedly';});return (<Wrapper><h2>Sign Up</h2><Form><label htmlFor="username">Username:</label><inputrequiredtype="text"id="username"name="username"placeholder="username"/><label htmlFor="email">Email:</label><inputrequiredtype="email"id="email"name="email"placeholder="Email"/><label htmlFor="password">Password:</label><inputrequiredtype="password"id="password"name="password"placeholder="Password"/><Button type="submit">Submit</Button></Form></Wrapper>);};export default SignUp;

React表单和状态

在应用程序中会有事件的扭转。数据输出到表单中,用户将点击按钮,发送音讯。在React中,咱们能够通过调配state来在组件级别跟踪这些申请。在咱们的表单中,咱们须要跟踪每个表单元素的状态,以便在前面能够提交它。

React Hooks

在本书中,咱们将应用性能组件和React的较新Hooks API。如果你应用了其余应用React的类组件的学习资源 ,则可能看起来有些不同。你能够在React文档中浏览无关钩子的更多信息。

要开始应用状态,咱们首先将src/pages/signup.js文件顶部的React导入更新为useState

import React, { useEffect, useState } from 'react';

接下来,在咱们的 SignUp 组件中,咱们将设置默认表单值状态:

const SignUp = props => {// set the default state of the formconst [values, setValues] = useState();// rest of component goes here};

当初,咱们将更新组件在输出表单字段时更改状态,并在用户提交表单时执行操作。首先,咱们将创立一个onChange 函数,该函数将在更新表单时更新组件的状态。

当用户做了扭转后,通过调用这个函数的onChange属性来更新每个表单元素的标记。

而后,咱们在onSubmit 处理程序更新表单元素。当初,咱们仅将表单数据输入到控制台。

在/src/pages/sigunp.js

const SignUp = () => {// set the default state of the formconst [values, setValues] = useState();// update the state when a user types in the formconst onChange = event => {setValues({...values,[event.target.name]: event.target.value});};useEffect(() => {// update the document titledocument.title = 'Sign Up — Notedly';});return (<Wrapper><h2>Sign Up</h2><Form onSubmit={event => {event.preventDefault();console.log(values);}}><label htmlFor="username">Username:</label><input required type="text" name="username" placeholder="username" onChange={onChange} /><label htmlFor="email">Email:</label><input required type="email" name="email" placeholder="Email" onChange={onChange} /><label htmlFor="password">Password:</label><input required type="password" name="password" placeholder="Password" onChange={onChange} /><Button type="submit">Submit</Button></Form></Wrapper>);};

应用此表单标记后,咱们就能够申请具备GraphQL批改的数据了。

批改注册

要注册用户,咱们将应用APIsignUp 申请。如果注册胜利,此申请将承受电子邮件、用户名和明码作为变量,并返回JWT。让咱们写出咱们的申请并将其集成到咱们的注册表单中。

首先,咱们须要导入咱们的Apollo库。咱们将利用useMutation useApolloClient 挂钩以及 Apollo Clientgql语法。

src/pages/signUp中,在其余库import语句旁边增加以下内容:

import { useMutation, useApolloClient, gql } from '@apollo/client';

当初编写GraphQL批改,如下所示:

const SIGNUP_USER = gql`mutation signUp($email: String!, $username: String!, $password: String!) {signUp(email: $email, username: $username, password: $password)}`;

编写了申请后,咱们能够更新React组件标记以在用户提交表单时将表单元素作为变量传递来执行批改。当初,咱们将响应(如果胜利,应该是JWT)输入到控制台:

const SignUp = props => {// useState, onChange, and useEffect all remain the same here//add the mutation hookconst [signUp, { loading, error }] = useMutation(SIGNUP_USER, {onCompleted: data => {// console.log the JSON Web Token when the mutation is completeconsole.log(data.signUp);}});// render our formreturn (<Wrapper> <h2>Sign Up</h2> {/* pass the form data to the mutation when a user submits the form */} <Form onSubmit={event => {event.preventDefault();signUp({variables: {...values}});}}>{/* ... the rest of the form remains unchanged ... */} </Form> </Wrapper>);};

当初,如果你实现并提交表单,你应该会看到一个JWT输入到控制台(图15-1)。

另外,如果你在GraphQLPlaygroundhttp:// localhost4000/api)中执行用户查问,你将看到新帐户(图15-2)。

[外链图片转存失败,源站可能有防盗链机制,倡议将图片保留下来间接上传(img-6crer0sJ-1606432851137)(http://vipkshttp0.wiz.cn/ks/s...]

15-1。如果胜利,当咱们提交表单时,JSON Web令牌将打印到咱们的控制台

15-2。咱们还能够通过在GraphQL Playground中执行用户查问来查看用户列表

设置好批改并返回冀望的数据后,接下来咱们要存储收到的响应。

JSON Web令牌和本地存储

胜利实现咱们的 signUp申请后,它会返回JSON Web令牌(JWT)。你可能会从本书的API局部回忆起JWT 容许咱们在用户设施上平安存储用户ID。为了在用户的Web浏览器中实现此目标,咱们将令牌存储在浏览器的 localStorage中。 localStorage 是一个简略的键值存储,可在浏览器会话之间保留,直到更新或革除该存储为止。让咱们更新申请以将令牌存储在 localStorage中。

src/pages/signup.js ,更新 useMutation 钩子以将令牌存储在本地存储中 ( 见图15-3):

const [signUp, { loading, error }] = useMutation(SIGNUP_USER, {onCompleted: data => {// store the JWT in localStoragelocalStorage.setItem('token', data.signUp);}});

15-3。咱们的Web令牌当初存储在浏览器的localStorage

JWT和安全性

当令牌存储在 localStorage中时,能够在页面上运行的任何JavaScript都能够拜访该令牌,而后容易受到跨站点脚本(XSS)攻打。因而,在应用 localStorage 存储令牌凭证时,须要分外小心以限度(或防止)CDN托管脚本。如果第三方脚本被盗用,它将有权拜访JWT

随着咱们的JWT存储在本地,咱们筹备在GraphQL申请和查问中应用它。

重导向

以后,当用户实现注册表单时,该表单会从新出现为空白表单。这不会给用户很多视觉提醒,表明他们的帐户注册胜利。相同,咱们能够将用户重定向到应用程序的主页。另一种抉择是创立一个“胜利”页面,该页面感激用户注册并将其注册到应用程序中。

你可能会在本章后面接触到,咱们能够将属性传递到组件中。咱们能够应用React Router的历史记录重定向路由,这将通过props.history.push 实现。为了实现这一点,咱们将更新咱们的批改的 onCompleted 事件,包含如下所示的重定向:

const [signUp, { loading, error }] = useMutation(SIGNUP_USER, {onCompleted: data => {// store the tokenlocalStorage.setItem('token', data.signUp);// redirect the user to the homepageprops.history.push('/');}});

进行此更改后,当初用户在注册帐户后将被重定向到咱们应用程序的主页。

发送附加标头的申请

只管咱们将令牌存储在 localStorage中,但咱们的API尚未拜访它。这意味着即便用户创立了帐户,API也无奈辨认该用户。如果你回忆咱们的API开发,每个API调用都会在申请的标头中收到一个令牌。咱们将批改客户端以将JWT作为每个申请的标头发送。

src/App.js中, 咱们将更新依赖项,包含来自Apollo ClientcreateHttpLink以及来自ApolloLink Context包的setContext 。而后,咱们将更新Apollo的配置,以在每个申请的标头中发送令牌:

// import the Apollo dependenciesimport {ApolloClient,ApolloProvider,createHttpLink,InMemoryCache} from '@apollo/client';import { setContext } from 'apollo-link-context';// configure our API URI & cacheconst uri = process.env.API_URI;const httpLink = createHttpLink({ uri });const cache = new InMemoryCache();// check for a token and return the headers to the contextconst authLink = setContext((_, { headers }) => {return {headers: {...headers,authorization: localStorage.getItem('token') || ''}};});// create the Apollo clientconst client = new ApolloClient({link: authLink.concat(httpLink),cache,resolvers: {},connectToDevTools: true});

进行此更改后,咱们当初能够将已登录用户的信息传递给咱们的API

本地状态治理

咱们曾经钻研了如何在组件中治理状态,然而整个应用程序呢?有时在许多组件之间共享一些信息很有用。咱们能够在整个应用程序中从根本组件传递组件,然而一旦咱们通过几个子组件级别,就会变得凌乱。一些库如Redux MobX 试图解决状态治理的挑战,并已证实对许多开发人员和团队十分有用。

在咱们的案例中,咱们曾经在应用Apollo客户端库,该库包含应用GraphQL查问进行本地状态治理的性能。让咱们实现一个本地状态属性,该属性将存储用户是否已登录,而不是引入另一个依赖关系。

Apollo React库将ApolloClient 实例放入 React的上下文中,但有时咱们可能须要间接拜访它。咱们能够通过useApolloClient挂钩,这将使咱们可能执行诸如间接更新或重置缓存存储区或写入本地数据之类的操作。

以后,咱们有两种办法来确定用户是否登录到咱们的应用程序。首先,如果他们胜利提交了注册表单,咱们晓得他们是以后用户。其次,咱们晓得,如果访问者应用存储在localStorage中的令牌拜访该站点 ,那么他们曾经登录。让咱们从用户填写注册表单时增加到咱们的状态开始。

为此,咱们将应用client.writeDatauseApolloClient挂钩间接将其写入Apollo客户的本地仓库。

src/pages/signup.js中,咱们首先须要更新 @apollo/client 库导入以蕴含 useApolloClient

import { useMutation, useApolloClient } from '@apollo/client';

src/pages/signup.js中, 咱们将调用 useApolloClient 函数,并在实现后应用writeData更新该批改以增加到本地存储中 :

// Apollo Clientconst client = useApolloClient();// Mutation Hookconst [signUp, { loading, error }] = useMutation(SIGNUP_USER, {onCompleted: data => {// store the tokenlocalStorage.setItem('token', data.signUp);// update the local cacheclient.writeData({ data: { isLoggedIn: true } });// redirect the user to the homepageprops.history.push('/');}});

当初,让咱们更新应用程序,以在页面加载时查看事后存在的令牌,并在找到令牌时更新状态。在 src/App.js ,首先将ApolloClient 配置更新为一个空的 resolvers 对象。这将使咱们可能在本地缓存上执行GraphQL查问。

// create the Apollo clientconst client = new ApolloClient({link: authLink.concat(httpLink),cache,resolvers: {},connectToDevTools: true});

接下来,咱们能够对应用程序的初始页面加载执行查看:

// check for a local tokenconst data = {isLoggedIn: !!localStorage.getItem('token')};// write the cache data on initial loadcache.writeData({ data });

这里很酷:咱们当初能够应用@client 指令在应用程序中的任何地位以GraphQL查问模式拜访 isLoggedIn

为了证实这一点,让咱们更新咱们的应用程序,

如果isLoggedInfalse,显示“注册”和“登录”链接。

如果 isLoggedIntrue 就显示“登记”链接。

src/components/Header.js ,导入必要的依赖项并像上面这样编写查问:

// new dependenciesimport { useQuery, gql } from '@apollo/client';import { Link } from 'react-router-dom';// local queryconst IS_LOGGED_IN = gql`{isLoggedIn @client}`;

当初,在咱们的React组件中,咱们能够包含一个简略的查问来检索状态,以及一个三级运算符,该运算符显示登记或登录的选项:

const UserState = styled.div`margin-left: auto;`;const Header = props => {// query hook for user logged in stateconst { data } = useQuery(IS_LOGGED_IN);return (<HeaderBar> <img src={logo} alt="Notedly Logo" height="40" /> <LogoText>Notedly</LogoText> {/* If logged in display a logout link, else display sign-in options */} <UserState> {data.isLoggedIn ? ( <p>Log Out</p> ) : ( <p> <Link to={'/signin'}>Sign In</Link> or{' '} <Link to={'/signup'}>Sign Up</Link> </p> )} </UserState> </HeaderBar> );};

这样,当用户登录时,他们将看到“登记”选项。否则,将因为本地状态来为他们提供用于登录或注册的选项。咱们也不限于简略的布尔逻辑。Apollo使咱们可能编写本地解析器和类型定义,从而使咱们可能利用GraphQL在本地状态下必须提供的所有。

登记

目前,一旦用户登录,他们将无奈退出咱们的应用程序。让咱们将题目中的“登记”变成一个按钮,单击该按钮将登记用户。为此,当单击按钮时,咱们将删除存储在localStorage中的令牌 。咱们将应用一个元素来实现其内置的可拜访性,因为当用户应用键盘导航应用程序时,它既充当用户动作的语义示意,又能够取得焦点(如链接)。

在编写代码之前,让咱们编写一个款式化的组件,该组件将出现一个相似于链接的按钮。在src/Components/ButtonAsLink.js中创立一个新文件,并增加以下内容:

import styled from 'styled-components';const ButtonAsLink = styled.button`background: none;color: #0077cc;border: none;padding: 0;font: inherit;text-decoration: underline;cursor: pointer;:hover,:active {color: #004499;}`;export default ButtonAsLink;

当初在 src/components/Header.js, 咱们能够实现咱们的登记性能。咱们须要应用React RouterwithRouter高阶组件来解决重定向,因为Header.js文件是UI组件,而不是已定义的路由。首先导入 ButtonAsLink 组件以及 withRouter

// import both Link and withRouter from React Routerimport { Link, withRouter } from 'react-router-dom';// import the ButtonAsLink componentimport ButtonAsLink from './ButtonAsLink';

当初,在咱们的JSX中,咱们将更新组件以包含 props 参数,并将登记标记更新为一个按钮:

const Header = props => {// query hook for user logged-in state,// including the client for referencing the Apollo storeconst { data, client } = useQuery(IS_LOGGED_IN);return (<HeaderBar> <img src={logo} alt="Notedly Logo" height="40" /> <LogoText>Notedly</LogoText> {/* If logged in display a logout link, else display sign-in options */} <UserState> {data.isLoggedIn ? ( <ButtonAsLink> Logout </ButtonAsLink> ) : ( <p> <Link to={'/signin'}>Sign In</Link> or{' '} <Link to={'/signup'}>Sign Up</Link> </p> )} </UserState> </HeaderBar> );};// we wrap our component in the withRouter higher-order componentexport default withRouter(Header);

路由器

当咱们想在自身不能间接路由的组件中应用路由时,咱们须要应用React RouterwithRouter 高阶组件。当用户登记咱们的应用程序时,咱们心愿重置缓存存储区,以避免任何不须要的数据呈现在会话内部。Apollo能够调用resetStore 函数,它将齐全革除缓存。

让咱们在组件的按钮上增加一个 onClick处理函数,以删除用户的令牌,重置Apollo 仓库,更新本地状态并将用户重定向到首页。为此,咱们将更新 useQuery 挂钩,以包含对客户端的援用,并将组件包装 在export 语句的 withRouter高阶组件中 。

const Header = props => {// query hook for user logged in stateconst { data, client } = useQuery(IS_LOGGED_IN);return (<HeaderBar> <img src={logo} alt="Notedly Logo" height="40" /> <LogoText>Notedly</LogoText> {/* If logged in display a logout link, else display sign-in options */} <UserState> {data.isLoggedIn ? ( <ButtonAsLink onClick={() => {// remove the tokenlocalStorage.removeItem('token');// clear the application's cacheclient.resetStore();// update local stateclient.writeData({ data: { isLoggedIn: false } });// redirect the user to the home pageprops.history.push('/');}}>Logout </ButtonAsLink> ) : ( <p> <Link to={'/signin'}>Sign In</Link> or{' '} <Link to={'/signup'}>Sign Up</Link> </p> )} </UserState> </HeaderBar> );};export default withRouter(Header);

最初,重置存储后,咱们须要Apollo将用户状态增加回咱们的缓存状态。在 src/App.js 将缓存设置更新为包含 onResetStore

// check for a local tokenconst data = {isLoggedIn: !!localStorage.getItem('token')};// write the cache data on initial loadcache.writeData({ data });// write the cache data after cache is resetclient.onResetStore(() => cache.writeData({ data }));

这样,登录用户能够轻松登记咱们的应用程序。咱们曾经将此性能间接集成到了 Header 组件中,然而未来咱们能够将其重构为一个独立的组件。

创立登录表单

以后,咱们的用户能够注册并登记咱们的应用程序,然而他们无奈从新登录。让咱们创立一个登录表单,并在此过程中进行一些重构,以便咱们能够重用许多代码在咱们的注册组件中找到。

咱们的第一步将是创立一个新的页面组件,该组件将位于/signin 。在src/pages/signin.js的新文件中 ,增加以下内容:

import React, { useEffect } from 'react';const SignIn = props => {useEffect(() => {// update the document titledocument.title = 'Sign In — Notedly';});return (<div> <p>Sign up page</p> </div>);};export default SignIn;

当初咱们能够使页面可路由,以便用户能够导航到该页面。在 src/pages/index.js 导入路由页面并增加新的路由门路:

// import the sign-in page componentimport SignIn from './signin';const Pages = () => {return (<Router> <Layout> // ... our other routes// add a signin route to our routes list <Route path="/signin" component={SignIn} /> </Layout> </Router> );};

在施行登录表单之前,让咱们暂停一下,思考咱们的选项。咱们能够从新实现一个表单,就像咱们在“注册”页面上写的那样,但这听起来很乏味,并且须要咱们保护两个类似的表单。当一个更改时,咱们须要确保更新另一个。另一个抉择是将表单隔离到其本人的组件中,这将使咱们可能重复使用通用代码并在单个地位进行更新。让咱们持续应用共享表单组件办法。

咱们将首先在src/components/UserForm.js中创立一个新组件,介绍咱们的标记和款式。

咱们将对该表单进行一些小的但值得注意的更改,应用它从父组件接管的属性。首先,咱们将onSubmit申请重命名为props.action,这将使咱们可能通过组件的属性将批改传递给表单。其次,咱们将增加一些条件语句,咱们晓得咱们的两种模式将有所不同。咱们将应用第二个名为formType的属性,该属性将传递一个字符串。咱们能够依据字符串的值更改模板的渲染。

咱们会通过逻辑运算符&&或三元运算符。

import React, { useState } from 'react';import styled from 'styled-components';import Button from './Button';const Wrapper = styled.div`border: 1px solid #f5f4f0;max-width: 500px;padding: 1em;margin: 0 auto;`;const Form = styled.form`label,input {display: block;line-height: 2em;}input {width: 100%;margin-bottom: 1em;}`;const UserForm = props => {// set the default state of the formconst [values, setValues] = useState();// update the state when a user types in the formconst onChange = event => {setValues({...values,[event.target.name]: event.target.value});};return (<Wrapper>{/* Display the appropriate form header */}{props.formType === 'signup' ? <h2>Sign Up</h2> : <h2>Sign In</h2>}{/* perform the mutation when a user submits the form */}<FormonSubmit={e => {e.preventDefault();props.action({variables: {...values}});}}>{props.formType === 'signup' && (<React.Fragment><label htmlFor="username">Username:</label><inputrequiredtype="text"id="username"name="username"placeholder="username"onChange={onChange}/></React.Fragment>)}<label htmlFor="email">Email:</label><inputrequiredtype="email"id="email"name="email"placeholder="Email"onChange={onChange}/><label htmlFor="password">Password:</label><inputrequiredtype="password"id="password"name="password"placeholder="Password"onChange={onChange}/><Button type="submit">Submit</Button></Form></Wrapper>);};export default UserForm;

当初,咱们能够简化 src/pages/signup.js 组件以利用共享表单组件:

import React, { useEffect } from 'react';import { useMutation, useApolloClient, gql } from '@apollo/client';import UserForm from '../components/UserForm';const SIGNUP_USER = gql`mutation signUp($email: String!, $username: String!, $password: String!) {signUp(email: $email, username: $username, password: $password)}`;const SignUp = props => {useEffect(() => {// update the document titledocument.title = 'Sign Up — Notedly';});const client = useApolloClient();const [signUp, { loading, error }] = useMutation(SIGNUP_USER, {onCompleted: data => {// store the tokenlocalStorage.setItem('token', data.signUp);// update the local cacheclient.writeData({ data: { isLoggedIn: true } });// redirect the user to the homepageprops.history.push('/');}});return (<React.Fragment><UserForm action={signUp} formType="signup" />{/* if the data is loading, display a loading message*/}{loading && <p>Loading...</p>}{/* if there is an error, display a error message*/}{error && <p>Error creating an account!</p>}</React.Fragment>);};export default SignUp;

最初,咱们能够应用 signIn 申请和 UserForm 组件编写 SignIn组件。

src/pages/signin.js

import React, { useEffect } from 'react';import { useMutation, useApolloClient, gql } from '@apollo/client';import UserForm from '../components/UserForm';const SIGNIN_USER = gql`mutation signIn($email: String, $password: String!) {signIn(email: $email, password: $password)}`;const SignIn = props => {useEffect(() => {// update the document titledocument.title = 'Sign In — Notedly';});const client = useApolloClient();const [signIn, { loading, error }] = useMutation(SIGNIN_USER, {onCompleted: data => {// store the tokenlocalStorage.setItem('token', data.signIn);// update the local cacheclient.writeData({ data: { isLoggedIn: true } });// redirect the user to the homepageprops.history.push('/');}});return (<React.Fragment><UserForm action={signIn} formType="signIn" />{/* if the data is loading, display a loading message*/}{loading && <p>Loading...</p>}{/* if there is an error, display a error message*/}{error && <p>Error signing in!</p>}</React.Fragment>);};export default SignIn;

这样,咱们当初有了一个易于治理的表单组件,并使用户可能注册和登录咱们的应用程序。

爱护路由

常见的应用程序模式是将对特定页面或网站局部的拜访权限限度为通过身份验证的用户。在咱们的状况下,未经身份验证的用户将无奈应用“我的笔记”或“收藏夹”页面。咱们能够在路由器中实现此模式,当未经身份验证的用户尝试拜访那些路由时,会将他们主动导航到应用程序的“登录”页面。

src/pages/index.js中, 咱们将首先导入必要的依赖项并增加咱们的 isLoggedIn 查问:

import { useQuery, gql } from '@apollo/client';const IS_LOGGED_IN = gql`{isLoggedIn @client}`;

当初,咱们将导入React RouterRedirect 库并编写一个 PrivateRoute 组件,如果用户未登录,它将对用户进行重定向:

// update our react-router import to include Redirectimport { BrowserRouter as Router, Route, Redirect } from 'react-router-dom';// add the PrivateRoute component below our `Pages` componentconst PrivateRoute = ({ component: Component, ...rest }) => {const { loading, error, data } = useQuery(IS_LOGGED_IN);// if the data is loading, display a loading messageif (loading) return <p>Loading...</p>;// if there is an error fetching the data, display an error messageif (error) return <p>Error!</p>;// if the user is logged in, route them to the requested component// else redirect them to the sign-in pagereturn (<Route{...rest}render={props =>data.isLoggedIn === true ? (<Component {...props} />) : (<Redirectto={{pathname: '/signin',state: { from: props.location }}}/>)}/>);};export default Pages;

最初,咱们能够更新用于登录用户的任何路由以应用 PrivateRoute 组件:

const Pages = () => {return (<Router><Layout><Route exact path="/" component={Home} /><PrivateRoute path="/mynotes" component={MyNotes} /><PrivateRoute path="/favorites" component={Favorites} /><Route path="/note/:id" component={Note} /><Route path="/signup" component={SignUp} /><Route path="/signin" component={SignIn} /></Layout></Router>);};

重定向状态

当咱们重定向公有路由时,咱们也将存储URL作为状态。这使咱们可能将用户重定向到他们最后试图导航到的页面。咱们能够更新登录页面的重定向,能够抉择应用props.state. ' ' location.from来启用这个性能。

当初,当用户试图导航到为已登录用户筹备的页面时,他们将被重定向到咱们的登录页面。

论断

在本章中,咱们介绍了构建客户端JavaScript应用程序的两个要害概念:身份验证和状态。通过构建残缺的身份验证流程,你已洞悉用户帐户如何与客户端应用程序一起应用。从这里开始,我心愿你摸索OAuth等代替选项以及Auth0OktaFirebase等身份验证服务。此外,你曾经学会了应用React Hooks API在组件级别管理应用程序中的状态,以及应用Apollo的本地状态治理整个应用程序中的状态。

有了这些要害概念,你当初就能够构建弱小的用户界面应用程序。

如果有了解不到位的中央,欢送大家纠错。如果感觉还能够,麻烦您点赞珍藏或者分享一下,心愿能够帮到更多人。