20200118のReactに関する記事は5件です。

VTuberのホームページをGitHubのプルリクだけで更新していったらどうなるのか?企画に参戦してみた

おめシスはいいぞ

VTuberのホームページをGitHubのプルリクだけで更新していったらどうなるのか?という企画に参戦してみた。技術的にも非常に面白い試みだったので書き残しておく。

経緯

夜中にTwitterのタイムラインを眺めていたら興味深い動画を見かけた。
【検証】1ヶ月間、プルリクだけでホームページ作ったらどうなるの?

どうやら「おめがシスターズ(通称:おめシス)」というVTuberが、自分たちのホームページをGitHubのプルリクだけで更新していったらどうなるのか?という検証を行なっていた模様。

はじめはYouTube、Twitter、GitHubリポジトリのURLをベタ貼りしただけのテキストページ。aタグでのリンクすら張られていない。
EOesMAsU8AAIdSh.jpg

これが1ヶ月後どうなるか、という企画のようだ。
めっちゃ面白そう。

条件

本人たちからの要望は特に出さず、更新が来たらなんでも反映するというスタンス。

以上。

結果

1ヶ月(動画公開時点)で来たプルリクの総数は222件。GitHubトレンド入りも果たし、海外ユーザーが困惑していたらしい。笑う。

で、それなりに良さげなホームページに成長。
おめシスのホームページ | 第1回おめシスのホームページをプルリクだけで更新していったらどうなるの?企画
EOe9-2jU0AESugd.jpg

主に載った機能としては、以下のようなものが挙げられる。

  • ファーストビューにアニメーション
  • 多言語対応
  • レスポンシブ対応
  • おすすめ動画紹介
  • ディスコグラフィー(歌詞表示あり)
  • Twitter埋め込み
  • 各種リンク
  • 3Dモデルビュワー
  • トップに戻るボタン
  • 隠しリンク
  • おめが診断
  • コントリビューター一覧
  • (ゲーム)

すごい。

参戦してみた

まだしばらくプルリクを受け付けているとのことだったので、自分も何かやってみようと触ってみた。

さっそくGitHubリポジトリを見に行く。
omegasisters/homepage: おめシスのホームページを作りたい

HTML、JavaScript、TypeScript、CSSあたりで構成されているようだ。
2020-01-18_18h53_38.jpg
TypeScriptよくわかんけど、軽くWebフロント触るくらいならできるっしょ(適当)

READMEもちゃんとあった。
2020-01-18_18h54_55.jpg

開発のやり方説明が簡素過ぎて笑う。
2020-01-18_18h56_13.jpg

npmはNode.js触る時に入れたので何となくわかる。
yarnとかいうやつ知らんけど、これくらいならまぁ何とかなるっしょ(適当)

開発環境構築

yarn導入してyarn startしたら早速エラー。
webpack-dev-serverコマンドが認識されてないとか何とか。

単純にyarnコマンド打ったらなんか入った。init的なやつが足りなかった感じかな。
知らんけど。

追記:yarn installが必要だったらしい
readme追記 by tktk0430 · Pull Request #302 · omegasisters/homepage

再度yarn start実行したらローカルでサイト表示できた。
EOfovboUEAAFQD0.jpeg
これでガチャガチャいじれるぞ。

ヘッダーテキストのローカライズ対応やる

動画ではまだ多言語対応が完全ではないとのことだったので、サクッとできそうな部分だけ見てみることにした。
多言語対応 by shinyoshiaki · Pull Request #253 · omegasisters/homepage

assets/i18n/resource.json にjsonで言語データを定義。
指定したキーがhtml側のid属性に対応している模様。

言語データは適当にGoogle翻訳で投げたものをブチ込んだ。

assets/i18n/resource.json
"section_discography": {
    "ja": "ディスコグラフィー",
    "en": "Discography",
    "cn": "唱片目录"
  },
  "section_movie": {
    "ja": "⭐【初心者向け】すごい面白い動画",
    "en": "⭐【Beginners】Recommended Videos",
    "cn": "⭐【对于初学者】推荐影片"
  },
  "section_link": {
    "ja": "リンク",
    "en": "Link",
    "cn": "连结"
  },
  "section_3d": {
    "ja": "3Dモデル",
    "en": "3D model",
    "cn": "3D模型"
  },
  "header_section_description": {
    "ja": "おめシスって?",
    "en": "What is Ome-sis?",
    "cn": "欧米茄姐妹 是什么?"
  },
  "header_section_movie": {
    "ja": "おすすめ動画",
    "en": "Recommended Videos",
    "cn": "推荐影片"
  },
  "header_section_discography": {
    "ja": "ディスコグラフィー",
    "en": "Discography",
    "cn": "唱片目录"
  },
  "header_section_link": {
    "ja": "リンク",
    "en": "Link",
    "cn": "连结"
  },
  "header_section_3d": {
    "ja": "3Dモデル",
    "en": "3D model",
    "cn": "3D模型"
  },
  "header_diagnosis": {
    "ja": "おめが診断",
    "en": "Omega Diagnosis",
    "cn": "欧米茄诊断"
  },
  "header_contact": {
    "ja": "お問い合わせ",
    "en": "Contact Us",
    "cn": "联络我们"
  }

