FC2ブログ

スマフォのアプリを作りたい(9):React Nativeでチャットアプリ味見②

   プログラミング [2020/01/29]
前回、とあるサイトを丸っとマネして、チャット風アプリをエミュレータ(AVD)上で実行するところまでやりました。
★1:https://qiita.com/Yorinton/items/b029df0713a471d4569a
でも、上記サイトで説明しているような表示になりませんでしたし、「実際チャットできるようにするには?」まで到達してません。
よって、前回の続きになります。

念のため:さるが調べて/試して分かったことを簡潔にメモしてるわけではありません。ボヤキや迷走具合をそのままダラダラ書いてます。読んでるとイライラするかと思います。悪しからず。


前回のサンプルプログラムの問題を解決するには、サンプルコードの理解からです。
コード内容全体は前回の書き込み「スマフォのアプリを作りたい(9)・・・」か★1サイトをご参照ください。

◆チャットサンプルのApp.js
React Nativeのテンプレートサンプルでは、メインの処理「App」は関数として定義されていました。
const App : () => React$Node = () => {・・・}
でも、チャット・サンプルでは、コンポーネントになってました。
class App extends Component<{}> {・・・}
なぜでしょう。深く考えなくてもいいんでしょうか。
念ためのご参考:https://mae.chab.in/archives/2956
「コンポーネント」にはClassとFunction(関数)が含まれるんですね。
少し役割も違うようですが、トップのレイヤーの定義なんでどっちでもよさそうですね。

それにしたって、Componentの後ろの<{}>さるには理解不能です。
だいぶ、この書き方について解説してくれてるところを探しました。
唯一、「extends の後では任意の式が指定できます。」と説明してくれてるところを見つけました。
参考:https://ja.javascript.info/class-inheritance
それと、classの基本的なこと。
参考:https://qiita.com/tadnakam/items/ae8e0e95107e1427983f
どっちにしろ<{}>の意味は掴めません。
{中身}に何も入ってないから気にしなくていいのかな。
あまりに消化不良すぎるので、さらに検索したら、TypeScriptという単語が引っ掛かりました。
「うゎ、出たTypeScript」とか思ったのですが、何なのかはよく知りません。
Flowの件を調べているときに、引き合いに出されていた記述を見たので、Flowと横並びな感じの表現手法のようなイメージです。
TypeScriptって?
「Microsoftによって開発・発表されたプログラミング言語です。JavaScriptを拡張して作られたものですが、JavaScriptとは違い静的型付けのクラスベースオブジェクト指向言語になっています。TypeScriptをコンパイルすると、JavaScriptのコードに変換されるためJavaScriptが動く環境であればすぐに使うことができます。」
参考:https://www.sejuku.net/blog/93230

ともかく、class定義の基底クラス名の後に<>がつくときの大まかな意図は以下の解説でなんとなく分かった気になりました。ただし、そこまでです。
参考:https://qiita.com/alfe_below/items/1cb81a6a03d8d6d73b27
FlowにTypeScript・・・もう勘弁してくだせー。さるには無理です。

プチ勉強したReactと違うと思えたところ
・Appの呼び出し(サンプルなので単に「一例」ということかもしれません。)
  React:ReactDOM.render(<App … />, document.getElementById('root'))
  ReactNative:AppRegistry.registerComponent(appName, () => App);


Appコンポーネントのrender()メソッドのreturn(中身)は、以下になってます。
----JSX:App.js:-----------------------------------
<Router>
<Scene key='root'>
<Scene key='Chat' component={Chat} title='チャット'/>
</Scene>
</Router>
--------------------------------------------------

使っているのは、追加フレームワーク:react-native-router-fluxの中のコンポーネントですね。
従って、「react-native-router-flux」でぐぐりました。
参考:https://qiita.com/YutamaKotaro/items/ab52b6ba664d88a87bd9
Reactのお勉強時に参考にしたサイトでルーティングの話が出てきたが、React Ntive(クライアント・アプリ)でも必要なん?
上記のサイトの説明にあるように、「戻る」機能とかを実装するときに便利なのかもしれないですね。

