8.5 こんな時どうする?

8.5.1 洗練されたGUIを作る

Reactでは、色々なコンポーネントを自分で作ることができますが、現在、主流になっている操作性や見た目が良いユーザーインタフェースを自分で一から作るのはとても大変です。それなりのGUIを作るためには、サードパーティーが提供している、「UIコンポーネントライブラリー」を使うのが一般的です。

例えば、以下のようなコンポーネントが提供されます。

  • 綺麗にデザインされたボタン、フォーム、表などの基本的なUIコンポーネント
  • ナビゲーションメニュー、ドロップダウンメニューなどのメニュー
  • アラート表示、タブ表示、カード表示などの表示エリア用コンポーネント
  • 処理を待っている間に表示されるスピナー

以下は、現在、よく使われている(と思われる)React用のUIコンポーネントライブラリの例です。

  • Material-UI (MUI)
    • googleによって提唱されているデザインガイドライン「Material Design」に基づいたUIを提供
    • 豊富なコンポーネント
    • カスタマイズ性が高い
  • Ant Design (Ant-D)
    • 柔軟なスタイリングが可能で、カスタマイズが簡単
    • 軽量で直感的な API
  • React Bootstrap
    • 歴史としては一番長いので、ドキュメントなど情報が豊富
    • レスポンシブデザイン(スマホ、タブレット、PCなど異なる画面幅に合わせて自動調整するWebデザイン手法)への対応

 このテキストでは、React Bootstrapを使った例で説明しますが、他のコンポーネントライブラリを使うのもありです。何年か前まではReact Bootstrapが一番使われているコンポーネントライブラリでしたが、現在では(おそらく)Material-UIがトップシェアになっているのではないかと思います。コンポーネントライブラリは移り変わりが早く、今後何が主流になっていくのかを見ながら、どれを使うのかを決めていくのが良いかと思います。

React Bootstrapを使うために、npmを使って、react-bootstrap と、これが依存しているbootstrapをインストールしておきます。

