React实战博客1---登录模块

前端模块

1.登录窗口设计

​ 登录窗包括了3个模块( 登录, 注册, 重置 ), 通过嵌套路由切换状态

登录窗口

​ 登录加密使用MD5算法加盐( 盐值随便添加的这里就不写出来了~ ), 如果没有注册账号, 可以选择游客登录, 但是会失去用户操作的权限, 而且也只能看见我本人的博客, 无法获取其他用户发表的博客哦( 虽然也没有其他用户了... )

2.react嵌套路由实现窗口切换

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom'
import { Provider } from 'react-redux'

import store from './reducers'     // 全局状态监控会在后面提到

ReactDOM.render(
  
    
      
    
  , document.getElementById('root'))


// App.js
render() {
    return (
        
); } // login.jsx return (
{ this.state.showTab ?
this.state.switchLogin(1)} className={isLogin ? 'active' : ''} to="/sign/login">账号登录 this.state.switchLogin(0)} className={!isLogin ? 'active' : ''} to="/sign/register">快速注册
: null } () } /> () } /> () } />
京ICP备19108748号-1
)
对比vue:
  • 1.路由组件: react在路由切换组件上与vue的 不同的是, 每个视图必须用独立的 组件包裹, 而vue则指定include属性来分辨不同视图对应的组件
  • 2.视图渲染: vue的组件渲染在配置路由时就指定好了, 十分方便快捷.而react的组件渲染是写在 组件上的.
  • 3.手动切换路由: vue在全局注册了$router属性来实现手动切换路由, 而react则通过高阶组件 WithRouter 传入的history属性进行路由切换( 说实话,每次都要引入再使用挺烦的..这回我支持vue哈哈 )

​ 个人认为react虽然写法繁琐了一点, 但是更加灵活, 可根据具体情况渲染不同的组件, 而vue相对更加轻快, 适合快速搭建中小型项目

性能优化:

组件能少用就少用, 因为每用一次组件就重新渲染一次, 上面的代码我在 //sign 时分别重定向了2次, 显然这是浪费性能的体现, 经过改进之后将第二个 去掉了

// App.js
{ /* */}
// login.jsx {/* */} () } /> () } /> () } />

3.react-redux全局状态监控实现登录授权

​ react-redux是在react的基础上对redux的拓展. 实现了许多用于react-dom中的方法和高阶组件

实际应用( 取自项目 )
// reducers/index.js
import { createStore } from 'redux'

let initialState = {
  loginState: localStorage.getItem('loginState') ? localStorage.getItem('loginState') : -1,     // 登录状态 -1.未登录   0.游客登录   1. 用户登录
  salt: '****',   // MD5盐值
  userInfo: !!sessionStorage.getItem('userInfo') ? JSON.parse(sessionStorage.getItem('userInfo')) : { nickname: '' }   // 用户信息对象
}

const mainReducer = (state = initialState, action) => {
  switch(action.type) {
    case 'changeLoginState':
      localStorage.setItem('loginState', action.payload)
      return {
        ...state,
        loginState: action.payload
      }
    case 'getUserInfo':
      sessionStorage.setItem('userInfo', action.payload)
      return {
        ...state,
        userInfo: JSON.parse(action.payload)
      }
    default:
      return state
  }
}

const store = createStore(mainReducer)

export default store


// login.jsx
import { connect } from 'react-redux'

class Login extends React.Component {

  static propTypes = {
    hideTab: PropTypes.func,
    match: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    history: PropTypes.object.isRequired
  }

  constructor(props) {
    super(props)
  
    this.state = {
      username: '',
      password: '',
      userId: '',
      salt: '',
      emptyName: false,
      emptyPass: false,
      hideTab: null
    }
  
  }

  static getDerivedStateFromProps(props, state) {
    return {
      ...props
    }
  }

