咱們要構建的聊天應用程序將具備如下功能:css
在咱們開始時,作一些假設:html
好的,咱們但願讓咱們的開發環境準備好開始編寫代碼。 首先,React須要Node和npm。前端
讓咱們從終端啓動一個新項目:react
npx create-react-app socket-client
cd socket-client
npm start
複製代碼
如今咱們應該可以導航到 http://localhost:3000, 在瀏覽器中獲取項目的默認歡迎頁面。git
從這裏開始,咱們將經過咱們正在使用的鉤子打破工做。 這應該有助於咱們理解鉤子,由於咱們將它們付諸實踐。github
咱們要使用的第一個鉤子是 useState 。 它容許咱們在組件中維護狀態,而不是使用編寫和初始化 this.state 。 保持不變的數據(例如用戶名)存儲在 useState 變量。 這可確保數據在須要大量數據時仍然易使用更少的代碼來寫。web
useState 的主要優勢是每當咱們更新應用程序的狀態時,它會自動反映在呈現的組件中。 若是咱們使用常規變量,它們將不被視爲組件的狀態,而且必須做爲props傳遞以從新呈現組件。 所以,咱們再次削減了大量工做並簡化了流程中的工做。npm
鉤子內置於React中,所以咱們能夠用一行導入它:數組
import React, { useState } from 'react';
複製代碼
咱們將建立一個簡單的組件,若是用戶已經登陸則返回「Hello」,若是用戶已註銷,則返回登陸表單。 咱們檢查一下id變量。瀏覽器
咱們的表單提交將由咱們建立的處理函數handleSubmit 。 它將檢查名稱表單字段是否完成。 若是是,咱們將設置ID和room爲該用戶的值。 不然,咱們將拋出一條消息,提醒用戶須要名稱字段才能繼續。
// App.js
import React, { useState } from 'react';
import './index.css';
export default () => {
const [room, setRoom] = useState('');
const [id, setId] = useState('');
const handleSubmit = e => {
e.preventDefault();
const name = document.querySelector('#name').value.trim();
const room_value = document.querySelector('#room').value.trim();
if (!name) {
return alert("Name can't be empty");
}
setId(name);
setRoom(document.querySelector('#room').value.trim());
};
return id !== '' ? (
<div>Hello</div>
) : (
<div style={{ textAlign: 'center', margin: '30vh auto', width: '70%' }}>
<form onSubmit={event => handleSubmit(event)}>
<input id="name" required placeholder="What is your name .." /><br />
<input id="room" placeholder="What is your room .." /><br />
<button type="submit">Submit</button>
</form>
</div>
);
};
複製代碼
這就是咱們如何使用 useState 掛鉤咱們的聊天應用程序。 一樣,咱們從React導入鉤子,爲用戶的ID和聊天室位置構建值,在用戶的狀態登陸時設置這些值,並在用戶註銷時返回登陸表單。
使用useSocket鉤子
咱們將使用一個名爲的開源鉤子 useSocket 保持與咱們的服務器的鏈接。 不像 useState ,這個鉤子沒有被 綁定 到React中,因此咱們必須在將它導入應用程序以前將它添加到咱們的項目中。
npm add use-socket.io-client
複製代碼
使用React Hooks版本維護服務器鏈接 socket.io ,是一種維護與服務器的websocket鏈接的簡單方法。 咱們使用它來發送和接收實時消息以及維護事件,例如鏈接到房間。
默認 socket.io 客戶端庫具備全局聲明,即咱們定義的套接字變量能夠被任何組件使用。 可是,咱們的數據能夠在任何地方進行操做,咱們也不知道這些變化發生在哪裏。 套接字鉤子經過在組件級別約束鉤子定義來對此進行抵消,這意味着每一個組件都負責其本身的數據傳輸。
useSocket 的基本用法看起來像這樣:
const [socket] = useSocket('socket-url')
複製代碼
咱們將使用幾個socket API。 爲了便於參考,全部這些都在文檔 socket.io 。 可是如今,咱們已經安裝了它,所以咱們將導入鉤子。
import useSocket from 'use-socket.io-client';
複製代碼
接下來,咱們必須經過鏈接到咱們的服務器來初始化鉤子。 而後咱們將在控制檯中記錄套接字以檢查它是否正確鏈接。
const [id, setId] = useState('');
const [socket] = useSocket('<https://open-chat-naostsaecf.now.sh>');
socket.connect();
console.log(socket);
複製代碼
打開瀏覽器控制檯,能夠看記錄在該代碼段的網址。
咱們的聊天應用程序將使用 useImmer 鉤子來管理數組和對象的狀態而不改變原始狀態。
它結合了 useState 和Immer 給予不可變的狀態管理。
這對於管理在線人員列表和須要顯示的消息很是方便。
使用帶有useState的Immer容許咱們經過從當前狀態建立新狀態來更改數組或對象,同時防止直接在當前狀態上發生突變。 這使得咱們更安全,只要保持當前狀態完整,同時可以根據不一樣條件操縱狀態。
一樣,咱們正在使用一個沒有內置到React中的鉤子,因此讓咱們將它導入到項目中:
npm add use-immer
複製代碼
基本用法很是簡單。 構造函數中的第一個值是當前狀態,第二個值是更新該狀態的函數。 該useImmer hook獲取當前狀態的起始值。
const [data, setData] = useImmer(default_value)
複製代碼
請注意 使用setData 在最後一個例子中掛鉤? 咱們正在使用它來製做當前數據的草稿副本,咱們可使用它來安全地操做數據,並在更改變爲不可變時將其用做下一個狀態。 所以,咱們的原始數據將被保留,直到咱們完成運行咱們的功能,而且咱們徹底清楚更新當前數據。
setData(draftState => {
draftState.operation();
});
// ...or
setData(draft => newState);
複製代碼
//這裏,draftState是當前數據的副本
好吧,咱們會用到一個內置於React的鉤子。 咱們打算用 useEffect 鉤子只在應用程序加載時運行一段代碼。 這樣能夠確保咱們的代碼只運行一次,而不是每次組件使用新數據從新渲染時,這對性能有利。
咱們須要作的就是開始使用鉤子來導入它 - 無需安裝!
import React, { useState, useEffect } from 'react';
複製代碼
咱們須要一個渲染a的組件 message 或者 update 基因而否在數組中存在 sende ID 。 做爲咱們的創造性人才,讓咱們稱之爲消息組件 。
const Messages = props => props.data.map(m => m[0] !== '' ?
(<li key={m[0]}><strong>{m[0]}</strong> : <div className="innermsg">{m[1]}</div></li>)
: (<li key={m[1]} className="update">{m[1]}</li>) );
複製代碼
讓咱們把套接字邏輯放在useEffect裏面,這樣,當組件從新渲染時,咱們不會重複複製同一組消息。 咱們將在組件中定義消息掛鉤,鏈接到套接字,而後爲新消息和更新設置監聽器 useEffect 鉤住本身。 咱們還將在偵聽器中設置更新函數。
const [socket] = useSocket('<https://open-chat-naostsaecf.now.sh>');
socket.connect();
const [messages, setMessages] = useImmer([]);
useEffect(()=>{
socket.on('update', message => setMessages(draft => {
draft.push(['', message]);
}));
socket.on('message que',(nick, message) => {
setMessages(draft => {
draft.push([nick, message])
})
});
},0);
複製代碼
若是用戶名和房間名稱正確,咱們將提供的另外一個是「加入」消息。 這將觸發其他的事件偵聽器,咱們能夠接收該房間中發送的過去消息以及所需的任何更新。
// ...
setRoom(document.querySelector('#room').value.trim());
socket.emit('join', name, room);
};
return id ? (
<section style={{display:'flex',flexDirection:'row'}} >
<ul id="messages"><Messages data={messages}></Messages></ul>
<ul id="online"> 🌐 :</ul>
<div id="sendform">
<form id="messageform" style={{display: 'flex'}}>
<input id="m" /><button type="submit">Send Message</button>
</form>
</div>
</section>
) : (
// ...
複製代碼
咱們只須要進行一些調整便可完成咱們的聊天應用。 具體來講,咱們還須要:
全部這些都創建在咱們迄今爲止已經涵蓋的範圍以外。 我要展現完整的代碼 App.js 文件,以顯示一切如何融合在一塊兒。
// App.js
import React, { useState, useEffect } from 'react';
import useSocket from 'use-socket.io-client';
import { useImmer } from 'use-immer';
import './index.css';
const Messages = props => props.data.map(m => m[0] !== '' ? (<li><strong>{m[0]}</strong> : <div className="innermsg">{m[1]}</div></li>) : (<li className="update">{m[1]}</li>) );
const Online = props => props.data.map(m => <li id={m[0]}>{m[1]}</li>);
export default () => {
const [room, setRoom] = useState('');
const [id, setId] = useState('');
const [socket] = useSocket('<https://open-chat-naostsaecf.now.sh>');
socket.connect();
const [messages, setMessages] = useImmer([]);
const [online, setOnline] = useImmer([]);
useEffect(()=>{
socket.on('message que',(nick,message) => {
setMessages(draft => {
draft.push([nick,message])
})
});
socket.on('update',message => setMessages(draft => {
draft.push(['',message]);
}))
socket.on('people-list',people => {
let newState = [];
for(let person in people){ newState.push([people[person].id,people[person].nick]); } setOnline(draft=>{draft.push(...newState)}); console.log(online) }); socket.on('add-person',(nick,id)=>{
setOnline(draft => {
draft.push([id,nick])
})
})
socket.on('remove-person',id=>{
setOnline(draft => draft.filter(m => m[0] !== id))
})
socket.on('chat message',(nick,message)=>{
setMessages(draft => {draft.push([nick,message])})
})
},0);
const handleSubmit = e => {
e.preventDefault();
const name = document.querySelector('#name').value.trim();
const room_value = document.querySelector('#room').value.trim();
if (!name) {
return alert("Name can't be empty");
}
setId(name);
setRoom(document.querySelector('#room').value.trim());
console.log(room)
socket.emit("join", name,room_value);
};
const handleSend = e => {
e.preventDefault();
const input = document.querySelector('#m');
if(input.value.trim() !== ''){
socket.emit('chat message',input.value,room);
input.value = '';
}
}
return id ? (
<section style={{display:'flex',flexDirection:'row'}} >
<ul id="messages"><Messages data={messages} /></ul>
<ul id="online"> 🌐 : <Online data={online} /> </ul>
<div id="sendform">
<form onSubmit={e => handleSend(e)} style={{display: 'flex'}}>
<input id="m" /><button style={{width:'75px'}} type="submit">Send</button>
</form>
</div>
</section>
) : (
<div style={{ textAlign: 'center', margin: '30vh auto', width: '70%' }}>
<form onSubmit={event => handleSubmit(event)}>
<input id="name" required placeholder="What is your name .." /><br />
<input id="room" placeholder="What is your room .." /><br />
<button type="submit">Submit</button>
</form>
</div>
);
};
複製代碼
咱們一塊兒構建了功能齊全的羣聊應用程序! 項目的完整代碼能夠是 found here 在GitHub上。
咱們在本文中介紹的僅僅是React Hooks如何經過強大的前端工具提升你的工做效率並幫助你構建功能強大的應用程序的一瞥。 我已經構建了一個更強大的聊天應用程序 這個全面的教程 。 若是你想進一步升級React Hooks,請繼續。
如今你已經擁有React Hooks的實踐經驗,使用你新得到的知識來得到更多練習! 如下是你能夠從這裏構建的一些想法:
一路上有疑問嗎? 發表評論,讓咱們一塊兒製做精彩的東西。
原文地址: