react項目實現登錄注冊

react項目中實現登錄注冊簡單粗暴感悟

全局安裝react官方推薦腳手架 create-react-app

npm install create-react-app -g

創建react項目

create-react-app react-login-register

進入項目并運行

npm start
image.png

安裝此demo中各種依賴

  npm install antd-mobile -S  // UI框架
  npm install redux -S  // redux
  npm install react-redux -S  // react-redux
  npm install react-router-dom -S  // react路由
  npm install axios -S // react請求交互
  npm install redux-thunk -S // redux中間件 配合redux解決異步問題
  npm install babel-plugin-transform-decorators-legacy -D // 配合react-redux 支持@裝飾器
  npm install mongoose -D // 安裝mondoose MongoDB數據庫插件
  npm install express -S // 基于node開發,搭建請求環境
  npm install babel-plugin-import -S antd-mobile按需加載babel插件
  npm install body-parser -S  后臺數據接口用戶傳進來的參數解析插件
  npm install cookie-parser -S 通過cookie存儲用戶登錄狀態
  PS: 合并安裝飛起來~

  npm run eject // 釋放react關于webpack的封裝配置
  sudo brew install mongodb(我這里是mac安裝方式,其他平臺可百度下)

刪掉 src目錄下面所有文件并新建index.js

 // index.js
import React from 'react'
import ReactDOM from 'react-dom'

ReactDOM.render(
    <div>Demo</div>,
    document.getElementById('root')
)
image.png

在src目錄新建一下文件和文件夾

image.png

components 放置組件
containers 放置頁面
redux 放置redux元素集合文件(定義各種常量,reducers, create actions)
reducer.js 集合redux里面各種文件

image.png

containers 新建登錄注冊兩個頁面基本內容

import React, {Component} from 'react'

class Login extends Component {
    render() {
        return (
            <div>登錄頁面</div>
        )
    }
}

export default Login

redux 新建user.redux.js

export function user(state=0,action) {
    switch (action.type) {
        default:
            return state;
    }
}

reducer.js 引入user.redux.js 并合并

import {combineReducers} from 'redux'

import {user} from './redux/user.redux'

export default combineReducers({user})

components 新建 checkLogin 判斷用戶是否登錄

import {Component} from 'react';
import {withRouter} from 'react-router-dom'

@withRouter
class CheckLogin extends Component {
    componentDidMount() {
         // 在這里請求相關接口判斷用戶是否完成登錄
        axios.get('xxxxx')
            .then(res => {
                if(res.status === 200) {
                    if(res.data.code === 0) {

                    }else {
                        this.props.history.push('/login')
                    }
                }
            })
    }
    render() {
        return null;
    }
}

export default CheckLogin;

index.js完成登錄注冊準備工作

import React from 'react'
import ReactDOM from 'react-dom'

// 配合applyMiddleware解決redux異步問題
import thunk from 'redux-thunk'

// createStore接受reducer生成stote compose合并生成store其他數據 applyMiddleware接受thunk解決redux異步問題
import {createStore, compose, applyMiddleware} from 'redux'

// Provider負責傳遞store
import {Provider} from 'react-redux'

// 引入react-router-dom各種路由元素
import {BrowserRouter as Router, Route} from 'react-router-dom'

// 引入判斷是否登錄組件
import CheckLogin from './components/checkLogin/CheckLogin'

// 引入頁面路由組件
import Login from './containers/login/login'
import Register from './containers/register/register'

// 生成store
import reducer from './reducer'

const store = createStore(reducer, compose(
    applyMiddleware(thunk), //解決redux異步問題
    window.devToolsExtension ? window.devToolsExtension() : f => f // chrome控制臺redux工具
))

// 頁面渲染
ReactDOM.render(
    <Provider store={store}>
        <Router>
            <div className="react-login-register">
                    <CheckLogin></CheckLogin>
                    <Route path='/login' component={Login}></Route>
                    <Route path='/register' component={Register}></Route>
            </div>
        </Router>
    </Provider>,
    document.getElementById('root')
)

從控制臺,可以看到redux相關信息(user: 定義的reducer user 值是返回state的初始值0)
PS:前提是chrome安裝redux插件Redux DevTools

image.png

