##目次
1. はじめに
2. 本記事の対象読者
3. Reactとは
4. 非同期処理
5. 初心者の私がつまづいた非同期処理の方式3選
6. 初心者の私がつまづいたJSXでの表示制御方式2選
7. 総評
8. 参考サイト
##1. はじめに
今回、部内の合同新人研修の一環として開発演習に取り組みました。
私たちのチームは、コロナ下におけるwebサイト上での飲食店営業情報が不確かであること、に対して
飲食店を利用するユーザたちが主体となって正しい営業時間を共有しあおうというテーマでWebアプリを制作しました。
実装した機能・方式は下記のとおり
- 近隣の飲食店の情報をHotpepperAPIとGoogleAPIからとってきて一覧表示
- 一覧の中から選択したお店の店舗IDを基にHotpepperAPIを再度叩いて、店舗詳細を表示
- 各店舗に対して"入店できた/できなかった"ボタンとコメント機能の実装
私はAPIを叩く際の非同期処理と、UI面全般を主に担当したのですが
Reactにおける非同期処理の実装と、JSXならではの記法、お作法につまづき
大変苦しい思いをしました。
つまづいたこと/時間がかかったポイントを図で示すと以下のようになります。
この記事には、つまづいたことの振り返りとしてオレンジ色の文字で示したポイントについて説明します。
##2. 本記事の対象読者
- これからはじめてReactを用いたwebアプリ開発しようと考えている方
- 「非同期処理」や「コンポーネント構造・表示制御」について把握しておきたい方
- 同じ箇所でつまづいて、理解を深めたい方
##3. 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を採用しました。
##4. 非同期処理
実装した要件として上に
近隣の飲食店の情報をHotpepperAPIとGoogleAPIからとってきて一覧表示
と書きましたが、
補足すると、HotpepperAPIから近隣の飲食店の情報を10件とってきて
その情報(店舗名)をもとにGoogleAPIを10件10件ぶんコールするという処理になります。
このとき、HotpepperAPIからのレスポンスを待たずにGoogleAPIを叩いてしまう、という問題にぶつかり
非同期処理の実装方法に悩みました。
HotpepperAPIからのレスポンス取得10件分がすべて完了するのを待ち合わせて、次にGoogleAPIをコールする必要があるのですが、この待ち合わせ/非同期処理の実装方法が、メンバ毎に異なっている状況でした。
そこで、こういった非同期処理をJavaScript/Reactにて実装する方法を整理してみたいと思います。
##5. 初心者の私がつまづいた非同期処理の方式3選
###5-1. React Hooks useEffect()
React HooksはReact16.8(2019年2月6日リリース)で追加された新機能です。
state(そのコンポーネントが持っている状態)などのReactの機能をより簡単に扱えるようになったようです。
その中のuseEffect()は、関数コンポーネント内で副作用を実行するための機能です。
副作用は、関数コンポーネントの出力(レンダリング)に関係ない処理のことを指します。
例えば、APIを叩く処理等です。
useEffect()のすごいところは、**依存配列(dependencies)**を指定することができ
この依存配列に動きがあった場合に副作用の処理を実行する等
副作用の処理をどのタイミングで実行するか決めることができます。
本来useEffect()は非同期処理を行うためのものではありませんが
"処理が終わったら依存配列が更新される"等の処理を加えることで
"~の処理が終わるまで待つ"ことができます。
useEffect(() => {
// 実行したい処理
},[依存配列])
useEffectの動作を確認できるサンプルプログラムを以下に示します。
ここで使用しているuseState()はReact Hooksのひとつで
現在のstateとそれを更新するための関数をペアにして返します。
以下のプログラムでは、stateという名前のstate変数と、setStateという名前のstateを変更するための関数を宣言しています。
useEffectの依存配列としてstateを指定していますので、
ボタンがクリックされるたびにstateの値が更新 → useEffect内の処理 console.log('effect'); が実行されます。
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 と表示される
###5-2. Promise
Promiseはresolveとrejectを引数として、実行中の処理を監視し
処理が問題なく完了すればresolve、反対に問題があればrejectを呼びます。
ある処理が終わった際に実行される関数のことをコールバックといいますが、
コールバックの中でさらにコールバック、というようにコールバックを使いすぎてしまう(コールバック関数地獄)と
コードが読みにくくなってしまいます。
この解決方法として、Promiseという仕組みが存在します。
Promise を作る際に、実行したい非同期処理をコールバックとして登録し
非同期処理が終わる場所で resolve() もしくは失敗した場合のreject()を指定します。
new Promise(function(resolve, reject) {
resolve(成果); //非同期な処理が成功したとき成果となる値を渡す
reject(問題); //非同期な処理が失敗したとき問題となるエラーオブジェクトなどを渡す
});
###5-3. async, await
async,awaitはPromiseをより簡単に使うことができる文法です。
関数の前に**"async"**とつけて非同期関数として定義します。非同期関数は呼び出されるとPromiseを返します。
awaitはasyncをつけた関数内でのみ使うことができ、await hoge() とするとhoge()の処理が終了するまで待つことができます。
※awaitはPromiseしか待たないことに注意!
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の処理が完了するまで次の処理を待っていることが確認できますね。
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を使っていこうと思いました。
##6. 初心者の私がつまづいたJSXでの表示制御方式2選
###6-1. childrenって何 -使い方
6-1. 6-2. では、JSXに関してのつまづきポイントを紹介します。
今回のプロジェクトの土台はチームのリーダーが作成してくれたのですが
その中で見たことがない記法 {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>
<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}として他要素がまとめて表示されます。
###6-2. JSX内での条件式
Reactでは、JSX内に条件式を埋め込むことができます。が、
その方法を知らなかったので、開発演習にてはじめての学びとなりました。
この記事の執筆中に気づいたのですが、条件式の埋め込み方は複数あり
JavaScriptの論理演算子**&&**を用いる方法、三項演算子を用いる方法、即時関数を用いる方法があるようです。
ここでは実際に実装に用いたJavaScriptの論理演算子&&を用いる方法と、
自分の学びメモとして、即時関数を用いる方法を紹介します。
{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内にどのように条件式を埋め込むかというと
<div>
{
(() => {
if(isFlag){
return 'hello world';
}
})()
}
</div>
このようになります。
即時関数の例では、アロー(=>)を用いず ( function ( ) { 文 } )( ) という形にしていましたが、
JSX内で即時関数を用いる場合、( ( )= >{ 文 } )( ) このようにアローを用いる必要があるようです。
JSX内で即時関数を用いて条件式を埋め込む場合、
適当なタグの間に{ }で囲った即時関数の構文を用意し、
さらにその中に条件式を書くことで、JSX内での条件式として扱えるようです。
今回&&を使う方法と即時関数を使う方法を紹介しましたが、
私の感想としては、即時関数を用いるとカッコの数が増えて可読性が低くなるため
&&を使った方がきれいだなあと思いました。
##7. 総評
開発演習でつまづいた「非同期制御」と「表示制御」に関して、
最終成果発表が終わって時間がたたないうちにこのように記事として残すことで、良い復習・理解を深める作業になりました。
感じたこととしては、まず公式リファレンスを見てサンプルコードを理解することが大事ということと、
それでも解決しない問題がある場合は、Reactは利用ユーザ数が多いのでやりたいことを検索すれば大体ヒットする! のが良いなと思いました。
あと、今までは簡単なホームページをHTML,CSSで作成した経験しかなかったので、
jsの中にHTMLを書けちゃうJSXの記法はとても便利に感じました。
他のAngular.jsやVue.jsも使ってみて、求められた方法でフレキシブルに開発ができるようになっていきたいです。
##8. 参考サイト
useEffect()
Promise
async / await
{children}
JSX内での条件式