あとはindex.htmlに対応id追加して終わり。
これでヘッダーテキストが日本語、英語、中国語に対応。
2020-01-18_12h39_53.jpg
2020-01-18_12h40_06.jpg
2020-01-18_12h43_16.jpg

簡体字、繁体字の区分がないのが若干気になった。
プルリク見てると正しいローカライズ修正してくれてる人をみかけたので、そのあたりは丸投げする。

OSSは適材適所や。

更新が早い

多言語対応初期導入時のプルリクではキー名が日本語になっていたが、翌日には英語キーに変更されていた。
CSSのIDを日本語から英語に変更、他 by s4na · Pull Request #257 · omegasisters/homepage

素晴らしい。

とりあえず今回は「プルリク投げてマージしてもらう」のが主目的なので、速度優先でヘッダーテキスト部分だけローカライズ対応してプルリク作った。
ヘッダーテキストのローカライズ対応 by unsolublesugar · Pull Request #293 · omegasisters/homepage

未対応部分は気が向いたときにでもやる。
1ヶ月でここまでのものが出来たということは、大体こういうスタンスの人たちが作ってきたのだろう。

プルリクでCIが走る

プルリク投げたらなんかCI回った。
2020-01-18_18h36_48.jpg
https://github.com/omegasisters/homepage/commit/5cff6658418c14c38d64b64b950bbbf7cbfe3d51/checks?check_suite_id=407543068

yarnでGitHub ActionsのCIによるテストを走らせるプルリクを発見。
yarn CIの追加 by s4na · Pull Request #181 · omegasisters/homepage

結構ちゃんとしてた。

無事、マージされる

おめシスをレビュワーとしてassignして放置。
2020-01-19_10h19_29.jpg

プルリクページ眺めてたら溜まってたプルリクがリアルタイムでマージされていっててドキドキした。

何個か前のプルリクマージで言語ファイルがコンフリクトしてしまったので秒で対応。
数時間後に無事マージされた。

コントリビューター一覧にも載って満足。
2020-01-18_17h05_28.jpg
やったぜ。

OSSは楽しいね

まともにOSSプロジェクトに参加した経験がなかったため、リポジトリをフォークしてからプルリク作るという手順を知らず困惑した。

オリジナルのリポジトリに対してブランチを直接プッシュを試みるも、403エラーに阻まれて奮闘。「あ、これもしかしてforkってやつしないといけないのか…?」という閃き()により解決。

基本cloneだけで生きてきたので、大変勉強になりました。

あとでやるつもりだった本文中のローカライズ対応も、すでに進行しており感動した。
英語・中国語の翻訳を追加 by johnmanjiro13 · Pull Request #295 · omegasisters/homepage

適当にブチ込んだ翻訳も修正してもらえた。
中国語の内容を一部修正 by hyouchimaru · Pull Request #300 · omegasisters/homepage

使用技術としてVue.jsやReactなども絡んでいるようなので、興味のある方はじっくり見てみると良いかと思います。
homepage/documents/environment at master · omegasisters/homepage

現場からは以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React x Django】日本の電力を見える化するWebアプリケーションを作って公開してみた。

はじめに

こんにちは。現在、株式会社パネイルでソフトウェアエンジニアをやっている@TsJazz27Suminです。SIerから転職して3ヶ月目に突入しました。

今回は、転職してからほぼ趣味で作ったWebアプリケーションが会社公認コンテンツとして公開されることになったので、その技術的なまとめ記事になります。

アプリケーション自体のリンクは、こちらです。
https://vedas.cloud/

きっかけ

転職したばかりということもあって、パネイルのコア技術であるPython・Djangoあたりを勉強していたのですが、やはり勉強ばかりしていてもおもしろくなく、何か作れないかなーと思っていました。

そんな折、一般送配電事業者が需給実績データをオープンデータとして公開していることを知り、「おっ、PythonのPandasとか使って大量データさばくのおもしろそう」「Reactとか使ってグリグリ動くデータ分析ツールにするとおもしろそう」ってことで作りました。

ReactもPythonも多くのライブラリが世界中の人たちによって作られており、いろいろと組み合わせながら実装するのは、非常に楽しかったです。この記事が少しでも皆様のお役に立てば幸いです。

技術構成

フロントエンド

  • Framework : React 16.11.0
  • Language : javascript(es6)

サーバーサイド

  • Framework : Django 3.0
  • Language : Python 3.8.0

デプロイ環境

  • AWS
    • WAF
    • CloudFront
    • S3
    • Network Load Balancer
    • EC2
      • NGINX
      • Gunicorn

React

Full Stack Open 2019

すでに日本でも紹介されている記事を見かけますが、The University of HelsinkiのFull Stack Open 2019、これはほんと分かりやすいです。特にhooksの使い方は、簡単な課題をいくつか解いていく中で身についた感じです。

ちと実用レベルなことをやりだすと、ここにある内容だけでは十分ではなかった感じですが、基礎的なところを学ぶにはオススメです。

Reduxの記事とか読んで「Reactっておもしろそうだけど、状態管理とか大変そう。。」と敬遠していたのですが、hooksから入ったらそんなこともなかったです。

