- 投稿日:2020-01-01T23:43:45+09:00
ReactのRedux
はじめに
ReactのReduxに関するメモ。
ReactのクラスコンポーネントとかJSXとかコンテキストとか調べてみたの続き参考
Redux
- 複数のコンポーネント、アプリケーション全体で使うような値をまとめるもの
- this.stateはコンポーネント単位で存在する
- 値や処理をアプリ内で統合し管理するための仕組みのライブラリ
- 値の保管は場所は一つだけ
- アプリケーションごとに一つだけ
- 値は読み取り専用、書き換え不可
- 変更は単純な関数で用意
インストール
npm install --save redux npm isntall --save react-redux npm isntall --save redux-devtools仕組み
- ストア
- 値を保管するステートと、値の操作処理であるレデューサーを内部に持っている
- プロバイダー
- ストアを他のコンポーネントに受け渡すための仕組み
- レデューサー
- ストアに保管されているステートを変更するための仕組み
Reduxの値の管理は、上記3つでできている
ストアの作成
変数= createStore(レデューサー);
作成されたストアを変数に収めておき、それを画面表示のJSXでプロバイダーに渡して利用
コンポーネントにストアを接続
変数=connect(ステート)(コンポーネント);これでthis.props.ストアの値とthis.props.dispatchが使えるようになる
変数=connect()(コンポーネント);これはステートを使わずthis.props.dispathを使えるようにする
providerでストアを受け渡す
<Provider store={store}> <APP/> </Provider>redux全体
index.jsでステート、レデューサーを定義して、renderでAppコンポーネントを利用。
Appコンポーネントの中で各コンポーネントを利用、ストアを使いたいコンポーネントだけプロバイダーでくくりその中でコンポーネントを利用する
Appコンポーネントで利用するコンポーネントの定義とコンポーネントとストアを結びつけることで、各コンポーネントから
dispatchからレデューサーを利用するデータの永続化 Redux Persist
Reduxのストアのデータをブラウザのローカルストレージに保存する
npm install --save redux-persist
- 投稿日:2020-01-01T15:44:46+09:00
ReactNative(expo)でアプリとWeb(View)で値をやり取りする
ReactNativeのWebView(Web)とアプリを連携する必要があったのでメモ。
忙しい人向け
- アプリからWeb => WebViewのinjectedJavaScriptにJSを渡して値をインジェクトしてWeb側でよしなにする
- Webからアプリ => window.ReactNativeWebView.postMessage("message")で送りonMessage()で受け取る
やりたいこと
例えば決済機能等の実装において、決済画面だけは決済サービス会社が提供するものを利用したいが、値はアプリ側で計算したものをデフォルト値として渡したいケースなど。
以下のような仕様。
最近の決済APIはカード情報非保持・非通過とするためWeb画面でカード番号を入れさせるものが多い。さらに言えばexpoでEjectしないで利用できるコンポーネントもない。
Web側
Web側は普通のHTMLだとまだ簡単なのですがReactを使うことが多いのでReactを使ってみます。
準備
場所作って必要なモジュールをインストール。
create-react-app web-app cd web-app npm install --save bootstrap reactstrap formik yup実装
続いて実装。
index.js
bootstrap cssの読み込みとServiceWorkerを削除している(キャッシュが効いちゃうので)。
index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import 'bootstrap/dist/css/bootstrap.min.css'; import './index.css'; import App from './App'; ReactDOM.render(<App />, document.getElementById('root'));App.js
App.jsに一旦すべて実装。
- priceというidのinputを用意
- cardというidのinputを用意
- priceにinjectedJavaScriptから値を設定
ということを想定。私いつもFormikつかうので使ってます。
App.jsimport React from 'react'; import { Form, FormGroup, Label, Input, Button, FormFeedback } from 'reactstrap'; import { Formik } from 'formik'; import * as Yup from 'yup'; class App extends React.Component { handlePayment = async (values) => { //1秒休む await this.sleep(1000); //終了したらアプリ側にメッセージを送る window.ReactNativeWebView.postMessage(values.price + "円の決済が完了しました。"); } //おやすみ補助関数 sleep = (msec) => { return new Promise((resolve) => { setTimeout(() => { return resolve(); }, msec) }) } render() { return ( <div className="container"> <h3 className="my-4 text-center">Payment(ここはWeb)</h3> <div className="col-10 mx-auto"> <Formik initialValues={{ price: 0, card: '1111-2222-3333-4444' }} onSubmit={(values) => this.handlePayment(values)} validationSchema={Yup.object().shape({ price: Yup.number().min(1).max(1000), card: Yup.string().required(), })} > { ({ handleSubmit, handleChange, handleBlur, values, errors, touched, setFieldValue }) => ( <Form> <FormGroup> <Label>金額</Label> <Input type="text" name="price" id="price" //idで強引に値をセット value={values.price} onChange={handleChange} onBlur={handleBlur} invalid={Boolean(touched.price && errors.price)} disabled /> <FormFeedback> {errors.price} </FormFeedback> </FormGroup> <FormGroup> <Label>カード番号</Label> <Input type="text" name="card" id="card" value={values.card} onChange={handleChange} onBlur={handleBlur} invalid={Boolean(touched.card && errors.card)} /> <FormFeedback> {errors.card} </FormFeedback> </FormGroup> <Button type="button" onClick={async () => { const price = document.getElementById("price"); //Formik使ってるので値を明示的にセットしてやる(完了するうちにValidationが走らないようawait) await setFieldValue("price", price.value); handleSubmit(); }}>購入</Button> </Form> ) } </Formik> </div> </div> ); } } export default App;とりあえず完成。window.ReactNativeWebView.postMessage()なんていう関数は標準のブラウザにはないのでchrome等でデバッグするとエラー出ますが無視します。
アプリ側
次にアプリ側。
場所の準備と必要コンポーネントをインストール。WebViewは普通にインストールするとexpoに怒られるのでexpo installコマンドで適切なバージョンのものをインストール。expo init app-web-integration cd app-web-integration expo install react-navigation react-native-gesture-handler react-native-reanimated react-native-screens expo install react-navigation-stack react-navigation-tabs react-navigation-drawer expo install react-native-webviewまずApp.js。基本的にStackNavigatorを設定しているだけ。
Home.jsとPayment.jsを利用しています。App.jsimport React from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { Card, Input, Button } from 'react-native-elements'; import { createAppContainer } from 'react-navigation'; import { createStackNavigator } from 'react-navigation-stack'; import Home from './Home'; import Payment from './Payment'; //stack navigator const HomeStack = createStackNavigator( { Home: { screen: Home, }, Payment: { screen: Payment, } } ); const AppContainer = createAppContainer(HomeStack); class App extends React.Component { render() { return ( <AppContainer /> ); } } export default App;Home.js
ボタンを配置してPayment.jsに移動します。またその時金額をパラメーターとして渡しています。
Home.jsimport React from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { Card, Input, Button } from 'react-native-elements'; class Home extends React.Component { render() { return ( <View style={{ flex: 1, alignItems: 'center', marginTop: 50 }}> <Text style={{ fontSize: 24 }}>Home(ここはアプリ)</Text> <Button title="100円コースを買う" style={{ width: '80%', marginTop: 20 }} onPress={() => this.props.navigation.navigate("Payment", { price: 100 })} /> <Button title="200円コースを買う" style={{ width: '80%', marginTop: 20 }} onPress={() => this.props.navigation.navigate("Payment", { price: 200 })} /> </View> ); } } export default Home;Payment.js
このコンポーネントはWebViewになります。WebViewの、
- injectedJavaScriptにWeb側に渡す値を設定
- onMessageに戻りの処理を書く
Payment.jsimport React from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { Card, Input, Button } from 'react-native-elements'; import { WebView } from 'react-native-webview'; class Payment extends React.Component { state = { js: '', } componentDidMount = async () => { //前のページからパラメータを受け取る(なければ0) const price = await this.props.navigation.state.params.price ? this.props.navigation.state.params.price : 0; //priceを設定するスクリプトを動的に生成 const js = ` const price = document.getElementById("price"); price.value = ${price} `; //stateを通じて渡す this.setState({ js: js }); } //Web側からのpostMessageに対応 onMessage = (event) => { const message = event.nativeEvent.data; this.props.navigation.navigate("Home"); alert(message); } render() { //js内の変数が処理されないうちにWebViewがレンダリングするのを防ぐ if (this.state.js === '') { return <Text>Loading...</Text> } //WebViewをレンダリング return ( <WebView source={{ uri: 'http://localhost:3000/' }} injectedJavaScript={this.state.js} onMessage={this.onMessage} /> ) } } export default Payment;かなり端折ってるけるけどとりあえず。
- 投稿日:2020-01-01T10:47:17+09:00
(2020年元旦時点で最新の)Stripeの決済をReactで使う
どこの決済サービスを利用するかは悩ましいところですが、業界標準のStripeはいずれにしてもおさえておきたい・・・ということで調査。意外と苦労したのでメモ。
前提知識
ネットに多くの情報がありますが、仕様が変化していて最新の情報を見つけるのに苦労しました。
事前に知っていればもっと楽だったことをまとめてみます。Stripeのサービス
Stripeが提供するサービスはいろいろある。
- PAYMENT(ま、普通の決済)
- BILLING(月額課金)
- CONNECT(プラットフォーマー用)
ここの記事では PAYMENT を扱います。
他にも色々ありますが、日本では使えないものもあるので注意(Issuingとか)。
PAYMENTの中でもいろいろ
1つのサービスの中でも自サイトへの埋め込み方法やAPIの種類など複数あります。
埋め込み方
- Checkoutを利用する(Stripeが用意した決済画面を利用する(自分のサイトに埋め込む))
- Stripe.js/Elementを利用する(パーツとして用意されたUIとJSを利用する)
決済(API群)の種類
2019年の9月にSCA Readyである必要が発生し、その対応のためにpaymentIntentが登場したもよう。
日本で言うカード情報の「非通過」、「非保持」のためのPCI-DSS対応のようなものなやつ。これが古い記事が参考にならない原因のようです。
- charge(古い => 事前にtokenを作るタイプのやつ(カード情報の処理が先))
- payementIntent(新しい => 事前にpaymentIntentを作る(カード情報の処理は後))
比較表が本家サイトにあります。
client側とserver側を実装する必要がある
プログラムはクライアントとサーバ側両方での実装が必要になります(めんどい)。
技術的には1つでもいい感じがしますが、paymentIntent作成リクエストに秘密鍵が必要なので、それを隠蔽するためかなという印象。
- server側プログラムが必要なのは 秘密鍵 を隠蔽するため(技術的にはなくても決済自体はできる)
React
これは私の用途限定。
- Reactに特化したelementとしてreact-stripe-elementsというパッケージがある
- 本家サイトで紹介されているのはcharge方式。ただ、paymentIntetにも対応している
- 本記事ではreact-stripe-elementsでpaymentIntentを利用する方法を紹介
ReactNativeだと現時点でtipsi-stripeとかを利用しないと行けないみたい(ExpoをEjectせずに利用できるライブラリは無いみたいです。。。)
paymentIntent方式のフロー
では、現時点で主流のpaymentIntetを利用する決済フローを見てみます。間違ってたらご指摘を。
フローでの処理は大きく2つ。
- 金額を投げてpaymentIntentを作成する(紐付いたclient_secret(tokenではない)が戻る)
- client_securetを利用してconfirmCardPayment()を実行すると、裏でカード情報が一緒にStripeサーバに送られる
という感じ。
まず、カード情報をStripeサーバに投げて、戻ってきたtokenを利用して金額等を投げる仕様とは逆なので注意。
図式化したイメージ。
実装
では上記を踏まえて実装してみます。
準備
Stripeのアカウントとかなければ作って下さい。あとはテスト用の公開キーと秘密キーがあればいいです。
- Stripeのアカウントを作る(なければ)
- ダッシュボードで左メニュー下段の「テストデータの表示」をOnした状態で「公開可能キー」と「シークレットキー」をメモしておく。
- テストだとpk_test_xxxx, sk_test_xxxxという形式。本番だとtestの部分がliveになる。
- 処理した結佐は左メニューの「支払い」から確認できる
完成図
完成予定は下記のような感じ。決済OKならアラート出します。
1つのクリックで上記2つの通信をします(ので分かりづらい)。
クライアント側
ではクライアント側から。流れはこの記事と同じですが、決済方式がchargeではなくpaymentIntetntになります。雛形作成にはcreate-react-appを利用します。
必要なモジュールのインストール。
create-react-app stripe-client cd stripe-client npm install --save react-stripe-elements bootstrap reactstrap formik yup実装。App.jsと同じ階層にCheckoutForm.jsを作成して下記のようにします。
CheckoutForm.jsimport React from 'react'; import { CardElement, injectStripe, CardNumberElement, CardExpiryElement, CardCVCElement, Elements } from 'react-stripe-elements'; import { Button, Form, FormGroup, Label, Input, FormFeedback } from 'reactstrap'; import { Formik } from 'formik' import * as Yup from 'yup'; class CheckoutForm extends React.Component { handlePayment = async (values) => { // alert(JSON.stringify(values)); const headers = new Headers(); headers.set('Content-type', 'application/json'); // headers.set('Access-Control-Allow-Origin', '*'); //paymentIntentの作成を(ローカルサーバ経由で)リクエスト const createRes = await fetch('http://localhost:9000/createPaymentIntent', { method: 'POST', headers: headers, body: JSON.stringify({ amount: values.amount, username: values.username }) }) //レスポンスからclient_secretを取得 const responseJson = await createRes.json(); const client_secret = responseJson.client_secret; //client_secretを利用して(確認情報をStripeに投げて)決済を完了させる const confirmRes = await this.props.stripe.confirmCardPayment(client_secret, { payment_method: { // card: this.props.elements.getElement('card'), card: this.props.elements.getElement('cardNumber'), billing_details: { name: values.username, } } }); if (confirmRes.paymentIntent.status === "succeeded") { alert("決済完了"); } } render() { console.log(this.props.stripe); return ( <div className="col-8"> <p>決済情報の入力</p> <Formik initialValues={{ amount: 100, username: 'TARO YAMADA' }} onSubmit={(values) => this.handlePayment(values)} validationSchema={Yup.object().shape({ amount: Yup.number().min(1).max(1000), })} > { ({ handleChange, handleSubmit, handleBlur, values, errors, touched }) => ( <Form onSubmit={handleSubmit}> <FormGroup> <Label>金額</Label> <Input type="text" name="amount" value={values.amount} onChange={handleChange} onBlur={handleBlur} invalid={Boolean(touched.amount && errors.amount)} /> <FormFeedback> {errors.amount} </FormFeedback> </FormGroup> <FormGroup> <Label>利用者名</Label> <Input type="text" name="username" value={values.username} onChange={handleChange} onBlur={handleBlur} invalid={Boolean(touched.username && errors.username)} /> <FormFeedback> {errors.username} </FormFeedback> </FormGroup> {/* <CardElement className="bg-light p-3" hidePostalCode={true} /> */} <legend className="col-form-label">カード番号</legend> <CardNumberElement ref={this.cardNumberRef} className="p-2 bg-light" /> <legend className="col-form-label">有効期限</legend> <CardExpiryElement className="p-2 bg-light" /> <legend className="col-form-label">セキュリティーコード</legend> <CardCVCElement className="p-2 bg-light" /> <Button onClick={this.submit} className="my-3" color="primary" > 購入 </Button> </Form> ) } </Formik> </div> ); } } export default injectStripe(CheckoutForm);App.jsでCheckoutForm.jsを読み込みます。また、鍵の設定等も行います。
App.jsimport React from 'react'; import { Elements, StripeProvider } from 'react-stripe-elements'; import CheckoutForm from './CheckoutForm'; function App() { return ( <StripeProvider apiKey="pk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"> <div className="container"> <h3 className="my-4">React Stripe Element Sample</h3> <Elements> <CheckoutForm /> </Elements> </div> </StripeProvider> ); } export default App;これでクライアント側は一旦完了。ボタンを押すと404エラーが出るはずです。
サーバ側
続いてサーバ側。
まず、必要なモジュールをインストールします。mkdir stripe-server cd stripe-server npm init -f npm install express body-parser stripeメイン実装。
index.jsconst app = require("express")(); const stripe = require("stripe")("sk_test_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); const cors = require('cors'); const bodyParser = require('body-parser'); app.use(require("body-parser").text()); app.use(cors()); app.use(bodyParser.urlencoded({ extended: true })); app.use(bodyParser.json()); app.post('/createPaymentIntent', async (req, res) => { const result = await stripe.paymentIntents.create({ amount: req.body.amount, currency: 'jpy', description: '●●商店決済', //option metadata: { username: req.body.username, tranId: '11111' } //option }); console.log(result); res.json(result); }); app.listen(9000, () => console.log("Listening on port 9000"));stripe.paymentIntetns.create()が裏でStripeサーバと通信をしてIntentを作成しています。
作成が完了したらクライアント側でに結果を戻します。動作確認
クライアント側
npm startサーバ側
node index.jsStripeダッシュボード
その他
サーバ側をFirebase Functionsに展開してみましたが、問題なく動きました。
あと、Functionsは1回以上実行される可能性もあるので冪等性を確保するためのkeyを付与したほうがいいという話があります。
- 投稿日:2020-01-01T03:45:54+09:00
DockerでReactの環境を作成してみた
はじめに
この記事ではJavaScriptのライブラリであるReactの環境をDockerを使用して構築したいと思います。
目次
- Dockerfileの用意
- Dockerfileをビルド
- コンテナの起動
- Reactアプリケーションの作成
- Reactアプリケーションの起動
- ブラウザで確認
- まとめ
1. Dockerfileの用意
今回はcreate-react-appを使用してReactの環境を構築していきます。
ディレクトリ構成は以下です。ディレクトリ構成. ├── app # Reactアプリケーションのフォルダ └── Dockerfile # React環境のDockerfileDockerfile# nodeのverを指定してDockerのイメージをpull FROM node:13.5.0 # Reactアプリケーション作成時に最低限の環境を提供してくれるライブラリをインストール RUN yarn global add create-react-app # コンテナ接続時のディレクトリを指定 WORKDIR /home # アプリケーションの起動時にコンテナで開放するポートを指定 EXPOSE 30002. Dockerfileをビルド
DockerfileからDockerイメージを作成します。
イメージ名はreact-tutorial
にします。
以下のコマンドをDockerfileが存在するディレクトリで実行してください。Dockerイメージのビルド$ docker build --rm -f "react-tutorial/Dockerfile" -t react-tutorial:latest "react-tutorial"3. コンテナの起動
起動すると以下のようになると思います。
これで、Reactアプリケーションが作成できる環境のコンテナに接続できたことになります。Dockerコンテナの起動$ docker run --rm -it -v ${PWD}/app:/home/react-tutorial -p 3000:3000/tcp react-tutorial:latest /bin/bash root@03887209ce2d:/home#4. Reactアプリケーションの作成
それでは、Reactアプリケーションの作成をしてきます。
作成にはcreate-react-app
を使用します。Reactアプリケーションの作成root@03887209ce2d:/home# create-react-app react-tutorial
コマンド実行中の表示(長いので畳んでおきます)
Creating a new React app in /home/react-tutorial. Installing packages. This might take a couple of minutes. Installing react, react-dom, and react-scripts with cra-template... yarn add v1.21.1 [1/4] Resolving packages... [2/4] Fetching packages... warning sha.js@2.4.11: Invalid bin entry for "sha.js" (in "sha.js"). info fsevents@1.2.9: The platform "linux" is incompatible with this module. info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. info fsevents@2.1.2: The platform "linux" is incompatible with this module. info "fsevents@2.1.2" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... warning "react-scripts > @typescript-eslint/eslint-plugin > tsutils@3.17.1" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta". [4/4] Building fresh packages... success Saved lockfile. success Saved 35 new dependencies. info Direct dependencies ├─ cra-template@1.0.0 ├─ react-dom@16.12.0 ├─ react-scripts@3.3.0 └─ react@16.12.0 info All dependencies ├─ @babel/plugin-proposal-class-properties@7.7.4 ├─ @babel/plugin-proposal-decorators@7.7.4 ├─ @babel/plugin-proposal-nullish-coalescing-operator@7.7.4 ├─ @babel/plugin-proposal-numeric-separator@7.7.4 ├─ @babel/plugin-proposal-optional-chaining@7.7.4 ├─ @babel/plugin-syntax-decorators@7.7.4 ├─ @babel/plugin-syntax-flow@7.7.4 ├─ @babel/plugin-syntax-nullish-coalescing-operator@7.7.4 ├─ @babel/plugin-syntax-numeric-separator@7.7.4 ├─ @babel/plugin-syntax-optional-chaining@7.7.4 ├─ @babel/plugin-transform-flow-strip-types@7.7.4 ├─ @babel/plugin-transform-runtime@7.7.4 ├─ @babel/plugin-transform-typescript@7.7.4 ├─ @babel/preset-typescript@7.7.4 ├─ @types/parse-json@4.0.0 ├─ babel-plugin-macros@2.7.1 ├─ babel-plugin-named-asset-import@0.3.5 ├─ babel-preset-react-app@9.1.0 ├─ core-js@3.6.1 ├─ cra-template@1.0.0 ├─ eslint-config-react-app@5.1.0 ├─ fork-ts-checker-webpack-plugin@3.1.0 ├─ lines-and-columns@1.1.6 ├─ open@7.0.0 ├─ promise@8.0.3 ├─ raf@3.4.1 ├─ react-app-polyfill@1.0.5 ├─ react-dev-utils@10.0.0 ├─ react-dom@16.12.0 ├─ react-error-overlay@6.0.4 ├─ react-scripts@3.3.0 ├─ react@16.12.0 ├─ scheduler@0.18.0 ├─ whatwg-fetch@3.0.0 └─ yaml@1.7.2 Done in 36.57s. Installing template dependencies using yarnpkg... yarn add v1.21.1 [1/4] Resolving packages... [2/4] Fetching packages... info fsevents@2.1.2: The platform "linux" is incompatible with this module. info "fsevents@2.1.2" is an optional dependency and failed compatibility check. Excluding it from installation. info fsevents@1.2.9: The platform "linux" is incompatible with this module. info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... warning "react-scripts > @typescript-eslint/eslint-plugin > tsutils@3.17.1" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta". warning " > @testing-library/user-event@7.2.1" has unmet peer dependency "@testing-library/dom@>=5". [4/4] Building fresh packages... success Saved lockfile. success Saved 18 new dependencies. info Direct dependencies ├─ @testing-library/jest-dom@4.2.4 ├─ @testing-library/react@9.4.0 ├─ @testing-library/user-event@7.2.1 ├─ react-dom@16.12.0 └─ react@16.12.0 info All dependencies ├─ @sheerun/mutationobserver-shim@0.3.2 ├─ @testing-library/dom@6.11.0 ├─ @testing-library/jest-dom@4.2.4 ├─ @testing-library/react@9.4.0 ├─ @testing-library/user-event@7.2.1 ├─ @types/prop-types@15.7.3 ├─ @types/react-dom@16.9.4 ├─ @types/react@16.9.17 ├─ @types/testing-library__dom@6.11.0 ├─ @types/testing-library__react@9.1.2 ├─ css.escape@1.5.1 ├─ csstype@2.6.8 ├─ min-indent@1.0.0 ├─ react-dom@16.12.0 ├─ react@16.12.0 ├─ redent@3.0.0 ├─ strip-indent@3.0.0 └─ wait-for-expect@3.0.1 Done in 9.58s. Removing template package using yarnpkg... yarn remove v1.21.1 [1/2] Removing module cra-template... [2/2] Regenerating lockfile and installing missing dependencies... info fsevents@2.1.2: The platform "linux" is incompatible with this module. info "fsevents@2.1.2" is an optional dependency and failed compatibility check. Excluding it from installation. info fsevents@1.2.9: The platform "linux" is incompatible with this module. info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. warning " > @testing-library/user-event@7.2.1" has unmet peer dependency "@testing-library/dom@>=5". warning "react-scripts > @typescript-eslint/eslint-plugin > tsutils@3.17.1" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta". success Uninstalled packages. Done in 7.62s. Success! Created react-tutorial at /home/react-tutorial Inside that directory, you can run several commands: yarn start Starts the development server. yarn build Bundles the app into static files for production. yarn test Starts the test runner. yarn eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back! We suggest that you begin by typing: cd react-tutorial yarn start Happy hacking!5. Reactアプリケーションの起動
Reactアプリケーションが作成されたので、実際に起動します。
Reactアプリケーションの起動# ディレクトリ確認 root@03887209ce2d:/home# ls node react-tutorial root@03887209ce2d:/home# cd react-tutorial/ # Reactアプリケーションの起動 root@03887209ce2d:/home/react-tutorial# yarn start6. ブラウザで確認
yarn start
を実行すると以下のように出力されます。Reactアプリケーションの起動後Compiled successfully! You can now view react-tutorial in the browser. Local: http://localhost:3000/ On Your Network: http://172.17.0.3:3000/ Note that the development build is not optimized. To create a production build, use yarn build.
あとは、ブラウザから
http://localhost:3000/
にアクセスしてみましょう。
以下のような表示がされれば完了です。
7. まとめ
Dockerを使用してReactの環境をお手軽に作成できました。
ホストマシンの環境をいじらずにお試しで環境を構築できるのはめっちゃ便利ですよね!!
あとは、appフォルダ配下のファイルを編集してアプリケーションを作成していくのみです!!
指摘や質問があれば大歓迎なので、是非よろしくお願いします。
以上です。ありがとうございました!
- 投稿日:2020-01-01T03:45:54+09:00
DockerでReactの環境を作成してみた。
はじめに
この記事ではJavaScriptのライブラリであるReactの環境をDockerを使用して構築したいと思います。
目次
- Dockerfileの用意
- Dockerfileをビルド
- コンテナの起動
- Reactアプリケーションの作成
- Reactアプリケーションの起動
- ブラウザで確認
- まとめ
1. Dockerfileの用意
今回はcreate-react-appを使用してReactの環境を構築していきます。
# nodeのverを指定してDockerのイメージをpull FROM node:13.5.0 # Reactアプリケーション作成時に最低限の環境を提供してくれるライブラリをインストール RUN yarn global add create-react-app # コンテナ接続時のディレクトリを指定 WORKDIR /home # アプリケーションの起動時にコンテナで開放するポートを指定 EXPOSE 30002. Dockerfileをビルド
DockerfileからDockerイメージを作成します。
イメージ名はreact-tutorial
にします。
以下のコマンドをDockerfileが存在するディレクトリで実行してください。$ docker build --rm -f "react-tutorial/Dockerfile" -t react-tutorial:latest "react-tutorial"3. コンテナの起動
$ docker run --rm -it -p 3000:3000/tcp react-tutorial:latest /bin/bash起動すると以下のようになると思います。
これで、Reactアプリケーションが作成できる環境のコンテナに接続できたことになります。$ docker run --rm -it -v ${PWD}/src:/home/react-tutorial -p 3000:3000/tcp react-tutorial:latest /bin/bash root@03887209ce2d:/home#4. Reactアプリケーションの作成
それでは、Reactアプリケーションの作成をしてきます。
root@03887209ce2d:/home# create-react-app react-tutorial
コマンド実行中の表示(長いので畳んでおきます)
Creating a new React app in /home/react-tutorial. Installing packages. This might take a couple of minutes. Installing react, react-dom, and react-scripts with cra-template... yarn add v1.21.1 [1/4] Resolving packages... [2/4] Fetching packages... warning sha.js@2.4.11: Invalid bin entry for "sha.js" (in "sha.js"). info fsevents@1.2.9: The platform "linux" is incompatible with this module. info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. info fsevents@2.1.2: The platform "linux" is incompatible with this module. info "fsevents@2.1.2" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... warning "react-scripts > @typescript-eslint/eslint-plugin > tsutils@3.17.1" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta". [4/4] Building fresh packages... success Saved lockfile. success Saved 35 new dependencies. info Direct dependencies ├─ cra-template@1.0.0 ├─ react-dom@16.12.0 ├─ react-scripts@3.3.0 └─ react@16.12.0 info All dependencies ├─ @babel/plugin-proposal-class-properties@7.7.4 ├─ @babel/plugin-proposal-decorators@7.7.4 ├─ @babel/plugin-proposal-nullish-coalescing-operator@7.7.4 ├─ @babel/plugin-proposal-numeric-separator@7.7.4 ├─ @babel/plugin-proposal-optional-chaining@7.7.4 ├─ @babel/plugin-syntax-decorators@7.7.4 ├─ @babel/plugin-syntax-flow@7.7.4 ├─ @babel/plugin-syntax-nullish-coalescing-operator@7.7.4 ├─ @babel/plugin-syntax-numeric-separator@7.7.4 ├─ @babel/plugin-syntax-optional-chaining@7.7.4 ├─ @babel/plugin-transform-flow-strip-types@7.7.4 ├─ @babel/plugin-transform-runtime@7.7.4 ├─ @babel/plugin-transform-typescript@7.7.4 ├─ @babel/preset-typescript@7.7.4 ├─ @types/parse-json@4.0.0 ├─ babel-plugin-macros@2.7.1 ├─ babel-plugin-named-asset-import@0.3.5 ├─ babel-preset-react-app@9.1.0 ├─ core-js@3.6.1 ├─ cra-template@1.0.0 ├─ eslint-config-react-app@5.1.0 ├─ fork-ts-checker-webpack-plugin@3.1.0 ├─ lines-and-columns@1.1.6 ├─ open@7.0.0 ├─ promise@8.0.3 ├─ raf@3.4.1 ├─ react-app-polyfill@1.0.5 ├─ react-dev-utils@10.0.0 ├─ react-dom@16.12.0 ├─ react-error-overlay@6.0.4 ├─ react-scripts@3.3.0 ├─ react@16.12.0 ├─ scheduler@0.18.0 ├─ whatwg-fetch@3.0.0 └─ yaml@1.7.2 Done in 36.57s. Installing template dependencies using yarnpkg... yarn add v1.21.1 [1/4] Resolving packages... [2/4] Fetching packages... info fsevents@2.1.2: The platform "linux" is incompatible with this module. info "fsevents@2.1.2" is an optional dependency and failed compatibility check. Excluding it from installation. info fsevents@1.2.9: The platform "linux" is incompatible with this module. info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. [3/4] Linking dependencies... warning "react-scripts > @typescript-eslint/eslint-plugin > tsutils@3.17.1" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta". warning " > @testing-library/user-event@7.2.1" has unmet peer dependency "@testing-library/dom@>=5". [4/4] Building fresh packages... success Saved lockfile. success Saved 18 new dependencies. info Direct dependencies ├─ @testing-library/jest-dom@4.2.4 ├─ @testing-library/react@9.4.0 ├─ @testing-library/user-event@7.2.1 ├─ react-dom@16.12.0 └─ react@16.12.0 info All dependencies ├─ @sheerun/mutationobserver-shim@0.3.2 ├─ @testing-library/dom@6.11.0 ├─ @testing-library/jest-dom@4.2.4 ├─ @testing-library/react@9.4.0 ├─ @testing-library/user-event@7.2.1 ├─ @types/prop-types@15.7.3 ├─ @types/react-dom@16.9.4 ├─ @types/react@16.9.17 ├─ @types/testing-library__dom@6.11.0 ├─ @types/testing-library__react@9.1.2 ├─ css.escape@1.5.1 ├─ csstype@2.6.8 ├─ min-indent@1.0.0 ├─ react-dom@16.12.0 ├─ react@16.12.0 ├─ redent@3.0.0 ├─ strip-indent@3.0.0 └─ wait-for-expect@3.0.1 Done in 9.58s. Removing template package using yarnpkg... yarn remove v1.21.1 [1/2] Removing module cra-template... [2/2] Regenerating lockfile and installing missing dependencies... info fsevents@2.1.2: The platform "linux" is incompatible with this module. info "fsevents@2.1.2" is an optional dependency and failed compatibility check. Excluding it from installation. info fsevents@1.2.9: The platform "linux" is incompatible with this module. info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation. warning " > @testing-library/user-event@7.2.1" has unmet peer dependency "@testing-library/dom@>=5". warning "react-scripts > @typescript-eslint/eslint-plugin > tsutils@3.17.1" has unmet peer dependency "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta". success Uninstalled packages. Done in 7.62s. Success! Created react-tutorial at /home/react-tutorial Inside that directory, you can run several commands: yarn start Starts the development server. yarn build Bundles the app into static files for production. yarn test Starts the test runner. yarn eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back! We suggest that you begin by typing: cd react-tutorial yarn start Happy hacking!5. Reactアプリケーションの起動
Reactアプリケーションが作成されたので、実際に起動します。
# ディレクトリ確認 root@03887209ce2d:/home# ls node react-tutorial root@03887209ce2d:/home# cd react-tutorial/ # Reactアプリケーションの起動 root@03887209ce2d:/home/react-tutorial# yarn start6. ブラウザで確認
yarn start
を実行すると以下のように出力されます。Compiled successfully! You can now view react-tutorial in the browser. Local: http://localhost:3000/ On Your Network: http://172.17.0.3:3000/ Note that the development build is not optimized. To create a production build, use yarn build.あとは、ブラウザから
http://localhost:3000/
にアクセスしてみましょう。
以下のような表示がされれば完了です。
7. まとめ
Dockerを使用してReactの環境をお手軽に作成できました。
ホストマシンの環境をいじらずにお試しで環境を構築できるのはめっちゃ便利ですよね!!
あとは、srcフォルダ配下のファイルを編集してアプリケーションを作成していくのみです!!
指摘や質問があれば大歓迎なので、是非よろしくお願いします。
以上です。ありがとうございました!
- 投稿日:2020-01-01T01:22:39+09:00
React + TensorFlow.jsでAdversarial Example (FGSM)をやった
はじめに
もうすぐ2019年が終わりますね!
あけました。はい。
そういうわけでTensorFlow.jsでAdversarial Exampleを体験できるデモを公開しました。
以下で遊べます。(特に学習の際は)重いので注意してください。真っ白なページになる場合は何度かリロードすると画面がちゃんと表示されることがあります。
https://adv-examples-fun.netlify.com/リポジトリ:https://github.com/Catminusminus/adv-examples-fun
順次ボタンを押していき、データのロード〜Adversarial Exampleの生成まで終わった後
以下、詳しい説明です。
Adversarial Example
Adversarial Exampleについて、画像分類の場合の説明をします。ここに人間の目にも、機械学習モデルにも1に見える手書き数字の画像があるとします。これに微小な摂動を加えることで、人間の目には1に見えるままで、機械学習モデルは別の数字、例えば9と誤分類させることができます。実際、これが上のスクリーンショットで起こっていることです(もとの画像は1と分類しているが、生成された摂動を0.3倍して足すと、9と誤分類している)。
今は手書きの数字で説明しましたが、それ以外でももちろんできます。よくパンダをテナガザルに誤分類させる例が挙げられます。
今回使用しているAdversarial Exampleの手法はFast Gradient Sign Method(FGSM)というものです。これは攻撃の対象となるモデルのlossを使うため、white-box attackであり、また特定のラベルへ誤分類を誘導できるのではなく、とにかく正解のラベルと違うラベルに分類させるという、untargeted attackです。
詳細は論文を参照してください。TensorFlow.js
TensorFlow.jsはJavaScriptの機械学習ライブラリです。なのでブラウザで動きます。今回はこれを用いて機械学習モデルの構築とFGSM Attackを行いました。
モデルの構築は、https://github.com/tensorflow/tfjs-examples/tree/master/mnist をTypeScriptで動くようにしただけです。ほとんどanyでサボっていますが…
で、FGSMは、TensorFlowによる実装が https://www.tensorflow.org/tutorials/generative/adversarial_fgsm で公開されています。これをTensorFlow.jsで書けば終わりです。
余談ですが、私が見たときにはFGSMの実装が間違っていて、修正するPRを出し、(修正後)マージされました。しばらく反映を待っていましたが、今見たら反映されていました。なので皆さんも上のチュートリアルでFGSMをやっていきましょう。攻撃部分だけ簡単に解説します。コードは以下です。
const testExamples = 100 const examples = data.getTestData(testExamples) tf.tidy(() => { const output = model.predict(examples.xs) const axis = 1 const labels = Array.from(examples.labels.argMax(axis).dataSync()) const predictions = Array.from(output.argMax(axis).dataSync()) const accIndices = selectAccurateExample(labels, predictions) const index = accIndices[Math.floor(Math.random() * accIndices.length)] const image = examples.xs.slice([index, 0], [1, examples.xs.shape[1]]) const loss = (input: any) => tf.metrics.categoricalCrossentropy( examples.labels.slice([index, 0], [1, examples.labels.shape[1]]), model.predict(input), ) const grad = tf.grad(loss) const signedGrad = tf.sign(grad(image)) const scalar = tf.scalar(0.3, 'float32') const outputAdv = model.predict(signedGrad.mul(scalar).add(image)) const predictionsAdv = Array.from(outputAdv.argMax(axis).dataSync()) })まず100件テストデータを取ってきます。
const testExamples = 100 const examples = data.getTestData(testExamples)次に、メモリリークを防ぐため、
tf.tidy
で囲っています。
そうしたら、100件のデータのうち、モデルの予測が正解ラベルと同じデータを取ってきます。そもそも予測があってないと攻撃も何もないからです。const output = model.predict(examples.xs) const axis = 1 const labels = Array.from(examples.labels.argMax(axis).dataSync()) const predictions = Array.from(output.argMax(axis).dataSync()) const accIndices = selectAccurateExample(labels, predictions) const index = accIndices[Math.floor(Math.random() * accIndices.length)] const image = examples.xs.slice([index, 0], [1, examples.xs.shape[1]])次が、チュートリアルでいうところの
loss_object = tf.keras.losses.CategoricalCrossentropy() def create_adversarial_pattern(input_image, input_label): with tf.GradientTape() as tape: tape.watch(input_image) prediction = pretrained_model(input_image) loss = loss_object(input_label, prediction) # Get the gradients of the loss w.r.t to the input image. gradient = tape.gradient(loss, input_image) # Get the sign of the gradients to create the perturbation signed_grad = tf.sign(gradient) return signed_gradに当たる部分です。それが以下です。
const loss = (input: any) => tf.metrics.categoricalCrossentropy( examples.labels.slice([index, 0], [1, examples.labels.shape[1]]), model.predict(input), ) const grad = tf.grad(loss) const signedGrad = tf.sign(grad(image))あとは摂動を元画像に加え、それをモデルに食わせるだけです。
const scalar = tf.scalar(0.3, 'float32') const outputAdv = model.predict(signedGrad.mul(scalar).add(image)) const predictionsAdv = Array.from(outputAdv.argMax(axis).dataSync())Reactで表示
上記で作った画像を表示するため、React Konvaを使いました。つまりcanvasを使っています。当初プラスの記号等を
@material-ui
のアイコンとしてLayer
の間に突っ込めないかやっていましたが、無理っぽいです(無理という情報源も当時見つけていた気がするのですが、覚えていません)。諦めてLine
で線を引いています。方法があれば教えていただけると喜びます。おわりに
記事を書き終わったら2019年も終わっていました。
ところで、TensorFlow.jsでAdversarial Exampleやってる人は他にいないと思ってたらなんと先駆者がいました。遥かにこちらの方がすごいので見ることをオススメします。
今後はデザイン周りをもう少しどうにかするとか他の攻撃手法への対応とかoff-the-main-thread対応とか無駄にPWA対応とかしていきたいです。
それではよい2020年を。Citation
FGSM is described by
@misc{goodfellow2014explaining, title={Explaining and Harnessing Adversarial Examples}, author={Ian J. Goodfellow and Jonathon Shlens and Christian Szegedy}, year={2014}, eprint={1412.6572}, archivePrefix={arXiv}, primaryClass={stat.ML} }.