[勉強メモ] Reactを使い「音楽プレイヤー」を作ります
こちらを元に実装しながら詰まったところや勉強になったところを、メモとして残していく
repo: https://github.com/sousquared/music-app
完成物
なぜかvercelではうまくdeployできない…
project作成
npx create-react-app music-app
tailwindcss
環境構築しなくても試せるサイト
tailwindcssを使うと、cssをほとんど書かなくていい
インストール
yarn add -D tailwindcss
yarn add -D tailwindcss コマンドの -D は、--dev の短縮形です。これは、パッケージを開発依存関係(devDependencies)としてインストールするためのオプションです。
具体的には、tailwindcss をプロジェクトの開発時に必要なパッケージとしてインストールし、プロダクション環境でのビルドや本番稼働時には含めないようにします。開発中にのみ必要なツールやライブラリ(例えば、テストフレームワークやビルドツールなど)は、devDependencies に含めるのが一般的です。
tailwind.config.jsとindex.cssでのimport
spotify api
dashboard
Redirect URIsは適当で大丈夫
webAPIにチェック
環境変数
.env に設定する (gitignoreすることを忘れない)
REACT_APP_ のprefixが必要
process.env.{環境変数名} でアクセス可能
spotify
Spotify Japanのデイリー急上昇チャート
API: Get Playlist Items
Playlist IDはチャートのURLから取れる(37i9dQZF1DX9vYRBO9gjDe)
array, fill, map
{Array(20)
.fill()
.map(() => {
return (
<div className="flex-none cursor-pointer ">
<img
alt="thumbnail"
src={
'https://i.scdn.co/image/ab67616d0000b2738b7a8c1322028d45a8355f7a'
}
className="mb-2 rounded"
/>
<h3 className="text-lg font-semibold">Song Name</h3>
<p className="text-gray-400">By Artist</p>
</div>
);
})}
Array(20)
1. まず、Array(20)
は長さが20の配列を作成します。しかし、この配列は要素が「空」の状態、つまり各インデックスに値が設定されていない状態です。このとき、配列の内容は以下のようになります:
[empty × 20]
このような「空のスロット」は、JavaScriptにおいては未定義の値 undefined
が入っているわけではなく、要素そのものが存在しない状態です。
.fill()
2. 次に .fill()
メソッドを使用します。.fill()
は配列の全ての要素を指定した値で埋めるメソッドです。引数を指定しない場合、デフォルトで undefined
が使用されます。
Array(20).fill()
これにより、配列の全ての要素が明示的に undefined
で埋められます。この状態の配列は以下のようになります:
[undefined, undefined, ..., undefined] // 長さ20
.fill()
を使用する理由は、配列のメソッドである .map()
や .forEach()
は要素が「存在する」場合にのみコールバック関数を適用するためです。要素が「空」のままだと、これらのメソッドはその要素を無視します。fill()
を使って要素を埋めることで、全ての要素に対してコールバック関数を適用できるようになります。
.map(() => { ... })
3. 最後に .map()
メソッドを使用します。.map()
は配列の各要素に対してコールバック関数を適用し、その結果から新しい配列を生成します。
Array(20)
.fill()
.map(() => {
// コールバック関数の内容
});
このコードでは、コールバック関数内で以下のようなReactのJSX要素を返しています:
return (
<div className="flex-none cursor-pointer">
<img
alt="thumbnail"
src={'https://i.scdn.co/image/ab67616d0000b2738b7a8c1322028d45a8355f7a'}
className="mb-2 rounded"
/>
<h3 className="text-lg font-semibold">Song Name</h3>
<p className="text-gray-400">By Artist</p>
</div>
);
つまり、配列の各要素(この場合は20個の undefined
)に対して、このJSX要素が生成されます。その結果、20個の同一のコンポーネントが含まれた配列が作成されます。
まとめ
-
Array(20)
:長さ20の「空」の配列を作成します。 -
.fill()
:配列の全ての要素をundefined
で埋め、要素が「存在する」状態にします。 -
.map(() => { ... })
:各要素に対してコールバック関数を適用し、JSX要素(Reactコンポーネント)を生成します。
このコード全体の目的は、20個の同一のコンポーネントを効率的に生成することです。例えば、音楽アプリやウェブサイトで、一覧表示するアイテムが複数ある場合に、このような手法でUIを構築することができます。
undefined
の値が入った要素」の違い
補足:配列の「空のスロット」と「-
空のスロット(sparse array):要素そのものが存在しない状態。
Array(20)
で生成される配列はこの状態です。この状態では、map()
やforEach()
などのメソッドは要素を無視します。 -
undefined
の値が入った要素:要素は存在し、その値がundefined
になっている状態。Array(20).fill()
で生成される配列はこの状態です。この状態では、map()
やforEach()
などのメソッドは要素を正しく処理します。
実際の使用例
このコードは、Reactを使用したコンポーネントのレンダリングにおいて、特定の数の同一コンポーネントを繰り返し表示したい場合に有用です。例えば、ダミーデータを表示してレイアウトを確認したい場合や、ローディング中にプレースホルダーとして表示する際に使われます。
注意点:
-
キーの指定:Reactで配列をレンダリングする際には、各要素に一意の
key
プロパティを設定することが推奨されます。このコードではkey
が指定されていないため、実際のアプリケーションではエラーや警告が発生する可能性があります。例:
map((_, index) => { return ( <div key={index} className="flex-none cursor-pointer"> {/* ... */} </div> ); });
-
パフォーマンスの最適化:大量の要素を一度にレンダリングすると、パフォーマンスに影響を与える可能性があります。必要に応じて、仮想スクロールやページネーションなどの手法を検討してください。
useRef
const audioRef = useRef(null);
const handleSongSelected = async (song) => {
setSelectedSong(song);
audioRef.current.src = song.preview_url;
audioRef.current.play();
setIsPlay(true);
};
useRef
フックとは
1. useRef
はReactのフックの一つで、ミューテーブルな(可変の)参照オブジェクトを作成します。このオブジェクトはコンポーネントのライフサイクル全体を通じて持続し、その間に値を保持または更新することができます。
const myRef = useRef(initialValue);
-
myRef
は{ current: initialValue }
という形のオブジェクトを返します。 -
initialValue
は参照の初期値です。
current
プロパティ
2. useRef
が返すオブジェクトには current
というプロパティがあります。この current
が実際の値を保持します。
myRef.current = newValue;
-
myRef.current
は、必要に応じて読み書きできます。 - Reactの再レンダー間で値を保持したい場合や、DOM要素への参照を保持したい場合に使用します。
3. DOM要素への参照
useRef
は主に以下の2つの用途で使用されます:
- DOM要素への直接アクセス:特定のDOM要素を直接操作したい場合。
- ミューテーブルな値の保持:レンダリング間で持続するミューテーブルな値を保持したい場合。
DOM要素への参照の例
import { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus(); // input要素にフォーカスを当てる
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>フォーカス</button>
</div>
);
}
-
inputRef
はuseRef(null)
によって初期化されています。 -
<input ref={inputRef} />
で、input
要素の参照をinputRef.current
に割り当てます。 -
inputRef.current
を通じてinput
要素にアクセスし、focus()
メソッドを呼び出しています。
current
4. ご提供のコードにおける ご提供いただいたコードを見てみましょう:
const audioRef = useRef(null);
const handleSongSelected = async (song) => {
setSelectedSong(song);
audioRef.current.src = song.preview_url;
audioRef.current.play();
setIsPlay(true);
};
解説
-
audioRef
の宣言const audioRef = useRef(null);
-
audioRef
は初期値null
を持つ参照オブジェクトです。 - つまり、
audioRef
は{ current: null }
というオブジェクトになります。
-
-
audioRef
を DOM 要素に結びつけるコード上では示されていませんが、おそらくレンダリング部分で
audio
要素にref
属性を付けているはずです。<audio ref={audioRef} />
- これにより、
audioRef.current
にaudio
要素の参照が代入されます。
- これにより、
-
audioRef.current
の使用audioRef.current.src = song.preview_url; audioRef.current.play();
-
audioRef.current
を通じて、audio
要素のプロパティやメソッドにアクセスしています。 -
src
に曲のプレビューURLを設定し、play()
メソッドで再生を開始しています。
-
5. まとめ
-
useRef
:Reactのフックで、ミューテーブルな参照オブジェクト{ current }
を返します。 -
current
:useRef
が返すオブジェクトのプロパティで、現在の参照先の値を保持します。 -
audioRef.current
:audio
DOM要素への参照を保持しており、そのプロパティやメソッドにアクセスできます。 -
用途:
current
を使用して、再レンダー間で持続する値を保持したり、DOM要素を直接操作できます。
補足:
-
useRef
と再レンダー:-
useRef
のcurrent
プロパティが更新されても、コンポーネントは再レンダーされません。 - そのため、
useState
と異なり、値の更新が再レンダーをトリガーしない場合に適しています。
-
-
なぜ
current
なのか:-
useRef
は単に値を保持するだけでなく、参照先の値が変化しても再レンダーを引き起こさない特性があります。 -
current
プロパティはそのために存在し、ミューテーブルな値を保持します。
-
具体的な例:
もしコード内で audio
要素を定義している部分が以下のようになっているとします:
return (
<div>
{/* 他のコンポーネントや要素 */}
<audio ref={audioRef} />
{/* 他のコンポーネントや要素 */}
</div>
);
- ここで、
<audio ref={audioRef} />
により、audioRef.current
には<audio>
要素の参照が格納されます。 - その後、
audioRef.current.src
やaudioRef.current.play()
を用いて音声の操作が可能になります。
まとめると、current
は useRef
フックが返すオブジェクトのプロパティであり、現在の参照先、つまり実際の値やDOM要素を指し示すものです。これを利用して、コンポーネント内で直接DOM要素を操作したり、ミューテーブルな値を保持することができます。
条件付きレンダリング
{selectedSong != null && <Player />}
このコードは、Reactコンポーネントのレンダー部分でよく用いられる条件付きレンダリングのパターンです。
意味と動作
-
selectedSong != null
:selectedSong
がnull
ではない、つまり何らかの値が設定されている場合をチェックしています。 -
&&
:論理AND演算子です。左側の条件がtrue
の場合にのみ、右側の値を評価・返します。 -
<Player />
:Player
というReactコンポーネントをレンダリングします。
したがって、このコードは次のような意味になります:
selectedSong
がnull
でなければ、<Player />
コンポーネントをレンダリングする。selectedSong
がnull
(またはundefined
)の場合、何もレンダリングしない。
別の書き方との比較
条件付きレンダリングは他の方法でも実現できます。例えば、三項演算子を使う方法です。
{selectedSong != null ? <Player /> : null}
しかし、論理AND演算子を使うパターンの方がコードが簡潔になるため、よく使用されます。
注意点
-
厳密等価演算子の使用:
!=
よりも!==
(厳密不等価演算子)を使うことで、型の違いによる予期せぬ挙動を防ぐことができます。{selectedSong !== null && <Player />}
-
undefined
の考慮:selectedSong
がundefined
になる可能性がある場合、null
とundefined
の両方を考慮するために、単にselectedSong
を条件として使うこともできます。{selectedSong && <Player />}
- この場合、
selectedSong
がnull
やundefined
、false
、0
、''
(空文字列)などの「falsy」な値であれば、<Player />
はレンダリングされません。 - ただし、
selectedSong
が数値の0
や空文字列の可能性がない場合、この方法で問題ありません。
- この場合、
応用例
以下は、このパターンを他の状況で適用する例です。
-
ユーザーの認証状態に応じた表示
{isLoggedIn && <Dashboard />}
-
isLoggedIn
がtrue
の場合、<Dashboard />
コンポーネントを表示します。
-
-
エラーメッセージの表示
{errorMessage && <div className="error">{errorMessage}</div>}
-
errorMessage
が存在する場合、その内容を表示します。
-
play()メソッドについて
play()
メソッドは、JavaScriptの標準的なWeb APIである HTMLMediaElement
インターフェースから提供されるメソッドです。これは、<audio>
や <video>
要素などのメディア要素に対して使用できます。
具体的な説明
-
audioRef
の宣言const audioRef = useRef(null);
-
audioRef
は初期値null
を持つ参照オブジェクトです。 -
useRef
フックを使用して、コンポーネント内で<audio>
要素への参照を保持します。
-
-
<audio>
要素への参照の設定レンダリング部分で
<audio>
要素にref
属性を設定しているはずです。return ( <div> {/* その他のコンポーネントや要素 */} <audio ref={audioRef}></audio> {/* その他のコンポーネントや要素 */} </div> );
-
<audio>
要素にref={audioRef}
を設定することで、audioRef.current
に<audio>
要素の DOM ノードが格納されます。
-
-
play()
メソッドの呼び出しconst handleSongSelected = async (song) => { setSelectedSong(song); audioRef.current.src = song.preview_url; // 再生する音声のソースを設定 audioRef.current.play(); // 再生を開始 setIsPlay(true); };
-
audioRef.current
は<audio>
要素を参照しています。 -
audioRef.current.play()
により、音声の再生を開始します。
-
play()
メソッドの出所
-
HTMLMediaElement.play()
-
play()
は、ブラウザの組み込みオブジェクトであるHTMLMediaElement
インターフェースが提供するメソッドです。 -
<audio>
要素はHTMLAudioElement
であり、これはHTMLMediaElement
を継承しています。 -
play()
メソッドはメディア(音声や動画)の再生を開始します。 - このメソッドは 非同期 であり、
Promise
オブジェクトを返します。
-
-
主なメディア要素とメソッド
-
<audio>
および<video>
要素は以下のようなメソッドを持っています:-
play()
: 再生を開始 -
pause()
: 再生を一時停止 -
load()
: ソースを再読み込み - その他、再生位置や音量を制御するプロパティ
-
-
注意点と補足
-
自動再生の制限
- ブラウザによっては、ユーザー操作なしに音声や動画の自動再生がブロックされる場合があります。
-
play()
メソッドを呼び出した際に、Promise
が拒否され(エラーが発生し)、再生が開始されないことがあります。
audioRef.current.play().catch((error) => { // 再生に失敗した場合の処理 console.error('再生エラー:', error); // 必要に応じてユーザーに通知や、代替処理を実行 });
-
async/await
の使用-
play()
メソッドはPromise
を返すため、await
を使用して再生が開始されるのを待つことができます。
const handleSongSelected = async (song) => { try { setSelectedSong(song); audioRef.current.src = song.preview_url; await audioRef.current.play(); // 再生が開始されるのを待つ setIsPlay(true); } catch (error) { // 再生エラーの処理 console.error('再生エラー:', error); } };
-
-
ユーザー体験の向上
- 再生ボタンを設けて、ユーザーの操作によって再生を開始することで、自動再生の制限を回避できます。
<button onClick={() => audioRef.current.play()}>再生</button>
まとめ
-
play()
メソッドは、ブラウザの標準的なWeb APIであるHTMLMediaElement
インターフェースから提供されるものです。 -
audioRef.current
が<audio>
要素を参照しているため、そのplay()
メソッドを呼び出すことで音声の再生を制御できます。 -
メディアの再生制御には他にも
pause()
,currentTime
,volume
など、多くのプロパティやメソッドがあります。