hooks

今回、主に実装した中で使っているのは、useState, useCallback, useEffectの3つです。

今回のアプリケーションの中でいうと、チャートの出力条件を指定するところで結構、使っています。

    const [tepcoChecked, setTepcoChecked] = useState(false));
    const handleTepcoChange = useCallback((newChecked) => {
        setTepcoChecked(newChecked);
    }, []);

例えば、上のコードは、チャートの出力対象として東京電力管内が選択されているかという状態を宣言しているものです。useStateで選択している(true)、選択していない(false)を表す変数tepcoCheckedとセッターのsetTepcoCheckedを宣言しています。

そして、tepcoCheckedのvalueがonChangeした時に実行される関数handleTepcoChangeをuseCallbackで宣言しています。この例では、実際に実行されるのは、セッターのsetTepcoCheckedです。

useEffect自体は、対象のcomponentが読み込まれた時に実行したい処理を定義しています。そういったケースがそんなになかったので、useState, useCallbackほど使っていません。

今回のアプリケーションの中でいうと、例えば、Usageのサンプルケースがクリックされた際に、ヒーローヘッダーをスルーして一気にチャート表示部分にスクロールさせています。

  useEffect(() => {
    if (qs.case !== undefined){
      window.scrollTo(0, 1300);
    }
  },// eslint-disable-next-line 
  [])

その他は、特定のcomponentが読み込まれた際にログを出したりといった具合です。基本的にhooksを使っているといっても、useState, useCallbackを多用する形で、状態管理周りの学習コストはそんなに高くないかな、と今回作ってみて感じました。

管理する状態の数が少ないうちは、素のuseState, useCallbackでよいのですが、今回、チャートの出力条件がカテゴリー別に10近くなるものもあり、そのまま書くとメインのcomponentのjsにこんな感じで書くことになります。

    const [hepcoChecked, setHepcoChecked] = useState(false);
    const handleHepcoChange = useCallback((newChecked) => {
        setHepcoChecked(newChecked);
    }, []);
    const [tohokuepcoChecked, setTohokuepcoChecked] = useState(false);
    const handleTohokuepcoChange = useCallback((newChecked) => {
        setTohokuepcoChecked(newChecked);
    }, []);
    const [rikudenChecked, setRikudenChecked] = useState(false);
    const handleRikudenChange = useCallback((newChecked) => {
        setRikudenChecked(newChecked);
    }, []);
    const [tepcoChecked, setTepcoChecked] = useState(false);
    const handleTepcoChange = useCallback((newChecked) => {
        setTepcoChecked(newChecked);
    }, []);
    //...以降続く...

うう、こんなのが1つのjsファイルにズラズラ並ぶなんて見るに堪えない。。ということでcustom hooksを作って、ある程度カテゴリーごとにまとめることにしました。

custom hooks

フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。
https://ja.reactjs.org/docs/hooks-custom.html

今回、自分はチャートの出力条件を整理するためにcustom hooksを作りました。
具体的にいうと、

  1. 期間
  2. 電力エリア
  3. 電力リソース

の3つそれぞれの条件カテゴリーにcustom hooksを作っています。

こうすることでuseState, useCallbackの列挙が緩和されてコードの見通しが良くなりました。あとは概念的な話ですが、状態をすべてフラットに扱うのではなく、いくつかのカテゴリーごとに分類することで理解しやすい構造になったと思います。

分割する前は、

  1. 期間 2
  2. 電力エリア 10
  3. 電力リソース 10

の合計22ぐらいのuseState, useCallbackのセットが並んでいたので。。。最初は、custom hooksを知らなかったので、存在を知ったときは救世主感がありました。

使用しているライブラリ

@shopify/polaris: 4.8.0

ShopifyのPolarisからReact Componentを一部使用しています。Shopifyは、カナダ発のEC関連企業で、Polarisというのは、彼らのデザインシステムのプロダクト名です。

今回、公開版を作る前のversion1としてPolarisのComponentを組み合わせて作ってみましたが、Componentの充実ぶりに感動しました。Polarisのサイトでは、Component利用のexampleも分かりやすく記述されており、とても参考になります。

また、design guidelineも充実しており、UI設計の考え方も非常に参考にさせていただきました。今回、公開版については、弊社のデザイナーに考えてもらったので、Polaris色は薄くなってしまったのですが、サクッといい感じに作りたいときはオススメです。

ただ、一歩踏み込んで独自色を出そうとすると、PolarisのComponentのCSSを上書きしたり、ちょっと無理してる感が出てしまうので課題感はあります。

一例としてSelect Boxは、公開版もPolarisのComponentを利用していますが、こんな感じで上書きしています。

.Polaris-Select {
  position: relative !important;

  height: 51px !important;
  width: 140px !important;

  padding-top: 6% !important;
  padding-left: 8% !important;
}

!importantで上書きはあまりやりたくないのですが、構造的にはPolarisのComponentを使いたく、しかしデザインは合わない、ということで苦肉の策で実装しています。

axios: 0.19.0

