- 投稿日:2020-08-11T23:44:08+09:00
git pushでnpmパッケージとデモページを更新する(React&TypeScript用のboilerplate付き)
(2020.08. 12更新)
実際にこのboilerplateを使ってnpmパッケージを公開してみました。
https://qiita.com/nariakiiwatani/items/aa6189e86c2bd9b4200fTL; DR
GitHub: dev-npm-package-react-typescript-boilerplate
概要
ReactコンポーネントやHooksを作ってnpmに公開する際、
- ソースコードのgit push
- npmパッケージのpublish
- デモページの更新
という作業が発生する。
(デモページが必要ない場合もあるかもしれないが、個人的な感覚として、デモページがあるかないかで、導入するモチベーションが5倍くらい変わるのでほんとに単純なものだけでもあった方が良いと思う。)これらの作業は独立にも行えるが、全て同じタイミングで反映されて欲しいので、手元のコマンド一発で全部できるようにしたい。
かつバージョンの不一致が起きないように、全てのリソースを一箇所に置いておきたい。今回やることは以下の環境づくり。
- ライブラリのソースコードをGitHubリポジトリにおく
- 同じリポジトリにデモページのソースコードを置く
- リポジトリにgit pushすると以下のGitHub Actinosが実行される
- デモページのビルドと公開
- ライブラリのビルドとnpmへのpublish
全部入りのリポジトリはこちら。
GitHub: dev-npm-package-react-typescript-boilerplate最低限必要な作業は、、、
- (GitHubに登録)
- (npmに登録)
- 自分のGitHubリポジトリの作成
- npm tokenを生成してリポジトリに登録(自動publish用)
- package.jsonの編集
- package名
- 制作者情報
- gitリポジトリ情報
- git push
- GitHub Pagesの公開ディレクトリ設定
以下、Readmeとかぶるところもあるがその他情報を載せておく。
package.jsonの編集
- package名
- 制作者情報
- gitリポジトリ情報
ライブラリ本体の開発
- まず
yarn run setup:all
する。- 開発は基本的に
src
フォルダ内で行う。- ここに置いたモジュールは、ビルド時に
dist
フォルダ内にトランスパイルされる。- モジュールの追加や削除をした際は
index.js
とindex.d.ts
も編集しておく。
- これを忘れると、import時に各モジュールへのパスをいちいち書くことになる。
- ローカル確認用にビルドする場合は
yarn run build
git commit
は通常通り行って良いが、コミットメッセージに
- fix: => パッチバージョンアップ
- feat: => マイナーバージョンアップ
- perf: => メジャーバージョンアップ
のいずれかを含まないと、pushしてもライブラリが公開されない。
コミットメッセージの詳細はsemantic-releaseのドキュメント(英語)を参照。
git-todos と一緒に使うと良さそう.※ ライブラリ本体から別のパッケージを使用する場合、
package.json
のdependencies
ではなくpeerDependencies
とdevDependencies
に追記するようにする。
こうしないと、このライブラリを使用する環境でたまたま同じパッケージを使用しようとした場合に重複することになり、実行時エラーが起きる可能性がある。
参考: Reactコンポーネントをnpmパッケージとして開発する関連スクリプト
# ライブラリをビルド。distフォルダに出力される yarn run buildデモページの作成
普通のReactアプリを開発する感じでOK。
demo/src/components/App.tsx
の編集から始めると良い。関連スクリプト
# デモページをビルド。demo/distフォルダに出力される yarn run build:demo # ライブラリをビルドして、デモページをホストするローカルサーバを起動 yarn run devリモートリポジトリ設定
npm accountページ -> Auth Tokens で新しいトークンを作ってクリップボードにコピー
GitHubリポジトリのSettings -> Secretsで新しい変数を作成する。
- NameはNPM_TOKEN
- Valueはコピーしたトークン
pushしてみる
git push
-> GitHub actionsのページから進捗が確認できる実際にこの仕組みを使って公開しているライブラリがこちらになります。
https://github.com/nariakiiwatani/react-plain-json-editor
https://www.npmjs.com/package/react-plain-json-editor
- 投稿日:2020-08-11T23:00:53+09:00
Next.jsで静的HTMLエクスポートしたアプリをローカルで確認する方法
経緯
Next.jsの静的HTMLエクスポート機能(Static HTML Export)を使うと、サーバーにNode.jsを必要とせずに、クライントのみで実行できる静的HTMLにアプリを出力することできます。
ただし、
create-next-app
でスキャフォールディングしたプロジェクトに用意されているdev
コマンドで起動したアプリはサーバーサイドレンダリング(以下SSR)が有効になっており、静的HTMLのみの確認ができません。そこでNext.jsで静的HTMLエクスポートしたアプリをローカルで確認する方法を調べました。
確認環境
- Node.js - 12.4.1
- Next.js - 9.5.2
設定方法
サンプルプロジェクトを作成します。すでに作成済みの場合は飛ばします。
$ npx create-next-app my-static-site $ cd my-static-site
serve
というパッケージをインストールします。静的ファイルをホスティングするローカルサーバーを建てることができます。
開発でのみ使用するため-D
オプションを指定しています。$ yarn add -D serve最後に
package.json
のnpmスクリプトを修正します。
静的ファイルを出力するexport
コマンドと、ローカルサーバーを立ち上げるserve
コマンドを定義します。
next export
ではファイルはデフォルトで./out
ディレクトリに出力されます。
そのため、serve ./out
でホスティングするディレクトリを指定しています。SSRせず、完全に静的HTMLエクスポートしかしない場合、誤認防止の為に不要なコマンド削除しても良いと思います。
"scripts": { - "dev": "next dev", - "build": "next build", - "start": "next start" + "export": "next build && next export", + "serve": "yarn export && serve ./out" },実際に下記のコマンドを打つと以下のように表示されます。
$ yarn serve
- 投稿日:2020-08-11T21:19:09+09:00
[react]nextjs関連のメモ
nextjs関連で学んだことをメモ
現在nextjsの勉強をしているので覚えておいた方が良さそうなところや、
仕組みが不明なとこなどをメモがわりに残しておく。ページのルーティング
nextjsをなんとなく使っていたが、どういうルールでルーティングができてるのかわからなかった。
nextjsのディレクトリはpages/ - index.js - sample/ - index.js - show.jsこのような構成になっているが
pages/index.jsはlocalhost:3000/
,
pages/sample/index.jsはlocalhost:3000/sample/
,
pages/sample/show.jsはlocalhost:3000/sample/show/
という風にpagesディレクトリに存在するファイルからエクスポートされたコンポーネントがルーティングになるらしい。sample/index.js
export default function Sample() { return <h1>sample</h1> }localhost:3000/sample/にアクセスすると確認できた。
プリレンダリングとは
あらかじめhtmlを生成しておき、後からjavascriptコードが読み込まれる。
(*詳しい方いたら補足して頂けると助かります。)プリレンダリングの2つの形式
静的生成(Static Generation)
本番にビルドしたタイミングでhtmlを生成しておき、ユーザーのリクエストに応じて生成されたhtmlを再利用して表示する。
サーバサイドレンダリング(SSR)
ユーザーからのリクエスト毎にhtmlが生成される。
- 投稿日:2020-08-11T19:04:44+09:00
React + TypeScriptで location.state を使う
React(TS)における、コンポーネント間のデータの受け渡しにlocation.stateを使うときの方法。
やりたいこと
bookというstateを表示する
Book.tsx
と、 そのbook情報を編集するEditBook.tsx
があったとします。
このときBook.tsx
内で編集ボタンをクリックするとbookの情報をEditBook.tsx
に渡したい。方法
Book.tsxinterface BookState { book: { id: number title: string author: string } } class Books extends Component<{}, BookState> { state: State { book: { id: props.id, title: props.title, author: props.author, } } // bookの内容をEditBookにわたす handleEditBook = () => { history.push({ pathname: '/edit_book', state: { Book: this.state.book} }); } }EditBook.tsxinterface EditBookProps { location: any } interface EditBookState { book: { id: number title: string author: string } } class EditBook extends Component<EditBookProps, EditBookState> { constructor(props: any) { super(props); this.state = { // this.props.location.stateで受け取り book: { id: this.props.location.state.Book.id, title: this.props.location.state.Book.title, author: this.props.location.state.Book.author, } }; } }
- 投稿日:2020-08-11T17:01:38+09:00
React初心者のための Material-UI 【Dialogの使い方編】
React + Material-UI を勉強しています。今日は「ダイアログ」「モーダル」などと呼ばれる機能の使い方をまとめます。
React のチュートリアルは一通り終えていざ自分で作ろうとするとつまずくことがあるかと思います。なかでも Material-UI の公式ページは便利だけど理解し辛いなあと思うので「使い方」の点からまとめます。ちなみに Dialog ってのはこんな奴ですね。GIFはMaterial-UI のサンプルページです。
大雑把なプロセスは
- Dialog のデザインを決める
- Dialog を含むコンポーネントを作る SampleDialog
- SampleDialog を開くトリガーを作る
- state のリフトアップ
となります。
ダイアログのデザインを決める
Dialog を含むコンポーネントのデザインを決めます。直接
<Dialog />
に props を渡せるなら一手間省けますが、今回はひとまわりラッピングされたコンポーネントを想定します。コンポーネントを作る
ダイアログとトリガーのコンポーネントを作ります。ここでは仮に SampleDialog, SampleCard と名付けます。トリガーのコンポーネントは Card にするつもりなので SampleCard と名付けました。 SampleCard のなかの Button をクリックしてダイアログを開くようにしたいと思います。
図のような構造になります。
SampleDialog を開くトリガーを作る
SampleDialog を開くトリガーを作ります。つまり、 SampleCard のなかに Button を配置します。
class SampleCard extends React.Component { render() { return ( <Card> <CardContent>Lorem ipsum dolor sit amet</CardContent> <CardActions> <Button variant="contained" color="primary"> Open Dialog! </Button> </CardActions> </Card> ); } }state のリフトアップ
このセクションがいちばん大変ですね笑。
React では2つ以上のコンポーネントで状態のやりとりをするために共通の親コンポーネントを作り state を一元化するという手法を取ります。これを state のリフトアップと呼んでいます。今回の場合は SampleDialog と SampleCard の間で open という bool 値を共有する必要があります。それを仮に Parent と名付けます。 Parent の state に open を用意します。
ダイアログを開く
- SampleCard 内部の Button がクリックされる
- Parent の open が true になる
- SampleDialog の props.open が true になりダイアログが開く
というステップがあります。 では、1 から 2 を繋ぐにはどうすればいいのでしょうか?子コンポーネントから親コンポーネントの state をいじるのはできません。そのため、親コンポーネントから渡す props に親コンポーネントの state を変更するメソッドを渡します。たとえば、
handleOpen
というメソッドを作ります。中身はthis.setState({open: true})
です。つまり、 Parent の state を変更するメソッドです。そして、 constructor でメソッドを.bind(this)
しておきます(ここは説明不十分だと思います。後日追記する予定)。
そして、このメソッドを SampleCard に渡します。
Card の props にonClick: handleOpen
が入っていますね。これを Button への props に渡すことで 1 から 2 へのステップが実現します。
2から3へのステップは簡単です。Parent から SampleDialog に渡された props.open を
<Dialog>
に渡します。これでダイアログを開けるようになりました。ダイアログを閉じる
Dialog は Backdrop(ダイアログの背景、つまり本体より外側の部分)をクリックすると
onClose
がコールバックされます。onClose
で Parent の open を false にすればダイアログも閉じられます。「ダイアログを開く」でやったのと同じように
- Parent で メソッド
handleClose
を定義する- Parent から SampleDialog へ props で
onClose={handleClose}
を渡すというステップです。
わかりにくかった点
Material-UI のサンプルは Hooks を使ったコードです。それを知らずに読んでいると混乱してしまいました。「俺の知ってる React じゃないぞ???」となりました笑。
おわり
途中で力尽きたので、コードサンプルは載せていません。気が向けば書きます笑。
自分を含めた初心者のための備忘録として書いています。間違い、もっとよい方法などございましたらぜひ教えてください。
参考
https://material-ui.com/api/dialog/
Dialog の props を調べられます。いろいろカスタムできます。
- 投稿日:2020-08-11T16:40:17+09:00
一から作るモダンフロントエンド環境【Webpack & Babel編編】
どうも、ディベロッパーの羽田です。
最近業務でモダンJSフレームワークやwebpackを使うことが多くなってきたので開発環境の構築手順をまとめてみます。長くなりそうなのでいくつかに分けます。
- 【基本構築編】
- 【Webpack & Babel編】←イマココ
- 【css & 画像バンドル編】(執筆中)
- 【React & React Router編】(執筆中)
- 【Redux編】(執筆中)Webpack周辺を構築
パッケージ名 説明 webpack 複数のファイルを1つにバンドルして出力してくれるツール。JSファイルを起点にローダーを読み込ませることであらゆるファイルをバンドルできる。 webpack-cli コマンドラインでwebpackを実行するために必要なパッケージ。 webpack-dev-server webpackを使用した開発環境向けのwebサーバー。様々なオプションを設定可能。 html-webpack-plugin webpackから生成したJavaScriptを埋め込んだHTMLテンプレートを生成できる。 $ npm i -D webpack webpack-cli webpack-dev-server html-webpack-pluginwebpackの設定を行うために
webpack.config.js
を作成します。webpackにはプラグインやローダー(JSファイル以外のファイルをバンドルするための拡張プログラム)を追加することができますがその際の設定もここに記述できます。
webpack.config.jsconst path = require('path'); //node.js上でファイルパスを操作できるモジュール const HtmlWebpackPlugin = require('html-webpack-plugin'); //html-webpack-pluginモジュール module.exports = { mode: 'development', //ビルド時の出力ファイルを圧縮するかしないかの設定。今回はdevelopment。 entry: 'src/index.html', //ビルド時に起点となるJSファイルを指定。 output: { filename: 'bundle.js', //ビルド時に出力されるファイル名を設定 path: path.resolve(__dirname, 'dist') //出力先のパスを指定(ここで絶対パスを指定するためにpathモジュールを使用) }, devServer: { port: 8000, //ローカルサーバーのポート番号を指定 open: true //webpack-dev-server起動時に自動でブラウザを開く設定 }, plugins: [ new HtmlWebpackPlugin() //html-webpack-pluginをwebpackに追加 ] }Babelの設定
Babelとは最新バージョンのECMAScript(Ecma Internationalによって策定されたJavaScriptの標準仕様)を古い型へとコンパイルするツールです。ブラウザによっては最新のECMAScriptをサポートしていない場合があるのでReactなどのモダンJSフレームワークを使用する場合は必須となります。
パッケージ名 説明 @babel/core Babel本体のモジュール @babel/cli コマンドラインでBabelを実行するために必要なパッケージ。 @babel/preset-env 自動で実行環境に合わせたバージョンでコードをコンパイルする。 babel-loader webpackにBabelを読み込む。 導入するには以下のコマンドを実行
$ npm i @babel/core @babel/cli @babel/preset-env babel-loader実際にwebpackを実行してみる
webpackを実行するためのスクリプトを
package.json
に追記します。
内容は前回の続きからです。package.json{ "name": "react_webpack", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack", "start": "webpack-dev-server" }, "keywords": [], "author": "", "license": "ISC" }追記箇所はscriptsプロパティの値です。
これで以下コマンドが使えるようになりました。$ npm run build $ npm run start
$ npm run build
がwebpackを実行してアプリケーションをビルドするコマンド。
$ npm run start
がwebpack-dev-serverを実行してローカルサーバー上でファイルを実行するコマンドになります。実際にビルドしてみるとディレクトリ内に
dist
フォルダが生成され、その中にアプリケーションファイルがあるのが確認できるはずです。ここまで出来たら一旦webpackの基礎構築は完了です。次はローダーを組み込んでみます。
続く...
- 投稿日:2020-08-11T16:40:17+09:00
一から作るモダンフロントエンド環境【Webpack & Babel編】
どうも、ディベロッパーの羽田です。
最近業務でモダンJSフレームワークやwebpackを使うことが多くなってきたので開発環境の構築手順をまとめてみます。概要
ウェブ開発のデファクトスタンダードとなりつつあるWebpackとモダンJSフレームワークを使用したフロント開発環境構築の手順をまとめました。
自分用のメモも兼ねてるので雑な部分や端折ったりしてる部分があります。対象者
- ある程度JSの知識がある人
- モダンJSフレームワークに興味がある人
- Webpackを使用した開発環境構築に興味ある人
章節
- 【基本構築編】
- 【Webpack & Babel編】←イマココ
- 【css & sass編】(執筆中)
- 【React & React Router編】(執筆中)
- 【Redux編】(執筆中)
Webpack周辺を構築
パッケージ名 説明 webpack 複数のファイルを1つにバンドルして出力してくれるツール。JSファイルを起点にローダーを読み込ませることであらゆるファイルをバンドルできる。 webpack-cli コマンドラインでwebpackを実行するために必要なパッケージ。 webpack-dev-server webpackを使用した開発環境向けのwebサーバー。様々なオプションを設定可能。 html-webpack-plugin webpackから生成したJavaScriptを埋め込んだHTMLテンプレートを生成できる。 html-loader webpackにHTMLを読み込む。 webpack周りの構築を行います。
以下のコマンドを実行してパッケージをダウンロードしてください。$ npm i -D webpack webpack-cli webpack-dev-server html-webpack-plugin html-loader次にwebpackの設定を行うために
webpack.config.js
を作成します。webpackにはプラグインやローダー(JSファイル以外のファイルをバンドルするための拡張プログラム)を追加することができますがその際の設定もここに記述できます。
webpack.config.jsconst path = require('path'); //node.js上でファイルパスを操作できるモジュール const HtmlWebpackPlugin = require('html-webpack-plugin'); //html-webpack-pluginモジュール const htmlWebpackPlugin = new HtmlWebpackPlugin({ template: "./src/index.html", filename: "./index.html" }); module.exports = { mode: 'development', //ビルド時の出力ファイルを圧縮するかしないかの設定。今回はdevelopment。 entry: './src/index.js', //ビルド時に起点となるJSファイルを指定。 output: { filename: 'bundle.js', //ビルド時に出力されるファイル名を設定 path: path.resolve(__dirname, 'dist') //出力先のパスを指定(ここで絶対パスを指定するためにpathモジュールを使用) }, devServer: { port: 8000, //ローカルサーバーのポート番号を指定 open: true //webpack-dev-server起動時に自動でブラウザを開く設定 }, plugins: [ htmlWebpackPlugin //html-webpack-pluginをwebpackに追加 ] }Babelの設定
Babelとは最新バージョンのECMAScript(Ecma Internationalによって策定されたJavaScriptの標準仕様)を古い型へとコンパイルするツールです。ブラウザによっては最新のECMAScriptをサポートしていない場合があるのでReactなどのモダンJSフレームワークを使用する場合は必須となります。
パッケージ名 説明 @babel/core Babel本体のモジュール @babel/cli コマンドラインでBabelを実行するために必要なパッケージ。 @babel/preset-env 自動で実行環境に合わせたバージョンでコードをコンパイルする。 babel-loader webpackにBabelを読み込む。 導入するには以下のコマンドを実行
$ npm i -D @babel/core @babel/cli @babel/preset-env babel-loaderBabelの導入に合わせて
webpack.config.js
に設定を追記します。webpack.config.jsconst path = require('path'); //node.js上でファイルパスを操作できるモジュール const HtmlWebpackPlugin = require('html-webpack-plugin'); //html-webpack-pluginモジュール const htmlWebpackPlugin = new HtmlWebpackPlugin({ template: "./src/index.html", filename: "./index.html" }); module.exports = { mode: 'development', //ビルド時の出力ファイルを圧縮するかしないかの設定。今回はdevelopment。 entry: './src/index.js', //ビルド時に起点となるJSファイルを指定。 output: { filename: 'bundle.js', //ビルド時に出力されるファイル名を設定 path: path.resolve(__dirname, 'dist') //出力先のパスを指定(ここで絶対パスを指定するためにpathモジュールを使用) }, devServer: { port: 8000, //ローカルサーバーのポート番号を指定 open: true //webpack-dev-server起動時に自動でブラウザを開く設定 }, //Babel追記箇所 loader: { //ここに各種ローダーを追加する rules: [ { test: /\.js$/, exclude: /node_modules/, //コンパイル対象からnode_modulesを除外 loader: "babel-loader", //ローダーの指定 query:{ presets: ["@babel/preset-env"] } }, ] }, plugins: [ htmlWebpackPlugin //html-webpack-pluginをwebpackに追加 ] }ビルド用のファイルを作成
Webpackを実行してアプリケーションファイルを出力するためには元となるファイルが必要です。
myApp
フォルダのディレクトリ下にsrc
フォルダを作成、src
フォルダ内部にindex.html
ファイルとindex.js
ファイルを作成して下記のコードをコピペしてください。index.html<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>myapp</title> </head> <body> <h1 id="text"></h1> </body> </html>index.jsconst text = document.getElementById('text'); text.innerHTML = "Hello Webpack!"実際にwebpackを実行してみる
webpackを実行するためのスクリプトを
package.json
に追記します。
内容は前回の続きからです。package.json{ "name": "myapp", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "build": "webpack", "start": "webpack-dev-server" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "@babel/cli": "^7.10.5", "@babel/core": "^7.11.1", "@babel/preset-env": "^7.11.0", "babel-loader": "^8.1.0", "html-loader": "^1.1.0", "html-webpack-plugin": "^4.3.0", "webpack": "^4.44.1", "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.0" } }追記箇所はscriptsプロパティの値です。
これで以下コマンドが使えるようになりました。$ npm run build $ npm run start
npm run build
がwebpackを実行してアプリケーションをビルドするコマンド。
npm run start
がwebpack-dev-serverを実行してローカルサーバー上でファイルを実行するコマンドになります。実際にビルドしてみるとディレクトリ内に
dist
フォルダが生成され、その中にアプリケーションファイルがあるのが確認できるはずです。確認のためwebpack-dev-serverを実行して確認してみましょう。
npm run start
を実行してください。ブラウザ上にHello Webpack!と表示されれば成功しています。
ここまで出来たら一旦webpackの基礎構築は完了です。
次はcssや画像周りを構築していきます。
続く...
- 投稿日:2020-08-11T15:08:44+09:00
『GAFAコーディング面接こんな感じでした』を読んで
GAFAコーディング面接こんな感じでした - yambe2002’s diary を読んで、課題のコードを書いてみる気に。
元記事だと「URL の履歴を保存するデータ構造」がひとつだけど、ふたつ (進む用 + 戻る用) に分割した方が良さそう。
あと、今更 React のチュートリアルを読んだので、練習を兼ねて。
ついでに Bootstrap 5 alpha もチラ見しよう。
Web ブラウザの履歴を管理するようなコード
比較用に 2 種類をつくる。
感想
Bootstrap 5 alpha
- v4 からの破壊的変更は少なめ
- v4 と v5 の差異 で気になった点は以下
- Custom forms 廃止 (通常のフォームに統合)
- フォームのレイアウト周りの廃統合
.form-row
->.row
.form-group
->.mb-3
.form-inline
->.col-auto
など- フォームラベルに
.form-label
が必要に- cf. https://v5.getbootstrap.com/docs/5.0/forms/layout/
.form-control-file
->.form-file
.form-control-range
->.form-range
- IE 11 などのサポート終了
- jQuery 廃止
- DOM API (Vanilla JS) が高速なのは分かるし使うけど、書き易くはない。。
React
- state と props の違い
state
- コンポーネント内部で使うプライベート変数のようなオブジェクト
- 変更は必ず
setState()
メソッドを使うことprops
- 別のコンポーネントから渡される、読み取り専用のオブジェクト
- React と HTML における属性名の差異
class
->className
for
->htmlFor
readOnly
->readOnly
など- jQuery との比較
- JSX とコンポーネントにより、JS 上での CSS セレクターによる要素の指定がほぼ不要に。
sate
オブジェクトで状態を保持して、それを更新するだけで自動的に DOM 側にも反映できるので簡潔に記述できる。- TODO
- コンポーネントをもっと分割できるのか?
- フォームの検証はどう書くのが良いのか?
- 良いコードをもっと読まないと。
- 投稿日:2020-08-11T14:52:33+09:00
Reactで作るandroidアプリ
作ったもの
Ionic reactでアプリを作成
ionicを使ったandroidアプリ開発に、Reactが使えると知ったので試してみた。
Reactでどのようにアプリを作っていくのかが知りたかったので、今回はサンプルをエミュレーターで動かすことを目標にした。$ npm install -g @ionic/cli $ ionic start myApp sidemenu --type=react $ cd ./myApp/ $ ionic serve以下のような画面が表示できれば成功
android stidio
次に、先ほど作ったアプリをandroidエミュレーターで動かすために準備をする。
ここからandroid studioをダウンロードする。
ダウンロードできたらユーザーガイドに従い、インストールを進める
まず、依存ライブラリをインストール
$ sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386次に、先ほどダウンロードしたものを展開し、起動
$ sudo mv ~/Downloads/android-studio-ide-193.6626763-linux/android-studio/ /usr/local/ $ cd /usr/local/android-studio/bin $ ./studio.sh環境変数の設定
以下の環境変数を
.bashrc
に追記する。$ export ANDROID_SDK_ROOT=$HOME/Android/sdk $ export PATH=$PATH:$ANDROID_SDK_ROOT/tools/bin $ export PATH=$PATH:$ANDROID_SDK_ROOT/build-tools $ export PATH=$PATH:$ANDROID_SDK_ROOT/platform-tools $ export PATH=$PATH:$ANDROID_SDK_ROOT/emulatorAndroid エミュレーターのセットアップ
エミュレーターを使うために、まずはKVMのインストール。
ターミナルに、以下のコマンドをうち、エミュレーターが使えるか確認(出力が0でなければ、エミュレーターを使うことができる)$ egrep -c '(vmx|svm)' /proc/cpuinfoエミュレーターを使えることが確認できたらkvmとついでに実機でデバックするためのadbをインストール。
$ sudo apt -y install qemu-kvm adb $ sudo gpasswd -a "${USER}" kvm設定を反映するため、再起動を行う。
$ sudo shutdown -r nowionicアプリのビルド
$ cd myapp/ $ ionic buildandroid向けにビルド。
$ ionic cap add androidandroid studioでビルドしたプロジェクトを開く。
$ ionic cap open android参考
- 投稿日:2020-08-11T14:13:55+09:00
ReactのRedux内でaxiosを使用した通信をする
はじめに
前々回のReactを使用してWeb画面を作成するでは、reactの簡単なサンプルと共に説明をまとめました。
前回のReduxとReduxToolkitを使用してReact内でデータを管理するでは、reduxのの簡単なサンプルと共に説明をまとめました。
今回はaxiosを使用してREST APIから情報を取得して画面に表示させるサンプルと説明をまとめようと思います。環境
- node.js: v12.18.2
- webpack: 4.44.1
- React: 16.13.1
- Redux: 7.2.1
- axios: 0.19.2
環境作成
前回のReactを使用してWeb画面を作成すると前回のReduxとReduxToolkitを使用してReact内でデータを管理するの続きとなります。
環境構築などはそちらを見てください。axios
REST APIへ通信するためにaxiosというライブラリを使用します。
axiosのインストール
npm install axiosRedux Thunk
axiosは非同期で通信を実行します。しかし、Reduxは非同期の処理をするのは若干手間です。それらを楽にするためにRedux Thunkというミドルウェアを使用します。
Redux Thunkのインストール
npm install redux-thunkBabel/polyfill
axiosを使用するにあたってpolyfillが必要になります。
Babel/polyfillのインストール
npm install @babel/polyfill --save-devReactでaxiosを使用する
Reactのソースの作成
前回のReduxとReduxToolkitを使用してReact内でデータを管理するの続きなので、ソースも前回のに対して変更していく形にします。
sliceファイルにREST API呼び出しの処理を追加
REST APIの呼び出しと呼び出した後の処理をsliceファイルに追加する。
今回呼び出すREST APIはhttp://localhost:3000/mess_api
、ボディは{["message":"testMessage"]}
を想定して、このtestMessageをstateのmessに入れて、画面に表示させます。messageSlice.jsimport { createSlice } from '@reduxjs/toolkit'; import axios from 'axios'; export const messageSlice = createSlice({ name: 'message', ~~~ 前回と同じ ~~~ reducers: { ~~~ 前回と同じ ~~~ // action内にREST APIから取ってきたデータが入っている getRequest: (state, action) => { // body内の最初のmessageキーの値をstateに入れる state.mess = action.payload[0].message; } }, }); // 今回追加したgetRequestもエクスポートする export const { sayhello, sayAmount, getRequest} = messageSlice.actions; // RESTAPIの発行とgetRequestの呼び出しをする export const getRequestAsync = amount => async dispatch => { // ここでREST APIの呼び出しをする const response = await axios.get('http://localhost:3000/mess_api').then(response => { // 呼び出した結果のボディだけgetRequestに与える(与えるものを返れば上のaction引数に入る内容も変わる) dispatch(getRequest(response.data)); }); }; export default messageSlice.reducer;storeファイルにthunkを追加
store.jsimport { configureStore } from '@reduxjs/toolkit'; import thunk from 'redux-thunk'; import messageReducer from './slice/messageSlice'; export default configureStore({ reducer: { message: messageReducer, }, middlewares: thunk });slice経由でstoreを使用する
コンポーネントでREST APIを発行する際には、sliceで作成したactionを呼び出してstoreを操作します。
App.jsimport React, { useState } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { sayhello, sayAmount, getRequestAsync } from '../store/slice/messageSlice'; export function Message() { const message = useSelector(state => state.message.mess); const dispatch = useDispatch(); const [messsageAmount, setMesssageAmount] = useState('2'); ~~~ 前回と同じ ~~~ {/* sliceで作成したgetRequestAsyncアクションを使用する */} <button aria-label="rest get" onClick={() => dispatch(getRequestAsync())}> 通信 </button> {/* テキストボックスとボタンにインポートしたものを適用する */} <input aria-label="set amount" value={messsageAmount} onChange={e => setMesssageAmount(e.target.value)} /> <button onClick={() => dispatch(sayAmount(messsageAmount))}> テキスト変更 </button> </div> </div> ); }Reactの実行
前回同様にReactを開発用のサーバで起動してブラウザからアクセスしてください。
"./node_modules/.bin/webpack-dev-server"通信ボタンを押すとREST APIを発行して、そのボディをstoreに格納することでWeb画面上のテキストも変わります。
REST APIサーバを立てる
REST APIさえ発行できれば何を使用しても良いのですが、手ごろな環境がない場合に簡単なREST APIサーバを立てる方法もまとめます。
node.jsがある環境なのでJSON Serverというライブラリを使用してREST APIを立てます。JSON Serverのインストール
npm install -g json-serverJSON用ファイルの作成
JSONのエンドポイントとボディを定義したファイルをJSON形式で作成します。
今回はReactからエンドポイント:mess_api
、ボディのキー:message
から情報を取っているためそのように記載します。db.json{ "mess_api": [ { "id": 1, "message": "testMessage" } ] }JSON Serverの起動
先ほど作成したJSON用ファイルを指定してJSON Serverを起動します。
json-server --watch db.jsonコンソールに起動した旨が表示されればREST APIサーバの完成です。
終わりに
ReactのWeb画面作成に関しては、ここで一旦終わりにしようかと思います。React Routerなど他のライブラリもReactにありますが、特に難しくなかったのでまとめなくて良いかなと思います。
REST APIに通信できるようになるとWeb画面で出来ることが増えるため、とても便利だと思います。しかし、Reactでのaxiosの使い方とReduxでのaxiosの使い方が結構違うので苦労しました。
次回はreact-testing-libraryの使い方でもまとめてみようと思います。
- 投稿日:2020-08-11T14:06:37+09:00
【備忘録】日本一わかりやすいReact-Redux講座 実践編 #7 「商品を一覧表示しよう」
はじめに
この記事は、トラハック氏(@torahack_)が運営するYoutubeチャンネル『トラハックのエンジニア学習ゼミ【とらゼミ】』の『日本一わかりやすいReact-Redux講座 実践編』の学習備忘録です。
前回まで講座で、Products に対する CRUD のうち、C(:Create), Update(:Update)を実装しました。今回の講座では、CRUD の 一覧表示
R(:Read)
、及びD(:Delete)
を実装します。※ 前回記事: 【備忘録】日本一わかりやすいReact-Redux講座 実践編 #6 「useEffectで編集機能を作ろう」
要点
- Reactでは、
親コンポーネントで子コンポーネントをイテレート
する形でリスト一覧表示を実装する。- Material-UI における
<Card/>
で、画像+文字列のコンポーネントを実装する。- Material-UIにおける
<Menu/>
で、モーダル開閉を含めたメニューバーを実装する。完成系イメージ
http://localhost:3000/
いままで殺風景だったルートページ("/")に、Products を一覧表示します。各Productの登録画像のうち一つがサムネイルとなって表示されます。
メニューを開くと「編集する」「削除する」ボタンが表示されます、。「編集する」をクリックすると、各Productsの編集ページ("/products/edit/:id")へ遷移します。
「削除する」をクリックすると、
Productが削除され、残りのProductsで一覧表示がされます。
#7「商品を一覧表示しよう」
reducksファイル
Productsの一覧リスト表示を実装するにあたり、まずreducksファイルを定義します。
operations.js
内でDBから Products を一括で取得する関数(fetchProducts()
)を定義し、取得結果を Store に保存するよう、actions.js
、reducers.js
に追記していきます。また、Store内のProductsをビュー側で参照できるセレクターを
selectors.js
に定義していきます。実装ファイル(reducks)1.src/reducks/products/operations.js 2.src/reducks/products/actions.js 3.src/reducks/products/reducers.js 4.src/reducks/products/selectors.js1.src/reducks/products/operations.jsimport { fetchProductsAction } from "./actions"; . . . const productsRef = db.collection("products") . . . export const fetchProducts = () => { return async (dispatch) => { productsRef.orderBy("updated_at","desc").get() .then(snapshots => { const productList = [] snapshots.forEach(snapshot => { const product = snapshot.data(); productList.push(product) }) dispatch(fetchProductsAction(productList)); }) } }
orderBy("updated_at","desc")
で、DBから取得した結果を更新日時に対する昇順に整理し、fetchProductsAction
へ渡します。2.src/reducks/products/actions.jsexport const FETCH_PRODUCTS = "FETCH_PRODUCTS"; export const fetchProductsAction = (products) => { return { type: "FETCH_PRODUCTS", payload: products } }3.src/reducks/products/reducers.jsimport * as Actions from './actions' import initialState from '../store/initialState' export const ProductsReducer = (state = initialState.products, action) => { switch (action.type) { case Actions.FETCH_PRODUCTS: return { ...state, list: [...action.payload] }; default: return state } }
list: ...action.payload
ではなく、list: [...action.payload]
とのように、新しい配列として定義することで、配列の格納先メモリが切り替わり、 state が変更されたことを Component 側で検知できるようにしています。4.src/reducks/products/selectors.jsimport {createSelector} from "reselect"; const productsSelector = (state) => state.products; export const getProducts = createSelector( [productsSelector], state => state.list )
getProducts()
で、Store内の Products を Component 側から参照できるようにします。ビューファイル
続いてビューファイルを作成します。
Reactにおいて、「DBの中身をリストで一覧表示」を実装するときは、親コンポーネントで子コンポーネントをイテレートするという実装が一般的です。
今回は、親:
ProductList.jsx
、子:ProductCard.jsx
として、リスト表示を実装します。実装ファイル(ビュー関連)1.src/Router.jsx 2.src/templates/ProductList.jsx 3.src/templates/index.js 4.src/components/Products/ProductCard.jsx 5.src/components/Products/index.js1.src/Router.jsximport React from 'react'; import {Route, Switch} from "react-router"; import {ProductEdit,ProductList,Reset,SignIn,SignUp} from "./templates"; import Auth from "./Auth" const Router = () => { return ( <Switch> <Route exact path={"/signup"} component={SignUp} /> <Route exact path={"/signin"} component={SignIn} /> <Route exact path={"/signin/reset"} component={Reset} /> <Auth> <Route exact path={"(/)?"} component={ProductList} /> {/* 修正 */} <Route path={"/product/edit(/:id)?"} component={ProductEdit} /> </Auth> </Switch> ); }; export default Routerルートページ("/")を
ProductList
テンプレートに設定します。2.src/templates/ProductList.jsximport React,{useEffect} from "react"; import {ProductCard} from "../components/Products"; import {useDispatch, useSelector} from "react-redux"; import {fetchProducts} from "../reducks/products/operations" import {getProducts} from "../reducks/products/selectors" const ProductList = () => { const dispatch = useDispatch(); const selector = useSelector((state) => state); const products = getProducts(selector); useEffect (() => { dispatch(fetchProducts()) },[]); return ( <section className="c-section-wrapin"> <div className="p-grid__row"> {products.length > 0 &&( products.map(product => ( <ProductCard key={product.id} id={product.id} name={product.name} images={product.images} price={product.price} /> )) )} </div> </section> ) } export default ProductList
useEffect()
により、初期レンダー時において、先ほどoperations.js
で定義したfetchProducts()
を実行します。その後、const products = getProducts(selector);
でDBから取得した商品情報がproducts
に格納されます。
products.length > 0 &&...
で、products
が1つ以上存在する場合のみ、以下の要素をレンダーするよう条件分岐させます。
products.map(product => (...))
で、mapメソッドを用いて配列の中身をイテレートし、子コンポーネントの<ProductCard />
に渡します。イテレートする際は、子コンポーネントには一意の値をkey
として渡す必要があるため、ユニークな値であるproduct.id
をkey
に設定します。3.src/templates/index.jsexport {default as Home} from './Home' export {default as ProductEdit} from './ProductEdit' export {default as ProductList} from './ProductList' //追記 export {default as Reset} from './Reset' export {default as SignIn} from './SignIn' export {default as SignUp} from './SignUp'
ProductList
をエントリーポイントに追加。4.src/components/Products/ProductCard.jsximport React, {useState} from 'react'; import { makeStyles } from '@material-ui/core/styles'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import CardMedia from '@material-ui/core/CardMedia'; import Typography from '@material-ui/core/Typography'; import NoImage from "../../assets/img/src/no_image.png"; import {push} from "connected-react-router"; import {useDispatch} from "react-redux"; import IconButton from "@material-ui/core/IconButton"; import Menu from "@material-ui/core/Menu"; import MenuItem from "@material-ui/core/MenuItem"; import MoreVertIcon from '@material-ui/icons/MoreVert'; import {deleteProduct} from "../../reducks/products/operations"; const useStyles = makeStyles((theme) => ({ root: { [theme.breakpoints.down("sm")]: { margin: 8, width: "calc(50% - 16px)" }, [theme.breakpoints.up("sm")]: { margin: 16, width: "calc(33.333% - 32px)" } }, content: { display: "flex", padding: "16 8", textAlign: "left", "&:last-child": { paddingBottom: 16 } }, media: { height: 0, paddingTop: "100%" }, price: { color: theme.palette.secondary.main, fontSize: 16 } })); const ProductCard = (props) => { const classes = useStyles(); const dispatch = useDispatch(); const [anchorEl, setAnchorEl] = useState(null); const handleClick = (event) => { setAnchorEl(event.currentTarget) }; const handleClose = () => { setAnchorEl(null) }; const images = (props.images.length > 0) ? props.images : [{path: NoImage}]; const price = props.price.toLocaleString(); return ( <Card className={classes.root}> <CardMedia className={classes.media} image={images[0].path} title="" onClick={() => dispatch(push("/product/" + props.id))} /> <CardContent className={classes.content}> <div onClick={() => dispatch(push("/product/" + props.id))}> <Typography color="textSecondary" component="p"> {props.name} </Typography> <Typography className={classes.price} component="p"> ¥{price} </Typography> </div> <IconButton onClick={handleClick}> <MoreVertIcon/> </IconButton> <Menu anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose} > <MenuItem onClick={() => { dispatch(push("/product/edit/" + props.id)) handleClose() }} > 編集する </MenuItem> <MenuItem onClick={() => { dispatch(deleteProduct(props.id)) handleClose() }} > 削除する </MenuItem> </Menu> </CardContent> </Card> ) } export default ProductCardMaterila-UIコンポーネントを多く使用しているため、補足します。
<Card>
コンポーネントで、各リストアイテムを表示しています。画像(<CardMedia />
)、文字列(<CardContent />
)の配置、及びサイズは任意に調整可能です。
<IconButton />
をクリックすることでhandleClick()
が発火し、setAnchorEl()
でanchirEl
に値がセットされることで、モーダルが開きます。「編集する」をクリックすると
dispatch(deleteProduct(props.id))
が発火し、該当するProductsの編集ページへ遷移します。「削除する」をクリックすると、
dispatch(deleteProduct(props.id))
が発火し、該当するProductsの削除処理が行われます(deleteProduct()
はこのあと実装)5.src/components/Products/index.jsexport {default as ImageArea} from "./ImageArea" export {default as ImagePreview} from "./ImagePreview" export {default as ProductCard} from "./ProductCard" //追記 export {default as SetSizeArea} from "./SetSizeArea"
ProductCard
コンポーネントをエントリーポイントに追加。ここまでで、一覧表示は一通り完成しました!
最後に、Productsに対するCRUDのDとして、
deleteProduct()
を実装します。商品を削除しよう
削除の実装は簡単です。reduckcパターンに則り、以下のファイルに削除処理を定義していきます。
実装ファイル(削除処理)1.src/reducks/products/operations.js 2.src/reducks/products/actions.js 3.src/reducks/products/reducers.js1.src/reducks/products/operations.jsimport { fetchProductsAction,deleteProductAction } from "./actions"; . . . const productsRef = db.collection("products") export const deleteProduct = (id) => { return async(dispatch, getState) => { productsRef.doc(id).delete() .then(() => { const prevProducts = getState().products.list; const nextProducts = prevProducts.filter(product => product.id !== id) dispatch(deleteProductAction(nextProducts)) }) } } . . .
productsRef.doc(id).delete()
とすることで、該当するid
の商品情報を、Cloud Firestore上から削除します。getState()
により、現時点でのprosuctsを取得します。その後、filter
メソッドで該当する商品情報を配列から削除し、deleteProductAction ()
に渡します。2.src/reducks/products/actions.jsexport const DELETE_PRODUCT = "DELETE_PRODUCT"; export const deleteProductAction = (products) => { return { type: "DELETE_PRODUCT", payload: products } } . . .3.src/reducks/products/reducers.jsimport * as Actions from './actions' import initialState from '../store/initialState' export const ProductsReducer = (state = initialState.products, action) => { switch (action.type) { // 追記 case Actions.DELETE_PRODUCT: return { ...state, list: [...action.payload] }; // 追記ここまで case Actions.FETCH_PRODUCTS: return { ...state, list: [...action.payload] }; default: return state } }これで
ProductCard.jsx
内で記述したdeleteProducts()
関数が正常に動作するようになり、削除機能が実装されます。動作確認
動作自体は完成系イメージと同様のため割愛します。
deleteProducts()
により、Cloud Firestore上でも商品情報の削除が行われているだけ、確認します。、productsが4つ保存されています。メニューから「削除する」をクリックします。
↓
商品情報が表示されなくなりました。Firebaseコンソールを確認すると、
確かに商品情報が削除されています!
さいごに
今回の要点を整理すると、
- Reactでは、
親コンポーネントで子コンポーネントをイテレート
する形でリスト一覧表示を実装する。- Material-UI における
<Card/>
で、画像+文字列のコンポーネントを実装する。- Material-UIにおける
<Menu/>
で、モーダル開閉を含めたメニューバーを実装する。今回は以上です!
このような学習内容を日々呟いていますので、よろしければTwitter(@ddpmntcpbr)のフォローもよろしくお願いします。
- 投稿日:2020-08-11T13:28:47+09:00
一から作るモダンフロントエンド環境【基本構築編】
どうも、ディベロッパーの羽田です。
最近業務でモダンJSフレームワークやwebpackを使うことが多くなってきたので開発環境の構築手順をまとめてみます。概要
ウェブ開発のデファクトスタンダードとなりつつあるWebpackとモダンJSフレームワークを使用したフロント開発環境構築の手順をまとめました。
自分用のメモも兼ねてるので雑な部分や端折ったりしてる部分があります。対象者
- ある程度JSの知識がある人
- モダンJSフレームワークに興味がある人
- Webpackを使用した開発環境構築に興味ある人
章節
- 【基本構築編】←イマココ
- 【Webpack & Babel編】
- 【css & sass編】(執筆中)
- 【React & React Router編】(執筆中)
- 【Redux編】(執筆中)
前提
実際のウェブ開発を想定して構築します
事前に導入しておくもの
- Node.js
- npm
上記ソフトウェアを使用するので環境に無ければインストールしておきます
$ node -v v12.16.1 $npm -v v6.13.4執筆時点のバージョン情報。
作業環境と下準備
ターミナル or コマンドラインを開き、任意のディレクトリ内で以下コマンドを実行。
mkdir myapp cd myappmyAppディレクトリ内で、下記コマンドを実行。
$ npm init -yすると以下のjsonファイルが生成されます。
packege.json{ "name": "myapp", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }ここまでが下準備。
続きは以下リンクから。
- 投稿日:2020-08-11T12:25:28+09:00
react-hook-formの基本的な使い方
Reactで入力フォームを開発する際に便利なライブラリであるreact-hook-formの基本的な使い方についてまとめておきます。
公式ドキュメントが丁寧に用意されていますので、網羅的に機能を知りたい方はそちらをご覧ください。
実装手順
基本
以下のようなフォームに値を入力させて、submitする想定で改修を加えていきます。
import React from 'react'; function App() { return ( <form> <input name="title" /> <textarea name="content" /> </form> ); } export default App;ライブラリのインストール
ライブラリをインストールします。
npm install react-hook-form or yarn add react-hook-form
refの登録
次にライブラリが各フィールドを参照できるよう、registerメソッドをそれぞれのrefに追加します。
TypeScriptの場合は予め、useFormに対して各フィールドを持った型を渡しておきましょう。import React from 'react'; import { useForm } from 'react-hook-form'; type Post = { title: string; content: string; }; function App() { const { register } = useForm<Post>(); return ( <form> <input name="title" ref={register({ required: true, maxLength: 30 })} /> <textarea name="content" ref={register} /> </form> ); } export default App;react-hook-formには基本的なバリデーションも用意されており、このregisterを追加する際に設定することが可能です。
利用できるバリデーションについては以下を参照ください。
https://react-hook-form.com/get-started/#Applyvalidation
バリデーションエラーについて
設定したバリデーションでエラーが発生した場合はreact-hook-formが用意しているerrorオブジェクトを利用してハンドリングすることができます。
import React from 'react'; import { useForm } from 'react-hook-form'; type Post = { title: string; content: string; }; function App() { const { register, errors } = useForm<Post>(); return ( <form> <input name="title" ref={register({ required: true, maxLength: 30 })} /> {errors.title && <span>タイトルは必須です。</span>} <textarea name="content" ref={register} /> </form> ); } export default App;submit
そして、formタグに対して、コールバックを渡したhandleSubmitを設定します。
import React from 'react'; import { useForm } from 'react-hook-form'; type Post = { title: string; content: string; }; function App() { const { register, handleSubmit, errors } = useForm<Post>(); const onSubmit = (data: Post) => { console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <input name="title" ref={register({ required: true, maxLength: 30 })} /> {errors.title && <span>タイトルは必須です。</span>} <textarea name="content" ref={register} /> </form> ); } export default App;これでフォームが送信アクションが発生した際に、入力した値がonSubmitに渡されるようになります。