[译]使用100行代码创建react Hooks聊天室

让我们看看要求
我们要构建的聊天应用程序将具有以下功能:
- 获取从服务器发送的过去消息的列表
- 连接到一个房间进行小组聊天
- 当人们断开连接或连接房间时获取更新
- 发送和接收消息
在我们开始时,做一些假设:
- 我们会考虑服务器用作黑盒子。 不要担心它的是否完美工作,因为我们将使用简单的套接字与它进行通信。
- 所有样式都包含在一个CSS文件中,可以复制到 src 目录。 应用程序中使用的所有样式都是 在存储库中链接 。
开始工作
好的,我们希望让我们的开发环境准备好开始编写代码。 首先,React需要Node和npm。
让我们从终端启动一个新项目:
npx create-react-app socket-client
cd socket-client
npm start
现在我们应该能够导航到 http://localhost:3000, 在浏览器中获取项目的默认欢迎页面。
从这里开始,我们将通过我们正在使用的钩子打破工作。 这应该有助于我们理解钩子,因为我们将它们付诸实践。
使用setState钩子
我们要使用的第一个钩子是 useState 。 它允许我们在组件中维护状态,而不是使用编写和初始化 this.state 。 保持不变的数据(例如用户名)存储在 useState 变量。 这可确保数据在需要大量数据时仍然易使用更少的代码来写。
useState 的主要优点是每当我们更新应用程序的状态时,它会自动反映在呈现的组件中。 如果我们使用常规变量,它们将不被视为组件的状态,并且必须作为props传递以重新呈现组件。 因此,我们再次削减了大量工作并简化了流程中的工作。
钩子内置于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钩子
我们的聊天应用程序将使用 useImmer 钩子来管理数组和对象的状态而不改变原始状态。
它结合了 useState 和Immer 给予不可变的状态管理。
这对于管理在线人员列表和需要显示的消息非常方便。
使用带有useState的Immer允许我们通过从当前状态创建新状态来更改数组或对象,同时防止直接在当前状态上发生突变。 这使得我们更安全,只要保持当前状态完整,同时能够根据不同条件操纵状态。
同样,我们正在使用一个没有内置到React中的钩子,所以让我们将它导入到项目中:
npm add use-immer
基本用法非常简单。 构造函数中的第一个值是当前状态,第二个值是更新该状态的函数。 该useImmer hook获取当前状态的起始值。
const [data, setData] = useImmer(default_value)
使用setData钩子
请注意 使用setData 在最后一个例子中挂钩? 我们正在使用它来制作当前数据的草稿副本,我们可以使用它来安全地操作数据,并在更改变为不可变时将其用作下一个状态。 因此,我们的原始数据将被保留,直到我们完成运行我们的功能,并且我们完全清楚更新当前数据。
setData(draftState => {
draftState.operation();
});
// ...or
setData(draft => newState);
//这里,draftState是当前数据的副本
使用useEffect钩子
好吧,我们会用到一个内置于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>
) : (
// ...
收尾完成
我们只需要进行一些调整即可完成我们的聊天应用。 具体来说,我们还需要:
- 显示在线人员的组件
- 一个 useImmer 用套接字监听器挂钩
- 具有适当套接字的消息提交处理程序
所有这些都建立在我们迄今为止已经涵盖的范围之外。 我要展示完整的代码 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的实践经验,使用你新获得的知识来获得更多练习! 以下是你可以从这里构建的一些想法:
- 一个博客平台
- 你自己的Instagram版本
- Reddit的克隆
一路上有疑问吗? 发表评论,让我们一起制作精彩的东西。
原文地址: