07-确定用户已登录
确定用户已登录
最后一项任务是确保将未经身份验证的用户重定向到登录页。鉴于当前应用程序的状况,用户是登录还是登出没有什么区别,因为他们只能看到一些与真实生活毫无关系的假数据(他们只是很高兴看到所有“星球大战”的随机语录和角色)。但在生产情形中,很可能绝对需要用户在拥有账号并已登录的情况下才能查看数据,这几乎是所有Web应用程序的基本需求。即使我们这里不重点关注安全,我们也需要确保只有用户成功登录才能查看社交网络。
有不同的方法可用来实现这种功能。在像React Router这样更为健壮和成熟的工具中,拥有当导航到特定的路由时能够执行的钩子函数——可以检查用户是否登录以及是否可以继续。这只是一种方法,而且我们并没有在Router组件中设置钩子函数的功能,但可以向主文件(index.js)添加一些逻辑来检查用户的在线状态并决定应该将他们路由到哪儿去。后续章节会过渡到使用React Router和这些钩子函数。我们还需将Login组件添加到Router中。
练习8-3 Firebase的替代品
在本书中,我们将Firebase作为“后端即服务”来使用。这极大地简化了学习,但这并非团队的做事方式。无须深入,想想应用可以用什么可以取代Firebase?
当用户登录之后,我们想要确保他们也会用自己的API被记录下来。我们使用Firebase进行身份验证,但仍想存储用户信息以便他们能够发帖和评论以及点赞(后续章节中会添加评论和点赞功能)。首先,需要检查用户是否存在,如果他们不存在,就需要将他们创建为系统的用户。我们构建出来的身份验证逻辑会把这些全都考虑在内。我们还将稍微修改浏览器历史监听器函数,以便它能根据用户是否登录来对其进行重定向。
代码清单8-14展示了如何在应用的主入口文件(src/index.js)中添加这段逻辑并修改历史监听器。
代码清单8-14 将Login容器添加到Router(src/index.js)
export const renderApp = (state, callback = () => {}) => {
render(
<Router {...state}>
<Route path="" component={App}>
<Route path="/" component={Home} />
<Route path="/posts/:postId" component={SinglePost} />
<Route path="/login" component={Login} /> ⇽--- 添加Login页的路由
<Route path="*" component={NotFound} />
</Route>
</Router>,
document.getElementById('app'),
callback
);
};
let state = {
location: window.location.pathname,
user: { ⇽--- 跟踪用户并相应地更新所创建的状态对象
authenticated: false,
profilePicture: null,
id: null,
name: null,
token: null
}
};
renderApp(state);
history.listen(location => {
const user = firebase.auth().currentUser; ⇽--- 在历史监听器中,首先检查是否有Firebase用户
state = Object.assign({}, state, {
location: user ? location.pathname : '/login' ⇽--- 在历史监听器中,首先检查是否有Firebase用户
});
renderApp(state);
});
firebase.auth().onAuthStateChanged(async user => { ⇽--- 使用async函数来响应Firebase用户状态的变更
if (!user) {
state = { ⇽--- 如果没有用户,更新状态并适当地渲染应用程序
location: state.location,
user: {
authenticated: false
}
};
return renderApp(state, () => { ⇽--- 如果没有用户,更新状态并适当地渲染应用程序
history.push('/login');
});
}
const token = await getFirebaseToken(); ⇽--- 如果有用户,使用await和我们创建的Firebase实用方法来获取他们的token
const res = await API.loadUser(user.uid); ⇽--- 尝试从我们的API加载用户信息
let renderUser; ⇽--- 声明一个要赋值的用户变量
if (res.status === 404) { ⇽--- 如果没有用户,需要注册他们
const userPayload = { ⇽--- 创建API能理解的用户载荷
name: user.displayName,
profilePicture: user.photoURL,
id: user.uid
};
renderUser = await API.createUser(userPayload).then(res => res.json()); ⇽--- 将请求发送给API并使用响应
} else {
renderUser = await res.json(); ⇽--- 如果用户已经存在,用它们渲染应用程序
}
history.push('/'); ⇽--- 将用户推送到主页
state = Object.assign({}, state, { ⇽--- 更新应用状态
user: {
name: renderUser.name,
id: renderUser.id,
profilePicture: renderUser.profilePicture,
authenticated: true
},
token
});
renderApp(state); ⇽--- 使用新状态渲染应用程序
});
现在用户可以登录并已拥有为其动态创建的账号。我们应该更新导航栏以便用户知道如何登录而且也能够看到登出选项。也许还记得在前几章中曾将 user
属性传给Navbar组件,尽管那时 user
属性还不存在。现在有了,Navbar组件可以根据用户的身份验证状态有条件地显示不同的视图。代码清单8-15展示了如何对Navbar组件进行这些修改。
代码清单8-15 更新Navbar组件(src/components/nav/navbar.js)
import React from 'react';
import PropTypes from 'prop-types';
import Link from '../router/Link';
import Logo from './logo';
import { logUserOut } from '../../backend/auth';
export const Navigation = ({ user }) => (
<nav className="navbar">
<Logo />
{user.authenticated ? ( ⇽--- 如果用户通过了身份验证,就展示他们的档案信息(姓名、资料图片)
<span className="user-nav-widget">
<span>{user.name}</span> ⇽--- 如果用户通过了身份验证,就展示他们的档案信息(姓名、资料图片)
<img width={40} className="img-circle"
src={user.profilePicture} alt={user.name} /> ⇽--- 如果用户通过了身份验证,就展示他们的档案信息(姓名、资料图片)
<span onClick={() => logUserOut()}> ⇽--- 为用户提供登出选项(使用我们先前创建的Firebase实用方法)
<i className="fa fa-sign-out" />
</span>
</span>
) : (
<Link to="/login"> ⇽--- 如果他们没有登录,展示一个登录链接
<button type="button">Log in or sign up</button>
</Link>
)}
</nav>
);
Navigation.propTypes = {
user: PropTypes.shape({ ⇽--- 声明组件属性的数据结构
name: PropTypes.string,
authenticated: PropTypes.bool,
profilePicture: PropTypes.string
}).isRequired
};
export default Navigation; ⇽--- 导出组件以便使用