亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

基于react+react-router+redux+socket.io+koa開(kāi)發(fā)一個(gè)聊天室

NusterCache / 1434人閱讀

摘要:最近練手開(kāi)發(fā)了一個(gè)項(xiàng)目,是一個(gè)聊天室應(yīng)用。由于我們的項(xiàng)目是一個(gè)單頁(yè)面應(yīng)用,因此只需要統(tǒng)一打包出一個(gè)和一個(gè)。而就是基于實(shí)現(xiàn)的一套基于事件訂閱與發(fā)布的通信庫(kù)。比如說(shuō),某一個(gè)端口了,而如果端口訂閱了,那么在端,對(duì)應(yīng)的回調(diào)函數(shù)就會(huì)被執(zhí)行。

最近練手開(kāi)發(fā)了一個(gè)項(xiàng)目,是一個(gè)聊天室應(yīng)用。項(xiàng)目雖不大,但是使用到了react, react-router, redux, socket.io,后端開(kāi)發(fā)使用了koa,算是一個(gè)比較綜合性的案例,很多概念和技巧在開(kāi)發(fā)的過(guò)程中都有所涉及,非常有必要再來(lái)鞏固一下。

項(xiàng)目目前部署在heroku平臺(tái)上,在線演示地址: online demo, 因?yàn)槭菄?guó)外的平臺(tái)速度可能有點(diǎn)慢,點(diǎn)進(jìn)去耐心等一會(huì)兒就能加載好了。

加載好之后,首先出現(xiàn)的頁(yè)面是讓用戶起一個(gè)昵稱:

輸入昵稱之后,就會(huì)進(jìn)入聊天頁(yè)面,左邊是進(jìn)入聊天室的在線用戶,右邊則是聊天區(qū)域,下圖是三個(gè)在線用戶聊天的情形:

項(xiàng)目源碼的github地址: 源碼地址, 有興趣的同學(xué)歡迎關(guān)注學(xué)習(xí)~

下面就來(lái)分析一下項(xiàng)目的整體架構(gòu),以及一下值得注意的技巧和知識(shí)點(diǎn)。

1. 整體結(jié)構(gòu)

項(xiàng)目的目錄如下:

├── 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當(dāng)中是前端部分的源代碼。項(xiàng)目使用webpack進(jìn)行打包,打包后的代碼在dist目錄當(dāng)中。由于我們的項(xiàng)目是一個(gè)單頁(yè)面應(yīng)用,因此只需要統(tǒng)一打包出一個(gè)bundle.js和一個(gè)bundle.css。而后端使用了koa框架,由于代碼相對(duì)比較少,都集中在了server.js這一個(gè)文件當(dāng)中。

開(kāi)發(fā)過(guò)程中,由于要webpack打包,一般我們會(huì)配合webpack-dev-server來(lái)使用。webpack-dev-server運(yùn)行的時(shí)候自身就會(huì)開(kāi)啟一個(gè)server,而在我們的項(xiàng)目當(dāng)中,后端koa也是一個(gè)server,因此為了簡(jiǎn)單起見(jiàn),我們可以使用koa-webpack-dev-middleware來(lái)在koa當(dāng)中開(kāi)啟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. 項(xiàng)目布局: flexbox實(shí)踐

在這個(gè)項(xiàng)目中我們有意識(shí)的使用了flex布局,作為面向未來(lái)的一種新的布局方式,實(shí)踐一下還是很有必要的!沒(méi)有學(xué)習(xí)郭flexbox的同學(xué)可以參考這篇來(lái)學(xué)一下:http://www.ruanyifeng.com/blog/2015/07/flex-grammar.html

以聊天界面為例進(jìn)行分析,使用flex布局的話,可以非常方便,下圖就是對(duì)界面的一個(gè)簡(jiǎn)單的切分:

整個(gè)聊天框最外層紅框框起來(lái)的部分display設(shè)置為flex,并且flex-direction設(shè)置為column,這樣它里面的兩個(gè)元素(即粉框和藍(lán)框部分)就會(huì)豎直方向排列,同時(shí)粉框的flex設(shè)置為0 0 90px,代表該框不可伸縮,固定高度90px,而對(duì)于藍(lán)框,則設(shè)置flex為1,代表伸展系數(shù)為1,這樣,藍(lán)框的高度就會(huì)占滿除了粉框以外的全部空間。

