【React+Node.js/socket.io】チャットアプリに入室機能とユーザ名表示機能を追加する
はじめに
前回、ReactとNode.js Expressを使って、チャットアプリの超基礎となる部分を作成した。
今回は、機能を追加し、さらにチャットアプリに近づける。
追加する機能
発言者名と発言時刻を表示する機能
発言者名と発言の時刻を取得して表示する機能。 発言者名は、入室機能によってブラウザごとに設定する。
入室機能(簡易)
チャット画面に入る前に、ユーザ名を設定する機能。
見た目の改善・コンポーネント化
マージンを設定したり、ボタンのデザインを作成。
前提
前回の記事の続きとなります。
最終的なソースコード
ソースコードはこちら。 github.com
修正内容の解説
発言時刻を表示する機能
メッセージ送信機能への変更
前回のApp.tsx
のうち、画面サーバ(クライアント側)からWebサーバに送信する内容を増やす。
// 「送る」ボタンを押したときの処理。サーバにmessageを送信する。 // messageContentオブジェクト(中身は名前/メッセージ/発言時刻) const onClickSend = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { e.preventDefault(); const date = new Date(); const messageContent: MessageContent = { name: name, message: message, postat: date.toLocaleString("ja"), }; socket.emit("chat message", messageContent); setMessage(""); };
時刻を取得するのはJavasciptのDateクラス
const date = new Date();
で実施している。
Webサーバ側の変更
Webサーバ側(index.js)は、オブジェクトを受けとってブロードキャストする動作は変わらないので、特に変更なし。
メッセージ受信、表示機能への変更
あとは画面サーバ側で、ブロードキャストされた値をそれぞれ表示してあげるだけで良い。
useEffect(() => { socket.on("chat", (messageContent) => { setMessageList((messageList) => [...messageList, messageContent]); }); }, []);
の部分でサーバから送信されたmessageContent
に必要な値が格納されている。
入室機能(簡易)
入室画面とメインチャット画面を作成する
これまで作成してきたメインのチャット画面に加え、入室画面を作成する。
これまで
- メインチャット画面:
/
(例えば開発環境ではhttp:localhost:8000
)
変更後
- ログイン画面:
/
(例えば開発環境ではhttp:localhost:8000
) - メインチャット画面
/main
(例えば開発環境ではhttp:localhost:8000/main
)
ルーティング
ルーティングはReact-router-domを利用した。
tomiko0404.hatenablog.com tomiko0404.hatenablog.com
App.tsx
import "./App.css"; import { ChakraProvider } from "@chakra-ui/react"; import { BrowserRouter } from "react-router-dom"; import { Router } from "./router/Router"; function App() { return ( <ChakraProvider> <> <BrowserRouter> <Router /> </BrowserRouter> </> </ChakraProvider> ); } export default App;
/router/Router.tsx
import { Route, Switch } from "react-router"; import { LoginPage } from "../components/pages/LoginPage"; import { MainChat } from "../components/pages/MainChat"; import { Page404 } from "../components/pages/Page404"; export const Router = () => { return ( <Switch> <Route exact path="/" render={() => <LoginPage />} /> <Route exact path="/main" render={() => <MainChat />} /> </Switch> ); };
入室画面は/pages/LoginPage.tsx
に、メイン画面は/pages/MainChat.tsx
に切り出す。
入室画面
入室画面では、stateとしてname
を利用する。
<input>
に入力されているvalue
をname
に常にname
に設定する。「入室」ボタンがクリックされたら、onClickJoin
関数により/main
のURLに遷移する。その際、stateとして{name: name}
が渡される。
/pages/LoginPage.tsx
import { useState } from "react"; import { useHistory } from "react-router"; import { NameInputArea } from "../organisms/NameInputArea"; export const LoginPage = () => { // 名前 const [name, setName] = useState(""); const history = useHistory(); // 名前エリアの文字が変更されたときの処理 const onChangeName = (e: React.ChangeEvent<HTMLInputElement>) => { setName(e.target.value); }; // 名前が登録されたときの処理 const onClickJoin = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => { history.push("/main", { name: name }); }; return ( <> <NameInputArea onChange={onChangeName} onClick={onClickJoin} value={name} /> </> ); };
/components/organisms/NameInputArea.tsx
import { VFC } from "react"; import { InputArea } from "../molecules/InputArea"; type Props = { value: string; // インプットに表示されているテキスト onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void; }; export const NameInputArea: VFC<Props> = (props) => { const { value, onChange, onClick } = props; return ( <InputArea value={value} onChange={onChange} onClick={onClick} placeholder="名前を入力してください" buttonText="入室" /> ); };
/components/molecules/InputArea.tsx
import { Input } from "@chakra-ui/input"; import { Flex } from "@chakra-ui/layout"; import { VFC } from "react"; import { PrimaryButton } from "../button/PrimaryButton"; type Props = { value: string; onChange: (e: React.ChangeEvent<HTMLInputElement>) => void; placeholder?: string; onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void; buttonText: string; }; export const InputArea: VFC<Props> = (props) => { const { value, onChange, placeholder, onClick, buttonText } = props; return ( <Flex align="center" justifyContent="center"> <Input placeholder={placeholder} value={value} onChange={onChange} /> <PrimaryButton onClick={onClick} disabled={!value}> {buttonText} </PrimaryButton> </Flex> ); };
メインチャット画面
前回のApp.tsx
を/pages/MainChat.tsx
に切り出す。
また、入室画面から遷移してきたときのstateオブジェクトを受け取る必要があるので、以下を追加する。
App.tsx
import { useLocation } from "react-router"; type LoginState = { name: string; }; export const MainChat = () => { const { state } = useLocation<LoginState>(); const { name } = state; .... 省略 ... }
見た目の改善
ChakraUIの機能を使って諸々の見た目を改善。 また、Atomicデザインを意識してある程度コンポーネント化した。
ChakraUIの利用にはコツがいるので、以下サイト
等を参考にした。
テキスト入力&送信部を中央添えにする部分は苦労したが、justifyContent
を使いこなす必要がありそう。
<Flex align="center" justifyContent="center"> <Input /> <PrimaryButton></PrimaryButton> </Flex>
おわりに
少しチャットらしいアプリにすることができた。
誰でも入室できてしまうので、socket.io の room
機能など使いこなす必要がありそう。