Djangoで作ったサーバーサイドのAPIをコールするために使っています。
ここらへんは、Ajax使ってWebアプリを作ってきた人は、馴染みやすいところかと思います。

一例として今回は、チャート表示用の元データを取得する処理で使用しています。
GetメソッドでURLと集計単位(unit)と期間(from, to)を指定して、サーバーサイドの処理を呼び出しています。

const get = (unit, from, to) => {
    const request = axios.get(baseUrl + 'get?unit=' + unit + '&from=' + from + '&to=' + to)
    return request.then(response => response.data).catch((err) => {
            console.error(err);
            return "err";
    });
}

lodash: 4.17.15

イベントを間引くために使用しています。今回のアプリケーションの具体例でいうと、スライダーで期間を指定する部分で使っています。

const debouncedHandleChange = debounce(
    (unit, from, to, value) => {
        if (unit === "y" || unit === "ym" || unit === "ymd") {
            japanEnergyService
                .get(unit, from, to)
                .then(initialData => {
                    setData(initialData);
                    setIsLoading(false);
                });
        }
    },
    500
);

スライダーでグリグリ動かして、何にもケアしていないとイベントが連続的に発生します。今回、期間を変えたらデータを取りに行くようにしていたので、イベントの発生と共にサーバーへのアクセスも発生して、処理が重くなっていたので間引きました。

上の例では、lodashのdebounceで対象の処理を囲んで一定の間隔(500)で待つようにしています。この間隔の範囲で次のリクエストが来たら処理は実行されず間引かれます。

query-string: 6.9.0

クエリーパラメーターを受け取るのに使用しています。今回のアプリケーションの具体例でいうと、xxx/?lang=jpとかUsageのサンプルから遷移する際のcase=1とかです。

import queryString from 'query-string';
import App from './App'

ReactDOM.render(
  <Router>
    <Route render={ (props) => 
      <App 
          qs={queryString.parse(props.location.search)}
      />
    }/>
  </Router>, 
  document.getElementById("root")
);

index.jsでこんな感じでparseを呼び出しているだけです。
あとは、受け取ったcomponent側でqs.langqs.caseといった形でパラメーターを参照する形です。

react-device-detect: 1.11.14

ライブラリのネーミング通りdeviceのタイプを判定するのに使っています。今回のアプリケーションの具体例でいうと、モバイル用にCSSの付け替えをしているので、componentの読み込み時にモバイルかどうか見ています。

import { isMobile } from "react-device-detect";

if (isMobile) {

}

importでisMobileを宣言してそのまま使うだけで分かりやすいです。
isMoblie以外にもosName, browserName, deviceTypeなどUserAgentから拾えるものは、一通り揃っています。

UserAgentのパース処理なども自前で書く必要もないので便利なライブラリです。

react-ga: 2.7.0

GoogleのAnaliticsをReactで使うために導入しています。
今回のアプリケーションでは、pageviewとevent trackingで使用しています。

  useEffect(() => {
    ReactGA.set({ page: pathname });
    ReactGA.pageview(pathname);
  });

useEffectと組み合わせて特定のcomponent読み込み時に実行しています。

react-share: 3.0.1

SNS用のシェアボタンを配置するのに使用しています。
今回のアプリケーションでは、facebook, twitter, line, weiboを対象としました。

    <FacebookShareButton url={current_url}>
        <FacebookIcon size={size} round />
      </FacebookShareButton>

      <TwitterShareButton url={current_url}
        hashtags={["パネイル", "Panair", "Vedas", "電力見える化"]}>
        <TwitterIcon size={size} round />
      </TwitterShareButton>

      <LineShareButton url={current_url}>
        <LineIcon size={size} round />
      </LineShareButton>

      <WeiboShareButton url={current_url}>
        <img width='100%' src={weibo_icon} alt="weibo_icon"/>
      </WeiboShareButton>

こういった形で基本的にシェアされたいURLと、twitterとかであればハッシュタグなどオプションを指定するだけです。今回、使用できたバージョンだとweiboのアイコンがなかったので画像指定をしています。

これだけの記述でSNSのシェアアイコンがサクッと並びます。

recharts: 1.8.5

需給実績のチャートを出すために使っています。今回は、シンプルなLineChartだけですが、他にもAreaChartBarChartPieChartなど多彩なチャートコンポーネントが用意されています。

<ResponsiveContainer width={isMobile ? '100%' : '95%'} aspect={aspect}>
      <LineChart data={props.energy_data}
        margin={{ top: 30, right: 30, left: 30, bottom: 5 }}>
        <XAxis dataKey="name" />
        <YAxis />
        <CartesianGrid strokeDasharray="3 3" />
        <Tooltip />
        <Legend />
        <Line unit={unit_word} type="monotone" name={dict.solar} key="solar" dataKey="solar" stroke="#F49342" />
      </LineChart>
    </ResponsiveContainer>

data={props.energy_data}でコンポーネントが要求しているJSON形式のデータを渡しています。レスポンシブにするためにResponsiveContainerで囲っていますが、ほぼrechartsのサイトにあるサンプルコードに近いです。

styled-components: 4.4.1