而于此同時(shí),粉框和藍(lán)框本身又分別設(shè)置display為flex。對(duì)粉框而言,內(nèi)部一共有歡迎標(biāo)簽和退出button兩個(gè)元素,分列兩側(cè),因此只需要設(shè)置justify-content為space-between即可做到這一點(diǎn)。而對(duì)藍(lán)框而言,內(nèi)部有在線用戶列表以及聊天區(qū)域兩個(gè)元素。這里在線用戶列表(即黃色框)需要設(shè)置固定寬度,因此類似于剛才粉框的設(shè)置,flex: 0 0 240px,而聊天區(qū)域(即綠色框)則設(shè)置flex為1,這樣會(huì)自適應(yīng)占滿剩余寬度。

最后,聊天區(qū)域內(nèi)部又分為信息展示區(qū)以及打字區(qū),因此聊天區(qū)域自身又是一個(gè)flexbox,設(shè)置方式類似,就不再具體分析了。

可以看出,使用flexbox,相比使用float以及position等等而言,更加的規(guī)整,使用這種思路,整個(gè)頁(yè)面就像庖丁解牛一般,布局格外清晰。

3. 設(shè)計(jì)頁(yè)面的數(shù)據(jù)結(jié)構(gòu)

項(xiàng)目中使用了redux作為數(shù)據(jù)流管理工具,配合react,能夠讓頁(yè)面組件同頁(yè)面數(shù)據(jù)形成規(guī)律的映射。

分析我們的聊天頁(yè)面,可以看出,主要的數(shù)據(jù)就是目前在線的用戶昵稱列表,以及消息記錄,此外我們還需要記錄自己的用戶昵稱,方便消息發(fā)送時(shí)候取用。因此,整個(gè)應(yīng)用的數(shù)據(jù)結(jié)構(gòu)如下, 也就是redux中的store的數(shù)據(jù)結(jié)構(gòu)如下:

{
  "nickName": "your nickname",
  "nameList": ["user A","user B","user C","...."],
  "msgList": [
    {
    "nickName": "some user",
    "msg": "some string"
  },{
    "nickName": "another user",
    "msg": "another string"
  },
  ]
}

有了這個(gè)總體的數(shù)據(jù)結(jié)構(gòu),我們就可以根據(jù)該結(jié)構(gòu)設(shè)計(jì)具體的action,reducer等等部分了。這里整個(gè)程序的模塊拆分遵循了redux官方實(shí)例當(dāng)中的拆分方法,action文件夾當(dāng)中定義action creators,reducer文件夾中定義reducer函數(shù),component文件夾中定義一些通用的組件,container文件夾當(dāng)中則是將通用組件取出,定義store中的數(shù)據(jù)同組件如何映射,以及組件中的事件如何dispatch action,從而引起store數(shù)據(jù)的改變。

以component/namelist中的組件為例,該組件用于顯示在線用戶昵稱列表,因此它接受一個(gè)數(shù)組,也就是store中的nameList作為參數(shù),因此其通用組件的寫(xiě)法也很簡(jiǎn)單:

class NameList extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    var {nameList} = this.props;
    return (
      
  • 在線用戶:
  • {nameList.map((item, index) => (
  • {item}
  • ))}
) } } export default NameList

而在container當(dāng)中,只需要將store中的nameList賦值到該組件的props上面即可。其他組件也是類似的寫(xiě)法。

可以看出,在redux的思想下,我們可以對(duì)整個(gè)應(yīng)用抽象出一個(gè)總體的數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)結(jié)構(gòu)的改變,會(huì)引發(fā)各個(gè)組件的改變,而組件當(dāng)中的各種事件,又會(huì)反過(guò)來(lái)修改數(shù)據(jù)結(jié)構(gòu),從而再次引起頁(yè)面的改變,這是一種單向的數(shù)據(jù)流,總體的數(shù)據(jù)都在store這個(gè)對(duì)象中進(jìn)行維護(hù),從而讓整個(gè)應(yīng)用開(kāi)發(fā)變得更加有規(guī)律。redux的這種程序架構(gòu)是對(duì)react提出的flux架構(gòu)的一種消化和改良,下圖是flux架構(gòu)的示意圖:

