Columnコラム

【aslead新人研修紹介】
React初心者の私が開発演習でつまづいたこと5選!

2021年12月2日

この記事では、今年度よりasleadチームに参加した新人メンバーによる開発演習の様子をご紹介します。

はじめに


私たちのチームは、コロナ下におけるwebサイト上での飲食店営業情報が不確かであること、に対して
飲食店を利用するユーザたちが主体となって正しい営業時間を共有しあおうというテーマでWebアプリを制作しました。

開発演習もて実装した機能・方式は下記のとおり
・近隣の飲食店の情報をHotpepperAPIとGoogleAPIからとってきて一覧表示
・一覧の中から選択したお店の店舗IDを基にHotpepperAPIを再度叩いて、店舗詳細を表示
・各店舗に対して"入店できた/できなかった"ボタンとコメント機能の実装

私はAPIを叩く際の非同期処理と、UI面全般を主に担当したのですが
Reactにおける非同期処理の実装と、JSXならではの記法、お作法につまづき 大変苦しい思いをしました。

つまづいたこと/時間がかかったポイントを図で示すと以下のようになります。
この記事には、つまづいたことの振り返りとしてオレンジ色の文字で示したポイントについて説明します。

困ったポイント


本記事の対象読者

  • これからはじめてReactを用いたwebアプリ開発しようと考えている方
  • 非同期処理コンポーネント構造・表示制御について把握しておきたい方
  • 同じ箇所でつまづいて、理解を深めたい方

Reactとは

Reactとは、Facebook社が開発しているWebサイト上のUIを構築するためのJavaScriptライブラリです。
Webページ内の各パーツをコンポーネントとして扱うことができる点や、
それをJavaScriptの中にHTMLタグで記述するJSXが使える点、
Virtual DOM(仮想DOM)を採用し、描画性能が優れている・高速などの特徴があります。
Vue.jsは.vueファイル内にHTML,JavaScript,CSSを3つエリア分けして書くのに対して
Reactは基本的にJavaScriptファイルのみで構成される(JSX記法)ため、Vue.jsのほうが初心者には優しいようです。

本開発演習では、先発で使用ユーザ数も多いReactを採用しました。
React - ユーザインターフェース構築のためのJavaScriptライブラリ

非同期処理

実装した要件として上に近隣の飲食店の情報をHotpepperAPIとGoogleAPIからとってきて一覧表示と書きましたが、
補足すると、HotpepperAPIから近隣の飲食店の情報を10件とってきて
その情報(店舗名)をもとにGoogleAPIを10件10件ぶんコールするという処理になります。
このとき、HotpepperAPIからのレスポンスを待たずにGoogleAPIを叩いてしまう、という問題にぶつかり
非同期処理の実装方法に悩みました。

HotpepperAPIからのレスポンス取得10件分がすべて完了するのを待ち合わせて、次にGoogleAPIをコールする必要があるのですが
この待ち合わせ/非同期処理の実装方法が、メンバ毎に異なっている状況でした。

そこで、こういった非同期処理をJavaScript/Reactにて実装する方法を整理してみたいと思います。

初心者の私がつまづいた非同期処理の方式3選

React Hooks useEffect()

React HooksはReact16.8(2019年2月6日リリース)で追加された新機能です。
state(そのコンポーネントが持っている状態)などのReactの機能をより簡単に扱えるようになったようです。
その中のuseEffect()は、関数コンポーネント内で副作用を実行するための機能です。
副作用は、関数コンポーネントの出力(レンダリング)に関係ない処理のことを指します。例えば、APIを叩く処理等です。

useEffect()のすごいところは、依存配列(dependencies)を指定することができ、この依存配列に動きがあった場合に副作用の処理を実行する等、副作用の処理をどのタイミングで実行するか決めることができます。
本来useEffect()は非同期処理を行うためのものではありませんが"処理が終わったら依存配列が更新される"等の処理を加えることで"~の処理が終わるまで待つ"ことができます。

●useEffectの定義

      useEffect(() => {
        // 実行したい処理
      },[依存配列])

useEffectの動作を確認できるサンプルプログラムを以下に示します。
ここで使用しているuseState()はReact Hooksのひとつで現在のstateとそれを更新するための関数をペアにして返します。
以下のプログラムでは、stateという名前のstate変数と、setStateという名前のstateを変更するための関数を宣言しています。
useEffectの依存配列としてstateを指定していますので、
ボタンがクリックされるたびにstateの値が更新 → useEffect内の処理
console.log('effect');
が実行されます。

