20191201のReactに関する記事は26件です。

実践 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つの特徴がありました。

  1. Webエンジニアのチーム
  2. アプリはView開発がメイン
  3. ネイティブ機能への強い依存がない

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の開発が進み、状況が変わってきた箇所の一つです。現状での選択肢は大きくは、

  1. Function Component + React 標準API( Hooks, Context API )
  2. 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 などをデザイナー主導で設計していくのか、エンジニア主導で設計していくのかです。

このような議論が起きる理由はコンポーネントが持つ責務にはデザイン以外の要素があるからだと考えています。

image.png

デザイナーとエンジニアで考える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 / コーディングスタイル

検討としては PrettierESLint ( 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環境は CircleCIBitrise でしょうか。キッチハイクではまだ導入していないので、今後の課題となっています。


ここまでReact Nativeの開発体験についての考慮ポイントを上げてきました。
まだ他にも

  • 機能要件
  • マーケティング要件
  • 運用設計

を追記して書こうと思っています。各項目を追加したら通知しますので、もし必要な方はストックしていただけると通知いたします。

最後まで読んでいただいてありがとうございました。

2日目は 1人チーム本実践Expo の著者ハムカツおじさんこと @watanabe_yu さんです。お願いします!

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

コピペで動く、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.tsx
import 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.ts
import { 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.tsx
import 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.tsx
import 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 を一から学ぶという状態では何が一番今風の書き方なのかなかなか分かりませんでした。

もっと調べていくと、さらにブラッシュアップされるのかもしれません。。

参考

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

【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-error.jpg
・・・嘘やん...

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で直った!!
キャッシュをクリアするってことらしい!!よかった~!(参考記事)
create-react-app-success.jpg

ベースとなるプロジェクトが作成できたので、ディレクトリを移動(cd <プロジェクト名>)してnpm startをたたきます!
すると、
execution_cm.jpg
となり、自動でブラウザが開きます。
execution-result.jpg

時間はかかりましたがなんとかReactなるものを動かすことができました!
次回以降、Clickの動作を加えたり、いろいろ試してアウトプットしていきます!!

補足(npmをインストールしていない方へ)

しっかり版
簡易版
npmはNode.jsに付随するものっぽいので、必要なのはNode.jsのインストールかと!
入ってるかわからないときは、とりあえずコマンドプロンプトで
node --version or npm --versionをたたいてみましょう!

参考リンク

新しい React アプリを作る
Create React Appで環境構築 – React入門
create-react-appを使ってReactコンポーネントの素振り、GitHub Pagesへのデプロイまで
Github issue
知っていると捗るcreate-react-appの設定

おわりに

環境構築ってとても難しい気がしていて、そこで挫折しかけることなんてざらにあると思います。
今回扱ったcreate-react-appは環境構築がほとんどいらないので、とりあえず触ってみたい初学者にとっては最高のものだと思います!
是非皆さんも試してみてください!
また、この記事で間違っているところがありましたら、教えていただけると大変助かります!
最後まで見ていただきありがとうございました。

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

短絡評価を用いたReactでのコンポーネント配置

はじめに

いなたつアドカレへようこそ初日です、頑張って一人で25記事書くODL(あうとぷっとどりぶんらーにんぐ)企画、完走したいですね。

自分の中にあるネタを引っ張り出して記事を書いていきます。。。くおりてぃ。。。。

今日は頭にあるとスマート?に実装をかける?やつを忘れないようにここにのこしておきます。

短絡評価とは

andやorで値を評価する時に

A && B // A,B共に真
A || B // A,Bどちらかが真

となるためA && Bの A が偽の場合やA || BのAが真の場合、つまりBの値にかかわらずA && BA || B の評価結果が決定する場合にはBを評価しないといった言語使用です。

主な使用ケースとしては、Bに動作の重い処理を置いて、Aの値で評価が決定してしまい、Bの結果が不要な(重い処理を走らせる必要がない)場合に処理を軽減する

今回はこの性質を利用し、ある条件が真となる場合にのみコンポーネントを配置するといったことを行う。

じっそー

結論から言ってこんな感じ

{i % 2 == 0 && <div>test</div>}

上のプログラムはiが偶数の時に<div>test</div>を配置するといった内容です。

A && Bの A が偽の場合やA || BのAが真の場合、つまりBの値にかかわらずA && BA || B の評価結果が決定する場合にはBを評価しないといった言語使用です。

&&の場合の性質の応用ですね、Aが偽の場合はたとえBが真であっても、結果は偽となるため、Bを評価しないと言った性質なので、iが奇数の場合は右の式が評価されず、<div>test</div>は配置されません。
しかし、iが偶数の場合はAが真なので、Bを評価しないと、式全体の評価ができないため、Bが評価され、<div>test</div>が配置されます。

おわりに

このようにして、とある条件が真の場合のみ、コンポーネントを配置することができます。
実際の実装では、三項演算子を用いて、条件が真の場合と偽の場合で配置するコンポーネントを分けると言った場面が多いかもしれませんが(知らんけど)&&の短絡評価の性質を利用することで、条件が真の場合のみコンポーネントを配置すると言った実装がスマート?にかけます。

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

D言語くんと秘密のレポート

はじめに

D言語くんは好奇心が旺盛なことで知られており、常にTwitterで自身に関するツイートを監視しています。

それこそまさしく365日24時間、監視中の鬼気迫る形相たるやD言語くんの如しです。

また、毎年そのD言語くんアイが捉えていたと思われるツイートを長年のD言語くん研究家である @simd_nyan 氏がまとめてレポートとして公開されています。

このレポートはツイートのIDを列挙したものである、ということまではわかっていますが、とても人間が理解できるものではありません。

そこでD言語くんの御心を理解するため、近年のWeb技術をもってビューアーを作り、そのレポートの真実に迫っていきたいと思います。

(とか言いながら同じことをやってもう3年目になります)

概要

D言語くんが見ていたものが何なのか、理解するために長ったらしい文章など必要ありません。
以下のリンクからビューアーを参照してください。

https://lempiji.github.io/dman-tweet-viewer/

毎度半日くらいでほぼゼロから書いているため、今年も今年で見た目はこんな感じです。

image.png

なお、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のサイトに行ったら日本語で表示できるようになっていた
  • 噂のSuspenseを使いたかったけど、せっかくなら…とuseRefと組み合わせたらイマイチうまく動かず諦めた
    • 紹介記事がどれもグローバル変数に入れているのは何か理由がありそうな気がする
  • useReducer使うように書き換える予定が前日に作業開始したため間に合わず
    • インクリメント/デクリメント周り、どう見てもdispatchで変化量を指定する方が良いですね。

というわけで以上です!

アドベントカレンダーの準備は計画的に、用法容量を守って正しくご参照ください!

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

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を入れる

そのまま。↓を入れる。
image.png

2. ESLintのファイルを生成する

ctrl + shift + P で、ESLint: create ESLint congiguration を選択する。
ESLintの設定ファイル生成のコマンドラインが起動するので、↓を参考に設定。
image.png

ワークスペースのルートに、eslintrc.jsファイルが生成される。

3. eslintrc.jsのベースルールをreact-appへ変更する

eslintrc.jsのextendsを、

'extends': 'eslint:recommended',

↓へ変更する。

'extends': 'react-app',

4. ファイル保存時に自動的にlintルールで整形されるようにする

ctrl + shift + P で、ユーザー設定を開く を選択する。
ユーザワークスペースへ変更する。
image.png
設定の検索に、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'
        ],
    }
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS amplify Reactでログイン画面作成

