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: ''}) } }) 複製程式碼
總之是個人的一個小實踐,大家在登入頁面的管理中有什麼更好的做法嗎?還有什麼複雜的情況需要考慮