●useEffectのサンプルプログラム

      const main = () => {
        const [state, setState] = useState('')

        useEffect(() => {
            console.log('effect');
        }, [state]);

        const onClick = () => {
            setState(s => s + 1);
        };

        return (
            <div>
                <p>{state}</p>
                <button type="button" onClick={onClick}>
                    click
                </button>
            </div>
        )
    }
    //ボタンがクリックされるたびにconsoleに effect と表示される
  • useEffect

Promise

Promiseはresolveとrejectを引数として、実行中の処理を監視し処理が問題なく完了すればresolve、反対に問題があればrejectを呼びます。
ある処理が終わった際に実行される関数のことをコールバックといいますが、コールバックの中でさらにコールバック、というようにコールバックを使いすぎてしまう(コールバック関数地獄)と、コードが読みにくくなってしまいます。
この解決方法として、Promiseという仕組みが存在します。
Promise を作る際に、実行したい非同期処理をコールバックとして登録し、非同期処理が終わる場所で resolve() もしくは失敗した場合のreject()を指定します。

●Promiseの作成方法

      new Promise(function(resolve, reject) {
        resolve(成果); //非同期な処理が成功したとき成果となる値を渡す
        reject(問題); //非同期な処理が失敗したとき問題となるエラーオブジェクトなどを渡す
    });

async, await

async,awaitはPromiseをより簡単に使うことができる文法です。
関数の前にasyncとつけて非同期関数として定義します。非同期関数は呼び出されるとPromiseを返します。
awaitはasyncをつけた関数内でのみ使うことができ、await hoge() とするとhoge()の処理が終了するまで待つことができます。
※awaitはPromiseしか待たないことに注意!

●async,awaitの使い方

      function foo() { // 非同期処理を含み Promiseを返す関数
        return new Promise((resolve, reject) => {
          // 非同期処理
          // 処理終了箇所でresolve()を実行、エラーなら reject() を実行
          // resolve(成果); 非同期な処理が成功したとき成果となる値を渡す
          // reject(問題); 非同期な処理が失敗したとき問題となるエラーオブジェクトなどを渡す
        });
      }

      async function main() {
        try {
          await foo(); // foo()の処理が終わるまで待つ
          bar(); // foo()の処理が終わったら実行される

        } catch(err) {
          alert(err);
        }
      }
      main();

async,awaitを使用したサンプルプログラムを以下に示します。
ここでのuseEffectは、wait()をレンダリング後に1度だけ実行させるために用いています。

async function wait()内で、Promiseの宣言をしていますが、 Promiseの宣言前にawait をつけない場合、Start、Finishが表示された3秒後に!setTimeout!が表示され、 Promiseの宣言前にawait をつける場合、Startが表示された3秒後に!setTimeout!、Finishが表示されます。
awaitがある場合はPromiseの処理が完了するまで次の処理を待っていることが確認できますね。

  • useEffect

●async,awaitを使用したサンプルプログラム

      const Test = () => {
        useEffect(() => {
            wait()
        }, [])
        return (<p>test</p>)
    }

    async function wait() {
        console.log('Start')
        await new Promise((resolve, reject) => setTimeout(() => {
            console.log('!setTimeout!')
            resolve()
        }, 3000));
        console.log('Finish')
    }

今回Reactにおける非同期処理実装方法を学びましたが、調査した中では、async/awaitを用いた方法が王道でした。
useEffect()は第一引数にPromiseを設定するとバグが起きたり、初回レンダリング時に必ず実行されたりと扱うのに少しコツがいる印象なので私の感想としても、今後非同期処理を実装する際はasync/awaitを使っていこうと思いました。

初心者の私がつまづいたJSXでの表示制御方式2選

childrenって何 -使い方

ここからは、JSXに関してのつまづきポイントを紹介します。
今回のプロジェクトの土台はチームのリーダーが作成してくれたのですが、その中で見たことがない記法 {children}がありました。

●childrenが使われていた箇所

      const Layout = ({children}) => {
        return (
          <div className={Style.wrapper}>
            <Header />
                {children}
          </div>
        )
    }

childrenの正体は、props.children。 {children}と書いても{props.children}と書いても良いようです。
この記述は、汎用的な"入れ物"を表すコンポーネント(上の例でいうとLayoutコンポーネント)を親コンポーネントとして、親コンポーネントのタグの間に入れられた要素(子要素)を出力するという意味です。
これにより他のコンポーネントからJSXをネストすることで、任意の子要素を渡すことができます。