在package.json的babel里添加一下插件

 "plugins": [
      "transform-decorators-legacy", // 配合react-redux 的connent 支持裝飾器寫法
      [
          "import",
          {
            "libraryName": "antd-mobile",
            "style": "css"
          }
      ] // antd-mobile 按需加載
  ]

接下來完善登錄注冊頁面的UI 和基本交互邏輯

登錄頁面

   // login.js
  import React, {Component} from 'react'
  import {List,InputItem,WingBlank,WhiteSpace, Button} from 'antd-mobile'
  import Logo from '../../components/logo/logo'

  class Login extends Component {
      render() {
          return (
              <div className="page-login">
                  <Logo />
                  <WhiteSpace></WhiteSpace>
                  <WhiteSpace></WhiteSpace>
                  <WhiteSpace></WhiteSpace>
                  <WhiteSpace></WhiteSpace>
                  <List>
                      <InputItem>lbj-賬號</InputItem>
                      <InputItem>lbj-密碼</InputItem>
                  </List>
                  <WhiteSpace></WhiteSpace>
                  <WhiteSpace></WhiteSpace>
                  <WhiteSpace></WhiteSpace>
                  <WhiteSpace></WhiteSpace>
                  <WhiteSpace></WhiteSpace>
                  <WingBlank>
                      <Button type="primary">登錄</Button>
                      <WhiteSpace></WhiteSpace>
                      <Button onClick={this.handleGoRegister.bind(this)} type="primary">去注冊</Button>
                  </WingBlank>
              </div>
          )
      }
      /*
      *     去注冊
      * */
      handleGoRegister() {
          this.props.history.push('/register');
      }
  }

  export default Login

注冊頁面

  import React, {Component} from 'react'
  import {List, InputItem, WingBlank, WhiteSpace, Button, Radio} from 'antd-mobile'
  import Logo from '../../components/logo/logo'

  class Register extends Component {
      constructor(props) {
          super(props);
          this.state = {
              username: '', //賬號
              pwd: '', // 密碼
              pwdConfirm: '', // 確認密碼
              type: 'worker', // 用戶類型 默認求職者
          }
      }

      render() {
          const RadioItem = Radio.RadioItem
          return (
              <div className="page-register">
                  <Logo/>
                  <List>
                      <InputItem onChange={value => this.handleChange('username', value)}>lbj-賬號</InputItem>
                      <InputItem onChange={value => this.handleChange('pwd', value)}>lbj-密碼</InputItem>
                      <InputItem onChange={value => this.handleChange('pwdConfirm', value)}>lbj-確認</InputItem>
                  </List>
                  <WhiteSpace></WhiteSpace>
                  <List>
                      <RadioItem
                          onClick={() => this.handleChange('type', 'worker')}
                          checked={this.state.type === 'worker'}>牛人    </RadioItem>
                      <RadioItem
                          onClick={() => this.handleChange('type', 'boss')}
                          checked={this.state.type === 'boss'}>BOSS</RadioItem>
                  </List>
                  <WhiteSpace></WhiteSpace>
                  <WhiteSpace></WhiteSpace>
                  <WhiteSpace></WhiteSpace>
                  <WingBlank>
                      <Button type="primary">登錄</Button>
                      <WhiteSpace></WhiteSpace>
                      <Button onClick={this.handleGoLogin.bind(this)} type="primary">已有賬號,去登錄</Button>
                  </WingBlank>
              </div>
          )
      }

      /*
      *     去登錄
      * */
      handleGoLogin() {
          this.props.history.push('/login')
      }

      /*
      *     綁定表單值
      * */
      handleChange(key, val) {
          this.setState({
              [key]: val
          })
      }
  }

  export default Register

下一步,就是搭建數據庫,之前安裝的MongoDB,mongoose, express派上了用場

項目根目錄下面新建server文件夾
文件夾下面新建server.js和user.js
server.js

const express = require('express');
const userRouter = require('./user')

// 新建app
const app = express();

app.use('/user',userRouter)

app.listen(8000, () => {
    `server is running at port 8000 success~~~`
})

user.js

const express = require('express')

// 生成express路由中間件
const Router = express.Router();

// CheckLogin.js 用戶查詢用戶是否登錄的接口
Router.get('/info', (req, res) => {
    return res.json({code: 1,msg: '未登錄'})
})

