React项目实践(二)一个登录页面的状态迁移

5,045 阅读3分钟

上一篇React项目实践(一)从框架配置讲起

这次谈谈一个登录页面的设计。在之前写过的项目中,我都是把表单放在一个页面也没有考虑到用户等待过程中的loading提示。这次重新规划一下,将展示组件与控制拆分,同时用状态机实现状态管理

状态分析

前几天刚好看到一篇文章前端状态管理请三思,觉得挺有意思的,原文作者利用状态机的思想,预先设想好所有状态和状态的迁移,优雅的管理页面登录状态避免过多变量的使用。本文参考作者的思想和代码,实现一个简单的登录页面。状态分析如下:

  • 初始登录页面是展示登录的表单(login form)
  • 当提交(submit)数据过程后,页面变为等待数据响应状态(loading)
    • 数据响应有两种状态,成功(success)页面跳转到首页;失败(failure)页面提示错误
    • 当登录成功,只有先退出登录(logout)之后才能重新登录
    • 当登录失败,重新提交(submit)回到加载状态(loading)
  • logout之后回到login form状态

依旧是模仿掘金app登录页面的一个实现效果:

登录成功效果

定义状态机

const machine = {
  states: {
    'login form': {
      submit: 'loading'
    },
    loading: {
      success: 'profile',
      failure: 'error'
    },
    profile: {
      viewProfile: 'profile',
      logout: 'login form'
    },
    error: {
      submit: 'loading'
    }
  }
}

实现一个状态控制函数,返回下一个状态

const stateTransformer = function(currentState, stepUp) {
  let nextState
  if (machine.states[currentState][stepUp]) {
    nextState = machine.states[currentState][stepUp]
  }
  console.log(`${currentState} + ${stepUp} --> ${nextState}`)
  return nextState || currentState
}

我们把状态控制的变量存储在redux中,定义一个简单的auth模块如下,stateChanger纯函数用于控制currentState的状态迁移,每次操作结果返回进行状态变换

export default {
  namespace: 'auth',
  state: {
    currentState: 'login form'
  },
  reducers: {
    stateChanger(state, {stepUp}) {
      return {
        ...state,
        currentState: stateTransformer(state.currentState, stepUp)
      }
    }
  },
  effects: dispatch => ({
  async loginByPhoneNumber(playload, state) {
    dispatch.auth.stateChanger({stepUp: 'submit'})
    let {data} = await api.auth.loginByPhoneNumber(playload)
    if (data.s === 0) {
      dispatch.auth.stateChanger({stepUp: 'success'})
      saveData('juejin_token', data.token)
    } else {
      dispatch.auth.stateChanger({stepUp: 'failure'})
      Toast.info('用户名或密码错误', 2)
    }
  }
})
}

那么在组件中,我们很容易写一个控制状态变化的组件


  render() {
    let {currentState} = this.props
    return (
      <>
        {(() => {
          switch (currentState) {
            case 'loading':
              return (
                //加载中展示组件
              )
            case 'profile':
              return <Redirect to={'/'} />//返回首页
            default:
              return (
                //登录表单
              )
          }
        })()}
      </>
    )
  }

具体配置补充

为了配合项目的用户登录验证,我们重新搭建一个本地服务,在react配置路由的代理转发,具体地,在根目录下新建文件src/setupProxy.js,将/api开头请求转发到服务器

const proxy = require('http-proxy-middleware')
module.exports = function(app) {
  app.use(proxy('/api', {target: 'http://localhost:8989/', changeOrigin: true}))
}

services/api定义数据接口

export async function loginByPhoneNumber({phoneNumber, password}) {
  return post('/api/auth/type/phoneNumber', {
    body: {
      phoneNumber,
      password
    }
  })
}

后端实现一个简单的中间件路由

const Koa = require('koa')
const router = require('./router')
router.post('/auth/type/phoneNumber', async (ctx, next) => {
  var {phoneNumber, password} = await parse.json(ctx.req)
  if (phoneNumber === '15111111111' && password === '123456') {
    let token = generateToken({uid: phoneNumber, password})
    ctx.response.body = JSON.stringify({
      s: 0,
      m: `账号登录成功错误`,
      d: '',
      token
    })
  } else {
    ctx.response.body = JSON.stringify({s: 1, m: '账号信息错误', d: ''})
  }
})

项目地址
总之是个人的一个小实践,大家在登录页面的管理中有什么更好的做法吗?还有什么复杂的情况需要考虑