摘要:最近練手開發了一個項目,是一個聊天室應用。由于我們的項目是一個單頁面應用,因此只需要統一打包出一個和一個。而就是基于實現的一套基于事件訂閱與發布的通信庫。比如說,某一個端口了,而如果端口訂閱了,那么在端,對應的回調函數就會被執行。
最近練手開發了一個項目,是一個聊天室應用。項目雖不大,但是使用到了react, react-router, redux, socket.io,后端開發使用了koa,算是一個比較綜合性的案例,很多概念和技巧在開發的過程中都有所涉及,非常有必要再來鞏固一下。
項目目前部署在heroku平臺上,在線演示地址: online demo, 因為是國外的平臺速度可能有點慢,點進去耐心等一會兒就能加載好了。
加載好之后,首先出現的頁面是讓用戶起一個昵稱:
輸入昵稱之后,就會進入聊天頁面,左邊是進入聊天室的在線用戶,右邊則是聊天區域,下圖是三個在線用戶聊天的情形:
項目源碼的github地址: 源碼地址, 有興趣的同學歡迎關注學習~
下面就來分析一下項目的整體架構,以及一下值得注意的技巧和知識點。
1. 整體結構項目的目錄如下:
├── README.md ├── node_modules ├── dist │?? ├── bundle.css │?? ├── bundle.js │?? └── resource │?? ├── background.jpeg │?? └── preview.png ├── package.json ├── server.js ├── src │?? ├── action │?? │?? └── index.js │?? ├── components │?? │?? ├── chatall │?? │?? │?? ├── index.js │?? │?? │?? └── index.less │?? │?? ├── login │?? │?? │?? ├── index.js │?? │?? │?? └── index.less │?? │?? ├── msgshow │?? │?? │?? ├── index.js │?? │?? │?? └── index.less │?? │?? ├── namelist │?? │?? │?? ├── index.js │?? │?? │?? └── index.less │?? │?? ├── nav │?? │?? │?? ├── index.js │?? │?? │?? └── index.less │?? │?? └── typein │?? │?? ├── index.js │?? │?? └── index.less │?? ├── container │?? │?? ├── chatAll.js │?? │?? └── login.js │?? ├── index.ejs │?? ├── index.js │?? ├── index.less │?? ├── index2.js │?? ├── reducer │?? │?? └── index.js │?? ├── redux_middleware │?? │?? └── index.js │?? └── resource │?? ├── background.jpeg │?? └── preview.png └── webpack.config.js
其中src當中是前端部分的源代碼。項目使用webpack進行打包,打包后的代碼在dist目錄當中。由于我們的項目是一個單頁面應用,因此只需要統一打包出一個bundle.js和一個bundle.css。而后端使用了koa框架,由于代碼相對比較少,都集中在了server.js這一個文件當中。
開發過程中,由于要webpack打包,一般我們會配合webpack-dev-server來使用。webpack-dev-server運行的時候自身就會開啟一個server,而在我們的項目當中,后端koa也是一個server,因此為了簡單起見,我們可以使用koa-webpack-dev-middleware來在koa當中開啟webpack-dev-server。
var webpackDev = require("koa-webpack-dev-middleware"); var webpackConf = require("./webpack.config.js"); var compiler = webpack(webpackConf); app.use(webpackDev(compiler, { contentBase: webpackConf.output.path, publicPath: webpackConf.output.publicPath, hot: true }));2. 項目布局: flexbox實踐
在這個項目中我們有意識的使用了flex布局,作為面向未來的一種新的布局方式,實踐一下還是很有必要的!沒有學習郭flexbox的同學可以參考這篇來學一下:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html
以聊天界面為例進行分析,使用flex布局的話,可以非常方便,下圖就是對界面的一個簡單的切分:
整個聊天框最外層紅框框起來的部分display設置為flex,并且flex-direction設置為column,這樣它里面的兩個元素(即粉框和藍框部分)就會豎直方向排列,同時粉框的flex設置為0 0 90px,代表該框不可伸縮,固定高度90px,而對于藍框,則設置flex為1,代表伸展系數為1,這樣,藍框的高度就會占滿除了粉框以外的全部空間。
而于此同時,粉框和藍框本身又分別設置display為flex。對粉框而言,內部一共有歡迎標簽和退出button兩個元素,分列兩側,因此只需要設置justify-content為space-between即可做到這一點。而對藍框而言,內部有在線用戶列表以及聊天區域兩個元素。這里在線用戶列表(即黃色框)需要設置固定寬度,因此類似于剛才粉框的設置,flex: 0 0 240px,而聊天區域(即綠色框)則設置flex為1,這樣會自適應占滿剩余寬度。
最后,聊天區域內部又分為信息展示區以及打字區,因此聊天區域自身又是一個flexbox,設置方式類似,就不再具體分析了。
可以看出,使用flexbox,相比使用float以及position等等而言,更加的規整,使用這種思路,整個頁面就像庖丁解牛一般,布局格外清晰。
3. 設計頁面的數據結構項目中使用了redux作為數據流管理工具,配合react,能夠讓頁面組件同頁面數據形成規律的映射。
分析我們的聊天頁面,可以看出,主要的數據就是目前在線的用戶昵稱列表,以及消息記錄,此外我們還需要記錄自己的用戶昵稱,方便消息發送時候取用。因此,整個應用的數據結構如下, 也就是redux中的store的數據結構如下:
{ "nickName": "your nickname", "nameList": ["user A","user B","user C","...."], "msgList": [ { "nickName": "some user", "msg": "some string" },{ "nickName": "another user", "msg": "another string" }, ] }
有了這個總體的數據結構,我們就可以根據該結構設計具體的action,reducer等等部分了。這里整個程序的模塊拆分遵循了redux官方實例當中的拆分方法,action文件夾當中定義action creators,reducer文件夾中定義reducer函數,component文件夾中定義一些通用的組件,container文件夾當中則是將通用組件取出,定義store中的數據同組件如何映射,以及組件中的事件如何dispatch action,從而引起store數據的改變。
以component/namelist中的組件為例,該組件用于顯示在線用戶昵稱列表,因此它接受一個數組,也就是store中的nameList作為參數,因此其通用組件的寫法也很簡單:
class NameList extends React.Component { constructor(props) { super(props); } render() { var {nameList} = this.props; return (
而在container當中,只需要將store中的nameList賦值到該組件的props上面即可。其他組件也是類似的寫法。
可以看出,在redux的思想下,我們可以對整個應用抽象出一個總體的數據結構,數據結構的改變,會引發各個組件的改變,而組件當中的各種事件,又會反過來修改數據結構,從而再次引起頁面的改變,這是一種單向的數據流,總體的數據都在store這個對象中進行維護,從而讓整個應用開發變得更加有規律。redux的這種程序架構是對react提出的flux架構的一種消化和改良,下圖是flux架構的示意圖:
4. socket.io的使用由于是一個即時聊天應用,websocket協議自然是首選。而socket.io就是基于websocket實現的一套基于事件訂閱與發布的js通信庫。
在socket.io中,主要有server端和client端。創建一個server和client都非常容易,對于server端,配合koa,只需要如下代碼:
var app=require("koa")(); var server = require("http").Server(app.callback()); var io = require("socket.io")(server);
client端更加簡單:
var io=require("socket.io-client"); var socket = io();
一旦連接建立,client和server即可通過時間訂閱與發布來彼此通信,socket.io提供的api非常類似于nodejs中的event對象的使用,對于server端:
io.on("connection",function(socket){ socket.on("some event",function(data){ //do something here.... socket.emit("another event",{some data here}); }); });
對于client端,同樣通過socket.on以及socket.emit來訂閱和發布事件。比如說,某一個client端口emit了event A,而如果server端口訂閱了event A,那么在server端,對應的回調函數就會被執行。通過這種方式,可以方便的編寫即時通信程序。
5. 一些值得注意的實現細節下面對程序中涉及的一些我認為值得注意的細節和技巧進行一下簡要分析。
1. socket.io同redux的結合方案:redux中間件的運用在程序編寫過程當中,我遇到一個難題,就是如何將socket.io的client實例結合到redux當中。
socket.io的client類似于一個全局的對象,它不屬于任何一個react組件,它訂閱到的任何消息都可能更改整個應用的數據結構,而這種更改在redux當中又只能通過dispatch來實現。思考之后,我覺得編寫一個redux中間件來處理socket.io相關的事件是一個很好的選擇。
關于redux中間件,簡單來說,就是在redux真正出發dispatch之前,中間件可以首先捕獲到react組件出發的action,并針對不同action做一些處理,然后再調用dispatch。中間件的寫法,在redux的官方文檔當中寫的非常詳細,有興趣的可以參考一下: http://redux.js.org/docs/advanced/Middleware.html , 后續我也會出一些系列文章,深入分析redux包括react-redux的原理,其中就會提到中間件的原理,盡請期待~
知道了redux中間件是怎么一回事之后,我們就可以發現,socket.io相關的事件非常適合通過寫一個中間件來處理。我們程序當中中間件如下所示:
import { message_update, guest_update } from "../action" function createSocketMiddleware(socket) { var eventFlag = false; return store => next => action => { //如果中間件第一次被調用,則首先綁定一些socket訂閱事件 if (!eventFlag) { eventFlag = true; socket.on("guest update", function(data) { next(guest_update(data)); }); socket.on("msg from server", function(data) { next(message_update(data)); }); socket.on("self logout", function() { window.location.reload(); }); setInterval(function() { socket.emit("heart beat"); }, 10000); } //捕獲action,如果是和發送相關的事件,則調用socket對應的發布函數 if (action.type == "MSG_UPDATE") { socket.emit("msg from client", action.msg); } else if (action.type == "NICKNAME_GET") { socket.emit("guest come", action.nickName); } else if (action.type == "NICKNAME_FORGET") { socket.emit("guest leave", store.getState().nickName); } return next(action); } } export default createSocketMiddleware
這段代碼是一個socket middleware的創建函數,從中我們可以看出,這個中間件如果第一次調用的話(eventFlag),會首先綁定一些訂閱主題和對應的回調函數,主要是訂閱了消息到達、新用戶來到、用戶離開等等事件。同時,中間件會在真正dispatch函數調用之前,首先捕獲action,然后分析action的type。如果是和發送事件相關的,就會調用socket.emit來發布對應的事件和數據。比如說,在我們的應用中,點擊“發送”按鈕會觸發一個type為"MSG_UPDATE"的事件,這個事件首先被中間件捕獲,那么這時候就會出發socket.emit("msg from client")來將消息發送給server。
2. 權限驗證: 單頁面應用中的頁面跳轉整個應用使用react-router,做成了一個單頁面應用,其中前端路由的層級非常簡單:
render(, document.getElementById("test"));
可以看出,主要是兩條路徑: "/"和"/login",其中"/"是我們的聊天界面,而"/login"則是起昵稱界面。
由于應用的邏輯是,只有用戶起了昵稱才可以進入聊天界面,因此我們需要做一些權限驗證,對于沒有起昵稱就進入"/"路徑的用戶,需要跳轉到"/login"。在傳統多頁面web應用中,我們對于跳轉非常熟悉,無非是服務器發送一個重定向請求,瀏覽器就會重定向到新的頁面。然而在單頁面中,由于始終只有一頁,服務器又能夠讓瀏覽器跳轉到哪里去呢?也就是說,服務器重定向的方法是行不通的。
因此,我們換一種思路,頁面跳轉的邏輯需要在瀏覽器端執行,在react-router的框架下,執行跳轉也非常簡單,只需要使用其中的hashHistory對象,通過hashHistory.push("path"),即可讓應用跳轉到指定路徑對應的界面。有了這個認知,那么我們下面要解決的,就是何時控制單頁面的跳轉?
我的思路是,將用戶的昵稱通過一定的加密和編碼,保存在cookie當中。當用戶訪問"/"的時候,在對于界面的組件掛載之前,首先會向服務器發送一個認證請求,服務器會從請求中讀取cookie,如果cookie當中沒有用戶名存在,那么服務器返回的參數當中有一個"permit"字段,設置為false,當應用解析到該字段后,就會調用hashHistory.push("/login")來讓頁面跳轉到起昵稱界面下。這部分對應的邏輯主要在container/chatAll.js文件當中實現。
3. 文本輸入的細節處理: xss的預防,以及組合鍵的識別在我們的聊天應用中,如果不對用戶的輸入進行一些處理,就有可能導致xss漏洞。舉個例子,比如說有一個用戶輸入了"",如果不進行一些防范,輸入到消息顯示界面,這段文字就直接被解析成為了一段js代碼。為了防范這類攻擊,這里我們需要做一些簡單的預防:
var regLeft = //g; value = value.replace(regLeft, "<"); value = value.replace(regRight, ">");
這段代碼在components/typein組件當中。
此外,為了方便用戶快速發送消息,在消息輸入框中,我們設置了"enter"按鍵為之間發送按鍵。那么,為了讓用戶能夠打出換行,我們模仿微信,約定用戶輸入ctrl+enter組合鍵的時候是換行,這樣,在消息輸入框中,就需要監聽組合鍵。在js的鍵盤事件中,event對象有一個ctrlKey屬性,用于判斷ctrl按鍵是否按下:
someDom.onkeydown=function(e){ if(e.keyCode==13&&e.ctrlKey){ //組合鍵被按下 } }
這就是組合鍵監聽的原理。
以上就是對于這個項目的概述以及一些細節的講解。最后安利一下我的博客 http://mly-zju.github.io/,會不定期更新我的原創技術文章和學習感悟,歡迎大家關注~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81934.html
摘要:異步最佳實踐避免回調地獄前端掘金本文涵蓋了處理異步操作的一些工具和技術和異步函數。 Nodejs 連接各種數據庫集合例子 - 后端 - 掘金Cassandra Module: cassandra-driver Installation ... 編寫 Node.js Rest API 的 10 個最佳實踐 - 前端 - 掘金全文共 6953 字,讀完需 8 分鐘,速讀需 2 分鐘。翻譯自...
摘要:是一個基于和的服務器端和瀏覽器端的的前后端全棧應用框架。是的組件,并且會進行數據初始化不但可以支持的數據初始化,還可以合并和的,使用同一個,和的無縫結合。 koa-cola是一個基于koa和react的服務器端SSR(server side render)和瀏覽器端的SPA(single page application)的web前后端全棧應用框架。 koa-cola使用typescr...
摘要:利用中間件實現異步請求,實現兩個用戶角色實時通信。目前還未深入了解的一些概念。往后會寫更多的前后臺聯通的項目。刪除分組會連同組內的所有圖片一起刪除。算是對自己上次用寫后臺的一個強化,項目文章在這里。后來一直沒動,前些日子才把后續的完善。 歡迎訪問我的個人網站:http://www.neroht.com/? 剛學vue和react時,利用業余時間寫的關于這兩個框架的訓練,都相對簡單,有的...
閱讀 1031·2021-11-23 09:51
閱讀 2352·2021-10-08 10:22
閱讀 2568·2021-09-29 09:35
閱讀 862·2021-09-22 15:20
閱讀 2864·2019-08-30 15:53
閱讀 2417·2019-08-30 13:55
閱讀 1106·2019-08-29 17:27
閱讀 2874·2019-08-29 17:26