module.exports = Router

進入server文件夾 運行

npm install nodemon -g   // nodemon 是基于node的實時監聽的工具
nodemon server.js

image.png

此時可以看到請求地址已經可以訪問
這里拋出一個問題,
項目地址是http://localhost:3001/
請求地址是http://localhost:8000/user/info
由于瀏覽器同源策略限制,直接請求會存在著跨域問題(協議,域名,端口任何一個不一致,即視為跨域),所以這里可以通過代理請求的方式解決跨域問題
在package.json 添加 "proxy": "http://localhost:8000"

在checkLogin.js里面請求user/info 來判斷是否登錄
繼續完善 checkLogin

import {Component} from 'react';
import {withRouter} from 'react-router-dom'  // 由于checkLogin 不是路由,要想用跳轉的話得引入withRouter

@withRouter
class CheckLogin extends Component {
    componentDidMount() {
        // 登錄注冊兩個頁面不需要判斷
        if(filterCheck.indexOf(this.props.location.pathname) > -1) {
            return;
        }
         // 在這里請求相關接口判斷用戶是否完成登錄
        axios.get('/user/info')
            .then(res => {
                if(res.status === 200) {
                    if(res.data.code === 0) {
                              
                    }else {
                        this.props.history.push('/login')
                    }
                }
            })
    }
    render() {
        return null;
    }
}

export default CheckLogin;

中場休息完繼續

通過上面的checkLogin,訪問其他頁面在沒有登錄的情況下,自動跳轉到登錄頁面。

從注冊頁面開始完善
因為項目是結合redux來操作,所以登錄注冊都是生成各種action,通過之前寫好的user這個reducer來改變state的初始值

在注冊頁面寫好register方法并導出給注冊按鈕調用,從而生成action,給reducer使用

// user.redux.js
import axios from 'axios'  // 用axios做請求

// 定義常量
const REGISTER_SUCCESS = 'REGISTER_SUCCESS'; // 注冊成功
const TODO_ERRSHOW = 'TODO_ERRSHOW'; // 操作失敗

// state初始值
let initState = {
    redirectTo: '', // 完成之后跳到哪里
    username: '', // 賬號
    pwd: '', // 密碼
    pwdConfirm: '', // 確認密碼
    type: '', // 用戶類型
    msg: '', // 錯誤消息
    isLogin: false // 是否登錄
}

export function user(state=initState,action) {
    switch (action.type) {
       case REGISTER_SUCCESS:
            return {...state,...action.data, msg: '', redirectTo: '/login'}
       case TODO_ERRSHOW:
            return {...state,msg: action.msg}
       default:
            return state;
    }
}

function registerFail(msg) {
    return {
        msg,
        type: TODO_ERRSHOW
    }
}

function registerSuccess(data) {
    return {
        data,
        type: REGISTER_SUCCESS
    }
}

// register是一個action creator ,返回的action供user這個reducer使用,從而改變state
export function register({username,pwd,pwdConfirm,type}) {
    if(!username || !pwd) {
        registerFail('賬號密碼不能為空')
    }
    if(pwd !== pwdConfirm) {
        registerFail('兩次密碼不一致')
    }
    return dispatch => {
        axios.post('/user/register',{username,pwd,type})
            .then(res => {
                if(res.status === 200 && res.data.code === 0) {
                    dispatch(registerSuccess(res.data.data))
                }else {
                    dispatch(registerFail(res.data.msg))
                }
            })
    }
}

接下來在register.js里面的注冊按鈕綁定的事件調用register這個方法

// register.js
import {connect} from 'react-redux'
import {register} from '../../redux/user.redux'
import Logo from '../../components/logo/logo'

@connect(
    state=> state,
    {register}
)

注意上面幾行代碼,之前在package.json提到的plugins transform-decorators-legacy是為上面的connect裝飾器支持@寫法準備的。

裝飾器connnect接受兩個參數,給props定義的數據和函數

// 注冊按鈕綁定的事件
/*
*   注冊
* */
handleRegister() {
    this.props.register(this.state)
}

因為在register這個函數里面,做了表單驗證判斷后開始請求“/user/register”這個接口,那么接下來完善這個接口

