@sashimimochiの技術ブログ

経緯とか

最近、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形式で返す
  2. 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から取得して表示してくれるようになりました。コンテンツ量が増えると崩壊しそうなので、そこはページを繰る度にロードするようにするなど要改善です。

その他参考になりそうな記事はこちら。

参考文献

Twitterのタイムラインを埋め込む

React Twitter Wigets
ページのチュートリアルにしたがって、

$ npm i react-twitter-widgets

で落としてきて埋め込みます。なんとお手軽で、screenNameusernameに埋め込みたいアカウント名(@で始まるやつ)を書くだけ。たぶん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);

その他の参考文献

この記事へのコメント

まだコメントはありません