4. socket.io的使用

由于是一個(gè)即時(shí)聊天應(yīng)用,websocket協(xié)議自然是首選。而socket.io就是基于websocket實(shí)現(xiàn)的一套基于事件訂閱與發(fā)布的js通信庫(kù)。

在socket.io中,主要有server端和client端。創(chuàng)建一個(gè)server和client都非常容易,對(duì)于server端,配合koa,只需要如下代碼:

var app=require("koa")();
var server = require("http").Server(app.callback());
var io = require("socket.io")(server);

client端更加簡(jiǎn)單:

var io=require("socket.io-client");
var socket = io();

一旦連接建立,client和server即可通過(guò)時(shí)間訂閱與發(fā)布來(lái)彼此通信,socket.io提供的api非常類似于nodejs中的event對(duì)象的使用,對(duì)于server端:

io.on("connection",function(socket){
  socket.on("some event",function(data){
    //do something here....
    socket.emit("another event",{some data here});
  });
});

對(duì)于client端,同樣通過(guò)socket.on以及socket.emit來(lái)訂閱和發(fā)布事件。比如說(shuō),某一個(gè)client端口emit了event A,而如果server端口訂閱了event A,那么在server端,對(duì)應(yīng)的回調(diào)函數(shù)就會(huì)被執(zhí)行。通過(guò)這種方式,可以方便的編寫(xiě)即時(shí)通信程序。

5. 一些值得注意的實(shí)現(xiàn)細(xì)節(jié)

下面對(duì)程序中涉及的一些我認(rèn)為值得注意的細(xì)節(jié)和技巧進(jìn)行一下簡(jiǎn)要分析。

1. socket.io同redux的結(jié)合方案:redux中間件的運(yùn)用

在程序編寫(xiě)過(guò)程當(dāng)中,我遇到一個(gè)難題,就是如何將socket.io的client實(shí)例結(jié)合到redux當(dāng)中。

socket.io的client類似于一個(gè)全局的對(duì)象,它不屬于任何一個(gè)react組件,它訂閱到的任何消息都可能更改整個(gè)應(yīng)用的數(shù)據(jù)結(jié)構(gòu),而這種更改在redux當(dāng)中又只能通過(guò)dispatch來(lái)實(shí)現(xiàn)。思考之后,我覺(jué)得編寫(xiě)一個(gè)redux中間件來(lái)處理socket.io相關(guān)的事件是一個(gè)很好的選擇。

關(guān)于redux中間件,簡(jiǎn)單來(lái)說(shuō),就是在redux真正出發(fā)dispatch之前,中間件可以首先捕獲到react組件出發(fā)的action,并針對(duì)不同action做一些處理,然后再調(diào)用dispatch。中間件的寫(xiě)法,在redux的官方文檔當(dāng)中寫(xiě)的非常詳細(xì),有興趣的可以參考一下: http://redux.js.org/docs/advanced/Middleware.html , 后續(xù)我也會(huì)出一些系列文章,深入分析redux包括react-redux的原理,其中就會(huì)提到中間件的原理,盡請(qǐng)期待~

知道了redux中間件是怎么一回事之后,我們就可以發(fā)現(xiàn),socket.io相關(guān)的事件非常適合通過(guò)寫(xiě)一個(gè)中間件來(lái)處理。我們程序當(dāng)中中間件如下所示:

import { message_update, guest_update } from "../action"