登錄注冊的接口設計到數據庫,這里選用了mongoDB這種類型的數據庫最為后臺數據支撐

新建數據模型

// server/model.js
const mongoose = require('mongoose')

// mongoose 連接這個數據庫并生成"react-login-register"這個集合
mongoose.connect('mongodb://127.0.0.1:27017/react-login-register')

// 連接成功后的打印
mongoose.connection.on('connected',() => {
    console.log('mongo connect success');
})

// 創建數據模型
const models = {
    user: {
        'username': {type:String, require: true}, // 賬號
        'pwd': {type:String, require: true}, // 密碼
        'type': {type:String, require: true} // 用戶類型
    }
}

// 遍歷生成數據模型
for (let m in models) {
    mongoose.model(m, new mongoose.Schema(models[m]))
}

// 導出供其他地方獲取
module.exports = {
    getModel: function (m) {
        return mongoose.model(m)
    }
}

// server/user.js
// 獲取注冊用戶列表
Router.get('/list',(req,res) => {
    //清空所有用戶
    // User.remove({},(err,doc) => {
    //  if(!err) {
    //      console.log(`用戶清空成功`);
    //  }
    // })
    // 在user這個數據模型中查詢所有用戶
    User.find({},(err,doc) => {
        if(!err) {
            return res.json({code: 0, data: doc,msg: '用戶列表獲取成功'})
        }
    })
})

完成注冊接口

// user.js
const express = require('express')
const model = require('./model')
const User = model.getModel('user')
// md5加密
const utils = require('utility')

// 生成express路由中間件
const Router = express.Router();

// 封裝MD5加密規則
function my_md5(pwd) {
    const xiao = 'akdf352FHhjfFHI34=123-`.WRL23K23fhKJFHkhFJ@1231!*@%!^';
    return utils.md5(utils.md5(xiao + pwd))
}

//過濾調不想暴露的數據
const _filter = {"__v": 0, "pwd": 0}

// CheckLogin.js 用戶查詢用戶是否登錄的接口
Router.get('/info', (req, res) => {
    return res.json({code: 1, msg: '未登錄'})
})

// 獲取注冊用戶列表
Router.get('/list', (req, res) => {
    //清空所有用戶
    // User.remove({}, (err, doc) => {
    //  if (!err) {
    //      console.log(`用戶清空成功`);
    //  }
    // })

    // 在user這個數據模型中查詢所有用戶
    User.find({}, (err, doc) => {
        if (!err) {
            return res.json({code: 0, data: doc, msg: '用戶列表獲取成功'})
        }
    })
})

// 注冊接口
Router.post('/register', (req, res) => {
    const {username, pwd, type} = req.body;
    // 在user這個數據模型中查詢用戶注冊的賬號是否存在
    User.findOne({username}, (err, doc) => {
        //
        if (doc) {
            return res.json({code: 1, msg: '用戶已存在'})
        }
        if (err) {
            return res.json({code: 1, msg: '服務器異常'})
        }
        User.create({username, pwd: my_md5(pwd), type}, (err, doc) => {
            if (err) {
                return res.json({code: 1, msg: '服務器異常'})
            }
            return res.json({code: 0, data: doc})
        })
    })
})


module.exports = Router
// 這里注意,因為要接受參數,所以在server.js安裝body-parser并app.use(bodyParser.json())

register.js

{this.props.user.redirectTo ? <Redirect to={this.props.user.redirectTo}></Redirect>:null} //注冊完成后跳轉到登錄頁面
<div className="err-show">{this.props.user.msg ? this.props.user.msg : ''}</div> //顯示錯誤信息

至此注冊部分基本完成


繼續

接下來完成登錄部分

大致思路是綁定事件

// login.js
/*
 *  登錄
 * */
handleLogin() {
    this.props.login(this.state)
}

定義執行調用actions creator

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

@connect (
    state => state,
    {login}
)

編寫login actions creator

// user.redux.js
// 登錄時候調用
export function login({username,pwd}) {
    if(!username || !pwd) {
        return toDoFail('賬號密碼不能為空')
    }
    return dispatch => {
        axios.post('/user/login',{username,pwd})
            .then(res => {
                if(res.status === 200 && res.data.code === 0) {
                    dispatch(loginSuccess(res.data.data))
                }else {
                    dispatch(toDoFail(res.data.msg))
                }
            })
    }
}

