TypeScript & React & Firebase で何かつくってみる3 React Hooks

さて, まずは React の簡単なサンプルコードでも動かしてみることにする.

やはり古い. あとで気づいたがもう始めてしまっていたのでこのまま行く.
代わりにこれをイマドキの書き方でリファクタリングすることにする. ちょうどいい練習問題だ.

React Hooks

私が以前勉強した書き方とはだいぶ違う. 今は 関数コンポーネントHooksを使う書き方が流行りらしい.

以下のサイトが簡潔にまとまっていてよかった.

以下のサイトも背景からきれいにまとめてあって素晴らしい.

「しょっちゅうコロコロ変わりやがって」と思ったが,書いてみると確かにスッキリかけてスコープも狭く見やすい気がする.

書き直したサンプルコード

元コードは JavaScript+ClassComponent+RealtimeDatabase だが, これを TypeScript+FunctionComponent+Firestore に書き換える.
まだ未熟だが, 熟練者が何か教えてくれるかもしれないので貼っておく.

Message.tsx

import React from 'react';

type Props = {
 profile_image: string
 user_name: string
 text: string
}
const Message: React.FC<Props> = props => {
 return (
   <div className="Message">
     <img className="" src={props.profile_image} alt='profile' />
     <div className="">
       <p className="">@{props.user_name}</p>
       <p className="">{props.text}</p>
     </div>
   </div>
 );
}
export default Message;

ChatBox.tsx

import React from 'react';

type Props = {
 onTextChange: React.ChangeEventHandler<HTMLElement>
 onButtonClick: React.MouseEventHandler<HTMLButtonElement>
}

const ChatBox: React.FC<Props> = props => {
 return (
   <div className="ChatBox">
     <div className="">
       <input name='user_name' onChange={props.onTextChange} className="" placeholder="名前" />
       <input name='profile_image' onChange={props.onTextChange} className="" placeholder="プロフィール画像URL" />
     </div>

     <textarea name='text' className="" onChange={props.onTextChange} />
     <button className="" onClick={props.onButtonClick}>送信</button>
   </div>
 );
}

export default ChatBox;

App.tsx

import React, { useEffect, useState } from 'react';
import './App.css';
import { firebaseApp } from './firebaseApp';
import Message from './components/Message';
import ChatBox from './components/ChatBox';

const firestore = firebaseApp.firestore();

type MessageProps = {
 text: string,
 user_name: string,
 profile_image: string,
}

function App() {
 const [messages, setMessages] = useState<MessageProps[]>([]);
 const [message, setMessage] = useState<MessageProps>({ user_name: '', profile_image: '', text: '' });

 useEffect(() => {
   // データベースを監視して画面を更新する 
   firestore.collection('messages').onSnapshot((docs) => {
     // ここ ちょいダサいな.
     const msgs: MessageProps[] = [];
     docs.forEach(doc => {
       msgs.push(doc.data() as MessageProps);
     });
     setMessages(msgs);
   });
 }, []);

 const onTextChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
   if (e.target.name === 'user_name') {
     message.user_name = e.target.value;
   } else if (e.target.name === 'profile_image') {
     message.profile_image = e.target.value;
   } else if (e.target.name === 'text') {
     message.text = e.target.value;
   }
   setMessage(message);
 };
 const onButtonClick: React.MouseEventHandler<HTMLButtonElement> = (e) => {
   if (message.user_name === "") {
     alert('user_name empty')
     return
   } else if (message.text === "") {
     alert('text empty')
     return
   }
   // ランダムな名前を付けて値を追加する.
   firestore.collection('messages').add(message);
 };
 return (
   <div className="App">
     <div className="App-header">
       <h2>Chat</h2>
     </div>
     <div className="MessageList">
       {messages.map((m, i) => <Message key={i} {...m} />)}
     </div>
     <ChatBox onTextChange={onTextChange} onButtonClick={onButtonClick} />
   </div>
 );
}

export default App;

Message.tsx の Props と同じものを App.tsx でも MessageProps として定義してしまったことを気にしてはいる. 

どうするのがいいのだろうか. TypeScript には namespace もあるようなので, Message.Props にして公開する? でもこれだと Message.Message になるな.  内部クラス とかは無いのか.  

あるいは Chat.Message と Chat.MessageProps か?  でも内部クラスのある言語に慣れてる身としては MessageProps って名前がちょいダサく感じる.

まぁここはこれで良しとするか. サンプルだし.

Firestore API をもう少し

実は移植で少しズルをしたというか, 元と仕様が異なるところがある.

// データベースを監視して画面を更新する 
firestore.collection('messages').onSnapshot((docs) => {
// ランダムな名前を付けて値を追加する.
firestore.collection('messages').add(message);

本来のコードは Realtime Database上に配列で保存しているが,  firestore.collection はキーバリューストアなのでこれは順番通りに並ばない.

別にチャットを作りたいわけでもないし 先に進めようと思ったが,たまたまちょうどいいサンプルがあったので, 追加順になるようにしてみる.

なるほど. Date インスタンスを入れとくと自動的にタイムスタンプ型として登録されるのか. そして orderBy() を使えば任意の要素でソートできると.

firestore.collection('messages').orderBy('create_at').onSnapshot((docs) => {
message.create_at = new Date();
firestore.collection('messages').add(message);

これで時系列順に並んだ. とりあえずはこれくらいでいいか.

この記事が気に入ったらサポートをしてみませんか?