今回、CSSをそのまま書くことはせず、9割はstyled-componentsを使って各コンポーネントに閉じる形でStyleを定義しました。残りの1割は、SVGアニメーションの記述だったり、styled-componentsを使うとフォーム部品の挙動が微妙になるので局所的にCSSを定義して使っています。

const getStyledComponents = () => {

  let ContentTitle = styled.div`
  padding: 2% 0% 2% 1.5%;
  margin:  0% 0% 0% 0%;
  `;

  if (isMobile) {
    ContentTitle = styled(ContentTitle)`
    padding-left: 38%;
    margin-top: 3%;
    `;
  }

  return {
    ContentTitle : ContentTitle
  };
}

一例です。きれいにPCとモバイルのStyleを分けてしまうかは、今回悩んだのですが、PCを基本にモバイルの差分だけ記述して上書きにしています。paddingとmarginがスタイリングされたDivタグをコンポーネントとして返すメソッドになります。

styled-componentsの良いところは、なんと言ってもCSSの定義がコンポーネントの中に閉じられるところだと感じました。普通にCSSを書いていくと、グローバルに一意なNameをつけてBEMなり意識して書いていかざるをえず、なかなか管理がつらい部分がありました。

Sass、LESS、StylusなどのCSSプリプロセッサを使用するのも一案だと思うのですが、Reactのコンポーネント単位で画面の部品を管理していく世界とは、どうも合わない気がして、今回、styled-componentsを導入してみました。

AtomicDesignとか意識して、どの粒度のコンポーネントでどういった種類のStyleを定義すべきか、を整理して実装するまでに今回至っていません。そのためstyled-componentsがReactのStylingのベストなのか、正直判断がつかない段階です。

ただ、1つ言えるのは普通にズラズラCSSを書いていくよりは、コンポーネント単位でまとまっているので、コードの見通しは良くなった実感がありました。

Django

pandas==0.25.3

こちらのブログを大変参考にさせていただきました。今回のアプリケーションでは、Pandasを使って需給実績のデータを読み込み・加工・出力しています。

各送配電事業者が公表しているデータは、CSV or EXCELですが、pandasを使うと簡単に読み込み・加工・出力ができるので今回初めて使ってみて驚きました。特にdataframeの形式でデータを扱えるので、集計処理をさせるのにもいちいち細かいロジックを書かずに済む点も魅力です。

    def sum_group_by_year_and_month(cls, data_frame):
        df_ym = data_frame.set_index([data_frame.index.year, data_frame.index.month])
        df_ym.index.names = ['year', 'month']
        df_ym.sort_index(inplace=True)

        try:
            result = df_ym.sum(
                level=['year', 'month']
            )[
                [
                    'demand',
                    'nuclear',
                    'thermal',
                    'hydro',
                    'geothermal',
                    'biomass',
                    'solar',
                    'solar_output_control',
                    'wind',
                    'wind_output_control',
                    'pumping',
                    'interconnection',
                    'total_supply_capacity'
                ]
            ]
        except Exception as e:
            raise e
        return DataFrameFunction.to_float_and_round(result).to_json()

上記は一例ですが、年月単位で合計値を集計させている部分です。手続き的にforで足し合わせていくのではなく、宣言的に集計軸と使う項目を指定すればいいので、見た感じも分かりやすく直感的に使える良いライブラリだなと、今回実装して感じました。C#だとlinqがsql likeにデータを扱えるので割と近いかもしれません。

今回、処理フロー的には、大きく2つ分かれます。

【事前のデータ準備】

  1. 各送配電事業のデータ(csv, excel)をダウンロード。
  2. ダウンロードしたデータをdata frame形式に変換。(Pandas)
  3. 集計処理がしやすいようにデータを加工。(Pandas)
  4. 加工後のデータをPickle形式で保存。(Pandas)

【Web UIからのデータ利用】
1. Reactのフロントエンドアプリケーションからデータをリクエスト。
2. Pickle形式で保存されたデータをdata frame形式に変換。(Pandas)
3. 集計処理を実行。(Pandas)
4. data frame形式からjson形式に変換。(Pandas)
5. json形式でフロントエンドにreturnして、画面描画。

このようにほぼサーバーサイドの処理はpandasを使って構築している感じです。
Pythonでロジックらしいロジックを書いている部分は、各送配電事業からのデータダウンロードぐらいです。

そのせいかPythonのコードよりもReactのフロントエンド部分のコード量が圧倒的に多く、VedasはGithubのリポジトリ上、HTMLで構成されたアプリケーションと認識されているようです。

現在、HTML:76.1%, JavaScript:15.9%, Python:7.3%、Other:0.7%となっています。こんなにPythonの構成比が低いのは、フロントエンドを凝った作りにしているということもあると思いますが、メインのデータ分析処理をpandasで作ったからだと考えています。

DB周り

今回、データベースは使用していません。理由はいくつかありますが、

  1. 認証不要なオープンなアプリケーションのためユーザ管理などデータがいらない。
  2. 分析・加工対象のデータは、pandasで扱いやすいようにPickle形式で保存している。
  3. その他、多少のパラメータ群はわざわざRDBで管理せずともjson形式のファイルで管理すれば十分。

ということでNo RDBの構成にしています。

