React系列实战篇:增加登录(四)

3,689 阅读5分钟

快来加入我们吧!

"小和山的菜鸟们",为前端开发者提供技术相关资讯以及系列基础文章。为更好的用户体验,请您移至我们官网小和山的菜鸟们 ( 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 的形式可以在线快速访问当前实战案例。

CodeSandBox

下节预告

到这里,我们 React 相关基础知识已学习完毕,下一节我们将向更高的山峰前进 —— Hooks,敬请期待吧!