react項目中實現登錄注冊簡單粗暴感悟
全局安裝react官方推薦腳手架 create-react-app
npm install create-react-app -g
創建react項目
create-react-app react-login-register
進入項目并運行
npm start
安裝此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')
)
在src目錄新建一下文件和文件夾
components 放置組件
containers 放置頁面
redux 放置redux元素集合文件(定義各種常量,reducers, create actions)
reducer.js 集合redux里面各種文件
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
在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
此時可以看到請求地址已經可以訪問
這里拋出一個問題,
項目地址是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"
后續解決~~ 帶理解~