結果的にAWS上でRDSとか使わずに住んでいるのでランニングコストもその分お得です。

django-cors-headers==3.2.0

今回、Djangoで作ったAPIをReactのフロントエンドアプリケーションからリクエストさせるため、開発中にcorsが発生しました。その対策としてdjango-cors-headersを導入しています。

CORS_ORIGIN_WHITELIST = (
    'http://localhost:3000',
    'https://vedas.cloud'
)

こんな感じでsettings.pyとかに記載してあげると、CORSになっても許可してくれます。

requests==2.22.0

需給実績のファイルを取得する部分で使っています。

    def get_decoded_data(cls, url):
        response = urllib.request.urlopen(url)
        if response.getcode() == 200:
            response.close()
            content = requests.get(url).content
            return content.decode('sjis')
        else:
            response.close()
            raise Exception(f'{url} is not found.')

URLをgetで指定して取得できたcontent=ファイルという感じです。

AWS

構成

  • AWS
    • WAF
    • CloudFront
    • S3
    • Network Load Balancer
    • EC2
      • NGINX
      • Gunicorn

Reactのフロントエンドアプリケーションは、S3にデプロイしてCloudFrontでキャッシュさせています。Djangoのサーバーサイドアプリケーションは、EC2上にデプロイしてAPIとして利用させる形です。

通信

Vedasにアクセスしていただくと分かりますが、HTTPSで公開しています。最近、割とGoogleがHTTPS以外のサイトを認めないという風潮なので乗っかりました。

HTTPSのフロントエンドアプリケーションからサーバーサイドのAPIを叩きにいくと、こちらもHTTPSをじゃないとMixed Contentということで怒られます。具体的には、axiosでhttpでコールした際にエラーが出ます。

対応策としては、API側もHTTPS化するのですが、今回選択肢が3つありました
1. NGINXでHTTPSの設定を行う。
2. DjangoでHTTPSの設定を行う。
3. Load BalancerでHTTPSの設定を行う。

この中で3を選んだのは、

  1. 証明書の管理をAWSにまとめたかった。
  2. AWSでLBをかませておいた方がスケールアウトしやすい。

というところです。

AWS環境構築にあたってお世話になったサイト

最後に

実際にアプリケーションを作ることで、アウトプットしながらインプットして、さらにインプットをそのままアウトプットするというサイクルを今回、かなり高速に回すことになりました。

やっぱり勉強のためにコードを書くのではなく、誰かの役に立ちそうなアプリケーションを作るでは、圧倒的な違いがあると今回感じました。React x Djangoで今回アプリケーションを構築しましたが、今までの自分がJavaやC#で作ってきたWebアプリケーションと違う世界で非常に刺激があり、毎日コードを書くのが楽しみでした。

世界中の優秀なエンジニアが作り上げてきた技術を使って、何かを作るということは、こんなにおもしろいものかと改めて感じた年末年始でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Expo.ioを使ってスマホアプリ開発環境を整える(React Native)

React Nativeを使ったスマホアプリの開発環境をExpo.ioというサービスを用い爆速で整えましょう。

手元のiPhoneがなかったので、本記事の実機検証はAndroid(Pixel4)を用いていますが、シミュレータ/エミュレータを導入すればiOSもAndroidも開発マシン上で検証できます。

Expo.ioのアカウントを作成する

アカウントを作成しておきましょう。
Expo.io

上記サイトでアカウントを作成します。

ここで登録したユーザーの認証情報は実機検証の際にも用いるので、もしパスワードをブラウザの自動生成で作成した場合は実機と共有するか、覚えておいてください。

作成したら、実機側にExpoアプリをインストールします。

Android版Expoアプリ
iOS版Expoアプリ

インストールが完了したら、アプリ側でも同じ認証情報でログインしておいてください。

開発マシン側の環境を整える

Expoはnpmでインストールするので、最新バージョンかどうかを確認しましょう。

$ npm -v
$ node -v

最新バージョンであれば、expo-cliをグローバルにインストールします

$ npm i -g expo-cli

少しだけ時間がかかります。

インストールが終われば、適当なディレクトリでinitします。

$ expo init MyProject
? Choose a template: (Use arrow keys)
  ----- Managed workflow -----
❯ blank                 a minimal app as clean as an empty canvas
  blank (TypeScript)    same as blank but with TypeScript configuration
  tabs                  several example screens and tabs using react-navigation
  ----- Bare workflow -----
  minimal               bare and minimal, just the essentials to get you started
  minimal (TypeScript)  same as minimal but with TypeScript configuration

テンプレートを選べ、と言われるのでアローキーを使ってテンプレートを選択してください。

用途に合わせて選びますが、今回はblank (TypeScript)を選択します。

Enterで進めると、必要なパッケージがインストールされますので待ちましょう。

完了すると

To get started, you can type:

  cd MyProject
  npm start

と表示されます。指示通りcdしてstartしましょう。

$ cd MyProject
$ npm start

すぐにブラウザで新しいページが開きます。

一応ターミナル側でもQRが表示されますが、ブラウザに表示されているものと同じものです。