function createSocketMiddleware(socket) {
  var eventFlag = false;
  return store => next => action => {
    //如果中間件第一次被調(diào)用,則首先綁定一些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,如果是和發(fā)送相關(guān)的事件,則調(diào)用socket對(duì)應(yīng)的發(fā)布函數(shù)
    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

這段代碼是一個(gè)socket middleware的創(chuàng)建函數(shù),從中我們可以看出,這個(gè)中間件如果第一次調(diào)用的話(eventFlag),會(huì)首先綁定一些訂閱主題和對(duì)應(yīng)的回調(diào)函數(shù),主要是訂閱了消息到達(dá)、新用戶來(lái)到、用戶離開(kāi)等等事件。同時(shí),中間件會(huì)在真正dispatch函數(shù)調(diào)用之前,首先捕獲action,然后分析action的type。如果是和發(fā)送事件相關(guān)的,就會(huì)調(diào)用socket.emit來(lái)發(fā)布對(duì)應(yīng)的事件和數(shù)據(jù)。比如說(shuō),在我們的應(yīng)用中,點(diǎn)擊“發(fā)送”按鈕會(huì)觸發(fā)一個(gè)type為"MSG_UPDATE"的事件,這個(gè)事件首先被中間件捕獲,那么這時(shí)候就會(huì)出發(fā)socket.emit("msg from client")來(lái)將消息發(fā)送給server。

2. 權(quán)限驗(yàn)證: 單頁(yè)面應(yīng)用中的頁(yè)面跳轉(zhuǎn)

整個(gè)應(yīng)用使用react-router,做成了一個(gè)單頁(yè)面應(yīng)用,其中前端路由的層級(jí)非常簡(jiǎn)單:

render(
  
    
      
      
    
  
  ,
  document.getElementById("test"));

可以看出,主要是兩條路徑: "/"和"/login",其中"/"是我們的聊天界面,而"/login"則是起昵稱界面。

由于應(yīng)用的邏輯是,只有用戶起了昵稱才可以進(jìn)入聊天界面,因此我們需要做一些權(quán)限驗(yàn)證,對(duì)于沒(méi)有起昵稱就進(jìn)入"/"路徑的用戶,需要跳轉(zhuǎn)到"/login"。在傳統(tǒng)多頁(yè)面web應(yīng)用中,我們對(duì)于跳轉(zhuǎn)非常熟悉,無(wú)非是服務(wù)器發(fā)送一個(gè)重定向請(qǐng)求,瀏覽器就會(huì)重定向到新的頁(yè)面。然而在單頁(yè)面中,由于始終只有一頁(yè),服務(wù)器又能夠讓瀏覽器跳轉(zhuǎn)到哪里去呢?也就是說(shuō),服務(wù)器重定向的方法是行不通的。

因此,我們換一種思路,頁(yè)面跳轉(zhuǎn)的邏輯需要在瀏覽器端執(zhí)行,在react-router的框架下,執(zhí)行跳轉(zhuǎn)也非常簡(jiǎn)單,只需要使用其中的hashHistory對(duì)象,通過(guò)hashHistory.push("path"),即可讓?xiě)?yīng)用跳轉(zhuǎn)到指定路徑對(duì)應(yīng)的界面。有了這個(gè)認(rèn)知,那么我們下面要解決的,就是何時(shí)控制單頁(yè)面的跳轉(zhuǎn)?

我的思路是,將用戶的昵稱通過(guò)一定的加密和編碼,保存在cookie當(dāng)中。當(dāng)用戶訪問(wèn)"/"的時(shí)候,在對(duì)于界面的組件掛載之前,首先會(huì)向服務(wù)器發(fā)送一個(gè)認(rèn)證請(qǐng)求,服務(wù)器會(huì)從請(qǐng)求中讀取cookie,如果cookie當(dāng)中沒(méi)有用戶名存在,那么服務(wù)器返回的參數(shù)當(dāng)中有一個(gè)"permit"字段,設(shè)置為false,當(dāng)應(yīng)用解析到該字段后,就會(huì)調(diào)用hashHistory.push("/login")來(lái)讓頁(yè)面跳轉(zhuǎn)到起昵稱界面下。這部分對(duì)應(yīng)的邏輯主要在container/chatAll.js文件當(dāng)中實(shí)現(xiàn)。

3. 文本輸入的細(xì)節(jié)處理: xss的預(yù)防,以及組合鍵的識(shí)別

在我們的聊天應(yīng)用中,如果不對(duì)用戶的輸入進(jìn)行一些處理,就有可能導(dǎo)致xss漏洞。舉個(gè)例子,比如說(shuō)有一個(gè)用戶輸入了"",如果不進(jìn)行一些防范,輸入到消息顯示界面,這段文字就直接被解析成為了一段js代碼。為了防范這類攻擊,這里我們需要做一些簡(jiǎn)單的預(yù)防:

var regLeft = //g;
value = value.replace(regLeft, "<");
value = value.replace(regRight, ">");

這段代碼在components/typein組件當(dāng)中。

