因为之前写项目一直没有用状态管理,打算在下个项目里用hooks
和react-redux
。所以最近学了一下redux
和hooks
,写了一个登录注册来练习练习。
前端:react + antd + react-router-dom +react-redux + react-hooks
后端:Node + MongoDB
1、前端所需第三方依赖:
2、后端所需第三方依赖
该案例我配置了antd的按需加载以及less语法的使用。
一、项目目录
该案例共分为client
和server
两个文件夹。
1、client文件夹
入口文件:index.js
1、api/ajax : 封装的ajax请求函数
2、api/index: 包含n个接口请求的函数模块
(函数返回值是promise对象)
2、server文件夹
入口文件:www.js
1、bin/www.js: 后端入口文件
2、 app.js: 配置文件
3、routes/index: 后端路由文件
3、前端静态页面的搭建
使用了antd组件库搭建静态页面.
效果图如下:
4、redux的搭建
针对于这一步我觉得大家有一定的基础都没问题,所以我就不贴代码了。
5、login组件
import React, { useState}from 'react';
import { Form, Input, Button,message} from 'antd'
import { UserOutlined, LockOutlined } from '@ant-design/icons';
import { Link } from 'react-router-dom'
import {connect} from 'react-redux'
import '../../assets/css/login.less'
import {login} from '../../redux/actions'
const FormItem = Form.Item;
function Login(props) {
const [username, setUsername] = useState('');
const [ password , setPassword ] = useState('');
const onFinish = () => {
if (!username || !password) {
return message.error('未输入用户名或密码')
}
props.login({username, password})
}
const{ msg} = props.user
return (
<div className='loginForm'>
<div className='auth-form-header'>
<h1 className='login-title'>Sign in</h1>
</div>
{msg ? <div className='error-msg'>{msg}</div> : null}
<Form
name="normal_login"
className="login-form"
initialValues={{
remember: true,
}}
onFinish={onFinish}
>
<FormItem
// name="username"
rules={[
{
required: true,
message: '请输入用户名!',
},
]}
>
<Input
defaultValue={username}
name="username"
prefix={<UserOutlined className="site-form-item-icon" />}
placeholder="Username"
// defaultValue={username}
onChange={(e)=>{setUsername(e.target.value)}} />
</FormItem>
<FormItem
// name="password"
rules={[
{
required: true,
message: '请输入密码!',
},
]}
>
<Input
defaultValue={password}
prefix={<LockOutlined className="site-form-item-icon" />}
type="password"
placeholder="Password"
name='password'
// defaultValue={password}
onChange={(e)=>{setPassword(e.target.value)}}
/>
</FormItem>
<FormItem>
<Button type="primary" htmlType="submit" className="login-form-button" >
登录
</Button>
<div className='register-prompt'>
<p>没有账号?
{/* <a href='/register' className='register-text'>去注册</a> */}
<Link to='/register'>去注册</Link>
</p>
</div>
</FormItem>
</Form>
</div>
)
}
export default connect(
state => ({user:state.user}),
{login}
)(Login)
5、register组件
import React, { useState, }from 'react';
import { Form, Input, Button, message } from 'antd'
import { Link } from 'react-router-dom'
import {connect ,}from 'react-redux'
import '../../assets/css/register.less'
import {register} from '../../redux/actions'
const formItemLayout = {
labelCol: {
xs: {
span: 18,
},
sm: {
span: 7,
},
},
wrapperCol: {
xs: {
span: 18,
},
sm: {
span: 18,
},
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: {
span: 24,
offset: 0,
},
sm: {
span: 10,
offset: 7,
},
},
};
function Register(props) {
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [confirmpassword, setconfirmpassword] = useState('')
// const dispatch = useDispatch();
const onFinish = () => {
if (!username || !password || !confirmpassword) {
return message.error('未输入用户名或密码')
}
// dispatch(register({username, password}))
props.register({username, password})
}
const{ msg} = props.user
return (
<div className='RegisterForm'>
<div className='auth-form-header'>
<h1 className='register-title'>Sign up </h1>
</div>
{msg ? <div className='error-msg'>{msg}</div> : null}
<Form
{...formItemLayout}
name="normal_login"
className="register-form"
initialValues={{
remember: true,
}}
onFinish={onFinish}
>
<Form.Item
name="username"
label="用户名"
// tooltip="What do you want others to call you?"
rules={[
{
required: true,
message: '请输入用户名!',
whitespace: true,
},
]}
>
<Input name='username' onChange={ (e)=>{setUsername(e.target.value)}}/>
</Form.Item>
<Form.Item
name="password"
label="输入密码"
rules={[
{
required: true,
message: '请输入密码!',
},
]}
hasFeedback
>
<Input.Password name='password' onChange={ (e)=>{setPassword(e.target.value)}}/>
</Form.Item>
<Form.Item
name="confirmpassword"
label="确认密码"
dependencies={['password']}
hasFeedback
rules={[
{
required: true,
message: '请确认密码!',
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('两次输入密码不一致!'));
},
}),
]}
>
<Input.Password name="confirmpassword" onChange={ (e)=>{setconfirmpassword(e.target.value)}}/>
</Form.Item>
<Form.Item {...tailFormItemLayout} >
<Button type="primary" htmlType="submit" className='register-form-button'>
注册
</Button>
</Form.Item>
<div className='login-prompt'>
<p>
已有账号?
{/* <a href='/login' className='login-text'>去登录</a> */}
<Link to='/login'>去登录</Link>
</p>
</div>
</Form>
</div>
)
}
export default connect (
state => ({user: state.user}),
{register}
)(Register)
后端node环境搭建
1、入口文件www.js
// 后端入口文件
var app = require('../app')
var http = require('http')
var server = http.createServer(app)
// server.listen(4000)
server.listen(4000, () => {
console.log('server is running at port 8000 success~~~');
})
2、app.js
// 后端配置
const bodyParser = require('body-parser')
const express = require('express')
const router = require('./routes/index')
var app = express()
const db = require('./db/connect') //连接数据库
// 配置解析表单 POST 请求体插件(注意:一定要在 app.use(router) 之前 )
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.use(router)
module.exports = app
3、connect.js 连接数据库文件
const mongoose = require('mongoose')
// 连接数据库
mongoose.connect('mongodb://localhost/usertest', { useNewUrlParser: true, useUnifiedTopology: true })
var db = mongoose.connection // 数据库的链接对象
db.on('error', console.error.bind(console, 'connection error:'))
db.once('open', function() {
console.log('数据库链接成功')
// we're connected!
})
userModels.js 数据表的设计
const mongoose = require('mongoose')
var Schema = mongoose.Schema
const userSchema = new Schema({
username: {
type: String
},
password: {
type: String
},
})
const UserModel = mongoose.model('user', userSchema)
exports.UserModel = UserModel
routes/index.js 路由文件
var express = require('express')
var router = express.Router()
const md5 = require('blueimp-md5') //md5加密的函数
const { UserModel } = require('../db/Models/userModel')
const filter = {password:0,__v:0} //查询时过滤出指定的属性
// 用户注册
router.post('/register', (req, res) => {
const { username, password } = req.body
UserModel.findOne({ username }, (err, user) => {
if (user) {
res.send({code:1, msg:'此用户已存在'})
}
else {
new UserModel({ username, password: md5(password) }).save((err, user) => {
if (err) {
console.log(err);
}
else {
// 生成一个 cookie(userid: user._id), 并交给浏览器保存
res.cookie('userid', user._id, { maxAge: 1000 * 60 * 60 * 24 * 7 }) // 持久化 cookie, 浏`览器会保存在本地文件
// 返回包含user的json数据
const data = {username, _id:user._id,msg:'注册成功!'} //响应数据中不要携带password
res.send({code:0 ,data})
}
})
}
})
})
// 用户登录
router.post('/login', (req, res) => {
const { username, password } = req.body
UserModel.findOne({ username, password: md5(password) }, filter, (err, user) => {
if (user) {
res.cookie('userid', user._id, { maxAge: 1000 * 60 * 60 * 24 * 7 }) // 持久化 cookie, 浏 览器会保存在本地文件
const data = {username,_id:user._id}
res.send({code:0, data,msg:'登录成功!'})
}
else {
res.send({code:1, msg:'用户名或密码不正确'})
}
})
})
module.exports = router;
下面关于redux的一些业务逻辑我只贴关键的action和reducer代码。
1、 actions.js
import {
AUTH_SUCCESS,
ERROR_MSG,
} from './action-types'
import {
reqRegister,
reqLogin,
}from '../api/index'
//授权成功的同步action
const authSuccess = (user,msg) => ({type:AUTH_SUCCESS,data:{user,msg}})
//错误提示信息的同步action
const errorMsg = (msg) => ({type:ERROR_MSG,data:msg})
// 注册异步action
export const register = (user) => {
const { username, password } = user
return async dispatch => {
const response = await reqRegister({ username, password })
const result = response.data
if (result.code === 0) {
dispatch(authSuccess(result.data))
}
else {
dispatch(errorMsg(result.msg))
}
}
}
// 登录异步action、
export const login = (user) => {
const { username, password } = user
return async dispatch => {
const response = await reqLogin({ username, password })
const result = response.data
// console.log(result);
if (result.code === 0) {
dispatch(authSuccess(result.data,result.msg))
}
else {
dispatch(errorMsg(result.msg))
}
}
}
2、reducers.js
import { combineReducers } from 'redux'
import {
AUTH_SUCCESS,
ERROR_MSG,
} from './action-types'
const initUser = {
username:'',//用户名
msg:'',//错误提示信息
}
function user(state = initUser, action) {
switch (action.type){
case AUTH_SUCCESS:
return { ...action.data }
case ERROR_MSG:
return { ...state, msg: action.data }
default:
return state
}
}
export default combineReducers(
{
user,
}
)
至此,一个完整的前后端登录注册就已经成功了。