使っている<Router><Scene>については、
切り換え先候補を<Scene>で定義して、それが複数あるときのルーティング(戻る:< とか)が使えるようにするために<Router>で囲うみたいです。
<Scene>は、遷移コンポーネントを列挙する場合と、その列挙したリストをグルーピングする場合に使うようです。
今回のチャットサンプルでは、<Scene key='root'>で「root」という最初の選択グループを作成し、そのグループ内に指定した<Scene key='Chat' ・・・ />で遷移先候補の実際のコンポーネントを指定します。
<Scene>の「key=」は行先としての「識別子」で、「component=」が行先コンポーネントを示します。「title=」は行先の「タイトル」ですね。
複数の<Scene>がある場合は、「initial」属性を指定して最初に動作するコンポーネントを指定するようですが、このサンプルでは1つしかないので、省略してます。つまり自動的に「Chat」という名前のコンポーネントを実行するようになっているのかと思います。

例えば、上記サンプルの「<Scene key='Chat' initial component={Chat} title='チャット'/>」と同じ並びで、「<Scene key='Search' component={Search} title='検索'/>」とかがあった場合、ChatコンポーネントからSearchコンポーネントへの遷移動作は、Chatコンポーネント側に無ければいけないみたいです。

このサンプルにおいては、全てはChat.js側ですね・・・


◆チャットサンプルのChat.js
ざっくり、チャット動作の仕組ですが、
・入力した、コメント(文字列)は、Chatコンポーネントのstateオブジェクトにmessagesに配列として格納するようになってるんでしょう。
・this.state.message[]を画面上に表示し、テキスト入力欄と[Send]ボタンを描画しているのはChat.render()メソッドです。
----JSX:Chat.js:------------------------------------
//メッセージ内容をstateで管理
state = {
messages:[],
};



render() {
return (
//react-native-gifted-chatが提供するコンポーネント
<GiftedChat
messages={this.state.messages}//stateで管理しているメッセージ
onSend={(messages) => this.onSend(messages)}//送信ボタン押した時の動作
user={{
_id: 1,
}}
/>
)
}
--------------------------------------------------

<GiftedChat ・・・/>で全部のようです。
短いのはある意味嬉しいけど・・・何をしてるか詳細を把握できないのは痛し痒し。

<GiftedChat ・・・/>のパラメタ、
・messages=は入力したその時点のメッセージの配列(this.state.messages)、
・onSend=は[Send]をタップしたときのイベントハンドラですね。this.onSendになってます。
 指定したハンドラは、このコンポーネンント内のonSend()メソッドのようですが、直接コールバックされるわけではなさそうです。
 呼び出される際には、messagesが渡されます。ただし、直前のmessagesと同じという意味でなく、単に引数名がいっしょなだけのようです。
・user=は送信者を表すオブジェクトで、{ _id:, name:, avatar: }となってます。
参考:https://www.npmjs.com/package/react-web-gifted-chat

直接呼出しではないにしろ、使われると思われるこのコンポーネントのonSend()は以下の内容です。
----JSX:Chat.js:------------------------------------
//「Send」ボタンが押された時に実行されるメソッド
onSend = (messages = []) => {
this.setState((previousState) => ({
//stateで管理しているmessagesに送信されたメッセージを追加
messages: GiftedChat.append(previousState.messages, messages),
}));
}
--------------------------------------------------

コールバックされたonSendに追加するmessagesが渡されて、それがpreviousState.messagesに追加されていくようになってます。たぶん。
でも、アロー関数式で渡されているpreviosStatusとthis.stateとの関係付けがどこでされてるのかちょっと理解できません。省略形が多すぎてよく分からん。
最初にコードを理解しようとしたときの独り言
・呼び出し側は、引数messagesにthis.state.messagesを設定している。
 ↑後でここが勘違いであることが分かりました。

onSned関数の引数の(messages = [])はmessagesが配列であることを示しているだけ?
previosStatusに対して、GiftedChat.append()でmessagesを追加してる っぽい。
GiftedChat.appendのリターンをオブジェクト内のmessagesに設定いている っぽい。
そのオブジェクトがリターンされて、this.setState()でthis.stateに設定している っぽい。

・では、previousStateはどこから出てきたのか?
 次にコールバックされたときのthis.stateをpreviousStateに入れているは?
・新たに入力されたメッセージテキストはどこでonSendの引数であるmessages反映されてる?
(previousState) => ({message:...,})=>後の()の意味は?
 単に中の{}がオブジェクトを表しているので、関数の1ステートメントであることを示す()なのか?

モヤモヤです。

