- 投稿日:2019-12-01T23:56:09+09:00
実践 React Native 設計チェックシート
この記事はReact Native Advent Calendar 2019の1日目の記事です。
キッチハイクのアプリはReact Nativeで開発を始め、3年ほど経ちました。初期リリース時には自動テストもなく、Firebaseライブラリも限定的に使うだけでした。プロダクションにリリースし、ノウハウが溜まってきて、いまReact Nativeで開発するならここを抑えておくぞ、というポイントをまとめたいと思います。
この記事では、これからReact Native でアプリ開発を始める方をメインの対象としていますが、すでに開発中の方にもメリットがあればと思います。キッチハイクアプリも最初から全てを入れていた訳ではなく、まだまだ導入したいライブラリや仕組みは多いです。
そのアプリ開発に React Native が最適か?
キッチハイクチームがアプリ開発ツールとしてReact Nativeに着目した理由は、アプリネイティブ開発者がチームにいないことでした。そこでまずは手始めにReactをRailsに入れることから始めます。それが2016年10月頃です。そこで手応えを掴んだことをきっかけにアプリをReact Nativeで開発することが決定しました。
これまで3年開発を続けた上で、当時を振り返りながらアプリ開発にReact Nativeを選ぶためのチェックポイントを考えてみました。当時は Xamarin や Flutter などの他のクロスプラットフォーム開発ツールはメジャーになっておらず、ネイティブ開発(Swift / Android)かReact Nativeかという視点でした。
キッチハイクアプリの開発には以下の3つの特徴がありました。
- Webエンジニアのチーム
- アプリはView開発がメイン
- ネイティブ機能への強い依存がない
1. Webエンジニアのチーム
当時のキッチハイクチームにはRailsエンジニアが多くWebアプリケーション開発が中心でした。アプリ開発の企画は数年前からあったものの、専任のエンジニアがなかなか見つからず、Railsエンジニアが挑戦するかどうか、機会を伺っていました。
React Nativeでのアプリ開発に着手する前に、ReactをRailsに入れてみました。Reactの設計思想を使えるReact Nativeでの開発はWeb開発が長いキッチハイクチームには魅力的でした。一方で、コンポーネントの共通化は思ったよりも難しいという印象でした。
コラム: Reactの思想
Reactは関数型プログラミングの思想、とりわけimmutabilityを大事にしているフレームワークだと感じます。この点も、React Nativeを取り入れた理由の一つです。Ruby / Railsとは別の設計思想に触れることにより、開発チームに奥行きを持たせたかったという思いがあります。
開発開始から3年、 React の思想である "Learn Once, Write Anywhere" はキッチハイクチームでは上手くワークしていると思います。
2. アプリはView開発がメイン
キッチハイクはWebアプリがすでにあったこともあり、ビジネスロジックはAPI側に持たせて、薄いViewとしてアプリを開発する方針にしました。原則として、View表示に必要なデータは全てAPIが返して、Viewはデータをレイアウトすることに注力します。React Nativeでの開発はこの設計方針にマッチしました。
コラム: View開発の効率性
React Nativeはビルドを必要とせずにUI変更をシミュレーターに反映できるHotリロード機能があります。
また、Xcodeのストーリーボードではなく、Webでの知識をほぼそのまま使えるCSS styleと合わせて、UIの微調整は効率よく行うことができます。3. ネイティブ機能への強い依存がない
ネイティブ機能(マルチカメラやジャイロセンサーなど)への要件がないことも重要でした。
コラム: ネイティブ機能の要件と expo or Not?
React Native での開発を選択したとして、次に expo を使うかどうかという検討があります。いくつか検討事項があるのですが、ポイントは expo がサポートしていないサードパーティSDKの要件があるかどうか、でしょうか。例: Repro, AppsFlyer, Adjust
expo については弊社テックブログですが、下記も参考になるかと思います。
React Native + Expoで1年以上運用したCTOが集まってみた 前編 - KitchHike Tech Blog
React Native + Expoで1年以上運用したCTOが集まってみた 後編 - KitchHike Tech Blog
ここまではチームがReact Nativeかそれ以外か、についての考慮ポイントを紹介しました。上記はキッチハイクチームの一例ですが、参考にしてもらえると嬉しいです。
ここからはReact Nativeでの開発を始めるに当たって、どのようなポイントで意思決定をしていくかを解説します。
React Native 主要アーキテクチャ選定
UIコンポーネント設計
多くの入門記事でも話題になるように、React NaitveではUIコンポーネントライブラリの選択肢がいくつかあります。Web開発でいうところのBootstrapで、コンポーネントにスタイルを自動で提供してくれるライブラリです。主なものを表にまとめました。
ライブラリ star数 React Native Elements 17,619 NativeBase 13,040 RN Material Kit 4,513 React Native Material UI 3,176 star数は2019年12月1日現在
キッチハイクでは?
キッチハイクでは検討した結果、ライブラリを導入せずにスクラッチからStyleを書いていくことにしました。背景にはRails開発でのBootstrapがあります。初期リリース時にはBootstrap導入にメリットがありましたが、長く開発を続けていくうちにカスタマイズにコストがかかったり、いざBootstapをやめようとしても影響範囲の把握が難しかったりします。そういった過去の開発経験から、アプリ開発では必要になるまではライブラリの導入はしないで開発を進めることにしました。結果、3年経ったいまも導入はしていません。
状態管理設計
React Nativeでの設計ポイントに状態管理があります。ここ数年でReactの開発が進み、状況が変わってきた箇所の一つです。現状での選択肢は大きくは、
- Function Component + React 標準API( Hooks, Context API )
- Reduxなどのライブラリを導入
の2つがあります。もしライブラリの導入を検討する場合、国内の情報が多いのはRedux, MobXでしょうか。ReactのContext API ベースに作成されている Unstated も検討できそうです。
ライブラリ star数 Redux 51,537 MobX 20,928 Unstated 7,047 キッチハイクでは?
ライブラリは必要になったら入れようというスタンスで、未導入でした。今後はReact標準のAPIを使うようにしていく方針です。
ナビゲーションライブラリの選定
React Nativeの公式ドキュメントで紹介されているReact Navigationと、 Wixが開発しているReact Native Navigation、そしてReact Navigationをベースに開発されているReact Native Router ( react-native-router-flux, 通称 RNRF ) の3つでまずは検討すると良いかと思います。ライブラリの名前が紛らわしいので、間違えないように理解することが重要です。
ライブラリ GitHubリポジトリ star数 React Navigation react-navigation/react-navigation 16,939 React Native Navigation wix/react-native-navigation 11,008 React Native Router aksonov/react-native-router-flux 8,582 キッチハイクでは?
React Nativeの公式ドキュメントを参考にして、 React Navigation を使っています
APIクライアント設計
APIクライアントは、JavaScript 標準API の Fetch API を使うか、より高度に制御したい場合はライブラリを導入するかの選択になります。
ライブラリ名 star数 特徴 axios 67,165 PromiseベースのHTTPクライアント。ブラウザやNode.jsに幅広く対応したJSライブラリ。 Frisbee 904 Alternative axiosとして後に開発が始まったReact Nativeに最適化したライブラリ。 キッチハイクでは?
ライブラリを導入せずに JavaScript の Fetch API を使っています。必要になったらライブラリの導入も検討する予定ですが、いまのところは問題ないです。
コラム: Firebase Remote Config の紹介
1年ほど前に Firebase Remote Config を導入しました。最初はキャンペーン情報の制御など、サーバーサイドのリリースをしないでも変更したい箇所から入れていきましたが、その使用範囲は広げていっています。リリースしなくともデータ変更ができるのは便利です。クライアント側からは参照のみで更新がなく、サーバーサイドで管理画面を作るほどでもないという条件のデータが使いどころだと感じています。
アプリストレージ DB 設計
アプリストレージは React Native 標準の Async Storage 、もしくはサードパーティが提供している DB ライブラリを使用するかの選択になります。
ストレージ ライブラリ star数 Async Storage Async Storage 1,105 Realm realm-js 3,768 SQLite react-native-sqlite-storage 1,849 Cloud Storage for Firebase react-native-firebase 6,608 コラム: ObjectBox
記事を執筆中に ObjectBox というモバイル用のクロスプラットフォーム対応DBを知りました。公式サイトには ObjectBox object oriented database - up to 10x faster than SQLite とあり、パフォーマンスに特化した設計方針のようです。
クライアントライブラリが swift / java / go / dart などはあるのですが、 JavaScript については公式には提供されてないようで React Native ではまだ使えないのですが、気になっています。
キッチハイクでは?
アプリ側に複雑なデータを持たせる要件がいまのところないので、Async Storageを採用してます。一方で、開発を続けていくとどうしてもアプリ側に持たせるデータやキャッシュが多くなってきたので、サードパーティDBも検討中です。
ここまでReact Nativeの開発を始めるにいたって、検討ポイントを上げてきました。
開発体験 Development Experience
ここからは実際に開発する際の考慮ポイントについて紹介していきます。今回は3つの観点で整理しました。デザイナーとの連携作業である コンポーネント設計 , 実際のコードに関連する TypeScript / Lintツール , そして テスト です。
コンポーネント設計
コンポーネント指向であるReactで開発する上では、コンポーネント設計と向き合っていく必要があります。ReactはView開発がメインとなるので、デザイナーとの連携は多くなります。このことから、 どのようにしてデザイナーとコンポーネント設計を共有するか が開発チームの課題となってきます。
コンポーネント設計におけるデザイナーとエンジニアの架け橋候補の一つが Atomic Design でしょう。キッチハイクチームでもAtmic Designの導入を検討しました。そこで課題になったのが、どこまで厳密にAtomic Designに従うかです。より具体的に言うと、Atom / Molecule などをデザイナー主導で設計していくのか、エンジニア主導で設計していくのかです。
このような議論が起きる理由はコンポーネントが持つ責務にはデザイン以外の要素があるからだと考えています。
デザイナーとエンジニアで考えるReactコンポーネント設計 - KitchHike Tech Blog より
上記の図でいう、デザインとしての構造性はエンジニアでは判断できず、逆にコードとしての再利用性をデザイナーが判断するのは難しいことです。チーム方針としてコンポーネント設計にどう向き合っていくかは重要なテーマであり、チーム状況によっても変わってくるものだと考えています。
キッチハイクでは?
Atomic Designをベースにしていますが、厳密に適用せずにエンジニア主導で開発するスタイルに落ち着きました。デザイナーは Figma を使ってカタログベースのデザインを作成し、どの部分をコンポーネント化するかはエンジニアが設計するというものです。ここに至る経緯に関しては弊社デザイナーの記事に詳細があります。
デザインシステムを持たない組織のこれまでの取り組みとこれからを考える|はのめぐみ | KitchHike|note
コンポーネントスタイルガイドツールの選定
デザイナーとエンジニアでコンポーネントを共有する際に、コンポーネントスタイルガイドツールが検討されるかと思います。Storybookなどに代表されるものです。ここでは、Storybookをはじめいくつかの代替ツールを紹介します。
ツール名 star数 特徴 Storybook 43,673 コードベースのコンポーネントスタイルガイドツール。スナップショットテストも可能。 Docz 17,407 ドキュメント管理ツール。Reactにも対応している。 React Styleguidist 8,023 Hotリロード対応。コンポーネントカタログツールとしてフォーカスする設計思想で開発されている。 キッチハイクでは?
コードベースのコンポーネントカタログツールは使わずに、Figmaを使ってカタログ化しデザイナーとやり取りをしています。詳細な経緯は弊社デザイナーの記事を参考にしてください。
デザインシステムを持たない組織のこれまでの取り組みとこれからを考える|はのめぐみ | KitchHike|note
型チェッカー TypeChecker
キッチハイクアプリの設計が始まった3年前は、FlowとTypeScriptが主なJavaScriptの型チェッカーでした。当時はReact Nativeと同じFacebookが開発しているFlowの方が親和性が高いのではと思っていましたが、ここ数年で状況は変わりました。
2017年4月にはGoogle社内の標準言語としてTypeScriptが承認されるというニュースが出ましたし、ReactやReact Nativeの主要なライブラリはTypeScriptで開発されていることも少なくありません。キッチハイクでは?
初期リリースでは型チェッカーは入っていませんでした。ここ半年ほどでTypeScriptを随時導入していき、現在は多くのコンポーネントがTypeScript化されています。
コラム: TypeScript と VS Code
TypeScriptの導入を決断したきっかけの一つが、VS Code ( Visual Studio Code ) でした。TypeScriptを導入すると、補完やエラー表示などVS CodeのIDEとしての機能を使えるようになり、開発効率が上がるのではと期待しました。
Lint / コーディングスタイル
検討としては Prettier か ESLint ( or TSLint ) か、もしくはその両方を連携させるかでしょうか。
ライブラリ star数 特徴 Prettier 34,595 フォーマッタ ESLint 15,412 フォーマッタ + 静的解析ツール。未定義の変数の警告などができる。 キッチハイクでは?
現状ではフォーマッタだけでやりたいことができているため、Prettierのみで運用しています。
テスト設計
テストで担保したいカバレッジや方針によって、どのツールを使ってどこまでやるかが決まってきます。今回はキッチハイクアプリでの事例を紹介します。
キッチハイクアプリでは、可能な限りビジネスロジックをAPI側に持たせる薄いViewとしてのアプリという設計指針で開発しています。ビジネスロジックはRailsのRSpecでテストするので、アプリではView側のテストが多くなります。開発初期は手動テストがメインでしたが、Jest, E2Eの導入により、手動テストの時間は大幅に削減され多くのテストが自動化されました。
Jest + Enzyme
コンポーネントのテストにはJestと、Airbnbが開発している Enzyme を一緒に使っています。
E2E
クロスプラットフォームのE2Eテストにはいくつか選択肢があります。
ライブラリ star数 特徴 Detox 5,632 React Native専用。テスト用のコンパイルが必要。 Appium 10,553 Seleniumを使用した汎用的の高いフレームワーク Cavy 1,068 React Native専用 キッチハイクでは多くのReact Nativeツールを公開しているwixが開発しているという点や、グレーボックステストというコンセプトから Detox を採用しました。
CI/CD
React NativeをサポートしてるCI/CD環境は CircleCI か Bitrise でしょうか。キッチハイクではまだ導入していないので、今後の課題となっています。
ここまでReact Nativeの開発体験についての考慮ポイントを上げてきました。
まだ他にも
- 機能要件
- マーケティング要件
- 運用設計
を追記して書こうと思っています。各項目を追加したら通知しますので、もし必要な方はストックしていただけると通知いたします。
最後まで読んでいただいてありがとうございました。
2日目は 1人チーム本 や 実践Expo の著者ハムカツおじさんこと @watanabe_yu さんです。お願いします!
- 投稿日:2019-12-01T23:49:38+09:00
コピペで動く、React Native + React Hooks + Redux のミニマムコード
React Hooks で Redux を使うための情報はパラパラありますが、一通り動作する小さなコードがなかなか見つからなかったため、こちらにまとめました。
※ 想定読者は「Redux は React などと一緒に状態管理に使うものらしい。Flux の図もなんとなく見たことある。でもコードは書いたことない」くらいの方です
※ サンプルコードは React Native + Expo、TypeScript です
※ 動作確認はexpo start
で実施したまでです
※ 筆者は React 超初心者ですソースコードは こちら です。
環境
$ node -v v12.3.1 $ yarn -v 1.16.0 $ expo --version 3.4.1その他のバージョンは package.json を参照ください。
準備
Expo プロジェクトの初期化
expo init
でプロジェクトを初期化しました。テンプレートとしては blank (TypeScript) を、パッケージマネージャとしては Yarn を選択しました。
Redux のインストール
$ yarn add redux react-redux $ yarn add --dev @types/react-redux初期化時点の App.tsx
App.tsximport React from 'react'; import { StyleSheet, Text, View } from 'react-native'; export default function App() { return ( <View style={styles.container}> <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', }, });これで実装の準備が整いました。
ミニマムコードの実装
合計で 3 つのファイルだけ新規作成・編集します。
内容は以下の通りです。
- Redux の action、reducer などを src/store.ts の 1 ファイルに全て書く
- Todo を入力・表示するコンポーネントを src/Todo.tsx に作る
- App.tsx で Redux の store を設定する
Redux の action、reducer などの実装
src/store.tsimport { createStore } from "redux" // actions const ADD_TODO_ACTION = 'ADD_TODO' // action creaters export function addTodo(todo: string) { return { type: ADD_TODO_ACTION, payload: { todo } } } // init state const initState = { todos: [] } // reducer export const todoReducer = (state = initState, action) => { switch (action.type) { case ADD_TODO_ACTION: return { todos: [...state.todos, action.payload.todo] } default: return state } } // store export const store = createStore(todoReducer)各要素はざっくり言うと以下のようになっています。
- actions ... store に保存した値への操作の種類を定義
- action creaters ... アクションの実態を生成する関数
- init state ... store に保存する状態の初期値
- reducers ... 各アクションに対する実際の処理を記述
Todo コンポーネントの作成
src/Todo.tsximport React, { useState } from "react"; import { StyleSheet, Text, View, TextInput, Button } from "react-native"; import { useDispatch, useSelector } from "react-redux"; import { addTodo } from "./../src/store"; export default function Todo() { const [todo, setTodo] = useState(""); const dispatch = useDispatch(); const todos = useSelector<any, Array<string>>(state => state.todos); const addTodoOnPress = () => { if (todo === "") { return; } dispatch(addTodo(todo)); setTodo(""); }; return ( <View style={styles.container}> <Text>TODO 入力</Text> <TextInput value={todo} onChangeText={t => setTodo(t)}></TextInput> <Button title="保存" onPress={addTodoOnPress} /> {todos.map((t, i) => ( <View key={i}> <Text>{t}</Text> </View> ))} </View> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: "#fff", alignItems: "center", justifyContent: "center", }, });使っている要素をざっくり説明すると ...
- useState ... コンポーネント内に状態を持つための関数。この戻り値の todo が TextInput と双方向バインドされるかんじ
- useDispatch ... Redux の action を実行 (dispatch) するための準備。Button の onPress で
dispatch(addTodo(todo))
という処理を実行し、store に addTodo という action creater の結果を実行 (dispatch) する- useSelector ... Redux の store から値を取り出している。
todo.map ...
の部分で一覧を画面に表示するApp.tsx の書き換え
App.tsximport React from "react"; import { Provider } from "react-redux"; import { store } from "./src/store"; import Todo from "./src/Todo"; export default function App() { return ( <Provider store={store}> <Todo /> </Provider> ); }全体を react-redux の
Provider
で囲み、store を設定してあげるだけです。混乱したところ
mapStateToProps、matchDispatchToProps、connect
Native Base のドキュメントの「Basic Redux Counter Example」などでは mapStateToProps、matchDispatchToProps、connect などがコンポーネントと一緒に書かれているが、これは必要なのか最初はよく分かりませんでした。
結論としては不要でした。
おわりに
最初は何をどうすればいいのか全然分からなかったので、こうしてまとめられて満足です。
React Hooks など、React の記述はどんどんアップデートされており、周辺エコシステムも充実しているため、React を一から学ぶという状態では何が一番今風の書き方なのかなかなか分かりませんでした。
もっと調べていくと、さらにブラッシュアップされるのかもしれません。。
参考
- 投稿日:2019-12-01T22:44:46+09:00
【React】はじめてReact触ってみた!~create-react-app編~
概要
JavaScriptを勉強しているときにReactなるものの存在を耳にした。同期にもReactを使って活躍している人もいるので、この機会に少しお勉強したいということでその備忘録。
Twitterで「Reactおちえて」って呟いたら、同期でProgateよりもSEOの高いReactの記事書いている子がいたので、その子の記事を参考にしました。この場を借りてお礼申し上げます。
その子のサイトはこちら→どらごんテックReactとは
Facebookが作ったJavaScriptのビュー・ライブラリのこと。
SPA(Single Page Application)への変化とともに需要が高まっている。
ReactがjQueryという麻薬から我々を解放してくれる、そんなお話。(抜粋)
堅苦しい定義は、優秀な先人の方々に任せたいなって...これからReactを勉強する人が最初に見るべきスライド7選
出来る限り短く説明するReact.js入門
今から始めるReact入門 〜 React の基本開発環境
Windows 10
npm(ver6.9.0)とりあえず動かしてみる
優秀な同期の一人の@atsuo1023に、Reactはとりあえず
create-react-app <プロジェクト名>
しとけば、難しい環境構築なしにできると聞いたのでやってみました。
まずは、create-react-app
のコマンドが打てるように、npm(Node Package Manager)を使って
npm install -g create-react-app
をして早速create-react-app test
でGO!!!
・・・嘘やん...
create-react-app test
打つだけでいいって言うたやん。こんなんあんまりや...なんでコマンド動かないん?
...私気になりますっ!色々調べて、試してみました。
・同じディレクトリ下に以前友達がcreate-react-app
で作ったフォルダがあったので、ディレクトリを変更 → 意味なし
・npm -v
でローカルにもnpmがあるか確認 → 6.9.0 あった
・create-react-app --version
で確認 → 3.2.0 あった
・npm update -g npm
でバージョン確認 → 反応なし(最新?)結果...
npm cache clean --force
で直った!!
キャッシュをクリアするってことらしい!!よかった~!(参考記事)
ベースとなるプロジェクトが作成できたので、ディレクトリを移動(
cd <プロジェクト名>
)してnpm start
をたたきます!
すると、
となり、自動でブラウザが開きます。
時間はかかりましたがなんとかReactなるものを動かすことができました!
次回以降、Clickの動作を加えたり、いろいろ試してアウトプットしていきます!!補足(npmをインストールしていない方へ)
しっかり版
簡易版
npmはNode.jsに付随するものっぽいので、必要なのはNode.jsのインストールかと!
入ってるかわからないときは、とりあえずコマンドプロンプトで
node --version
ornpm --version
をたたいてみましょう!参考リンク
・新しい React アプリを作る
・Create React Appで環境構築 – React入門
・create-react-appを使ってReactコンポーネントの素振り、GitHub Pagesへのデプロイまで
・Github issue
・知っていると捗るcreate-react-appの設定おわりに
環境構築ってとても難しい気がしていて、そこで挫折しかけることなんてざらにあると思います。
今回扱ったcreate-react-app
は環境構築がほとんどいらないので、とりあえず触ってみたい初学者にとっては最高のものだと思います!
是非皆さんも試してみてください!
また、この記事で間違っているところがありましたら、教えていただけると大変助かります!
最後まで見ていただきありがとうございました。
- 投稿日:2019-12-01T22:37:27+09:00
短絡評価を用いたReactでのコンポーネント配置
はじめに
いなたつアドカレへようこそ初日です、頑張って一人で25記事書くODL(あうとぷっとどりぶんらーにんぐ)企画、完走したいですね。
自分の中にあるネタを引っ張り出して記事を書いていきます。。。くおりてぃ。。。。
今日は頭にあるとスマート?に実装をかける?やつを忘れないようにここにのこしておきます。
短絡評価とは
andやorで値を評価する時に
A && B // A,B共に真 A || B // A,Bどちらかが真となるため
A && B
の A が偽の場合やA || B
のAが真の場合、つまりBの値にかかわらずA && B
やA || B
の評価結果が決定する場合にはBを評価しないといった言語使用です。主な使用ケースとしては、Bに動作の重い処理を置いて、Aの値で評価が決定してしまい、Bの結果が不要な(重い処理を走らせる必要がない)場合に処理を軽減する
今回はこの性質を利用し、ある条件が真となる場合にのみコンポーネントを配置するといったことを行う。
じっそー
結論から言ってこんな感じ
{i % 2 == 0 && <div>test</div>}上のプログラムはiが偶数の時に
<div>test</div>
を配置するといった内容です。
A && B
の A が偽の場合やA || B
のAが真の場合、つまりBの値にかかわらずA && B
やA || B
の評価結果が決定する場合にはBを評価しないといった言語使用です。の
&&
の場合の性質の応用ですね、Aが偽の場合はたとえBが真であっても、結果は偽となるため、Bを評価しないと言った性質なので、iが奇数の場合は右の式が評価されず、<div>test</div>
は配置されません。
しかし、iが偶数の場合はAが真なので、Bを評価しないと、式全体の評価ができないため、Bが評価され、<div>test</div>
が配置されます。おわりに
このようにして、とある条件が真の場合のみ、コンポーネントを配置することができます。
実際の実装では、三項演算子を用いて、条件が真の場合と偽の場合で配置するコンポーネントを分けると言った場面が多いかもしれませんが(知らんけど)&&
の短絡評価の性質を利用することで、条件が真の場合のみコンポーネントを配置すると言った実装がスマート?にかけます。
- 投稿日:2019-12-01T22:23:37+09:00
D言語くんと秘密のレポート
はじめに
D言語くんは好奇心が旺盛なことで知られており、常にTwitterで自身に関するツイートを監視しています。
それこそまさしく365日24時間、監視中の鬼気迫る形相たるやD言語くんの如しです。
また、毎年そのD言語くんアイが捉えていたと思われるツイートを長年のD言語くん研究家である @simd_nyan 氏がまとめてレポートとして公開されています。
- 2019年 : https://gist.github.com/simdnyan/a82a49ed5a2d4e559b393f20746a6587
- 2018年 : https://gist.github.com/simdnyan/1f9f19c523100ceeadc8f67b017b7ddb
- 2017年 : https://gist.github.com/simdnyan/02fbf4106ad9bd39cf02eb418ced5fa5
このレポートはツイートのIDを列挙したものである、ということまではわかっていますが、とても人間が理解できるものではありません。
そこでD言語くんの御心を理解するため、近年のWeb技術をもってビューアーを作り、そのレポートの真実に迫っていきたいと思います。
(とか言いながら同じことをやってもう3年目になります)
概要
D言語くんが見ていたものが何なのか、理解するために長ったらしい文章など必要ありません。
以下のリンクからビューアーを参照してください。https://lempiji.github.io/dman-tweet-viewer/
毎度半日くらいでほぼゼロから書いているため、今年も今年で見た目はこんな感じです。
なお、0以上を想定するカウンター値が初手-1を押すだけで負の値を取るバグっぷりです。
「これでよく公開できたな」と称賛してください。こんな感じで大丈夫です。知らんけど。詳細
こちらのソースは以下のリポジトリに一式置いてあります。
https://github.com/lempiji/dman-tweet-viewer
日ごろ使っている安定のサンドボックス的最小構成である TypeScript + React + parcel をベースに Material UI + gh-pages を追加している感じです。
React的な話をしておくと、最近流行ってると噂の React Hooks を使って埋め込みツイートを表示するコンポーネントを作り、あとはgistからテキスト取ってきて加工、適当にスライダーなど揃えて突貫工事でビューアーとして仕立てました。
今年は年を切り替えられるようにしたので、今後はそこらへんをメンテするだけでD言語くんと共に歩むことができますね!
準備
埋め込みツイートの表示は、公式で提供されるスクリプトを読み込んでやれば簡単に行えます。
TypeScriptで使う定義と合わせて以下のようにしておきます。スクリプトの読み込み<script src="https://platform.twitter.com/widgets.js"></script>Twitterのスクリプトに対応するための定義interface Twttr { widgets: { createTweet(id: string, container: HTMLElement, options: {}): Promise<HTMLElement>; }; } declare const twttr: Twttr;(Windowインターフェースを拡張するnamespaceを書くほうが良い気がしましたが、気が向いたら書き換えることにします)
埋め込みツイート用コンポーネント
イマドキなら useRef と useEffectを使えば楽勝ですね。(本当か?)
Twitterのスクリプトが提供する関数を使えば簡単に埋め込みツイートを表示できます。
関数の引数に使うため、ノードの参照を取ってきて、useEffect
の中で呼び出します。
createTweet
が勝手にノードの挿入までやってくれるので使うのは楽なのですが、戻り値がPromiseなので、IDを切り替えるときに前のツイートを消すのがちょっと面倒です。普通は初期化したら表示しっぱなしだと思うのでこれは仕方ないですかね。
useEffectの戻り値でノードを削除する処理を返すあたりがポイントです。ツイート表示用のコンポーネントinterface TweetProps { tweet: string; } function Tweet({ tweet }: TweetProps) { const containerRef = React.useRef<HTMLDivElement>(); React.useEffect(() => { const t = twttr.widgets.createTweet(tweet, containerRef.current, {}); return () => { t.then(e => { if (e && e.parentNode) { e.parentNode.removeChild(e); } }); }; }, [tweet]); return <div ref={containerRef}></div> }振り返り
- 久々にMaterial UIのサイトに行ったら日本語で表示できるようになっていた
- https://material-ui.com/ja/
- (コンポーネント名を翻訳するのはやめてほしい)
- 噂のSuspenseを使いたかったけど、せっかくなら…とuseRefと組み合わせたらイマイチうまく動かず諦めた
- 紹介記事がどれもグローバル変数に入れているのは何か理由がありそうな気がする
- useReducer使うように書き換える予定が前日に作業開始したため間に合わず
- インクリメント/デクリメント周り、どう見てもdispatchで変化量を指定する方が良いですね。
というわけで以上です!
アドベントカレンダーの準備は計画的に、用法容量を守って正しくご参照ください!
- 投稿日:2019-12-01T22:01:40+09:00
create-react-appで生成したReactのESLintをVSCodeでやる(自動整形付き)
概要
create-react-app用のESLintをVSCodeでやる。
前提知識
ESLint自体はcreate-react-appすると自動的にインストールされている。
で、create-react-appした時のpackage.json
に↓があるのがポイント。{ //...略... "eslintConfig": { "extends": "react-app" }, //...略... }
npm run start
すると、サーバが起動し、その中のメッセージで自動的にlintして警告を出したりしてくれるが、これはreact-app
というルールを使用していることになる。設定
で、VSCodeに
react-app
のlintを同じように使用するためには、1. VSCodeの拡張にESLintを入れる
2. ESLintのファイルを生成する
ctrl + shift + P
で、ESLint: create ESLint congiguration
を選択する。
ESLintの設定ファイル生成のコマンドラインが起動するので、↓を参考に設定。
ワークスペースのルートに、
eslintrc.js
ファイルが生成される。3. eslintrc.jsのベースルールをreact-appへ変更する
eslintrc.jsの
extends
を、'extends': 'eslint:recommended',↓へ変更する。
'extends': 'react-app',4. ファイル保存時に自動的にlintルールで整形されるようにする
ctrl + shift + P
で、ユーザー設定を開く
を選択する。
ユーザ
をワークスペース
へ変更する。
設定の検索
に、ESLint
と入力する。
Files: Auto Fix On Save
にチェックを入れる。↓諸々含めた設定例。
{ "files.eol": "\n", "editor.formatOnSave": true, "eslint.autoFixOnSave": true, "editor.codeActionsOnSave": { "source.organizeImports": true // importの自動編成 } }おまけ
- セミコロン付与
- インデントスペース4つ
- シングルクオート
- 末尾の空白を削除
- 複数空行禁止
のサンプル
eslintrc.js
module.exports = { 'env': { 'browser': true, 'es6': true }, 'extends': 'react-app', 'globals': { 'Atomics': 'readonly', 'SharedArrayBuffer': 'readonly' }, 'parserOptions': { 'ecmaFeatures': { 'jsx': true }, 'ecmaVersion': 2018, 'sourceType': 'module' }, 'plugins': [ 'react' ], 'rules': { 'indent': [ 'error', 4, { "SwitchCase": 1 } ], 'quotes': [ 'error', 'single' ], 'semi': [ 'error', 'always' ], 'no-trailing-spaces': [ "error", { "skipBlankLines": true } ], 'no-multiple-empty-lines': [ 'error' ], } };
- 投稿日:2019-12-01T21:56:07+09:00
AWS amplify Reactでログイン画面作成
amplifyとやらが便利そうなので入門として、TODOリストでも作ってみようかなと思った、第一弾です。
次は、Cognito認証付きAPIを作ろうと思います。0. 環境
$ node -v v10.16.3 $ npm -v 6.9.01. Amplify CLIインストール(準備)
公式: https://aws-amplify.github.io/docs/
$ sudo npm install -g @aws-amplify/cli$ amplify configure Scanning for plugins... Plugin scan successful Follow these steps to set up access to your AWS account: Sign in to your AWS administrator account: https://console.aws.amazon.com/ Press Enter to continue ## ここでブラウザが開き、ログイン画面が出るのでログインする。 ## すでにログインしている場合は、AWSマネジメントコンソールが開く。 ## ログインできたら、エンターを押す。 Specify the AWS Region ? region: eu-west-1 eu-west-2 eu-central-1 ❯ ap-northeast-1 ## 上・下カーソルで選択できるので、入力。 ? user name: [任意の名前を入れる] Complete the user creation using the AWS console https://console.aws.amazon.com/iam/home?region=undefined#/users$new?step=final&accessKey&userNames=************************************ Press Enter to continue ## ブラウザが開くのでそのまま登録(プログラムによるアクセスだけチェック入っていればOKです) Enter the access key of the newly created user: ? accessKeyId: ****************** ? secretAccessKey: ******************************* This would update/create the AWS Profile in your local machine ? Profile Name: default ## ~/.aws/credentials が更新されるので、すでにdefault登録ずみの場合は注意。 Successfully set up the new user.2. ReactJS生成
マニュアル: https://aws-amplify.github.io/docs/js/start?platform=react
今回はReactでマニュアルに則って作ってみます。
$ npx create-react-app mytodo $ cd mytodo $ npm install aws-amplify $ npm install aws-amplify-react $ npm startここまでで一旦Reactのデフォルト画面が表示されます。
3. バックエンドのセットアップ
3-1. 初期設定
★★のところが自分で入力できる箇所です。
★★★のところはそのままenterでOKです。$ amplify init Scanning for plugins... Plugin scan successful Note: It is recommended to run this command from the root of your app directory ? Enter a name for the project mytodo ★★ ? Enter a name for the environment dev ★★ ? Choose your default editor: Visual Studio Code ★★ ? Choose the type of app that you're building javascript ★★ Please tell us about your project ? What javascript framework are you using react ★★ ? Source Directory Path: src ★★★ ? Distribution Directory Path: build ★★★ ? Build Command: npm run-script build ★★★ ? Start Command: npm run-script start ★★★ Using default provider awscloudformation For more information on AWS Profiles, see: https://docs.aws.amazon.com/cli/latest/userguide/cli-multiple-profiles.html ? Do you want to use an AWS profile? Yes ★★ ? Please choose the profile you want to use default ★★←ここはamplify configureで設定したProfile Nameを入力. ⠼ Initializing project in the cloud... ~~~~~ ✔ Successfully created initial AWS cloud resources for deployments. ✔ Initialized provider successfully. Initialized your environment successfully. Your project has been successfully initialized and connected to the cloud! Some next steps: "amplify status" will show you what you've added already and if it's locally configured or deployed "amplify <category> add" will allow you to add features like user login or a backend API "amplify push" will build all your local backend resources and provision it in the cloud "amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud Pro tip: Try "amplify add api" to create a backend API and then "amplify publish" to deploy everything3-2. auth(Cognito)の設定
3-2-1. Cognito作成
Emailでのログインを設定しました。
$ amplify add auth Using service: Cognito, provided by: awscloudformation The current configured provider is Amazon Cognito. Do you want to use the default authentication and security configuration? Default configuration ★★ Warning: you will not be able to edit these selections. How do you want users to be able to sign in? Email ★★ Do you want to configure advanced settings? No, I am done. ★★ Successfully added resource mytodo locally Some next steps: "amplify push" will build all your local backend resources and provision it in the cloud "amplify publish" will build all your local backend and frontend resources (if you have hosting category added) and provision it in the cloud以下コマンドでCloudFormation Stackが作成されます。
一度作ってみて、ログイン機能の確認をしてみます。$ amplify push3-2-2. ログイン機能の実装
公式: https://aws-amplify.github.io/docs/js/authentication
SignUp/Inページ自体は、以下をのコードをApp.jsに追加するだけでできます。
ログイン後、先ほどのデフォルトページが表示さるようになります。App.jsimport Amplify, { Auth } from 'aws-amplify'; import { withAuthenticator } from 'aws-amplify-react'; import awsconfig from './aws-exports'; Amplify.configure(awsconfig); const signUpConfig = { header: 'MyTodo SignUp', hideAllDefaults: true, defaultCountryCode: 1, signUpFields: [ { label: 'email', key: 'username', required: true, displayOrder: 1, type: 'email' }, { label: 'password', key: 'password', required: true, displayOrder: 2, type: 'password' }, ] }うまくいかないときは、src/aws-wxports.jsの設定が正しいか確認してください。
自分は、この設定が別プロジェクトのものになっていて詰まりました(´-`)また、
<button onClick={() => Auth.signOut()}>Sign Out</button>を追加してあげるとログアウトできます。
4. おわり
おそらく、この通りにやれば10分~30分程度でログイン画面ができてしまいます。便利な世の中になりましたね。
今回は以上です。
- 投稿日:2019-12-01T20:45:54+09:00
React + Redux + TypeScript でモダンなwebアプリを作るチュートリアルを考えてみた①
はじめに
- フロントエンドで利用されているフレームワークでReactがjQueryを抜いて1位になったというアンケート結果が公表されました
調査:フロントエンドまわりのアンケート調査結果 2019年版。Sass の利用率77%。JSはReactがjQueryを上回っています。英語ですが要チェック! https://t.co/hs4h5TCF2e
— Webクリエイター ボックス (@webcreatorbox) November 14, 2019Q. Which JavaScript libraries and/or frameworks do you currently use most frequently on projects?
- 自分も普段からReactを使用しているのですが、日本ではReactに関するイベントも少なく、イマイチ盛り上がりに欠けるような気がしています
- Vueは Vue Fes Japan を開催していて羨ましい・・・(今年は台風で中止でしたが)
- 確かに、自分も初めてReactに触れた時はこれまでのJavaScriptと概念も書き方も違いすぎて困惑しましたし、次々と新しい機能がリリースされるのでキャッチアップが大変な面もありました
- それでも、慣れてしまえばサクサクComponentを作成できますし、パフォーマンスの面でも優れていますし、React Nativeでネイティブアプリも作ることもできます
- 日本でもっとReactユーザーが増えて欲しい・盛り上がって欲しいという願いも込めて、実践的なチュートリアルっぽいものを作ってみました
作るもの
- Google Books のAPIを使って本の検索アプリを作る
主な技術・ライブラリなど
- React (Hooks)
- Redux (Hooks)
- TypeScript
- styled-components
- react-router
- immutable.js
- redux-saga
- axios
- eslint
- semantic-ui
- yarn
手順
yarn
のインストール
- パッケージマネージャーについて、
npm
がデフォルトで入ってますがyarn
の方が高速で安定性があるのでyarn
を使用しています- https://yarnpkg.com/lang/ja/docs/install/
terminalbrew install yarn
create react-app
でアプリの基盤を作成
react-tutorial
という名前のアプリを作成します--typescript
のオプションをつけることでTypeScriptでアプリを作成できます- https://create-react-app.dev/docs/getting-started/
- nodeのversionが古いとエラーになるので、その場合は
nodebrew
などで新しいversionにしておきましょうterminalyarn create react-app react-tutorial --typescript
- これだけでアプリを動かす環境が整いました
アプリを起動する
- 作成したアプリのディレクトリに移動して
yarn start
で起動します- 他のスクリプトについても自動で生成された
README
に説明があるので確認しておきましょうterminalcd react-tutorial yarn start
- デフォルトだと http://localhost:3000 で次のような画面が表示されるはずです
- この状態が次のコミットになります
eslint
でコードの書き方を統一する
- 先に
eslint
でコードの静的チェックをすることで不用意なエラーや書き方のズレを防ぐようにしておくと便利です- TypeScript用の
tslint
もありますが、今後はeslint
に統合されていくようなのでeslint
だけ使用していますpackage.json
のdevDependencies
に以下を追記し、yarn install
を実行して必要なライブラリをinstallしますpackage.json{ ... "devDependencies": { "@typescript-eslint/eslint-plugin": "^2.0.0", "@typescript-eslint/parser": "^2.0.0", "eslint": "^6.1.0", "eslint-config-prettier": "^6.0.0", "eslint-config-react": "^1.1.7", "eslint-import-resolver-webpack": "^0.11.1", "eslint-plugin-import": "^2.18.2", "eslint-plugin-prettier": "^3.1.0", "eslint-plugin-react": "^7.14.3", "prettier": "^1.18.2" } ... }
.eslintrc.json
を作成してeslintの設定を記述します.eslintrc.json
は好きなようにカスタマイズできますが、次に自分の例を載せておきます
- 基本的にはrecommendedの設定をそのまま使用しています
- React, TypeScript, Prettier を併用できるようにpluginを設定しています
- また、importのソートも統一したかったので
eslint-plugin-import
も追加しています- TypeScriptを使用している場合はPropTypesがほぼ不要になるため、今回は
"ignoreDeclarationSort": true
としています.eslintrc.json{ "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:@typescript-eslint/recommended", "prettier/@typescript-eslint", "plugin:prettier/recommended", "plugin:import/errors", "plugin:import/warnings", "plugin:import/typescript" ], "plugins": ["react", "@typescript-eslint", "prettier"], "env": { "node": true, "browser": true, "jest": true, "es6": true }, "rules": { "sort-imports": ["error", { "ignoreDeclarationSort": true }], "import/order": ["error", { "newlines-between": "always" }], "prettier/prettier": [ "error", { "singleQuote": true, "semi": true, "printWidth": 120, "trailingComma": "all", "jsxSingleQuote": true } ] }, "parser": "@typescript-eslint/parser", "settings": { "react": { "version": "detect" }, "react/prop-types": ["error", { "skipUndeclared": true }], "import/ignore": ["node_modules"], "import/resolver": { "node": { "moduleDirectory": ["node_modules", "src"] } } } }
- さらに、
scripts
にlint用の記述を追加しておくとyarn lint:fix
のように呼び出せて便利ですpackage.json{ ... "scripts": { "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "lint:fix": "yarn lint --fix" }, ... }
lintに対応したのが次のコミットになります
VSCodeを使用している場合は
.vscode/setting.json
に次のようにワークスペースの設定を追加しておくと捗ります
- VSCodeデフォルトのフォーマッターとバッティングしてしまうので
"editor.formatOnSave": false
にしておく必要があります.vscode/setting.json{ "editor.rulers": [120], "files.trimTrailingWhitespace": true, "eslint.enable": true, "editor.renderWhitespace": "all", "css.lint.ieHack": "warning", "javascript.implicitProjectConfig.checkJs": true, "typescript.updateImportsOnFileMove.enabled": "always", "eslint.validate": [ "javascript", "javascriptreact", { "language": "typescript", "autoFix": true }, { "language": "typescriptreact", "autoFix": true } ], "editor.formatOnSave": false, "eslint.autoFixOnSave": true }スタイルを
styled-components
で記述する
styled-components
を導入することで、class名でスタイルのマッピングをすることを辞め、コンポーネントに直感的にスタイルを適用することができるようになります- https://www.styled-components.com/
- ライブラリに型定義ファイルが提供されている場合、
@types/XXX
をinstallできます- 型定義ファイルのように開発環境のみで使用するライブラリは
devDependencies
に記述するため、-D
オプションが必要ですterminalyarn add styled-components yarn add -D @types/styled-components
- 試しに、既存の
.css
ファイルを置き換えてみます- VSCodeをご利用の方は次のようなextensionもあるのでオススメです
- https://marketplace.visualstudio.com/items?itemName=jpoissonnier.vscode-styled-components
- グローバル(アプリ全体)に適用させるスタイルは
createGlobalStyle
で定義します<>
は<React.Fragment>
の省略で、複数の要素を返す場合に1つにまとめる時に使用します- https://ja.reactjs.org/docs/fragments.html
App.tsximport React from 'react'; import styled, { createGlobalStyle } from 'styled-components'; import logo from './logo.svg'; const App: React.FC = () => { return ( <> <GlobalStyle /> <Wrapper> <Header> <Logo src={logo} className='App-logo' alt='logo' /> <Text> Edit <CodeText>src/App.tsx</CodeText> and save to reload. </Text> <OfficialLink className='App-link' href='https://reactjs.org' target='_blank' rel='noopener noreferrer'> Learn React </OfficialLink> </Header> </Wrapper> </> ); }; const GlobalStyle = createGlobalStyle` body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } `; const Wrapper = styled.div` text-align: center; `; const Header = styled.header` background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); color: white; `; const Logo = styled.img` height: 40vmin; `; const OfficialLink = styled.a` color: #09d3ac; `; const Text = styled.p``; const CodeText = styled.code``; export default App;
styled-componsnts
に対応したのが次のコミットになります新しいルーティングにコンポーネントを追加する
react-router
を使用することでURLによって表示するコンポーネントを切り替えるルーティングを実装することができますreact-router
はreact-router-dom
に含まれているので、react-router-dom
のみ追加すれば大丈夫ですyarn add react-router-dom yarn add -D @types/react-router-dom
- 先に新しいルーティングで表示する仮のOtameshiコンポーネントを作成しておきます
Otameshi.tsximport React from 'react'; import styled from 'styled-components'; export const Otameshi: React.FC = () => { return <Wrapper>Otameshi</Wrapper>; }; const Wrapper = styled.div``;
routes.tsx
を作成してルーティングを定義しますSwitch
を使用するとpathが一番最初に合致したRoute
のみ表示させることができます- 一番最後に
Redirect
を記述することで、どれにも合致しなかった時にto
にリダイレクトさせることができますRedirect
の代わりに404ページなどを表示させたい場合は、pathの指定のないRoute
を記述すればOKですroutes.tsximport React from 'react'; import { Switch, Route, Redirect } from 'react-router-dom'; import App from 'App'; import { Otameshi } from 'Otameshi'; export const Path = { app: '/', otameshi: '/otameshi', }; const routes = ( <Switch> <Route exact path={Path.app} component={App} /> <Route exact path={Path.otameshi} component={Otameshi} /> <Redirect to={Path.app} /> </Switch> ); export default routes;
- これを
ReactDOM.render
で描画するように書き換えますRouter
にはブラウザの履歴を記録するhistory
が必要なので、createBrowserHistory
で生成していますindex.tsximport React from 'react'; import ReactDOM from 'react-dom'; import { createBrowserHistory } from 'history'; import { Router } from 'react-router-dom'; import routes from 'routes'; import * as serviceWorker from './serviceWorker'; const history = createBrowserHistory(); ReactDOM.render(<Router history={history}>{routes}</Router>, document.getElementById('root')); serviceWorker.unregister();
- これでrouterが適用されたので、 http://localhost:3000/otameshi にアクセスすると仮のOtameshiコンポーネントが表示されます
- http://localhost:3000/otameshiiii など定義していないURLアクセスするとリダイレクトされることも確認できます
- さらに、Appコンポーネントにこのpathへのリンクを作ってみましょう
- styled-componentsでは
styled(XXX)
のようにコンポーネントのスタイルを拡張することができます- ここでは、
styled(Link)
としてLink
コンポーネントを拡張してスタイルを当てていますApp.tsximport React from 'react'; import styled, { createGlobalStyle } from 'styled-components'; import { Link } from 'react-router-dom'; import logo from './logo.svg'; import { Path } from 'routes'; const App: React.FC = () => { return ( <> <GlobalStyle /> <Wrapper> <Header> <Logo src={logo} className='App-logo' alt='logo' /> <Text> Edit <CodeText>src/App.tsx</CodeText> and save to reload. </Text> <OfficialLink className='App-link' href='https://reactjs.org' target='_blank' rel='noopener noreferrer'> Learn React </OfficialLink> <OtameshiLink to={Path.otameshi}>おためしページへのリンク</OtameshiLink> </Header> </Wrapper> </> ); }; ... const OtameshiLink = styled(Link)` color: #fff; margin-top: 30px; `; ...
- 上記の対応は次のコミットを参考にしてみてください
React Hooksで状態管理を行う
- React Hooksを使用するとFunctional Componentでもstate(状態)を管理できるようになります
- 試しに、入力したテキストを受け取って表示させてみましょう
useState
を使用する場合はconst [state名, stateを変更する関数名] = useState(初期値);
のように定義します- テキストエリアに入力された時に
onChange
イベントが発火し、入力されたvalueをchangeText
に渡して更新しています- 公式の説明
Otameshi.tsximport React, { useState } from 'react'; import styled from 'styled-components'; export const Otameshi: React.FC = () => { const [text, changeText] = useState(''); return ( <Wrapper> <Body> <Title>Otameshi Component</Title> <TextArea placeholder='テキストを入力してね!' onChange={(event): void => changeText(event.target.value)} /> <TextResult>{text}</TextResult> </Body> </Wrapper> ); }; const Wrapper = styled.div` display: flex; justify-content: center; `; const Body = styled.div``; const Title = styled.h1` text-align: center; `; const TextArea = styled.textarea` display: block; margin: 0 auto; box-sizing: border-box; width: 200px; `; const TextResult = styled.p` width: 200px; padding: 10px; margin: 20px auto; border: 1px solid blue; white-space: pre-wrap; box-sizing: border-box; `;
- 上記の対応は次のコミットを参考にしてみてください
Layoutコンポーネントを作成する
- HeaderやFoooterなど複数のページで表示したい共通コンポーネントがある場合、別々に呼び出すのは面倒です
- そこで、react-routerの仕組みを使って複数のコンポーネントに共通コンポーネントを当てることができます
- ここでは全てのコンポーネントの枠組みとなるLayoutコンポーネントを作成してみましょう
- Globalスタイルなどもここに移植しましょう
- childrenはコンポーネントの子要素が渡ってきます
Layout.tsximport React from 'react'; import styled, { createGlobalStyle } from 'styled-components'; import { Reset } from 'styled-reset'; export const Layout: React.FC = ({ children }) => { return ( <> <Reset /> <GlobalStyle /> <Wrapper> <Header>React Tutorial</Header> <Body>{children}</Body> </Wrapper> </> ); }; const GlobalStyle = createGlobalStyle` body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; } `; const Wrapper = styled.div` height: 100%; `; const Header = styled.div` display: flex; align-items: center; height: 60px; color: #fff; background-color: #09d3ac; font-size: 20px; font-weight: bold; padding: 0 20px; `; const Body = styled.div` height: calc(100vh - 60px); `;
routes.tsx
で<Switch>
の親要素にLayoutを適用しましょうroutes.tsximport React from 'react'; import { Redirect, Route, Switch } from 'react-router-dom'; import { Layout } from 'components/Layout'; import { App } from 'components/App'; import { Otameshi } from 'components/Otameshi'; export const Path = { app: '/', otameshi: '/otameshi', }; const routes = ( <Layout> <Switch> <Route exact path={Path.app} component={App} /> <Route exact path={Path.otameshi} component={Otameshi} /> <Redirect to={Path.app} /> </Switch> </Layout> ); export default routes;
- これで必ずLayoutコンポーネントを経由して子要素が呼ばれるようになりました
- App, Otameshi両方のコンポーネントにHeaderが表示されます
- 今回はここまでです!
今後の予定
- reduxの導入
- redux hooksでの状態管理
- redux-sagaでの非同期処理
- axiosでの通信処理
- immutable.jsでデータのモデル化
- 投稿日:2019-12-01T19:42:03+09:00
TypescriptとReact HooksでReduxはもうしんどくない
?背景
Reduxはしんどい、だるい、でかい、というイメージが定着して久しいですね ?
僕も3年ほど前にどっぷり触ったときは「こいつぁなかなか」という感想でした。
しかしながら状態管理ライブラリやらFlux思想やらの流れとしてReduxが不可避の存在だったために、おつらい経験をされた方も多かったのかなとお察しします。時代は巡り2019年末、令和元年のご時世ではすっかりTypescriptによる型安全、正式提供されたReact Hooksによる脱Class component・HOCフリーな省エネ設計などが定着してきており、この両者を前提とした構築がもはやスタンダードとなってきています。
諸兄の人柱的知見も相まって最近は敬遠されがちなReduxパイセンですが、この度久方ぶりにがっつりと向き合ってみると、上述した両者の恩恵を受けてなんだか垢抜けた感じになっていました。知ってましたか? ??
といった趣向の記事です。
?♀️ この記事でやらないこと
状態管理ライブラリ比較
- MobXのほうが〜、Context APIとくらべて〜、とかのあたり
- ぶっちゃけ一長一短なので導入するべきケースとかを比較しだすと記事長が30mくらいになるので
- ジハードがしたいわけではない
Redux不要論へのアンサー
- すみませんAdvent Calendar一日目の方の内容への当てこすり記事ではないんです ?タイミング悪くて申し訳ない…
- Reduxサイコ〜〜みんなRedux使うともれなく幸せになれます ?♀️ といった類の記事ではないです
その他推奨ライブラリの細かいHow to
- immerやらreselectやらの絶対使ったほうがいいライブラリってのはあるんですが、詳しい使い方とかはここでは書きません ?♂️
- 非同期処理に関しては個人的にredux-observableがダントツいいんじゃないかな〜とは思っていますが、RxJSをちょっとは知っとかないとダメだったり、Type定義あたりでちょこちょこストレスがあったり、という状況なので今回は言及しません ?
- 正直middlewareでうまいことやれてしまう説もあります。
? サンプル
https://gitlab.com/Ky7ie/redux_a_g0_g0
突貫ですみませんがサンプルリポジトリを用意しました。
状態管理部分の実装サンプルとしてご参照していただければと思っています。
(その他のとことかはいろいろ手を抜いています)? つらくないポイント解説
ActionとReducerをTypeScriptでさらっと面倒みてあげられる
結局のところ、素でReduxつかうとしんどいところって
「記述が分断されていて定義とかを追うのが大変… ?」
とか
「他人が書いたActionやらReducerをぐるっと追って理解しないと実装できないのキツ〜〜〜 ?」
という不透明性、見通しの悪さによるところが多かったなあという印象なんですがいかがでしょう。Typescript(およびTS連携できるIDE環境)の恩恵によって、そのあたりの「どこみたらいいか全然わからんし頭に全く入ってこないぞ ?♀️」がほぼ無くなりました。
Action
自前でちゃんとType定義してもいいですが、定義支援ライブラリの typescript-fsa を使うと記述がむちゃ簡素で気持ちいいです。
store/todo/actions/index.tsimport { actionCreatorFactory } from 'typescript-fsa'; const actionCreator = actionCreatorFactory('TODO'); export const Add = actionCreator<{ title: string }>('ADD'); export const ChangeStatus = actionCreator<{ index: number }>('CHANGE_STATUS'); export const Delete = actionCreator<{ index: number }>('DELETE');こう記述すると fsa = Flux Standard Actionに則ったActionをType定義つきで生成してくれます ?
Reducer
ReducerもType定義でちゃんと縛っておくと、後から処理を記述するときなどにヒジョーに快適です。
store/todo/reducer/index.tsimport { Reducer } from 'redux'; import { isType } from 'typescript-fsa'; import { produce } from 'immer'; import { Todo } from '..'; import { Add, ChangeStatus, Delete } from '..'; const initialState: Todo[] = []; export const reducer: Reducer<Todo[]> = (state = initialState, action) => { if (isType(action, Add)) { return produce(state, draft => { draft.push({ title: action.payload.title, status: 'not yet' }); }); } // 中略... return state; }; export default reducer;?
State
ちょっとしたTipsですが、State全体のType定義は
ReturnType
を使うことでサボれます。store/index.tsimport { combineReducers, createStore } from 'redux'; import { composeWithDevTools } from 'redux-devtools-extension'; import todo from './todo'; const reducers = combineReducers({ todo }); export const Store = createStore(reducers, composeWithDevTools()); export type State = ReturnType<typeof Store.getState>;React Hooksを使うとconnect()もmapStateToPropsもいらなくなる
個人的感動ポイントがここです ?
Reduxを勉強し始めたときも、書き始めてからも、
connect()()
mapStateToProps()
mapDispatchToProps()
このあたりの「おなじみ呪文シリーズ ?♀️」がどうにも気持ち悪く
(ヤダな〜〜 ?)
と苦々しく思っていたのですが、React Hooksの恩恵によりこれらがスッキリとした基準に置き換えられ、すべてをFunctional Componentとして記述できるようになりました ?✨
components/organisms/todo/index.tsximport React from 'react'; import { useDispatch, useSelector } from 'react-redux'; // ココ! import { Todo as TodoType, Add, GetAllTodos } from 'store/todo'; export const Todo: React.FC = () => { // 略... // // dispatcherを用意 const dispatch = useDispatch(); // これだけでstateを参照できる const todos = useSelector(GetAllTodos); const onSubmit = methods.handleSubmit(({ title }) => { // これだけでactionをdispatchできる dispatch(Add({ title })); // 略... // }); return ( <Container> // 略... // {todos.map((todo, index) => ( <TodoCard key={index} index={index} {...todo} /> ))} </Container> ); };store/todo/selectors/index.tsimport { createSelector } from 'reselect'; import { State } from 'store'; import { Todo } from '..'; export const GetAllTodos = createSelector( (state: State) => state.todo, (todos: Todo[]) => todos );Selectorによるstateのメモ化がもたらしてくれるパフォーマンスチューニング的意義に関しての解説アレコレ〜〜〜〜は今回は省きます ?
が、単純にコードの見通しだけ考えても非常に簡潔化でき、読みやすくなったのではないでしょうか ?
コラム : Re-ducksパターン ?
「ActionとかReducerとか記述散らばるのがしんどいぜ」というReduxあるあるがありましたが、そのあたりのディレクトリ構成に関しても研究が進んでおりRe-ducksパターンというものが個人的には一番しっくりきました ???
アレコレと合理的な理由付けと経緯があるのですが、よくまとめていただいている記事がありましたので... ?
そちらを読んでいただいた上でサンプルリポジトリの構成をご参照いただけるとスッキリ理解していただけるかと思います。
? Reduxはまだまだ有力な選択肢
フロントエンド技術の発展とトレンドの移り変わりがReduxを押し流してしまったように勝手に感じていたのですが、見つめ直してみると
「Redux全然イケてるじゃん ?」
と惚れ直す結果となりました。
Middlewareや強力なDevtoolなど、単純な状態管理にとどまらない部分も依然として魅力的なライブラリです。
これから改めて学ぶのも全然アリだと思います!
参考
- 投稿日:2019-12-01T18:50:34+09:00
カフェの注文でいつも焦るので、Reactアプリを作って解決した
アプリ概要
スタバやドトールなどの主要カフェチェーンのドリンク・フードメニューを店ごとに一覧できるアプリを作りました。
商品名と各サイズの値段が表示され、行をタップすれば公式の詳細ページに飛びます。
なぜ作ったか
いわゆる「喫茶店」だと席についてからゆっくりとメニューを見られますが、スタバなんかだとレジの目の前で即断しないといけないこともあります。
後ろに人が並んでるし、目の前には店員さんもいる・・・。
この状況ではメニューをくまなく見れないし、結局前と同じ無難な注文をしがちです。
並んでいる最中にゆっくりと吟味できたらいいのにと思ったので作りました。
URL
技術
すべてAWS上で構成しました。
(矢印はユーザーが求めるデータの流れです)
フロントエンド
ReactによるSPAで、S3上にホスティングしました。
S3の静的サイトホスティング機能でも十分かなと思いますが、httpsに対応するためにCloudfrontを通しています。
UIフレームワークにはMaterial-UIを使わせてもらいました。
バックエンド
ReactからAPI Gateway -> Lambda関数を通して、S3上に保存されているメニューデータをjsonを返しています。
そのデータは各カフェチェーンの公式サイトから毎日一度だけスクレイピングさせてもらっています。
スクレイピング
言語はPythonで、 requests-htmlというライブラリを使用しました。
PythonといえばrequestsやBeautiful soupなんかが有名ですが、requests-htmlはそのあたりのライブラリをまとめて使いやすくしたもののようです。
実際、かなり直感的に使えるのでオススメです。
課題
Reactにまだ慣れない
初めて作ったReactアプリなのでいろいろと戸惑うことも多かったです。
各コンポーネントの依存関係や責任範囲などは、reduxも含めてもっと勉強したいと思います。
プロダクトとしての価値
適当な理想を掲げればwebサービスなんていくらでもデッチ上げられますが、多くの人に使ってもらえるようなプロダクトは稀です。
このアプリは「ショボくても、ダサくても、確実に誰かのニーズを満たせること」を目指してアイデアを練った結果生まれました。
ただ、どれだけ考えても確実なアイデアなんて出ないのはしょうがないと思います。
とにかくフットワークを軽くして、小さな検証を積み重ねていくつもりです。
- 投稿日:2019-12-01T18:14:24+09:00
react-hooks-global-stateの紹介
はじめに
React Hooksがアナウンスされた時に、まずやってみようと思ったことが簡易なglobal stateの実現でした。React Hooks登場以前から試みていたのですが、それのHooks版を開発しました。
ライブラリ
https://github.com/dai-shi/react-hooks-global-state
このライブラリはとてもシンプルなものになっています。つまり、React ContextとReact Hooksを素直に使ったglobal stateになっています。
いくつかの特徴はありますが、あとで説明します。
使い方
まず必要なライブラリをimportします。
import React from 'react'; import { createGlobalState } from 'react-hooks-global-state';最初にinitialStateを定義します。
const initialState = { count: 0, text: 'hello', };次に、それをもとにglobal stateを作成します。
const { GlobalStateProvider, useGlobalState } = createGlobalState(initialState);global stateを使うコンポーネントを作ります。
const Counter = () => { const [count, setCount] = useGlobalState('count'); return ( <div> <span>Counter: {count}</span> <button onClick={() => setCount(v => v + 1)}>+1</button> <button onClick={() => setCount(count - 1)}>-1</button> </div> ); };
useGlobalState
の引数にglobal stateのproperty nameを指定しているところがポイントです。これを指定することで他のpropertyが変更にあった場合でも本コンポーネントは再renderする必要がなくなります。最後にAppでProviderを指定します。
const App = () => ( <GlobalStateProvider> <Counter /> <Counter /> </GlobalStateProvider> );デモ
CodeSandboxで動作させることができます。
特徴
React ContextとReact Hooksを使ってglobal stateを実現するライブラリは世の中にたくさんあります。本ライブラリはそのうちの一つですが、いくつかの特徴があります。
select by property name
global stateを設計する上でのポイントの一つは、大きなstateのうち、一部分を使う場合にそれを限定して使うための仕組みです。例えば、Reduxではselectorというインタフェースを使って、stateから派生したデータを作ります。本ライブラリはよりシンプルな手法として、stateオブジェクトのproperty nameでselectするという手法を採用しています。これは、selectorのようにオブジェクトの深い構造をselectすることはできませんが、reselectのようなselectorをmemoizeする必要がないというメリットがあります。その代わりstateオブジェクトはflatになるような設計にする必要はあります。同様のアプローチを採用しているライブラリにstoreonがあります。
unstable_observedBits
本ライブラリは上記selectの実現にobservedBitsという仕組みを使っています。observedBitsについてはこちらの記事が詳しいです。この仕組みを使うと、contextが変更した際に、一部のコンポーネントだけを再renderすることができます。
おわりに
本ライブラリは、名前が直接的なためか比較的参照されることが多いようですが、実は今後の方針は悩んでいます。一つは、今回紹介しなかったreducerインタフェースがReduxの完全な互換にはできないことと、もう一つは、observedBitsが将来のReactでは使えなくなる可能性が高いことがあります。ユーザの利用シーンをヒアリングしつつ今後の方針を決めていきたいと思います。
- 投稿日:2019-12-01T18:09:22+09:00
デザインからReact Native, React のコードを生成するツール「BuilderX」を使ってみた
この記事は React Native Advent Calendar 2019の4日目の記事です。
はじめに
こんにちは、React Native でアプリ開発をしている@ariiyu です。
最近、React Native 向けのデザインツールに BuilderX というものがあると知りました。
UIデザインの作業をするだけでReact コンポーネントのコードも出来上がるツールということで、上手く使うことができればフロントエンドのデザインや実装が捗りそうです。
BuilderX についての情報はまだあまり見当たらないので、今回調べて少し使ってみました。BuilderX とは
BuilderX は、React Native や React のコードを生成するブラウザベースのデザインツールです。
デザイナーと開発者が同じソースファイルを触ることを目指したものであり、デザインをするのと同時に、美しく、可読性が高く、編集可能なコードを生み出すものであると謳っています。
価格設定はFree, Personal, Team の三段階で、30日間は無料で試用することができます。
将来的にFlutter にも対応するようです。誰が開発・提供しているのか
BuilderX はGeekyAnts というインド、バンガロールのプロダクト・サービス開発会社が提供しています。
GeekyAnts はReact Native 向けUI コンポーネントライブラリであるNative Base や、モバイルアプリ構築フレームワークであるVue Native にも取り組んでいます。特徴
BuilderX はGeekyAnts のサイトで「Sketch のようなアプリ」とも書かれていますが、以下のような特徴があります。
- 美しく、可読性が高く、プロダクションレベルのコードを生成する (Beautiful, Readable & Production-ready Code)
- プロジェクトをリンクで共有できる
- Sketch のファイルをReact Native とReact 向けに変換できる
- Flex レイアウトに対応
- コンポーネント単位でのエクスポートができる
- 豊富なコンポーネントのライブラリを持つ
- オープンソースのアイコンが利用できる
BuilderXの最大の特徴は、GUI でUI デザインの作業を行うのと同時にReact Native および React のコードを生成してくれるところです。
また、BuilderX はWeb ブラウザで利用することができ、デスクトップアプリのインストールが不要です(元々はmacOS アプリとして提供していたようです) 。
プロジェクトをリンクで共有できる機能もあります。共有について、今の所はFigma やInVision ほど高機能ではないようです。BuilderX を使ってみた
BuilderX は公式サイトのOpen BuilderX から簡単に試すことができます。
操作感
他のUIデザインツールと同様に、要素をドラッグアンドドロップしたり、インスペクタで値を調整したりしてデザインを作成することができます。
画面右上の
CODE
とDESIGN
とを変更することによって、コードの表示とデザインのインスペクタを切り替えます。ここで
Download Component
を押すと、コードをファイル形式でダウンロードすることができます。コードの取得は、画面単位と個々のコンポーネント単位でそれぞれ可能です。例えば、あるボタンコンポーネントのコードだけ取得するということが出来ます。カードやボタンなど独自のコンポーネントが用意されていることや、オープンソースのアイコンをGUI で選んでデザインが作成出来るのは、プロトタイプを作るのに便利そうだなと思いました。
ネイティブ開発のSwiftUI のようにエディタ上でコードを編集することはできませんが、大きな問題ではないでしょう。React Native の場合は実際のアプリに組み込んだ後にコードを直せばよいです (開発時のリロードが速いので) 。
気になった点としては、コンポーネント同士の階層構造の変更など、GUI の操作がやや分かりづらい印象を受けました。もしかしたら、様々なデザインツールを使ってきた方なら難なく使えるかもしれません。
また、不具合と思われるような挙動もありました。デザインのプレビューとコードの内容が合っていないなど...今回は自分でUIデザインを作成するのではなく、Sketch ファイルをインポートすることにしました。
Sketch ファイルを変換してBuilderX で扱えるようにする
今回は既に用意されているデザインを利用してみました。macOS のSketch アプリのメニューから
New from Template
->Prototyping Tutorial
を選んで、Sketch のファイルを作成しました。そして、作成したSketch ファイルをBuilderX にインポートしました。
ここでBuilderX の
CODE
を見ると、React Native のコードが表示されました!プレビューに表示されている画面の内容が、全てコードとして出力されています。アイコンなど、Sketch の内容がそのままBuilderX 上に反映されない箇所がありました。フォントについてはBuilderX 上の警告に従って設定をすれば適用できそうです。
生成されるコード
この画面のコードは700行ちょっとになりました。スタイル部分のコードが500行くらい。
結構、それっぽいコードが生成されるという印象を受けました。コードはGistに上げておきました。
Gist: Sketch Prototyping Tutorial to BuilderXそしてこのコードを自分で新しく作成したReact Native のプロジェクトに組み込んでみたところ、実際のアプリとして起動することができました!
それっぽいのですが、BuilderX 上のプレビューともまた違う見た目になっています。。
今回のコードはそのままで使うには無理がありました。少なくとも以下については自分でコードを書き直すことになりそうです。
(もしかしたらデザイン時の設定によって解決できるのかもしれませんが、未検証です)
- コンポーネント同士の階層構造: ヘッダがメインコンテンツの層に入ってしまっている
- 制約によるレイアウト:
width
などに固定値が指定されており、多解像度対応が考慮されていない- アイコンが違うとかサムネイルのレイアウトが違うとか
コンポーネントの大まかな構造に関しては結構いい感じにコードに出力してくれていて、UI実装の3割くらいの作業はやってくれたという感覚でした。コード量が多くなる画面ほどコード自動生成のメリットが大きくなるかもしれません。
もちろんリストなどの動的な処理が必要な箇所については後で自分でコードを書くことになりますが、静的なコンポーネントのコードをある程度書いてくれるという点で、BuilderX はいい仕事をしてくれそうな予感がしました。BuilderX を導入すべきか?
数時間触ってみての所感です。
BuilderX は意図通りの挙動をしてくれない箇所がいくつもあって、いま普段使いするにはちょっと厳しいなという印象を受けました。
ただし、以下のような用途であれば試す価値があるかもしれません。
- サポートする端末の解像度を1種類に限定して、あくまでプロトタイピングのツールとして使う
- コーディング量が多くなりそうなコンポーネントの構造だけを作る (基本的にはエンジニアが実装をする前提で、大枠だけコードを自動生成する)
自分が使いこなせていないだけという可能性は十分ありますし、不具合や使いにくい箇所については今後改善されるはずなので、もうちょっとBuilderX を使ってみたいなと思いました。
まとめ
- BuilderX はReact Native やReact のコードを生成してくれるデザインツール
- そのままプロダクションで使えるコードではないが、それっぽいコードは出力してくれる
- UIデザイン・開発を楽にするツールとしてもう少し使ってみたい
さいごに
5日目は「おかんPAY」でPdM兼エンジニアをされている @knsg16 さんです!お願いします。
- 投稿日:2019-12-01T18:09:04+09:00
Node.js & Express & MySQL & React でTODOリスト API編
目的
ReactでTODOアプリを作成したことはありますが、自分でAPIを書いたことがなかったので書いてみました。
Web企業未経験なので、間違いなどありましたらご指摘いただけると助かります!APIを書くまえに
まずは簡単にNode.jsとReactの勉強をUdemyで行いました。
Linkは下記になります。【フロントエンドエンジニアのための React ・ Redux アプリケーション開発入門】
https://www.udemy.com/course/react-application-development/【Node.js速習講座 Part1 <導入&基礎編>】
https://www.udemy.com/course/nodejs-part1/【Node.js速習講座 Part1 】
https://www.udemy.com/course/nodejs-part2-express/データベースとテーブルの準備
まずはじめにMySQLで必要なテーブルを準備します。
$ sudo service mysqld status $ mysql -u root mysql> create database todo_api; mysql> use todo_api; mysql> create table todo_list(id int auto_increment, name varchar(255), isDone boolean, index(id)); mysql> insert into todo_list(name, isDone) values('todo sample', true); mysql> exitname : やることを保存
isDone : todoが完了しているか(true / false)これでテーブルの準備は完了です。
AUTO_INCREMENTを使用する際はindexを作成する必要がある点にご注意ください。Node.jsで実際に処理を書いていく
ここからがいよいよAPIを書いていきます。
まずは、必要なnpmパッケージをインストールしましょう。
# 作成したディレクトリ内で $ npm init $ npm install express --save $ npm install body-parser --save $ npm install mysql --savebody-parserを使用することで、データの取得が容易になります。
後ほど使用します。まずは、定型文的な処理を書いていきます。
index.jsconst express = require("express") const app = express() const bodyParser = require("body-parser"); const mysql = require("mysql") app.use(bodyParser.urlencoded({ extended: true })); # *******は、ご自身のパスワード const connection = mysql.createConnection({ host: 'localhost', user: 'root', password: '*******', database: 'todo_api' }) connection.connect(); const port = process.removeListener.PORT || 3000; app.listen(port)ここからMethotごとの処理を記述していきます。
まずはgetからindex.jsapp.get('/api/v1', (req, res) => { connection.query('select * from todo_list', (error, results, fields) => { if (error) { console.log(error) } res.send(results) }) }) app.get('/api/v1/:id', (req, res) => { connection.query("select * from todo_list where id = ?", req.params.id, (error, result, fields) => { if (error) { console.log(error) } res.send(result) }) })select * from todo_list where id = ?", req.params.id はSQLインジェクション対策です。
select * from todo_list where id = ${req.params.id}" は危険です。
他は簡単ですね。続いてPOST
index.jsapp.post('/api/v1', (req, res) => { let todo = { name: req.body.name, isDone : req.body.isDone } connection.query("insert into todo_list(name, isDone) values(?, ?)", [todo.name, todo.isDone], (error, results, fields) => { if (error) { console.log(error) } res.send(todo) }) })body-parserを使用することによって、req.body.nameのように書くことでデータを取得することができます。
ただ、APIとして使用する際にtodoというオブジェクトが必要なのかどうかは自分はまだよくわかっていないので、
もしかしたら消すかもしれません(笑)続いてPUT
index.jsapp.put('/api/v1/:id', (req, res) => { connection.query(`select * from todo_list`, (error, results, fields) => { connection.query("update todo_list set name = ?, isDone = ? where id = ?", [req.body.name, req.body.isDone, req.params.id], (error, result, fields) => { if (error) { console.log(error) } res.send(result) }) }) })update todo_list set name = ?, isDone = ? where id = ?,[req.body.name, req.body.isDone, req.params.id]は、
? は複数個になった場合は引数に配列で値を配置します。
配置する値がフィールドの場合は ?? とします。書き方はとんでもなく汚いような気がします。。。。。
何か他の方法があれば教えていただきたいです(切実)
やっていることは、POSTの処理のSQL文をUPDATEにしただけです。
簡単ですね。最後にDELETE
index.jsapp.delete('/api/v1/:id', (req, res) => { connection.query(`select * from todo_list`, (error, results, fields) => { connection.query("delete from todo_list where id = ?", req.params.id, (error, result, fields) => { if (error) { console.log(error) } res.send(result) }) }) })特に解説する部分はありません。
Postmanでテスト
APIを作成する際はPostmanが便利です。
Link https://www.getpostman.com/試しにGETを送信すると、こんな感じでJSON形式でデータが返ってきます。
いい感じですね。
終わり
これでとりあえずAPIは終了です。
昨日Nodeの勉強を始めたばかりなので、書き方が間違えている部分はお許しください(笑)今後はAPIをHerokuにデプロイして、それを使用してReactでTODOリストの作成をします。
強強エンジニアの方、ぜひ間違えている部分を個人的にでもコメントにでも良いので教えていただきたいです。
- 投稿日:2019-12-01T17:53:38+09:00
Nextで特定のAPI リクエストをproxyする方法
書いてる事
Next.jsで一部のリクエストを別環境へproxyしたいケースは多いかと思います。
例えば、下記のようなケース
- フロントエンドをバックエンドと環境を分離している
- Backend向けAPIだけ裏側へproxyしたい
- バックエンド環境を自身の開発環境に用意するのが面倒(管理・運用面で)
- できれば、最新状態がキープされている共通環境側へproxyしたい
上記の場合の対応方法について書きました。
書いてない事
Node(JavaScript)以外で頑張る方法。
Nodeがあれば再現できる方法がフロントエンド開発者としては嬉しいと思うので。1. micro-proxyをNextと別で立ち上げる
あまりオススメしない方法。
zeitが出しているNodeライブラリーであるmicro-proxyを使う案。別プロセスでproxy立ち上げ、一部のリクエストをproxy向けにリクエストを飛ばしてproxyさせる手法。ただし、この方法は問題点が何個かあって
- そもそもarchiveされているライブラリー
- micro-proxyはNextと別プロセスで立ち上げるので別ポートでListenする必要があり、Nextとポートを分けないといけない
- リクエストするクライアント側からも振り分けないといけなくなる
- proxyするリクエストとproxyしないリクエストでクロスドメインになるのでその対処が必要
- 例えば、Nextは
localhost:3000
、micro-proxyがlocalhost:9000
だと、micro-proxy向けのリクエストはクロスドメイン扱いとなり、。ブラウザ側で拒否される2. NextのCustome Server(Routes)上でhttp-proxy-middlewareを使う。
個人的なオススメ。
http-proxy-middlewareはNode製のproxy library。Nodeのweb framework、例えばexpressなどで簡単に利用できる。これをNextのCustom Server上で利用する。
以下は実際に設定したコード例です。NextのCustom Server例を参考にしました。server.jsconst express = require('express') const next = require('next') const port = parseInt(process.env.PORT, 10) || 3000 const dev = process.env.NODE_ENV !== 'production' const app = next({ dev }) const handle = app.getRequestHandler() app.prepare().then(() => { const server = express() app.use( '/api', proxy({ target: 'http://www.example.org', changeOrigin: true }) ); server.all('*', (req, res) => { return handle(req, res) }) server.listen(port, err => { if (err) throw err console.log(`> Ready on http://localhost:${port}`) }) })上記でCustom Serverを起動すると
/api
向けのリクエストはhttp://www.example.org
へproxyされ、他リクエストはNext.js内部で処理(SSRなど)する。同プロセス、同ポートへのリクエストとなり、クロスオリジンの問題は起きない。まとめ
2番目がオススメ。ただ、他にオススメがあれば教えてください。
- 投稿日:2019-12-01T17:16:17+09:00
Reduxから Context API with Hooks へ
React Advent Calendar 2019 の2日目を担当してみたGenkiです。
一昨日 11月29日に見たらまだ空いていたのでReduxについて少し話ができればと思い色々調べたりしてみました。
State Managementについて
必要性
Reactのみでコレまで書いていくとコンポーネントを超えてデータを渡したければ
Prop-drilling
と言われる、Propsをどんどん下層コンポーネントに渡していく必要がありました!
そのため必要な一つのデータが親からひ孫やひしゃ孫に渡していくリレーのように次の子孫にデータを渡す必要があり、無駄と思われるようなコードがどんどん増えてきてしまいまいした!Redux
Redux
などは別にreactのためだけに作られたstate-management library ではありませんでしたが、上で出ていた問題Prop-drilling
を解決する上でとても利便性の高い
Libraryとして多くの人に使われました。Reduxの光と闇
光
Redux
を入れたことによってProp-drilling
はなくなってStore
で管理されているGlobal-state
に対してどこからでもアクセスできるようになりました!
おそらくこの記事を呼んでいる方なら、connect
mapStateToProps
,mapDispatchToProps
を使ったことがあると思いますが、こういったfunctionやargumentを使うことで
自由にどこからでもglobal-state
へアクセス出来るようになりました。闇
使うためにはいくつか覚えなければいけなくて、それが複雑性を発生させていたり、管理を行うために
actions
,reducers
,store
を覚えていく必要がありました。
reduxの詳細説明は本家へ https://redux.js.org/introduction/getting-startedContextAPIの登場
ContextAPI
がver.16.xで実験的に登場して 16.3.0で productionで使えるレベルになり、本リリースされ大きくstate
管理におけるreactの方向性が変わってきました。
ContextAPIの説明でも言われているようにContext provides a way to pass data through the component tree without having to pass props down manually at every level.
Contextによってコンポーネントツリー間におけるデータの橋渡しを、すべての階層ごとにprops
として渡す必要性がなくなった。???? これってReduxいらないってことじゃん!
ということで、Reduxを使わなくても下の階層に対して
Prop-drilling
をしなくても下の階層でContext
に収容されているデータにアクセスできるようになりました!以下は ContextAPIで紹介されているSampleをかんたんな日本語意訳でお届けです!
https://reactjs.org/docs/context.htmlreact.js// ContextAPIを使って default値 light(明るい) でテーマカラーの設定をします const ThemeContext = React.createContext('light'); class App extends React.Component { render() { // Providerを使って テーマカラーを dark(暗い)にしてコンポーネントツリーに渡してしまいます return ( <ThemeContext.Provider value="dark"> <Toolbar /> </ThemeContext.Provider> ); } } // ここではProps Drillingを使う必要がなくなっている!!!! function Toolbar(props) { return ( <div> <ThemedButton /> </div> ); } class ThemedButton extends React.Component { // ここで親からPropsで渡されてもいないのにデータをアクセスできる! static contextType = ThemeContext; render() { return <Button theme={this.context} />; } }Hooksの登場で Hook into Context APIにしていく
Hooksが入ったことによって Class Componentじゃなくても State管理が出来るようになっただけでなく、既存のAPIをより強力にすることが出来るようになりました。
今から以下で使っていくHooksは2つで、全体的に Redux使わなくても ContextAPIとHooksでState managementが出来るよーってことに触れていきたいと思います。
useContext
とuseReducer
実際に使ってカウンターアプリを作ります!
今回作るものはボタンでプラスマイナスが出来ることと現在のカウントを表示するコンポーネントです!
以下がファイル構成-src |- App.jsx |- index.jsx |- Components |- Counter |- index.jsx |- Display |- index.jsx |- store |- index.jsxやること
- state管理の要、Storeの作成(useReducer & createContext)
- Providerをindexにおいてアプリ全体に反映させる
- Access用のComponentの作成(useContext)
- Update用のComponentの作成(useContext, dispatch)
- App.jsで定義して試してみる
1. state管理の要、Storeの作成(useReducer & createContext)
ここで行っているのは storeの定義と初期値の設定、プロバイダーを作る。
Providerが上階層で定義されていればこのファイルのstoreを呼び出せばどこからでも globalStateにアクセスできるようになります。src/store/index.jsx// 必要なAPIの呼び出し import React, { createContext, useReducer } from 'react'; const initialState = { count: 0 }; // ここで Globalに活躍してもらうための Storeの作成 const store = createContext(initialState); // Providerの定義 const { Provider } = store; const StateProvider = ({ children }) => { const [state, dispatch] = useReducer((state, action) => { switch (action.type) { case 'INCREMENT_COUNT': return { ...state, count: state.count + 1 } case 'DECREMENT_COUNT': return { ...state, count: state.count - 1 } default: throw new Error(); }; }, initialState); return <Provider value={{ state, dispatch }}>{children}</Provider>; }; export { store, StateProvider }2. Providerをindexにおいてアプリ全体に反映させる
ここで行っているのはProviderを index.jsで定義してあげてacctionやreducerにアクセスできるようにしている
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import { StateProvider } from './store'; const app = ( <StateProvider> <App /> </StateProvider> ); ReactDOM.render(app, document.getElementById('root'));3. Access用のComponentの作成(useContext)
現在のglobalなデータに対してアクセスしている
import React, { useContext } from 'react'; import { store } from '../../store'; const Display = () => { const { state } = useContext(store); return <div>The count is { state.count }!</div> };4. Update用のComponentの作成(useContext, dispatch)
dispatchしている
import React, { useContext } from 'react'; import { store } from '../../store'; const Counter = () => { const { state, dispatch} = useContext(store); return ( <div> <button onClick={() => dispatch({ type: "INCREMENT_COUNT" })}>Plus</button> <hr/> <button onClick={() => dispatch({ type: "DECREMENT_COUNT" })}>Minus</button> </div> ) }; export default Counter;5. App.jsで定義して試してみる
まぁ雑にはなるがとりあえずおいてみる
import React from 'react'; import Display from './Components/Display'; import Counter from './Components/Counter'; const App = () => { return ( <div> <div> <h1>Display</h1> <hr/> <AccessExampleComponent /> </div> <div> <h1>Counter</h1> <hr/> <Counter /> </div> </div> ); } export default App;完成品
できた!!!
感想
結局Reduxから抜けたからといってあの面倒な actions&reducers&storeから抜け出せたという感覚は否めなかった。
- 投稿日:2019-12-01T14:29:34+09:00
Docker/Rails/ReactをつかってHelloWorld!
初めまして。
プログラミングを始めてからあと四ヶ月で一年が経とうとしている。
本当に時間が立つのは早い...
今回は、RailsとReactを使って、HelloWorldをしてみる。
Railsを主にバックエンド、Reactをフロント、データベースはPostgresSQLを使用する。
1.Dockerで環境構築
2.RailsとReactの導入
- RailsTutorialを完走し、簡易的なアプリ開発経験があるレベル。
- ReactTutorial
- Dockerインストール
追記(注意事項)
記事を書き終わった後に、RailsへのReact導入の方法がこれがベストではない感じがしてきました。
https://qiita.com/ry_2718/items/9b824a3f9ca750ce403e
rails new . --skip-coffee --skip-turbolinks --skip-sprockets --webpack=vue
rails 5.1からはこれでAssetpiplineの代わりにwebpackを導入することができるみたい。
情報収拾の仕方を改めて考えさせられました。最近になってからは公式リファレンスや、検索機能に1ヶ月以内などを指定して、英語の記事でもGoogle翻訳を駆使しながら頑張って読んでいる...。Dockerで環境構築をする
Dockerとは?なぜDockerか。 (読まなくていい)(間違ってたらすいません)
そもそもOSとは
http://www.toha-search.com/it/os.htm
OSとはOperation System(オペレーティング・システム)の略で、アプリやデバイスを動作させるための基本となるソフトウェアのことです。 具体的には、キーボードやマウス・タッチパッドなどのデバイスから入力した情報をアプリケーションに伝え、またソフトウェアとハードウェアの連携を司る中枢的な役割を果たします。
つまり、OSはハードウェアや入力デバイス出力デバイス、アプリケーションなどを容易に操作するためのもの。
それで、このOSの上でどんな感じで仮想環境を作るかで違いがでる。
https://udemy.benesse.co.jp/development/web/docker.html
ハードウェアを仮想化し、複数のサーバを構築できる仕組みは変わりません。ただ、コンテナは1つのOSを共有して利用しているのに対し、仮想マシンはサーバごとにOSをインストールし動かしていきます。
つまり、
仮想マシンはホストOSの上でもう一つのOS(ゲストOS)を起動すること。(VirtualBoxとか)
virtual boxとかを使ったことがある人はわかると思うが、仮想化させたいOSイメージを指定した後、設定で仮想化したOSが使用するハードディスクやメモリの分割を行う。<-結果的にゲストOSとホストOSが同時にメモリを占有するので処理が重たいコンテナは仮想化をホストOSの上で行う(Dockerとか)
コンテナでは、ホストOSの上で直接仮想化する(ゲストOSを建てない)ので非常に動作が軽い。Docker上であれば基本的に環境の差異による影響を受けない
また、DockerにはDockerHubというのがあり、そこからすでに環境が構築されたテンプレートや、MySQLやRubyなどのツールや言語をDocker上にイメージとしてインストールしてくれる。Dockerの基本コマンド
とりあえず目を通して、どんな動作を行うコマンドがあるかみてください。
初心者用Docker基本コマンド一覧(新旧スタイル対応)
DockerComposeの基本Dockefileとdocker-compose.ymlの設定
まずはDockerで環境構築
$ mkdir myblog $ cd myblog $ touch {Dockerfile,docker-compose.yml}Dockerfileはこの記事が非常にわかりやすいです。
Docker初心者がRails + PostgreSQL or MySQLで仮想環境構築した手順を丁寧にまとめる
Dockerfile 解説 FROM dockerhubからイメージをダウンロード WORKDIR 作業ディレクトリの指定 RUN コマンドの実行 COPY 引数1を引数2にコピー yarnインストール参考docker for macでrails × yarn × webpackerのfront環境を整える
myblog/DockerfileFROM ruby:2.5.5 RUN apt-get update && apt-get install -y build-essential nodejs libpq-dev #yarnインストール webpackで必要になります。 RUN curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn RUN mkdir /rails WORKDIR /rails COPY Gemfile /rails/Gemfile COPY Gemfile.lock /rails/Gemfile.lock RUN bundle install COPY . /railsdocker-composeはこの記事が非常にわかりやすいです
docker-compose.ymlの書き方について解説してみた
docker-compose 解説 version docker-composeの文法はバージョンごとにことなるので指定が必要 servise 動かすアプリケーションの指定。ここでは、webとdb。 他 Service設定する際の項目について docker-compose.ymlversion: '3' services: web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/rails ports: - "3000:3000" #ポート3000番を開けてコンテナの3000番に転送 depends_on: - db db: image: postgres volumes: - datavol:/var/lib/postgresql/data volumes: datavol:Railsアプリを作る。
以下のコマンドを入力
$ touch {Gemfile,Gemfile.lock} $ echo "source 'https://rubygems.org' gem 'rails','5.1.4' gem 'pg', '~> 0.20.0'" > Gemfile $ docker-compose run web bundle exec rails new . --force --database=postgresql $ docker-compose buildここまででRailsサーバーを立ち上げる準備が整っているはずなので立ち上げてみる。
$ docker-compose up -d //サーバー起動 $ docker-compose run web rake db:create //db作成ここにアクセス
みなさんは成功したでしょうか??....Reactを導入
とりあえずRailsの初期画面から変更を行う。
コントローラーを作ろう
$ rails g controller StaticPages home about contactルートの設定
routes.rbRails.application.routes.draw do root 'static_pages#home' get '/about', to: 'static_pages#about' get '/contact', to: 'static_pages#contact' endこれでReactでviewに変更を加える準備ができました。
gem追加
$ echo "gem 'webpacker' gem 'react-rails'">>Gemfile $ docker-compose run web bundle updatewebpack設定
$ docker-compose run web rails webpacker:install $ docker-compose run web rails webpacker:install:reactここまでくると、app/assets/javascriptというファイルが作成される。
この中のファイルがreactファイルになっている。試しにrailsのviewに呼び出したいころではあるが、railsサーバーを再起動しないと反映されないのでrailsコンテナを再起動。
$ docker ps //稼働中のコンテナの表示 0739cbd77243 170064292a20 "bundle exec rails s…" 12 hours ago Up 12 hours 0.0.0.0:3000->3000/tcp myblog_web_1 0d302bae2084 postgres "docker-entrypoint.s…" 13 hours ago Up 13 hours 5432/tcp myblog_db_1上の場合だと
0739cbd77243
これがrailsコンテナのIDになるので、このIDを指定してコンテナの再起動をする$ docker restart 0739cbd77243 //コンテナ起動参考
Rails で postgresql を使う(インストールからマイグレーションまで)
Docker初心者がRails + PostgreSQL or MySQLで仮想環境構築した手順を丁寧にまとめる
既存のRailsアプリにReactを導入する方法
- 投稿日:2019-12-01T12:11:50+09:00
JSの文法すら怪しいフロント初心者がblog用のgatsby starter(っぽいもの)を自作・公開してみた
最近ホットだけど日本での知名度が若干低いプロジェクトにgatsbyがあります。
ご存知でしょうか?
2019年のOctoverseを見ると、Fastest growing open source projects by contributorsの第8位にgatsbyjs/gatsbyがあります。
今回はそんなgatsbyのstarter(wordpressのテーマみたいなの)を自作していきます。今回の目的
デザインに専念できる環境を作成していきましょう。
私自身のデザイナーとしての能力が低くて現時点でいいデザインを作ることは難しいですが、だれでもカスタマイズできる状態で公開するところまでこぎつけます。デザインに集中できるだけの基本的な後ろ側を作れることをゴールとします。
コンセプトとデザインを仮決める
こんな感じのブログにしましょう。
starterの名前とコンセプトはhinoiri(日の入)。行間がおかしいとか、なんかずれてるとか突っ込みどころ満載の設計図ですが、テーマ作成の際に直します。
色一覧
背景色1:ネイビー。#000030。夜の帳をイメージ。
背景色2:群青。#023457。暮れかけの夜をイメージ。
テキスト:薄紫。#B0D0FF。ただの白ではイメージに合わないので青みを強くした。
強調色1:オレンジ。#F37F00。太陽
強調色2:太陽の周りの赤。#D33422。正直納得いってないので後で変えます。
基礎となるgatsby starterを選ぶ
今回は一から作るのではなく、
gatsby-starter-default
というgatsby-starterをもとに製作していきます。
gatsby-starter-default
はgatsby-starterの中でもっともシンプルなものです。プラグイン、テーマを追加
- gatsby-theme-blog-core
- gatsby-plugin-sitemap
- gatsby-plugin-google-fonts
- gatsby-plugin-sass
gatsby-theme-blog-core
今回はblogを作る予定なので、gatsby-theme-blog-coreを採用。
また、gatsby-theme-blogから重要そうなコードを移植します。
gatsby-theme-blogはstyleに影響を与えてしまうためそのままの採用はしません。
src/components/
およびsrc/gatsby-theme-blog-core/components
内に必要なコードを移植します。結構面倒くさいですが、ソースコード読んで良しなにお願いします。
その他のプラグイン
- 素のCSSではきつくなる(予定)のでSCSSを採用。
- フォントをきれいにしたいいためgatsby-google-fontsを採用(ただし重くなる)
- gatsby-plugin-sitemapはとりあえず採用
実運用するといろいろ不足があると思いますが、いったんはこれで。
scssを適用
src/ assets/ scss/ components/ gatsby-theme-blog-core/今回は上記ディレクトリ構成で。
どこからでも使うscssは、src/assets/scss/内に配置し、init.scssで一括して呼び出せるように変更。
各コンポーネントごとのscssはmoduleとして各jsファイルと一緒に配置します。サンプルページ作成
様々なHTMLタグが出てくるmdファイルを作成し、実際に試してみます。
まあ、こんなものかな?
公開
githubにpushしてリポジトリを公開状態にすれば終わりです。
実際に使えるか試してみましょう。
gatsby new project-name https://github.com/aimof/gatsby-starter-hinoiridemoページ作るほどの価値がこのstarterにまだない気がしたので、デモはなし。
しっかりしたstarterを作れれば公式で紹介してもらえる可能性もあるようです。まとめ
gatsby触ってみましたが、フロント面白いです。
デザインについては並行して学習中です。なぜかgatsbyの知名度が日本では若干低い気がするので盛り上がっていくとよいですね
- 投稿日:2019-12-01T12:05:17+09:00
React Nativeでライブラリを使わずに枠線だけの吹き出しを作る
Fringe81アドベントカレンダー1日目の記事です。
弊社の提供しているUniposというサービスではWeb版のほか、Android/iOS向けにReact Nativeを使ってアプリを開発しています。
今回はそのアプリの新機能開発において、「枠線だけの吹き出し」を作る必要が出たためやってみた、という軽めの記事でございます。「枠線だけの吹き出し」というのはこんなやつです。
react-native-svgなどを使って作る方法もありそうですが、今回は依存ライブラリなしでReact Nativeの標準コンポーネントだけを使って作ってみました。
環境
- React Native 0.59.9
- TypeScript 3.7.2
今回使用した環境は上記ですが、おそらく他の環境でも問題なく使用できるはずです。
結論
仕組みはどうあれ、先に出来上がったコードを貼ります。
import * as React from 'react'; import {View, Text, StyleSheet} from 'react-native'; const BALLOON_TRIANGLE_HEIGHT = 14; export const CommentBalloon = ({text}: {text: string}) => { return ( <View style={{justifyContent: 'flex-end', paddingTop: BALLOON_TRIANGLE_HEIGHT}}> <View style={styles.balloonContainer}> <View style={[styles.balloonTriangleBase, styles.balloonTriangleOuter]} /> <View style={[styles.balloonTriangleBase, styles.balloonTriangleInner]} /> <View style={styles.textContainer}> <Text style={styles.text}>{text}</Text> </View> </View> </View> ); }; const styles = StyleSheet.create({ balloonContainer: { width: '100%', backgroundColor: '#FFFFFF', borderWidth: 1, borderColor: '#E1E6E6', borderRadius: 2 }, balloonTriangleBase: { width: 0, height: 0, position: 'absolute', bottom: '100%', borderTopColor: 'transparent', borderLeftColor: 'transparent', borderRightColor: 'transparent' }, balloonTriangleOuter: { left: 32, borderWidth: BALLOON_TRIANGLE_HEIGHT, borderBottomColor: '#E1E6E6' }, balloonTriangleInner: { left: 33, borderWidth: BALLOON_TRIANGLE_HEIGHT - 1, // border分だけ引く borderBottomColor: '#FFFFFF' }, textContainer: { padding: 15 }, text: { fontWeight: '300', fontSize: 14, lineHeight: 20, color: '#4A4A4A', maxWidth: '100%', letterSpacing: 0 } });仕組み
触ったことがある方はすでにご存知の通り、React NativeではCSSと似た形式のプロパティを持ったオブジェクトを用いて画面のスタイルを定義します。
そのため、CSSでできることは大抵できるのですが、いくつかできないことがあります。その中の一つに、:before
や:after
といった擬似要素が挙げられます。CSSを使って吹き出しを作る方法として有名なのは、
:before
や:after
擬似要素を用いて、大きさのない要素に対してborder
を当てることによって、吹き出しの三角形の部分(余談ですがくちばしって呼ぶらしいですね)を作る方法があります。【コピペ改変OK】CSSで作れる吹き出しデザイン8選 | creive【クリーブ】
Webでの方法はこちらの記事がとても分かり易かったです。
React Nativeでは擬似要素が使用できないため、代わりに中身のない
View
コンポーネントを配置して、そのコンポーネントに対してスタイルを当てています。また、枠線のみの吹き出しにするため、大きさの違うくちばしを2つ用意し、外側の線部分と、内側の塗りの部分をずらして重ねています。(
balloonTriangleOuter
とballoonTriangleInner
がそれ)
また、くちばしの部分は絶対位置指定(
position: 'absolute'
)で配置しているため、このままだと上に配置されたコンポーネントがくちばしの上に重なってしまうのを防止するため、<View style={{justifyContent: 'flex-end', paddingTop: BALLOON_TRIANGLE_HEIGHT}}>
というコンポーネントでラップすることでCommentBalloonコンポーネント全体で見たときにくちばしを含んだ高さとなるように調整しています。所感
React NativeのstyleがWebのCSSと同じ挙動になるように実装されているおかげもあり、こうやってWebでよく用いられるテクニックを転用して、さほど悩むことなく画面を作れるのは幸せなことだと改めて思いました。
- 投稿日:2019-12-01T03:24:56+09:00
GraphCMSからAbsintheをりようして作るElixirで体験的にGraphQLSeverを作る「ポエム」
まえがき
この記事は、「fukuoka.ex Elixir/Phoenix Advent Calendar 2019」の1日目になります。
東京だけどfukuoka.exのYOSUKENAKAO.meです。普段は合同会社The Waggleで「教育」に関わるサービス作りのお仕事と学習教材の開発や
研修講座の企画開発をしています。この記事の構成
このカレンダーでは、以下3つのAdovent Calendarに投稿する予定の3部構成の第1弾となります。
そして、Advent Calender で扱うテーマは「GraphQLとElixirで始めるプロトタイプ開発の未来」
という名のポエムです。3部構成の大まかな予定は以下です。
Adovent Calendar fukuoka.ex 1日目
GraphCMSからAbsintheをりようして作るElixirで体験的にGraphQLSeverを作る「ポエム」
Adovent Calendar NervesJP 6日目
https://qiita.com/advent-calendar/2019/nervesjp
NervesとGraphQLseverの組み合わせを考える「ポエム」Adovent Calendar Elixir 24日目
https://qiita.com/advent-calendar/2019/elixir
GraphCMSから入り、Absintheを利用して作って動かす「チュートリアル」GraphCMSからAbsintheをりようして作るElixirで体験的にGraphQLSeverを作る「ポエム」
テーマ:GraphQLとElixirが普及したら開発者は人間力で選ばれる
開発の生産性があがり、その手法がコモディティ化すると、より人間力が重要になる未来
それを感じるくらい、簡単に作れる時代が来たなぁ。という感想を持っている今日この頃の俺だぁ。という事で、GraphCMSのサンプルをダウンロードし、GraphCMSでスキーマの設計をしてGraphCMSへの接続をする。
その後、ElixirでPhoenixframeworkを利用して、Absintheを導入してgraphql severを立ち上げ先に繋いでいたReactのエンドポイントを変更するだけで移行が簡単に済む。
という体験をおすそ分けするチュートリアルを12月24日に向けて書いています。
なぜ、今回はポエムなの?
はい、それはですね。ボリュームが多いので3部作で作る予定でいてまずは全体の流れを掴んでもらう部分を
第1回目で予定しているのですが、流れを書く部分で終わるので今回はポエムとして眺めてください。1.GraphCMS サンプルをダウンロード
種類はたくさんありますが、今回はReactを選択します。
https://github.com/GraphCMS/graphcms-examples/tree/master/current/react-apollo-blog2. How to Start
Hwo to Startにあるコマンドをコマンドプロンプトやターミナルに貼り付ける
git clone https://github.com/GraphCMS/graphcms-examples.git && cd graphcms-examples/current/react-apollo-blog && yarn && yarn start3. GraphCMSのアカウントを登録してプロジェクトを作る
https://graphcms.com/
1. サインアップする
2. ログインして「Create new project」でプロジェクトを作る
3. Choose a way to create your new projectで「From Scratch」を選択する
4. Set Project Detailsの「Name」に任意の名前(今回はSample blog)と入力
5. Select a region で「Asia East」を選んでCreateを押す
6. Planを選択する画面が来るので、今回は無料を選ぶ4. Modelを作りエンドポイントを変更する
- gitからダウンロードしたプロジェクトフォルダの中のsrcフォルダのAbout.jsの中に書かれているクエリを見る
- GraphCMSでAbout.jsに書かれているクエリに書かれているクエリの内容に合わせてスキーマを作る。
- Author name bibliography avatar で作成
- Post slug title dataAndTime coverImage で作成
- SettingsのEndpointをindex.jsのGRAPHCMS_APIに代入します。
- Public API PermissionsのScoopeをPROTECTEDからQUERYに変更します。
- localhost:3000で確認する
5. PhoenixfreamworkでGraphQLをセットする方法
1. sampleBlogという名前のプロジェクトを生成する
mix phx.new sampleBlog --no-html --no-webpack mix ecto.create2. Ectoを利用して、新しいPhoenixアプリケーションのPostgreSQLに接続する為のコンテキストを生成します。
GraphCMSで作成したモデルと同じものを作成しますが、今回は画像を追加は省きたいと思いますのでavatarは無くします。cd sampleBlog mix phx.gen.context Blog Author authors name:string bibliography:string3. authorsのデータベーステーブルを作成します。
mix ecto.migrate4. クエリを実行する為にデータベースに幾つかのデータが必要なのでデータベースに読み込むデータを準備します。
priv/repo/seeds.exs
に以下を書き込みます。alias SampleBlog.Repo alias SampleBlog.Blog.Author %Author{ name: "Yosuke Nakao", bibliography: "The Waggle", } |> Repo.insert! %Author{ name: "Nakao Yosuke", bibliography: "Fukuoka.ex", } |> Repo.insert!5. シードデータをデータベースにロードします。
mix run priv/repo/seeds.exs6. Absintheを使用してGraphQLのアプリをセットアップします。
defp deps do [ # existing dependencies {:absinthe, "~> 1.4.16"}, {:absinthe_plug, "~> 1.4.0"}, {:absinthe_phoenix, "~> 1.4.0"} ] endmix deps.get7.sampleBlog_webにschemaフォルダを作成し、schema.exファイルを作成して、ファイルを作成します。
defmodule SampleBlogWeb.Schema.Schema do use Absinthe.Schema query do @desc "Get a list of authors" field :authors, list_of(:authors) do resolve &SampleBlogWeb.Resolvers.Blog.authors/3 end @desc "Get a author by its id" field :author, :author do arg :id, non_null(:id) resolve &SampleBlogWeb.Resolvers.Blog.author/3 end end object :author do field :id, non_null(:id) field :name, non_null(:string) field :bibliography, non_null(:string) end end8.リゾルバモジュールファイルを作成します。 sampleBlog_webにresolversフォルダを作成し、blog.exファイルを作成して、ファイルを作成します。
defmodule SampleBlog.Resolvers.Blog do alias Getaways.Blog def authors(_, _, _) do {:ok, Blog.list_authors()} end def author(_, %{id: id}, _) do {:ok, Blog.get_author!(id)} end end9.スキーマとリゾルバの準備をしたら、ルーターを設定します。
sampleBlog\lib\sampleBlog_web\router.exdefmodule SampleBlogWeb.Router do defmodule SampleBlogWeb.Router do use SampleBlogWeb, :router pipeline :api do plug :accepts, ["json"] end scope "/", SampleBlogWeb do pipe_through :api end forward "/api", Absinthe.Plug, schema: SampleBlogWeb.Schema.Schema forward "/graphiql", Absinthe.Plug.GraphiQL, schema: SampleBlogWeb.Schema.Schema, interface: :simple end最後にサーバーを起動してみます。
mix phx.serverこれで
localhost:4000/graphiql
にアクセスして以下の画面が出てきたら作成準備完了です。次回は、これをGraphCMSのサンプルで作ったReactのフロントへ接続するものをポエムとして書きます。
手順をチュートリアル的に書く予定は24日の記事に上げるので本日はこの辺で。
- 投稿日:2019-12-01T03:24:56+09:00
GraphCMSからAbsintheを利用して作るElixirで体験的にGraphQLSeverを作る「ポエム」
まえがき
この記事は、「fukuoka.ex Elixir/Phoenix Advent Calendar 2019」の1日目になります。
東京だけど fukuoka.ex の YOSUKENAKAO.me です。普段は合同会社The Waggleで「教育」に関わるサービス作りのお仕事と学習教材の開発や
研修講座の企画開発をしています。この記事の構成
このカレンダーでは、以下3つの Advent Calendar に投稿する予定の3部構成の第1弾となります。
そして、Advent Calender で扱うテーマは「GraphQL と Elixir で始めるプロトタイプ開発の未来」
という名のポエムです。3部構成の大まかな予定は以下です。
Advent Calendar fukuoka.ex 1日目
GraphCMS から Absinthe を利用して作る Elixir で体験的に GraphQLSever を作る「ポエム」
Advent Calendar NervesJP 6日目
https://qiita.com/advent-calendar/2019/nervesjp
Nerves と GraphQLsever の組み合わせを考える「ポエム」Advent Calendar Elixir 24日目
https://qiita.com/advent-calendar/2019/elixir
GraphCMS から入り、Absintheを利用して作って動かす「チュートリアル」GraphCMS から Absinthe を利用して作る Elixir で体験的に GraphQLSever を作る「ポエム」
テーマ:GraphQL と Elixir が普及したら開発者は人間力で選ばれる
開発の生産性があがり、その手法がコモディティ化すると、より人間力が重要になる未来
それを感じるくらい、簡単に作れる時代が来たなぁ。という感想を持っている今日この頃の俺だぁ。という事で、GraphCMS のサンプルをダウンロードし、GraphCMS でスキーマの設計をして GraphCMS への接続をする。
その後、Elixir で Phoenix Frameworkを利用して、Absinthe を導入して GraphQLSever を立ち上げ先に繋いでいた React のエンドポイントを変更するだけで移行が簡単に済む。
という体験をおすそ分けするチュートリアルを12月24日に向けて書いています。
なぜ、今回はポエムなの?
はい、それはですね。ボリュームが多いので3部作で作る予定でいてまずは全体の流れを掴んでもらう部分を
第1回目で予定しているのですが、流れを書く部分で終わるので今回はポエムとして眺めてください。1.GraphCMS サンプルをダウンロード
種類はたくさんありますが、今回は React を選択します。
https://github.com/GraphCMS/graphcms-examples/tree/master/current/react-apollo-blog2. How to Start
Hwo to Start にあるコマンドをコマンドプロンプトやターミナルに貼り付ける
git clone https://github.com/GraphCMS/graphcms-examples.git && cd graphcms-examples/current/react-apollo-blog && yarn && yarn start3. GraphCMS のアカウントを登録してプロジェクトを作る
https://graphcms.com/
1. サインアップする
2. ログインして「Create new project」でプロジェクトを作る
3. Choose a way to create your new project で「From Scratch」を選択する
4. Set Project Details の「Name」に任意の名前(今回は Sample blog)と入力
5. Select a region で「Asia East」を選んで Create を押す
6. Plan を選択する画面が来るので、今回は無料を選ぶ4. Model を作りエンドポイントを変更する
- git からダウンロードしたプロジェクトフォルダの中の src フォルダの About.js の中に書かれているクエリを見る
- GraphCMS で About.js に書かれているクエリに書かれているクエリの内容に合わせてスキーマを作る。
- Author name bibliography avatar で作成
- Post slug title dataAndTime coverImage で作成
- Settings の Endpoint を index.js の GRAPHCMS_API に代入します。
- Public API Permissions の Scoope を PROTECTED から QUERY に変更します。
- localhost:3000 で確認する
5. Phoenix Framework で GraphQL をセットする方法
1. sampleBlog という名前のプロジェクトを生成する
mix phx.new sampleBlog --no-html --no-webpack mix ecto.create2. Ecto を利用して、新しい Phoenix アプリケーションからデータベース(PostgreSQL)に接続する為のコンテキストを生成します。
GraphCMS で作成したモデルと同じものを作成しますが、今回は画像を追加は省きたいと思いますので avatar は無くします。cd sampleBlog mix phx.gen.context Blog Author authors name:string bibliography:string3. authors のデータベーステーブルを作成します。
mix ecto.migrate4. クエリを実行する為にデータベースに幾つかのデータが必要なのでデータベースに読み込むデータを準備します。
priv/repo/seeds.exs
に以下を書き込みます。priv/repo/seeds.exsalias SampleBlog.Repo alias SampleBlog.Blog.Author %Author{ name: "Yosuke Nakao", bibliography: "The Waggle", } |> Repo.insert! %Author{ name: "Nakao Yosuke", bibliography: "Fukuoka.ex", } |> Repo.insert!5. シードデータをデータベースにロードします。
mix run priv/repo/seeds.exs6. Absinthe を使用して GraphQL のアプリをセットアップします。
mix.exsdefp deps do [ # existing dependencies {:absinthe, "~> 1.4.16"}, {:absinthe_plug, "~> 1.4.0"}, {:absinthe_phoenix, "~> 1.4.0"} ] endmix deps.get7.sampleBlog_web に schema フォルダを作成し、schema.ex ファイルを作成して、ファイルを作成します。
lib/sampleBlog_web/schema/schema.exdefmodule SampleBlogWeb.Schema.Schema do use Absinthe.Schema query do @desc "Get a list of authors" field :authors, list_of(:authors) do resolve &SampleBlogWeb.Resolvers.Blog.authors/3 end @desc "Get a author by its id" field :author, :author do arg :id, non_null(:id) resolve &SampleBlogWeb.Resolvers.Blog.author/3 end end object :author do field :id, non_null(:id) field :name, non_null(:string) field :bibliography, non_null(:string) end end8.リゾルバモジュールファイルを作成します。 sampleBlog_web に resolvers フォルダを作成し、blog.exファイルを作成して、ファイルを作成します。
lib/sampleBlog_web/resolvers/blog.exdefmodule SampleBlog.Resolvers.Blog do alias Getaways.Blog def authors(_, _, _) do {:ok, Blog.list_authors()} end def author(_, %{id: id}, _) do {:ok, Blog.get_author!(id)} end end9.スキーマとリゾルバの準備をしたら、ルーターを設定します。
sampleBlog/lib/sampleBlog_web/router.exdefmodule SampleBlogWeb.Router do defmodule SampleBlogWeb.Router do use SampleBlogWeb, :router pipeline :api do plug :accepts, ["json"] end scope "/", SampleBlogWeb do pipe_through :api end forward "/api", Absinthe.Plug, schema: SampleBlogWeb.Schema.Schema forward "/graphiql", Absinthe.Plug.GraphiQL, schema: SampleBlogWeb.Schema.Schema, interface: :simple end最後にサーバーを起動してみます。
mix phx.serverこれで
localhost:4000/graphiql
にアクセスして以下の画面が出てきたら作成準備完了です。次回は、これを GraphCMS のサンプルで作った React のフロントへ接続するものをポエムとして書きます。
手順をチュートリアル的に書く予定は24日の記事に上げるので本日はこの辺で。
- 投稿日:2019-12-01T02:32:45+09:00
Redux不要論と、ReactNの紹介
Redux不要論
若干強めのタイトルです。あらゆるケースでReduxが不要と主張するつもりはありません。
しかし、Reduxが不要と思われるケースでもReduxが使われることを避けるため、「Reduxは必ず採用しなければならないものではない」ということを意識していただきたく、刺激的なタイトルで始めました。
(個人的にはむしろ、積極的に採用すべき理由がなければ採用しない方が良いくらいに思っています)(MobXとか他のライブラリについては一旦置いておきます)
Reduxのメリット
Redux's motivation 曰く、SPAに於けるstate管理とDOM操作の複雑性のうち、Reactがviewレイヤの問題を整理しようとしている。Reduxはstate管理の部分を担当し、stateの変更を予測可能なものにする(to make state mutations predictable)ことを目指しているとのことです。
そしてこれはReduxのthree principlesに反映されています。
- Single source of truth: アプリケーションのすべての状態が単一のstoreに格納される
- State is read-only: stateは読み取り専用で、変更する唯一の方法はactionである
- Changes are made with pure functions: actionによる変更はpure functionによって定義される = reducer
これらによって、以下のようなメリットがあります
- 開発中、アプリケーションの状態を調査しやすくなる
- 開発中、アプリケーション状態の永続化が可能になるなど、開発サイクルが早くなる
- Undo/Redoなどの実装が簡単
- 変更が1箇所に順々に適用されるので、微妙なrace conditionが起こらなくなる
- Actionsはプレーンなオブジェクトなので、ロギングやシリアライズ、後からのリプレイなどが容易
- reducerによって、ロジックの分割・再利用が可能になる
これらのprinciplesと、それによってもたらされる様々なメリットは素晴らしいと思います(詳しい内容は↑のページを読んでください)。
しかし、これらのprinciplesのために導入された制約は、現実的に様々な問題を引き起こします。
また、これらのメリットの内、あなたのアプリケーション開発に本当に必要なものはどれほどあるでしょうか(本当にアプリケーションワイドでのUndo/Redoを実装したいケースがどれほどあるのでしょうか)。Reduxのデメリット
ボイラープレートの増加
よく言われるReduxのデメリットその1です。
何か一つ処理を追加するために、action(とaction-creator)とreducerの追加が必要になります。この問題に対処するために、様々な設計やラッパーライブラリが提案されてきました。
それでも結局、背後では、actionをdispatchしてreducerでstateを更新するという処理が動いていることは意識しないといけません。
Action creatorでactionの作成をカプセル化したり、reducerを生成する関数を作ってボイラープレートを減らしたりしたとしても、それによって抽象化されたロジックの裏側を忘れることはできません。チーム全員が、そのような抽象化を行なったことを了解していないといけません。ボイラープレートを減らすための抽象化は、初見で何をしているのかが分かりづらくなるトレードオフがあります。
結局Reduxを扱うメンタルモデルは変えられず、Reduxの複雑性に向き合うことからは逃れられません。非同期処理
よく言われるReduxのデメリットその2です。
Redux自身は非同期処理に一切関知しません。非同期処理をどこにどう実装するかはユーザに委ねられています。
とはいえ、例えばRedux公式のレシピ集にはaction creatorで非同期処理を行う例が紹介されていますし、ライブラリとしてはredux-thunkとredux-sagaという二大巨頭があります。
しかし、これらって、複雑だと感じないでしょうか?
ただAPIを叩くPromiseを生成して、thenで結果を受け取りたいだけなのに(もしくはasync/awaitしたいだけなのに)、Reduxと組み合わせるために結局またボイラープレートが増えてしまいます。また、データを表示するComponentから離れた場所にロジックが置かれ、処理の流れをぱっと見で把握できなくなります。コードジャンプ無しでは読むのも辛いです。それ、stateful component/
useState
hookでよくないですか?ReduxのサンプルでTodoアプリが紹介されていますが、これを見ても牛刀をもって鶏を割いているようにしか見えません。
Todoアプリなら、プレーンなReactのstateで作れます(React.Component
を使うならthis.state
。最近はuseState
hookという選択肢もあります)Todoアプリはまあチュートリアルなのでこれを以ってRedux不要論を唱えるのはもちろん不適切です。
ただ、実際に作るアプリもよく考えてみると機能的にはTodoアプリに毛が生えた程度のものだったり、
より大規模なアプリだとしても、分解してみると相互にあまり連携しないアプリが複数バンドルされているような構成のアプリ(View数が大量にあっても、各Viewは別々のデータソースのCRUD+αくらいを担当しているようなアプリとか)だったりするケースって、実はそれなりにあるのではないでしょうか。
このようなシーンでは、Reduxを使うメリットは薄いでしょう。
各view componentでAPIを叩き、結果をローカルステートに格納し、それを表示すれば良いのです。実際、ReactはContextもHooksも備え、単体でかなりのことがスマートにできるようになっています。
Redux-Formもいらない
Reduxが登場してから、フォーム管理のためのRedux Formが登場し、人気を伸ばしてきたと記憶しています。
しかし、Formの状態管理は、大抵のケースでグローバルステートに入れなくてもよいものの代表でしょう。
キーストロークのたびにグローバルステートを書き換えたとして、それを参照してformから遠く離れたcomponentの表示を変えたいケースがどのくらいあるでしょうか(Validation/エラー表示などでform内のコンポーネントの表示を変えるだけなら、グローバルステートを経る必要はありません)。ただformの状態管理をしたいだけなのに、キーストロークのたびにactionを発行してreducerを通してグローバルステートを更新して、というのは大げさすぎないでしょうか。後発のformライブラリ、formikのoverviewページでは、まさにこの点が指摘されています。
Why not Redux-Form?
By now, you might be thinking, "Why didn't you just use Redux-Form?" Good question.
- According to our prophet Dan Abramov, form state is inherently ephemeral and local, so tracking it in Redux (or any kind of Flux library) is unnecessary
- Redux-Form calls your entire top-level Redux reducer multiple times ON EVERY SINGLE KEYSTROKE. This is fine for small apps, but as your Redux app grows, input latency will continue to increase if you use Redux-Form.
Formikは登場以降人気を伸ばし、近年はRedux-Formを抜いています。
Formの状態管理にRedux-Formを使う意味はほぼ無いでしょう。
redux-form vs formik | npm trends(補足)Redux-Formの各種便利機能(validationサポートやpristine状態の管理など)は、ステート管理の話とは別物です。これらの機能はformikでも使えます。
(補足2)Form管理の話でいうと、最近はReact Hook Formというのも出てきています。こちらはformのinput componentsをuncontrolled componentsとして扱い、onSubmitなどの必要になるタイミングでref経由で入力内容を取得することで、Reactレイヤでのキーストロークごとのstate更新を無くし、re-renderingを大幅に抑制しているようです。それによるパフォーマンスの良さと、hooksを利用したAPIの使い勝手から、このライブラリもこれから選択肢に上がってきそうです。Redux(グローバルstate)が必要になりやすい代表的な例
さて、ここまで散々Reduxは要らないと書いてきましたが、Redux(というか、Componentごとに分断されたstateでは無い、グローバルなstate)が欲しくなることもあります。
代表的なものは、ログイン状態の管理でしょう。
ログイン中ユーザの情報はアプリの様々な場所で使われます。
ログイン中ユーザの名前を表示する場所がアプリ内の様々な場所に存在したり、またプライベートエンドポイントを叩くあらゆるAPIコールではユーザIDやアクセストークンが必要になります。ただ、管理したい対象がログイン状態だけなら、Reduxを使わなくても
ReactのContextで十分かもしれません。あと、「アプリケーションの状態全てをsave/restoreしたい」というようなケースもあるかもしれません。
Reduxのstateを逐一local storageに保存するようなアプリだと、
それこそRedux-Formも使ってフォームのデータも全部Reduxのstoreに入れておけば、全てのフォームの入力状態すら復元することができます。
そういった要件がある場合は、「Reduxを積極的に採用すべき理由」になると思います。ぜひ使ってください。ReactNの紹介
React用のシンプルなグローバルステート管理ライブラリとして最近気に入っているのがReactNです。
GitHub: https://github.com/CharlesStover/reactn
(GitHubのスター数も1.5kを超えてきているのですが、日本語での紹介を全く見ません…)
このライブラリの紹介をしたい、というのがこの記事の主な目的です。ここまでのRedux不要論とかは、まあそのための掴みです。先ほど、「管理したい対象がログイン状態だけなら、Reduxを使わなくてもReactのcontextで十分かもしれない」と書きましたが、
私は今作っているアプリでログイン状態(とページタイトル)の管理のためだけにReactNを使っています。Contextを使うよりボイラープレートが減るからです。
なんと、「
import React from 'react'
をimport React, { useGlobal } from 'reactn'
に変える」だけで、グローバルステートがuseState
と同じ使い勝手で使えるようになります!import React, { useGlobal } from 'reactn'; ...略... const App: React.FC = () => { const [foo, setFoo] = useGlobal('foo'); ...略... setFoo('some value for foo') // グローバルステート'foo'に値をセット return ( <div>{foo}</div> // グローバルステート'foo'に値を参照 ); }私はhooksで利用しているので、そのような例を載せますが、従来のstateful component風の使い方もできるようです。
以下に、実際のコードからコピーして来たコードを載せます(不要と思われる部分を消したり若干変えています&変更後の動作確認はしていません)。
- ログイン機構はFirebase Authenticationを利用
- ルータにreact-router-domを利用
App/index.tsx
こちらがルートコンポーネントです。
App/index.tsximport React, { useState, useEffect, useGlobal } from 'reactn'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import { firebaseAuth } from '../firebase'; import { AppContainer } from '../components/layout'; import PrivateRoute from '../auth/PrivateRoute'; import Login from '../auth/Login'; import Foo from '../Foo'; import PrivatePage from '../PrivatePage'; import DefaultPage from '../DefaultPage'; const App: React.FC = () => { const [firstAuthLoading, setFirstAuthLoading] = useState(true); const setUser = useGlobal('user')[1]; useEffect(() => { return firebaseAuth.onAuthStateChanged(user => { setUser(user); setFirstAuthLoading(false); }); }, [setUser]); const [pageTitle] = useGlobal('pageTitle'); useEffect(() => { if (pageTitle) { document.title = `FooBar App "${pageTitle}"`; } else { document.title = 'FooBar App'; } }, [pageTitle]); if (firstAuthLoading) { return <span>Authenticating...</span>; } return ( <AppContainer> <Router> <Switch> <Route exact path="/login" component={Login} /> <Route exact path="/foo" component={Foo}/> <PrivateRoute exact path="/private" component={PrivatePage} /> <Route component={DefaultPage} /> </Switch> </Router> </AppContainer> ); }; export default App;1つ目の
useEffect
がユーザ情報に関する処理になっています。
Firebase Authenticationは便利でして、firebaseAuth.onAuthStateChanged
でコールバックを設定しておくと、ログイン状態の変更で発火してくれます。
そのコールバック内で、useGlobal
で得たsetUser
により、グローバルステートuser
にユーザオブジェクトをセットしています。ログインページは別にあり(ここでは載せませんが)、そこでログイン処理が行われると、ログイン状態が変わり、このコールバックが発火し、ユーザオブジェクトがグローバルステートにセットされます。
ログアウトも同様で、このコールバックがuser = null
で呼ばれるので、そのままnull
をグローバルステートにセットします。2つ目の
useEffect
はtitle要素を書き換える処理です。
グローバルステートpageTitle
を読み出し、document.title
を設定しています。例えば、どこかの子ページで、以下のような処理でグローバルステート
pageTitle
を設定することができます。
この子ページを表示した(このcomponentをマウントした)タイミングで、グローバルステートpageTitle
に'Foo page'
がセットされ、それがApp/index.tsx
で読み出されてtitleが書き換わります。FooPage.tsximport React, {useGlobal, useEffect} from 'reactn'; const FooPage: React.FC = () => { const setPageTitle = useGlobal('pageTitle')[1]; useEffect(() => { setPageTitle('Foo page'); }, []); ...略... }
PrivateRoute
また、上記
App/index.tsx
を見ると、ルーティングにPrivateRoute
が使われていますが、これは以下のようなコンポーネントです。auth/PrivateRoute.tsximport React, { useGlobal } from 'reactn'; import { Route, Redirect, RouteProps } from 'react-router-dom'; const PrivateRoute: React.FC<RouteProps> = props => { const [user] = useGlobal('user'); const { children, render, ...restProps } = props; return ( <Route {...restProps} render={renderProps => { if (!user) { return ( <Redirect to={{ pathname: '/login', state: { from: renderProps.location } }} /> ); } if (render) { return render(renderProps); } else { return children; } }} /> ); }; export default PrivateRoute;グローバルステート
user
を読み出して、ログインしていれば(user != null
)そのcomponentを表示し、ログインしていなければ(user == null
)ログインページにリダイレクトします。
これはreact-router-dom公式サンプルが元になっています。TypeScript
また、ご覧の通りTypeScriptで書いていますが、ReactNはTypeScriptにも対応しています。
以下のように型定義を書いておけば、useGlobal
についても型推論が働きます。VSCodeの補完もバッチリです。global.d.tsimport 'reactn'; declare module 'reactn/default' { export interface State { user: firebase.User | null; pageTitle: string | null; } }まとめ
Reduxの採用を決める前に、そのアプリケーションでは本当にReduxが必要なのか、よく考えてみてください。
Reduxを入れることによって却ってコードが複雑化しかねません。特に、ちょっとしたグローバルステートが管理できれば良いのであれば、Contextや、ぜひReactNを使ってみてください。
(補足)Reduxが不要なケースに気づいて、React組み込みのContextやReactNを使おう、という結論の文章ですので、MobXには触れませんでした。そちらについても、「使うメリットがあれば使う、そうでなければ使わない」にしかなりません。ただ、結局、なるべくシンプルに済ませられないか考え続けるのは大事かなと思います。
- 投稿日:2019-12-01T02:32:45+09:00
Redux不要論と、グローバルステート管理ライブラリReactNの紹介
Redux不要論
若干強めのタイトルです。あらゆるケースでReduxが不要と主張するつもりはありません。
しかし、Reduxが不要と思われるケースでもReduxが使われることを避けるため、「Reduxは必ず採用しなければならないものではない」ということを意識していただきたく、刺激的なタイトルで始めました。
(個人的にはむしろ、積極的に採用すべき理由がなければ採用しない方が良いくらいに思っています)(MobXとか他のライブラリについては一旦置いておきます)
Reduxのメリット
Redux's motivation 曰く、SPAに於けるstate管理とDOM操作の複雑性のうち、Reactがviewレイヤの問題を整理しようとしている。Reduxはstate管理の部分を担当し、stateの変更を予測可能なものにする(to make state mutations predictable)ことを目指しているとのことです。
そしてこれはReduxのthree principlesに反映されています。
- Single source of truth: アプリケーションのすべての状態が単一のstoreに格納される
- State is read-only: stateは読み取り専用で、変更する唯一の方法はactionである
- Changes are made with pure functions: actionによる変更はpure functionによって定義される = reducer
これらによって、以下のようなメリットがあります
- 開発中、アプリケーションの状態を調査しやすくなる
- 開発中、アプリケーション状態の永続化が可能になるなど、開発サイクルが早くなる
- Undo/Redoなどの実装が簡単
- 変更が1箇所に順々に適用されるので、微妙なrace conditionが起こらなくなる
- Actionsはプレーンなオブジェクトなので、ロギングやシリアライズ、後からのリプレイなどが容易
- reducerによって、ロジックの分割・再利用が可能になる
これらのprinciplesと、それによってもたらされる様々なメリットは素晴らしいと思います(詳しい内容は↑のページを読んでください)。
しかし、これらのprinciplesのために導入された制約は、現実的に様々な問題を引き起こします。
また、これらのメリットの内、あなたのアプリケーション開発に本当に必要なものはどれほどあるでしょうか(本当にアプリケーションワイドでのUndo/Redoを実装したいケースがどれほどあるのでしょうか)。Reduxのデメリット
ボイラープレートの増加
よく言われるReduxのデメリットその1です。
何か一つ処理を追加するために、action(とaction-creator)とreducerの追加が必要になります。この問題に対処するために、様々な設計やラッパーライブラリが提案されてきました。
それでも結局、背後では、actionをdispatchしてreducerでstateを更新するという処理が動いていることは意識しないといけません。
Action creatorでactionの作成をカプセル化したり、reducerを生成する関数を作ってボイラープレートを減らしたりしたとしても、それによって抽象化されたロジックの裏側を忘れることはできません。チーム全員が、そのような抽象化を行なったことを了解していないといけません。ボイラープレートを減らすための抽象化は、初見で何をしているのかが分かりづらくなるトレードオフがあります。
結局Reduxを扱うメンタルモデルは変えられず、Reduxの複雑性に向き合うことからは逃れられません。非同期処理
よく言われるReduxのデメリットその2です。
Redux自身は非同期処理に一切関知しません。非同期処理をどこにどう実装するかはユーザに委ねられています。
とはいえ、例えばRedux公式のレシピ集にはaction creatorで非同期処理を行う例が紹介されていますし、ライブラリとしてはredux-thunkとredux-sagaという二大巨頭があります。
しかし、これらって、複雑だと感じないでしょうか?
ただAPIを叩くPromiseを生成して、thenで結果を受け取りたいだけなのに(もしくはasync/awaitしたいだけなのに)、Reduxと組み合わせるために結局またボイラープレートが増えてしまいます。また、データを表示するComponentから離れた場所にロジックが置かれ、処理の流れをぱっと見で把握できなくなります。コードジャンプ無しでは読むのも辛いです。それ、stateful component/
useState
hookでよくないですか?ReduxのサンプルでTodoアプリが紹介されていますが、これを見ても牛刀をもって鶏を割いているようにしか見えません。
Todoアプリなら、プレーンなReactのstateで作れます(React.Component
を使うならthis.state
。最近はuseState
hookという選択肢もあります)Todoアプリはまあチュートリアルなのでこれを以ってRedux不要論を唱えるのはもちろん不適切です。
ただ、実際に作るアプリもよく考えてみると機能的にはTodoアプリに毛が生えた程度のものだったり、
より大規模なアプリだとしても、分解してみると相互にあまり連携しないアプリが複数バンドルされているような構成のアプリ(View数が大量にあっても、各Viewは別々のデータソースのCRUD+αくらいを担当しているようなアプリとか)だったりするケースって、実はそれなりにあるのではないでしょうか。
このようなシーンでは、Reduxを使うメリットは薄いでしょう。
各view componentでAPIを叩き、結果をローカルステートに格納し、それを表示すれば良いのです。実際、ReactはContextもHooksも備え、単体でかなりのことがスマートにできるようになっています。
Redux-Formもいらない
Reduxが登場してから、フォーム管理のためのRedux Formが登場し、人気を伸ばしてきたと記憶しています。
しかし、Formの状態管理は、大抵のケースでグローバルステートに入れなくてもよいものの代表でしょう。
キーストロークのたびにグローバルステートを書き換えたとして、それを参照してformから遠く離れたcomponentの表示を変えたいケースがどのくらいあるでしょうか(Validation/エラー表示などでform内のコンポーネントの表示を変えるだけなら、グローバルステートを経る必要はありません)。ただformの状態管理をしたいだけなのに、キーストロークのたびにactionを発行してreducerを通してグローバルステートを更新して、というのは大げさすぎないでしょうか。後発のformライブラリ、formikのoverviewページでは、まさにこの点が指摘されています。
Why not Redux-Form?
By now, you might be thinking, "Why didn't you just use Redux-Form?" Good question.
- According to our prophet Dan Abramov, form state is inherently ephemeral and local, so tracking it in Redux (or any kind of Flux library) is unnecessary
- Redux-Form calls your entire top-level Redux reducer multiple times ON EVERY SINGLE KEYSTROKE. This is fine for small apps, but as your Redux app grows, input latency will continue to increase if you use Redux-Form.
Formikは登場以降人気を伸ばし、近年はRedux-Formを抜いています。
Formの状態管理にRedux-Formを使う意味はほぼ無いでしょう。
redux-form vs formik | npm trends(補足)Redux-Formの各種便利機能(validationサポートやpristine状態の管理など)は、ステート管理の話とは別物です。これらの機能はformikでも使えます。
(補足2)Form管理の話でいうと、最近はReact Hook Formというのも出てきています。こちらはformのinput componentsをuncontrolled componentsとして扱い、onSubmitなどの必要になるタイミングでref経由で入力内容を取得することで、Reactレイヤでのキーストロークごとのstate更新を無くし、re-renderingを大幅に抑制しているようです。それによるパフォーマンスの良さと、hooksを利用したAPIの使い勝手から、このライブラリもこれから選択肢に上がってきそうです。Redux(グローバルstate)が必要になりやすい代表的な例
さて、ここまで散々Reduxは要らないと書いてきましたが、Redux(というか、Componentごとに分断されたstateでは無い、グローバルなstate)が欲しくなることもあります。
代表的なものは、ログイン状態の管理でしょう。
ログイン中ユーザの情報はアプリの様々な場所で使われます。
ログイン中ユーザの名前を表示する場所がアプリ内の様々な場所に存在したり、またプライベートエンドポイントを叩くあらゆるAPIコールではユーザIDやアクセストークンが必要になります。ただ、管理したい対象がログイン状態だけなら、Reduxを使わなくても
ReactのContextで十分かもしれません。あと、「アプリケーションの状態全てをsave/restoreしたい」というようなケースもあるかもしれません。
Reduxのstateを逐一local storageに保存するようなアプリだと、
それこそRedux-Formも使ってフォームのデータも全部Reduxのstoreに入れておけば、全てのフォームの入力状態すら復元することができます。
そういった要件がある場合は、「Reduxを積極的に採用すべき理由」になると思います。ぜひ使ってください。ReactNの紹介
React用のシンプルなグローバルステート管理ライブラリとして最近気に入っているのがReactNです。
GitHub: https://github.com/CharlesStover/reactn
(GitHubのスター数も1.5kを超えてきているのですが、日本語での紹介を全く見ません…)
このライブラリの紹介をしたい、というのがこの記事の主な目的です。ここまでのRedux不要論とかは、まあそのための掴みです。先ほど、「管理したい対象がログイン状態だけなら、Reduxを使わなくてもReactのcontextで十分かもしれない」と書きましたが、
私は今作っているアプリでログイン状態(とページタイトル)の管理のためだけにReactNを使っています。Contextを使うよりボイラープレートが減るからです。
なんと、「
import React from 'react'
をimport React, { useGlobal } from 'reactn'
に変える」だけで、グローバルステートがuseState
と同じ使い勝手で使えるようになります!import React, { useGlobal } from 'reactn'; ...略... const App: React.FC = () => { const [foo, setFoo] = useGlobal('foo'); ...略... setFoo('some value for foo') // グローバルステート'foo'に値をセット return ( <div>{foo}</div> // グローバルステート'foo'に値を参照 ); }私はhooksで利用しているので、そのような例を載せますが、従来のstateful component風の使い方もできるようです。
以下に、実際のコードからコピーして来たコードを載せます(不要と思われる部分を消したり若干変えています&変更後の動作確認はしていません)。
- ログイン機構はFirebase Authenticationを利用
- ルータにreact-router-domを利用
App/index.tsx
こちらがルートコンポーネントです。
App/index.tsximport React, { useState, useEffect, useGlobal } from 'reactn'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import { firebaseAuth } from '../firebase'; import { AppContainer } from '../components/layout'; import PrivateRoute from '../auth/PrivateRoute'; import Login from '../auth/Login'; import Foo from '../Foo'; import PrivatePage from '../PrivatePage'; import DefaultPage from '../DefaultPage'; const App: React.FC = () => { const [firstAuthLoading, setFirstAuthLoading] = useState(true); const setUser = useGlobal('user')[1]; useEffect(() => { return firebaseAuth.onAuthStateChanged(user => { setUser(user); setFirstAuthLoading(false); }); }, [setUser]); const [pageTitle] = useGlobal('pageTitle'); useEffect(() => { if (pageTitle) { document.title = `FooBar App "${pageTitle}"`; } else { document.title = 'FooBar App'; } }, [pageTitle]); if (firstAuthLoading) { return <span>Authenticating...</span>; } return ( <AppContainer> <Router> <Switch> <Route exact path="/login" component={Login} /> <Route exact path="/foo" component={Foo}/> <PrivateRoute exact path="/private" component={PrivatePage} /> <Route component={DefaultPage} /> </Switch> </Router> </AppContainer> ); }; export default App;1つ目の
useEffect
がユーザ情報に関する処理になっています。
Firebase Authenticationは便利でして、firebaseAuth.onAuthStateChanged
でコールバックを設定しておくと、ログイン状態の変更で発火してくれます。
そのコールバック内で、useGlobal
で得たsetUser
により、グローバルステートuser
にユーザオブジェクトをセットしています。ログインページは別にあり(ここでは載せませんが)、そこでログイン処理が行われると、ログイン状態が変わり、このコールバックが発火し、ユーザオブジェクトがグローバルステートにセットされます。
ログアウトも同様で、このコールバックがuser = null
で呼ばれるので、そのままnull
をグローバルステートにセットします。2つ目の
useEffect
はtitle要素を書き換える処理です。
グローバルステートpageTitle
を読み出し、document.title
を設定しています。例えば、どこかの子ページで、以下のような処理でグローバルステート
pageTitle
を設定することができます。
この子ページを表示した(このcomponentをマウントした)タイミングで、グローバルステートpageTitle
に'Foo page'
がセットされ、それがApp/index.tsx
で読み出されてtitleが書き換わります。FooPage.tsximport React, {useGlobal, useEffect} from 'reactn'; const FooPage: React.FC = () => { const setPageTitle = useGlobal('pageTitle')[1]; useEffect(() => { setPageTitle('Foo page'); }, []); ...略... }
PrivateRoute
また、上記
App/index.tsx
を見ると、ルーティングにPrivateRoute
が使われていますが、これは以下のようなコンポーネントです。auth/PrivateRoute.tsximport React, { useGlobal } from 'reactn'; import { Route, Redirect, RouteProps } from 'react-router-dom'; const PrivateRoute: React.FC<RouteProps> = props => { const [user] = useGlobal('user'); const { children, render, ...restProps } = props; return ( <Route {...restProps} render={renderProps => { if (!user) { return ( <Redirect to={{ pathname: '/login', state: { from: renderProps.location } }} /> ); } if (render) { return render(renderProps); } else { return children; } }} /> ); }; export default PrivateRoute;グローバルステート
user
を読み出して、ログインしていれば(user != null
)そのcomponentを表示し、ログインしていなければ(user == null
)ログインページにリダイレクトします。
これはreact-router-dom公式サンプルが元になっています。TypeScript
また、ご覧の通りTypeScriptで書いていますが、ReactNはTypeScriptにも対応しています。
以下のように型定義を書いておけば、useGlobal
についても型推論が働きます。VSCodeの補完もバッチリです。global.d.tsimport 'reactn'; declare module 'reactn/default' { export interface State { user: firebase.User | null; pageTitle: string | null; } }まとめ
Reduxの採用を決める前に、そのアプリケーションでは本当にReduxが必要なのか、よく考えてみてください。
Reduxを入れることによって却ってコードが複雑化しかねません。特に、ちょっとしたグローバルステートが管理できれば良いのであれば、Contextや、ぜひReactNを使ってみてください。
(補足)Reduxが不要なケースに気づいて、React組み込みのContextやReactNを使おう、という結論の文章ですので、MobXには触れませんでした。そちらについても、「使うメリットがあれば使う、そうでなければ使わない」にしかなりません。ただ、結局、なるべくシンプルに済ませられないか考え続けるのは大事かなと思います。
- 投稿日:2019-12-01T02:32:45+09:00
Redux不要論と、グローバル状態管理ライブラリReactNの紹介
Redux不要論
若干強めのタイトルです。あらゆるケースでReduxが不要と主張するつもりはありません。
しかし、Reduxが不要と思われるケースでもReduxが使われることを避けるため、「Reduxは必ず採用しなければならないものではない」ということを意識していただきたく、刺激的なタイトルで始めました。
(個人的にはむしろ、積極的に採用すべき理由がなければ採用しない方が良いくらいに思っています)(MobXとか他のライブラリについては一旦置いておきます)
Reduxのメリット
Redux's motivation 曰く、SPAに於けるstate管理とDOM操作の複雑性のうち、Reactがviewレイヤの問題を整理しようとしている。Reduxはstate管理の部分を担当し、stateの変更を予測可能なものにする(to make state mutations predictable)ことを目指しているとのことです。
そしてこれはReduxのthree principlesに反映されています。
- Single source of truth: アプリケーションのすべての状態が単一のstoreに格納される
- State is read-only: stateは読み取り専用で、変更する唯一の方法はactionである
- Changes are made with pure functions: actionによる変更はpure functionによって定義される = reducer
これらによって、以下のようなメリットがあります
- 開発中、アプリケーションの状態を調査しやすくなる
- 開発中、アプリケーション状態の永続化が可能になるなど、開発サイクルが早くなる
- Undo/Redoなどの実装が簡単
- 変更が1箇所に順々に適用されるので、微妙なrace conditionが起こらなくなる
- Actionsはプレーンなオブジェクトなので、ロギングやシリアライズ、後からのリプレイなどが容易
- reducerによって、ロジックの分割・再利用が可能になる
これらのprinciplesと、それによってもたらされる様々なメリットは素晴らしいと思います(詳しい内容は↑のページを読んでください)。
しかし、これらのprinciplesのために導入された制約は、現実的に様々な問題を引き起こします。
また、これらのメリットの内、あなたのアプリケーション開発に本当に必要なものはどれほどあるでしょうか(本当にアプリケーションワイドでのUndo/Redoを実装したいケースがどれほどあるのでしょうか)。Reduxのデメリット
ボイラープレートの増加
よく言われるReduxのデメリットその1です。
何か一つ処理を追加するために、action(とaction-creator)とreducerの追加が必要になります。この問題に対処するために、様々な設計やラッパーライブラリが提案されてきました。
それでも結局、背後では、actionをdispatchしてreducerでstateを更新するという処理が動いていることは意識しないといけません。
Action creatorでactionの作成をカプセル化したり、reducerを生成する関数を作ってボイラープレートを減らしたりしたとしても、それによって抽象化されたロジックの裏側を忘れることはできません。チーム全員が、そのような抽象化を行なったことを了解していないといけません。ボイラープレートを減らすための抽象化は、初見で何をしているのかが分かりづらくなるトレードオフがあります。
結局Reduxを扱うメンタルモデルは変えられず、Reduxの複雑性に向き合うことからは逃れられません。非同期処理
よく言われるReduxのデメリットその2です。
Redux自身は非同期処理に一切関知しません。非同期処理をどこにどう実装するかはユーザに委ねられています。
とはいえ、例えばRedux公式のレシピ集にはaction creatorで非同期処理を行う例が紹介されていますし、ライブラリとしてはredux-thunkとredux-sagaという二大巨頭があります。
しかし、これらって、複雑だと感じないでしょうか?
ただAPIを叩くPromiseを生成して、thenで結果を受け取りたいだけなのに(もしくはasync/awaitしたいだけなのに)、Reduxと組み合わせるために結局またボイラープレートが増えてしまいます。また、データを表示するComponentから離れた場所にロジックが置かれ、処理の流れをぱっと見で把握できなくなります。コードジャンプ無しでは読むのも辛いです。それ、stateful component/
useState
hookでよくないですか?ReduxのサンプルでTodoアプリが紹介されていますが、これを見ても牛刀をもって鶏を割いているようにしか見えません。
Todoアプリなら、プレーンなReactのstateで作れます(React.Component
を使うならthis.state
。最近はuseState
hookという選択肢もあります)Todoアプリはまあチュートリアルなのでこれを以ってRedux不要論を唱えるのはもちろん不適切です。
ただ、実際に作るアプリもよく考えてみると機能的にはTodoアプリに毛が生えた程度のものだったり、
より大規模なアプリだとしても、分解してみると相互にあまり連携しないアプリが複数バンドルされているような構成のアプリ(View数が大量にあっても、各Viewは別々のデータソースのCRUD+αくらいを担当しているようなアプリとか)だったりするケースって、実はそれなりにあるのではないでしょうか。
このようなシーンでは、Reduxを使うメリットは薄いでしょう。
各view componentでAPIを叩き、結果をローカルステートに格納し、それを表示すれば良いのです。実際、ReactはContextもHooksも備え、単体でかなりのことがスマートにできるようになっています。
Redux-Formもいらない
Reduxが登場してから、フォーム管理のためのRedux Formが登場し、人気を伸ばしてきたと記憶しています。
しかし、Formの状態管理は、大抵のケースでグローバルステートに入れなくてもよいものの代表でしょう。
キーストロークのたびにグローバルステートを書き換えたとして、それを参照してformから遠く離れたcomponentの表示を変えたいケースがどのくらいあるでしょうか(Validation/エラー表示などでform内のコンポーネントの表示を変えるだけなら、グローバルステートを経る必要はありません)。ただformの状態管理をしたいだけなのに、キーストロークのたびにactionを発行してreducerを通してグローバルステートを更新して、というのは大げさすぎないでしょうか。後発のformライブラリ、formikのoverviewページでは、まさにこの点が指摘されています。
Why not Redux-Form?
By now, you might be thinking, "Why didn't you just use Redux-Form?" Good question.
- According to our prophet Dan Abramov, form state is inherently ephemeral and local, so tracking it in Redux (or any kind of Flux library) is unnecessary
- Redux-Form calls your entire top-level Redux reducer multiple times ON EVERY SINGLE KEYSTROKE. This is fine for small apps, but as your Redux app grows, input latency will continue to increase if you use Redux-Form.
Formikは登場以降人気を伸ばし、近年はRedux-Formを抜いています。
Formの状態管理にRedux-Formを使う意味はほぼ無いでしょう。
redux-form vs formik | npm trends(補足)Redux-Formの各種便利機能(validationサポートやpristine状態の管理など)は、ステート管理の話とは別物です。これらの機能はformikでも使えます。
(補足2)Form管理の話でいうと、最近はReact Hook Formというのも出てきています。こちらはformのinput componentsをuncontrolled componentsとして扱い、onSubmitなどの必要になるタイミングでref経由で入力内容を取得することで、Reactレイヤでのキーストロークごとのstate更新を無くし、re-renderingを大幅に抑制しているようです。それによるパフォーマンスの良さと、hooksを利用したAPIの使い勝手から、このライブラリもこれから選択肢に上がってきそうです。Redux(グローバルstate)が必要になりやすい代表的な例
さて、ここまで散々Reduxは要らないと書いてきましたが、Redux(というか、Componentごとに分断されたstateでは無い、グローバルなstate)が欲しくなることもあります。
代表的なものは、ログイン状態の管理でしょう。
ログイン中ユーザの情報はアプリの様々な場所で使われます。
ログイン中ユーザの名前を表示する場所がアプリ内の様々な場所に存在したり、またプライベートエンドポイントを叩くあらゆるAPIコールではユーザIDやアクセストークンが必要になります。ただ、管理したい対象がログイン状態だけなら、Reduxを使わなくても
ReactのContextで十分かもしれません。あと、「アプリケーションの状態全てをsave/restoreしたい」というようなケースもあるかもしれません。
Reduxのstateを逐一local storageに保存するようなアプリだと、
それこそRedux-Formも使ってフォームのデータも全部Reduxのstoreに入れておけば、全てのフォームの入力状態すら復元することができます。
そういった要件がある場合は、「Reduxを積極的に採用すべき理由」になると思います。ぜひ使ってください。ReactNの紹介
React用のシンプルなグローバルステート管理ライブラリとして最近気に入っているのがReactNです。
GitHub: https://github.com/CharlesStover/reactn
(GitHubのスター数も1.5kを超えてきているのですが、日本語での紹介を全く見ません…)
このライブラリの紹介をしたい、というのがこの記事の主な目的です。ここまでのRedux不要論とかは、まあそのための掴みです。先ほど、「管理したい対象がログイン状態だけなら、Reduxを使わなくてもReactのcontextで十分かもしれない」と書きましたが、
私は今作っているアプリでログイン状態(とページタイトル)の管理のためだけにReactNを使っています。Contextを使うよりボイラープレートが減るからです。
なんと、「
import React from 'react'
をimport React, { useGlobal } from 'reactn'
に変える」だけで、グローバルステートがuseState
と同じ使い勝手で使えるようになります!import React, { useGlobal } from 'reactn'; ...略... const App: React.FC = () => { const [foo, setFoo] = useGlobal('foo'); ...略... setFoo('some value for foo') // グローバルステート'foo'に値をセット return ( <div>{foo}</div> // グローバルステート'foo'に値を参照 ); }私はhooksで利用しているので、そのような例を載せますが、従来のstateful component風の使い方もできるようです。
ReactNの利用例
以下に、実際のコードからコピーして来たコードを載せます(不要と思われる部分を消したり若干変えています&変更後の動作確認はしていません)。
- ログイン機構はFirebase Authenticationを利用
- ルータにreact-router-domを利用
App/index.tsx
こちらがルートコンポーネントです。
App/index.tsximport React, { useState, useEffect, useGlobal } from 'reactn'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import { firebaseAuth } from '../firebase'; import { AppContainer } from '../components/layout'; import PrivateRoute from '../auth/PrivateRoute'; import Login from '../auth/Login'; import Foo from '../Foo'; import PrivatePage from '../PrivatePage'; import DefaultPage from '../DefaultPage'; const App: React.FC = () => { const [firstAuthLoading, setFirstAuthLoading] = useState(true); const setUser = useGlobal('user')[1]; useEffect(() => { return firebaseAuth.onAuthStateChanged(user => { setUser(user); setFirstAuthLoading(false); }); }, [setUser]); const [pageTitle] = useGlobal('pageTitle'); useEffect(() => { if (pageTitle) { document.title = `FooBar App "${pageTitle}"`; } else { document.title = 'FooBar App'; } }, [pageTitle]); if (firstAuthLoading) { return <span>Authenticating...</span>; } return ( <AppContainer> <Router> <Switch> <Route exact path="/login" component={Login} /> <Route exact path="/foo" component={Foo}/> <PrivateRoute exact path="/private" component={PrivatePage} /> <Route component={DefaultPage} /> </Switch> </Router> </AppContainer> ); }; export default App;1つ目の
useEffect
がユーザ情報に関する処理になっています。
Firebase Authenticationは便利でして、firebaseAuth.onAuthStateChanged
でコールバックを設定しておくと、ログイン状態の変更で発火してくれます。
そのコールバック内で、useGlobal
で得たsetUser
により、グローバルステートuser
にユーザオブジェクトをセットしています。ログインページは別にあり(ここでは載せませんが)、そこでログイン処理が行われると、ログイン状態が変わり、このコールバックが発火し、ユーザオブジェクトがグローバルステートにセットされます。
ログアウトも同様で、このコールバックがuser = null
で呼ばれるので、そのままnull
をグローバルステートにセットします。2つ目の
useEffect
はtitle要素を書き換える処理です。
グローバルステートpageTitle
を読み出し、document.title
を設定しています。例えば、どこかの子ページで、以下のような処理でグローバルステート
pageTitle
を設定することができます。
この子ページを表示した(このcomponentをマウントした)タイミングで、グローバルステートpageTitle
に'Foo page'
がセットされ、それがApp/index.tsx
で読み出されてtitleが書き換わります。FooPage.tsximport React, {useGlobal, useEffect} from 'reactn'; const FooPage: React.FC = () => { const setPageTitle = useGlobal('pageTitle')[1]; useEffect(() => { setPageTitle('Foo page'); }, []); ...略... }
PrivateRoute
また、上記
App/index.tsx
を見ると、ルーティングにPrivateRoute
が使われていますが、これは以下のようなコンポーネントです。auth/PrivateRoute.tsximport React, { useGlobal } from 'reactn'; import { Route, Redirect, RouteProps } from 'react-router-dom'; const PrivateRoute: React.FC<RouteProps> = props => { const [user] = useGlobal('user'); const { children, render, ...restProps } = props; return ( <Route {...restProps} render={renderProps => { if (!user) { return ( <Redirect to={{ pathname: '/login', state: { from: renderProps.location } }} /> ); } if (render) { return render(renderProps); } else { return children; } }} /> ); }; export default PrivateRoute;グローバルステート
user
を読み出して、ログインしていれば(user != null
)そのcomponentを表示し、ログインしていなければ(user == null
)ログインページにリダイレクトします。
これはreact-router-dom公式サンプルが元になっています。TypeScript
また、ご覧の通りTypeScriptで書いていますが、ReactNはTypeScriptにも対応しています。
以下のように型定義を書いておけば、useGlobal
についても型推論が働きます。VSCodeの補完もバッチリです。global.d.tsimport 'reactn'; declare module 'reactn/default' { export interface State { user: firebase.User | null; pageTitle: string | null; } }まとめ
Reduxの採用を決める前に、そのアプリケーションでは本当にReduxが必要なのか、よく考えてみてください。
Reduxを入れることによって却ってコードが複雑化しかねません。特に、ちょっとしたグローバルステートが管理できれば良いのであれば、Contextや、ぜひReactNを使ってみてください。
(補足)Reduxが不要なケースに気づいて、React組み込みのContextやReactNを使おう、という結論の文章ですので、MobXには触れませんでした。そちらについても、「使うメリットがあれば使う、そうでなければ使わない」にしかなりません。ただ、結局、なるべくシンプルに済ませられないか考え続けるのは大事かなと思います。
- 投稿日:2019-12-01T02:15:58+09:00
パ○ドラの盤面欠損率を計算するWebアプリを作った話(nextjs + TypeScript + Mobx)
アドベントカレンダーの一発目がこんなネタ記事でとても申し訳ないです。去年のアドベントカレンダーでパズ○ラの盤面欠損率を計算するスクリプトを高速化する話を書きました。
今回は、そのときに使ったロジックを使って、「パズドラの盤面欠損率を計算するWebアプリ」を作ってみた話です。
DEMO
以下のURLから使えます。PCスマホ両対応です
https://youthful-cray-84efcb.netlify.com/pazdora-calまた、ソースコードは例によってGitHubにあげています。
使い方
ここからはこのアプリの使い方を説明します。パズドラに興味のない人、よく知らない人は 内部の話まで飛ばすと良いかなと思います。
Webアプリ上で盤面の条件を定義するカードを作成し、その条件を満たす盤面がどの程度の確率で存在するかを計算します。
条件カードには以下の3種類が存在します。
- ドロップ条件
- コンボ条件
- 多色条件
まずドロップ条件の使い方から説明します。
ドロップ条件
画面の「+ドロップ」ボタンを押すと、ドロップ条件を追加できます。ドロップ条件の中で設定できる値は以下です。
* ドロップの色: 条件に合致する色を選択します
* ドロップの個数/条件: 選択した色のドロップが何個(以上/以下)あればよいかを選択します例えば、以下のリーダースキルを見てみてください
火と水の同時攻撃で攻撃力4倍と書かれていますね。
この条件を満たす盤面の出現率を計算すると以下のようになります。こんな感じで複数カードを設定すると、それぞれの条件のアンドを取って計算します。
指定2色が存在する確率は83%ぐらいですね
5~6回に一回は欠損する計算です。意外と欠損するかも?コンボ条件
お次はコンボ条件です。「+コンボ」のボタンを押すと追加できます。これは比較的わかりやすいんじゃないでしょうか?以下の項目を設定できます
- 消せるドロップ数: 一コンボするのに必要なドロップ数を設定します。普通は3個です
- コンボ数/条件: 条件を満たすコンボ数を設定します
- 繋げるドロップがある場合は繋げるドロップを一種類だけ指定できます
56盤面で7コンボある確率を計算してみました。これをみると、ほとんどの場合で7コンボはありますね。
つまり7コンボできないのは甘えです。
7コンボ強化の覚醒スキルが如何に強いかがわかります。繋げるドロップ条件についても見ていきましょう。
例えば、「回復を5個繋いで、なおかつ7コンボ以上ある確率」を計算したいとします。
一見、先程紹介したドロップ条件とコンボ条件の組み合わせで表現できるような気がしますが、実際には、コンボ条件は他のカードの条件とは独立して計算されてしまうため、例えば回復が盤面に6個しかない場合、本来であれば5個繋げるので1コンボ分とカウントしたいのですが、2分割して2コンボ分とカウントしてしまいます。この問題を回避するために、コンボ条件の中で繋げるドロップを指定できるようにしています。
例えば追い打ち7コンボ以上ある確率は以下のようになります。
比較のため、ドロップ条件と組み合わせた場合の確率も計算してみます。
比べてみると、前者のほうが少し確率が下がっている事がわかりますね。多色条件
最後は多色条件です。これが一番ややこしいです。が、設定できる幅は一番広くて、1つ目のドロップ条件の上位互換みたいな設定ができます。
順番に説明していきましょう
- 選択する色: 多色条件に含まれる色を複数選択できます。
- ドロップの種類: 上の選択した色の中から、N種類以上条件を満たしているかを設定します
- ドロップの個数: これはドロップ条件と同じです。
言葉で説明してもわからないと思うので例を出します。以下のようなリーダースキルを考えてみましょう。
このリーダースキルを満たす盤面の出現率は以下になります。
7コンボ以上という条件はコンボ条件のときと同じですね。
注目すべきは一枚目のカードです。「4色以上同時攻撃」というのは正確には
「「火水木光闇」の5種類のドロップのうち、4種類以上の色が3個以上存在する」
という条件になります。これを多色条件のカードで表すと画像のようになります。ちなみに、5色中4色存在する確率はこんな感じでかなり高いです。追い打ちとか無効貫通とか考えず、欠損率だけ考えるならかなり優秀なリーダースキルですね
もう一つ例を出しましょう。以下のようなリーダースキルを考えます。
かなりややこしそうですね。しかしこれも計算できます。
多色条件で2色選択して、ドロップの種類を2種類以上に設定すると、指定2色を計算したときと同じ条件になります。
更に、残りの4色を選択して、1種類以上が5個以上ある条件を追加することで、「火光以外を5個以上繋げて消すと~」の条件を表すことができます。計算できない条件
さて、ここまで紹介した機能で殆どのLSの条件は計算できます。
しかし、一部正しく計算できないLSのもあります。例えば
- 十字消しやL字消しなど、消すDropの形が定義されているLS(とくに十字消しは難しい)
- 複雑な条件にコンボ条件が追加されたLS(コンボ数は他の消し方条件によって計算方法が変わるので正確に計算することが難しい)
具体的な例で言えば、「火か水を5個以上繋げて、7コンボ以上」とかは正確に計算できません。
ここらへんは今後の課題ですね内部の話
Nextjs1 + TypeScript + Mobx2で作りました。
Reactでまともに開発するのはこれが初めてでしたが、すごく良かったです。TypeScriptとの相性の良さ
まず、TypeScriptとの相性が良いです。Vueのテンプレートと違って、Reactではtsx部分はすべて型が付きます。
そして今回使用したNext.jsのver9では、特に何も設定せずともts / tsxファイルを解釈してくれる様になったので、導入コストも低いです。
フレームワークの機能に関しても殆どの部分に型がついているので、コーディングしていて型がなくて困るようなこともありませんでした。
また、TypeScript自体も良くできていて、今回は特にkeyof
キーワードが活躍しました。これとジェネリックを組み合わせることで、引数に指定したオブジェクトのメンバのキーのみを引数として受け付けるような関数を書くことができます。例を示しましょう。
以下のようなオブジェクトのプロパティを取得する関数を考えます。/** * 引数に指定したオブジェクトのキーの値を所得する */ function propGet<T, K extends keyof T>(obj: T, key: K): T[K]{ return obj[key] }これを以下のようなuserオブジェクトに適用すると
const user = { name: 'testuser', id: 1, obj: { obj2: { aaa: 'aaaa' }, obj3:{ ccc: 'cccc' }, bbb: 'bbbb', } }このような感じで、第一引数の型(今回はuserオブジェクトの型)を使って、第2引数の型を単なるstringではなく、userオブジェクトのキーのみをstringリテラルで推論してくれます。
propGet(user,'name') // => 返り値の型がstringになる propGet(user,'id') // => 返り値の型がnumberになる propGet(user,'nana') // => nanaというプロパティは存在しないのでコンパイルエラー今回は、欠損率の計算をWebWorkerを使ってメインスレッドとは別の場所で行った関係上、条件カードの生成をFactory経由で行う必要がありました。
keyof
キーワードを使っておくと、factory関数の引数に指定する値を、各条件カードクラスのメンバから推論させることができ、
仮に各カードクラスのメンバをあとから変更したとしても、factory側の引数の型を変更する必要がなくなり、常に整合性の取れた状態にすることができます。
さすがTypeScriptとついているだけありますね。
ただあまりやりすぎると、型パズル状態になってあとから読んだときに全然読めなくなってしまうので、そこは注意する必要はありそうです。また、
interface
やabstract class
のような機能があるので、JavaScriptよりもよりオブジェクト指向的なコードが書きやすくなっています。
特にJavaとかではおなじみのinterface
は似たようなクラスを複数実装していく際に適切に制限をかけていくことができるので、
特にチーム開発するときなんかでは非常に便利ではないかなと思います。React Hooks
React Hooksは本当によくできてて感動しました。
useEffect()
なんかは、あのめんどくさいViewのライフサイクル周りの処理を一つのAPIだけで完結させてしまっていて、「設計した人頭良すぎでは?」ってなってます。
よくVueはeasyでReactはsimpleみたいな話を聞きますが、このReact Hooksを見てると、できるだけsimpleかつJSの標準機能だけで実現しようとしている感じがすごく伝わってきてきます。ここらへんのReact Hooksの設計思想の話は公式サイト(日本語)にとても詳しく書かれているので興味のある人は読んで見るといいと思います。
あと、Custom Hookは無限に遊べます。
こんな感じで色々な機能をもったHookを自作できるので、ぜひ皆さんも作って遊んでみてくださいMobX
MobXは、React用のStoreライブラリの一つで、Reduxに比べて、非常にシンプルな設計になっているのが特徴です。Reduxでは、一つの変数(状態)を変更するにも、actionとreducerを経由して変更を行わなければいけませんでした。これは大規模なアプリやチーム開発では、適切な秩序をもたらすことができるので良いのですが、個人開発のような小規模なアプリでは、正直大げさだなと感じる事が多いです。
MobXではそこらへんの複雑なストア周りの機能がまるっと削られていて、「結局君等がほしいのって、変更検知できるObservableなObjectなんでしょ?」と言わんばかりの簡単な設計になっています。これにより学習コストが低いのはもちろん、Storeが単純なJSのクラスなので、VuexやReduxと違い、なにも考えなくても型が付きます。これはTypeScriptを導入する上で非常に大きなメリットになります。ただし、Reduxのように設計思想を押し付けてくるわけじゃないので、チーム開発で使うならちゃんとルールを決めないと、あっという間にカオスになっていくだろうなという感じはあります。ただそのぶん、ユースケースに合わせてとても柔軟にStoreを設計できるので、設計力のある人が使うとこれ以上ないツールになるんだろうなと思います。
例えば以下は最小設計のStoreです。actionもreducerもなく、あるのは、「変更検知可能なオブジェクト」だけです。
@observable
をつけたメンバが「変更検知可能なメンバ」になり、このメンバを、@action
をつけたメソッド経由で変更すれば、それだけで、このメンバを参照しているコンポーネントの再レンダリングが走ってくれるようになります。import { observable } from "mobx" class CounterStore { @observable count = 0; @action increment() { this.count += 1; } @action decrement() { this.count -= 1; } }使うときは、このStoreのインスタンスを以下のように作っておいて
store.tsimport { CounterStore } from "./CounterStore"; import { configure } from "mobx"; import React, { useContext } from "react"; configure({ enforceActions: "always" }); export class Store { counterStore = new CounterStore(this); } export const store = new Store(); // Storeのインスタンスを作成 export const storeContext = React.createContext(store); // storeのcontextを作成 export const useStore = () => useContext(storeContext); // コンポーネントでstoreを簡単に読み込むためのカスタムフックを作成context経由で下の階層にstoreを渡します。
App.tsximport React from "react"; import "./App.css"; import { Counter } from "./components/Counter"; import { storeContext, store } from "./store/store"; const App: React.FC = () => { return ( <storeContext.Provider value={store}> <div className="App"> <Counter></Counter> </div> </storeContext.Provider> ); }; export default App;で、コンポーネントの中では以下のような感じでストアを使えます。
counter.tsxexport const Counter = observer(() =>{ const { counterStore } = useStore(); return ( <div> カウンター: {counterStore.count} <p> <button onClick={() => counterStore.increment()}> カウントを増やす </button> </p> </div> ) })以下にサンプルプロジェクトを作ったので、興味のある方は見てみてください
https://github.com/kuwabataK/my-mobx-test-prjまた、mobxをReduxやVuexのようなFLUXモデルで扱うことができるmobx-state-treeというライブラリもあるので、Reduxで型付けるのしんどいという方はこちらを試してみるのも良いかもしれません。
シンプルに使いたい人も、ガッツリ使いたい人も満足できるような柔軟性の高さがとても魅力的なライブラリです。
ロジックの話
ロジックは前回と同じで、50万個の盤面を生成し、モンテカルロ法で欠損率を計算しています。
計算はすべてフロントエンドで行っているので、サーバー側はNetlify3でhtmlとjs(とCSS)を配信しているだけです。また、DOMの更新などを行わない単純な計算処理なので、盤面の生成から計算までをすべてWeb Worker(Service Workerじゃないよ)を使ってメインスレッドとは別のスレッドで実行し、メインスレッドをブロッキングしないようにしています。
これによって、計算中に画面がフリーズするようなことが起こらなくなっています。一昔前のスマホなどの貧弱な環境では、計算の終了までに2秒以上かかる場合もあるので、その間画面がフリーズしないというのはとても大事です。devツールを使うと、ページを読み込んだ際に、4つのworkerスレッドが作成されているのがわかるかと思います。
4並列で計算を行っているので、環境によってはメインスレッドだけで処理するよりも高速に動作します。
ただし、JSのWebWorkerは、以下のような問題点があってあまり流行っていません。
- スレッド間の通信時にデータのシリアライズ / デシリアライズが入るので、スレッド間で頻繁にデータをやり取りしたり、巨大なオブジェクトを送ったりすると非常に遅くなってしまう(一応バイナリデータであれば直接参照を渡すこともできるようです)
- WorkerスレッドはDOMに直接アクセスできない
- Workerスレッド自体の作成コストも馬鹿にならない
一応最近はworkerスレッドでcanvasを扱えるようになったらしいので、webGL周りをやる人なら使う機会があるのかもしれません。ちなみにScrapboxさんは、フロントエンドでの検索機能周りでWebWorker使ってるらしいです。
- WebWorkerをproductionで使ってる話今後、こういうWeb Workerを使いたくなる負荷の高い計算処理なんかは、WebAssemblyが担っていくのかもしれません。JSと違って、コンパイル済みのコードをブラウザ上で実行できるようになるので、JSを並列化するよりはパフォーマンスも高くなることが期待できます(とはいえJavaScriptもスクリプト言語としては異常な速さですが)。特に最近は業界のRust熱の高まりもあるので、来年はWebAssemblyを使ったプロダクトも増えていくかもしれませんね。
パフォーマンス
Light houseのスコアも測ってみました。
特に何もしていないですが、全部90点以上になってくれました。Next.jsとNetlifyの優秀さがわかりますね。SEOはなにも考えていないので良いとして、performanceの減点は、workerを生成する処理(正確にはworker.jsをロードする処理)に時間がかかってるせいみたいです。
workerのpreloadができればよいのですが、Webpack環境でこれを行ううまい方法が見つからなかったので諦めています。
まあ初期ロードは遅くなりますが、実行時の体感性能は上がっているので良しとしましょう。
残念ながらLightHouseでは、計算実行時のパフォーマンスを測定してはくれないですが・・・そもそもコードの規模が小さいし、外部API叩いてたり、画像読み込んだりしてるわけじゃないのでパフォーマンスが悪くなりようはないんですが、nextjs + netlifyなら、変なことしない限りは高速なページを作成できるということは言えるんじゃないでしょうか
おわりに
以上、こんなネタ記事に付き合っていただきありがとうございました。
2年連続で、パズ○ラをやったことのある人にしかわからないマイナーネタをぶっこんでしまって申し訳なさしかないです
今回で普段使う機会がないけれど使いたいなと思っていた技術(ReactとかMobxとか)はだいたい使えたので、割と満足しました。(あとはGraphQLとかも触ってみたいですが、バックエンド作るのめんどくさいなと思ってしまってやってないです・・・)私個人はこの一年、業務でもプライベートでもほとんどWebフロントエンドしか触りませんでした。変化の激しいと言われるWebフロントエンドですが、開発標準が変わるような大きな出来事は、観測範囲だとReact Hooksが正式リリースされたぐらい(個人の感想です)なので、だいぶ落ち着いてきたかなと言う印象です。
ただ、来年はVueの3.0も出るし(TypeScriptベースになるらしい!)、JSの機能追加も続々追加されている(Optional Chainingとか)ので、まだまだ色々ありそうです。今後もこの変化を楽しんでやっていければいいなと思います。
では皆さん、来年も良い年になりますよう。m(_ _)m
NextJSはReactベースのフロントエンドフレームワークで、Reactで作られたアプリをSSR(サーバーサイドレンダリング)することができます。Vueに対するNuxtjsのような立ち位置のフレームワークだと思って貰えれば良いかなと思います。 ↩
MobXはReact用のストアライブラリの一つで、フロントエンドの状態管理を行うことができます。同じようなものでは、ReactだとReduxが有名ですし、VueではVuexというライブラリを公式が提供しています ↩
Netlifyは静的なサイトをホスティングしてくれるWebサービスです。GitHubと連携すると、masterブランチにpushするだけで最新版のサイトをデプロイすることができるようになり、かなり便利です。グローバルにCDNが整備されていて、日本からのアクセスも早いです。 ↩
- 投稿日:2019-12-01T02:06:05+09:00
Reactの最強フレームワークGatsby.jsの良さを伝えたい!!
Gatsby.jsって?
この記事はGatsby.js Advent Calendar 2019 1日目の記事です。
Gatsby.jsはReactで作られた静的サイトジェネレーターです。内部的にGraphQLを用いてデータを取得し、markdownからHTMLを生成、などの処理を簡単に行うことができます。
静的サイトジェネレーターが何かと言うと、何かしらの言語で書かれたソースから、静的なHTML/CCC & JavaScriptを生成するツールのことを言います。
今現在どの静的サイトジェネレーターが人気かというのを一覧で見れるStaticGenサイトもあります。
Starの数を見るとあのnuxtよりも多いんですよね、日本で使っている方をあまりみないのが不思議なくらいです。
このサイトには、他にも割とモダンなフレームワークが目白押しなので、ぜひ見てみてくださいね。
さて、以下ではGatsbyのいいところを紹介していきます。
これを見ればあなたもGatsbyが使いたくなること間違いなし!というテンションでいきます。まず簡単なまとめとして、Gatsbyのいい点を列挙し、さらに深掘りする構成で書いていきます。自分が思うGatsbyの素晴らしいポイントは以下です。
- 脳死でサイトが作れる簡単さ & サーバを意識しなくていい!
- 生成されたサイトがSPA & サイトがとにかく早い
- プラグインによって導入が超速 -> めんどくさいことをしなくていい
- 初心者がとっつきやすい
ぜひ節目ごとに「Gatsby最高!」と叫びながら読んでみてください。気持ちが高まります。
叫びたくないですか?では私が代わりに叫びます。Gatsby最高!!!!!
Gatsbyのよいところ
脳死でサイトが作れる簡単さ & サーバ ガン無視!
熟達したReactユーザーのあなたにはこんな経験があるかもしれません(熟達していない方は少しお待ちください)。
「適当にペライチのサイトを作りたいだけなのに、Webpackの設定とかがめんどくさい...URLにハッシュがついて汚いな...SEOも気にしたいけどSSRをしないと...」
create-react-app使えばええやんと思ってる方もいるかもしれませんが、HashRouterに縛られざるを得なかったり、SSRが面倒臭くてしんどかったりと、少し思いつくだけでも無限の苦しみがあります。
というか、create-react-appで謎のバグを踏むことが多くてキレそうになった人は多いんじゃ無いでしょうか。
せっかくSPAでDX(開発体験)よく、気持ちよく開発ができると思ってたのに...モダン技術でもめんどくさいことばかり...そう思った回数も1億回を超えた頃でしょう。
Gatsbyはそんな煩わしさからあなたを解放します。
Gatsbyはなんといっても静的サイトジェネレーターです。なので、一旦SSRという概念を忘れられます。もう「フロントエンド書いてるのになんでサーバのこと考えなかんねん!」みたいな気持ちになることがなくなります。
しかも、ちゃんとホスティングしているサーバには静的なHTMLが置いてあるので、URLを直叩きしようがリロードしようが、404エラーが帰ってくることはなくなります。URLも綺麗!
生のReactやVueだとハッシュを消すために苦心するところですが、Gatsbyならノー設定でこれができます。SEOのためのSSRとかいう面倒臭い作業からも完全解放。しかもサーバに関するルーティング関連のコードを何も書かなくていいシンプルさです。
また、あくまでGatsbyによって生成されるサイトはHTML&CSSを基本とした静的なサイトです。なので、AWS S3やGitHub Pages、Netlifyやfirebaseなどの静的サイトホスティングサービスならどこでも置けます。
これは非常に便利で、サーバ付きのPaaSを用いる必要がなくなり、それによって相対的に設定も少なく簡単にデプロイが出来ます。また、これらの静的サイトホスティングサービスは、一定以内なら無料であるところが多いので非常に経済的です。
私はNetlifyで自分のポートフォリオサイトを運用していますが、GitHubにpushするだけでデプロイが完了し、さらに高速かつ無料という気持ち良さを味わっています。
ではGatsbyの気持ち良さが分かってきたところで、とりあえず叫んでおきましょう。
Gatsby最高!!!!!!!!!!!!!!!!!!!!!
とにかくGatsbyは開発体験が最高すぎるんですよね。
いやほんと、なんでReact書いてんのにexpress書くんじゃみたいな気持ちになることがなくなるので、純粋にフロントエンドのためのコードに集中できる感じが大変良いです。12/2 追記
こちらですが、コメントにてご指摘を頂いた通り、next v9ではルーティングのためにexpressを書く必要が無くなっているようです。
ちょっとnextに浮気したくなってますが気のせいです。また別の話です。
コメントくださった@ykztsさん、ありがとうございます!生成されたサイトがSPA & サイトがとにかく早い
Gatsby.jsは公式サイトに「blazing fast?」と自分で書いちゃうほど早いフレームワークです(blazing fastはめっちゃ早いくらいの認識でよさそう)。
この早さはどこからくるかというと、Gatsbyによって生成されるWebサイトはSPAとして出力されるからと、様々な最適化・コード分割をGatsbyがやってくれるからです。
なんと静的サイトとしてビルドされたGatsbyのサイトはSPAであり、初回ロード以降はロード時間ゼロで画面遷移できます。
さらに、Gatsbyが専用のLinkコンポーネントを用意しており、それを用いると、ページ内に入っているリンクを自動で検出してプリフェッチを行ってくれたりします。必要そうなデータは自動で読み込んでくれるということです。
さらにさらにSSRと同様、配信されるのはHTML/CSS & JavaScriptなので、クライアントサイドでDOMを構築する時間も不要となり、JavaScriptの実行時間を短縮できます。これにより初期表示もはやい!
Wordpressのように動的にページを作成することも無くなるので、サーバサイドのプログラム実行時間とクライアントサイドのプログラム実行時間を両方削減することが出来ます。
しかも、Gatsby製のサイトは簡単にLighthouse(Chromeの拡張機能として提供されているサイトパフォーマンス計測ツール)にて、様々な項目で簡単に100点を取れるほどの性能があります。
つまり、Gatsby製のサイトは簡単に構築でき、さらにパフォーマンスは最高、SEOもバッチリと無敵のスペックを誇ります。
そろそろ叫びたくなってきましたよね?
それではご一緒にGatsby最高!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
プラグインによって導入が超速
Gatsbyにはプラグインという機能が存在しています。これは、npmパッケージとして公開されたGatsby用のプラグイン(拡張機能)を、簡単に導入できるという優れものです。
これによって、非常に楽にMarkdownファイルをブログとして公開するためのソースにできたり、QiitaやCMS(Wordpressなどのコンテンツ管理サービス)の記事を引っ張ってきて自分のブログに移植したり、PWA対応を一瞬で終わらせたりと、高速かつ簡単に様々な機能を追加できます。
Wordpressにもプラグインがあり、その膨大な拡張機能によって様々なことを実現できます。Gatsbyも同様に、様々な機能を自分で開発し、外部に公開することが出来ます。
Wordpressにおけるテーマのような機能も実装されており(そのままズバリthemeと言います)、デザイナーさんが作られた素晴らしいサイトテーマを導入することも出来ます(まだ量は多くはありませんが)。
これらの拡張機能によって、電光石火でサイトを公開することが出来ます。最高かな?
Gatsbyは内部的にGraphQLを使っています。ただ、これはGraphQLを覚えなければいけないかというとそうではなくて、簡単なお作法を覚えるだけである程度はかけてしまいます。
さらに、このGraphQLはビルド時限定で働くので、クライアントサイドでは特に難しいことを考える必要がなく、普通にReactアプリケーションを書くだけで良いです。
また、静的サイトジェネレーターではありますが、ちゃんとaxiosなどを使った、非同期のdata fetchも出来ます。通常のReactアプリケーションとやれることは同等です。
GraphQLを使うと何がいいかというと、これによりデータソースへのインターフェイスを一元化できます。例えばQiitaとはてなブログの記事の構成や形態はかなり違ったものになっているかもしれません。
しかし、その違いをプラグインが吸収して統一することで、開発者はデータソースがなんであるかを意識せずに、ただクエリを書くだけで必要なデータを取得できます。こういった抽象化もちゃんとやっているのがGatsbyの良さだと思います。
では気持ちもノってきたと思うので叫びましょう。
Gatsby最高!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
初心者がとっつきやすい
私は何気にこれが一番大事な気がしています。
例えば初心者の方がcreate-react-appでまともなプロジェクト構成をしようと思うと、なかなかキツイと感じるのではないでしょうか。私は、このReactに対する取っつきづらさに、Webpackやbabelの設定が少なからず関わっていると思います。せっかくReactを勉強しようと思ったのに、謎の設定ファイル的なものに煩わされるのは嫌ですよね。
Gatsbyはこういった面倒くささを、プラグインによって隠蔽してくれます。もちろん複雑なカスタマイズも出来て、やりたい時には新しく設定ファイルを作るだけで良いです。
さらに、Gatsbyにはstarterというものがあり、Gatsbyが用意しているCLIのコマンド叩くだけで、先人が作ったテンプレートがダウンロードでき、すぐにデプロイできます。
初心者の方にとっては、すぐに動くものが作れて、しかも設定が少ない。これは非常に魅力的な点なんじゃないかと思います。
また、GatsbyはRoutingをディレクトリ構成で表現する(pages/home.jsに対してexample.com/home が対応する)ので、ルーティングをわざわざ追加したりといった面倒な作業がありません。
Gatsbyはただの静的サイトジェネレーターというよりは、フロントエンドアプリケーションフレームワークとしての側面も持ち合わせており、ここがReact初心者の方に優しく、とっつきやすさを出している部分だと思います。
なにより、自分が勉強しているフレームワークが、モダンでなおかつパフォーマンスもいいと聞いたら、やる気出ちゃいますよね。
ただ、初心者の方にお勧めできない点もあって、それはやはり日本語リファレンスの少なさかなと思います。何を調べるにしても、基本的には英語の記事を漁ることになるので、ここは日本語の記事が増えてほしいなぁと思うところでもあります。
ただ、Reactをメインで書く上で面倒なエラーを回避出来るという利点と天秤にかけると、そもそもGatsbyで英語の記事を読む必要があるのはGatsbyに関する情報が欲しい時で、Reactに関しては十分日本語の記事があるため、まだまだGatsbyの良さは揺るがないな、と思っています。
ではReact初心者のあなたも叫んでみましょう。
Gatsby最高!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!
!!!!!!!!!!!!!!!!!!!!!!!!!!!まとめ
Gatsbyは(というかそもそもReactが)日本ではそんなに流行っていません。
しかし、Reactを用いた開発には、大規模プロジェクトにおけるコードの保守性や、その開発体験に強い魅力があると思っています。特に初心者やReactに慣れていない方には、Gatsbyの「簡単に・ハイパフォーマンスなサイトをサーバーレスで」出来る能力には、大変な魅力を感じざるを得ません。
本記事ではGatsbyの細かい技術的な部分には触れませんでしたが、ぜひあなたもGatsbyに入門して、自分だけのサイトを作ってみて頂きたいです。
あなたに良きフロントエンド人生が訪れんことを!
最後に叫んでおきます。
Gatsby最高!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- 投稿日:2019-12-01T01:58:47+09:00
React+TypeScriptではじめるp5.jsチュートリアル
この記事ではp5.jsをReactと一緒に使えるようプロジェクトの構築方法をまとめた記事です。記事前半ではTypeScriptやPercelについても言及しているので、p5を使わない場合でも環境構築の参考にしていただけたら嬉しいです。
p5.jsとは
p5.jsとはクリエイティブコーディングで使われるJavaScriptライブラリです。以下のような表現豊かな描写が可能になります。
確かにp5はかっこいいけどp5だけで使いたいわけじゃない
公式チュートリアルのページを覗くと以下のような画像とともにコーディングの初め方が丁寧に解説されています。
上記をご覧いただくと分かるように、既に
setup()
やdraw()
と行った関数が指定されていて、そこに以下のように記述すると描画することができる仕組みとなっています。sketch.jsfunction setup() { } function draw() { ellipse(50, 50, 80, 80); }index.html<html> <head> <script src="https://cdn.jsdelivr.net/npm/p5@0.10.2/lib/p5.js"></script> <script src="sketch.js"></script> </head> <body> </body> </html>ただ、通常のプロジェクトに組み込む場合、htmlやjsファイルを以下のように使うことは稀で、今だとVueやReact等のフレームワークと一緒に使用することが大半かと思います。
そこで今回はReactをベースとしてp5.jsを組み込んだ場合の構築方法をチュートリアル形式でまとめていきます。
1. プロジェクト作成
まずはプロジェクトの雛形を作るために以下のコマンドを入力します。今回は
npm init
ではなくyarn init
を使っていきます。$ brew install yarn $ yarn initまた、Webpackではなく
Percel
を使ってプロジェクトをビルドしていきます。
- Percelとは$ yarn global add parcel-bundler最低限であるReactとp5をプロジェクトに追加します。
$ yarn add @types/p5 @types/react @types/react-domライブラリを追加したので、ここからはファイルの中身を作成していきましょう。
ここで必要になるのは骨格となるファイル3つとTypeScript用の設定ファイル1つで計4つのみです。どれもシンプルな最小限の記述にしています。index.html<html> <head> </head> <body> <div id="root"></div> <script src="./index.tsx"></script> </body> </html>index.tsximport * as React from "react" import { render } from "react-dom" import App from './App' const Root = () => { return <App/> } const rootElement = document.getElementById("root") render(<Root />, rootElement)App/index.tsimport React from 'react' const App = () => { return ( <p>Hello World!</p> ) } export default Apptsconfig.json{ "compilerOptions": { "typeRoots": [ "./node_modules/@types", "./@types" ], "traceResolution": false, "module": "esnext", "target": "es5", "lib": [ "es2015", "es2017", "es6", "es7", "es5", "dom", "scripthost" ], "jsx": "react", "experimentalDecorators": true, "moduleResolution": "node", "baseUrl": "./", "paths": { "~*": ["./*"] }, "noUnusedLocals": true, "noUnusedParameters": true, "esModuleInterop": true, "importHelpers": true, "strict": true, "sourceMap": true, "resolveJsonModule": true }, "include": [ "**/*", ] }ここまででプロジェクトは以下のようになっているはずです。
それでは実行してみましょう。以下のコマンドをプロジェクトのルートから打ち込むと画面(
http://localhost:1234
)に文字が表示されるはずです。$ parcel ./index.html2. p5.js用キャンバスの追加
ここからp5を使って単純なサークルを画面に描いていきます。公式チュートリアルにある関数
ellipse
を使って以下のファイルを追加しましょう。sketch/circle.tsimport p5 from 'p5' const circle = (p: p5) => { p.setup = () => { } p.draw = () => { p.ellipse(50, 50, 80, 80) } } export default circleApp/Canvas.tsximport React, { useEffect } from "react" import p5 from 'p5' const Canvas = (props: any) => { useEffect(() => { new p5(props.sketch) }, [props.sketch]) return (<></>) } export default Canvasここまででプロジェクト構成は以下のようになります。
node_modules
等一部のディレクトリは自動的に追加されているものですので手動での操作は不要です。
index.tsx
は以下のように修正します。index.tsximport React from 'react' import sketch from '~sketch/circle' import Canvas from './Canvas' const App = () => { return ( <> <p>Hello World!</p> <Canvas sketch={sketch} /> </> ) } export default App以下のように画面が更新されていることが確認できるかと思います。
3. Headerの追加
画面にヘッダーを追加して構成を整えます。以下のファイルを追加してください。
App/Header.tsximport React from "react" import { AppBar, Typography, Toolbar, IconButton } from '@material-ui/core' import MenuIcon from '@material-ui/icons/Menu' import { createStyles, makeStyles, Theme } from '@material-ui/core/styles' const useStyles = makeStyles((theme: Theme) => createStyles({ root: { flexGrow: 1, }, menuButton: { marginRight: theme.spacing(2), }, title: { flexGrow: 1, }, }), ); const Header = () => { const classes = useStyles({}) return ( <AppBar> <Toolbar> <IconButton edge="start" className={classes.menuButton} color="inherit" aria-label="menu"> <MenuIcon /> </IconButton> <Typography variant="h6" className={classes.title}> Tutorial </Typography> </Toolbar> </AppBar> ) } export default HeaderHeaderを
App/index.tsx
に追加します。App/index.tsximport React from 'react' import sketch from '~sketch/circle' import Canvas from './Canvas' import Header from './Header' const App = () => { return ( <> <Header/> <Canvas sketch={sketch} /> </> ) } export default Appせっかくなので描画もインタラクティブなものへアップデートしましょう。
sketch/circle.tsimport p5 from 'p5' const circle = (p: p5) => { p.setup = () => { p.createCanvas(640, 480); } p.draw = () => { if (p.mouseIsPressed) { p.fill(0); } else { p.fill(255); } p.ellipse(p.mouseX, p.mouseY, 80, 80) } } export default circleここまでで、ヘッダーを含めたWebアプリケーションの一部としてp5.jsを組み込んできました。
マウスを画面上で動かしてみましょう。以下のような描画が可能になります。
おまけ:3Dグラフィックスの追加
p5.jsを使った3D描画を行うため、以下のファイルを追加して試してみましょう。
sketch/sincos.tsimport p5 from 'p5' const sincos = (p: p5) => { p.setup = () => { p.createCanvas( p.windowWidth, // 画面いっぱいまで拡大 p.windowHeight, // 画面いっぱいまで拡大 p.WEBGL) // WebGL } p.draw = () => { p.background(0) // 背景を黒 for (let i = 0; i < 3; i++) { p.translate(100, 100, 100) p.sphere(20, 10, 5) } } } export default sincos
sphere
は球体を表示します。今回は3つループを回しているので3つの球体が表示されていますが、translate
を使用することでx軸y軸z軸それぞれを少しずつずらしています。3つ目の球体は他に比べて手前に表示されているのが確認できます。次に、フレームごとにX軸を中心とした回転を加えてみましょう。
sketch/sincos.ts... p.draw = () => { p.background(0) for (let i = 0; i < 3; i++) { p.translate(100, 100, 100) p.sphere(20, 10, 5) p.rotateX(p.frameCount * 0.01) // 追加 } } ...1つ前の球体を中心とした軸をもち、X軸に対して回転していることがわかります。
続いてsinとcosを使用した運動の描画です。
translate
で指定した定数を以下のように書きかえてみます。sketch/sincos.ts... p.background(0) for (let i = 0; i < 3; i++) { p.translate( 10 * p.sin(p.frameCount * 0.1 + i), 10 * p.cos(p.frameCount * 0.1 + i), 50) p.sphere(20, 10, 5) } ...EXILEのチューチュートレインはsinとcosによって再現できることがわかります。
最後に、ここまでの要素を組み合わせていきましょう。X軸回転に加えてY軸Z軸にも回転を加えていきます。さらに、よりインパクトのある表現となるようオブジェクトの個数を80まで増やしていきます。色も白から艶やかな色へと変更してみます。
sketch/sincos.ts... import p5 from 'p5' const sincos = (p: p5) => { p.setup = () => { p.createCanvas( p.windowWidth, p.windowHeight, p.WEBGL) p.colorMode(p.HSB, 100) // 色の追加 } p.draw = () => { p.background(0) for (let i = 0; i < 80; i++) { p.fill(50, 55, 100) // 色の追加 p.translate( 10 * p.sin(p.frameCount * 0.1 + i), 10 * p.cos(p.frameCount * 0.1 + i), 50) p.sphere(20, 10, 5) p.rotateX(p.frameCount * 0.01) p.rotateY(p.frameCount * 0.01) p.rotateZ(p.frameCount * 0.01) } } } export default sincos ...さらなる拡張
今回のチュートリアルではプロジェクトの骨格となるファイルを作成していきました。
ヘッダーを追加した要領でサイドバーやポップアップを追加することも可能です。また、sketch
ディレクトリの下にcircle.ts
以外のファイル(例えばtriangle.ts
など)を作成し、画面の描画を動的に変えることも可能です。以下にソースコードを置いておくのでぜひ独自の拡張を加えてみてください。