此外,為了方便用戶快速發(fā)送消息,在消息輸入框中,我們?cè)O(shè)置了"enter"按鍵為之間發(fā)送按鍵。那么,為了讓用戶能夠打出換行,我們模仿微信,約定用戶輸入ctrl+enter組合鍵的時(shí)候是換行,這樣,在消息輸入框中,就需要監(jiān)聽(tīng)組合鍵。在js的鍵盤(pán)事件中,event對(duì)象有一個(gè)ctrlKey屬性,用于判斷ctrl按鍵是否按下:

someDom.onkeydown=function(e){
  if(e.keyCode==13&&e.ctrlKey){
    //組合鍵被按下
  }
}

這就是組合鍵監(jiān)聽(tīng)的原理。

以上就是對(duì)于這個(gè)項(xiàng)目的概述以及一些細(xì)節(jié)的講解。最后安利一下我的博客 http://mly-zju.github.io/,會(huì)不定期更新我的原創(chuàng)技術(shù)文章和學(xué)習(xí)感悟,歡迎大家關(guān)注~

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/81934.html

相關(guān)文章

  • node技術(shù)棧 - 收藏集 - 掘金

    摘要:異步最佳實(shí)踐避免回調(diào)地獄前端掘金本文涵蓋了處理異步操作的一些工具和技術(shù)和異步函數(shù)。 Nodejs 連接各種數(shù)據(jù)庫(kù)集合例子 - 后端 - 掘金Cassandra Module: cassandra-driver Installation ... 編寫(xiě) Node.js Rest API 的 10 個(gè)最佳實(shí)踐 - 前端 - 掘金全文共 6953 字,讀完需 8 分鐘,速讀需 2 分鐘。翻譯自...

    王偉廷 評(píng)論0 收藏0
  • koa-cola:只需一個(gè)react組件,同時(shí)支持單頁(yè)應(yīng)用(SPA)和服務(wù)器渲染(SSR)

    摘要:是一個(gè)基于和的服務(wù)器端和瀏覽器端的的前后端全棧應(yīng)用框架。是的組件,并且會(huì)進(jìn)行數(shù)據(jù)初始化不但可以支持的數(shù)據(jù)初始化,還可以合并和的,使用同一個(gè),和的無(wú)縫結(jié)合。 koa-cola是一個(gè)基于koa和react的服務(wù)器端SSR(server side render)和瀏覽器端的SPA(single page application)的web前后端全棧應(yīng)用框架。 koa-cola使用typescr...

    XGBCCC 評(píng)論0 收藏0
  • 一些基于React、Vue、Node.js、MongoDB技術(shù)棧的實(shí)踐項(xiàng)目

    摘要:利用中間件實(shí)現(xiàn)異步請(qǐng)求,實(shí)現(xiàn)兩個(gè)用戶角色實(shí)時(shí)通信。目前還未深入了解的一些概念。往后會(huì)寫(xiě)更多的前后臺(tái)聯(lián)通的項(xiàng)目。刪除分組會(huì)連同組內(nèi)的所有圖片一起刪除。算是對(duì)自己上次用寫(xiě)后臺(tái)的一個(gè)強(qiáng)化,項(xiàng)目文章在這里。后來(lái)一直沒(méi)動(dòng),前些日子才把后續(xù)的完善。 歡迎訪問(wèn)我的個(gè)人網(wǎng)站:http://www.neroht.com/? 剛學(xué)vue和react時(shí),利用業(yè)余時(shí)間寫(xiě)的關(guān)于這兩個(gè)框架的訓(xùn)練,都相對(duì)簡(jiǎn)單,有的...

    tangr206 評(píng)論0 收藏0
  • 前端文檔收集

    摘要:系列種優(yōu)化頁(yè)面加載速度的方法隨筆分類中個(gè)最重要的技術(shù)點(diǎn)常用整理網(wǎng)頁(yè)性能管理詳解離線緩存簡(jiǎn)介系列編寫(xiě)高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問(wèn)性能優(yōu)化方案實(shí)現(xiàn)的大排序算法一怪對(duì)象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁(yè)面加載速度的方法 隨筆分類 - HTML5 HTML5中40個(gè)最重要的技術(shù)點(diǎn) 常用meta整理 網(wǎng)頁(yè)性能管理詳解 HTML5 ...

    jsbintask 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<