快来加入我们吧!
"小和山的菜鸟们",为前端开发者提供技术相关资讯以及系列基础文章。为更好的用户体验,请您移至我们官网小和山的菜鸟们 ( https://xhs-rookies.com/ ) 进行学习,及时获取最新文章。
"Code tailor" ,如果您对我们文章感兴趣、或是想提一些建议,微信关注 “小和山的菜鸟们” 公众号,与我们取的联系,您也可以在微信上观看我们的文章。每一个建议或是赞同都是对我们极大的鼓励!
实战案例(四):完善留言版登录
我们这次学了一些新内容,我们需要将之前的改版。
首先我们需要登录页面,并且通过HOC
(高阶组件)添加鉴权功能。加上路由跳转,完善页面。
增加路由
yarn add react-router-dom
我们先加入react-router
修改路由配置
我们需要修改index.js
,之前的index.js
中一直只有App.js
一个,我们将路由的配置添加到index.js
中。
ReactDOM.render(
<React.StrictMode>
<BrowserRouter>
<Switch>
<Route path="/login" component={Login} />
<Route path="/home" component={App} />
<Redirect path="/" to="/login" exact />
</Switch>
</BrowserRouter>
</React.StrictMode>,
document.getElementById('root'),
)
默认情况下,我们让页面指向Login
页面。
Login 页面
登录状态维护
如果我们登录成功后,我们应该需要有一个地方用于存放是否登录成功的信息,为后面鉴权做准备,我们采用localstorage
做数据持久化处理。
this.props.history.replace('/home')
window.localStorage.islogin = '1'
鉴权跳转
我们需要在登录页面鉴权,我们让login
页面在加载完成的时候判断,如果已经登录过了,那么我们就跳转到home
主页,这里我们采用 react 组件生命周期中的 componentDidMount()
方法,在加载完成后进行判断。
componentDidMount() {
let localStorage = window.localStorage
if (localStorage.islogin === '1') {
this.props.history.replace("/home")
}
}
最终组合
class Login extends PureComponent {
componentDidMount() {
let localStorage = window.localStorage
if (localStorage.islogin === '1') {
this.props.history.replace('/home')
}
}
constructor(props) {
super(props)
this.state = {
username: '',
password: '',
}
}
render() {
return (
<div className="login">
<h2>欢迎来到XXX博客区</h2>
<form className="form">
<div className="formItem">
<label htmlFor="username">用户名:</label>
<input
type="text"
id="username"
value={this.state.username}
onChange={(e) => {
this.setState({ username: e.target.value })
}}
/>
</div>
<div className="formItem">
<label htmlFor="password">密码:</label>
<input
type="password"
id="password"
value={this.state.password}
onChange={(e) => {
this.setState({ password: e.target.value })
}}
/>
</div>
<div
className="loginBtn"
onClick={() => {
this.handleLogin()
}}
>
登录
</div>
</form>
</div>
)
}
handleLogin() {
if (this.state.username && this.state.password) {
this.props.history.replace('/home')
window.localStorage.islogin = '1'
alert('欢迎!')
} else {
alert('请输入用户名和密码!')
}
}
}
export default Login
不需要组件
上次我们将InputCompoent
输入框组件和EvaluateCompoent
列表展示组件抽象出来放置于 component
文件夹中,我们先将这两个组件直接放置于App.js
中。
我们只需要抽象一个comment
组件,给上次的EvaluateCompoent
列表展示组件加上我们的点赞功能,每个列表中的评论我们都可以进行点赞。
因此我们将首页App.js
修改为如下:
import React, { PureComponent } from 'react'
import Comment from './comment'
import './App.css'
class App extends PureComponent {
constructor() {
super()
this.state = {
title: 'Hello React',
desc: '你知道有这么一个团队吗?他们怀揣梦想,艰苦奋斗,作为一群大学生菜鸟,放弃了平时娱乐的时间,选择一起学习,一起成长,将平时学习的笔记,心得总结为文章,目的很简单,希望可以帮助向他们一样的菜鸟们?你想了解更多吗?快搜索微信公众号:小和山的菜鸟们,加入他们吧!',
comments: [
{
headPortrait: 'https://xhs-rookies.com/img/rookie-icon.png',
time: new Date(2021, 4, 14, 21, 2, 30),
nickName: '小菜鸟',
detail: '这是一个即将推出系列文章的团队,我们一起期待他们的作品吧!',
liked: true,
likeNum: 23,
},
],
text: '',
}
}
render() {
const { title, desc, comments, text } = this.state
return (
<div className="App">
<h2>{title}</h2>
<div className="desc">{desc}</div>
<div style={{ width: '100%' }}>
<p className="commentsTitle">评论</p>
{comments.map((item, index) => {
return (
<Comment
key={item.time.getTime()}
changeLike={() => {
this.changeLike(index)
}}
{...item}
/>
)
})}
</div>
<div className="newComment">
<div style={{ display: 'flex' }}>
<img src="https://xhs-rookies.com/img/rookie-icon.png" className="" alt="" />
<textarea value={text} onChange={(e) => this.changeText(e)} placeholder="请输入评论" />
</div>
<div
className="submit"
onClick={() => {
this.addComment()
}}
>
发表
</div>
</div>
</div>
)
}
changeText(e) {
this.setState({ text: e.target.value })
}
changeLike(index) {
let newArray = [...this.state.comments]
let newItem = { ...newArray[index] }
if (newItem.liked) {
newItem.liked = false
newItem.likeNum -= 1
} else {
newItem.liked = true
newItem.likeNum += 1
}
newArray[index] = newItem
this.setState({
comments: newArray,
})
}
addComment() {
if (!this.state.text) {
alert('请输入留言内容')
return
}
let detail = this.state.text
this.setState({ text: '' })
let newComment = {
headPortrait: 'https://xhs-rookies.com/img/rookie-icon.png',
time: new Date(),
nickName: '小菜鸟',
detail,
liked: false,
likeNum: 0,
}
this.setState({
comments: [newComment, ...this.state.comments],
})
}
}
App.propTypes = {}
export default App
主页修改
主页鉴权
这里我们也需要鉴权,也就是说,如果在浏览器内直接输入home
的时候,如果没有登录,我们则需要让其跳转至login
登录页面.
我们是否可以使用和登录一样的时候,在加载完成后,判断然后跳转呢?
componentDidMount() {
let localStorage = window.localStorage
if (localStorage.islogin === '1') {
this.props.history.replace("/home")
}
}
这里其实有问题,我们如果在加载完成直接判断后跳转,是否每个页面都要加入呢?
但是这个鉴权其实是一个通用功能,如果现在还有一个个人信息页面的话,是不是也需要这么一个功能呢?
高阶组件鉴权
我们采用高阶组件进行鉴权,那么以后每个页面如果需要鉴权,都只需要通过高阶组件包裹即可。
import React from 'react'
import { Redirect } from 'react-router-dom'
export default function checkRole(WrapperComponent) {
let localStorage = window.localStorage
return (props) => {
if (localStorage.islogin === '1') {
return <WrapperComponent {...props} />
} else {
return <Redirect to="/" />
}
}
}
然后我们将Home
主页(App.js
)进行包裹
export default checkRole(App)
登出选项
我们登录成功后,那么我们自然在主页面需要加入登出的选项,再登出后,再次进入主页面就会被鉴权判定跳转至登录页面。
handleLogout() {
window.localStorage.islogin = '0'
this.props.history.replace("/login")
}
最终的主页面如下:
import React, { PureComponent } from 'react'
import Comment from './comment'
import checkRole from './checkRole'
import './App.css'
class App extends PureComponent {
constructor() {
super()
this.state = {
title: 'Hello React',
desc: '你知道有这么一个团队吗?他们怀揣梦想,艰苦奋斗,作为一群大学生菜鸟,放弃了平时娱乐的时间,选择一起学习,一起成长,将平时学习的笔记,心得总结为文章,目的很简单,希望可以帮助向他们一样的菜鸟们?你想了解更多吗?快搜索微信公众号:小和山的菜鸟们,加入他们吧!',
comments: [
{
headPortrait: 'https://xhs-rookies.com/img/rookie-icon.png',
time: new Date(2021, 4, 14, 21, 2, 30),
nickName: '小菜鸟',
detail: '这是一个即将推出系列文章的团队,我们一起期待他们的作品吧!',
liked: true,
likeNum: 23,
},
],
text: '',
}
}
render() {
const { title, desc, comments, text } = this.state
return (
<div className="App">
<span
className="logout"
onClick={() => {
this.handleLogout()
}}
>
退出登录
</span>
<h2>{title}</h2>
<div className="desc">{desc}</div>
<div style={{ width: '100%' }}>
<p className="commentsTitle">评论</p>
{comments.map((item, index) => {
return (
<Comment
key={item.time.getTime()}
changeLike={() => {
this.changeLike(index)
}}
{...item}
/>
)
})}
</div>
<div className="newComment">
<div style={{ display: 'flex' }}>
<img src="https://xhs-rookies.com/img/rookie-icon.png" className="" alt="" />
<textarea value={text} onChange={(e) => this.changeText(e)} placeholder="请输入评论" />
</div>
<div
className="submit"
onClick={() => {
this.addComment()
}}
>
发表
</div>
</div>
</div>
)
}
handleLogout() {
window.localStorage.islogin = '0'
this.props.history.replace('/login')
}
changeText(e) {
this.setState({ text: e.target.value })
}
changeLike(index) {
let newArray = [...this.state.comments]
let newItem = { ...newArray[index] }
if (newItem.liked) {
newItem.liked = false
newItem.likeNum -= 1
} else {
newItem.liked = true
newItem.likeNum += 1
}
newArray[index] = newItem
this.setState({
comments: newArray,
})
}
addComment() {
if (!this.state.text) {
alert('请输入留言内容')
return
}
let detail = this.state.text
this.setState({ text: '' })
let newComment = {
headPortrait: 'https://xhs-rookies.com/img/rookie-icon.png',
time: new Date(),
nickName: '小菜鸟',
detail,
liked: false,
likeNum: 0,
}
this.setState({
comments: [newComment, ...this.state.comments],
})
}
}
App.propTypes = {}
export default checkRole(App)
总结
到这里我们整个React
实战案例就结束了,虽然就是一个简简单单的留言案例,但是却包含了很多知识点,从开始的HTML
到脚手架创建。
加入了首页鉴权,使用PropTypes
检查传进来的数据是否符合要求,通过HOC
(高阶组件)来增加通用组件功能。
直接预览
我们建议采用 codesanbox
的形式可以在线快速访问当前实战案例。
下节预告
到这里,我们 React
相关基础知识已学习完毕,下一节我们将向更高的山峰前进 —— Hooks
,敬请期待吧!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。