その他、bootstrap-iconsというボタンなどに表示するアイコンライブラリ(https://icons.getbootstrap.com)も一揃えあります。以下のコマンドでインストールしておきます。

8.5.2 操作パネルからメイン画面を切り替える

 下図のように、「操作パネル」を設け、これの操作により「メイン画面」の表示を変えたり変更したりというのはよくあるGUIのパターンです。このようなGUIの実現方法を説明します。

操作パネルを使った画面の例

このパターンは、Reactの以下の機能を使って実現します。

  • propsによって関数をコンポーネントに渡す
  • propsによって値を渡す
  • useState()による状態変数によって、画面を切り替える

Reactではpropsを使ってコンポーネントに値を引き渡すことができることはすでに説明しましたが、値だけでなく関数も引き渡すことができます。以下のサンプルコードを見てください。

親コンポーネントであるControlPanelPageでは、useState()を使い、状態変数’league_value’とその値を設定する関数‘setLeague’が定義されています。

setLeagueは、以下の行で子コンポーネントControlPanelに’league_handler’という名前で引き渡されています。

 <ControlPanel league_handler={setLeague} />

こうすることによって、ControlPanelコンポーネントからsetLeagu関数を使うことができるようになります。つまり、ControlPanelコンポーネントから親コンポーネントが持つ状態変数league_valueを変更できるようになります。

関数を渡されたControlPanelのコードは以下です。

  • const leagueHandler = props.league_handler; の行で渡された関数は、’leagueHandler’という変数に格納され’leagueHandler’という関数名で実行できるようになります。
  • select要素のハンドラーsetActionでleagueHandlerが呼び出され、selectで選ばれたアイテムの値が親コンポーネントの状態変数league_valueにセットされます。
  • league_valueの状態が変わると、親コンポーネントのレンダーが呼び出され、TeamListに変更されたleague_valueの値が引き渡されます。
  • これにより、TeamListのレンダーが呼び出されTeamListの表示が更新されます。

8.5.3 ページネーション

 Djangoによるバックエンドと、Reactによるfrontendを併せてページネーションGUIを実現する方法を解説します。Django側のコードは以下で、MyPaginationというページネーションクラスを使うことにします。

これに対し、React側のコードが以下です。

<div className="pagination_box"> ... </div>の部分に注目してください。以下のように表示され、バックエンドから帰ってきた値やハンドラーが紐付けられます。

この「ページネーションボックス」の矢印ボタンの操作によって、バックエンドにGETリクエストが送られ、得られた値の’results’が最終的にTeamListに引き渡されてTeamListの表示が更新されます。

TeamListのコードは以下のように、引き渡された値をそのまま表示するようになっています。

8.5.4 メニュー選択でページを切り替える

ナビゲーションメニューを上部に設け、これによりページを切り替える典型的なGUIの基本的な実現方法をサンプルコードで説明します。

メニュー画面の例

メニューはreact-bootstrapのナビゲーションメニュー関連のコンポーネントを使うことにします。

また、ページの動的な切り替えを行うためには、Reactに「ルーティング」の機能を追加してやる必要があり、そのためにreact-router-domというサードパーティーモジュールをインストールします。

また、切り替えるページを表示するコンポーネントとして、PageA, PageB, PageC1, PageC2の4つのコンポーネントが用意されているものとします。

まず、親コンポーネントであるMenuSamplePageのコードを見てみます。

ここで、ルーティングの設定が行われています。ルーティングとは、送られてきたURLに基づいて表示するコンポーネントを決定することです。

まず、<BrowserRouter>...</BrowserRouter>でアプリケーション全体をラップすることによりルーティング機能が使えるようになります。

<Routes>...</Routes>で囲まれたエレメントの中にルーティングルールが記述されています。

例えば、以下は、/page_aというURLが送られてきたらPageAのコンポーネントを子のelementとして表示するという意味です。

これにより<Routes>...</Routes>の部分は<PageA />に置き換えられます。

一方で、NavigationMenuの方は以下のようになっています。

<Nav.Link><NavDropdown.Item>hrefでクリックされたら呼び出すURLを定義しています。URL呼び出しが行われると、これがBrowerRouterのルーティングルールによって解釈され<Route>で指定されたコンポーネントが表示されます。

Webサーバーにデプロイするための修正

npm start を使ってReactの開発用サーバーを使って表示を行う場合は、上のコードでも良いのですが、本番用ののWebサーバーにReactのプログラムをデプロイして使う際には、これではうまくいきません。

8.5.5 ファイルのアップロード

Djangoによるバックエンドと、Reactによるfrontendを併せてファイルのアップロードを実現する方法を解説します。

 バックエンド側の仕組みは、「7.3.8 ファイルをアップロードしたい」で説明していますので、こちらのサンプルプログラムと組み合わせて使うReactのプログラム例を示します。

このプログラムでは、アップロードするファイルを、ドラッグ&ドロップで実現するようにします。以下のように、ファイルをドロップする「ドロップエリア」を表示します。

ファイルをドロップすると下のようなプレビュー画面が表示されます。ここでキャプションの文字列を入れて「送信」ボタンを押すと、ファイルがアップロードされその結果がデータベースに反映されます。

Screenshot

ドラッグ&ドロップの機能は、「react-dropzone」というサードパーティーライブラリを使って実現しますので、これをインストールしておきます。

また、プレビュー画面は現在表示中の画面(親画面)の上に重なって表示される「モーダルウインドウ」にしています。これはreact bootstarpのModalコンポーネントを使って実現します。

まず、ドロップエリアのプログラムです。

return()の中の最後の要素に<FileUploader>が定義されていますが、これはModalウインドウのコンポーネントです。これに渡されている属性showfalseの時には表示されず、trueの時に表示されます。

FileUploaderのプログラムは以下です。

sendForm関数では、バイナリファイルを送信するために「Form」を使った送信を行います。Formを作り、内容をappend()でセットし、これをHTTPリクエストのbodyに入れてバックエンドに送信します。

8.5.6 Rest APIを呼び出す(fetch関数)

DRFなどで提供されるRest APIを呼び出すには、fetchというWebブラウザに組み込まれているHTTP リクエストを行い、レスポンスを処理するための APIを使います。

fetchは非同期処理を行う非同期関数です。

  • 同期処理:タスクを1つずつ順に実行し、完了を待って次へ進む方式
  • 非同期処理:時間のかかるタスク(通信等)の完了を待たずに次のタスクを実行する方式

同期処理でRest APIの処理を行うと、バックエンドから応答が返ってくるまでの間、何も処理が実行できない待ち状態が発生してしまい、待ち状態の間アプリや画面がフリーズしたようになってしまいます。このためインターネットを介して応答を待つ処理は、応答を待っている間にも他の処理(例えば、マウス入力の受付や画面の描画など)を行うことができるよう、非同期処理にするのが一般的です。

fetch関数の処理はブラウザがバックグラウンドで実行します。fetchは呼ばれると、処理をバックグラウンドに引き渡しpromiseというオブジェクトを返します。promise’status’’result’という変数を持っています。statusの値は以下のいずれかになります。

  • 待機 (pending): 初期状態。成功も失敗もしていません。
  • 履行 (fulfilled): 処理が成功して完了したことを意味します。
  • 拒否 (rejected): 処理が失敗したことを意味します。

バックグラウンド処理が終わり、成功した時にはstatusは’fullfiled’に、失敗した時には’rejected’に変わります。成功した時の実行結果はresult変数に格納されます。

fetchが呼ばれた後、その結果を待つことなく次の処理が実行されます。それでは、fetchの結果をどうやって処理すれば良いのでしょう?

実は、promiseは結果が得られた時に呼び出されたされる「コールバック関数」を持つことができます。このコールバック関数の中に結果を処理するコードを書き、この関数をpromiseに登録します。

状態がfulfilledまたはrejectedになったときに呼び出されるコールバック関数をそれぞれ登録することができます。

  • then( callback_fullfiled) : promiseが成功した場合に呼び出すコールバック関数を登録します。
  • catch(callback_rejected) : promiseが失敗した場合に呼び出すコールバック関数を登録します。

これらのメソッド自体もプロミスを返すので、次のように連結することができます。

rejectedの状態のpromiseから呼ばれたthenのハンドラーは、元のプロミスと同じ状態のものを返します。最初のpromiseがrejectedの状態であれば、次のthen()は同じrejectedのプロミスを返し、さらに次のthen()rejectedpromiseを返します。このpromiseにはcatch()が紐づけられているので、handler3が実行されます。このように、上記のコードでは連鎖の中のどこでエラーが起きても、最終的にはエラー処理handler3が実行されます。

以下は、fetchを使った実際の処理の例です。

この処理は、次のように非同期で処理されます。