ブラウザ側のQRの上にあるメニューからiOS/Androidのシミュレータ/エミュレータが起動します。入っていなければ開発マシンに追加しろってエラーを吐かれます。

スクリーンショット 2020-01-18 14.58.21.png

スクリーンショット 2020-01-18 14.58.40.png

ここまで来たら、実機側のアプリからScan QR Codeを選び、スキャンします。

Screenshot_20200118-150311.png

App.tsxを開いて開発を始めましょう!って表示されるので、開発マシンでApp.tsx(テンプレートでTypeScript選ばなければApp.js)を開き、コードを編集してみます。

App.tsx
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';

export default function App() {
  return (
    <View style={styles.container}>
      <Text>Hello!! MyProject!</Text> 
      <Text>Open up App.tsx to start working on your app!</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

編集し、保存すると実機側もリアルタイムで更新されます。ホットリロード機能が最初からついているのです。

Screenshot_20200118-150707.png

これでReact Nativeを使ってアプリ開発を行う環境が整いました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FirestoreクライアントとしてのRedux Saga Firebase

はじめに

こんにちは。最近友人と開発している React × Firebase のWebアプリで redux-saga-firebase を使用しているので、その紹介をしたいと思います。redux-saga-firebase が Firestore クライアントとしてどう機能するのかということを使用例などを交えて書いていきます。

Redux-Saga-Firebase とは

Reduxのアプリケーションにおいては、Firebaseとのやり取りなどの非同期処理はミドルウェアに切り出して処理します。そのミドルウェアの一つが redux saga なのですが、そこでの処理を良しなにやってくれる便利なライブラリが redux-saga-firebase です。

スクリーンショット 2020-01-10 13.57.37.png

npm trends (2020/1/16現在) で見てみるとこんな感じです。ものすごく使われているという感じではないですが、更新状況も新しく、それなりに使用されているのが分かります。

スクリーンショット 2020-01-15 23.16.23.png

どう使えるのか

では、redux-saga-firebase は実際どのように使用されるのかということです。ここでは、使用しない場合と比べながら、ドキュメント取得、コレクション取得、リアルタイムアップデートに着目して紹介していきます。

まずこちらは redux-saga-firebase を使用するにあたって初期化などの準備です。これ以降、redux-saga-firebase を使用する際にはこの rsf を使います。

// 初期化
const firebaseApp = firebase.initializeApp({ ... })

const rsf = new ReduxSagaFirebase(firebaseApp)

【使用例1】Firestoreのドキュメント取得

ここではFirestoreからドキュメントデータを取得する方法についてです。

redux-saga-firebaseを使用しない場合

redux-saga-firebse を使用しない場合と比較するため、まずは使用しない場合を考えてみます。

下の例では、Firestoreからのデータが配列で返ってくる処理を想定しています。.where()を使用して条件の合致する全てのドキュメントをクエリして.get()によってその結果を取得しています。この例では、uid (ユーザID) で絞ってドキュメントのデータを取得しています。

db.collection("hogehoge").where("uid", "==", uid)
    .get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            let sample = []
            sample.push(doc.data())
        });
    })
    .catch(function(error) {
        console.log("Error getting documents: ", error);
    });

また、上記の書き方では、配列sampleを参照する処理がここ以降に存在する場合、全てのドキュメントを配列sampleに追加する前の状態を参照してしまう同期的な処理になっています。その対策としては、sync/awaitを使用したりするのですが、ここで、一旦 redux-saga-firebase を検討してみましょう。

redux-saga-firebase を使用する場合

redux-saga-firebaseを使用する場合についてみてみます。.getDocumentによって特定のドキュメントを hoghoge コレクションから取得しています。同じ処理部分だけを見ると、redux-saga-firebaseを使用した場合は一行のコードで済んでスッキリしていて可読性も高いです。このようにデータをドキュメントの配列で取得する場合でも面倒くさい配列の操作なども要りませんし、非同期処理を同期的に書くことができるのでコールバック地獄に陥ることもありません。

sagas/hogehoge.ts
    // firestoreの'hogehoge'というコレクションからuidで絞ったドキュメントのデータを取得する
    const doc = yield call(rsf.firestore.getDocument, 'hogehoge/'.concat(uid))

【使用例2】コレクションの取得について

コレクションの取得については.getCollectionメソッドを使用します。ここでは hogesという配列を用意してそれぞれ格納しています。