  loginToBlog() {
    let { username, password } = this.state

    let emptyName = username === ''
    let emptyPass = password === ''

    // 校验是否有空选项
    if (!emptyName && !emptyPass) {
      login({
        username,
        password
      })
        .then(res => {
          if (res.code == 0) {
            const modal = Modal.warning()
            modal.update({
              content: '该用户还未注册',
              onOk() {
                modal.destroy()
              }
            })
          }else if (res.code == 1) {    // 成功登陆, 储存用户信息
            message.success('欢迎')
            window.localStorage.setItem('user', JSON.stringify({ username, password }))
            this.props.changeLoginState(1)
            this.props.getUserInfo(JSON.stringify(res.data.userInfo))
            this.props.history.push('/main')
          }else if (res.code == 2) {   // 密码错误
            const modal = Modal.warning()
            modal.update({
              content: '密码错误!请重试',
              onOk() {
                modal.destroy()
              }
            })
          }
        })
        .catch(err => console.log(err))
    }else {
      this.setState({
        emptyName,
        emptyPass
      })
    }
  }

  changeUsername(target) {
    let username = target.value.trim()
    let emptyName = false
    this.setState({
      username,
      emptyName
    })
  }
  changePassword(target) {
    let password = target.value.trim()
    let emptyPass = false
    password = MD5(password + this.props.salt)
    this.setState({
      password,
      emptyPass
    })
  }

  render() {
    let { emptyName, emptyPass } = this.state
    return (
      
this.changeUsername(target)} placeholder="请输入用户名" type="text"/> this.changePassword(target)} placeholder="请输入密码" type="password"/>
this.props.changeLoginState(0)}>游客进入 忘记密码?
) } } Login = withRouter(Login) const mapLoginStateToProps = (state) => { return { loginState: state.loginState } } const mapLoginDispatchToProps = (dispatch) => { return { changeLoginState(type) { dispatch({ type: 'changeLoginState', payload: type }) }, getUserInfo(data) { dispatch({ type: 'getUserInfo', payload: data }) } } } Login = connect(mapLoginStateToProps, mapLoginDispatchToProps)(Login)
对比vue:
  • 1.react中使用 createStore 创建一个store实例, vue使用 Vuex.store() 创建store实例, 创建方法一致, 但是传参却有很大不同
  • 2.react并没有将同步和异步方法分割开, 统一通过action属性判断并调用相关方法, vue则通过mutations和actions分别操作同步和异步方法
  • 3.react注册修改状态的方法比较零散, 一般写在各个组件或者当前视图的主页面下( 因为方法写好后要通过 connect() 方法注册到当前组件下 ). 而vue则更为集中, 一般把所有的mutations和actions分别写在一个汇总文件下, 再输出模块到各个文件里面
  • 4.与vue相同, 在刷新之后store中的数据会重置会初始状态, 所以最好将一些持续的状态保存在 localstoragesessionstorage 里面

​ react中的一些方法命名还是非常清楚明白的, 我们可以从上面的例子清楚看到 connect() 方法传入2个参数, mapStateToProps 是将store中的对象映射到当前组件的props中, 而 mapDispatchToProps 就是将一些方法映射到props中, 个人认为这样在日后维护代码的时候会比统一管理舒服一点吧, 这也是react灵活多变的体现

登录授权控制

​ 目前我只做了登录状态和用户操作权限的控制, 因为项目进行到目前为止还没有太多需要权限控制的操作, 所以我仅仅只判断了登录状态( 状态码上面示例有写 )

// app.js
class App extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      loginState: 1
    }
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.loginState < 0 && prevState.loginState >= 0) {   // 避免递归
      nextProps.history.replace('/sign/login')
      return {
        ...prevState,
        ...nextProps
      }
    }else {
      return prevState
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    return nextProps.loginState !== nextState.loginState
  }
  
  render() {
    return (
      
); } } App = withRouter(App) const mapStateToProps = (state) => { return { loginState: state.loginState } } App = connect(mapStateToProps)(App) export default App;
聊一聊getDerivedStateFromProps

getDerivedStateFromProps 是react16推出的新生命周期函数, 代替了 componentWillReceivePropscomponentWillMount ( 16版本以后使用这两个旧生命周期会被提示添加前缀 UNSAFE_ )具体如何请移步到这篇 React新生命周期--getDerivedStateFromProps

​ 其实我也还不太习惯这个方法, 因为如果要在初始化组件时发起网络请求, 就会出现请求2次的情况( 每发生一次数据变化就会调用一次 ), 所以在性能优化方面需要做得更好

后端模块

// app.js  入口文件
const Koa = require('koa');
const KoaBodyParser = require('koa-bodyparser');   // 格式化http参数
const KoaStatic = require('koa-static');         // 静态文件路径

