- 投稿日:2020-09-28T20:23:19+09:00
サクッとi18n対応を行うためのReact hooksライブラリ、use-mini18nを作った。
現在、私はいくつかのアプリを個人で開発しています。
それらのほとんどはReactを用いたWebアプリです。アプリはどれも小さなものですが、世界中の人に使ってもらいたいと考えているため、i18n対応はなるべく行うようにしています。
(といっても、なかなか時間が確保できず、英語と日本語だけしか対応できていないケースばかりですが...)一例を挙げると、ランダムな音楽を勧めてくれる、下記のようなシンプルなWebアプリを作っています。
Dig Music - Dig Musicはレコメンドエンジンを使用せず、完全にランダムな形でユーザに新しい音楽を紹介するWebアプリです。Reactでi18n対応をする場合、react-i18nextやreact-intl (FormatJSと呼ばれるモノレポに含まれる形に変わったようですね)を使うことが多いと思います。
私もこれらを使用したことがありますが、どちらも素晴らしいライブラリだと感じています。ですが、私が作成するアプリは非常に小規模なものが多いため、これらのライブラリがオーバースペックに感じることもあります。
設定された言語設定に応じて、適切な言語ファイルを読み込み、表示できればそれでいいんだよな...と思いましたが、少し調べてみたところ、そのようなライブラリはなさそうに思えたので、自分で作ることにしました。
TypeScriptを用いてReact hooksライブラリを作成してみたかったという別の理由もあります。シンプルでこじんまりとしたi18nライブラリ、use-mini18n
こちらが作ったものになります。
use-mini18n - A simple and minimalistic React hook library for i18n
ロゴも思い切って作りました。勢い大事。
こちらのライブラリでやれることはシンプルです。
以下のコードサンプルは、このライブラリがやれることをすべて表しています。
(実際に動作しているところをCodeSandbox上で確認できます)import React from "react"; import ReactDOM from "react-dom"; import { TransProvider, useI18n } from "use-mini18n"; import "./styles.css"; const i18n = { en: { Hello: "Hello {name}", "Start editing to see some magic happen!": "Start editing to see some magic happen!" }, ja: { Hello: "こんにちは {name}", "Start editing to see some magic happen!": "いくつかの魔法を目にするために編集を開始します!" } }; const App = () => { const { t, lang, langs, changeLang, getText } = useI18n(); return ( <div className="App"> <h1>{getText("Hello", { name: "CodeSandbox" })}</h1> <h2>{t["Start editing to see some magic happen!"]}</h2> <hr /> <p>Selected lang: {lang}</p> <select name="lang" onChange={({ target }) => changeLang(target.value)} value={lang} > {langs.map((l, i) => ( <option value={l} key={i}> {l} </option> ))} </select> </div> ); }; const rootElement = document.getElementById("root"); ReactDOM.render( <React.StrictMode> <TransProvider i18n={i18n}> <App /> </TransProvider> </React.StrictMode>, rootElement );開発者が
use-mini18n
でやれることは、このサンプルコードに記載されていることだけです。
いくつかの細かなオプションも用意してあるので、そちらについてはリポジトリ内のREADME、もしくはexamplesを参照してください。また、
use-mini18n
はデフォルトで、ユーザの希望した言語設定をブラウザのlocalStorage
に保存します。
私がこのような設計にしたのは、開発者として言語設定の管理に一切時間を割きたくなかったからです。
もしそのような動作を望まない場合は、localStorage
を利用しないオプションも存在しています。<TransProvider i18n={i18n} enableLocalStorage={false}> <App /> </TransProvider>これで
use-mini18n
に関する話は終わりとなります。
use-mini18n
のミニマムな世界観には満足しています。このライブラリはi18n関連のすべてをカバーしているわけではありませんが、それはそれで良いと考えています。
もしuse-mini18n
が力不足だと感じるときがあれば、そのときは他の素晴らしいi18nライブラリに切り替えるときだと考えています。もしバグや疑問点などありましたら、上に貼ったGitHubリポジトリか、こちらのポストに直接コメントを頂けたらと思います。
というわけで、サクッとi18n対応を行うためのReact hooksライブラリを作ってみた、という内容でした。
最後まで読んでいただき、ありがとうございます!
- 投稿日:2020-09-28T17:55:43+09:00
ReactHooksの学習記録!
はじめに
2020年2月に正式にリリースされたReactHooksについて、学習をしましたので、アウトプットに当記事を投稿します!
この記事を読めば以下のことがわかります!
・クラスと違いがわかる
・ReactHooksの便利さ
・具体的な記述方法当記事の読者へ、少しでも参考になればとても嬉しいです
解説におかしな点がございましたら、是非ご指摘ください
まずフックとは何か?!!
まずフック(hook)とは何か?
公式のドキュメントを確認すると以下の様に書いております。state などの React の機能を、クラスを書かずに使えるようになります。
もっと言うのであれば、
フック(hook)を使用すれば、クラス機能(stateやライフサイクル)をFunctional Componentでも使える様になるということです!補足:
Class Component
➡︎クラスによって定義されたコンポーネント
Functional Component
➡︎関数型で定義されたコンポーネントなぜフック(hook)を使用するのか
結論から、シンプルさを保つためです。
例えば、個人的ですがclass Componentでは以下の様な部分が難しいと感じることがあります。
・thisを把握する
これは、他者が書いた記述を見るときにthisがどこを指しているのか把握しづらいという点です。
旧Reactでクclass Componentを記述する場合、thisをどうしても使用しなければならなくなって来ます。
例えば、以下の様な記述があった場合で考えてみます。import React from 'react'; Class Article extends React.Component{ constructor(props){ super(props); } render(){ return( <div> <h2>{this.props.title}</h2> </div> ); } }今回の場合ですと、thisはpropsの中から取手しているという意味になりますが、クラスコンポーネントを使用した場合この様な書き方をしなくてはならなくなります。
近年では、thisをなるべく使用しないというのがトレンドとしてもある様で、
Functional Componentを使用するとthisを使用しなくすむため、他の言語との挙動が混在することがなくなります!Functional Componentの場合の記述
import React from 'react'; const Article = (props) => { return( <div> <h2>{props.title}</h2> </div> ); };・stateのロジックが複雑になりやすい
例えば、複数stateが同時に動いていて、各stateがそれぞれのstateに作用してしまうと状況の把握が難しくなり、混乱しやすくなります。
そう言った意味でも、フック(hook)を使用することでシンプルなコードが保てる様になします!
これまでとの変化
今までのReactは、基本的にClass Componentじゃないとstateやライフサイクルが扱えないものでした。
しかし、フック(hook)を使用すれば、先ほど述べた通り、Functional Componentでもstateやライフサイクル使用できる様になりました。
実際に見てみよう!
コードは、Reactのドキュメントから引用してきました!
import React, { useState } from 'react'; import './App.css'; function App() { const [count, setCount] = useState(0); return ( <div className="App"> <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> </div> ); } export default App;では、見ていきましょう!
ReactHooksの概念!
ReactHooksをもう少し深ぼって説明すると、Functional Componentにもstateを持たせよう!です。
しかし、そもそもこれまでのReactはclass Componentにしかstateを持たせることができませんでした。
それを、Functional Componentにも持たせられる様にと開発されたのがReactHooksです。どこがReactHooksの記述なのか
正解は下記述です!
import React, { useState } from 'react';上記の記述は、{ useState }がReactHooksの概念にあたる部分です。
記述の意味は、"useState関数をインポート"です。const [count, setCount] = useState(0);こちらは、普段のFunctional Componentと同様に関数を宣言します。
ポイントは、右辺のuseState()の値で関数を宣言している点です。
もう少し記述を詳しく見てみると記述は以下の様な意味を持っています。const [stateの変数, stateの変更関数名] = useState(stateの初期値);となっています。
最終的に!
ここで定義した、stateの変更関数名をJSX内でアロー関数を用いて呼び出します!
<button onClick={() => setCount(count + 1)}> Click me </button>以上がReactHooksの概念になります!
補足
ここは、個人的にポイントだと感じので書きます!
公式ドキュメントを確認すると以下の様な記載があります。100% 後方互換です。フックには破壊的な変更は一切含まれていません。
つまり、ReactHooksと旧Reactの記述では、それぞれが独立しているため、例えば旧Reactの記述方法で開発を進めていて、途中でReactHooksを導入しても旧Reactの記述には影響は与えないということです。
旧Reactの記述方法で開発を進めていたとして、途中ReactHooksを使用したとしても問題これまでの記述には悪影響を出さないという事の様ですね!!最後に
Reactを勉強をはじめ一ヶ月半経ち、簡単なポートフォリオも完成したのでこれまで学んできた事をアウトプットしました!
もし、記事に間違っている点がありました遠慮なくご指摘ください
皆さんのお役に立てれば幸いです!
- 投稿日:2020-09-28T17:55:43+09:00
ReactHooksについてまとめた!
はじめに
2020年2月に正式にリリースされたReactHooksについて、学習をしましたので、アウトプットに当記事を投稿します!
この記事を読めば以下のことがわかります!
・クラスと違いがわかる
・ReactHooksの便利さ
・具体的な記述方法当記事の読者へ、少しでも参考になればとても嬉しいです
解説におかしな点がございましたら、是非ご指摘ください
まずフックとは何か?!!
まずフック(hook)とは何か?
公式のドキュメントを確認すると以下の様に書いております。state などの React の機能を、クラスを書かずに使えるようになります。
もっと言うのであれば、
フック(hook)を使用すれば、クラス機能(stateやライフサイクル)をFunctional Componentでも使える様になるということです!補足:
Class Component
➡︎クラスによって定義されたコンポーネント
Functional Component
➡︎関数型で定義されたコンポーネントなぜフック(hook)を使用するのか
結論から、シンプルさを保つためです。
例えば、個人的ですがclass Componentでは以下の様な部分が難しいと感じることがあります。
・thisを把握する
これは、他者が書いた記述を見るときにthisがどこを指しているのか把握しづらいという点です。
旧Reactでクclass Componentを記述する場合、thisをどうしても使用しなければならなくなって来ます。
例えば、以下の様な記述があった場合で考えてみます。import React from 'react'; Class Article extends React.Component{ constructor(props){ super(props); } render(){ return( <div> <h2>{this.props.title}</h2> </div> ); } }今回の場合ですと、thisはpropsの中から取手しているという意味になりますが、クラスコンポーネントを使用した場合この様な書き方をしなくてはならなくなります。
近年では、thisをなるべく使用しないというのがトレンドとしてもある様で、
Functional Componentを使用するとthisを使用しなくすむため、他の言語との挙動が混在することがなくなります!Functional Componentの場合の記述
import React from 'react'; const Article = (props) => { return( <div> <h2>{props.title}</h2> </div> ); };・stateのロジックが複雑になりやすい
例えば、複数stateが同時に動いていて、各stateがそれぞれのstateに作用してしまうと状況の把握が難しくなり、混乱しやすくなります。
そう言った意味でも、フック(hook)を使用することでシンプルなコードが保てる様になします!
これまでとの変化
今までのReactは、基本的にClass Componentじゃないとstateやライフサイクルが扱えないものでした。
しかし、フック(hook)を使用すれば、先ほど述べた通り、Functional Componentでもstateやライフサイクル使用できる様になりました。
実際に見てみよう!
コードは、Reactのドキュメントから引用してきました!
import React, { useState } from 'react'; import './App.css'; function App() { const [count, setCount] = useState(0); return ( <div className="App"> <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> </div> ); } export default App;では、見ていきましょう!
ReactHooksの概念!
ReactHooksをもう少し深ぼって説明すると、Functional Componentにもstateを持たせよう!です。
しかし、そもそもこれまでのReactはclass Componentにしかstateを持たせることができませんでした。
それを、Functional Componentにも持たせられる様にと開発されたのがReactHooksです。どこがReactHooksの記述なのか
正解は下記述です!
import React, { useState } from 'react';上記の記述は、{ useState }がReactHooksの概念にあたる部分です。
記述の意味は、"useState関数をインポート"です。const [count, setCount] = useState(0);こちらは、普段のFunctional Componentと同様に関数を宣言します。
ポイントは、右辺のuseState()の値で関数を宣言している点です。
もう少し記述を詳しく見てみると記述は以下の様な意味を持っています。const [stateの変数, stateの変更関数名] = useState(stateの初期値);となっています。
最終的に!
ここで定義した、stateの変更関数名をJSX内でアロー関数を用いて呼び出します!
<button onClick={() => setCount(count + 1)}> Click me </button>以上がReactHooksの概念になります!
補足
ここは、個人的にポイントだと感じので書きます!
公式ドキュメントを確認すると以下の様な記載があります。100% 後方互換です。フックには破壊的な変更は一切含まれていません。
つまり、ReactHooksと旧Reactの記述では、それぞれが独立しているため、例えば旧Reactの記述方法で開発を進めていて、途中でReactHooksを導入しても旧Reactの記述には影響は与えないということです。
旧Reactの記述方法で開発を進めていたとして、途中ReactHooksを使用したとしても問題これまでの記述には悪影響を出さないという事の様ですね!!最後に
Reactを勉強をはじめ一ヶ月半経ち、簡単なポートフォリオも完成したのでこれまで学んできた事をアウトプットしました!
もし、記事に間違っている点がありました遠慮なくご指摘ください
皆さんのお役に立てれば幸いです!
- 投稿日:2020-09-28T17:55:43+09:00
すぐわかる!ReactHooksとは何か?!
はじめに
2020年2月に正式にリリースされたReactHooksについて、学習をしましたので、アウトプットに当記事を投稿します!
この記事を読めば以下のことがわかります!
・クラスと違いがわかる
・ReactHooksの便利さ
・具体的な記述方法当記事の読者へ、少しでも参考になればとても嬉しいです
解説におかしな点がございましたら、是非ご指摘ください
まずフックとは何か?!!
まずフック(hook)とは何か?
公式のドキュメントを確認すると以下の様に書いております。state などの React の機能を、クラスを書かずに使えるようになります。
もっと言うのであれば、
フック(hook)を使用すれば、クラス機能(stateやライフサイクル)をFunctional Componentでも使える様になるということです!補足:
Class Component
➡︎クラスによって定義されたコンポーネント
Functional Component
➡︎関数型で定義されたコンポーネントなぜフック(hook)を使用するのか
結論から、シンプルさを保つためです。
例えば、個人的ですがclass Componentでは以下の様な部分が難しいと感じることがあります。
・thisを把握する
これは、他者が書いた記述を見るときにthisがどこを指しているのか把握しづらいという点です。
旧Reactでクclass Componentを記述する場合、thisをどうしても使用しなければならなくなって来ます。
例えば、以下の様な記述があった場合で考えてみます。import React from 'react'; ============================== Class Article extends React.Component{ constructor(props){ super(props); } render(){ return( <div> <h2>{props.title}</h2> </div> ); } }今回の場合ですと、thisはpropsの中から取手しているという意味になりますが、クラスコンポーネントを使用した場合この様な書き方をしなくてはならなくなります。
近年では、thisをなるべく使用しないというのがトレンドとしてもある様で、
Functional Componentを使用するとthisを使用しなくすむため、他の言語との挙動が混在することがなくなります!Functional Componentの場合の記述
import React from 'react'; ============================== const Article = (props) => { return( <div> <h2>{props.title}</h2> </div> ); };・stateのロジックが複雑になりやすい
例えば、複数stateが同時に動いていて、各stateがそれぞれのstateに作用してしまうと状況の把握が難しくなり、混乱しやすくなります。
そう言った意味でも、フック(hook)を使用することでシンプルなコードが保てる様になします!
これまでとの変化
今までのReactは、基本的にClass Componentじゃないとstateやライフサイクルが扱えないものでした。
しかし、フック(hook)を使用すれば、先ほど述べた通り、Functional Componentでもstateやライフサイクル使用できる様になりました。
実際に見てみよう!
コードは、Reactのドキュメントから引用してきました!
import React, { useState } from 'react'; import './App.css'; function App() { const [count, setCount] = useState(0); return ( <div className="App"> <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> </div> ); } export default App;では、見ていきましょう!
ReactHooksの概念!
ReactHooksをもう少し深ぼって説明すると、Functional Componentにもstateを持たせよう!です。
しかし、そもそもこれまでのReactはclass Componentにしかstateを持たせることができませんでした。
それを、Functional Componentにも持たせられる様にと開発されたのがReactHooksです。どこがReactHooksの記述なのか
正解は下記述です!
import React, { useState } from 'react';上記の記述は、{ useState }がReactHooksの概念にあたる部分です。
記述の意味は、"useState関数をインポート"です。const [count, setCount] = useState(0);こちらは、普段のFunctional Componentと同様に関数を宣言します。
ポイントは、右辺のuseState()の値で関数を宣言している点です。
もう少し記述を詳しく見てみると記述は以下の様な意味を持っています。const [stateの変数, stateの変更関数名] = useState(stateの初期値);となっています。
最終的に!
ここで定義した、stateの変更関数名をJSX内でアロー関数を用いて呼び出します!
<button onClick={() => setCount(count + 1)}> Click me </button>以上がReactHooksの概念になります!
補足
ここは、個人的にポイントだと感じので書きます!
公式ドキュメントを確認すると以下の様な記載があります。100% 後方互換です。フックには破壊的な変更は一切含まれていません。
つまり、ReactHooksと旧Reactの記述では、それぞれが独立しているため、例えば旧Reactの記述方法で開発を進めていて、途中でReactHooksを導入しても旧Reactの記述には影響は与えないということです。
旧Reactの記述方法で開発を進めていたとして、途中ReactHooksを使用したとしても問題これまでの記述には悪影響を出さないという事の様ですね!!最後に
Reactを勉強をはじめ一ヶ月半経ち、簡単なポートフォリオも完成したのでこれまで学んできた事をアウトプットしました!
もし、記事に間違っている点がありました遠慮なくご指摘ください
皆さんのお役に立てれば幸いです!
- 投稿日:2020-09-28T07:29:53+09:00
もう複雑なState管理や非同期処理に悩まない。そう、Trelaならね?
この記事について
この記事は、筆者が現在製作しているフレームワークについて解説した記事です。
紹介するフレームワークは
JavaScript
で使う事が出来ますが、TypeScript
を使う事で最大のメリットを受けれるフレームワークであるため、この記事で紹介するソースコードはTypeScript
を用いて解説します。そのため、JavaScript
の知識に加えてTypeScript
の理解もあるとより理解できます。概要について知ることが出来る。そう、Trelaならね。
Trelaは、現在開発している非同期処理管理フレームワークです。
「 非同期処理をシンプルにする 」ことを目標に開発しており、非同期処理をより便利に扱えるようなAPIを提供するとともに、新しいパラダイムへの緩衝材として機能するように設計しています。※ 現在は
React Hooks
に依存しているため、React.js専用フレームワークとなっていますが、Trelaが持つ概念自体は他のフレームワークでも適用できると思いますので、後々は他のフレームワークに展開したいと思っています。※この記事ではReact.jsのソースコードとともに説明したいと思います。
インストールとリポジトリ
まだ開発途中ではありますが、ある程度の機能は実装してnpmjs.comで配信していますので、以下の方法でインストールして使うことが出来ます。
npmでインストール$> npm install --save trelaまたは、
yarnでインストール$> yarn add trela
リポジトリ
OSSとしての開発を目指していますので、以下のURLから実際のソースコードを見ることが出来ます。
※ 開発途中なので、まだ粗削りな所があったりします。
Trelaの特徴
Trelaには以下の特徴があります。
- 記述が少なく、シンプルで分かりやすい( 宣言的・同期的に書ける )
- 複雑な非同期処理もシンプルに書ける
- コンポーネントの描画をシンプルに最適化する
- Store管理もシンプルに出来る
- TypeScriptフレンドリー
今はまだ上記の特徴について理解できないと思いますが、この記事を読んでいくと上記の意味が分かるようになると思います。
また、「 TypeScriptフレンドリー 」はTrelaの恩恵を最大限に受けるための特徴です。
この特徴はTypeScriptの特性を生かした書き方をすることによって、素晴らしい開発者体験をあなたに与えることが出来ます。私はその書き方を「 TS Hack 」と勝手に呼んでいますが、この記事ではこの「 TS Hack 」についても解説します。※ 注意として「 TS Hack 」はあくまで開発者体験を上げるだけなので、処理的なメリットはありません。あくまでも、書き方のテクニックの一つです。
なぜ非同期処理?
多くの人にとって状態管理が必要な場面とは、通信などの非同期処理が発生する場合です。
同期的な処理では、状態管理をするほどの複雑なフラグ管理なんてほとんどしませんし、同期的な処理はReactのContext APIなどのようなAPIで事足ります。
そのため、これからの状態管理フレームワークに必要なのは、「 非同期処理によって発生する状態を管理することであり、非同期処理をシンプルにすることである 」と考え、Trelaを非同期処理に特化したモノにしました。
また、ただ通信結果をキャッシュするだけでは、今後のフロントエンドで発生する問題は解決できないだろうと考え、描画の最適化とStore管理のアプローチなどもTrelaでは実装しています。
非同期処理をシンプルに書ける。そう、Trelaならね。
Trelaは「 非同期処理をシンプルにする 」事を目標に開発しているため、非同期処理をとてもシンプルかつ分かりやすく記述することが出来ます。
それを理解するために、「 Trelaを使ってない場合 」と「 Trelaを使っている場合 」を比較しながら見ていきましょう。
まずは、「 Trelaを使ってない場合 」の例からです。
Trelaを使ってない場合
Trelaを使ってない例import React, { useState, useEffect } from "react"; interface User { id: number; name: string; icon: string; } const fetchUser = async (uid: number): Promise<User> => { const ref = await fetch("https://example.com/api/user/" + uid); const json = await ref.json(); return json as User; }; /** * @description User情報をロードして、カードとして表示するコンポーネント */ const UserCard: React.FC<{ uid: number }> = ({ uid }) => { const [isLoading, setLoadingStatus] = useState(false); const [user, setUser] = useState<User>({ id: uid, name: "", icon: "default icon path", }); useEffect(() => { setLoadingStatus(true); // ローディングを開始 fetchUser(uid).then((result:User) => { setUser(result); // 通信結果をViewに反映 setLoadingStatus(false); // ローディングを終了 }); }, []); if (isLoading) return <div className="loading-bar">通信中です</div>; // 通信結果をUserCardとして表示 return ( <div className="user-card"> <div className="user-icon"> <img src={user.icon} alt="user icon" /> </div> <div className="user-name">{user.name}</div> </div> ); }; /* -- 以下、UserCardコンポーネントを使ったソースコード -- */上記のソースコードでもシンプルですが、以下の問題点が挙げられます。
上記のソースコードの問題点
- コールバックを使っていて、処理が見にくい( 同期的でない )
useState Hooks
を使って、自分でState管理する必要がある( 宣言的でない )useEffect Hooks
の罠にハマりやすい( 値の参照など )- コンポーネントがマウントされるたびに、非同期処理が実行される
- キャッシュを設けることで回避可能
- 安全にアンマウント( コンポーネントの削除 )が出来ない
- 非同期処理が終わる前にアンマウントした場合、非同期処理が完了後setStateしてしまう
また
fetchUser関数
は、Promise
を使っているのでasync/await
を使いたいところですが、useEffect Hooks
でやるには少々面倒くさいやり方をする必要があります。async/awaitで書くと以下のようになるuseEffect(() => { (async () => { setLoadingStatus(true); // ローディング開始 const result:User = awiat fetchUser(uid); // ここで非同期処理( 同期的に書けているのがポイント! ) setUser(result); // 通信結果をViewに反映 setLoadingStatus(false); // ローディング終了 })(); }, []);上記のように
async/await
を使う事で、同期的に書けるようにはなりましたが、あまり見やすいとは言えませんね。
これを踏まえた上で、「 Trelaを使った場合 」を見ていきましょう。Trelaを使った場合
trelaを使った例import React from "react"; import { createTrelaContext } from "trela"; interface User { id: number; name: string; icon: string; } const fetchUser = async (uid: number): Promise<User> => { const ref = await fetch("https://example.com/api/user/" + uid); const json = await ref.json(); return json as User; }; // ProvierコンポーネントとuseTrela関数を取得 const { Provider, useTrela } = createTrelaContext({ apis: { fetchUser } }); /** * @description User情報をロードして、カードとして表示するコンポーネント */ const UserCard: React.FC<{ uid: number }> = ({ uid }) => { const { apis } = useTrela(); // 非同期処理のRefを取得( まだ非同期処理はやってない ) // default関数は初期値を設定する関数 const ref = apis.fetchUser(uid).default({ id: uid, name: "", icon: "default icon path", }) const [user, isLoading] = ref.read(); // ここで非同期処理をしている if (isLoading) return <div className="loading-bar">通信中です</div>; // 通信結果をUserCardとして表示 return ( <div className="user-card"> <div className="user-icon"> <img src={user.icon} alt="user icon" /> </div> <div className="user-name">{user.name}</div> </div> ); }; /* -- 以下、UserCardとProviderコンポーネントを使ったソースコード -- */上記が、Trelaを使ったソースコードになります。
ここで注目すべき点は、
fetchUser関数
を実行するのにコールバックやPromiseと言った要素が出てきていません。そのため、ソースコードを宣言的・同期的に書けています。これは
useEffect Hooks
で実装した時よりも、明らかに分かりやすく・シンプルです。また、
useState関数
などのReact Hooks
を使っていません。それもそのはずです。
非同期処理に伴う状態管理もTrelaが勝手にやってくれているからです。これにより、わざわざuseState関数
を使って自前でState管理しなくてもよくなるので、安全にアンマウントすることが出来ます。勿論、
useEffect Hooks
も使っていないので、実行タイミングの罠にはまる事もありません。Trelaを使えば、コールバック地獄や
async/await
地獄に悩まされることなく、非同期処理の結果や処理状態を受け取ることが出来ます。もう複雑な非同期処理に悩まない。そう、Trelaならね。
Trelaを使えば、非同期処理をシンプルに書ける事は分かりましたが、非同期処理には厄介なモノがあります。
それは、直列処理と並列処理です。
これら2つは
async/await
を使えばシンプルに書けますが、描画処理が絡むコンポーネント内ではasync/await
で書くことが難しく、また書けたとしても複数のState管理に悩むことは想像に難くありません。Trelaでは、勿論それらの問題を解決できます。例を見ていきましょう。
※ 直列処理と並列処理の例では、全てのコードを書くとすごく長くなってしまうため、重要な部分のみ記述します。ご了承ください。
直列処理 : Trelaを使ってない場合
直列処理をTrelaを使わず書いた例const UserCard: React.FC<{ uid: number }> = ({ uid }) => { const [isLoading, setLoadingStatus] = useState(false); const [resultData, setResult] = useState({ isLogin: false, user: defaultUserValue }); useEffect(() => { (async () => { setLoadingStatus(true); // ローディング開始 const isLogin = await fetchLoginStatus(uid); // ログイン状態を取得 const user = await fetchUser(uid); // User情報を取得 setResult({ isLogin, user }); // 通信結果をViewに反映 setLoadingStatus(false); // ローディング終了 })(); }, []) /* -- 以下、描画処理 -- */ }上記の例では、
fetchLoginStatus関数
とfetchUser関数
の二つを直列に実行しています。
useEffect Hooks
内でasync/await
を使ってシンプルにしていますが、コールバックとかの影響で見にくいですし、上記で指摘した問題点も解決できていません。それを踏まえて、次にTrelaを使った場合を見てみましょう。
直列処理 : Trelaを使った場合
直列処理をTrelaを使って書いた例const UserCard: React.FC<{ uid: number }> = ({ uid }) => { const { apis, steps } = useTrela(); const loginRef = apis.fetchLoginStatus().default(false); const userRef = apis.fetchUser().default(defaultUserValue); // fetchLoginStatus -> fetchUserの順に直列実行するRefを取得 const stepsRef = steps([loginRef, userRef]); // fetchLoginStatus -> fetchUserの順に直列実行する const [result, isLoading] = stepsRef.read(); const loginResult = result[0]; const userResult = result[1]; /* -- 以下、描画処理 -- */ }
steps関数
という関数が新たに登場しました!この関数に非同期処理の
ref
を配列で渡すことで、配列の順番通りに渡した非同期処理を実行する事が出来ます。そして、処理の流れが一つの非同期処理を実行する時と同じなので、覚えることが少なくて済みますし、分かりにくいですが、上記で指摘した問題点もTrela内部で解決してくれているので、安全に直列処理を実行する事が出来ています。
ここまでで直列処理が分かったら、今度は並列処理の例を見てきましょう。
並列処理 : Trelaを使ってない場合
並列処理をTrelaを使わずに書くconst UserCard: React.FC<{ uid: number }> = ({ uid }) => { const [isLoading, setLoadingStatus] = useState(false); const [resultData, setResult] = useState({ isLogin: false, user: defaultUserValue }); useEffect(() => { (async () => { setLoadingStatus(true); // ローディング開始 // fetchLoginStatus と fetchUser を並列処理する const results = await Promise.all([ fetchLoginStatus(uid), fetchUser(uid), ]) setResult({ isLogin: results[0], user: results[1] }); // 結果をViewに反映 setLoadingStatus(false); // ローディング終了 })(); }, []) /* -- 以下、描画処理 -- */ }直列処理の時と大体一緒ですが、
Promise.all関数
を使って二つの非同期処理を並列処理しているため、ソースコードが少し冗長になってしまいました。ただでさえ上記の問題点を抱えているのに、ソースコードまで冗長になってしまうのはあまり良くありませんね。
Trelaを使った例を見てみましょう。
並列処理 : Trelaを使った例
並列処理をtrelaを使って書いた例const UserCard: React.FC<{ uid: number }> = ({ uid }) => { const { apis, all } = useTrela(); // 非同期処理のRefを取得( まだ非同期処理はしてない ) const loginRef = apis.fetchLoginStatus().default(false); const userRef = apis.fetchUser().default(defaultUserValue); // fetchLoginStatus と fetchUser を並列に実行するRefを取得 const stepsRef = all([loginRef, userRef]); // fetchLoginStatus と fetchUser を並列に実行する const [result, isLoading] = stepsRef.read(); const loginResult = result[0]; const userResult = result[1]; /* -- 以下、描画処理 -- */ }上記が並列処理のソースコードになりますが、ほとんど直列処理のソースコードと同じになっています!
直列処理との違いはsteps関数
のところが、all関数
に変わっただけです!これによって、Trelaを使ってない時に比べて書き方が統一されているので、とても読み易く、修正しやすいコードになっています。そして、それに加えてより複雑な処理も書くことが出来ます。
以下のソースコードを見てください!
直列と並列を組み合わせるconst UserCard: React.FC<{ uid: number }> = ({ uid }) => { const { apis, all, steps } = useTrela(); const ref_1 = apis.fetch1(); const ref_2 = apis.fetch2(); const step_1_2 = steps([ref_1, ref_2]); // ref_1 -> ref_2の順に直列するRef const ref_3 = apis.fetch3(); const ref_4 = apis.fetch4(); const step_3_4 = steps([ref_3, ref_4]); // ref_3 -> ref_4の順に直列するRef const all_ref = all([ step_1_2, step_3_4 ]); // 二つの直列処理を並列実行するRef const [result, isLoading] = all_ref.read(); // ここで非同期処理を実行 /* -- 以下、描画処理 -- */ }上記のソースコードは、
ref_1 -> ref_2
・ref_3 -> ref_4
と実行する直列処理二つを並列に実行しています。
この処理をTrelaを使わないでやろうとすると、とても複雑なソースコードになってしまいますが、Trelaを使うとシンプルで分かりやすく、修正しやすいコードになっています。キャンセルだってできる。そう、Trelaならね。
Trelaでは、非同期処理をキャンセルすることが出来ます。
これは一つの非同期処理の時は勿論、直列処理や並列処理に対しても出来るようになっていますので、大変便利な機能となっています。キャンセルするには以下のようにします。
非同期処理をキャンセルするconst Component = () => { const { apis, all, steps } = useTrela(); const singleRef = apis.SampleApi(); // 非同期処理のRefを取得 singleRef.cancel(); // 非同期処理をキャンセルする const stepRef = steps([ singleRef ]); // 直列処理のRefを取得する stepRef.cancel(); // 直列処理をキャンセルする const allRef = all([ singleRef, stepRef ]); // 並列処理のRefを取得 allRef.cancel(); // 並列処理をキャンセルする /* -- 省略 -- */ }上記のように
cancel関数
を実行すれば、それだけで非同期処理をキャンセルできます。ただ注意点として、Trelaでは実際の非同期処理をキャンセルしているのでなく、実行中の非同期処理から結果を受け取らないようにしているだけです。そのため、通信処理やバックグランド処理は動き続けたままになります。
※ ただ、ちゃんとキャンセル出来るようなAPIを実装予定なので、今後のバージョンアップで実行中の非同期処理をちゃんとキャンセル出来るようになります。
気づかないうちにすべてが最適化されている。そう、Trelaならね。
さて、Trelaのメリットや特徴をここまで紹介してきましたが、実はTrelaの真骨頂はここからです!それを見ていきましょう!
コンポーネントの描画更新の最適化
ここまでの解説で、Trelaが提供する
useTrela
というReact Custom Hooks
を用いて、非同期処理を実行してきました。
この時の非同期処理が完了するとTrelaが勝手に描画更新をしてくれるのですが、実はこの時に描画更新の最適化をしてくれています。どういう事か見てきましょう。以下のソースコードを見てください。
無駄描画してしまう例const useSample = (): string => { const [result, setId] = useState<string>(""); useEffect(() => { // fetchId関数はPromiseをキャッシュするので、無駄な通信はしない // なので、一回だけIDを取得する処理をする fetchId().then(setId); }, []); return result; } const ComponentA = () => { const id = useSample(); return ( <p>あなたのIDは : {id} です</p> ); } const Root = () => { const id = useSample(); return ( <div> <p>あなたのIDは : {id} です</p> <ComponentA /> </div> ); }突っ込みどころの多いコードですが( 大目に見てください )、上記のコードには無駄な描画更新が発生する可能性があります。
具体的には以下の流れです。
- Rootが実行される( マウントはされてない )
- ComponentAがマウントする
- ComponentAのuseEffect関数内でfetchIdが実行される
- Rootがマウントする
- RootのuseEffect関数内でfetchIdが実行される
- ComponentAのfetchIdの処理が終わり描画更新される( ComponentAは一回描画更新した )
- RootのfetchIdの処理が終わり描画更新される( Rootは一回描画更新した )
- ComponentAが描画更新された( ComponentAは二回描画更新された )
上記の流れで、
<ComponentA />
は二回描画更新1されましたが、これが無駄な描画更新です。 何故なら、二回目の描画更新では<ComponentA />
は何も変化がないからです。ここで、「 React.jsでは差分描画するから大丈夫では? 」と思った人もいる事でしょう。
実際に上記の例でも、React.jsの差分描画によってリアルなDOMに変更はありません。しかし、ComponentA関数は実行されるので、差分を確認するためのオブジェクト生成や、比較する処理が走るため、リソースは消費します。そのため、大きな構造を持つコンポーネントはリアルDOMに反映されなくても、とても重い処理となってしまう可能性があります。
そして、巨大なアプリや多くのデータを表示するアプリ( 台帳アプリなど )などの大きな構造を持ちやすいモノほど、複雑な描画更新フローになりやすいので、上記のような事が発生しやすいです。 ※ 小さいアプリでも、描画更新が多いと同じような問題が起こります。
しかし、Trelaではコンポーネントの親子構造を解析して、描画更新を最小限に抑えます。 そのため、Trelaを使えば上記の例の
<ComponentA />
を一回の描画更新に抑えることが出来ます。無駄描画してしまう例をtrelaで書き換えconst useSample = (): string => { const { apis } = useTrela(); const ref = apis.fetchId().default(""); // 非同期処理のRefを取得 const [result] = ref.read(); // ここで非同期処理を実行 return result; } const ComponentA = () => { const id = useSample(); return ( <p>あなたのIDは : {id} です</p> ); } const Root = () => { const id = useSample(); return ( <div> <p>あなたのIDは : {id} です</p> <ComponentA /> </div> ); }上記のソースコードでは、
<ComponentA />
の描画更新は一回だけになります。 しかし、ソースコードにはそのような事が分かるような記述はありません。そうです!ここがTrelaの真骨頂です!
Trelaを使うと、勝手に描画更新を最適化してくれます。 そのためTrelaでは、わざわざ
useMemo
やuseCallback
、React.memo
などを使って描画更新を抑える必要はありません。どの情報が必要なのかを宣言するだけでいいんです。これによって、プログラマーはよりやりたいことに専念することが出来ると思います。
通信の最適化
Trelaは、通信の処理も勝手に最適化してくれます。
例を見てみましょう。
無駄な通信が走る例const ListItem = ({ item }) => { const [user, setUser] = useState({ name : "" }); useEffect(() => { // getUser関数の定義は省略してます。 getUser(item.userId).then(setUser); // 初マウント時に一回だけ通信処理をする },[]); return ( <li> <p>{item.name} @ create by {user.name}</p> </li> ); } const App = () => { // userIdを持つオブジェクトを含む配列 const items = [{ id: 0, name: "hello", userId: 1 }, { id: 1, name: "world", userId: 1 }]; return ( <ul> { items.map(item => ( <ListItem key={item.id} item={item} userName={users[item.userId]} /> )) } </ul> ); }上記の例では、私が前に書いた記事の例を持ってきました。
やっている事は、配列から
<ListItem />
を複数描画しています。
<ListItem />
は、初回マウント時にユーザーを取得する処理をしています。
この時、itemsの要素(item)
の中に同じuserId
を持つitem
が複数あります。そのため、同じユーザーを取得する処理を複数回(今回は二回)走らせてしまいます。これは
Items
のサイズが大きくなり、同じuserId
を持つitem
が多くなれば、描画が遅くなったり、サーバーにも余計な負荷がかかるなどいろいろな問題が発生します。しかし、TrelaではPromiseをキャッシュしているので無駄な通信を省くことが出来ます。
無駄な通信が走る例をtrelaで書き換えconst ListItem = ({ item }) => { const { apis } = useTrela(); const ref = apis.getUser().default({ name : "" }); // 通信処理のRefを取得 const [result] = ref.read(); // 通信処理を実行 return ( <li> <p>{item.name} @ create by {user.name}</p> </li> ); } const App = () => { // userIdを持つオブジェクトを含む配列 const items = [{ id: 0, name: "hello", userId: 1 }, { id: 1, name: "world", userId: 1 }]; return ( <ul> { items.map(item => ( <ListItem key={item.id} item={item} userName={users[item.userId]} /> )) } </ul> ); }勿論、使う側は通信の最適化について何もする必要はありません。ただ取得したいという事を宣言するだけですので、上記のようにシンプルに記述できています。
Store管理だって簡単にできる。そう、Trelaならね。
Store管理を可能にする
Store API
は、2020/09現在ではまだ制作途中ですが考え自体は既にあるのでご紹介します。
既存のライブラリと比較しながらだと分かりやすいと思いますので、この記事ではRedux
とRecoil
のアプローチと比較しながらご紹介したいと思います。Reduxのアプローチ
※ 簡略化のためActionCreatorの部分は省略しています。ご了承ください。
Reduxのアプローチは、複雑なデータフローをシンプルなデータフローに強制し、状態を一元管理しています。それによって、テスト・デバッグがしやすく、データの復元が容易にでき、複数の状態を伴った計算などもやり易いです。一方で、
Sotre
やReducer
が肥大化して扱いづらくなったり、非同期処理がミドルウェアを使っても大変だったりします。Reduxの良い所
- データフローが分かりやすい
- 状態管理がしやすい
- テストやデバッグがしやすい
Reduxの良くない所
- 記述が多い
- 非同期処理がミドルウェア使っても大変
- 巨大なデータを一元管理するのは大変( 分割して柔軟に処理したい )
Recoilのアプローチ
最近何かと話題のRecoilですが、正直グラフにするのが難しかったので上記の画像はあまり参考にはならないと思います。なので、全体の雰囲気だけ理解してもらえると良いと思います。
Recoilのアプローチは、コンポーネントと状態を強く紐づけて、影響範囲を小さくするという感じです。これによって、影響範囲が小さくて済み、無駄な描画更新が発生しません。また状態を細かく分割して組み合わせる事で、柔軟な状態管理が可能です。 非同期処理にも対応しているので、Reduxよりも高性能で扱いやすい印象です。
一方、懸念点としては
atom
やselector
の数が多くなってしまい複雑化しやすいと思いますし、selector
のset
が気を付けて使わないとより複雑さを招いてしまいそうな感じです。ただ、まだまだ開発中のモノなので、これからのアップデートで変わっていくと思いますし、facebookが作っているというのは、とても安心感があると思います。
Recoilの良い所
- 描画効率が良い
- 柔軟なState管理が可能
- 非同期処理が扱える( React Concurrent Modeに依存していますが、時間の問題だと思います )
- facebookが作っているので、Reactとの相性は良い
Recoilの良くない所
- データフローが複雑( グラフの雰囲気から分かると思います )
- 記述量が多くなる( 場合によっては、Reduxより多くなると思う )
atom
やselector
の数が多くなって複雑化する( keyの指定はどうにかならないのか? )Trelaのアプローチ
Trelaのアプローチはとても単純です。
View
とAPIs
のやり取りを横取りして新しいState作るという感じです。そのため、基本的に
View
やAPIs
は直接的にStore
の処理に関与しません。 一方、Store
もView
とAPIs
のやり取りを受け取るのみで、やり取り自体に関与しません。 また、出力もサブスクリプションしているコンポーネントにだけ反映するため影響範囲も少ない事が特徴です。また、グラフからは分かりにくいですが、
Store
はRecoilのselector
みたいな感じに、定義・使用できるような作りにしようと思っているので、Reduxのような一元管理ではなく、分散型のState管理だと思ってください。今はまだ構想段階で、具体的な処理などはありませんが、最終的にはReduxとRecoilの両方の性質を併せ持ったモノにしたいと思ってます。
Trela Storeの良い所
Trela Storeの良くない所
React Concurrent Modeに対応できる。そう、Trelaならね。
※ この機能はまだ開発中です。
React Concurrent Modeは、Reactで非同期処理を扱える新しいAPIになります。現在( 2020/09 )ではまだ実験段階ですが、おそらくこれからのReactで非同期処理を扱うのによく使われるようになると思います。
Trelaではその事を見越して、今まで紹介したソースコードを崩さずにReact Concurrent Modeに対応する事が出来ます!
具体的には以下のようにします。
Trelaを使ったコンポーネント/* -- fetchUserListの定義は省略します -- */ const { Provider, useTrela, TrelaSuspence } = createTrelaContext({ apis: { fetchUserList } }); const UserList = () => { const { apis } = useTrela(); const [users, isLoading] = apis.fetchUserList.read(); // ここで非同期処理をする if( isLoading ) return <div className="loading-bar" /> return ( <ul className="user-list"> { users.map(user => ( <li key={user.id} className="user-list-item">{ user.name }</li> )) } </ul> ); } const App = () => ( <TrelaSuspence fallback={<div className="loading-bar" />}> <UserList /> </TrelaSuspence> );
<UserList />
は、今まで紹介してきたTrelaでの処理を実行しています。この<UserList />
を<TrelaSuspence />
という新しく出てきたTrelaが提供するコンポーネントで囲った上げるだけで、その部分のみReact Concurrent Modeとして実装されます!これによって、React Concurrent Modeを段階的に導入したり、部分的に使用することが可能になります!
このAPIの良い所は、React Concurrent Modeの実装と従来の実装の切り替えが簡単な所にあります。
もしReact Concurrent Modeが嫌になったら、<TrelaSuspence />
を削除するだけです。<UserList />
には何も変更を加える必要はありません。これによって、
<UserList />
をより柔軟に扱う事が可能になります!Anyなんて言わせない。そう、Trelaならね。
TrelaはTypeScriptで作っているため、TypeScriptによる型推論の恩恵を多く受け取ることが出来ます。例えば、
apis
で非同期処理のRefを取得する時などは、入力保管が効いていてとても便利です。また、非同期処理に引数を渡す時もちゃんと型が付いているので、型安全に非同期処理を行えます。
Let's TS Hack!
上記のメリットをより多く受け取るために、ちょっとしたTypeScriptの書き方をご紹介します。
まず初めにTypeScirptのすごい所として、ファイル内の変数を全てimportするとそのimportした変数の型推論をやってくれるところがあります。
test.tsexport const Hello = "test"; export const Hoge = 0;test.tsをすべてimportimport * as AllValue from "./test.ts"; type AllValueType = typeof AllValue; /* AllValueの型を持ってくることができる { Hello: string; Hoge: number; } */これを利用すると疎結合なソースコードをTrelaを使って書くことが出来ます。
その例をこれから見てきます。まずはファイル構造を見てましょう。ファイル構造├ apis.ts <- Api関数を定義するファイル ├ context.ts <- TrelaのContextをexportするファイル └ component.tsx <- コンポーネントを定義するファイル次に上記の
apis.ts
ファイルにAPI関数を定義していきます。api.ts// 渡されたUserIdのユーザー情報を取得するAPI export const fetchUser = async (id: number): Promise<{ id: number; name: string; }> => { const ref = await fetch(`https://example.com/api/user/${id}`); const json = await ref.json(); return { id, name: json.name } }APIが出来たら、それを使ってTrelaのContextを作ります。
Contextは別ファイルに書くと、後はimportして使うだけになって、ほとんど変更しなくて済むのでお勧めです。context.tsimport { createTrelaContext } from "trela"; import * as apis from "./apis"; // import * as apis ~ としているのがポイント! // apis.tsで定義しexportした関数をcreateTrelaContextに渡す const { Provider, useTrela } = createTrelaContext({ apis }); export { Provider, // Trelaが提供するProvierコンポーネント( Rootコンポーネントとして指定する必要があります ) useTrela // Trelaが提供するReact Custom Hooks関数 };Contextが定義出来たら、上記でexportしたモノをコンポーネントで使います。
以下のソースコードは、UserIdを使ってUser情報を取得し、<UserCard />
を表示しています。component.tsximport React from "react"; import ReactDOM from "react-dom"; import { Provider, useTrela } from "./context"; // 渡されたIDを持つUserをロードして表示する const UserCard:React.FC<{ id: number }> = ({ id }) => { const { apis } = useTrela(); const [user, isLoading] = apis.fetchUser(id).read(); // ここで非同期処理を実行している if(isLoading) return <div className="loading">通信中です</div> return ( <div className="user-card"> <div className="card-label">{ user?.name }</div> </div> ); } const App = () => { const userIds = [ 1, 2, 3 ]; // 取得するユーザーのID return ( <div className="app"> { userIds.map((id) => ( <UserCard id={id} key={id} /> )) } </div> ); } ReactDOM.render( <Provider> <App /> </Provider>, document.getElementById("root") );ここまでは普通のTrelaの使い方ですね。
上記のソースコードのapis.ts
にfetchFollowers
という新しいAPIをapis.ts
に追加します。apis.tsにfetchFolloers関数を追加// 渡されたUserIdのユーザー情報を取得するAPI export const fetchUser = async (id: number): Promise<{ id: number; name: string; }> => { const ref = await fetch(`https://example.com/api/user/${id}`); const json = await ref.json(); return { id, name: json.name } } // フォロワーを取得するAPI関数 export const fetchFollowers = async (user_id: number) => { const ref = await fetch(`https://example.com/api/followers/${user_id}`); const json = await ref.json(); return json as Array<{ name: string }>; }次に、
fetchFollowers
を<UserCard />
で使うには以下のようにします。component.tsxでfetchFollowers関数を使うimport React from "react"; import ReactDOM from "react-dom"; import { Provider, useTrela } from "./context"; // 渡されたIDを持つUserをロードして表示する const UserCard:React.FC<{ id: number }> = ({ id }) => { const { apis } = useTrela(); const [user, isLoading] = apis.fetchUser(id).read(); // ここで非同期処理を実行している // fetchFollowersを追加 const [followers] apis.fetchFollowers(id).default([]).read(); if(isLoading) return <div className="loading">通信中です</div> return ( <div className="user-card"> <div className="card-label">{ user?.name }</div> <ul> { followers.map((follower, i) => ( <li key={`follower_${i}`}>{ follower.name }</li> )) } </ul> </div> ); } /* -- 以下、変更が無いので省略 -- */ここで注目すべき点は、
components.ts
のimport部分は全く変更しなくてもfetchFollowers
を使えている所です。これによって、API部分とAPIを使う部分が疎結合となっているので、修正箇所が少なくて済みますし、ソースコードが見やすいと思います。勿論、上記で示したようにTypeScriptのサポートも受けることが出来ます。TS Hack発展形
また、これを発展させて
apis.ts
をフォルダーにしてfetchUser
とfetchFollowers
を別ファイルにしてみましょう。まずはフォルダー構造は以下のようになります。
ファイル構造├ apis <- フォルダーに変更 │ ├ index.ts │ ├ user.ts │ └ follower.ts ├ context.ts <- TrelaのContextをexportするファイル └ component.tsx <- コンポーネントを定義するファイル今度は上記の新しく作ったファイルを定義していきます。
apis/index.tsexport * from "./user" // user.ts の export しているものすべてを export export * from "./folloer"; // follower.ts の export しているものすべてを exportapis/user.ts// 渡されたUserIdのユーザー情報を取得するAPI export const fetchUser = async (id: number): Promise<{ id: number; name: string; }> => { const ref = await fetch(`https://example.com/api/user/${id}`); const json = await ref.json(); return { id: number, name: json.name } }apis/follower.ts// 渡されたUserIdをフォローしているUser情報を取得するAPI export const fetchFollowers = async (id: number): Promise< Array<{ id: number; name: string; }> > => { const ref = await fetch(`https://example.com/api/followers/${id}`); const json = await ref.json(); return json as Array<{ id: number; name: string; }>; }上記のようにファルダー構造を変更しても、
context.ts
とcomponents.ts
には何の変更もありません。
これによりAPIの種類によってファイルを分割して書けるので、よりソースコード管理が簡単になりますので是非ご活用ください!あなたでも貢献できる。そう、Trelaならね。
TrelaはOSSとして開発していますので、誰でも貢献することが出来ます。
勿論、IssuesやPull Requestを使って貢献してもらいたいですが、それ以外にも貢献方法はあると私は思っています。
この際なので、そのことについてちょっと言わせてください!OSSを評価して貢献しよう!
OSSへの貢献でよく誤解されているのが、IssuesやPull Requestを出すことだけが貢献することだと思われている事です。しかし、これは大きな間違いです!OSSを評価することも立派なOSSへの貢献です!
例えば、Githubのスターを付ける事や、議論できる場でOSSについて肯定・否定することなど、自分なりの評価をしましょう。
例えそれが信ぴょう性に欠けるものでも、みんながやる事で積もり積もって信頼できる評価に変わります!
だから、ちゃんと評価してあげましょう。ボタンを押すぐらい、今すぐにでも出来るはずですよね?また、「 良いモノなら絶対に評価されているはずだ! 」などの愚考はしないで下さい。もし絶対に評価されるなら「 埋もれた名作 」なんて言葉はあるはずが無いですし、それを理由に評価しないという事にはなりません。
有名でも、無名でも、あなたなりの評価をすべきです。そして、みんながちゃんと評価し合うようになれば、知られていないだけで物凄いライブラリーとかが出てくるかもしれません。それがあなたの貢献で見つかったのなら、それって凄く魅力的な事だと思いませんか?
私は思います :)OSSを布教して貢献しよう!
貢献したいOSSの事を他の人に教えてあげることも立派なOSSへの貢献です。
- SNSで貢献したいOSSの事をフォロワーに教える
- 自分のブログや記事投稿サイトに貢献したいOSSの事を書く
- 自分の仕事仲間や上司に教えてあげる
- 誰かが布教したモノを、再度布教する
などなど、自分じゃない誰かに教えてあげることで、もしかしたら教えたその人がOSSに貢献してくれるかもしません。そうなったら、あなたは間接的にOSSに貢献していると言えますよね?だから、「 貢献したい! 」と思ったら、まず初めに誰かにその事を伝えてみると言うのが一番初めに出来る貢献だと思います。
また、布教の良い所は 技術も、意見も、知識も、何も無くても出来るところです!
評価することに抵抗がある人も、このくらいの貢献なら出来ると思います。日本人なら日本人がやっているOSSに真っ先に貢献して!
これは単純な話です。
「 日本人の事を一番理解できるのは、日本人だからです。 」
そのOSSの開発者が日本人なら、同じ日本人である私たちの方がコミュニケーションはスムーズですね?
それに同じ日本人だから、応援したいと思うのはダメなのでしょうか?前に「 日本人は日本人に対して厳しい 」という話をどっかで聞いたことがあるのですが、昨今のIT業界を見ていると「 本当にそうだな 」と思う事が良くあります。特に、新しい事を挑戦的にやっている人に対して厳しい感じがします。 Winny事件はその最たる例でしょう。
前例を踏襲しているモノを評価する事もいいですが、新しい事に挑戦している事もちゃんと評価してあげる必要があると思います。特にIT業界によく触れ合っている私たちは、その重要性を理解しているはずです。
あとがき。そう、Trelaのね。
ここまで読んでくれてありがとうございます?
まだまだ開発中のモノなので色々とバグが多いと思いますし、コンポーネントの構造を動的に解析する部分は本当に良いアプローチなのか未だに分かってないので、誰か意見をくれると嬉しいです?♂️
実は、開発している最中にRecoilの発表があったので開発を辞めようかなと思っていたのですが、自分が思っていたよりRecoilに良さを感じなかったのと、「 Recoilの良い所を真似すればいいじゃない! 」という発想で開発を辞めませんでした。実際、Recoilのソースコードを何度も見て、どこかに参考となるような部分が無いか探していました。( Flowが見やすいエディタ誰か教えて。。。VSCodeは重すぎて私の環境だと無理。。。 )
あと、Recoilはデータフローが複雑になると思うのですが、これは問題にならないのでしょうか??
それにselector
のset
は危ないような気がするのですが、そこら辺に詳しい人が居ましたらご教授いただければ幸いです。OSSについては、お願いだからみんな日本のOSSを応援してあげて!
Trelaはもうこの際どうでもいいので、せめて他の人のプロジェクトに貢献してあげて?
中国語のOSSは凄く活気があって楽しそうなのに、日本のOSSは活気が無いのを見てると、とっても悲しくなってくる。。。
いいねを押すとか、布教くらいなら、時間が無くても出来るでしょ?言っておくけど、みんなが評価しないから、READMEとか日本語で書けないし、書きたくないんだよ!
みんながちゃんと評価してくれるなら日本語でやろうと思えるし、多言語対応なんて必要になった時にやればいいし、まずは小さく作って、みんなで評価して、スピード感を持って大きく育てる必要があると思う。いつまで海外の目を気にしたら気が済むの?もっと日本に目を向けて頑張るべきじゃない?
ぶっちゃけると、
「 なんで、英語でOSSしなきゃならんのさ!多様性の時代とはよく言ったモノだな? 」
って、私はいつも思ってる。( ´Д`)=3 フゥ
他にも言いたいことはいっぱいあるけど、それはまた別の機会にでも話します。
Trelaについて他に気になる点などがありましたら、コメントなどで教えてください。
それでは、また?
ここでの描画更新とはコンポーネント関数( クラスではrender関数 )を実行する事として扱います。分かりにくい言い方だとは思いますが、他に思いつかなかったのでこれで通します。ご了承ください。 ↩
- 投稿日:2020-09-28T01:38:24+09:00
React+GraphQLをHeroku+Hasura でセットアップする
今回は以前から気になっていた GraphQL を Heroku + Hasura で構築し、React から Apollo Client で呼び出すまでの流れを備忘録として記事にしたいと思います。
実際に試してみると、Firebase と同じくらいに簡単にサーバーを構築することができました。Hasura でプロジェクトを作成する
Hasura で新しく GraphQL のサーバーを作成します。
PostgresSQL サーバーを選択しますが、Heroku を使用する場合はアカウントリンクをすることで Heroku 側のプロジェクトを自動的に初期化してくれます。
プロジェクトの作成が完了すると管理画面が表示され、GraphQL のエンドポイントやおなじみの GraphQL の画面を確認することができます。
サンプルのデータを追加する
この状態ではデータベースに何もデータがない状態なので、ダミーデータを入れてみます。
データは何でも良いですが、このリポジトリ の load_employees.dump から一部データを取ってきました。Create Table からテーブルの型を決めます。今回は次のようにしました。
テーブルの型を決めたら Insert Row からデータをいくつか入れます。
最終的には次のようになりました。
データの入力が完了すると、トップページから GraphQL のクエリを試すことができます。
クエリ構文を覚えていなくても Explorer から選ぶだけで自動でクエリが生成されるのでかなり簡単ではないでしょうか。React アプリケーションの作成
上記で用意した GraphQL サーバーを React+Apollo Client で呼び出してみます。
GitHub も公開しているので良かったらクローンして試してみてください。Client を作成し Provider に与える
React hooks から GraphQL を呼び出せるように、作成した
client
を Provider で 指定します。index.tsximport { ApolloProvider } from "@apollo/client"; import { ApolloClient, InMemoryCache } from "@apollo/client"; const client = new ApolloClient({ uri: process.env.REACT_APP_SERVER_URL, cache: new InMemoryCache(), }); ReactDOM.render( <ApolloProvider client={client}> <App /> </ApolloProvider>, document.getElementById("root") );コンポーネント側から
useQuery
を使用してクエリを実行します。App.tsximport { useQuery, gql } from "@apollo/client"; const GET_EPLOYEES = gql` query GetEmployees { employees { birth_date emp_no first_name hire_date last_name } } `; function App() { const { data } = useQuery(GET_EPLOYEES); return ( <Container> <Pre>{JSON.stringify(data, null, 2)}</Pre> </Container> ); }これを表示してみると、次のようになります。
先程 Hasura で登録した内容を表示できていることが分かります。取得するデータを型安全にする
ここからは TypeScript を使用する人向けです。
先程取得したdata
はany
型となっています。これを apollo-tooling を使用することでクエリ文から型を生成できるようになります。まず Apollo Tooling をインストールします。
yarn global add apolloそして React プロジェクトのルートに
apollo.config.js
を作成します。
GraphQL の URL もこの中に指定します。module.exports = { client: { name: "client", includes: ["src/**/*.ts", "src/**/*.tsx"], tagName: "gql", addTypename: true, service: { // remote endpoint name: "sever", url: "https://xxxxxxxxxxxxxxxxx/graphql", }, }, };そして
client:codegen
コマンドで型定義ファイルを生成します。apollo client:codegen --target typescript types
完了すると、
src/types/xxxx.ts
に生成されたファイルが保存されます。export interface GetEmployees_employees { __typename: "employees"; birth_date: any; emp_no: number; first_name: string; hire_date: any; last_name: string; } export interface GetEmployees { /** * fetch data from the table: "employees" */ employees: GetEmployees_employees[]; }
useQuery
のジェネリクスにこの生成された型を指定すると、data
に型が付きます。 ?const { data } = useQuery<GetEmployees>(GET_EPLOYEES);おわりに
DB サーバーの用意から React での使用まで、想像より遥かに簡単に行うことができました。
さらには TypeScript の型付けも自動で行うことができ、かなり DX が向上すると思います。
また、Hasura では データベース内のアクセス権限などの管理はできるようですが、認証などは搭載していないようです。このあたりは Firebase と組み合わせて使用するのが一番簡単かもしれないですね。追記
JOIN などを行う方法を記事にしました。(https://qiita.com/TakaoNarikawa/items/c599a84c8f587fdcc867)
参考
- 投稿日:2020-09-28T00:10:03+09:00
お手頃・高機能スライダーはSwiper使っておけばいい
概要
実装をする中で実装可能だけど、そこに時間あんまり費やしたくないなぁって時結構ありますよね。
画像のスライダー機能の実装でそのような状況になりました。
自作実装もできるけど。。アニメーションを場面ごと切り替えたりするのが結構手間><
そこで、よく使われているライブラリを探していたところSwiper
を見つけて実装がとても楽だったので共有したいと思います。実装例
スライダーで使うcssをimportする
index.tsximport "swiper/swiper.scss"; import 'swiper/components/pagination/pagination.scss';スライダー用のコンポーネントを実装する
SwiperComponent.tsximport * as React from 'react'; import SwiperCore, { Pagination, Autoplay } from "swiper"; import { Swiper, SwiperSlide } from "swiper/react"; SwiperCore.use([Pagination, Autoplay]); interface Props { imageData: Array<string>; // trueで自動でスライダーアニメーションが動く isAuto: boolean; // trueでpaginationクリックでスライダーアニメーションが動く clickable: boolean } const SwiperComponent: React.FC<Props> = (props) => { return ( <div> <Swiper pagination={{ clickable: props.clickable }} autoplay={props.isAuto}> {props.imageData.map((imageSrc, i) => { return ( <SwiperSlide key={`${imageSrc}${i}`}> <img key={`${imageSrc}`} src={imageSrc} /> </SwiperSlide> ); })} </Swiper> </div> ); }; export default SwiperComponent;これだけ!!
あとはスライダー用のコンポーネントをスライダー実装したい箇所に組み込んであげるだけ!
<SwiperComponent imageData={["image/image1.jpg", "image/image2.jpg"]} isAuto={true} clickable={false} />これで画像スライダーの完成!
とても簡単。他にも便利な機能が山ほどあります。
スライドアニメーションもたくさんありますので色々と試してみるといいかもです。まとめ
今回は一番シンプルなスライダーを
Swiper
を使って簡単な実装例と共に紹介しました。
ちょっと実装に時間かかりそうなところがめちゃくちゃシンプルに実装できましたね。
Vueにも対応しているみたい※注意点
ライブラリは便利な反面、何気なく使ってしまうと危険な時があります。
急に開発元が開発をやめたり、特定のバージョンや環境でしか動かなこともザラにあります。
ライブラリを使うときはドキュメントをしっかり読み、ライセンスの確認、使っているユーザーの数(githubスターの数)
さらには開発状況などを確認した上で慎重に使うことをお勧めします。
個人で試す、勉強で使う場合は問題ないと思いますが、
仕事として今後世に出ていくようなサービスで使う場合はその辺りは意識してライブラリは使っていくといいと思います。参照