sagas/hoghoge.ts
    // hogehogeコレクションを取得
    const snapshot = yield call( rsf.firestore.getCollection,'hogehoge')
    // 配列を用意して格納
    let hoges = []
    snapshot.forEach( hoge => {
      hoges = [...hoges, hoge.data()]
    }

ここで、コレクション取得に制限や条件をかけるクエリを紹介します。

  • 取得数を制限する:.limit()

例えば、コレクションから8つだけ取得する場合は以下の通りです。

    const snapshot = yield call(
      rsf.firestore.getCollection, firebase.firestore().collection('hogehoge').limit(8),
    )
  • 並び替えてから任意の数だけ取得する:.orderBy().limit()

例えば、ageプロパティで並び替え(デフォルトは昇順)て、5つだけ取得する場合は以下の通りです。

    const snapshot = yield call(
      rsf.firestore.getCollection, firebase.firestore().collection('hogehoge').orderBy(`age`).limit('5'),
    )

このようなlimit()orderBy()は redux saga firebase ライブラリではなく firebase のリファレンス(上記のようにfirebase.firestore()から参照している)です。このように、redux saga firebase ライブラリを使用しつつも firebase リファレンスも併用することもできます。コレクション取得の際に使用できる firebase リファレンスはここに載っているので興味ある方は覗いてみてください。

【使用例3】リアルタイムアップデートについて

Firestore のリアルタイムでの動きを感知して更新したいときには.channel()メソッドが使用できます。

例えば、SNSのタイムラインのようなリアルタイムでの更新です。タイムライン更新のような、外部でのイベント発生をsagaが監視することでリアルタイムアップデートを行います。

sagaでの記述例を下に書きます。前提として、更新日時で並び変えられたドキュメントを持つtimelineというコレクションが Firestore にあるとします。そのコレクションの変更をrsf.firestore.channel()によって感知してhogehogeという変数に格納しています。そして最後に redux で定義されている action のパラメータとして渡すことで、更新を反映しています。

saga/timeline.ts
  // timelineというコレクションを更新日時によって並び変えている
  const colRef = db.collection('timeline').orderBy('updatedAt')
  // そのtimelineコレクションの並びに変更があるか監視
  const channel = rsf.firestore.channel(colRef)

  while (true) {
    // 更新を感知してhogehogeに格納
    const hogehoge = yield take(channel)
    // hogehogeをパラメータにactionを呼ぶ
    yield put (action(hogehoge));
  }

リアルタイムアップデートの処理は冗長になりがちですが、redux saga firebase ライブラリを使用するととてもスッキリ書けるのがわかると思います。

終わりに

この記事では、redux-saga-firebase について紹介してきました。
Redux で Firestore を使用する場合、その非同期処理を saga および redux-saga-firebase というライブラリに任せてみてはいかがでしょうか。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Built-in CSSがサポートされました[Next.js 9.2更新情報]

はじめに

ReactでSSR(Server Side Rendering)が可能なフレームワークであるNext.js。久しぶりに見てみると2020年1月15日にバージョン9.2を公開しており、個人的に嬉しい機能が追加されたということで紹介していきたいと思います。

この記事は公式ブログで紹介されている内容をまとめる形になるので、詳しい内容は公式ブログで確認してください。

新(?)機能まとめ

  • Built-in CSS Support for Global Stylesheets
  • Built-in CSS Module Support for Component-Level Styles

CSSを別ファイルからグローバルやコンポーネント単位のモジュールとしてのインポートが簡単になりました。

  • Code-Splittingの改善

ビルド時のコード分割を改善することにより、アプリケーションのサイズがかなり小さくなりました。

  • 動的パスを全て取得

例えば/post/a/b/とパスが動的パスであるとき、/pages/post/[...slug].js[...name]シンタックスを使用することで、queryとして{ slug: ["a", "b"] }を得ることができ動的パスをまとめて取得することが可能になりました。

本記事では最初のBuilt-in CSSについて少し話して終わります。

Built-in CSSのサポート

今まで別ファイルのCSSを使用するには、以下のような手順が必要でした。

  1. @zeit/next-cssのインストール。
  2. next.config.jsを作成し、設定を記述。

2手間ではありますが、毎回設定するのであればさすがに面倒だと思います。さらに、このnext-cssにはある束縛がありました。それは

グローバルかローカルかどちらかしか選択ができない。

ということです。これらの問題を解決したのが今回のアップデートになります。
グローバルとローカルの区別は以下のようになります。

  • グローバル ... [name].css
  • ローカル ... [name].module.css

それでは、例を挙げていきます。

例: グローバル

public/style.cssを作成します。

public/style.css
body {
  padding: 20px 20px 60px;
  margin: 0;
}

h1{
  color: red;
}

アプリケーション全体で適用したいので、_app.jsxでこのファイルを読み込みます。

pages/_app.jsx
import '../public/style.css'

export default function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

そして、ホーム画面を用意します。

pages/index.jsx
export default () => {
  return (
    <>
      <h1>HOME</h1>
    </>
  )
};

すると...
global.PNG
ちゃんと赤色になってますね。要素を見てみると...
global2.PNG
グローバルになってそうですね。

例: ローカル

pages/home.module.cssを作成します。

pages/home.module.css
.underline{
  border-bottom: 2px dotted black;
}

そして、pages/index.jsxを以下のように修正します。

pages/index.jsx
import styles from "./home.module.css"

export default () => {
  return (
    <>
      <h1>HOME</h1>
      <h1 className={styles.underline}>Welcome to Next.js 9.2!</h1>
    </>
  )
};

そうすると、しっかりアンダーラインが引かれて、またモジュールとして呼び出せていることがわかります。
local.PNG

おわりに

今回はNext.jsのバージョンアップに伴った更新点を紹介しました。
本記事ではBuilt-in CSSのみ紹介しましたが、他の機能も本格的なアプリケーションを作る際に助かるような内容になっています。ぜひ公式のブログにアップデートの内容が詳しく書かれているので読んでみてください。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む