ポッドキャスト始めてみたくてReact触ってみた
経緯とか
最近、Tech系のポットキャストを趣味で聴き始めたのですが、そんな話を仲間内でしたら「自分たちでもやってみない?」ということになったのでReact使って公開用のページを作ってみました。とはいえ、React初心者が作ったものなので、クオリティはお察しです。 今回の目標は、 - Youtubeの動画を埋め込む - 毎回の放送をカードで表示する - カードの内容をGoogle Sprede Sheetから取ってくる - Twitterのタイムラインを埋め込む
ができれば最低限の体裁はできるかなと言うことで、取っ付きやすそなのから順番に試して意図通りに動いたら即採用。なので、もっといい方法はきっとあると思いますが、そこは今後追求します。
環境構築etc.
React初心者なので、ディレクトリ構成とかBabelとか言われてもちんぷんかんぷんなので、その辺りをよしなにしてくれるcreate-react-appを使うことにします。なんでもfacebookが提供しているお手軽プロジェクトビルダーだとか。インストールにはこちらを参考にしました。 Windows+VSCode上でReact開発環境を構築 Macの場合は私はこんな感じでインストールしました。 MacでReactの環境を作ろうと思ってコケた あとはこのあたりを参考にざっと斜め読みして、書き方のお作法を勉強しました。 jQueryを卒業したかった僕がReact StaticでReactをイチから学んでWebサイトを作った話
Youtubeの動画を埋め込む
React Player 日本語の参考ページがみたい方はこちら Reactアプリケーションで動画や音楽ファイルを扱うために「react-player」を使用する ページにしたがって、
$ npm install react-player --save
でインストールする。今回は音声ファイルはYoutubeにあげるつもりなので、Youtubeの動画を再生できればOKにします。React Playerはいろんな形式の動画を再生できそうなんだけど、Youtubeは専用のPlayerが用意されているようなので、そいつを使うことに。これも公式ページの通りに組んでみると、ちゃんと再生できたし、シークバーも表示されました。純粋なReact Playerだとシークバーは出ませんでした。
毎回の放送をカードで表示する
そろそろBootstrapを卒業しようと思うので、Reactと相性がいいらしい、Material UIを使ってみることにします。 【React】Material-UIでReactアプリケーションをマテリアルデザイン化する もっと凝ったUIを作りたい場合は公式ページにソース付きでデモがあるのでお試しあれ。 Material-UI Cards 他の候補としてはこの辺りがおすすめぽいです。 無料のマテリアルデザインフレームワーク10選
カードの内容をGoogle Sprede Sheetから取ってくる
これが正直一番難しかったです。最終的に取った手段が 1. Google Spread SheetをGoogle Apps Script(GAS)で取得&JSON形式で返す 1. ReactでJSONを読み込んで所望の形式に変換して表示
でした。 まず、こんな感じのSpread Sheet を用意します。
id | title | url | description |
---|---|---|---|
No.1 | Title1 | https://www.youtube.com/watch?v=*** | その回の説明を書く |
No.2 | Title2 | https://www.youtube.com/watch?v=*** | 説明2 |
No.3 | Title3 | https://www.youtube.com/watch?v=*** | 説明3 |
これをGASでJSON形式でGETリクエストに対して返してあげるようにします。こちらを参考に実装しました。 Google App Scriptを用いてGoogleスプレッドシートからJSONを生成してみよう 実践編の通りにGASを書いて1行目をkeyにしてこんな感じのJSONを取得できるようにしました。
[{"id":"No.1","title":"Title1","url":"https://www.youtube.com/watch?v=***","description":"その回の説明を書く"},{"id":"No.2","title":"Title2","url":"https://www.youtube.com/watch?v=***","description":"説明2"},{"id":"No.3","title":"Title3","url":"https://www.youtube.com/watch?v=***","description":"説明3"}]
セキュリティ的にはガバガバですが、まあ、これで半分クリアです。残りは、React側での受け取り処理を作ります。今回はaxiosを使います。Reactから手軽にHTTPリクエストが送れるライブラリのようです。 例によって参考ページはこちらです。 React+axiosでLaravel APIからJSON取得 axiosで取得して、先ほどのCardの中に流し込みます。
{// 一部抜粋 } import axios from 'axios'; class App extends Component { constructor(props) { super(props); this.state = { contents: [] }; this.getData = this.getData.bind(this); } getData() { axios .get('GASの公開URL') .then(results => { const data = results.data; console.log(data); this.setState({ contents: [...data] }); }); } render() { const classes = this.props.classes; console.log(classes); const contents = this.state.contents.map(content => { return <Card className={classes.card}> <CardContent> <h2 key={content.id}>{content.title}</h2> <div align="center" key={content.id}> <YoutubePlayer url={content.url} controls /> </div> <p key={content.id}>{content.description}</p> </CardContent> </Card> }); return ( <Fragment> <div className="App"> <button onClick={this.getData}>getData</button> {contents} </div> </Fragment> ); } } export default withStyles(styles)(App);
基本的にはこれでできたのですが、このままだとユーザーが毎回ボタンを押さないとGASから読み込みが行われないので、ページロード時点で読み込めるようにします。やりたいこととしてはonLoad
のようなことをしたいのですが、Reactの場合は、componentDidMount
をよく初期化に使うと聞いたので、これを使います。本当は使い方気をつけないと無限ループとは発生して危ないらしいのですが、冒頭の通り動いているからまあいっかの精神で書きます。
Reactのライフサイクルメソッドとその使いドコロのまとめ - ajax callをするのに最も適した場所は?
先ほどのコードでgetData
の部分を全てcomponentDidMount
に置換します。<button>
の部分はいらないので削除します。これでリロードした度にGASから取得して表示してくれるようになりました。コンテンツ量が増えると崩壊しそうなので、そこはページを繰る度にロードするようにするなど要改善です。
その他参考になりそうな記事はこちら。
参考文献
- 3分で API を作って世の中にデプロイするライブコーディング今日から君もスピードスターエンジニア
- Googleスプレッドシートのデータを表示させてリッチな静的サイトを作る
- [axios] axios の導入と簡単な使い方
- ReactでAxiosを使ってGoogleAPIを叩いてみた
- react-reduxでページ読み込み時、actionを呼び出す方法
Twitterのタイムラインを埋め込む
React Twitter Wigets ページのチュートリアルにしたがって、
$ npm i react-twitter-widgets
で落としてきて埋め込みます。なんとお手軽で、screenName
とusername
に埋め込みたいアカウント名(@で始まるやつ)を書くだけ。たぶんsourceType
を変えれば表示内容も変えられると思われる。今回は自分のアカウントのツイートを表示したいだけなので、デフォルトのprofile
にしてます。
今回実装した全体のコード
以上、これで当初の目的は達成できました。
全体のコードを晒しておきます。コンポーネント化は度外視なので全部App.js
に書いてます。
肝心の放送内容はこれから収録します...近いうちに投稿したいなあ...もし、無事投稿できたらそちらもよろしくお願いします。
import React, { Component, Fragment } from 'react'; import axios from 'axios'; import ReactPlayer from 'react-player'; import YoutubePlayer from 'react-player/lib/players/YouTube'; import { withStyles } from '@material-ui/core/styles'; import Button from '@material-ui/core/Button'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; import Typography from '@material-ui/core/Typography'; import Card from '@material-ui/core/Card'; import CardAction from '@material-ui/core/CardActions'; import CardContent from '@material-ui/core/CardContent'; import { Timeline } from 'react-twitter-widgets'; import logo from './logo.svg'; import './App.css'; import { getConfig } from 'react-player/lib/utils'; const styles = { card: { margin: 20, height: 580 }, }; class App extends Component { constructor(props) { super(props); this.state = { contents: [] }; this.componentDidMount = this.componentDidMount.bind(this) } componentDidMount() { axios .get('GASの公開URL') .then(results => { const data = results.data; console.log(data); this.setState({ contents: [...data] }); }); } render() { const classes = this.props.classes; console.log(classes); const contents = this.state.contents.map(content => { return <Card className={classes.card}> <CardContent> <h2 key={content.id}>{content.title}</h2> <div align="center" key={content.id}> <YoutubePlayer url={content.url} controls /> </div> <p key={content.id}>{content.description}</p> </CardContent> </Card> }); return ( <Fragment> <div className="App"> <AppBar position="static" color="default"> <Toolbar> <Typography variant="title" color="inherit"> Page Title </Typography> </Toolbar> </AppBar> <header className="App-header"> <h1> Welcome to Our Site </h1> <p> Abstract about this page. </p> </header> {contents} <Timeline dataSource={{ sourceType: 'profile', screenName: 'Sashimimochi_343' }} options={{ username: 'Sashimimochi_343', width: '50%', height: '400' }} onLoad={() => console.log('Timeline is loaded!')} /> </div> </Fragment> ); } } export default withStyles(styles)(App);