amplifyとやらが便利そうなので入門として、TODOリストでも作ってみようかなと思った、第一弾です。
次は、Cognito認証付きAPIを作ろうと思います。

0. 環境

$ node -v
v10.16.3
$ npm -v
6.9.0

1. 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 everything

3-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 push

3-2-2. ログイン機能の実装

公式: https://aws-amplify.github.io/docs/js/authentication

SignUp/Inページ自体は、以下をのコードをApp.jsに追加するだけでできます。
ログイン後、先ほどのデフォルトページが表示さるようになります。

App.js
import 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分程度でログイン画面ができてしまいます。便利な世の中になりましたね。
今回は以上です。

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

React + Redux + TypeScript でモダンなwebアプリを作るチュートリアルを考えてみた①

:star: はじめに

  • フロントエンドで利用されているフレームワークでReactがjQueryを抜いて1位になったというアンケート結果が公表されました

Q. 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ユーザーが増えて欲しい・盛り上がって欲しいという願いも込めて、実践的なチュートリアルっぽいものを作ってみました

:star: 作るもの

  • Google Books のAPIを使って本の検索アプリを作る

:star: 主な技術・ライブラリなど

  • React (Hooks)
  • Redux (Hooks)
  • TypeScript
  • styled-components
  • react-router
  • immutable.js
  • redux-saga
  • axios
  • eslint
  • semantic-ui
  • yarn

:star: 手順

:pencil: yarn のインストール

  • パッケージマネージャーについて、 npm がデフォルトで入ってますが yarnの方が高速で安定性があるので yarn を使用しています
  • https://yarnpkg.com/lang/ja/docs/install/
terminal
brew install yarn

:pencil: create react-appでアプリの基盤を作成

  • react-tutorial という名前のアプリを作成します
  • --typescript のオプションをつけることでTypeScriptでアプリを作成できます
  • https://create-react-app.dev/docs/getting-started/
  • nodeのversionが古いとエラーになるので、その場合は nodebrew などで新しいversionにしておきましょう
terminal
yarn create react-app react-tutorial --typescript
  • これだけでアプリを動かす環境が整いました

:pencil: アプリを起動する

  • 作成したアプリのディレクトリに移動して yarn start で起動します
  • 他のスクリプトについても自動で生成された README に説明があるので確認しておきましょう
terminal
cd react-tutorial
yarn start

localhost_3000_(Laptop with HiDPI screen).png

:pencil: eslint でコードの書き方を統一する

  • 先に eslint でコードの静的チェックをすることで不用意なエラーや書き方のズレを防ぐようにしておくと便利です
  • TypeScript用の tslint もありますが、今後は eslint に統合されていくようなので eslint だけ使用しています
  • package.jsondevDependencies に以下を追記し、 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"
  },

  ...

}

.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
}

:star: スタイルを styled-components で記述する

  • styled-components を導入することで、class名でスタイルのマッピングをすることを辞め、コンポーネントに直感的にスタイルを適用することができるようになります
  • https://www.styled-components.com/
  • ライブラリに型定義ファイルが提供されている場合、 @types/XXX をinstallできます
  • 型定義ファイルのように開発環境のみで使用するライブラリは devDependencies に記述するため、 -D オプションが必要です
terminal
yarn add styled-components
yarn add -D @types/styled-components
App.tsx
import 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;

:star: 新しいルーティングにコンポーネントを追加する

  • react-router を使用することでURLによって表示するコンポーネントを切り替えるルーティングを実装することができます
  • react-routerreact-router-dom に含まれているので、 react-router-dom のみ追加すれば大丈夫です
yarn add react-router-dom
yarn add -D @types/react-router-dom
  • 先に新しいルーティングで表示する仮のOtameshiコンポーネントを作成しておきます
Otameshi.tsx
import 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.tsx
import 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.tsx
import 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.tsx
import 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;
`;

...

:star: React Hooksで状態管理を行う

  • React Hooksを使用するとFunctional Componentでもstate(状態)を管理できるようになります
  • 試しに、入力したテキストを受け取って表示させてみましょう
  • useState を使用する場合は const [state名, stateを変更する関数名] = useState(初期値); のように定義します
  • テキストエリアに入力された時に onChange イベントが発火し、入力されたvalueを changeText に渡して更新しています
  • 公式の説明
Otameshi.tsx
import 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;
`;

localhost_3000_otameshi(Laptop with MDPI screen) (1).png

:star: Layoutコンポーネントを作成する

  • HeaderやFoooterなど複数のページで表示したい共通コンポーネントがある場合、別々に呼び出すのは面倒です
  • そこで、react-routerの仕組みを使って複数のコンポーネントに共通コンポーネントを当てることができます
  • ここでは全てのコンポーネントの枠組みとなるLayoutコンポーネントを作成してみましょう
    • Globalスタイルなどもここに移植しましょう
    • childrenはコンポーネントの子要素が渡ってきます