編寫user/login這個接口

 // user.js
 Router.post('/login', (req, res) => {
    const {username, pwd} = req.body;
    console.log(username);
    User.findOne({username, pwd: my_md5(pwd)}, _filter, (err, doc) => {
        if (!doc) {
            return res.json({code: 1, msg: '賬號密碼不正確'})
        }
        if (err) {
            return res.json({code: 1, msg: '服務器異常'})
        }
        res.cookie('userId',doc._id) // 登錄成功保存cookie
        return res.json({code: 0, msg: '登錄成功', data: doc})
    })
})

登錄的步驟和注冊部分很多相似之處
接下來,完善判斷用戶是否登錄的接口,'/user/info'

// user.js
Router.get('/info', (req, res) => {
    const {userId} = req.cookies
    if (!userId) {
        res.json({code: 1, msg: '用戶未登錄'})
    }
    User.findOne({_id: userId}, _filter, (err, doc) => {
        if (err) {
            return res.json({code: 1, msg: '服務器異常'})
        }
        if (doc) {
            return res.json({code: 0, msg: '用戶已登錄',data: doc})
        }
    })
})
PS: server.js 記得使用cookie插件
const cookieParser = require('cookie-parser')
app.use(cookieParser())

最后,我們在登錄完成之后所進入的頁面展示用戶的基本信息

// checkLogin.js
axios.get('/user/info')
  .then(res => {
      console.log(res);
      if(res.status === 200) {
          if(res.data.code === 0) {
              this.props.getUserInfo(res.data.data)
          }else {
              this.props.history.push('/login')
          }
      }
  })


// user.redux.js
// 登錄之后獲取用戶信息
export function getUserInfo(userInfo) {
    return {
        type: GET_USER_INFO,
        payload: userInfo
    }    
}

對此次demo完善退出登錄
跳轉到login頁面,清空cookies

// workerInfo.js
import React, {Component} from 'react';
import {connect} from 'react-redux'
import {Button,WingBlank} from 'antd-mobile'
import {Redirect} from 'react-router-dom'
import {loginOut} from '../../redux/user.redux'

@connect(
    state => state,
    {loginOut}
)

class WorkerInfo extends Component {
    render() {
        return (
            <div>
                {this.props.user.redirectTo ? <Redirect to={this.props.user.redirectTo}></Redirect>:null}
                <h3>worker信息頁面</h3>
                <p>用戶名稱:{this.props.user.username} </p>
                <p>用戶類型:{this.props.user.type} </p>
                <WingBlank>
                    <Button onClick={this.handleLoginOut.bind(this)} type="primary">退出登錄</Button>
                </WingBlank>
            </div>
        );
    }
    /*
    *   退出登錄
    * */
    handleLoginOut() {
        this.props.loginOut();
    }
}

export default WorkerInfo;

編寫退出登錄action creator

// 退出登錄調用
export function loginOut() {
    return dispatch => {
        axios.get('/user/loginOut')
                .then(res => {
                if (res.status === 200 && res.data.code === 0) {
                    dispatch(loginOutSuccess(res.data.data))
                } else {
                    dispatch(toDoFail(res.data.msg))
                }
            })
    }
}

編寫退出登錄接口

// user.js
// 退出登錄
Router.get('/loginOut', (req, res) => {
    const {userId} = req.cookies;
    if(!userId) {
        res.json({code: 1, msg: '服務器異常'})
    }
    res.cookie('userId','');
    return res.json({code: 0, msg:'退出成功'})
})

到此整個demo寫完

留下一個報錯信息,但是對功能沒有影響

登錄或退出成功或注冊成功進行頁面跳轉后,會報錯
Warning: You tried to redirect to the same route you're currently on: "/workerInfo"
Warning: You tried to redirect to the same route you're currently on: "/bossInfo"
Warning: You tried to redirect to the same route you're currently on: "/login"
后續解決~~ 帶理解~

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,412評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,514評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,373評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,975評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,743評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,199評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,262評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,414評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,951評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,780評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,527評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,218評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,649評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,889評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,673評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,967評論 2 374

推薦閱讀更多精彩內容