以下の例のように、Layoutコンポーネントのタグの中に要素を記述するとまずLayoutコンポーネント内で指定されているHeaderコンポーネントの内容が表示され、その次に{children}としての要素(Layoutタグで挟まれている箇所)が表示されます。

●Layoutコンポーネント使用例

      <Layout>
        <div className=" w-3/4 mx-auto">
            <div className="grid grid-cols-4">
                <div className="col-span-2">
                    {selectedShop && <ShopImage src={selectedShop.photo.pc.l} alt={selectedShop.id} />}
                </div>
                <div className="col-span-2">
                    <EnterGragh />
                </div>
            </div>
            <div className="mx-auto">
                <ShopDetail selectedShop={selectedShop} /></div>
            <EnterButtonlist />
            <CommentApp />
        </div>
    </Layout>

Layoutのみを出力すると、図①のようにLayoutコンポーネントに含まれるHeaderコンポーネントのみ表示されますが、上のコードのように、Layoutタグで他要素を挟むと、図②のようにHeaderコンポーネント以下に{children}として他要素がまとめて表示されます。

  • 画面レイアウト

  • 画面レイアウト({chilfren}含む)

JSX内での条件式

Reactでは、JSX内に条件式を埋め込むことができます。が、その方法を知らなかったので、開発演習にてはじめての学びとなりました。
この記事の執筆中に気づいたのですが条件式の埋め込み方は複数あり、JavaScriptの論理演算子&&を用いる方法、三項演算子を用いる方法、即時関数を用いる方法があるようです。
ここでは実際に実装に用いたJavaScriptの論理演算子&&を用いる方法と、自分の学びメモとして、即時関数を用いる方法を紹介します。

●JSX内条件式&&のサンプル

      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }

条件部分がtrueであれば、&&の後に書かれた要素が出力されます。
falseの場合は、後に書かれた要素は無視されます。
即時関数とは?
関数呼び出しをせず、実際に関数を定義したものにすぐ引数を与えて即時実行される無名関数のことを指します。

●即時関数の例

      const display_num = (function(arg1, arg2){
        // ここになんらかの処理 ※例↓
      console.log(arg1)
        console.log(arg2)
    })(1,2);

一か所にしか使わない処理に関して、即時関数を使ってシンプルに書くことができます。
この即時関数を用いて、JSX内にどのように条件式を埋め込むかというと...

●JSX内条件式即時関数のサンプル

  <div>
    {
      (() => {
        if(isFlag){
            return 'hello world';
        }
      })()
    }
  </div>

このようになります。
即時関数の例では、アロー(=>)を用いず ( function ( ) { 文 } )( ) という形にしていましたが、JSX内で即時関数を用いる場合、( ( )= >{ 文 } )( ) このようにアローを用いる必要があるようです。
JSX内で即時関数を用いて条件式を埋め込む場合、適当なタグの間に{ }で囲った即時関数の構文を用意し、さらにその中に条件式を書くことで、JSX内での条件式として扱えるようです。
今回&&を使う方法と即時関数を使う方法を紹介しましたが、私の感想としては、即時関数を用いるとカッコの数が増えて可読性が低くなるため&&を使った方がきれいだなあと思いました。

総評

開発演習でつまづいた「非同期制御」と「表示制御」に関して、最終成果発表が終わって時間がたたないうちにこのように記事として残すことで、良い復習・理解を深める作業になりました。
感じたこととしては、まず公式リファレンスを見てサンプルコードを理解することが大事ということと、それでも解決しない問題がある場合は、Reactは利用ユーザ数が多いのでやりたいことを検索すれば大体ヒットする! のが良いなと思いました。
あと、今までは簡単なホームページをHTML,CSSで作成した経験しかなかったので、jsの中にHTMLを書けちゃうJSXの記法はとても便利に感じました。
他のAngular.jsやVue.jsも使ってみて、求められた方法でフレキシブルに開発ができるようになっていきたいです。

参考サイト

useEffect()
https://tyotto-good.com/blog/basic-useeffect
https://ja.reactjs.org/docs/hooks-intro.html
Promise
https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises
async / await
https://qiita.com/niusounds/items/37c1f9b021b62194e077
https://laboradian.com/use-js-async-await/
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/async_function
{children}
https://ja.reactjs.org/docs/composition-vs-inheritance.html
JSX内での条件式
https://ja.reactjs.org/docs/conditional-rendering.html

お問い合わせ

aslead に関するご相談は、下記よりお気軽にお問い合わせください。

簡易フォームでお問い合わせ   チャットBotでお問い合わせ ※個人情報は、お問い合わせ対応に利用致します。
お問い合わせ