さらに迷走します
「(プロジェクトフォルダ)\node_modules\react-native-gifted-chat\\lib\GiftedChat.js」があったので中身を見てみた。
まずは、GiftedChat.append()。
----JSX:------------------------------------------
static append(currentMessages = [], messages, inverted = true) {
if (!Array.isArray(messages)) {
messages = [messages];
}
return inverted
? messages.concat(currentMessages)
: currentMessages.concat(messages);
}
---------------------------------------------------

第3パラメタは指定してないので、invertedはtrueのはず。
なので、messagesに対してcurrentMessagesを追加している感じになるのかな?
でも、invertedで逆パターンもあって、messagesとcurrentMessagesのどっちが新規のメッセージなのは自判断つかず。

onSendコールバックについても、再度まさぐってみました。
GiftedChat.js内でGiftedChatコンポーネントのコンストラクタでthis.onSendが定義されてました。
----JSX:------------------------------------------
this.onSend = (messages = [], shouldResetInputToolbar = false) => {
if (!Array.isArray(messages)) {
messages = [messages];
}
const newMessages = messages.map(message => {
return {
...message,
user: this.props.user,
createdAt: new Date(),
_id: this.props.messageIdGenerator && this.props.messageIdGenerator(),
};
});onSend({ text: text.trim() }, tr
if (shouldResetInputToolbar === true) {
this.setIsTypingDisabled(true);
this.resetInputToolbar();
}
if (this.props.onSend) {
this.props.onSend(newMessages);
}
if (shouldResetInputToolbar === true) {
setTimeout(() => {
if (this.getIsMounted() === true) {
this.setIsTypingDisabled(false);
}
}, 100);
}
};
---------------------------------------------------

コンポーネント内のthisはいわばグローバルだそうなので、Chat.js側で、「onSend={」とされていたのはこっちですね。
上記処理の中のthis.props.onSendが、きっとChat.js内のonSend関数ですね。(propsとしてonSendがいつ設定されているか分からない。)
でもthis.onSendは結局、入力のmessagesを成形しなおして、this.props.onSendに渡しているだけ。
結局previousStateにたどり着かない。

もういいです。机上でアハできなかったけど、次行きます。

デバッガを使って、実際の動きがどうなのか見てみました。
this.onSendのmessagesパラメタで渡されて来るのは、新規に入力されたメッセージでした。
なので、Chat.jsのonSend関数内のpreviousState.messagesが当初の予想通りthis.state.messagesの内容で、messagesが新規に入力したメッセージでした。
でもやっぱり、previousStateの由来が不明でした。.append()のところまで行くと、this.stateの内容が仕込まれています。
何故だー。

さらに半日余り、Web上の情報を探しまくって、以下のサイトに辿り着きました。
参考:https://qiita.com/im36-123/items/857c96ff60024c0d8a1c
「setState()の第1引数にオブジェクトを指定した場合と関数を指定した場合で動きが異なる」と書いてあります。
!!
さらに探して、以下。
参考:https://ja.reactjs.org/docs/state-and-lifecycle.html
「その関数は前の state を最初の引数として受け取り、更新が適用される時点での props を第 2 引数として受け取ります」とある。
えーそうなのー。早く行ってよ。
つまり、this.setState((previousState) => {・・・})と書いてあるだけで、{・・・}の処理を実行する前にpreviousStateにthis.stateがセットされるということですね。
もう、そういうことだってことにしましょう。

...messageってどういう意味?
「...配列名」はスプレッド構文と呼ばれるもので、配列を展開する(中の要素毎に列挙してくれる)ものだそうです。
「...配列名」は、「配列名[0], 配列名[1], 配列名[2], ・・・」ってことのようです。


functionで定義した関数とアロー関数式とでthisの意味が変わる件
ざっくり言ってしまえば、
「function Xxxx(){ console.log(this.myval) }」とやった 場合、Xxxx()のコール元のthis。
オブジェクトが関数を含む場合「obj.func();」とかの場合はobj自体がthisになる。
「Xxxx = () => { console.log(this.myval) }」の場合、記述した位置でthisが決まる。
クラス内でthisを使った場合は、そのクラス内のメンバを表す。ただし、値はそのクラスのインスタンス毎。


やっと、次へ進める。


◆チャットサンプルでの日本語入力
前の件でGiftedChatのコードを見ていたら、initLocale()、setLocale()という関数を見つけた。
----GiftedChat.js----------------------------------
initLocale() {
if (this.props.locale === null ||
moment.locales().indexOf(this.props.locale || 'en') === -1) {
this.setLocale('en');
}
else {
this.setLocale(this.props.locale || 'en');
}
}
setLocale(locale) {
this._locale = locale;
}
---------------------------------------------------

<GifredChat>コンポーネントのパラメタにもlocale=があるようです。
参考:https://www.npmjs.com/package/react-web-gifted-chat
localeを指定してみることにしました。
----Chat.js----------------------------------------
【変更前】
<GiftedChat
messages={this.state.messages}//stateで管理しているメッセージ
onSend={(messages) => this.onSend(messages)}//送信ボタン押した時の動作
user={{
_id: 1,
}}
/>
【変更後】
<GiftedChat
messages={this.state.messages}//stateで管理しているメッセージ
onSend={(messages) => this.onSend(messages)}//送信ボタン押した時の動作
user={{
_id: 1,
}}
locale='ja' //日本語対応
/>
---------------------------------------------------

Chat.js内の<GifredChat>のパラメタにlocale='ja'を追加してみました。
ただし、何の変化もなし。
デバッガでinitLocal()の処理後で、this._localeを確認してみましたが'en'のままです。
ちょっと、ぐぐってみたら、それだけでは足りないとの記述が見つかりました。
https://github.com/FaridSafi/react-native-gifted-chat/issues/614
import 'moment/locale/ja'を追加するとひとまず効果はあるようです。
ただし、他に方法ないか?と聞いてて、
moment.jsをアップデートして解決した/いや変わらないとあります。
まずは、効果がありそうな方を試してみました。
----GiftedChat.js----------------------------------
【変更前】
import moment from 'moment';

【変更後】
import 'moment/locale/ja';
import moment from 'moment';
---------------------------------------------------

initLocal()の処理後のthis._localeは'ja'になってますが、見た感じは変化なし。

「そもそも、エミュレータ自体のロケールが「英」になってるもんね。ダメなわけだ。」と
思って、以下を参考にAVDのローケールを変更。
https://qiita.com/na3ksg/items/c52fbf30e8559eee756b
ちょっと説明が足んない感じだったのでもう少し説明足します。
以下、エミュレータはAndoid6です。

-「ホーム」ボタン(お家)20200129_1.jpgをタップ
-表示されている真ん中のアイコン(6個穴ボタン)20200129_2.jpgをタップ
-以下の画面上の「Custum Locale」アイコンをタップ
20200129_3.jpg
-表示されているリストをスクロールさせて、「ja-ja」を選択し、[Select 'JA']をタップ
20200129_4.jpg
-「戻る」ボタン20200129_5.jpgをタップして、
表示されているアイコン名が日本語に代わっているのを確認
20200129_6.jpg
-同画面上の「設定」アイコン(歯車マーク)をタップ
-リストされている内容をスクロールさせて、「言語と入力」をタップ
20200129_7.jpg
-表示される「言語 日本語」になっていることを確認し、「現在のキーボード」タップ
20200129_8.jpg
-表示されるポップアップで「キーボードの選択」をタップ
20200129_9.jpg
-表示項目の「Japanese IME」の右側スライダー●を右にスライドさせて有効化する
20200129_10.jpg
-「戻る」ボタン20200129_5.jpgをタップして、さらに「現在のキーボード」を再度タップ
-「日本語 Jananese IME」を選択
20200129_11.jpg

-後は「戻る」を2回タップしてホーム画面に戻ります
-デバッグ途中で上記設定を行った場合は、再度6個穴アイコンをタップして、
 内容をスクロールさせて、デバッグ中のアプリアイコンをタップすればリロードになります。
20200129_12.jpg

日本語が入力できるようになりました。\(^o^)/
20200129_13.jpg
でも、タイトル「チャット」は文字化けしたまま。

ちなみに、Chat.jsに入れた修正「locale='ja' //日本語対応」をコメントアウトして再度実行してみました。
結果は同じ、日本語入力はできました。関係無かったのかな?
localeパラメタの説明を見ると「日付をローカライズするためのもの」と書かれてあって、言語(テキスト)表示と関係ありそうではない。かといって、'ja'としたときと、しなかったときいずれも時刻は日本時間のようでした。

タイトルを出力してるところを追っかけます。



◆日本語文字化けの件
「チャット」は、このアプリのApp.jsの中で、react-native-router-fluxの「<Scence … title='チャット' />」で指定されています。
<Scence;gt;コンポーネントの説明を見ると、「ナビゲーションバーの中央に表示するテキスト」と説明されていた。
予想としては、ScenceからReact Nativeの標準的なコンポーネントを呼んでいるだけのような気がする。
「どっかでロケールとかを指定しておく必要があるのか?」とか、「router-fluxのマルチバイト文字対応の不具合?」とか色々ぐぐってみたけど、全然それらしい情報に当たりません。
そもそも、そんな不具合があるのならバンバン記事が出てきてよさそうなもの。
最初にこのスクリプトを開示してくれていたサイト★1でもそんなことは触れられていない。

試しに、Chatから離れて、App.jsを以下のようなコードに変更してみた。
----JSX:------------------------------------------
import React, { Component } from 'react';
import { SafeAreaView, View, Text,} from 'react-native';
import {
Router,
Scene,
} from 'react-native-router-flux';
import Chat from './Chat';

class MyText extends Component {
render() {
return (
<SafeAreaView>
<View>
<Text>MyText:日本語</Text>
</View>
</SafeAreaView>
);
}
}


export default class App extends Component<{}> {
render() {
return (
<Router>
<Scene key='root'>
<Scene key='Text' component={MyText} title="MyText:文字を表示するだけ" />
<Scene key='Chat' component={Chat} title="VTChat:チャットサンプル" />
</Scene>
</Router>
);
}
}
---------------------------------------------------

リロードしてみると、いきなり「MyText:日本語」と書いたところで、シンタックスエラーが発生。
----node------------------------------------------
error: bundling failed: SyntaxError: D:\AppMake\proj\VTChat\App.js: Unexpected character '�' (29:25)
27 | <SafeAreaView>
28 | <View>
> 29 | <Text>MyText:���{��</Text>
30 | </View>
31 | </SafeAreaView>
---------------------------------------------------

ホワイ?
どっかで、ソースがマルチバイトであることを伝えないといけないのか?
さんざん、検索しまくったが、何も出てこない。
どこのサンプルも何も付け足しなしで<Text …>(日本語の言葉)</Text>とやってる!

まさかと思って、ソースファイルの文字コードセットを確認してみた。
SJISとなっている。・・・シフトJISはダメなのか?
でも、追加インストールしたライブラリのソースでも文字セットはシフトJISでした。

再度散々調べた挙句、試しにと思って、.jsファイルをUTF-8で保存しなおして見たら・・・
20200129_14.jpg
動きました。
とほほ。
スクリプトを元に戻して動かしてみて、以下のようになりました。
20200129_15.jpg


結局、
今回分かったスクリプト絡みの知識は、
1)「class ・・・extends Component<・・・> {」の<>は、TypeScriptの型指定の記述。
2)setState()関数の第1引数に関数を指定すると、とっても楽チンなコードが書ける。
 JavaScriptの標準メソッド系の動き(仕様)には注意しろ!普通のC関数とは違うぞ!
くらいかな。


Chatサンプル味見の日本語絡みの不味かった点は、
1)エミュレータのロケール、キーボード(IME)設定ができていなかった。
2)スクリプト・ファイルがUTF-8ではなかった。

でした。

やれ、やれ。気付くのに時間かかり過ぎですよね。
さる過ぎて、涙が出ます。


ここで話を一旦切ります。
次回は、試しにエミュレータ上で音声入力できるかどうかをすこもこしたいと思います。

では、またその内。
(m__)m
スポンサーサイト





コメントの投稿

非公開コメント

カレンダー
01 | 2024/02 | 03
- - - - 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 - -
プロフィール

さるもすなる

Author:さるもすなる
さるです。別HPサイト「さるもすなる」から侵食してきました。 山菜/きのこ、それとタイトルにしたPPバンド籠のことをメインに徒然に・・・・暇を持て余したさるの手仕事:男手芸のブログってことで。

最新記事
最新コメント
月別アーカイブ
カテゴリ
天気予報

-天気予報コム- -FC2-
本家のHPのトップ
山菜や茸の話です
PPバンドの籠作品と作り方です
投稿をお待ちしております



PVアクセスランキング にほんブログ村 にほんブログ村 ハンドメイドブログへ



マニュアルのお申し込み



検索フォーム
リンク
RSSリンクの表示
ブロとも申請フォーム

この人とブロともになる

QRコード
QR