接下来介绍里面的几个参数:
//r-routes/indeximport React from 'react';import ReactDOM from 'react-dom';import ReactApp from '../modules/r_app'//根组件ReactDOM.render(<ReactApp />,document.getElementById('app'));
可能会疑惑,如何知道把
output:配置打包输出位置以及输出文件名字。(html的生成是通过new HtmlWebpackPlugin方法)
module:里面是各种loader加载器;webpack理论上只能加载js文件,但是通过各种loader它可以加载图片、css等等文件。
项目用到的loader:style-loader、css-loader、file-loader...详见package.json
要使webpack打包支持react和ES6语法还需要安装babel等依赖
npm install --save-dev react react-dom babelify babel-preset-reactnpm install --save babel-preset-es2015 //支持ES6语法//loader配置参考上面的即可
plugins:各种插件配置
例如上面全局jquery的配置(记得安装jquery依赖包npm install jquery --save)
注意我这里没有配置热更新,因为热更新有自己的服务,但我想使用node启动服务,不用webpack-dev-server的服务,所以就没配置(网上应该有解决方法,给node服务添加热更新,但是我没找到,所以项目只有自动打包,但还是需要手动刷新浏览器)
npm install webpack-dev-server --save-dev //热更新安装
至此,webpack.config.js配置完成。接下来我们看看package.json
//package.json{ "name": "app", "version": "0.0.0", "private": true, "scripts": { "start": "node ./bin/www", "build": "webpack --progress --watch"
}, "dependencies": { "cookie-parser": "~1.4.3", "debug": "~2.6.9", "ejs": "^2.6.1", "express": "~4.16.0", "http-errors": "~1.6.2", "jade": "~1.11.0", "jquery": "^3.3.1", "less": "^3.8.1", "morgan": "~1.9.0"
}, "devDependencies": { "babel-core": "^6.26.3", "babel-loader": "^7.1.5", "babel-preset-env": "^1.7.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "css-loader": "^1.0.0", "file-loader": "^1.1.11", "html-webpack-plugin": "^3.2.0", "http-proxy-middleware": "^0.18.0", "less-loader": "^4.1.0", "react": "^16.4.2", "react-dom": "^16.4.2", "react-router-dom": "^4.3.1", "socket.io": "^2.1.1", "socket.io-client": "^2.1.1", "style-loader": "^0.22.1", "url-loader": "^1.0.1", "webpack": "^3.0.0", "webpack-cli": "^3.1.0", "webpack-dev-middleware": "^3.1.3", "webpack-dev-server": "^2.9.7"
}
}
安装的依赖包我就不具体介绍,重点介绍scripts的参数
..."scripts": { "start": "node ./bin/www", "build": "webpack --progress --watch"
},...
这里是可根据情况配置一些指代命令。
本来项目启动需要node ./bin/www,但是通过配置,我终端输入npm run start(npm run + 指令)也能达到一样的效果。
同理,我利用npm run build 代替了webpack的打包命令,并附带了一些参数命令。
--progress //显示进度条 --watch //监听变动并自动打包 -p //压缩脚本
大吉大利!枯燥的项目配置到此结束!
3. 熟悉的前端味道:编写React组件
//r-routes/index.jsimport ReactApp from '../modules/r_app' //根组件
我们可以看到r-routes/index.js引用了一个根组件r_app,r_app再分成
//r_app.js//引入组件import AppHead from './head/index'import AppContent from './content/index'import AppFoot from './foot/index'import UserInfoModal from '../component/userInfoModal/index'import './r_app.less'...const storage = window.localStorage;
storage.removeItem('userInfo'); //进入页面时清除localStroage
if(!storage){ // console.log("浏览器不支持localstorage");
return false;
}else{ // console.log("浏览器支持localstorage");
//判断是否存在localStroage
if(storage['userInfo']){ //已经存在localStroage.隐藏输入信息框
this.setState({
userInfo:JSON.parse(storage['userInfo']), //把StringObject转换成Object
userInfoState:true,
})
}
}
}
...
render (){ // console.log(this.state.userInfo)
return (
<div className="appWried" >
{ this.state.userInfoState ? '' : <UserInfoModal onSubmitData={this.onGetData} />
}
{
<div style={{height:'100%',width:'100%'}} className={this.state.userInfoState ? '' : 'unClick'} >
<AppHead />
<AppContent userInfo={this.state.userInfo}/>
<AppFoot userInfo={this.state.userInfo} />
</div>
}
</div>
);
}
可以发现,r_app.js只是做了localStroage的读取和判断,但是并没有写入任何,localStroage字段。并且永远不会进入if(storage['userInfo'])语句,因为每次在最前面都会把信息remove。所以信息输入框每次刷新页面都依然会弹出来.
耍我呢?localStroage出来秀逗的?
= =localStroage在这里确实有点大材小用,因为一开始想持续性保存用户的信息以及聊天记录,但是发现这样测试难以进行。我就一部电脑,读取的localStroage['userInfo']不就一模一样么。
说回正题,那添加localStroage的操作在哪执行?
答案就在
//UserInfoModal.js...
<button className="submitBtn" onClick={this.submitFn.bind(this)}>提交</button>
submitFn(){ let userName = $('#userName').val(); if(!userName){
alert('名字还未输入哦') return;
} let headImg = this.state.choseImg; let userId = "indexCode" + Math.round(Math.random() * 100000); //随机创建id,用来判断是自身信息还是别人信息
//数据传回父组件r_app.js
this.props.onSubmitData({
userName,
headImg,
userId,
})
}
...//r_app.js...//子级返回数据onGetData (e){ // console.log(e);获得子组件传递的数据,包括userName和headImg
let userInfo = {};
userInfo.userName = e.userName;
userInfo.headImg = e.headImg;
userInfo.userId = e.userId;
this.setState({
userInfo,
userInfoState:true,//隐藏输入信息框
},function(){ const storage = window.localStorage;
storage['userInfo'] = JSON.stringify(this.state.userInfo);//localStorage只能存储String类型,需将对象转换成string
// console.log(storage['userInfo'])
})
}
...
注意:localStroage只能存储String类型的数据,如果需要存储对象,需要通过JSON.Stringify()转换。取数据的时候通过JSON.parse()即可
2. Socket.io的使用
实现效果:底部input发送的数据传递到content组件并展示,并且要求所有客户端都能收到。
实现思路:利用socket.io实现实时通信,先把发送信息的客户端的用户信息以及信息中转到服务器,服务器再分派给所有订阅了这个socket事件的客户端。接收到消息的
npm install socket.io --save-dev //安装服务器端的socket.ionpm install socket.io-client --save-dev //安装客户端的socket.io
// bin/www//新增socket.io模块var io = require('socket.io')(server);
io.on('connection', function(socket){ //接受客户端传送的sendMessage命令
socket.on('sendMessage', function(ioUserInfo,msg){ console.log(ioUserInfo); //用户ioUserInfo
console.log(msg); //接收用户的发送信息
//通过接受sendMessage这个action的数据再广播给所有'订阅的人'(即on了这个事件的)
socket.broadcast.emit('getMessage', ioUserInfo, msg); //socket.emit()发送信息给全部人,只要订阅了getMessage的人都会收到变量ioUserInfo和msg
//socket.broadcast是发送除自己外的人
});
})
引入socket.io模块,当处于connection的时候即可进行接收、发送信息。上面服务器接收(on)到某个用户传来的信息之后再广播(emit)给大家
on和emit可以这么理解,接收信息是on事件,发送信息是emit事件
//发送标志为message信息,信息内容为:testsocket.emit('message','test')//订阅了标志为message的信息的客户端将会接收到这条test信息socket.on('message',function(data){ console.log(data);//test })
const socket = require('socket.io-client')('http://localhost:8000');
socket.on()....socket.emit()...
//footComponent.js...let disabled = Object.keys(this.props.userInfo).length ? '' : 'disabled'; //未填用户信息的时候禁止input输入内容return (
<div className="footDiv">
<input disabled={disabled} className="footIpt" placeholder="请输入..." onChange={this.dataChange} value={this.state.message}/>
<button className={`footBtn ${this.state.hasCont}`} onClick={this.clickBtn}>发送</button>
</div>
)
...
componentDidMount(){
document.addEventListener("keydown",this.handleEnterKey);(绑定一个键盘按下的方法)
}//点击按钮发送信息clickBtn(){ const { message } = this.state; //获取input输入的内容
const { userInfo } = this.props; // console.log('发送' + this.state.message)
//是否发送内容
this.sendMessage(userInfo,message);
}//回车后发送信息handleEnterKey(e){ let that = this; const { message } = this.state; //获取input输入的内容
const { userInfo } = this.props; if(e.keyCode === 13){
//回车keyCode==13
//是否发送内容
this.sendMessage(userInfo,message);
}
}
sendMessage(ioUserInfo,message){ if(message){ this.sendSocketIO(ioUserInfo,message);//发送websocket
this.setState({
message:'', //清空input内容
})
}
}
sendSocketIO(ioUserInfo,message){
socket.emit('sendMessage',ioUserInfo,message) //客户端发送}
//content/index.jssocket.on('getMessage', function(ioUserInfo,msg){ console.log(ioUserInfo); //ioUserInfo为发送msg的用户信息
console.log(msg) //用户发送的内容}
componentWillReceiveProps(nextProps){
const { userInfo } = nextProps; //获取父级传递过来的userInfo,里面携带自身的userId
socket.on('getMessage', function(ioUserInfo,msg){
console.log(ioUserInfo);
// 如果socket传回ioUserInfo.userId和自身相同,则判断为自身发送的信息
let appendLi = ''
if(ioUserInfo.userId == userInfo.userId){
appendLi = `<li class="contLi contLiMy">
<div class="contLiMy">
<div class="headImg">
<img src=${userInfo.headImg} />
</div>
<div class="chatContent">
<div class="chatName">
<span>${userInfo.userName}</span>
</div>
<div class="chatBg">
<span>${msg}</span>
</div>
</div>
</div>
</li>`
}
else{
appendLi = `<li class="contLi contLiOther">
<div class="contLiOther">
<div class="headImg">
<img src=${ioUserInfo.headImg} />
</div>
<div class="chatContent">
<div class="chatName">
<span>${ioUserInfo.userName}</span>
</div>
<div class="chatBg">
<span>${msg}</span>
</div>
</div>
</div>
</li>
`
}
$('.contUl').append(appendLi);
});
}
return( <div className="content">
<ul className="contUl">
<div className="contTop">欢迎你:{this.props.userInfo.userName}</div>
</ul>
</div>)
这里有一个小技巧,如果内容超出高度出现滚动条的时候,需要保持显示底部的内容.在填充内容后加上一行代码
...
$('.contUl').append(appendLi);
$('.contUl').scrollTop($('.contUl')[0].scrollHeight);//保持显示滚动条高度的位置(即底部)...

至此项目的主要功能都已经完成啦。未介绍的less其实和css写法差不多,这里less只是简化了父层的写法

一些点击、hover功能都是用最基础的js、jq实现的。
例如:聊天框的实现(利用伪类:after制作三角形)
//对方聊天框
.chatBg{ font-size:15px; padding:3px 10px; border-radius: 3px; color: #EFEFEF; background-color: #8c628d; margin-right:8px; position: relative;
}
//聊天框三角形的制作
.chatBg:before{ right:-12px;
border-color:transparent transparent transparent #8c628d; //四边分别代表:上右下左 }
六、马后话(总结)
1. 总结
项目总体的实现没有难度,都是最基础的东西。能帮助初学者(例如我)学到和巩固知识点才是最重要的!有任何疑问或者任何错误,欢迎留言啦!谢谢小伙伴们的耐心阅读~~
原文出处:https://www.cnblogs.com/soyxiaobi/p/9489444.html