day01
1. 项目开发准备
1). 项目描述: 整体业务功能/功能模块/主体的技术/开发模式
2). 技术选型: 数据展现/用户交互/组件化, 后端, 前后台交互, 模块化, 项目构建/工程化, 其它
3). API接口: 接口的4个组成部分, 接口文档, 对/调/测接口
2. git管理项目的常用操作
1). 创建本地仓库
创建.gitignore配置文件
git init
git add *
git commit -m "xxx"
2). 创建github远程仓库
New Repository
指定名称
创建
3). 将本地仓库推送到远程仓库
git remote add origin https://github.com/zxfjd3g/170612_JSAdvance.git 关联远程仓库
git push origin master
4). push本地的更新
git add *
git commit -m "xxx"
git push origin master
5). pull远程的更新
git pull origin master
6). 克隆github上的项目:
git clone https://github.com/zxfjd3g/xxx.git
3. 搭建项目
1). 使用create-react-app脚手架创建模板项目(工程化)
2). 引入antd-mobile, 并实现按需打包和自定义主题
3). 引入react-router-dom(v4):
HashRouter/Route/Switch
history: push()/replace()
4). 引入redux
redux/react-redux/redux-thunk
redux: createStore()/combineReducers()/applyMiddleware()
react-redux: <Provider store={store}> / connect()(Xxx)
4个重要模块: reducers/store/actions/action-types
5).src 客户端代码文件夹
api ajax请求相关模块文件夹=>发送ajax请求的函数模块 n个接口请求的函数模块 函数的返回值都是promise实例
assets 共用资源文件夹 =>样式以及图片
components UI组件模块文件夹 =>头像 logo 底部 用户列表
containers 容器组件模块文件夹 => 登陆 注册 主页面(聊天 老板 大神 信息完善 消息列表 个人中心)
redux redux相关模块文件夹
utils 工具模块文件夹 功能函数就是重定向
index.js 入口JS 就是匹配路由三者(登陆注册和主页面)
4. 登陆/注册界面
首先先拆分路由 路由分为main/login/register为一级路由 还有就是main里面的二级路由
映射路由import {HashRouter,Switch,Route} from 'react-router-dom'
1). 创建3个1级路由: main/login/register
2). 完成登陆/注册的静态组件
antd组件: NavBar/WingBlank/WhiteSpace/List/InputItem/Radio/Button 导航栏/两翼留白/上下留白/列表/文本输入/Gird宫格
路由跳转: this.props.history.replace('/login')
收集表单输入数据: state/onChange/变量属性名
5. 实现简单后台
1). 使用webstorm创建基于node+express的后台应用
2). 根据需求编写后台路由
3). 使用postman测试后台接口
4). 使用nodemon实现后台应用的自动重启动
5). 路由回调函数的3步: 读取请求参数/处理/返回响应数据
day02
1. 使用mongoose操作数据库
1). 连接数据库
2). 定义schema和Model
3). 通过Model函数对象或Model的实例的方法对集合数据进行CRUD操作
2. 注册/登陆后台处理
1). models.js
连接数据库: mongoose.connect(url)
定义文档结构: schema
定义操作集合的model: UserModel
2). routes/index.js
根据接口编写路由的定义
注册: 流程
登陆: 流程
响应数据结构: {code: 0, data: user}, {code: 1, msg: 'xxx'}
3. 注册/登陆前台处理
1). ajax
ajax请求函数(通用): 使用axios库, 返回的是promise对象
后台接口请求函数: 针对具体接口定义的ajax请求函数, 返回的是promise对象
代理: 跨域问题/配置代理解决
await/async: 同步编码方式实现异步ajax请求
2). redux
store.js
生成并暴露一个store管理对象
reducers.js
包含n个reducer函数
根据老state和指定action来产生返回一个新的state
actions.js
包含n个action creator函数
同步action: 返回一个action对象({type: 'XXX', data: xxx})
异步action: 返回一个函数: disptach => {执行异步代理, 结束时dispatch一个同步action}
action-types.js
action的type名称常量
3). component
UI组件:
组件内部没有使用任何redux相关的API
通过props接收容器组件传入的从redux获取数据
数据类型: 一般和函数
容器组件
connect(
state => ({user: state.user}),
{action1, action2}
)(UI组件)
day03
1. 实现user信息完善功能
1). 用户信息完善界面路由组件:
组件: dashen-info/laoban-info =>通过onChange/state来收集用户输入数据(头像名称 职位简介 职位名称 公司名称 工资)
header-selector(共同的部分)=>头部作为UI组件,作为父组件定义一个设置头像的函数并传给子组件,子组件点击头像的时候首先要先更新state里的状态,还要去调用父组件传过来的方法,方法传入头像
界面: Navbar/List/Grid(宫格)/InputItem(文本输入)/Button/TextareaItem(多行输入)
注册2级路由: 在main路由组件
界面完善后就是点击保存前后台交互的过程了 之前登陆和注册成功后 store里定义了一个重定向的地址为/ 而在入口文件这个/指主页面
而在信息完善的静态组件完成后有四种情况 首先有两种类型用户 老板和大神 成功之后有信息已完善和未完善
四个就四个路由路径 判断是否完善信息,看user.header是否有值 判断用户类型:看user.type 此时就要定义一个功能函数
2). 登陆/注册成功后的跳转路由计算 就是看后台返回的数据看是否有头像和什么类型 进行相应的路由跳转
定义工具函数 getRedirectTo(type,header)
计算逻辑分析
3). 后台路由处理 更新用户的路由 更新的时候是需要id,但是前面登陆和注册的时候已经把id存在浏览器cookie里
从请求cookie得到userid
const userid = req.cookies.userid//cookies是对象 是个键值对 如果没有说明没有登陆直接返回提示还未登陆
根本userid找到对应的user 然而user一定有值嘛 不一定/要是里面的cookie数据被篡改了 没有对应的user就更新不了
//通知浏览器删除userid cookie // 返回一个提示信息请先登陆
将user信息和前台发送的数据进行拼接 Object.assign(req.body, {_id, username, type}) 返回一个合并的对象
4). 前台接口请求函数 更新用户信息接口 export const reqUpdateUser = (user) => ajax('/update', user, 'POST')
5). 前台redux
action-types
异步action/同步action
reducer
6). 前台组件 点击保存派发异步action 执行异步action返回一个函数: disptach => {执行异步代理(注册的接口 向后台发送ajax请求)
将后台返回的数据通过派发同步action的方式派发给reducers,reducers将之前的数据和后台返回的数据进行一个拼接将其保存在state 里,然后映射到相应组件的props里。就会重新render
UI组件包装生成容器组件用connect
读取状态数据
更新状态 如果更新失败就说明前台数据有问题或者有人篡改了cookie 派发一个action返回一个...initUser 自动跳转登陆界面
因为这是什么都没有
2. 搭建整体界面(上)
1). 登陆状态维护
后台将userid保存到cookie中
前台读取cookie中的userid
redux中管理user信息状态
访问二级路由都需要登陆状态
2). 实现自动登陆 在Main组件实现自动登陆 当组件挂载时去获取用户信息,因为之前后台在做登陆的路由使用cooike来标识了登陆的有效时间
也就是你完成注册就不需要登陆了并且你关闭页面输入相应地址都能回到相应的页面有效期为24小时 => 登陆过(cookie中有userid),但没有有登陆(redux管理的user中没有_id)发请求获取对应的user 就要去派发action去获取相应的user信息
步骤:读取cookie中的userid 如果没有,自动重定向到登陆界面
如果有,读取redux中的user状态=> 如果 user没有_id,返回null(不做任何显示) 因为是异步的 当数据返回时就不显示null了 短暂
如果有_id,说明已经登陆 显示对应的界面 还要一种情况就是请求根路径
根据user的type和header来计算出一个重定向的路由路径,并自动重定向
如果有user_id且请求的不是根路径就显示相对应的路由(映射)
后台逻辑分析 :而后台先读取cookie 中的 userid 如果没有返回请先登陆 如果有就将相应的user信息返回至前台
这里派发的同步action都是和更新是一样的
ajax请求根据cookie中的userid查询获取对应的user信息
获取用户信息的路由是为了实现自动登录存在userid没有user._id的情况
day04
1. 搭建整体界面(下)
封装导航路由相关数据(数组/对象):在Main组件引入这四个导航的容器组件 还有一个404的UI组件
因为每一个容器组件都有自己的一个路径以及组件标题 图标 图标下面的文本,这些信息为了更好的管理就要拿 每一个路由都要有这些信息, 就要拿对象来存取底部导航的信息,每一个对象组成一个数组(大神列表 老板列表 消息列表 个人中心)
path:'/laoban',//路由路径
component:Laoban,
title:'大神列表',
icon:'dashen',
text:'大神'
遍历该数组去映射路由(因为数组里的每个个对象都是一个底部导航都是以一个个键值对的形式存在的)
包括头部导航的设置是通过遍历数组找到请求的路径等于底部导航的路径
并且可以设置hidden值将数组中的某一项进行隐藏 当请求的路径等于点击底部导航的路径时如果用户的类型是大神 隐藏大神的列表,将隐藏后的navList底部导航
抽取底部导航组件:将隐藏后的导航列表传给底部导航组件 底部导航的UI组件拿到导航列表进行遍历
title={nav.text} 文本
icon={{uri:require(`./nav/${nav.icon}.png`)}} 图标
selectedIcon={{uri:require(`./nav/${nav.icon}-selected.png`)}} 选中
selected={path===nav.path} 默认选中
onPress={()=>this.props.history.replace(nav.path)}//按下去跳转路由 由于没有这个属性
引入import {withRouter} from 'react-router-dom' 内部会想组件中传入一些路由组件特有的属性 history/location/math
非路由组件使用路由组件API 引入import {withRouter} from 'react-router-dom'
底部导航组件不是路由组件 路由组件是被Route包裹的
2. 个人中心
读取user信息显示
退出登陆 退出登陆去派发重置User的同步action 去除cookie中id
3. 用户列表
为大神/老板列表组件抽取用户列表组件 抽取共性组件
异步读取指定类型用户列表数据 组件挂载时获取用户列表 发送异步action 之前的都是操作user 现在要操作整个user列表
因此要重新写一个reducers
后台路由 根据类型获取用户列表const{type} = req.query
api 请求获取用户列表的接口export const reqUserList = (type) => ajax('/userlist', {type})
redux
component 将state里状态映射到老板大神组件props里 传给子组件也就是用户列表这个UI组件 子组件接收到遍历相应的信息
4. socket.io
实现实时聊天的库
包装的H5 WebSocket和轮询---> 兼容性/编码简洁性
它包装的是 H5 WebSocket 和轮询, 如果是较新的浏览器内部使用 WebSocket,
如果浏览器不支持, 那内部就会使用轮询实现实时通信
包含2个包:
socket.io: 用于服务器端
socket.io-client: 用于客户端
基本思想: 远程自定义事件机制
on(name, function(data){}): 绑定监听
emit(name, data): 发送消息
io: 服务器端核心的管理对象
socket: 客户端与服务器的连接对象
day05
1. 聊天组件功能:
后台接口:首先要引入由mongoose操作的聊天的数据库(发送用户的id 接收用户的id from和to组成的字符串 内容 标识是否已读 创建时间)
//关于后台的东西:聊天的消息要用数据库存起来,每发一条消息都会产生一条记录在server里db models里写 之前只定义一个users的集 //合现在要多定义一个chats集合的文档结构
其次引入socket.io这个库 返回的是一个函数,这个函数接收一个server(服务器),相当于服务器挂在上面得到一个io对象,用这个对象有一个on来1.监视客户端与服务器的连接,绑定监听, 接收客户端发送的消息 2.处理数据 3. 向所有连接上的客户端发消息 这样做的前提就是发给浏览器端要确认一下是不是发给我的
新增获取当前用户所有相关聊天信息列表:根据接口文档返回的是除了消息数组还有包含所有users相关信息的一个对象容器
对象的好处就是根据某一个user_id能够得到user信息 因为chatmsg里有from和to的user_id 你只要告诉我chatmsg我就能找到users
{
"code": 0,
"data": {
"users": {
"5ae1d5d19151153d30e008fd": {
"username": "ds2"
},
"5ae1ddd99ca58023d82351ae": {
"username": "aa",
"header": "头像1"
},
"5ae1df049ca58023d82351af": {
"username": "aa2"
},
"5ae1e72aa072c522e024b18e": {
"username": "bb",
"header": "头像3"
},
"5ae1f088d37a442b749fc143": {
"username": "laoban1",
"header": "头像2"
}
},
"chatMsgs": [
{
"read": false,
"_id": "5ae1f3c3a95eb824841199f1",
"from": "5ae1f088d37a442b749fc143",
"to": "5ae1ddd99ca58023d82351ae",
"content": "aa",
"create_time": 1524757443374,
"__v": 0
}
]
}
}
获取当前用户所有相关聊天信息列表路由:
获取cookie中的userid
const userid = req.cookies.userid
查询得到所有user文档数组
UserModel.find(function (err, userDocs) {
// 用对象存储所有user信息: key为user的_id, val为name和header组成的user对象
const users = {} // 对象容器
userDocs.forEach(doc => {
users[doc._id] = {username: doc.username, header: doc.header} //找用户名和头像
return users
})
ChatModel.find({'$or': [{from: userid}, {to: userid}]}, filter, function (err, chatMsgs) {
// 返回包含所有用户和当前用户相关的所有聊天消息的数据
res.send({code: 0, data: {users, chatMsgs}}) //找聊天记录
})
返回一个对象给前台
chat静态组件:点击userlist里就要跳转到聊天的界面<Card onClick={() => this.props.history.push(`/chat/${user._id}')}>
也是需要withrouter
发送消息与接收消息 点击发送(有消息才)派发action(要传入发送的id 和接收的id 和content)
const to = this.props.match.params.userid 对方
const from = this.props.user._id redux将状态映射在chat组件的props
现在发消息不是用ajax请求了 是用socket.io来发 后台接收到消息进行处理返回给前台 前台也要对数据进行筛选
完成这些之后,只要用户登陆就去获取消息列表,有三种情况
注册成功,登陆成功,getUser自动登陆 之后都要获取消息列表 封装一个得到消息列表 消息列表是之前就获得的了
希望initIO调用的实际是一上来就希望初始化sockio 能启动监视
做到这里 redux就有了消息列表的数据 之后就是显示在组件里
对chatMsgs进行过滤
const targetId = this.props.match.params.userid
const chatId = [meId,targetId].sort().join('_')
const msgs = chatMsgs.filter(msg=>msg.chat_id===chatId)
此时的msgs就是两个人互相发的消息 但是还是要过滤 因为左边是对方发给我的 右边是我发给对面的
此时就实现了消息列表显示了
接收消息显示: 因为后台是向所有连接上的客户端发消息,只有当chatMsg是与当前用户相关的消息, 才去分发同步action保存消息
完善列表显示: 表情功能 初始化表情列表数据 设置是否显示表情列表的状态 通过点击表情图案显示和触焦的时候隐藏
day06
1. 消息列表
对消息进行分组保存, 且只保存每个组最后一条消息 message路由组件
首先先读store保存的数据
定义一个getLastMsg的函数
使用{}进行分组(chat_id),只保存每个组最后一条msg let lastMsg = lastMsgObjs[chatId] 如果没有这个遍历的msg就是
如果有就比较时间 越晚的
到所有分组的lastMsg组成数组 const lastMsgs = Object.values(lastMsgObjs)
对数组排序(create_time,降序) return m2.create_time - m1.create_time //降序排列 大的放前面
对于对象容器和数组容器的选择
数组排序
2. 未读消息数量显示
每个组的未读数量统计 由
总未读数量统计显示
查看消息后, 更新未读数量
3. 自定义redux和react-redux
理解redux模块
1). redux模块整体是一个对象模块
2). 内部包含几个函数
createStore(reducers) // reducers: function(state, action){ return newState}
combineReducers(reducers) // reducers: {reducer1, reducer2} 返回: function(state, action){ return newState}
applyMiddleware() // 暂不实现
3). store对象的功能
getState() // 返回当前state
dispatch(action) // 分发action: 调用reducers()得到新的总state, 执行所有已注册的监听函数
subscibe(listener) // 订阅监听: 将监听函数保存起来
理解react-redux模块
1). react-redux模块整体是一个对象模块
2). 包含2个重要属性: Provider和connect
3). Provider
值: 组件类
作用: 向所有容器子组件提供全局store对象
使用: <Provider store={store}><Xxx/></Provider>
4). connect
值: 高阶函数
作用: 包装组件生成容器组件, 让被包装组件能与redux进行通信
使用: connect(mapStateToProps, mapDispatchToProps)(Xxx)
硅谷直聘总结
1 | 1、使用 create-react-app(脚手架)搭建项目(工程化) |