良い一日、友達! Socket.IO
ライブラリを使用してReactで簡単なチャットを開発した私の経験をあなたと共有したいと思います 。 名前付きライブラリに精通していることを前提としています。慣れていない場合は、バニラJavaScriptでtudushkaとチャットを作成する例を含む 関連ガイドをご覧ください。 また、少なくとも表面的にはNode.jsに精通していることを前提としています 。 この記事では、Socket.IO、React、およびNode.jsの使用の実用性に焦点を当てます。 チャットには次の主な機能があります。
- 部屋の選択
- メッセージの送信
- 送信者によるメッセージの削除
- JSON形式でローカルデータベースにメッセージを保存する
- ブラウザのローカルストレージにユーザー名とIDを保存する
- アクティブユーザー数の表示
- オンラインインジケーターでユーザーのリストを表示する
絵文字を送信する機能も実装します 。
興味のある方はフォローしてください。
コードだけに興味がある人のために: ここにリポジトリへのリンクがあります。
サンドボックス:
プロジェクトの構造と依存関係
プロジェクトの作成を始めましょう:
mkdir react-chat
cd react-chat
Create React Appを使用してクライアントを作成し ます:
yarn create react-app client
#
npm init react-app client
#
npx create-react-app client
将来的には、yarn: を使用して依存関係をインストールし
yarn add = npm i, yarn start = npm start, yarn dev = npm run dev
ます。
「client」ディレクトリに移動し、追加の依存関係をインストールします。
cd client
yarn add socket.io-client react-router-dom styled-components bootstrap react-bootstrap react-icons emoji-mart react-timeago
- socket.io-client-Socket.IOクライアント側
- react-router-dom-ルーティング
- styled-components-スタイリング(CSS-in-JS)
- bootstrap、react-bootstrap-スタイリング
- react-icons-アイコン
- emoji-mart-絵文字
- react-timeago-日付と時刻のフォーマット
「package.json」ファイルの「dependencies」セクション:
{
"bootstrap": "^4.6.0",
"emoji-mart": "^3.0.0",
"react": "^17.0.1",
"react-bootstrap": "^1.5.0",
"react-dom": "^17.0.1",
"react-icons": "^4.2.0",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.1",
"react-timeago": "^5.2.0",
"socket.io-client": "^3.1.0",
"styled-components": "^5.2.1"
}
ルートディレクトリ(react-chat)に戻り、「サーバー」ディレクトリを作成して移動し、プロジェクトを初期化して、依存関係をインストールします。
cd ..
mkdir server
cd server
yarn init -yp
yarn add socket.io lowdb supervisor
- socket.io-Socket.IOバックエンド
- lowdb -JSON形式のローカルデータベース
- スーパーバイザー-開発サーバー(nodemonの代替。Node.jsの最新の安定バージョンでは正しく機能しません。子プロセスの誤った開始/停止と関係があります)
「start」コマンドを追加して本番サーバーを起動し、「dev」コマンドを追加して開発サーバーを起動します。package.json:
{
"name": "server",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"private": true,
"dependencies": {
"lowdb": "^1.0.0",
"socket.io": "^3.1.0",
"supervisor": "^0.12.0"
},
"scripts": {
"start": "node index.js",
"dev": "supervisor index.js"
}
}
ルートディレクトリ(react-chat)に戻り、プロジェクトを初期化して、依存関係をインストールします。
cd .. yarn init -yp yarn add nanoid concurrently
react-chat / package.json(npmのコマンドは異なって見えることに注意してください。同時にドキュメントを参照してください):
{
"name": "react-chat",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"private": true,
"dependencies": {
"concurrently": "^6.0.0",
"nanoid": "^3.1.20"
},
"scripts": {
"server": "yarn --cwd server dev",
"client": "yarn --cwd client start",
"start": "concurrently \"yarn server\" \"yarn client\""
}
}
これで、プロジェクトの主要な構造の形成と、必要な依存関係のインストールが完了しました。サーバーの実装を始めましょう。
サーバーの実装
「サーバー」ディレクトリの構造:
|--server |--db - |--handlers |--messageHandlers.js |--userHandlers.js |--index.js ...
「index.js」ファイルでは、次のことを行います。
- HTTPサーバーの構築
- Socket.IOを接続します
- ポート5000でサーバーを起動します
- ソケット接続時のイベントハンドラーの登録
index.js:
// HTTP-
const server = require('http').createServer()
// Socket.IO
const io = require('socket.io')(server, {
cors: {
origin: '*'
}
})
const log = console.log
//
const registerMessageHandlers = require('./handlers/messageHandlers')
const registerUserHandlers = require('./handlers/userHandlers')
// (, = )
const onConnection = (socket) => {
//
log('User connected')
// ""
const { roomId } = socket.handshake.query
//
socket.roomId = roomId
// ( )
socket.join(roomId)
//
//
registerMessageHandlers(io, socket)
registerUserHandlers(io, socket)
// -
socket.on('disconnect', () => {
//
log('User disconnected')
//
socket.leave(roomId)
})
}
//
io.on('connection', onConnection)
//
const PORT = process.env.PORT || 5000
server.listen(PORT, () => {
console.log(`Server ready. Port: ${PORT}`)
})
ファイル「handlers / messageHandlers.js」では、次のことを行います。
- lowdbを使用してJSON形式でローカルデータベースを設定する
- データベースに初期データを書き込みます
- メッセージの受信、追加、削除のための関数の作成
- 対応するイベントの処理を登録します。
- メッセージ:get-メッセージを受信する
- メッセージ:追加-メッセージを追加します
- メッセージ:削除-メッセージを削除します
メッセージは、次のプロパティを持つオブジェクトです。
- messageId(文字列)-メッセージ識別子
- userId(文字列)-ユーザーID
- senderName(文字列)-送信者名
- messageText(文字列)-メッセージテキスト
- createdAt(日付)-作成日
ハンドラー/messageHandlers.js:
const { nanoid } = require('nanoid')
//
const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')
// "db" "messages.json"
const adapter = new FileSync('db/messages.json')
const db = low(adapter)
//
db.defaults({
messages: [
{
messageId: '1',
userId: '1',
senderName: 'Bob',
messageText: 'What are you doing here?',
createdAt: '2021-01-14'
},
{
messageId: '2',
userId: '2',
senderName: 'Alice',
messageText: 'Go back to work!',
createdAt: '2021-02-15'
}
]
}).write()
module.exports = (io, socket) => {
//
const getMessages = () => {
//
const messages = db.get('messages').value()
// ,
// - , ,
io.in(socket.roomId).emit('messages', messages)
}
//
//
const addMessage = (message) => {
db.get('messages')
.push({
// nanoid, 8 - id
messageId: nanoid(8),
createdAt: new Date(),
...message
})
.write()
//
getMessages()
}
//
// id
const removeMessage = (messageId) => {
db.get('messages').remove({ messageId }).write()
getMessages()
}
//
socket.on('message:get', getMessages)
socket.on('message:add', addMessage)
socket.on('message:remove', removeMessage)
}
ファイル「handlers / userHandlers.js」では、次のことを行います。
- ユーザーとの正規化された構造を作成する
- ユーザーを取得、追加、削除するための関数を作成します
- 対応するイベントの処理を登録します。
- ユーザー:取得-ユーザーを取得
- user:add-ユーザーを追加します
- user:leave-ユーザーを削除します
lowdbを使用して、ユーザーのリストを操作することもできます。必要に応じてこれを行うことができます。私はあなたの許可を得て、自分自身をオブジェクトに限定します。
ユーザーの正規化された構造(オブジェクト)の形式は次のとおりです。
{ id (string) - : { username (string) - , online (boolean) - } }
実際、ユーザーを削除するのではなく、ステータスをオフラインに転送します(「online」プロパティを「false」に割り当てます)。
ハンドラー/userHandlers.js:
//
//
const users = {
1: { username: 'Alice', online: false },
2: { username: 'Bob', online: false }
}
module.exports = (io, socket) => {
//
// "roomId" ,
// ,
//
const getUsers = () => {
io.in(socket.roomId).emit('users', users)
}
//
// id
const addUser = ({ username, userId }) => {
// ,
if (!users[userId]) {
// ,
users[userId] = { username, online: true }
} else {
// ,
users[userId].online = true
}
//
getUsers()
}
//
const removeUser = (userId) => {
// ,
// (O(1))
// ()
// redux, , immer,
users[userId].online = false
getUsers()
}
//
socket.on('user:get', getUsers)
socket.on('user:add', addUser)
socket.on('user:leave', removeUser)
}
サーバーを起動して、パフォーマンスを確認します。
yarn dev
「サーバーの準備ができました。ポート:5000 "、および初期データを含むファイル" messages.json "が" db "ディレクトリに表示されました。これは、サーバーが期待どおりに機能していることを意味し、クライアント部分の実装に進むことができます。
クライアントの実装
クライアントの場合、すべてがやや複雑になります。「クライアント」ディレクトリの構造:
|--client |--public |--index.html |--src |--components |--ChatRoom |--MessageForm |--MessageForm.js |--package.json |--MessageList |--MessageList.js |--MessageListItem.js |--package.json |--UserList |--UserList.js |--package.json |--ChatRoom.js |--package.json |--Home |--Home.js |--package.json |--index.js |--hooks |--useBeforeUnload.js |--useChat.js |--useLocalStorage.js App.js index.js |--jsconfig.json ( src) ...
名前が示すように、「components」ディレクトリにはアプリケーションコンポーネント(ユーザーインターフェイスの一部、モジュール)が含まれ、「hooks」ディレクトリにはユーザー(「custom」)フックが含まれ、そのメインはuseChat()です。
コンポーネントディレクトリ内のファイル「package.json」には、JSファイルへのパスの値を含む単一のフィールド「main」があります。次に例を示します。
{
"main": "./Home"
}
これにより、ファイル名を指定せずにディレクトリからコンポーネントをインポートできます。次に例を示します。
import { Home } from './Home'
//
import { Home } from './Home/Home'
「components / index.js」ファイルと「hooks / index.js」ファイルは、それぞれコンポーネントとフックを集約および再エクスポートするために使用されます。
コンポーネント/index.js:
export { Home } from './Home'
export { ChatRoom } from './ChatRoom'
フック/index.js:
export { useChat } from './useChat'
export { useLocalStorage } from './useLocalStorage'
export { useBeforeUnload } from './useBeforeUnload'
これにより、コンポーネントとフックをディレクトリごとに同時にインポートできます。集約と再エクスポートにより、名前付きコンポーネントのエクスポートが使用されます(Reactのドキュメントではデフォルトのエクスポートを使用することを推奨しています)。
jsconfig.jsonファイルは次のようになります。
{
"compilerOptions": {
"baseUrl": "src"
}
}
これは、モジュールのインポートが「src」ディレクトリから開始されることをコンパイラに「通知」するため、たとえば、コンポーネントは次のようにインポートできます。
//
import { Home, ChatRoom } from 'components'
//
import { Home, ChatRoom } from './components'
カスタムフックを見てみましょう。
既製のソリューションを使用できます。たとえば、react-useライブラリによって提供されるフックは 次のとおりです。
#
yarn add react-use
#
import { useLocalStorage } from 'react-use'
import { useBeforeUnload } from 'react-use'
useLocalStorage()フックを使用すると、ブラウザのローカルストレージに値を保存(書き込みおよび取得)できます。これを使用して、ブラウザセッション間でユーザー名とユーザーIDを保存します。ユーザーに毎回名前を入力させたくはありませんが、このユーザーに属するメッセージを判別するにはIDが必要です。フックは、キーの名前と、オプションで初期値を取ります。
フック/useLocalstorage.js:
import { useState, useEffect } from 'react'
export const useLocalStorage = (key, initialValue) => {
const [value, setValue] = useState(() => {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
})
useEffect(() => {
const item = JSON.stringify(value)
window.localStorage.setItem(key, item)
// , key, useEffect, ,
// useEffect
// eslint-disable-next-line
}, [value])
return [value, setValue]
}
「useBeforeUnload()」フックは、ページ(ブラウザタブ)がリロードまたは閉じられたときにメッセージを表示したり、関数を実行したりするために使用されます。これを使用して「user:leave」イベントをサーバーに送信し、ユーザーのステータスを切り替えます。「useEffect()」フックによって返されたコールバックを使用して、指定されたイベントのディスパッチを実装する試みは失敗しました。フックは、プリミティブまたは関数の1つのパラメーターを取ります。
フック/useBeforeUnload.js:
import { useEffect } from 'react'
export const useBeforeUnload = (value) => {
const handleBeforeunload = (e) => {
let returnValue
if (typeof value === 'function') {
returnValue = value(e)
} else {
returnValue = value
}
if (returnValue) {
e.preventDefault()
e.returnValue = returnValue
}
return returnValue
}
useEffect(() => {
window.addEventListener('beforeunload', handleBeforeunload)
return () => window.removeEventListener('beforeunload', handleBeforeunload)
// eslint-disable-next-line
}, [])
}
useChat()フックは、アプリケーションのメインフックです。1行ずつコメントすると簡単になります。
フック/useChat.js:
import { useEffect, useRef, useState } from 'react'
// IO
import io from 'socket.io-client'
import { nanoid } from 'nanoid'
//
import { useLocalStorage, useBeforeUnload } from 'hooks'
//
// -
const SERVER_URL = 'http://localhost:5000'
//
export const useChat = (roomId) => {
//
const [users, setUsers] = useState([])
//
const [messages, setMessages] = useState([])
//
const [userId] = useLocalStorage('userId', nanoid(8))
//
const [username] = useLocalStorage('username')
// useRef() DOM-,
//
const socketRef = useRef(null)
useEffect(() => {
// ,
// ""
// socket.handshake.query.roomId
socketRef.current = io(SERVER_URL, {
query: { roomId }
})
// ,
// id
socketRef.current.emit('user:add', { username, userId })
//
socketRef.current.on('users', (users) => {
//
setUsers(users)
})
//
socketRef.current.emit('message:get')
//
socketRef.current.on('messages', (messages) => {
// , ,
// "userId" id ,
// "currentUser" "true",
// ,
const newMessages = messages.map((msg) =>
msg.userId === userId ? { ...msg, currentUser: true } : msg
)
//
setMessages(newMessages)
})
return () => {
//
socketRef.current.disconnect()
}
}, [roomId, userId, username])
//
//
const sendMessage = ({ messageText, senderName }) => {
// id
socketRef.current.emit('message:add', {
userId,
messageText,
senderName
})
}
// id
const removeMessage = (id) => {
socketRef.current.emit('message:remove', id)
}
// "user:leave"
useBeforeUnload(() => {
socketRef.current.emit('user:leave', userId)
})
// ,
return { users, messages, sendMessage, removeMessage }
}
デフォルトでは、すべてのクライアント要求はlocalhost:3000(開発サーバーが実行されているポート)に送信されます。 「サーバー」サーバーが実行されているポートに要求をリダイレクトするには、プロキシを実行する必要があります。これを行うには、「src /package.json」ファイルに次の行を追加します。
"proxy": "http://localhost:5000"
アプリケーションコンポーネントを実装することは残っています。
ホームコンポーネントは、ユーザーがアプリケーションを起動したときに最初に表示されるものです。これには、ユーザーが自分の名前を入力して部屋を選択するように求められるフォームが含まれています。実際には、部屋の場合、ユーザーは選択の余地がなく、1つのオプション(無料)しか利用できません。 2番目の(無効な)オプション(ジョブ)は、アプリケーションをスケーリングする機能です。チャットを開始するためのボタンの表示は、ユーザー名のフィールドによって異なります(このフィールドが空の場合、ボタンは表示されません)。ボタンは実際にはチャットページへのリンクです。
コンポーネント/Home.js:
import { useState, useRef } from 'react'
// react-router-dom
import { Link } from 'react-router-dom'
//
import { useLocalStorage } from 'hooks'
// react-bootstrap
import { Form, Button } from 'react-bootstrap'
export function Home() {
//
//
const [username, setUsername] = useLocalStorage('username', 'John')
//
const [roomId, setRoomId] = useState('free')
const linkRef = useRef(null)
//
const handleChangeName = (e) => {
setUsername(e.target.value)
}
//
const handleChangeRoom = (e) => {
setRoomId(e.target.value)
}
//
const handleSubmit = (e) => {
e.preventDefault()
//
linkRef.current.click()
}
const trimmed = username.trim()
return (
<Form
className='mt-5'
style={{ maxWidth: '320px', margin: '0 auto' }}
onSubmit={handleSubmit}
>
<Form.Group>
<Form.Label>Name:</Form.Label>
<Form.Control value={username} onChange={handleChangeName} />
</Form.Group>
<Form.Group>
<Form.Label>Room:</Form.Label>
<Form.Control as='select' value={roomId} onChange={handleChangeRoom}>
<option value='free'>Free</option>
<option value='job' disabled>
Job
</option>
</Form.Control>
</Form.Group>
{trimmed && (
<Button variant='success' as={Link} to={`/${roomId}`} ref={linkRef}>
Chat
</Button>
)}
</Form>
)
}
UserListコンポーネントは、その名前が示すように、ユーザーのリストです。これには、アコーディオン、リスト自体、およびユーザーのオンラインプレゼンスのインジケーターが含まれています。
コンポーネント/UserList.js:
//
import { Accordion, Card, Button, Badge } from 'react-bootstrap'
// -
import { RiRadioButtonLine } from 'react-icons/ri'
// -
export const UserList = ({ users }) => {
//
const usersArr = Object.entries(users)
// ( )
// [ ['1', { username: 'Alice', online: false }], ['2', {username: 'Bob', online: false}] ]
//
const activeUsers = Object.values(users)
//
// [ {username: 'Alice', online: false}, {username: 'Bob', online: false} ]
.filter((u) => u.online).length
return (
<Accordion className='mt-4'>
<Card>
<Card.Header bg='none'>
<Accordion.Toggle
as={Button}
variant='info'
eventKey='0'
style={{ textDecoration: 'none' }}
>
Active users{' '}
<Badge variant='light' className='ml-1'>
{activeUsers}
</Badge>
</Accordion.Toggle>
</Card.Header>
{usersArr.map(([userId, obj]) => (
<Accordion.Collapse eventKey='0' key={userId}>
<Card.Body>
<RiRadioButtonLine
className={`mb-1 ${
obj.online ? 'text-success' : 'text-secondary'
}`}
size='0.8em'
/>{' '}
{obj.username}
</Card.Body>
</Accordion.Collapse>
))}
</Card>
</Accordion>
)
}
MessageFormコンポーネントは、メッセージを送信するための標準フォームです。Pickerは、emoji-martライブラリによって提供される絵文字コンポーネントです。このコンポーネントは、ボタンを押すことで表示/非表示になります。
コンポーネント/MessageForm.js:
import { useState } from 'react'
//
import { Form, Button } from 'react-bootstrap'
//
import { Picker } from 'emoji-mart'
//
import { FiSend } from 'react-icons/fi'
import { GrEmoji } from 'react-icons/gr'
//
export const MessageForm = ({ username, sendMessage }) => {
//
const [text, setText] = useState('')
//
const [showEmoji, setShowEmoji] = useState(false)
//
const handleChangeText = (e) => {
setText(e.target.value)
}
// /
const handleEmojiShow = () => {
setShowEmoji((v) => !v)
}
//
// ,
const handleEmojiSelect = (e) => {
setText((text) => (text += e.native))
}
//
const handleSendMessage = (e) => {
e.preventDefault()
const trimmed = text.trim()
if (trimmed) {
sendMessage({ messageText: text, senderName: username })
setText('')
}
}
return (
<>
<Form onSubmit={handleSendMessage}>
<Form.Group className='d-flex'>
<Button variant='primary' type='button' onClick={handleEmojiShow}>
<GrEmoji />
</Button>
<Form.Control
value={text}
onChange={handleChangeText}
type='text'
placeholder='Message...'
/>
<Button variant='success' type='submit'>
<FiSend />
</Button>
</Form.Group>
</Form>
{/* */}
{showEmoji && <Picker onSelect={handleEmojiSelect} emojiSize={20} />}
</>
)
}
MessageListItemコンポーネントは、メッセージリストアイテムです。TimeAgoは、日付と時刻をフォーマットするためのコンポーネントです。日付を取り、「1か月前」のような文字列を返します。この行はリアルタイムで更新されます。メッセージを削除できるのは、それらを送信したユーザーのみです。
コンポーネント/MessageListItem.js:
//
import TimeAgo from 'react-timeago'
//
import { ListGroup, Card, Button } from 'react-bootstrap'
//
import { AiOutlineDelete } from 'react-icons/ai'
//
export const MessageListItem = ({ msg, removeMessage }) => {
//
const handleRemoveMessage = (id) => {
removeMessage(id)
}
const { messageId, messageText, senderName, createdAt, currentUser } = msg
return (
<ListGroup.Item
className={`d-flex ${currentUser ? 'justify-content-end' : ''}`}
>
<Card
bg={`${currentUser ? 'primary' : 'secondary'}`}
text='light'
style={{ width: '55%' }}
>
<Card.Header className='d-flex justify-content-between align-items-center'>
{/* TimeAgo */}
<Card.Text as={TimeAgo} date={createdAt} className='small' />
<Card.Text>{senderName}</Card.Text>
</Card.Header>
<Card.Body className='d-flex justify-content-between align-items-center'>
<Card.Text>{messageText}</Card.Text>
{/* */}
{currentUser && (
<Button
variant='none'
className='text-warning'
onClick={() => handleRemoveMessage(messageId)}
>
<AiOutlineDelete />
</Button>
)}
</Card.Body>
</Card>
</ListGroup.Item>
)
}
「MessageList」コンポーネントはメッセージのリストです。「MessageListItem」コンポーネントを使用します。
コンポーネント/MessageList.js:
import { useRef, useEffect } from 'react'
//
import { ListGroup } from 'react-bootstrap'
//
import { MessageListItem } from './MessageListItem'
// (inline styles)
const listStyles = {
height: '80vh',
border: '1px solid rgba(0,0,0,.4)',
borderRadius: '4px',
overflow: 'auto'
}
//
// "MessageListItem"
export const MessageList = ({ messages, removeMessage }) => {
// ""
const messagesEndRef = useRef(null)
// ,
useEffect(() => {
messagesEndRef.current?.scrollIntoView({
behavior: 'smooth'
})
}, [messages])
return (
<>
<ListGroup variant='flush' style={listStyles}>
{messages.map((msg) => (
<MessageListItem
key={msg.messageId}
msg={msg}
removeMessage={removeMessage}
/>
))}
<span ref={messagesEndRef}></span>
</ListGroup>
</>
)
}
Appコンポーネントは、アプリケーションの主要コンポーネントです。ルートを定義し、インターフェイスをアセンブルします。
src / App.js:
//
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
//
import { Container } from 'react-bootstrap'
//
import { Home, ChatRoom } from 'components'
//
const routes = [
{ path: '/', name: 'Home', Component: Home },
{ path: '/:roomId', name: 'ChatRoom', Component: ChatRoom }
]
export const App = () => (
<Router>
<Container style={{ maxWidth: '512px' }}>
<h1 className='mt-2 text-center'>React Chat App</h1>
<Switch>
{routes.map(({ path, Component }) => (
<Route key={path} path={path} exact>
<Component />
</Route>
))}
</Switch>
</Container>
</Router>
)
最後に、「src /index.js」ファイルはWebpackのJavaScriptエントリポイントです。Appコンポーネントのグローバルなスタイル設定とレンダリングを行います。
src / index.js:
import React from 'react'
import { render } from 'react-dom'
import { createGlobalStyle } from 'styled-components'
//
import 'bootstrap/dist/css/bootstrap.min.css'
import 'emoji-mart/css/emoji-mart.css'
//
import { App } from './App'
// ""
const GlobalStyles = createGlobalStyle`
.card-header {
padding: 0.25em 0.5em;
}
.card-body {
padding: 0.25em 0.5em;
}
.card-text {
margin: 0;
}
`
const root = document.getElementById('root')
render(
<>
<GlobalStyles />
<App />
</>,
root
)
さて、私たちは小さなアプリケーションの開発を終えました。
それが機能することを確認する時が来ました。これを行うには、プロジェクトのルートディレクトリ(react-chat)で、コマンド「yarnstart」を実行します。その後、開いたブラウザタブに、次のようなものが表示されます。
結論の代わりに
アプリケーションを改善したい場合は、次のアイデアがあります。
- ユーザー用のDBを追加します(同じlowdbを使用)
- 2番目の部屋を追加します-これには、サーバー上でメッセージリストの個別の処理を実装するだけで十分です
- ( ) —
- — MongoDB Cloud Mongoose; Express
- : (, , ..) — react-filepond, — multer; WebRTC
- よりエキゾチックなものから:音声をテキストに追加し、音声メッセージをテキストに翻訳します-これにはreact-speech-kitを使用できます
これらのアイデアのいくつかは、チャットを改善するための私の計画に含まれています。
ご清聴ありがとうございました。良い一日を。