Layout.tsx
import 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.tsx
import 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が表示されます

localhost_3000_otameshi(Laptop with HiDPI screen).png

localhost_3000_otameshi(Laptop with HiDPI screen) (1).png

  • 今回はここまでです!

今後の予定

  • reduxの導入
  • redux hooksでの状態管理
  • redux-sagaでの非同期処理
  • axiosでの通信処理
  • immutable.jsでデータのモデル化
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.ts
import { 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.ts
import { 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;


Dec-01-2019 15-21-28_c.gif

🥰

State

ちょっとしたTipsですが、State全体のType定義はReturnTypeを使うことでサボれます。

store/index.ts
import { 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.tsx
import 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.ts
import { 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など、単純な状態管理にとどまらない部分も依然として魅力的なライブラリです。

これから改めて学ぶのも全然アリだと思います!

参考

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

カフェの注文でいつも焦るので、Reactアプリを作って解決した

アプリ概要

スタバやドトールなどの主要カフェチェーンのドリンク・フードメニューを店ごとに一覧できるアプリを作りました。

商品名と各サイズの値段が表示され、行をタップすれば公式の詳細ページに飛びます。

なぜ作ったか

いわゆる「喫茶店」だと席についてからゆっくりとメニューを見られますが、スタバなんかだとレジの目の前で即断しないといけないこともあります。

後ろに人が並んでるし、目の前には店員さんもいる・・・。

この状況ではメニューをくまなく見れないし、結局前と同じ無難な注文をしがちです。

並んでいる最中にゆっくりと吟味できたらいいのにと思ったので作りました。

URL

アプリはこちら

リポジトリはこちら

技術

Untitled Diagram.png

すべて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サービスなんていくらでもデッチ上げられますが、多くの人に使ってもらえるようなプロダクトは稀です。

このアプリは「ショボくても、ダサくても、確実に誰かのニーズを満たせること」を目指してアイデアを練った結果生まれました。

ただ、どれだけ考えても確実なアイデアなんて出ないのはしょうがないと思います。

とにかくフットワークを軽くして、小さな検証を積み重ねていくつもりです。

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

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では使えなくなる可能性が高いことがあります。ユーザの利用シーンをヒアリングしつつ今後の方針を決めていきたいと思います。

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

デザインからReact Native, React のコードを生成するツール「BuilderX」を使ってみた

この記事は React Native Advent Calendar 2019の4日目の記事です。

はじめに

こんにちは、React Native でアプリ開発をしている@ariiyu です。
最近、React Native 向けのデザインツールに BuilderX というものがあると知りました。
UIデザインの作業をするだけでReact コンポーネントのコードも出来上がるツールということで、上手く使うことができればフロントエンドのデザインや実装が捗りそうです。
BuilderX についての情報はまだあまり見当たらないので、今回調べて少し使ってみました。

BuilderX とは

https://builderx.io

BuilderX は、React Native や React のコードを生成するブラウザベースのデザインツールです。
デザイナーと開発者が同じソースファイルを触ることを目指したものであり、デザインをするのと同時に、美しく、可読性が高く、編集可能なコードを生み出すものであると謳っています。
価格設定はFree, Personal, Team の三段階で、30日間は無料で試用することができます。
将来的にFlutter にも対応するようです。

image.png

image.png

誰が開発・提供しているのか

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デザインツールと同様に、要素をドラッグアンドドロップしたり、インスペクタで値を調整したりしてデザインを作成することができます。

image.png

画面右上の CODEDESIGN とを変更することによって、コードの表示とデザインのインスペクタを切り替えます。

image.png

ここで Download Component を押すと、コードをファイル形式でダウンロードすることができます。コードの取得は、画面単位と個々のコンポーネント単位でそれぞれ可能です。例えば、あるボタンコンポーネントのコードだけ取得するということが出来ます。

カードやボタンなど独自のコンポーネントが用意されていることや、オープンソースのアイコンをGUI で選んでデザインが作成出来るのは、プロトタイプを作るのに便利そうだなと思いました。

image.png

ネイティブ開発のSwiftUI のようにエディタ上でコードを編集することはできませんが、大きな問題ではないでしょう。React Native の場合は実際のアプリに組み込んだ後にコードを直せばよいです (開発時のリロードが速いので) 。

気になった点としては、コンポーネント同士の階層構造の変更など、GUI の操作がやや分かりづらい印象を受けました。もしかしたら、様々なデザインツールを使ってきた方なら難なく使えるかもしれません。
また、不具合と思われるような挙動もありました。デザインのプレビューとコードの内容が合っていないなど...

今回は自分でUIデザインを作成するのではなく、Sketch ファイルをインポートすることにしました。

Sketch ファイルを変換してBuilderX で扱えるようにする

今回は既に用意されているデザインを利用してみました。macOS のSketch アプリのメニューから New from Template -> Prototyping Tutorial を選んで、Sketch のファイルを作成しました。

image.png

そして、作成したSketch ファイルをBuilderX にインポートしました。

image.png

ここでBuilderX の CODE を見ると、React Native のコードが表示されました!プレビューに表示されている画面の内容が、全てコードとして出力されています。

アイコンなど、Sketch の内容がそのままBuilderX 上に反映されない箇所がありました。フォントについてはBuilderX 上の警告に従って設定をすれば適用できそうです。

生成されるコード

この画面のコードは700行ちょっとになりました。スタイル部分のコードが500行くらい。
結構、それっぽいコードが生成されるという印象を受けました。コードはGistに上げておきました。
Gist: Sketch Prototyping Tutorial to BuilderX

そしてこのコードを自分で新しく作成したReact Native のプロジェクトに組み込んでみたところ、実際のアプリとして起動することができました!

image.png

それっぽいのですが、BuilderX 上のプレビューともまた違う見た目になっています。。

今回のコードはそのままで使うには無理がありました。少なくとも以下については自分でコードを書き直すことになりそうです。
(もしかしたらデザイン時の設定によって解決できるのかもしれませんが、未検証です)

  • コンポーネント同士の階層構造: ヘッダがメインコンテンツの層に入ってしまっている
  • 制約によるレイアウト: width などに固定値が指定されており、多解像度対応が考慮されていない
  • アイコンが違うとかサムネイルのレイアウトが違うとか

コンポーネントの大まかな構造に関しては結構いい感じにコードに出力してくれていて、UI実装の3割くらいの作業はやってくれたという感覚でした。コード量が多くなる画面ほどコード自動生成のメリットが大きくなるかもしれません。
もちろんリストなどの動的な処理が必要な箇所については後で自分でコードを書くことになりますが、静的なコンポーネントのコードをある程度書いてくれるという点で、BuilderX はいい仕事をしてくれそうな予感がしました。

BuilderX を導入すべきか?

数時間触ってみての所感です。
BuilderX は意図通りの挙動をしてくれない箇所がいくつもあって、いま普段使いするにはちょっと厳しいなという印象を受けました。
ただし、以下のような用途であれば試す価値があるかもしれません。

  • サポートする端末の解像度を1種類に限定して、あくまでプロトタイピングのツールとして使う
  • コーディング量が多くなりそうなコンポーネントの構造だけを作る (基本的にはエンジニアが実装をする前提で、大枠だけコードを自動生成する)

自分が使いこなせていないだけという可能性は十分ありますし、不具合や使いにくい箇所については今後改善されるはずなので、もうちょっとBuilderX を使ってみたいなと思いました。

まとめ

  • BuilderX はReact Native やReact のコードを生成してくれるデザインツール
  • そのままプロダクションで使えるコードではないが、それっぽいコードは出力してくれる
  • UIデザイン・開発を楽にするツールとしてもう少し使ってみたい

さいごに

5日目は「おかんPAY」でPdM兼エンジニアをされている @knsg16 さんです!お願いします。

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

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> exit

name : やることを保存
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 --save

body-parserを使用することで、データの取得が容易になります。
後ほど使用します。

まずは、定型文的な処理を書いていきます。

index.js
const 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.js
app.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.js
app.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.js
app.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.js
app.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形式でデータが返ってきます。

スクリーンショット 2019-12-01 18.04.44.png

いい感じですね。

終わり

これでとりあえずAPIは終了です。
昨日Nodeの勉強を始めたばかりなので、書き方が間違えている部分はお許しください(笑)

今後はAPIをHerokuにデプロイして、それを使用してReactでTODOリストの作成をします。
強強エンジニアの方、ぜひ間違えている部分を個人的にでもコメントにでも良いので教えていただきたいです。

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

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.js
const 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番目がオススメ。ただ、他にオススメがあれば教えてください。

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

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 に対してどこからでもアクセスできるようになりました!
おそらくこの記事を呼んでいる方なら、 connectmapStateToProps, mapDispatchToProps を使ったことがあると思いますが、こういったfunctionやargumentを使うことで
自由にどこからでも global-stateへアクセス出来るようになりました。

使うためにはいくつか覚えなければいけなくて、それが複雑性を発生させていたり、管理を行うために actions, reducers, store を覚えていく必要がありました。
reduxの詳細説明は本家へ https://redux.js.org/introduction/getting-started

ContextAPIの登場

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.html

react.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が出来るよーってことに触れていきたいと思います。
useContextuseReducer

実際に使ってカウンターアプリを作ります!

今回作るものはボタンでプラスマイナスが出来ることと現在のカウントを表示するコンポーネントです!
以下がファイル構成

-src
|- App.jsx
|- index.jsx
|- Components
 |- Counter
  |- index.jsx
 |- Display
  |- index.jsx
|- store
 |- index.jsx

やること

  1. state管理の要、Storeの作成(useReducer & createContext)
  2. Providerをindexにおいてアプリ全体に反映させる
  3. Access用のComponentの作成(useContext)
  4. Update用のComponentの作成(useContext, dispatch)
  5. 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;

完成品

GIF.gif

できた!!!

感想

結局Reduxから抜けたからといってあの面倒な actions&reducers&storeから抜け出せたという感覚は否めなかった。

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

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/Dockerfile
FROM 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 . /rails

docker-composeはこの記事が非常にわかりやすいです
docker-compose.ymlの書き方について解説してみた

docker-compose 解説
version docker-composeの文法はバージョンごとにことなるので指定が必要
servise 動かすアプリケーションの指定。ここでは、webとdb。
Service設定する際の項目について
docker-compose.yml
version: '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.rb
Rails.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 update

webpack設定

$ 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 //コンテナ起動

こうなったら成功です!おつかれさまでした!
スクリーンショット 2019-11-30 13.58.04.png

参考

Rails で postgresql を使う(インストールからマイグレーションまで)

Docker初心者がRails + PostgreSQL or MySQLで仮想環境構築した手順を丁寧にまとめる
既存のRailsアプリにReactを導入する方法

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

JSの文法すら怪しいフロント初心者がblog用のgatsby starter(っぽいもの)を自作・公開してみた

最近ホットだけど日本での知名度が若干低いプロジェクトにgatsbyがあります。
ご存知でしょうか?
2019年のOctoverseを見ると、Fastest growing open source projects by contributorsの第8位にgatsbyjs/gatsbyがあります。
今回はそんなgatsbyのstarter(wordpressのテーマみたいなの)を自作していきます。

今回の目的

デザインに専念できる環境を作成していきましょう。
私自身のデザイナーとしての能力が低くて現時点でいいデザインを作ることは難しいですが、だれでもカスタマイズできる状態で公開するところまでこぎつけます。

デザインに集中できるだけの基本的な後ろ側を作れることをゴールとします。

コンセプトとデザインを仮決める

iPhone X-XS-11 Pro – 1.png

こんな感じのブログにしましょう。
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ファイルを作成し、実際に試してみます。

191201-0.PNG

まあ、こんなものかな?

公開

githubにpushしてリポジトリを公開状態にすれば終わりです。

実際に使えるか試してみましょう。

gatsby new project-name https://github.com/aimof/gatsby-starter-hinoiri

demoページ作るほどの価値がこのstarterにまだない気がしたので、デモはなし。
しっかりしたstarterを作れれば公式で紹介してもらえる可能性もあるようです。

まとめ

gatsby触ってみましたが、フロント面白いです。
デザインについては並行して学習中です。

なぜかgatsbyの知名度が日本では若干低い気がするので盛り上がっていくとよいですね

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

React Nativeでライブラリを使わずに枠線だけの吹き出しを作る

Fringe81アドベントカレンダー1日目の記事です。

弊社の提供しているUniposというサービスではWeb版のほか、Android/iOS向けにReact Nativeを使ってアプリを開発しています。
今回はそのアプリの新機能開発において、「枠線だけの吹き出し」を作る必要が出たためやってみた、という軽めの記事でございます。

「枠線だけの吹き出し」というのはこんなやつです。

スクリーンショット 2019-11-28 9.38.48.png

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つ用意し、外側の線部分と、内側の塗りの部分をずらして重ねています。(balloonTriangleOuterballoonTriangleInnerがそれ)


また、くちばしの部分は絶対位置指定(position: 'absolute')で配置しているため、このままだと上に配置されたコンポーネントがくちばしの上に重なってしまうのを防止するため、 <View style={{justifyContent: 'flex-end', paddingTop: BALLOON_TRIANGLE_HEIGHT}}> というコンポーネントでラップすることでCommentBalloonコンポーネント全体で見たときにくちばしを含んだ高さとなるように調整しています。

Simulator Screen Shot - iPhone 11 Pro Max - 2019-12-01 at 11.41.34.png

所感

React NativeのstyleがWebのCSSと同じ挙動になるように実装されているおかげもあり、こうやってWebでよく用いられるテクニックを転用して、さほど悩むことなく画面を作れるのは幸せなことだと改めて思いました。

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

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-blog

2. How to Start

Hwo to Startにあるコマンドをコマンドプロンプトやターミナルに貼り付ける

git clone https://github.com/GraphCMS/graphcms-examples.git && cd graphcms-examples/current/react-apollo-blog && yarn && yarn start

graphcms-sample.PNG

3. 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を作りエンドポイントを変更する

  1. gitからダウンロードしたプロジェクトフォルダの中のsrcフォルダのAbout.jsの中に書かれているクエリを見る
  2. GraphCMSでAbout.jsに書かれているクエリに書かれているクエリの内容に合わせてスキーマを作る。
  3. Author name bibliography avatar で作成
  4. Post slug title dataAndTime coverImage で作成
  5. SettingsのEndpointをindex.jsのGRAPHCMS_APIに代入します。
  6. Public API PermissionsのScoopeをPROTECTEDからQUERYに変更します。
  7. localhost:3000で確認する

5. PhoenixfreamworkでGraphQLをセットする方法

1. sampleBlogという名前のプロジェクトを生成する

mix phx.new sampleBlog --no-html --no-webpack
mix ecto.create

2. Ectoを利用して、新しいPhoenixアプリケーションのPostgreSQLに接続する為のコンテキストを生成します。
GraphCMSで作成したモデルと同じものを作成しますが、今回は画像を追加は省きたいと思いますのでavatarは無くします。

cd sampleBlog
mix phx.gen.context Blog Author authors name:string bibliography:string

3. authorsのデータベーステーブルを作成します。

mix ecto.migrate

4. クエリを実行する為にデータベースに幾つかのデータが必要なのでデータベースに読み込むデータを準備します。
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.exs

6. Absintheを使用してGraphQLのアプリをセットアップします。

defp deps do
 [
   # existing dependencies

   {:absinthe, "~> 1.4.16"},
   {:absinthe_plug, "~> 1.4.0"},
   {:absinthe_phoenix, "~> 1.4.0"}
 ]
end
mix deps.get

7.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
  end

8.リゾルバモジュールファイルを作成します。 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
end

9.スキーマとリゾルバの準備をしたら、ルーターを設定します。

sampleBlog\lib\sampleBlog_web\router.ex
defmodule 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にアクセスして以下の画面が出てきたら作成準備完了です。

キャプチャ.PNG

次回は、これをGraphCMSのサンプルで作ったReactのフロントへ接続するものをポエムとして書きます。

手順をチュートリアル的に書く予定は24日の記事に上げるので本日はこの辺で。

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

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-blog

2. How to Start

Hwo to Start にあるコマンドをコマンドプロンプトやターミナルに貼り付ける

git clone https://github.com/GraphCMS/graphcms-examples.git && cd graphcms-examples/current/react-apollo-blog && yarn && yarn start

graphcms-sample.PNG

3. 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 を作りエンドポイントを変更する

  1. git からダウンロードしたプロジェクトフォルダの中の src フォルダの About.js の中に書かれているクエリを見る
  2. GraphCMS で About.js に書かれているクエリに書かれているクエリの内容に合わせてスキーマを作る。
  3. Author name bibliography avatar で作成
  4. Post slug title dataAndTime coverImage で作成
  5. Settings の Endpoint を index.js の GRAPHCMS_API に代入します。
  6. Public API Permissions の Scoope を PROTECTED から QUERY に変更します。
  7. localhost:3000 で確認する

5. Phoenix Framework で GraphQL をセットする方法

1. sampleBlog という名前のプロジェクトを生成する

mix phx.new sampleBlog --no-html --no-webpack
mix ecto.create

2. Ecto を利用して、新しい Phoenix アプリケーションからデータベース(PostgreSQL)に接続する為のコンテキストを生成します。
GraphCMS で作成したモデルと同じものを作成しますが、今回は画像を追加は省きたいと思いますので avatar は無くします。

cd sampleBlog
mix phx.gen.context Blog Author authors name:string bibliography:string

3. authors のデータベーステーブルを作成します。

mix ecto.migrate

4. クエリを実行する為にデータベースに幾つかのデータが必要なのでデータベースに読み込むデータを準備します。
priv/repo/seeds.exsに以下を書き込みます。

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.exs

6. Absinthe を使用して GraphQL のアプリをセットアップします。

mix.exs
defp deps do
 [
   # existing dependencies

   {:absinthe, "~> 1.4.16"},
   {:absinthe_plug, "~> 1.4.0"},
   {:absinthe_phoenix, "~> 1.4.0"}
 ]
end
mix deps.get

7.sampleBlog_web に schema フォルダを作成し、schema.ex ファイルを作成して、ファイルを作成します。

lib/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
  end

8.リゾルバモジュールファイルを作成します。 sampleBlog_web に resolvers フォルダを作成し、blog.exファイルを作成して、ファイルを作成します。

lib/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
end

9.スキーマとリゾルバの準備をしたら、ルーターを設定します。

sampleBlog/lib/sampleBlog_web/router.ex
defmodule 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にアクセスして以下の画面が出てきたら作成準備完了です。

キャプチャ.PNG

次回は、これを GraphCMS のサンプルで作った React のフロントへ接続するものをポエムとして書きます。

手順をチュートリアル的に書く予定は24日の記事に上げるので本日はこの辺で。

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

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-thunkredux-sagaという二大巨頭があります。

しかし、これらって、複雑だと感じないでしょうか?
ただAPIを叩くPromiseを生成して、thenで結果を受け取りたいだけなのに(もしくはasync/awaitしたいだけなのに)、Reduxと組み合わせるために結局またボイラープレートが増えてしまいます。また、データを表示するComponentから離れた場所にロジックが置かれ、処理の流れをぱっと見で把握できなくなります。コードジャンプ無しでは読むのも辛いです。

それ、stateful component/useState hookでよくないですか?

ReduxのサンプルでTodoアプリが紹介されていますが、これを見ても牛刀をもって鶏を割いているようにしか見えません。
Todoアプリなら、プレーンなReactのstateで作れます(React.Componentを使うならthis.state。最近はuseStatehookという選択肢もあります)

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ライブラリ、formikoverviewページでは、まさにこの点が指摘されています

Why not Redux-Form?

By now, you might be thinking, "Why didn't you just use Redux-Form?" Good question.

  1. 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
  2. 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を使う意味はほぼ無いでしょう。

npm trends: redux-form vs formik
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です。

banner
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.tsx
import 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.tsx
import 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.tsx
import 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.ts
import '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には触れませんでした。そちらについても、「使うメリットがあれば使う、そうでなければ使わない」にしかなりません。ただ、結局、なるべくシンプルに済ませられないか考え続けるのは大事かなと思います。

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

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-thunkredux-sagaという二大巨頭があります。

しかし、これらって、複雑だと感じないでしょうか?
ただAPIを叩くPromiseを生成して、thenで結果を受け取りたいだけなのに(もしくはasync/awaitしたいだけなのに)、Reduxと組み合わせるために結局またボイラープレートが増えてしまいます。また、データを表示するComponentから離れた場所にロジックが置かれ、処理の流れをぱっと見で把握できなくなります。コードジャンプ無しでは読むのも辛いです。

それ、stateful component/useState hookでよくないですか?

ReduxのサンプルでTodoアプリが紹介されていますが、これを見ても牛刀をもって鶏を割いているようにしか見えません。
Todoアプリなら、プレーンなReactのstateで作れます(React.Componentを使うならthis.state。最近はuseStatehookという選択肢もあります)

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ライブラリ、formikoverviewページでは、まさにこの点が指摘されています

Why not Redux-Form?

By now, you might be thinking, "Why didn't you just use Redux-Form?" Good question.

  1. 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
  2. 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を使う意味はほぼ無いでしょう。

npm trends: redux-form vs formik
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です。

banner
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.tsx
import 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.tsx
import 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.tsx
import 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.ts
import '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には触れませんでした。そちらについても、「使うメリットがあれば使う、そうでなければ使わない」にしかなりません。ただ、結局、なるべくシンプルに済ませられないか考え続けるのは大事かなと思います。

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

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-thunkredux-sagaという二大巨頭があります。

しかし、これらって、複雑だと感じないでしょうか?
ただAPIを叩くPromiseを生成して、thenで結果を受け取りたいだけなのに(もしくはasync/awaitしたいだけなのに)、Reduxと組み合わせるために結局またボイラープレートが増えてしまいます。また、データを表示するComponentから離れた場所にロジックが置かれ、処理の流れをぱっと見で把握できなくなります。コードジャンプ無しでは読むのも辛いです。

それ、stateful component/useState hookでよくないですか?

ReduxのサンプルでTodoアプリが紹介されていますが、これを見ても牛刀をもって鶏を割いているようにしか見えません。
Todoアプリなら、プレーンなReactのstateで作れます(React.Componentを使うならthis.state。最近はuseStatehookという選択肢もあります)

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ライブラリ、formikoverviewページでは、まさにこの点が指摘されています

Why not Redux-Form?

By now, you might be thinking, "Why didn't you just use Redux-Form?" Good question.

  1. 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
  2. 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を使う意味はほぼ無いでしょう。

npm trends: redux-form vs formik
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です。

banner
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.tsx
import 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.tsx
import 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.tsx
import 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.ts
import '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には触れませんでした。そちらについても、「使うメリットがあれば使う、そうでなければ使わない」にしかなりません。ただ、結局、なるべくシンプルに済ませられないか考え続けるのは大事かなと思います。

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

パ○ドラの盤面欠損率を計算するWebアプリを作った話(nextjs + TypeScript + Mobx)

アドベントカレンダーの一発目がこんなネタ記事でとても申し訳ないです。

去年のアドベントカレンダーでパズ○ラの盤面欠損率を計算するスクリプトを高速化する話を書きました。

今回は、そのときに使ったロジックを使って、「パズドラの盤面欠損率を計算するWebアプリ」を作ってみた話です。

DEMO

以下のURLから使えます。PCスマホ両対応です
https://youthful-cray-84efcb.netlify.com/pazdora-cal

また、ソースコードは例によってGitHubにあげています。

使い方

ここからはこのアプリの使い方を説明します。パズドラに興味のない人、よく知らない人は 内部の話まで飛ばすと良いかなと思います。

Webアプリ上で盤面の条件を定義するカードを作成し、その条件を満たす盤面がどの程度の確率で存在するかを計算します。
条件カードには以下の3種類が存在します。

  • ドロップ条件
  • コンボ条件
  • 多色条件

まずドロップ条件の使い方から説明します。

ドロップ条件

画面の「+ドロップ」ボタンを押すと、ドロップ条件を追加できます。ドロップ条件の中で設定できる値は以下です。
* ドロップの色: 条件に合致する色を選択します
* ドロップの個数/条件: 選択した色のドロップが何個(以上/以下)あればよいかを選択します

例えば、以下のリーダースキルを見てみてください

ファイル名

火と水の同時攻撃で攻撃力4倍と書かれていますね。
この条件を満たす盤面の出現率を計算すると以下のようになります。

image.png

こんな感じで複数カードを設定すると、それぞれの条件のアンドを取って計算します。

指定2色が存在する確率は83%ぐらいですね
5~6回に一回は欠損する計算です。意外と欠損するかも?

コンボ条件

お次はコンボ条件です。「+コンボ」のボタンを押すと追加できます。これは比較的わかりやすいんじゃないでしょうか?以下の項目を設定できます

  • 消せるドロップ数: 一コンボするのに必要なドロップ数を設定します。普通は3個です
  • コンボ数/条件: 条件を満たすコンボ数を設定します
  • 繋げるドロップがある場合は繋げるドロップを一種類だけ指定できます

image.png

56盤面で7コンボある確率を計算してみました。これをみると、ほとんどの場合で7コンボはありますね。
つまり7コンボできないのは甘えです。
7コンボ強化の覚醒スキルが如何に強いかがわかります。

繋げるドロップ条件についても見ていきましょう。
例えば、「回復を5個繋いで、なおかつ7コンボ以上ある確率」を計算したいとします。
一見、先程紹介したドロップ条件とコンボ条件の組み合わせで表現できるような気がしますが、実際には、コンボ条件は他のカードの条件とは独立して計算されてしまうため、例えば回復が盤面に6個しかない場合、本来であれば5個繋げるので1コンボ分とカウントしたいのですが、2分割して2コンボ分とカウントしてしまいます。

この問題を回避するために、コンボ条件の中で繋げるドロップを指定できるようにしています。

例えば追い打ち7コンボ以上ある確率は以下のようになります。

image.png

比較のため、ドロップ条件と組み合わせた場合の確率も計算してみます。
比べてみると、前者のほうが少し確率が下がっている事がわかりますね。

image.png

多色条件

最後は多色条件です。これが一番ややこしいです。が、設定できる幅は一番広くて、1つ目のドロップ条件の上位互換みたいな設定ができます。
順番に説明していきましょう

  • 選択する色: 多色条件に含まれる色を複数選択できます。
  • ドロップの種類: 上の選択した色の中から、N種類以上条件を満たしているかを設定します
  • ドロップの個数: これはドロップ条件と同じです。

言葉で説明してもわからないと思うので例を出します。以下のようなリーダースキルを考えてみましょう。

image.png

このリーダースキルを満たす盤面の出現率は以下になります。

image.png

7コンボ以上という条件はコンボ条件のときと同じですね。
注目すべきは一枚目のカードです。「4色以上同時攻撃」というのは正確には
「「火水木光闇」の5種類のドロップのうち、4種類以上の色が3個以上存在する」
という条件になります。これを多色条件のカードで表すと画像のようになります。

ちなみに、5色中4色存在する確率はこんな感じでかなり高いです。追い打ちとか無効貫通とか考えず、欠損率だけ考えるならかなり優秀なリーダースキルですね

もう一つ例を出しましょう。以下のようなリーダースキルを考えます。

image.png

かなりややこしそうですね。しかしこれも計算できます。

image.png

多色条件で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.ts
import { 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.tsx
import 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.tsx
export 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スレッドが作成されているのがわかるかと思います。

tempsnip.png

4並列で計算を行っているので、環境によってはメインスレッドだけで処理するよりも高速に動作します。

ただし、JSのWebWorkerは、以下のような問題点があってあまり流行っていません。

  • スレッド間の通信時にデータのシリアライズ / デシリアライズが入るので、スレッド間で頻繁にデータをやり取りしたり、巨大なオブジェクトを送ったりすると非常に遅くなってしまう(一応バイナリデータであれば直接参照を渡すこともできるようです)
  • WorkerスレッドはDOMに直接アクセスできない
  • Workerスレッド自体の作成コストも馬鹿にならない

一応最近はworkerスレッドでcanvasを扱えるようになったらしいので、webGL周りをやる人なら使う機会があるのかもしれません。ちなみにScrapboxさんは、フロントエンドでの検索機能周りでWebWorker使ってるらしいです。
- WebWorkerをproductionで使ってる話

今後、こういうWeb Workerを使いたくなる負荷の高い計算処理なんかは、WebAssemblyが担っていくのかもしれません。JSと違って、コンパイル済みのコードをブラウザ上で実行できるようになるので、JSを並列化するよりはパフォーマンスも高くなることが期待できます(とはいえJavaScriptもスクリプト言語としては異常な速さですが)。特に最近は業界のRust熱の高まりもあるので、来年はWebAssemblyを使ったプロダクトも増えていくかもしれませんね。

パフォーマンス

Light houseのスコアも測ってみました。

image.png

特に何もしていないですが、全部90点以上になってくれました。Next.jsとNetlifyの優秀さがわかりますね。SEOはなにも考えていないので良いとして、performanceの減点は、workerを生成する処理(正確にはworker.jsをロードする処理)に時間がかかってるせいみたいです。
workerのpreloadができればよいのですが、Webpack環境でこれを行ううまい方法が見つからなかったので諦めています。
まあ初期ロードは遅くなりますが、実行時の体感性能は上がっているので良しとしましょう。
残念ながらLightHouseでは、計算実行時のパフォーマンスを測定してはくれないですが・・・

image.png

そもそもコードの規模が小さいし、外部API叩いてたり、画像読み込んだりしてるわけじゃないのでパフォーマンスが悪くなりようはないんですが、nextjs + netlifyなら、変なことしない限りは高速なページを作成できるということは言えるんじゃないでしょうか

おわりに

以上、こんなネタ記事に付き合っていただきありがとうございました。
2年連続で、パズ○ラをやったことのある人にしかわからないマイナーネタをぶっこんでしまって申し訳なさしかないです
今回で普段使う機会がないけれど使いたいなと思っていた技術(ReactとかMobxとか)はだいたい使えたので、割と満足しました。(あとはGraphQLとかも触ってみたいですが、バックエンド作るのめんどくさいなと思ってしまってやってないです・・・)

私個人はこの一年、業務でもプライベートでもほとんどWebフロントエンドしか触りませんでした。変化の激しいと言われるWebフロントエンドですが、開発標準が変わるような大きな出来事は、観測範囲だとReact Hooksが正式リリースされたぐらい(個人の感想です)なので、だいぶ落ち着いてきたかなと言う印象です。

ただ、来年はVueの3.0も出るし(TypeScriptベースになるらしい!)、JSの機能追加も続々追加されている(Optional Chainingとか)ので、まだまだ色々ありそうです。今後もこの変化を楽しんでやっていければいいなと思います。

では皆さん、来年も良い年になりますよう。m(_ _)m


  1. NextJSはReactベースのフロントエンドフレームワークで、Reactで作られたアプリをSSR(サーバーサイドレンダリング)することができます。Vueに対するNuxtjsのような立ち位置のフレームワークだと思って貰えれば良いかなと思います。 

  2. MobXはReact用のストアライブラリの一つで、フロントエンドの状態管理を行うことができます。同じようなものでは、ReactだとReduxが有名ですし、VueではVuexというライブラリを公式が提供しています 

  3. Netlifyは静的なサイトをホスティングしてくれるWebサービスです。GitHubと連携すると、masterブランチにpushするだけで最新版のサイトをデプロイすることができるようになり、かなり便利です。グローバルにCDNが整備されていて、日本からのアクセスも早いです。 

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

Reactの最強フレームワークGatsby.jsの良さを伝えたい!!

Gatsby.jsって?

この記事はGatsby.js Advent Calendar 2019 1日目の記事です。

Gatsby.jsReactで作られた静的サイトジェネレーターです。内部的にGraphQLを用いてデータを取得し、markdownからHTMLを生成、などの処理を簡単に行うことができます。

ダウンロード.png

静的サイトジェネレーターが何かと言うと、何かしらの言語で書かれたソースから、静的なHTML/CCC & JavaScriptを生成するツールのことを言います。

今現在どの静的サイトジェネレーターが人気かというのを一覧で見れるStaticGenサイトもあります。

スクリーンショット 2019-11-30 23.33.43.png

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最高!!!!!!!!!!!!!!!!!!!!!!!!!!!!

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

React+TypeScriptではじめるp5.jsチュートリアル

この記事ではp5.jsをReactと一緒に使えるようプロジェクトの構築方法をまとめた記事です。記事前半ではTypeScriptやPercelについても言及しているので、p5を使わない場合でも環境構築の参考にしていただけたら嬉しいです。

p5.jsとは

p5.jsとはクリエイティブコーディングで使われるJavaScriptライブラリです。以下のような表現豊かな描写が可能になります。

Untitled_ Dec 1 2019 1_04 AM - Edited.gif

確かにp5はかっこいいけどp5だけで使いたいわけじゃない

公式チュートリアルのページを覗くと以下のような画像とともにコーディングの初め方が丁寧に解説されています。

image.png

上記をご覧いただくと分かるように、既にsetup()draw()と行った関数が指定されていて、そこに以下のように記述すると描画することができる仕組みとなっています。

sketch.js
function 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を使ってプロジェクトをビルドしていきます。
- :pencil: 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.tsx
import * 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.ts
import React from 'react'

const App = () => {
    return (
        <p>Hello World!</p>
    )
}
export default App
tsconfig.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": [
        "**/*",
    ]
}

ここまででプロジェクトは以下のようになっているはずです。

image.png

それでは実行してみましょう。以下のコマンドをプロジェクトのルートから打ち込むと画面(http://localhost:1234)に文字が表示されるはずです。

$ parcel ./index.html

image.png

2. p5.js用キャンバスの追加

ここからp5を使って単純なサークルを画面に描いていきます。公式チュートリアルにある関数ellipseを使って以下のファイルを追加しましょう。

sketch/circle.ts
import p5 from 'p5'

const circle = (p: p5) => {
    p.setup = () => {
    }
    p.draw = () => {
        p.ellipse(50, 50, 80, 80)
    }
}
export default circle
App/Canvas.tsx
import 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等一部のディレクトリは自動的に追加されているものですので手動での操作は不要です。

image.png

index.tsxは以下のように修正します。

index.tsx
import 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

以下のように画面が更新されていることが確認できるかと思います。

Screen Shot 2019-11-28 at 18.18.59.png

3. Headerの追加

画面にヘッダーを追加して構成を整えます。以下のファイルを追加してください。

App/Header.tsx
import 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 Header

HeaderをApp/index.tsxに追加します。

App/index.tsx
import 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.ts
import 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を組み込んできました。

マウスを画面上で動かしてみましょう。以下のような描画が可能になります。

image.png

おまけ:3Dグラフィックスの追加

p5.jsを使った3D描画を行うため、以下のファイルを追加して試してみましょう。

sketch/sincos.ts
import 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つ目の球体は他に比べて手前に表示されているのが確認できます。

Screen Shot 2019-12-01 at 1.15.48.png

次に、フレームごとに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軸に対して回転していることがわかります。

Untitled_ Dec 1 2019 1_27 AM - Edited.gif

続いて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によって再現できることがわかります。

Untitled_ Dec 1 2019 1_35 AM - Edited.gif

最後に、ここまでの要素を組み合わせていきましょう。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
...

Untitled_ Dec 1 2019 1_43 AM - Edited.gif

さらなる拡張

今回のチュートリアルではプロジェクトの骨格となるファイルを作成していきました。
ヘッダーを追加した要領でサイドバーやポップアップを追加することも可能です。また、sketchディレクトリの下にcircle.ts以外のファイル(例えばtriangle.tsなど)を作成し、画面の描画を動的に変えることも可能です。以下にソースコードを置いておくのでぜひ独自の拡張を加えてみてください。

https://github.com/shunp/p5_react

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