const path = require('path');

// 创建一个Koa对象表示web app本身:
const app = new Koa();
// 跨域处理
const cors = require('koa2-cors');     // 开启跨域
app.use(cors({
    origin: function (ctx) {
      return ctx.header.origin;      // 建议不要写'*', 别问我为什么知道
    },
    exposeHeaders: ['WWW-Authenticate', 'Server-Authorization'],
    maxAge: 5,
    credentials: true,
    allowMethods: ['GET', 'POST', 'DELETE'],
    allowHeaders: ['Content-Type', 'Authorization', 'Accept'],
}))
app.use(KoaBodyParser());
app.use(KoaStatic(path.join(__dirname, '/client/public/index.html')));

const router = require('./routers');
app.use(router.routes());
app.use(router.allowedMethods());

// 在端口7458监听:
app.listen(7458);
console.log('app started at port 7458...');


// routers.js
const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost:27017/data')
const db = mongoose.connection
db.on('error', console.error.bind(console, '连接数据库失败'));

let Schema, User, Article
db.on('open', function() {
  Schema = new mongoose.Schema({
    username: String, 
    password: String,
    nickname: String,
    email: String,
    phone: String,
  });
  User = mongoose.model('users', Schema);
  Article = mongoose.model('articles', Schema);
})

const Router = require('koa-router');
const router = new Router();

// 登录接口
router.post('/login', async(ctx, next) => {
  let body = ctx.request.body
  let { username, password } = body

  // code: 0 未注册, 1 成功, 2 密码错误
  let user = null 
  try {
    user = await User.find({'username': username})
    ctx.response.type = 'application/json';
    if (user.length > 0) {
      if (user[0].password == password) {
        ctx.response.body = {
          code: 1,
          msg: 'success',
          data: {
            userInfo: {
              nickname: user[0].nickname
            }
          }
        };
      }else {
        ctx.response.body = {
          code: 2,
          msg: '密码错误'
        };
      }
    }else {
      ctx.response.body = {
        code: 0,
        msg: '请先注册账户'
      };
    }
  } catch (error) {
    console.log(error)
    ctx.response.status = 500
    return next()
  }
})

// 注册接口
router.post('/register', async(ctx, next) => {
  let body = ctx.request.body
  let { username, password, nickname, email, phone } = body
  ctx.response.type = 'application/json'

  // 搜索数据库是否已经有该用户名
  let users = null
  try {
    users = await User.find({'username': username})
    users = await User.find({'nickname': nickname})
    if (users.length > 1 || (users[0] && users[0].username == username)) {
      ctx.response.body = { code: 0, msg: '该用户名已被注册!' }
      return
    }
    if (users.length > 1 || (users[0] && users[0].nickname == nickname)) {
      ctx.response.body = { code: 0, msg: '该昵称已存在!' }
      return
    }
  } catch (error) {
    console.log(error)
    ctx.response.status = 500
    return next()
  }

  // 注册新用户
  let doc = null
  try {
    doc = await User.create({ username, password, nickname, email, phone })
  } catch (error) {
    console.log(error)
    ctx.response.status = 500
    return next()
  }
  ctx.response.body = { code: 1, msg: 'success', data: doc }
})

// 重置密码接口
router.post('/reset', async(ctx, next) => {
  let body = ctx.request.body
  let { username, password, email, phone } = body
  ctx.response.type = 'application/json'

  // 搜索数据库是否已经有该用户名
  let users = null
  try {
    users = await User.findOneAndUpdate({ username, email, phone }, { password })
    if (!!users) {
      ctx.response.body = { code: 1, msg: 'success', data: users }
    }else {
      ctx.response.body = { code: 0, msg: '手机及邮箱验证失败', data: {} }
    }
  } catch (error) {
    console.log(error)
    ctx.response.status = 500
    return next()
  }
})


module.exports = router

​ 后端小白, 目前只能写一些增删改查的接口功能, 返回的数据参考RESTful设计, 对我来说相比增删改查更难的反而是es7的async await方法, 实在是不太熟悉, trycatch用的也不多, 果然自己还是太菜了...

你可能感兴趣的:(React实战博客1---登录模块)