- 投稿日:2019-03-30T22:48:45+09:00
React vs React Static速度対決...したけど、引き分け
結論
React製の超シンプルなWEBページを静的サイトジェネレータ「React Static」で静的ページにしたけど、別に速くならなかった
https://speed-test-react.netlify.com/
https://speed-test-react-static.netlify.com/背景
コンポーネント指向の書き方が大好きなので、全然状態の変化しないフツーのサイトもReactで書きたくなりました。
ただ、表示が遅くなりそう...
ただ、誰がいつ見ても同じ内容なのに、一々ブラウザのJSでレンダリングしてもらうなんて...静的サイトジェネレータを使えば、高速表示のままReactで開発できるのでは...?
やったこと
画像とテキストが並ぶ超シンプルなSP前提のWEBページをReactとReact Staticで作ってみました。
Ajax通信も一切ありません。React版
https://speed-test-react.netlify.com/
https://github.com/IYA-UFO/speed-test-react
- React
- create-react-app
- styled-components
- BootStrap4のCSS
- 合計700KB程度の画像 一部別サーバー
React Static版
https://speed-test-react-static.netlify.com/
https://github.com/IYA-UFO/speed-test-react-static上記+React Static
比較ツール
パフォーマンス測定超初心者なので、ググりながらやりました。
Chrome devtools ダウンロード速度10MB CPU4倍遅い設定(MacBook Pro)
比較観点
静的ページ、特にマーケティング系のページにとって、重要なのはファーストビューがちゃんと見えるまでの時間です。
それを比べたい、...いざ、勝負
結果
Chrome devtools Performanceタブ
変わらねぇええ!!
画面左端がindex.htmlを取得した瞬間です。これより前の出来事にフロントエンドは関係ありません。
上の波みたいなのがCPUの使用量を示します。
大雑把に以下です。
- 青 HTMLパース
- 黄色 JS実行
- 紫 CSS適用
よく見ると、React側はレンダリング前に50msほどJSの実行時間があります。これが心配していた「遅延」です。
React Staticは、その賢い仕様上、HTML、CSSの処理が終わった後にイベントなどを処理するJSをマウントしています。ただ、今回は活躍の機会はありません。Chrome devtools Networkタブ
ちょっと...違う..?
今回のページにはファーストビューにヒーローイメージ?(320KBのやつ)があるので、その子の表示が体感速度のカギとなります。
React側はmain.jsによるHTML生成が終わるまでヒーローイメージをリクエストできないのに対し、React Staticは最初からMax6本リクエストを出していると思われます。おかげで700msほど?速く落とせています!見た目
変わらねぇええ
主題はファーストビューの高速表示でしたが、複数回やれば逆転するレベルで同じです。
React+styled-componentsが裏でこそこそ何しているのかは知りませんが、React Staticは古き良きHTML⇨CSS追加の表示ですね。結論
超シンプルなReactアプリケーション?を静的ジェネレータにかけても、表示がすごく速くなることはない。
感想
パフォーマンスの測定
dentoolsの表示の2割も理解していません。ブラウザの動きを深く知らないとキビシイ世界のようです。
devtoolsの見方を体系的に学びたい...静的サイトジェネレータ
今回のReactさんは50msでHTMLを出しましたが、本格的なReactアプリケーションでは数秒かかることも珍しくないと思います。静的サイトでも本格的に運用すれば数十のコンポーネントを使うわけで、そのとき、静的サイトジェネレータの真の力が唸るのかもしれません。
もちろん、CMSからAjaxでデータを取得する場合、ジェネレータによる事前ビルドの効果は明らかでしょう。
おわりに
ブラウザのことよく知らないのに背伸びしました。仮説、間違いのご指摘、なんでも全力で歓迎いたします。
それまで、Reactでいいや
- 投稿日:2019-03-30T22:05:48+09:00
Electron & React & Redux & TypeScript アプリ作成ワークショップ をやってみた2
https://qiita.com/y_ohr/items/5c18dd1621b5342a05ea の続き。
概要
以下をやってみた記録。良記事に感謝。
- https://qiita.com/EBIHARA_kenji/items/25e59f7132b96cb886f3 (前回済)
- https://qiita.com/EBIHARA_kenji/items/e6da1c3d6d16cf07b60a (前回済)
- https://qiita.com/EBIHARA_kenji/items/1a043794014dc2f3a7db (途中まで前回済)
環境
Node.jsとnpmのバージョン$ node -v v10.13.0 $ npm -v 6.4.1component の作成
ユーザー名入力画面の作成
ts/components/UserForm.tsximport React from 'react'; import IUser from '../states/IUser'; import { TextBox } from './TextBox'; /** * ユーザ名を入力して表示する */ class UserForm extends React.Component<IUser, {}>{ public render() { return ( <div> <p> <TextBox label="ユーザー名" type="text" value={this.props.name} onChangeText={this.onChangeText} /> </p> <p>名前: {this.props.name}</p> </div> ); } private onChangeText = (value: string) => { // action や store ができてから書く } }action と action creator の作成
uuidインストール$ npm install --save uuid && npm install --save-dev @types/uuidts/actions/UserNameEvents.tsimport Redux from 'redux'; import { v4 as UUID } from 'uuid'; /** * ユーザー名を変更するアクション・タイプ */ export const CHANGE_USER_NAME = UUID(); /** * ユーザー名を変更するアクション */ export interface IChangeUserNameAction extends Redux.Action { /** 変更する名前の文字列 */ name: string; } /** * ユーザー名変更アクション・クリエイター * @param name 変更する名前の文字列 * @return ユーザー名変更アクション */ export const createChangeUserNameAction: Redux.ActionCreator<IChangeUserNameAction> = (name: string) => { return { name, type: CHANGE_USER_NAME, }; };reducer を作成する
cloneインストールnpm install --save clone && npm install --save-dev @types/clonets/reducers/UserReducer.tsimport Clone from 'clone'; import Redux from 'redux'; import { CHANGE_USER_NAME, IChangeUserNameAction } from '../actions/UserNameEvents'; import IUser, { initUser } from '../states/IUser'; export const UserReducer: Redux.Reducer<IUser> = (childState = initUser, action) => { let newChildState: IUser = childState; switch (action.type) { case CHANGE_USER_NAME: { newChildState = Clone(childState); newChildState.name = (action as IChangeUserNameAction).name; } break; } return newChildState; };store を作成する
ts/Store.tsimport { combineReducers, createStore } from 'redux'; import { UserReducer } from './reducers/UserReducer'; import IUser from './states/IUser'; /** * store のデータ型を定義する。(親state) * * プロパティには、管理する child_state を指定する */ export interface IState { User: IUser; // state が増えたら足していく } // 複数の reducer を束ねる const combinedReducers = combineReducers<IState>({ User: UserReducer, // reducer が増えたら足していく }); // グローバルオブジェクトとして、store を作成する。 export const store = createStore(combinedReducers); // improt store from './Store' とアクセスできるように default として定義する export default store;store と component を連結させる
ts/components/UserForm.tsxdiff --git a/ts/components/UserForm.tsx b/ts/components/UserForm.tsx index 36a22f1..6c3f614 100644 --- a/ts/components/UserForm.tsx +++ b/ts/components/UserForm.tsx @@ -1,5 +1,7 @@ import React from 'react'; +import { connect, MapStateToPropsParam } from 'react-redux'; // 追加 import IUser from '../states/IUser'; +import { IState } from '../Store'; // 追加 import { TextBox } from './TextBox'; /** @@ -22,3 +24,9 @@ class UserForm extends React.Component<IUser, {}>{ // action や store ができてから書く } } +// 追加 --> +const mapStateToProps = (state: IState) => { + return state.User; +}; +export default connect(mapStateToProps)(UserForm); +// <- 追加component から action を reducer に送信する
ts/components/UserForm.tsxdiff --git a/ts/components/UserForm.tsx b/ts/components/UserForm.tsx index 6c3f614..a51d8f8 100644 --- a/ts/components/UserForm.tsx +++ b/ts/components/UserForm.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { connect, MapStateToPropsParam } from 'react-redux'; // 追加 import IUser from '../states/IUser'; -import { IState } from '../Store'; // 追加 +import { createChangeUserNameAction } from '../actions/UserNameEvents'; // 追加 +import store, { IState } from '../Store'; // 変更 import { TextBox } from './TextBox'; /** @@ -21,7 +22,7 @@ class UserForm extends React.Component<IUser, {}>{ } private onChangeText = (value: string) => { - // action や store ができてから書く + store.dispatch(createChangeUserNameAction(value)); } } // 追加 -->HTMLへのレンダリング
ts/index.tsxdiff --git a/ts/index.tsx b/ts/index.tsx index c25e4e5..58d6af1 100644 --- a/ts/index.tsx +++ b/ts/index.tsx @@ -1,9 +1,15 @@ import React from 'react'; import ReactDom from 'react-dom'; +import { Provider } from 'react-redux'; // 追加 +import UserForm from './components/UserForm'; // 追加 +import Store from './Store'; // 追加 const container = document.getElementById('contents'); - +// 変更 --> ReactDom.render( - <p>こんにちは、世界</p>, + <Provider store={Store}> + <UserForm /> + </Provider>, container, ); +// 変更 <--ビルドして動作確認する。
webpack実行とelectron起動$ npm run build $ npm start動いた!
資産構成
感想
- ちゃんと動いて感動した
- Reduxに慣れが必要
- クラスや定数のimportが縦横無尽で追えなくなる。脳内マップが必要。
- ライブラリに追加した
uuidやcloneは一般的なベストプラクティスなのだろうか?- componentなどのUI関連クラスと、actionなどのRedux関連クラスは、もう少しフォルダ等を整理して、関心を分離できないのだろうか?こういうものなのだろうか。
- VSCodeでソースを整形しても、tslintに警告されないようにしたい。
以上
- 投稿日:2019-03-30T20:52:13+09:00
TypeScript: Duplicate identifier 'LibraryManagedAttributes'. 解消法
環境
ts-lint: 5.14.0
yarn: 1.15.2
node: v10.15.1エラーログ
/Users/ユーザー名/プロジェクト名/node_modules/@types/react-dom/node_modules/@types/react/index.d.ts
(2777,14): Duplicate identifier 'LibraryManagedAttributes'.原因
調べてみたところ、LibraryManagedAttributesの定義が
①node_modules/@types/react/index.d.ts
②node_modules/@types/react-dom/node_modules/@types/react/index.d.ts
と2箇所存在。
@types/reactの定義がyarn.lockにバージョン違いで2つ定義されていたことが原因。解消法
①package.jsonに"resolutions"を追加。
②yarn install実施package.json"devDependencies": { "@types/react": "^16.8.10", "@types/react-dom": "^16.8.3", ... }, "resolutions": { "@types/react": "^16.8.10" }③yarn.lockを確認してみると
"@types/react@*", "@types/react@^16.8.10":
といった形でバージョンを統一。yarn.lock"@types/react-dom@^16.8.3": version "16.8.3" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.8.3.tgz#6131b7b6158bc7ed1925a3374b88b7c00481f0cb" integrity sha512-HF5hD5YR3z9Mn6kXcW1VKe4AQ04ZlZj1EdLBae61hzQ3eEWWxMgNLUbIxeZp40BnSxqY1eAYLsH9QopQcxzScA== dependencies: "@types/react" "*" "@types/react@*", "@types/react@^16.8.10": version "16.8.10" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.10.tgz#1ccb6fde17f71a62ef055382ec68bdc379d4d8d9" integrity sha512-7bUQeZKP4XZH/aB4i7k1i5yuwymDu/hnLMhD9NjVZvQQH7ZUgRN3d6iu8YXzx4sN/tNr0bj8jgguk8hhObzGvA== dependencies: "@types/prop-types" "*" csstype "^2.2.0" ...参考サイト
- 投稿日:2019-03-30T18:31:13+09:00
AWS S3を使ってホームページを公開する
背景
独自ドメインを取得してホームページを開設したかった。
レンタルサーバを利用しなくてもAWSのS3を利用して簡単にできるようだったので、試しにやってみる。
参考(AWSチュートリアル)全体像
作業概要
だいたいの作業手順はこんな感じです。
- 独自ドメインの購入
- バケット作成
- Route53へバケットのエイリアスレコード追加
作業内容
独自ドメインの購入
AWSドキュメントに、より詳細な内容が書かれていますので参考に。AWSドキュメント
- AWSコンソールにログイン後、
サービスからRoute53を選択する。画面真ん中あたりのテキストボックス(Type a domain name)に取得したいドメイン名を入力し、Checkボタンをクリックする。(.com以外のドメインが欲しい場合は右隣のリストボックスから選択すること)
Add to cartをクリックし、欲しいドメインをカートに加える。
(画像はgetsampleapp.comをカートに加えた後のキャプチャです)
画面を下にスクロールしてcontinueボタンをクリックする。
購入します。
以下の作業を行うと、Complete Purchase作成が完了するとroute53のRegistered domainsに出現します。
バケットの作成
AWSサービスの中からS3を選択し、バケットを作成する。
ホスティングするウェブサイトと同じ名前のバケットを作成すること(AWSチュートリアル参考)。バケットポリシーを設定する
画面下のポリシージェネレータを利用すると簡単にjsonを生成することができます。
こんな感じで良いかと思います(ARNは作成したS3バケットのARNを設定してください)
Generate Policyボタンをクリックし、画面に表示されたポリシーをコピペして貼り付けてください。
※バケットのパブリックアクセス設定内の新規のパブリックバケットポリシーをブロックする (推奨)にチェックが入っているとポリシーの保存ができないので要注意です(そりゃそうか...って感じですが)。
ウェブサイトのホストとして設定する
AWSチュートリアルを参考に、S3のバケットをウェブサイトのホストとして利用する設定をします。
ひとまず簡単なindex.htmlを作成してホスティングの設定をするのが良いかと思います。S3エンドポイントへアクセスしてみる
設定がうまくいっていればエンドポイントへアクセスできるはずなのでお試ししてみてください。
AccessDenyエラーが出るときはバケットのアクセス権限周りの設定を再確認してみると良いかもしれません。Route53へエイリアスレコードの追加
AWSチュートリアルのエイリアスレコードを追加するを参考に、エイリアスレコードを設定します。
Nameと同じ名前でないとAlias Targetとして表示されないので注意です。同じ名前のバケット名を作成したのはそのためか。と一人で納得しました。
作成後、
http://xxxx.com(xxxxは購入したドメイン名)にアクセスすると、いい感じにアクセスできます。詰まったところ
全体的にあまりつまるところはなかったけど、地味にちょこちょこ引っかかってしまいました...
バケットポリシーが保存できない
S3アクセス権限の「パブリックアクセス設定」
新規のパブリックバケットポリシーをブロックするにチェックが入ってた。チェックを外して保存できたS3のエンドポイントにアクセスしてもコンテンツが表示されない
S3アクセス権限の「パブリックアクセス設定」
バケットにパブリックポリシーがある場合、パブリックアクセスとクロスアカウントアクセスをブロックするにチェックが入ってた。チェックを外してアクセスできたエイリアスターゲットにS3のバケットが表示されない
バケット名のスペルが誤っていた。正しい名前(Route53レコード追加画面の
Nameと同じ名前)のバケット名を作成したらエイリアスターゲットとして選択することができた今後の課題と感想
ACMとCloudFrontを利用してHTTPS化したい。文書を書くのに途中で力尽きてしまって急に適当になってしまいがちなので、なんとか工夫したい...段々雑になってごめんなさい...
- 投稿日:2019-03-30T17:11:49+09:00
Reactでinput type=fileのフォームを作っていい感じにスタイルする
input要素って、typeがfileになると途端にスタイルが面倒くさくなりますよね。
reactでそれっぽいコンポーネントを作ったので、残しておきます。import React, { useState } from 'react'; import styled from 'styled-components'; const Wrapper = styled.div` display: flex; `; const Label = styled.label` お好きにどうぞ `; const Input = styled.input` display: none; `; const FileName = styled.p` お好きにどうぞ `; const onChange = (event, cb, setFileName) => { cb(event); const targetName = event.target.files.item(0).name; setFileName(targetName); }; const InputFile = props => { const [filename, setFileName] = useState('選択されていません'); if (props.type !== 'file') return <p>typeの指定、間違ってるよ</p>; return ( <Wrapper> <Label> ファイルを選択 <Input {...props} onChange={e => onChange(e, props.onChange, setFileName)} /> </Label> <FileName>{filename}</FileName> </Wrapper> ); }; export default InputFile;使い方はこんな感じ
<InputFile type='file' accept='image/*' onChange={e => なんか好きな処理} />
- 投稿日:2019-03-30T16:01:37+09:00
Reactと他のライブラリとのインテグレーション
公式サイトではjQueryプラグイン(Chosen)のラッパーのサンプルコードが掲載されていますが
実際に要素を追加したときの動きの違いを確認しました。
componentDidUpdateにあるChosenライブライのtriggerを呼び出さないと、
Reactがstateの変化を検知してrenderを実行してもChosenが生成するドロップダウンのリストには反映されません。App.jsimport React, { Component } from "react"; import "./App.css"; import $ from "jquery"; import "chosen-js/chosen.css"; import "chosen-js/chosen.jquery.js"; class Chosen extends React.Component { componentDidMount() { this.$el = $(this.el); this.$el.chosen(); this.handleChange = this.handleChange.bind(this); this.$el.on("change", this.handleChange); } componentDidUpdate(prevProps) { if (prevProps.children !== this.props.children) { this.$el.trigger("chosen:updated"); //ここをコメントアウトすると・・・ } } componentWillUnmount() { this.$el.off("change", this.handleChange); this.$el.chosen("destroy"); } handleChange(e) { this.props.onChange(e.target.value); } render() { return ( <div> <select className="Chosen-select" ref={el => (this.el = el)}> {this.props.children} </select> </div> ); } } class App extends Component { constructor(prpps) { super(prpps); this.state = { items: ["one", "two", "three"] }; } handleAddList = () => { this.setState({ items: this.state.items.concat(["four"]) }); }; render() { return ( <div className="App"> <button onClick={this.handleAddList}>追加</button> {/* Reactの守備範囲外 */} <Chosen onChange={value => console.log(value)}> {this.state.items.map(item => ( <option key={}>{item}</option> ))} </Chosen> {/* Reactの守備範囲 */} <select> {this.state.items.map(item => ( <option key={item}>{item}</option> ))} </select> </div> ); } } export default App;
componentDidUpdateをコメントアウトした場合、追加ボタンによって新たな要素が追加され
DOMには4つめの要素が生成されますが、Chosenが生成しているSelectには表示されません。
もちろん、普通のを利用しているリストには追加した要素が表示されます。
基本的にはReactのComponentとして提供されているライブラリを利用する方がよいですが、
必要に迫られた場合には、やれないことはないということですね。
- 投稿日:2019-03-30T15:49:02+09:00
React.jsの環境構築
Reactの環境構築は10〜15分あれば出来るので思ったより簡単でした。
node.jsのインストール
公式サイト node.js
注 Homebrewがインストールされている程で進めていきます。
brew install node
バージョンの切り替えツールのインストール
npm install -g n
バージョンのインストール
n 8.12.0
↑
ここでError: sudo requiredというエラーが出た場合
sudo mkdir /usr/local/nでディレクトリを作り再びTRY!
自分の場合正しいのか少し不安ですがsudo n 8.12.0で進めてしまいましたがそれでも出来ました。ディレクトリにroot権限を与える
sudo chown -R $(whoami) /usr/local/n
インストールとバージョン確認
n 8.12.0
node -vJSのパッケージマネージャyarnのインストール
brew install yarn --without-node
ここでError: invalid option: --without-nodeというエラーが出た場合
brew install yarn --ignore-dependenciesとしてTRY!
インストールの確認
yarn --vesion
バージョンが確認できればOKです。webpack4インストール
webpackとライブラリなどのインストール
yarn add --dev webpack webpack-cliこれでReact.jsの開発環境が整いました。
- 投稿日:2019-03-30T12:41:53+09:00
Formik で使う yup のスキーマパターン
はじめに
以前に別の記事に書いたのですが、うちの職場ではフォームの管理は Formik、バリデーションチェックは yup を使っています。
いくつかのやりたいことは無理やり実現してる感がありますが、使っていて便利だなーと思うのでいくつかのパターンをシェアします。
GitHub の issue も漁りましたが会心の解決策に出会えてないものもあり…、もっと良いパターンがあったらぜひとも知りたい。(切望Formik の利用パターン
私の環境で使用してる Formik の使い方は以下の通りです。
const FormView: React.SFC<Props> = ({ ...props }) => { const schema = genSchema(something); return <Formik initialValues={initialValues} validationSchema={schema.validations} onSubmit={(values, { setSubmitting }) => { // something... setSubmitting(false); }} render={(props: FormikProps<Values>) => ( <> <CustomField item={schema.somethingSchema} /> <Button title="Submit" onPress={props.handleSubmit} disabled={props.isSubmitting} /> </> )} /> ); }Formik には
validationとvalidationSchemaの使い方があると思います。
ここでは、 yup で定義したスキーマを直接渡す形を取っています。
(そのせいで(?)、詰まった部分もあります。。。)ポイントは、
genSchemaという処理を作って、そこでスキーマを取得していることです。(詳細は後述)yup のスキーマ
getSchema
getSchemaは以下のような形で定義します。
引数argを渡しているのは、このほうがダイナミックなスキーマの利用ができるからです。export const genSchema = (arg: any) => { const somethingSchema = { fieldValidations: Yup.string().required(), // something... }; const validations = Yup.object().shape({ something: somethingSchema.fieldValidations, // others... }); return { somethingSchema, // others... validations, }; }スキーマのパターン
いくつかの(よく使う)スキーマパターンを紹介します。
なお、.requiredみたいなベーシックなものは省略します。公式ドキュメントの内容を自分で試してみて、コメント(感想?)を載せているようなものですので、どうぞ温かい目で見守ってください。
mixed.oneOf(...)
ラジオボタンや yes / no のチェックなど、複数の中から一つを選択させるフォームで活用します。
選択肢のリストを変数として渡せば、何かのはずみで別の値が渡されたとしてもエラーとして弾いてくれます。
yup.refと組み合わせて、パスワード(確認用)のフォームを作ることも簡単です。
.requiredを忘れると、選択しなくても通るので注意してください。const radioButtonSchema = { fieldValidations: Yup.string().oneOf(optionsList).required(), // ... } const passwordSchema = { fieldValidations: Yup.string().required(), // ... } const confirmPasswordSchema = { fieldValidations: Yup.string().oneOf([Yup.ref('password')]).required(), // ... };mixed.when
公式に以下のような記載があります。
Adjust the schema based on a sibling or sibling children fields.
別のスキーマ(おそらく同じ
Yup.object().shape({ ... })で定義されたもの)を参照することができます。
自分自身の値を参照しようとすると、循環参照エラー(?)になりました。活用シーンは、 yes / no ボタンを用意し、 no を選択したケースだと必須入力の詳細内容フォームが現れる、といったケースです。
const detailSchema = { fieldValidations: Yup.string().when('yesNoCheck', (value, schema) => value === 'no' ? schema.required() : schema, // ... }このパターンでは、他の
yesNoCheckスキーマの値を参照し、No の場合は必須入力に、Yes の場合はパスさせる、という処理をしています。
detailSchemaの初期値がnullになる場合は、schema.nullable()にしてあげる必要があります。mixed.test
.testは第三引数で渡すテスト関数の返り値で検証します。All tests must provide a name, an error message and a validation function that must return true or false or a ValidationError. To make a test async return a promise that resolves true or false or a ValidationError.
テスト関数は
true/false/ValidationErrorを返す必要があります。
また、asyncも使えるみたいです。
.testでは、自分自身の値と、thisが使えるのが特徴です。
前回の記事にも書きましたが、 arrow 関数を使ってしまうとthisが束縛されてしまうので、function(value) { ... }の形で定義する必要があります。Note that to use the this context the test function must be a function expression (function test(value) {}), not an arrow function, since arrow functions have lexical context.
動的なハンドリング
この記事を書こうと決意した理由がこの動的なハンドリングです。
私が求めていた動的なハンドリングが発生するケースとは、フォームの値以外の条件(たとえばバックエンドから返ってきたステータスなど)によって、フォームの表示 / 非表示が変化する場合です。ここでは、
genSchemaの引数を利用します。export const genSchema = (arg: any) => { const somethingSchema = { fieldValidations: arg === 'something' ? Yup.string().required() : Yup.string(), // something... }; // ... }この方法を使うことで、フォーム以外の値でもフォームのバリデーションをコントロールすることができます。
ちょこっとコラム(その1)
動的なハンドリングについて、当初
.whenのcontextを利用できないかと画策しました。参考になりそうな issue も見つけました。
.whenはcontextで渡したパラメータを$somethingで参照できる機能を持ちます。
この質問者のケースでは、Formにcontextprops を渡すことで実現できたとあります。ですが私の場合、(
validationSchemaを使っているからか?) JSX にcontextを渡してもうまくハンドルできませんでした。
schema.isValidやschema.validationを直接コールするとバリデーションチェックはできているものの、Formik を介してのチェックが走りません。
そのため、やむ終えず上記の手法にチェンジしました。ちょこっとコラム(その2)
Formikには<Form />や<Field />が用意されています。
<Form />は<form>、<Field />は<input>のラッパーとして定義されているので、React Native でそのままは利用できません。そのため
CustomFieldComponent を用意することで、フォームの入力フィールドを作りました。
TextInput以外にも、ラジオボタンなども定義可能です。const CustomField: React.SFC<Props> = ({ item }) => ( <Field name={item.fieldName}> {({ form, field }: FieldProps) => { const hasError = !!form.errors[field.name] && !!form.touched[field.name]; return ( <View style={{ ... }}> <TextInput style={{ ... }} value={field.value} placeholder={item.defaultLabel} onChangeText={(v) => form.setFieldValue(field.name, v)} onBlur={() => form.setFieldTouched(field.name)} /> <View style={{ ... }}> {hasError && ( <Text style={{ ... }}> {form.errors[field.name]} </Text> )} </View> </View> ); }} </Field> );おわりに
.whenをcontextはハンドリングしようとしてドツボにはまりました。(結局、解決策を見つけられなかった)同僚に相談したら、
「フィールドの表示制御も以ってるんでしょ?それならgenSchemaに引数渡しちゃいなよ。」
って言われて解決しました。
視野を狭めず、いろんな可能性を考えることの重要性を再認識させられました。同じ悩みを持つ誰かの助けになれば幸いです。
- 投稿日:2019-03-30T00:31:14+09:00
Electron & React & Redux & TypeScript アプリ作成ワークショップ をやってみた1
概要
以下をやってみた記録。良記事に感謝。
- https://qiita.com/EBIHARA_kenji/items/25e59f7132b96cb886f3
- https://qiita.com/EBIHARA_kenji/items/e6da1c3d6d16cf07b60a
- https://qiita.com/EBIHARA_kenji/items/1a043794014dc2f3a7db (途中まで)
環境
Node.jsとnpmのバージョン$ node -v v10.13.0 $ npm -v 6.4.1npmプロジェクトの作成
フォルダとpackage.json作成$ mkdir electron-react-app $ cd electron-react-app $ npm initpackage.json{ "name": "electron-react-app", "version": "1.0.0", "description": "", "main": "main.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", "license": "ISC" }gitで作業を記録することにする。
以降、記事には記載しないが、適時コミットしてワークショップを進める。gitの初期設定$ git init $ git add . $ git commit -m "npm init" $ git remote add origin (your remote repo) $ git push -u origin master日本語対策$ set LANG=ja_JP.UTF-8.gitignorenode_modules/ /dist/必要なライブラリをインストールする
各ライブラリの詳細は、元記事参照。
各種ライブラリインストール$ npm install --save react react-dom redux react-redux styled-components $ npm install --save-dev electron typescript tslint webpack webpack-cli ts-loader tslint-loader $ npm install --save-dev @types/react @types/react-dom @types/redux @types/react-redux
ここから二日目の内容です。
TypeScript コンパイラ・オプションファイルの作成
tsconfig.json作成$ "./node_modules/.bin/tsc" --inittsconfig.json{ "compilerOptions": { "target": "es5", "module": "commonjs", "jsx": "react", "outDir": "./dist", "strict": true, "esModuleInterop": true, "sourceRoot": "./tsx", "inlineSourceMap": true, "inlineSources": true }, "include": [ "./ts/**/*" ] }tslint 設定ファイル の作成
tslint.json作成$ "./node_modules/.bin/tslint.cmd" --inittslint.json{ "defaultSeverity": "error", "extends": [ "tslint:recommended" ], "jsRules": {}, "rules": { "quotemark": [ true, "single", "jsx-double" ] }, "rulesDirectory": [] }webpack.config.js の作成
2か所タイポがあったのは秘密(以下は修正済み)。
webpack.config.jsconst path = require('path'); module.exports = { // node.js で動作することを指定する target: 'node', // 起点となるファイル entry: './ts/index.tsx', // webpack watch したときに差分ビルドができる cache: true, // development は、source map fileを作成。再ビルド時間の短縮などの設定となる mode: 'development', // "production" | "development" | "none" // ソースマップのタイプ devtool: 'source-map', // 出力先設定 __dirname は node ではカレントディレクトリのパスが格納される変数 output: { path: path.join(__dirname, 'dist'), filename: 'index.js' }, // ファイルタイプ毎の処理を記述する module: { rules: [{ // 正規表現で指定する // 拡張子 .ts または .tsx の場合 test: /\.tsx?$/, // ローダーの指定 // TypeScript をコンパイルする use: 'ts-loader' }, { // 拡張子 .ts または .tsx の場合 test: /\.tsx?$/, // 事前処理 enforce: 'pre', // TypeScript をコードチェックする loader: 'tslint-loader', // 定義ファイル options: { configFile: './tslint.json', // airbnb という JavaScript スタイルガイドに従うには下記が必要 typeCheck: true, }, }], }, // 処理対象のファイルを記載する resolve: { extensions: [ '.ts', '.tsx', '.js', // node_modules のライブラリ読み込みに必要 ] }, };HTML の作成
index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Electronチュートリアル</title> </head> <body> <div id="contents"></div> <script src="dist/index.js"></script> </body> </html>main.js の作成
main.jsconst { app, BrowserWindow } = require('electron') // レンダープロセスとなるブラウザ・ウィンドウのオブジェクト。 // オブジェクトが破棄されると、プロセスも終了するので、グローバルオブジェクトとする。 let win function createWindow() { // ブラウザウィンドウの作成 win = new BrowserWindow({ width: 800, height: 600 }) // index.htmlをロードする win.loadFile('index.html') // 起動オプションに、"--debug"があれば開発者ツールを起動する if (process.argv.find((arg) => arg === '--debug')) { win.webContents.openDevTools() } // ブラウザウィンドウを閉じたときのイベントハンドラ win.on('closed', () => { // 閉じたウィンドウオブジェクトにはアクセスできない win = null }) } // このメソッドは、Electronが初期化を終了し、 // ブラウザウィンドウを作成する準備ができたら呼び出される。 // 一部のAPIは、このイベントが発生した後にのみ使用できる。 app.on('ready', createWindow) // 全てのウィンドウオブジェクトが閉じたときのイベントハンドラ app.on('window-all-closed', () => { // macOSでは、アプリケーションとそのメニューバーがCmd + Qで // 明示的に終了するまでアクティブになるのが一般的なため、 // メインプロセスは終了させない if (process.platform !== 'darwin') { app.quit() } }); app.on('activate', () => { // MacOSでは、ドックアイコンがクリックされ、 // 他のウィンドウが開いていないときに、アプリケーションでウィンドウを // 再作成するのが一般的です。 if (win === null) { createWindow() } });コンパイル確認用スクリプトの記述
ts/index.tsximport React from 'react'; import ReactDom from 'react-dom'; const container = document.getElementById('contents'); ReactDom.render( <p>こんにちは、世界</p>, container, );コンパイルの確認
webpack実行とelectron起動$ "./node_modules/.bin/webpack" $ "./node_modules/.bin/electron" ./npm script を利用する
package.jsondiff --git a/package.json b/package.json index a98d36d..f69de75 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,8 @@ "description": "", "main": "main.js", "scripts": { + "build": "webpack", + "start": "electron ./", "test": "echo \"Error: no test specified\" && exit 1" }, "author": "",webpack実行とelectron起動$ npm run build $ npm start
ここから三日目の内容です。
child_state の作成
ts/IUser.tsexport default interface IUser { name: string; } export const initUser: IUser = { name: '', };component の作成
ラベル付きテキストボックスの作成
ts/components/TextBox.tsximport React from 'react'; // 親コンポーネントから渡されるプロパティを定義する interface IProps { // ラベル文字列 label: string; // テキストボックスのタイプ type: 'text' | 'password'; // テキストボックスに表示する値 value: string; // 値の確定時にその値を親プロパティが取得するためにコールバック関数を提供する onChangeText: (value: string) => void; } export class TextBox extends React.Component<IProps, {}>{ // DOMエレメントをレンダリングする public render() { // ラベルが設定されていない場合は、label を出力しない const label = (!!this.props.label) ? <label>{this.props.label}</label> : null; return ( <span> {label} <input name="username" type={this.props.type} value={this.props.value} onChange={this.onChangeText}></input> </span> ); } // 値を変更したら、store.dispatch で action を reducer に渡して、state を更新する。 // state が更新されたら component の prop が更新され、再レンダリングされ、テキストボックスの内容が変更される。 private onChangeText = (e: React.ChangeEvent<HTMLInputElement>) => { this.props.onChangeText(e.target.value); } }続きは次回。
感想
- Electronはもちろんだけど、周辺ツールについても、とても勉強になる
- TypeScriptの書き方も同上
以上
















