前言:最近在学习Redux+react+Router+Nodejs全栈开发高级课程,这里对实践过程作个记录,方便自己和大家翻阅。最终成果github地址:https://github.com/66Web/react-antd-zhaoping,欢迎star。
一、信息完善
boss信息完善页前端实现
- container目录下:创建bossinfo组件目录,使用actd-mobile实现信息输入列表
import React from 'react' import {NavBar, InputItem, TextareaItem, Button} from 'antd-mobile' import AvatarSelector from '../../component/avatar-selector/avatar-selector' class BossInfo extends React.Component{ constructor(props){ super(props) this.state = { title: '' } } onChange(key, val){ this.setState({ [key]: val }) } render(){ return ( <div> <NavBar mode="dark">Boss完善信息页面</NavBar> <AvatarSelector selectAvatar={(imgname) => { this.setState({ avatar: imgname }) }} ></AvatarSelector> <InputItem onChange={(v) => this.onChange('title', v)}> 招聘职位 </InputItem> <InputItem onChange={(v) => this.onChange('company', v)}> 公司名称 </InputItem> <InputItem onChange={(v) => this.onChange('money', v)}> 职位薪资 </InputItem> <TextareaItem rows={3} title="职位要求" autoHeight onChange={(v) => this.onChange('desc', v)} > </TextareaItem> <Button type="primary">保存</Button> </div> ) } } export default BossInfo
-
component目录下:创建avatar-selector选择头像组件目录
import React from 'react' import {Grid, List} from 'antd-mobile' class AvatarSelector extends React.Component{ constructor(props){ super(props) this.state = {} } render(){ const avatarList = 'boy,girl,man,woman,bull,chick,crab,hedgehog,hippopotamus,koala,lemur,pig,tiger,whale,zebra' .split(',') .map(v=>({ icon:require(`../img/${v}.png`), text:v })) const gridHeader = this.state.text ? (<div> <span>已选择头像</span> <img style={{ 20}} src={this.state.icon}/> </div>) : <div>'请选择头像'</div> return ( <div> <List renderHeader={() => gridHeader}> <Grid data={avatarList} columnNum={5} onClick={elm => { this.setState(elm) this.props.selectAvatar(elm.text) }} /> </List> </div> ) } } export default AvatarSelector
boss信息完善页后端实现
- user.redux.js中:添加authSuccess验证成功相关的reducer和action
- authSuccess同步action:代替registerSuccess和loginSuccess,它们执行的是相同操作
//action type const AUTH_SUCCESS = 'AUTH_SUCCESS' //验证成功 //reducer中替换 case AUTH_SUCCESS: return {...state, msg:'', redirectTo:getRedirectPath(action.payload), ...action.payload} //同步action function authSuccess(obj){ const {pwd,...data} = obj return {type: AUTH_SUCCESS, payload:data} }
- 定义update异步action:完善信息页提交后调用服务端/user/update接口,执行更新操作
export function update(data){ return dispatch=>{ axios.post('/user/update', data) .then(res=>{ if (res.status==200&&res.data.code===0) { dispatch(authSuccess(res.data.data)) }else{ dispatch(errorMsg(res.data.msg)) } }) } }
-
user.js中:判断cookie中是否存储了userid,若有则根据userid查找并更新用户信息文档(findByIdAndUpdate)
//更新信息 Router.post('/update', function(req, res){ const userid = req.cookies.userid if(!userid){ return json.dumps({code:1}) } const body = req.body User.findByIdAndUpdate(userid, body, function(err, doc){ const data = Object.assign({},{ user: doc.user, type: doc.type, }, body) return res.json({code:0, data}) }) })
-
bossinfo.js中:使用connect连接组件和redux
-
点击保存按钮时,调用redux中的update异步action更新数据
import {connect} from 'react-redux' import {update} from '../../redux/user.redux' import {Redirect} from 'react-router-dom' @connect( state => state.user, {update} ) <Button onClick={()=>{ this.props.update(this.state) }} type="primary" >保存</Button>
-
判断props中redirect是否存在,且不等于当前路由即(location.pathname)时执行跳转
const path = this.props.location.pathname const redirect = this.props.redirectTo return ( <div> {redirect&&redirect!==path ? <Redirect to={this.props.redirectTo}/> : null}
牛人信息完善
- geniusinfo.js中:同bossinfo.js共用一套逻辑
import React from 'react' import {NavBar, InputItem, TextareaItem, Button} from 'antd-mobile' import AvatarSelector from '../../component/avatar-selector/avatar-selector' import {connect} from 'react-redux' import {update} from '../../redux/user.redux' import {Redirect} from 'react-router-dom' @connect( state => state.user, {update} ) class GeniusInfo extends React.Component{ constructor(props){ super(props) this.state = { title: '', desc: '' } } onChange(key, val){ this.setState({ [key]: val }) } render(){ const path = this.props.location.pathname const redirect = this.props.redirectTo return ( <div> {redirect&&redirect!==path ? <Redirect to={this.props.redirectTo}/> : null} <NavBar mode="dark">牛人完善信息页面</NavBar> <AvatarSelector selectAvatar={(imgname) => { this.setState({ avatar: imgname }) }} ></AvatarSelector> <InputItem onChange={(v) => this.onChange('title', v)}> 求职职位 </InputItem> <TextareaItem rows={3} title="个人简介" autoHeight onChange={(v) => this.onChange('desc', v)} > </TextareaItem> <Button onClick={()=>{ this.props.update(this.state) }} type="primary" >保存</Button> </div> ) } } export default GeniusInfo
组件属性类型校验
- PropTypes提供了多种验证器:react15.5之前是封装在react中的,react16之后分离出来,需要另外安装
npm install prop-types --save
-
JavaScript基础数据类型,包括数组、布尔、函数、数字、对象、字符串
-
optionalArray: React.PropTypes.array,
-
optionalBool: React.PropTypes.bool,
-
optionalFunc: React.PropTypes.func,
-
optionalNumber: React.PropTypes.number,
-
optionalObject: React.PropTypes.object,
-
optionalString: React.PropTypes.string
-
如果不能为空
isRequired
avatar-selector.js中:指定组件属性类型为函数,且不能为空
import PropTypes from 'prop-types' static propTypes = { selectAvatar: PropTypes.func.isRequired }
-
指定数据类型成数组
组件名称:PropTypes.arrayOf(PropTypes.number)
-
指定数据类型到对象
组件名称:PropTypes.objectOf(PropTypes.number)
二、用户列表
应用骨架
- index.js中:添加路由组件DashBoard
- 不设置path,则当路由匹配不到对应组件时,都会跳转到DashBoard组件中
- 使用DashBoard组件同时管理四个路由:/boss,/genius,/msg,/me
import DashBoard from './component/dashboard/dashboard' <Route component={DashBoard}></Route>//一定要放在<Switch>内容的最下方
导航和跳转
- component目录下:创建dashboard管理组件
- 定义navList数组:根据底部导航条分类,集中存储路由必需信息对象
const navList = [ { path: '/boss', text: '牛人', icon: 'boss', title: '牛人列表', component: Boss, hide: user.type == 'genius' }, { path: '/genius', text: 'Boss', icon: 'job', title: 'Boss列表', component: Genius, hide: user.type == 'boss' }, { path: '/msg', text: '消息', icon: 'msg', title: '消息列表', component: Msg }, { path: '/me', text: '我', icon: 'user', title: '个人中心', component: User } ]
-
使用NavBar组件:判断当前pathname与路由对象中的path相等时,找到对应的路由对象,显示顶部导航信息title
import {NavBar} from 'antd-mobile' const {pathname} = this.props.location <NavBar mode='dard'>{navList.find(v => v.path==pathname).title}</NavBar>
-
应用NavLinkBar底部导航组件:将路由信息数组navList传入组件
import NavLinkBar from '../navlink/navlink' <NavLinkBar data={navList}></NavLinkBar>
注意:这里信息数组是必须要传的,因此需在navlink.js中添加组件属性类型校验为isRequired
import PropTypes from 'prop-types' static propTypes = { data: PropTypes.array.isRequired }
-
component目录下:创建navlink.js底部导航组件 ↑
- 用户信息,聊天列表,职位列表页面共享底部TabBar
- 对获取到的navList先进行过滤:通过.filter过滤掉依据当前user.type指定要hide的路由对象
- 过滤后的navList通过遍历:为每一个要显示的路由对象设置TabBar.Item
import React from 'react' import {TabBar} from 'antd-mobile' import PropTypes from 'prop-types' import {withRouter} from 'react-router-dom' @withRouter class NavLinkBar extends React.Component{ static propTypes = { data: PropTypes.array.isRequired } render(){ const {pathname} = this.props.location const navList = this.props.data.filter(v=>!v.hide) // console.log(navList) return ( <TabBar> {navList.map(v=>( <TabBar.Item key={v.path} title={v.text} icon={{uri: require(`./img/${v.icon}.png`)}} selectedIcon={{uri: require(`./img/${v.icon}-active.png`)}} selected={pathname===v.path} onPress={()=>{ this.props.history.push(v.path) }} > </TabBar.Item> ))} </TabBar> ) } } export default NavLinkBar
牛人列表
- Boss用户在/bose中看到的是牛人列表
- dashboard.js中:通过Switch组件将遍历navList得到且匹配到的第一个路由对象信息注入<Route/>,实现导航对应的不同页面内容
<div style={{marginTop: 45}}> <Switch> {navList.map(v => ( <Route key={v.path} path={v.path} component={v.component}></Route> ))} </Switch> </div>
-
boss.js中:调用axios.get('/user/list?type=genius'),获得所有牛人用户的信息
componentDidMount(){ axios.get('/user/list?type=genius') .then(res => { if(res.data.code==0){ this.setState({data:res.data.data}) } }) }
- 遍历所有牛人用户的信息对象,使用Card、Card.Header、Card.Body显示牛人信息
-
判断牛人有无头像:如没有则默认没有完善用户信息,不显示在牛人列表中
render(){ // console.log(this.state.data) const Header = Card.Header const Body = Card.Body return ( <WingBlank> <WhiteSpace /> {this.state.data.map(v=>( v.avatar ? <Card key={v._id}> <Header title={v.user} thumb={require(`../img/${v.avatar}.png`)} extra={<span>{v.title}</span>} > </Header> <Body> {v.desc.split(' ').map(item=>( <div key={item}>{item}</div> ))} </Body> </Card> : null ))} </WingBlank> ) }
-
user.js中:修改/user/list的get请求,获取req.query中的type参数,依据type查询User中的对应类型的数据
//用户信息列表 Router.get('/list', function(req, res){ const {type} = req.query // User.remove({}, function(err, doc){}) User.find({type}, function(err, doc){ return res.json({code:0, data:doc}) }) })
-
Router.get的参数:用res.query获取
-
Router.post的参数:用res.body获取
使用redux管理牛人列表
- redux目录下:创建chatuser.redux.js用户聊天相关redux数据管理
import axios from 'axios' //action type const USER_LIST = 'USER_LIST' const initState = { userlist:[] } //reducer export function chatuser(state=initState, action){ switch(action.type){ case USER_LIST: return {...state, userlist:action.payload} default: return state } } //action creator function userList(data){ return { type:USER_LIST, payload:data} } //异步操作数据的方法 export function getUserList(type){ return dispatch=>{ axios.get('/user/list?type='+type) .then(res=>{ if (res.data.code==0) { //dispatch触发数据变化,执行action dispatch(userList(res.data.data)) } }) } }
-
reducer.js中:将chatuser合并进reducer,返回
import { chatuser } from './redux/chatuser.redux' export default combineReducers({user, chatuser})
-
boss.js中:不再需要调用axios获取数据,而是使用connect连接组件和redux,并使用getUserList方法异步获取数据
import {connect} from 'react-redux' import {getUserList} from '../../redux/chatuser.redux' @connect( state => state.chatuser, {getUserList} ) componentDidMount(){ this.props.getUserList('genius') // axios.get('/user/list?type=genius') // .then(res => { // if(res.data.code==0){ // this.setState({data:res.data.data}) // } // }) } //遍历userlist获得牛人信息对象 {this.props.userlist.map(v=>(
优化组件
- Boss列表组件和牛人列表组件的实现逻辑相同,会有大部分的复用代码,现将其抽离为一个基础组件
- UserCard.js中:通过Card相关组件显示获取到的用户信息
- 设置userlist为必需传的数组属性类型
- Boss列表有两个不同的选项,判断v.type若存在显示这两条信息
import React from 'react' import PropTypes from 'prop-types' import {Card, WhiteSpace, WingBlank} from 'antd-mobile' class UserCard extends React.Component{ static propTypes = { userlist: PropTypes.array.isRequired } render(){ const Header = Card.Header const Body = Card.Body return ( <WingBlank> <WhiteSpace /> {this.props.userlist.map(v=>( v.avatar ? <Card key={v._id}> <Header title={v.user} thumb={require(`../img/${v.avatar}.png`)} extra={<span>{v.title}</span>} > </Header> <Body> {v.type=='boss' ? <div>公司:{v.company}</div> : null} {v.desc.split(' ').map(d=>( <div key={d}>{d}</div> ))} {v.type=='boss' ? <div>薪资:{v.money}</div> : null} </Body> </Card> : null ))} </WingBlank> ) } } export default UserCard
-
boss.js和genius.js中:删除重复代码,直接返回UserCard组件,传入redux中的userlist即可
return <UserCard userlist={this.props.userlist}></UserCard>
注:项目来自慕课网