20210411のReactに関する記事は18件です。

useEffect のクリーンアップのタイミングは?

function cleanup () {} useEffect(() => { return cleanup }) cleanup が実行されるタイミングはいつでしょう? アンマウント時だけでなく、次回の副作用実行前にも実行されるようです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel+React でpusherを使う

バージョンは laravel 6 react 16.14. 0 npm 7.5.3 laravelのconfig\app.phpの config\app.php App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, App\Providers\BroadcastServiceProvider::class, <- コメントアウト解除 App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, .envの BROADCAST_DRIVER=pusher に変更 resources/js/bootstrap.js /*全てコメントアウト*/ import Echo from 'laravel-echo'; window.Pusher = require('pusher-js'); window.Echo = new Echo({ broadcaster: 'pusher', key: process.env.MIX_PUSHER_APP_KEY, cluster: process.env.MIX_PUSHER_APP_CLUSTER, encrypted: true }); 自分のlaravelのフォルダ直下で以下のコマンドを実行 ターミナル $ composer require pusher/pusher-php-server pusherに登録してください https://pusher.com/ create-appを押してアプリを作成 この画面になったら ・アプリ名 ・フロントまたはバックエンドの使用している言語を選択してCreate appを押す 作成したアプリのApp Keysを見る app_id key secret clusterをコピー .envで /.env PUSHER_APP_ID= PUSHER_APP_KEY= PUSHER_APP_SECRET= PUSHER_APP_CLUSTER= ここにコピー ターミナルで $ php artisan make:event PostCreated ここから個人の開発によって違いますがなるべくわかりやすく説明します app/Events/PostCreated. <?php namespace App\Events; use App\Post; <-自分はここを追記 use App\User; <-自分はここを追記 use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PresenceChannel; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class PostCreated implements ShouldBroadcast { use Dispatchable, InteractsWithSockets, SerializesModels; public $post; public $user; public function __construct(Post $post, User $user) { $this->post = $post; $this->user = $user; } public function broadcastOn() { return [ new PrivateChannel('new-post'), new PrivateChannel('App.User.'.$this->post->user->id), ]; } public function broadcastWith() { return [ 'post' => array_merge($this->post->toArray(), [ 'user' => $this->post->user, ]), 'user' => $this->user, ]; } } use App\Postで自分が欲しい情報をとってきています broadcastOnにPrivateChannel('new-post')があると思います これはreact側で呼び出すための鍵です 使用しているControllerに飛びます app\Http\Controllers <?php namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Post; use App\Events\PostCreated; <-追記 public function create(Request $request,Post $post) { $createdPost = $request->user()->posts()->create([ 'body' => $request->body, ]); broadcast(new PostCreated($createdPost, $request->user()))->toOthers(); <-これを追記 return response()->json($post->with('user')->find($createdPost->id)); } routes/channels.php /*追記*/ Broadcast::channel('new-post', function ($user) { return Auth::check(); }); reactで resources/js/app.js useEffect(() => { window.Echo.private('new-post').listen('PostCreated', e => { if (window.Laravel.user.following.includes(e.post.user_id)) { setPosts([e.post, ...posts]) } }) }, []) 自分はtwitterアプリを作ったのでメッセージが届いたら表示するために setPosts()で更新をかけています pusherはかなりハマりやすいと思います 公式ドキュメントhttps://readouble.com/laravel/5.8/ja/broadcasting.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React JS × Onsen UI 入門

Onsen UIについて 公式ページ 以下公式サイトから抜粋 モバイルアプリの開発に特化したUIコンポーネントの集まり ネイティブなiOSとAndroidのデザインガイドに準拠したデザインと機能を持ってる 無料で利用でき、完全にオープンソースなソフトウェア Onsen UIが吸収する各OSのUIの違い 例えば、AndroidではタブがUIでは上にあることがスタンダードとなっていて、iOSではタブがUIでは下にあることがスタンダードとなっています。 Onsen UIではこの2つの異なるUIを同じソースコードで実装できます。 他にも、新規のスクリーンがUIに重なる場合、iOSのデフォルトでは右からスライドしてきますが、Androidでは下からフェードしつつ重なってきます。このような細かいインタラクションの表現もOnsen UIでは自動で吸収してくれます。 UserAgentでCSSやJSの切り替えを行っているので、ChromeのデベロッパーツールでもUserAgentoを切りかるだけでUIの表現が変わるのを確認できます。 React.js と Onsen UI Onsen UIが2.0になって、React JSと組み合わせて使えるようになりました。 ※Onsen UI1.0はAngular JS 1.*系のみサポート $ npm install -g create-react-app $ npx create-react-app my-app $ cd my-app $ yarn add onsenui react-onsenui --save-dev $ yarn start src/App.js import logo from './logo.svg'; import './App.css'; // ボタンコンポーネントのインポート import { Button } from 'react-onsenui'; // Onsen UIのCSSのロード import "onsenui/css/onsen-css-components.css" function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <Button modifier="outline"> button </Button> </header> </div> ); } export default App; 以下のように、いつものReactのデフォルトページにOnsen UIのボタンが追加されます。 Onsen UIを利用したナビゲーション Onsen UIを利用してページ遷移を実装します。 Navigationというコンポーネントを利用します。 src/App.js import logo from './logo.svg'; import './App.css'; // ボタンコンポーネントのインポート import { Button } from 'react-onsenui'; // Onsen UIのCSSのロード import "onsenui/css/onsen-css-components.css" import Block from './components/block' function App() { return ( <div key="App" className="App"> <header className="App-header"> <Block /> </header> </div> ); } export default App; src/components/block.js import React from 'react'; import {Navigator, Page, Button, Toolbar, BackButton} from 'react-onsenui'; import "onsenui/css/onsen-css-components.css" class MainPage extends React.Component { pushPage() { this.props.navigator.pushPage({component: SecondPage}); } render() { return ( <Page key="page1"> <Toolbar> <div className="center">Navigator</div> </Toolbar> <p style={{textAlign: 'center'}}> <Button onClick={this.pushPage.bind(this)}>Push page</Button> </p> </Page> ); } } class SecondPage extends React.Component { pushPage() { this.props.navigator.pushPage({component: SecondPage}); } popPage() { this.props.navigator.popPage(); } render() { return ( <Page key="page2"> <Toolbar> <div key="div1" className="left"><BackButton>Back</BackButton></div> <div key="div2" className="center">Another page</div> </Toolbar> <p style={{textAlign: 'center'}}> <Button key="button2" onClick={this.popPage.bind(this)}>Pop page</Button> </p> </Page> ); } } class Block extends React.Component { renderPage(route, navigator) { const props = route.props || {}; props.navigator = navigator; props.key = route.component.name; return React.createElement(route.component, props); } render() { return ( <Navigator key="nav1" initialRoute={{component: MainPage}} renderPage={this.renderPage} /> ); } } export default Block; 各OSでのUIの違い 以下のようにNavigationではOSでUIが異なります Androidでの見た目 iOSでの見た目
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSのEC2でReactアプリをGithubからクローンして、EC2でアプリを起動するまで(nginx)

環境 MacOS BigSur 11.2.3 事前に *AWSのアカウント作成が終わっている方向けの内容です。 *AWSアカウントの初期設定まとめの内容も実施推奨です。 目的 ・ローカルでcreate-react-appで作成したアプリをGithubへpush、 アプリをEC2にclone、ブラウザからEC2へのアクセスして、アプリを表示させます。 ・シンプルな構造で、AWSの主要サービスの理解を深める。 ・本編で作成した環境は今後も拡張していき学習の効率化を図る。 以下の方が対象になります create-react-appでReactアプリ作成したことがある。 AWSのEC2でReactアプリを起動してみたい。 Git/Githubが使える。 簡単なvim操作ができる。 1.VPCの作成 AWS マネジメントコンソールからVPCを検索します。 *VPCはAWSクラウド内に独自にネットワークを構築できるサービスです。 VPCを作成していきます。 VPCの名前は任意で構いませんが、今回はmy-react-vpc-1で作成し、 IPv4 CIDR ブロック情報は10.0.0.0/2としています。 その他の項目はデフォルト値で作成します。 2.パブリックサブネット作成 サイドバーメニューのサブネットから作成します。 サブネット名はmy-react-public-subnet-1で作成します。 作成したVPCを選択、アベイラビリティゾーン(ap-northeast-1a)を選択。 *IPv4 CIDRブロックは10.0.0.0/24を指定します。 先ほどのVPCを指定していきます。 3.プライベートサブネットを作成 同様の手順で、サブネット名my-react-private-subnet-1で作成していきます。 本記事ではプライベートサブネットにEC2やRDSは配置しませんが、今後の拡張のため作成しています。 作成したVPCを選択、アベイラビリティゾーン(ap-northeast-1a)を選択。 *IPv4 CIDRブロックは10.0.2.0/24を指定します。 4.インターネットゲートウェイを作成 *インターネットゲートウェイはVPCとインターネット間の通信を可能にするために必要です。 名前タグはmy-react-internet-gw-1を入力、 インターネットゲートウェイの作成をクリックします。 作成したゲートウェイは、すでに作成しているVPCにアタッチします。 5.EC2インスタンス作成 インスタンスを作成していきます。 EC2はクラウド内で提供されるコンピューター(リソース)です。 後に作成するReactアプリはこのEC2にクローンします。 検索窓でEC2を検索します。 インスタンスを起動します。 マシンイメージはAmazon Linux 2 AMIを選択。 インスタンスタイプはt2.microを選択。 次のステップをクリックします。 続いて、ネットワークはVPCを選択し、サブネットはmy-react-public-subnet-1、 自動割り当てパブリックIPを有効にします。 その他の項目はデフォルト値で次のステップに進みます。 ストレージの追加もデフォルト値で次のステップに進みます。 次のステップでタグを追加します。キーにName、値はmy-react-web-server-1として次のステップへ。 続いて、新しいセキュリティグループを作成します。 セキュリティグループ名と説明はmy-react-sg-1として、 ルールを追加します。 ルールはタイプにHTTPを指定、プロトコルはTCPを選択、ポートは80を指定します。 入力が終わったら、起動と確認へ進みます。 設定に間違いがないことを確認し、起動をクリックします。 インスタンスの起動前にキーペアの作成画面が表示されるので、キーペアを新規作成します。 作成したキーペアはご自身のPCに保存します。(このキーファイルは安全な場所に保管してください) インスタンスの作成をクリックしてインスタンスの表示をクリック。 実行中になっていればインスタンスの作成は完了です。 6.ルートテーブルの編集 VPC IDがmy-react-vpc-1のルートテーブルを選択、 ルートの編集をクリックし、ルートを追加していきます。 追加するルートの送信先は0.0.0.0/0、ターゲットはInternet Gatewayを選択、 すでに作成しているmy-react-internet-gw-1を選択し、ルートを保存します。 7.作成したEC2でreactアプリを起動する Gitを使うので事前にご自身のPCにnpmをインストールします。 Terminal npm install -g npm EC2にSSH接続 SSH接続する際に先ほど作成したプライベートキーファイルが必要になります。 このキーペアファイルは権限を変更しないとエラーになるので、あらかじめchmodで権限変更しておきます。 Terminal chmod 400 キーペアファイルのパス chmod 400 Downloads/my-react-key-pare-1.pem キーペアの権限が変更されているか確認します。 Terminal ls -l my-react-key-pare-1.pem 以下のように-r--------となっていれば権限変更されています。 EC2へ接続します。 Terminal ssh -i <キーファイルを指定> ec2-user@<EC2のパブリックIPv4アドレス> ssh -i Downloads/my-react-key-pare-1.pem ec2-user@10.0.1.1 *EC2のパブリックIPv4アドレスはEC2コンソール画面から確認できます。 接続の際に以下の内容を聞かれるので、yesを入力します。 Are you sure you want to continue connecting (yes/no/[fingerprint])? yes 以下のEC2が表示されればSSH接続完了できてます。 EC2への外部からのアクセスをローカルホストにつなげるようにする 現在の設定ではアプリケーションをクローンして起動しても、 localhost:3000で起動することしかできないので、 nginx を使って「外部からのアクセスをローカルホストにつなげる」仕組みを用意します。 nginx をリバースプロキシとして用い、ポート80へのアクセスをlocalhostの3000ポートに振り向けるようにします。 nginxをインストール *EC2にSSH接続した状態で以下コマンドを実行します。 権限ユーザーの切り替え EC2 sudo su - root EC2のパッケージをアップデート EC2 yum update -y nginxをインストール EC2 yum install nginx -y ↑これを実行すると、nginx の install 時に Amazon Linux 2だとエラーになるので、 エラーメッセージの指示に従い、以下を実行。 EC2 sudo amazon-linux-extras install nginx1.12 nginxを起動します。 EC2 sudo systemctl start nginx.service 以下のコマンドでnginxのstatusを確認します。 EC2 sudo systemctl status nginx.service Acrive: active (running)となっていれば起動しています。 Gitをインストールします。 EC2 sudo yum -y install git *あらかじめ、ご自身のPCでcreate-react-appでアプリを作成し、ご自身のGithubリポジトリへpushしてください。 EC2にアプリをクローン EC2 git clone <https://github.com/<your sample app repository> Githubのユーザー名とパスワードを聞かれるので入力してクローンします。 アプリケーションのビルドにnode.jsが必要なのでインストールします。 EC2 curl -sL https://rpm.nodesource.com/setup_14.x | bash - yum install -y nodejs npmとnodeがインストールされていることを確認します。 下記のようにバージョンが表示されれば無事インストールされています。 EC2 npm -v 6.13.4 node -v v13.5.0 アプリケーションのディレクトリへ移動します。 EC2 cd アプリケーションディレクトリ 必要なパッケージをインストールして、ビルドします。 EC2 npm install npm run build EC2でアプリケーションを実行しEC2のパブリックIPv4アドレスに接続。 アプリケーション起動 EC2 npm run start その後、ブラウザのアドレスバーにEC2のパブリックIPアドレスを入力してみる。 しかし、このままでは下記エラーが表示されてしまうので、 EC2の下記パスにnginxの設定ファイルがあるのでvimで編集していきます。 EC2 vim /etc/nginx/nginx.conf 以下のlocationを追加します。 /etc/nginx/nginx.conf server { location / { # WEBリクエストをローカルホスト3000番ポートへリダイレクト proxy_pass http://localhost:3000/; } } ファイルの編集が完了したらnginxを再起動します。 EC2 sudo systemctl restart nginx 再度、アプリケーションを起動。 EC2 npm run start アプリが起動している状態で、対象のEC2インスタンスのパブリックIPv4アドレスを ブラウザに入力してアクセスします。 ロゴが表示されれば成功です! 最後までお読みいただきありがとうございます! ご指摘などありましたら、お気軽にコメントください^^ この記事はAWS初学者を導く体系的な動画学習サービス 「AWS CloudTech」の課題カリキュラムで作成しました。 https://aws-cloud-tech.com
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

5歳娘「パパ、余分なpropsいっぱい書くんだね!」

とある平日 娘(5歳)「パパ、今日は何のお仕事してるの?」 ワイ「おお、娘ちゃん」 ワイ「今日はな、ショッピングサイトを作ってんのや」 今日のお仕事内容 ワイ「↓このデザインの通りに、コーディングをせなあかんのや」 娘「なるほどー」 娘「このショッピングサイトで商品を売りたい!っていうお店があったとして」 娘「そのお店の人が、最初にお店の情報を登録するためのページだね!」 ワイ「せやせや」 まずはデザインを眺めてみる ワイ「この店舗登録ページにはなぁ」 ワイ「↑こんな感じの」 ワイ「項目名と入力欄がセットになったパーツが何度も登場するから」 ワイ「そのためのコンポーネントを作ろうかなー、って」 ワイ「そう思ってたとこなんや」 娘「ふーん」 娘「ラベル付きテキストフィールド的なコンポーネントってことだね」 娘「どこまで作ったの?」 ワイ「まだ、ページ側のファイルを作っただけや」 ワイ「↑これだけや」 /pages/form.tsx import React from 'react' const FormPage: React.FC = () => { return ( <> <h1>店舗登録</h1> <p>店舗情報を入力してください。</p> </> ) } export default FormPage ワイ「↑コードもまだこんだけや」 娘「なるほどね」 ワイ「ほな今から、ラベル付きテキストフィールドのコンポーネントを作っていくで!」 コンポーネント作り開始 ワイ「まずは・・・」 /components/LabeledInput.tsx export const LabeledInput: React.FC<Props> = (props) => { return ( <label> <span>項目名</span> <input type="text" name="項目名" /> </label> ) } ワイ「↑こんな感じや」 ワイ「これをページ側で呼び出すには・・・」 /pages/form.tsx import React from 'react' + import { LabeledInput } from 'src/components/LabeledInput' const FormPage: React.FC = () => { return ( <> <h1>店舗登録</h1> <p>店舗情報を入力してください。</p> + <LabeledInput /> + <LabeledInput /> + <LabeledInput /> + <LabeledInput /> + <LabeledInput /> </> ) } export default FormPage ワイ「↑こうやな」 ワイ「こうすると、画面の方は・・・」 ワイ「↑こうやな」 娘「わー、ちゃんと表示されたね」 ワイ「おお」 娘「でも、ラベル部分が全部項目名になっちゃってるよ?」 ワイ「大丈夫や」 ワイ「初期リリース時はその要件でOKなはずや」 娘「そんなわけないからprops書こうよ」 ワイ「せやな」 ワイ「ワイが会社からリリースされてしまうところやったわ」 まずはpropsの型を書く ワイ「ほんなら」 ワイ「input要素に渡すtypeとかnameと」 ワイ「あと、ラベル文字列もpropsで渡したいから」 ワイ「labelTextってのも定義しておこか」 /components/LabeledInput.tsx import React from 'react' - type Props = {} + type Props = { + type: string + name: string + labelText: string + } export const LabeledInput: React.FC<Props> = (props) => { return ( <label> <span>項目名</span> <input type="text" name="項目名" /> </label> ) } ワイ「↑propsの型はこんな感じやな」 ワイ「そんで、親コンポーネントからもらってきたpropsを」 ワイ「子コンポーネントの中で表示せなあかんから・・・」 /components/LabeledInput.tsx import React from 'react' type Props = { type: string name: string labelText: string } export const LabeledInput: React.FC<Props> = (props) => { + const { type, name, labelText } = props + return ( <label> - <span>項目名</span> - <input type="text" name="項目名" /> + <span>{labelText}</span> + <input type={type} name={name} /> </label> ) } ワイ「↑こうやな!」 娘「なるほど〜」 娘「ページの方からは、このコンポーネントにどうやってpropsを渡すの?」 ワイ「ページの方は・・・」 /pages/form.tsx import React from 'react' import { LabeledInput } from 'src/components/LabeledInput' const FormPage: React.FC = () => { return ( <> <h1>店舗登録</h1> <p>店舗情報を入力してください。</p> - <LabeledInput /> - <LabeledInput /> - <LabeledInput /> - <LabeledInput /> - <LabeledInput /> + <LabeledInput type="text" name="shopName" labelText="店舗名" /> + <LabeledInput type="text" name="repName" labelText="代表者名" /> + <LabeledInput type="text" name="repName" labelText="業種" /> + <LabeledInput type="text" name="repName" labelText="郵便番号" /> + <LabeledInput type="text" name="repName" labelText="住所" /> </> ) } export default FormPage ワイ「↑こんな感じや」 ワイ「こうすると画面は・・・」 ワイ「↑こうやな」 娘「わーい!ほぼ完成だね!」 ワイ「いや、全然まだまだやで」 娘「そうなの?」 propsをどんどん追加していく ワイ「input要素にdisabled属性をつけたり」 ワイ「onClick属性としてイベントハンドラ関数を渡したり」 ワイ「そういうpropsがまだまだ全然足りてへん」 娘「確かに」 ワイ「せやから、MDNのページとかを見ながら」 ワイ「input要素に必要そうなpropsを全部書いていくんや・・・!!!」 /components/LabeledInput.tsx import React from 'react' type Props = { type: string name: string + placeholder: string + autoComplete: string + disabled: boolean + required: boolean + minLength: number + maxLength: number + onChange: React.ChangeEventHandler<HTMLInputElement> + onBlur: React.FocusEventHandler<HTMLInputElement> labelText: string } export const LabeledInput: React.FC<Props> = (props) => { - const { type, name, labelText } = props + const { + type, + name, + value, + placeholder, + autoComplete, + disabled, + required, + minLength, + maxLength, + onChange, + onBlur, + labelText, + } = props return ( <label> <span>{labelText}</span> - <input type={type} name={name} /> + <input + type={type} + name={name} + value={value} + placeholder={placeholder} + autoComplete={autoComplete} + disabled={disabled} + required={required} + minLength={minLength} + maxLength={maxLength} + onChange={onChange} + onBlur={onBlur} + /> </label> ) } ワイ「↑こんなもんや!!!!」 ワイ「(ドヤァ・・・・)」 ワイ「(パパはいつも、こんな大変なお仕事をしてるんやでぇ・・・!?)」 5歳娘「パパ、余分なpropsいっぱい書くんだね!」 ワイ「ファッ!?」 5歳児の書き方 娘「パパ」 娘「input要素の属性一覧を、一生懸命ネットで調べるのもいいんだけどさ」 /components/LabeledInput.tsx type InputProps = JSX.IntrinsicElements['input'] 娘「↑これだけでいいんだよ」 ワイ「ファーーー・・・(失神)」 娘「これだけで、input要素の属性一覧の型情報を取得できるの」 娘「つまり、propsの型定義の部分は」 /components/LabeledInput.tsx - type Props = { - type: string - name: string - placeholder: string - autoComplete: string - disabled: boolean - required: boolean - minLength: number - maxLength: number - onChange: React.ChangeEventHandler<HTMLInputElement> - onBlur: React.FocusEventHandler<HTMLInputElement> - labelText: string - } + type InputProps = JSX.IntrinsicElements['input'] + type Props = InputProps & { labelText: string } 娘「↑この2行で済むわけだね」 ワイ「ぐぬぬ・・・」 ワイ「あ、ありがとうやで娘ちゃん・・・」 ワイ「だいぶコードがスッキリしたわ・・・」 娘「え、まだまだ余分なところがいっぱいあるよ?」 ワイ「ファファファ・・・」 propsはスプレッド構文で渡せる 娘「propsはね、スプレッド構文を使うことで・・・」 /components/LabeledInput.tsx <input {...props} /> 娘「↑こんな感じで一気に渡せるの」 ワイ「おお・・・」 娘「つまり、さっきのコンポーネントのコードは・・・」 /components/LabeledInput.tsx export const LabeledInput: React.FC<Props> = (props) => { - const { - type, - name, - value, - placeholder, - autoComplete, - disabled, - required, - minLength, - maxLength, - onChange, - onBlur, - labelText, - } = props + const { labelText } = props return ( <label> <span>{labelText}</span> - <input - type={type} - name={name} - value={value} - placeholder={placeholder} - autoComplete={autoComplete} - disabled={disabled} - required={required} - minLength={minLength} - maxLength={maxLength} - onChange={onChange} - onBlur={onBlur} - /> + <input {...props} /> </label> ) } 娘「↑こうまとめられちゃうね!」 ワイ「......シテ......コロシテ......」 しかし、このままだとlabelTextもinput要素に渡ってしまう 娘「でもね、これだけだと」 娘「ページ側から渡されたlabelTextっていうpropsも」 娘「まとめてそのままinput要素に渡されてしまうの」 娘「そうすると・・・」 Warning: ReactはDOM要素のlabelTextというpropを認識しません。 意図的にカスタム属性としてDOMに表示させたい場合は、代わりに小文字の labeltext と綴ります。 誤って親コンポーネントから渡してしまった場合は、DOM要素から削除してください。 娘「↑こんな警告がコンソールに出ちゃうの」 ワイ「なるほどな」 娘「だからlabelTextとそれ以外を分割して」 娘「必要なものだけinput要素に渡したいわけ」 ワイ「そのやり方を教えてください(観念)」 娘「はい」 /components/LabeledInput.tsx export const LabeledInput: React.FC<Props> = (props) => { - const { labelText } = props + const { labelText, ...inputProps } = props return ( <label> <span>{labelText}</span> - <input {...props} /> + <input {...inputProps} /> </label> ) } 娘「↑こうだね」 ワイ「おお、なるほど」 /components/LabeledInput.tsx const { labelText, ...inputProps } = props ワイ「↑こう、分割代入と残余構文を使うことで」 ワイ「labelTextとそれ以外に分割してやるんか」 娘「そう」 娘「こないだ@suinさんが似た感じのことをやってたのを、さっき思い出したの」 ワイ「おお、いつもお世話になっております・・・」 最終的に 娘「ってことで、パパの書いたコンポーネントのコードは」 /components/LabeledInput.tsx import React from 'react' type InputProps = JSX.IntrinsicElements['input'] type Props = InputProps & { labelText: string } export const LabeledInput: React.FC<Props> = (props) => { const { labelText, ...inputProps } = props return ( <label> <span>{labelText}</span> <input {...inputProps} /> </label> ) } 娘「↑こんな感じに変わったね!」 ワイ「いやほぼ全部変わっとるやないかい!!!」 まとめ input要素の属性の型は、まとめて取得できる → JSX.IntrinsicElements['input'] propsを渡すとき、スプレッド構文を使って一気に書ける → <input {...props} /> propsを何かとそれ以外に分けたい場合は、分割代入&残余構文で書く → const { labelText, ...inputProps } = props ワイ「ってことやな!」 娘「そうだね!」 その日の夜 ワイ「娘ちゃんは凄いな〜」 ワイ「5歳なのにあんなコードが書けるなんて」 ワイ「もしかして、もう算数とかもできるんちゃうか?」 娘「できるよ!」 娘「パパ、問題出してみて!」 ワイ「ええで!」 ワイ「ほな行くで〜」 ワイ「りんご1個 + みかん1個、合わせていくつ?」 娘「えぇ〜と・・・」 娘「わかった!」 娘「答えは、"りんご1個みかん1個"!」 ワイ「いや文字列結合!」 ワイ「JavaScriptのやり過ぎか!!!」 〜おしまい〜 余談
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Native + Expo CLI 環境構築 Windows編

Expo さわってみる React Native + Expoは使ったことがなく、既存案件のネイティブアプリはXamarinを使用している。 それに対し、WebサイトはReact + javascriptやReact + typescriptで構築している。 これまでは様々な事情があり上記の構成で運用してきたが、Reactで使用している業務ロジックをネイティブアプリでも使用していくことで効率化を図りたい。 よってReact Native + Expoを開発環境作成から進めてみる。 百聞は一見に如かずではないが、聞くでも見るでもなく、とにかく一度動かしてみよう! インストール expo-cliのインストール(npmやnodeが入っている前提) npm i -g expo-cli プロジェクト作成とコンパイル プロジェクトを作成のディレクトリまで移動 expo init myFirstApp 今回はTypeScriptを選択 プロジェクト作成完了 expo実行 エラーですね。package.jsonがないと言っている。焦ってはいけません。 プロジェクト作成後に作成したディレクトリに移動してからexpoの実行 実行できると規定のブラウザ(http://localhost:19002/)が起動します  実行 App.tsxが作成されていることを確認して、コンポーネントの一部を変更して実行 スマホアプリでQRコードを読み取りアクセスすると画面が表示される ここまでのまとめ React Native + Expo CLI 環境構築 と実効までの時間はnpmやNode.jsなどインストール10分程度。非常に簡単に開発環境を構築できることはメリットである。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【React】レンダリング制御について (React.memo / useCallback / useReducer)

はじめに React のコンポーネントのレンダリングの条件とどのように不必要な再レンダリングを防ぐかについて、手元で動かして確認しながらまとめました。 誤り等ありましたらコメントでご指摘いただけますと幸いです レンダリングの条件 まず、再レンダリングとはどのようなことを指すのでしょうか。 コンポーネントは以下のような流れで画面上に表示、更新されます。 レンダリング -> マウント -> 【state の更新など何かしらのイベント】 -> 再レンダリング -> 仮想DOMの差分を検知 -> リアルDOMに差分を反映 -> ... -> アンマウント 一度 DOM にマウントされたコンポーネントに state の更新などが生じると、React はコンポーネントを再度レンダリングします。state の更新以外にも、親コンポーネントがレンダリングされた場合や渡される props が更新された場合などもレンダリングが走ります。 state の更新によるレンダリング useState のセッター関数が呼ばれたときなど、state が更新されることによりレンダリングが走ります。 例えば、以下のようなコンポーネント。 const App = () => { console.log("Appレンダリング!") const [count, setCount] = useState(0); const incrementCount = () => { setCount((count) => count + 1); } return ( <> <div> {count} </div> <button onClick={incrementCount}> CountUp! </button> </> ); } CountUp ボタンを押すたびにこのコンポーネントのレンダリングが走ります。この再レンダリングは描画の変更の有無に関わらず実行されるので、例えば {count} をdivタグごと削除したとしても CountUp ボタンを押した時にレンダリング処理が走ります。 親コンポーネントのレンダリングによるレンダリング 親コンポーネントがレンダリングされると、渡される props の変更の有無に関わらずレンダリングが走ります。つまりコンポーネントツリーのルートコンポーネントがレンダリングした場合、その子コンポーネント、孫コンポーネントと連鎖的にレンダリング処理が走ります。 以下のように親コンポーネントにあたる App コンポーネントから子コンポーネントにあたる Child コンポーネントを呼んでみます。 const Child = () => { console.log("Childレンダリング!") return ( <div> Childコンポーネント! </div> ); } const App = () => { console.log("Appレンダリング!") const [count, setCount] = useState(0); const incrementCount = () => { setCount((count) => count + 1); } return ( <> <div> {count} </div> <button onClick={incrementCount}> CountUp! </button> <Child /> </> ); } 親コンポーネントにあたる App コンポーネントにレンダリングが走ると、子コンポーネントである Childコンポーネントにもレンダリング処理が走ります。例え props に変更がなくても(props 自体がなくても)子コンポーネントは再レンダリングします。 再レンダリングの防止方法 レンダリングコストの高いコンポーネントの場合、不必要な再レンダリングをメモ化で未然に防ぐことによりパフォーマンスの向上が見込めます。 (メモ化自体がコストのかかる処理であるため、必要以上にメモ化で再レンダリングを防ごうとすると返ってパフォーマンスが悪くなる可能性もあります。) React.memo による再レンダリング防止 React.memo() 関数にコンポーネントを渡すことにより、props の変更の有無を確認して再レンダリングが制御されるラッパーコンポーネントが返されます。 const Child = React.memo(({message}) => { console.log("Childレンダリング!") return ( <div> {message} </div> ); }) const App = () => { console.log("Appレンダリング!") const [count, setCount] = useState(0); const incrementCount = () => { setCount((count) => count + 1); } return ( <> <div> {count} </div> <button onClick={incrementCount}> CountUp! </button> <Child message={"Childコンポーネント!"}/> </> ); } 上のように React.memo() で Child コンポーネントをラップすることにより、親コンポーネントのレンダリング による再レンダリングを防ぐことができます。 (props として count を渡すようにすると、ボタンクリックの度に渡される props の値が変わるので、ちゃんと Child コンポーネントも再レンダリングされるようになります。) ここで、props として渡している値が関数であった場合では React.memo() のみの制御ではうまくいかなくなります。なぜなら、関数はレンダリングごとに別参照のオブジェクトとして生成されるため、props が等価でないと判断されるためです。 const Child = React.memo(({action}) => { console.log("Childレンダリング!") return ( <button onClick={action}> Action! </button> ); }) const App = () => { console.log("Appレンダリング!") const [count, setCount] = useState(0); const incrementCount = () => { setCount((count) => count + 1); } const sayHello = () => { console.log('Hello!'); } return ( <> <div> {count} </div> <button onClick={incrementCount}> CountUp! </button> <Child action={sayHello}/> </> ); } CountUp! ボタンを押して App コンポーネントが再レンダリングする度に新しい参照の sayHello 関数が渡され、Child コンポーネントのレンダリングも走ります。 このような場合は useCallback の使用を検討します。 useCallback による再レンダリング防止 useCallback に関数を渡すことにより、メモ化をおこなってくれます。また第二引数に依存配列を渡すことによりそのメモ化をコントロールすることができます。今回はただの文字列をコンソール出力する関数なので依存配列は空配列とします。 const Child = React.memo(({action}) => { console.log("Childレンダリング!") return ( <button onClick={action}> Action! </button> ); }) const App = () => { console.log("Appレンダリング!") const [count, setCount] = useState(0); const incrementCount = () => { setCount((count) => count + 1); } const sayHello = useCallback(() => { console.log('Hello!'); }, []) return ( <> <div> {count} </div> <button onClick={incrementCount}> CountUp! </button> <Child action={sayHello}/> </> ); } このようにして関数をメモ化することにより、CountUp! ボタンを押すたびに Child コンポーネントが再レンダリングされるのを防ぐことができます。 また、渡す props をメモ化しても Child コンポーネント自体がメモ化されていなかったら普通に再レンダリングが走ってしまうので注意が必要です。(親コンポーネントがレンダリングされていることに変わりはないため) 最後に、もう一捻り加えてみます。 message という state を追加して、count を2倍した値を使って message を更新する関数を props として渡してみます。依存配列には count を入れる必要があります。( count を依存に設定しないと、いくら CountUp! ボタンをクリックしても Action! ボタンをクリックしたときの出力が Double : 0 のままになってしまいます) const Child = React.memo(({action}) => { console.log("Childレンダリング!") return ( <button onClick={action}> Action! </button> ); }) const App = () => { console.log("Appレンダリング!") const [count, setCount] = useState(0); const [message, setMessage] = useState("initialized..."); const incrementCount = () => { setCount((count) => count + 1); } const updateMessage = useCallback(() => { setMessage(() => `Double : ${count * 2}`); }, [count]) return ( <> <div> {count} </div> <div> {message} </div> <button onClick={incrementCount}> CountUp! </button> <Child action={updateMessage}/> </> ); } CountUp! ボタンを押すたびに updateMessage 関数が新しい count に基づいて作られるため、どうしても Child コンポーネントのレンダリングが走ってしまいます。 props として渡している関数が state に依存しているのが再レンダリングの原因であり、このような場合に Child コンポーネントの再レンダリングを避けたいときは、useReducer により「 state に非依存な関数」を props として渡すようにする方法があります。 useReducer での state 管理 使い方については省略しますが、useReducer は useState のような state を管理するためのフックのひとつ(というか、useState は内部的に useReducer を使っています。)です。重要なのは、今回の場合 useReducer を使用することにより dispatch という state に依存していない関数を props として渡すことができるようになる、ということです。updateMessage 関数の中で count を参照していたような処理は reducer の中に閉じ込めることができます。 まずは、reducer を下のように作成します。 const initialState = { count: 0, message: "initialized..." }; const reducer = (state = initialState, action) => { switch (action.type) { case 'count/increment': return { ...state, count: state.count + 1 } case 'message/update': return { ...state, message: `Double : ${state.count * 2}` } default: return state; } }; count と message をシングルツリーの state オブジェクトの中にまとめることにより、「既存の state を受け取って新しい state オブジェクトを返す」というピュアな reducer 動きの中に処理を押し込むことができます。 あとは、しかるべき action を dispatch する関数をコンポーネントに配置すればよいだけなので、以下のように依存のない関数を porps として渡すことができるようになります。 const Child = React.memo(({action}) => { console.log("Childレンダリング!") return ( <button onClick={action}> Action! </button> ); }) const App = () => { console.log("Appレンダリング!") const [{ count, message }, dispatch] = useReducer(reducer, initialState); const incrementCount = () => { dispatch({ type: 'count/increment' }); } const updateMessage = useCallback(() => { dispatch({ type: 'message/update' }); }, []) return ( <> <div> {count} </div> <div> {message} </div> <button onClick={incrementCount}> CountUp! </button> <Child action={updateMessage}/> </> ); } これで、CountUp! ボタンを押してもその度に Child コンポーネントがレンダリングされるのを防げるようになりました。 Action! ボタンを押しても正常に動作します。 今回は dispatch を呼ぶ場所を App コンポーネントにまとめていますが、<Child dispatch={dispatch}/> のようにして Child コンポーネントから dispatch するやり方もあります。その場合、useCallback のラップがなくなりすっきりしそうです。 まとめ 今回挙げたもの以外にも、useMemo というメモ化された値を返すフックもあります。 繰り返しになりますが、メモ化自体にコストがかかるため、パフォーマンス計測などを通して適切にコンポーネントのレンダリングを制御することが大切だと思います! 参考 ・https://qiita.com/hellokenta/items/6b795501a0a8921bb6b5 ・https://qiita.com/soarflat/items/b9d3d17b8ab1f5dbfed2 ・https://qiita.com/uhyo/items/cea1bd157453a85feebf
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactハンズオン

Reactハンズオン はじめに この記事では、ハンズオン形式でReactの基礎を学ぶことができます。JavaScriptがなんとなく読める程度の知識があると読みやすいかと思います。 研修の流れ Reactとは Reactの環境構築 Reactでカウントアプリ/タスク管理アプリを作ってみる Reactとは ReactはJavaScriptのライブラリです。Reactを使うことでWEBページを独自の記法で簡単に作成することができます。 HTMLとかCSSで作らないの? WEBページの作り方を学ぶ際、はじめにHTMLやCSSを使った作成方法を学ぶかと思います。ReactでWEBページを作成するときも、最終的にはReactの記述を元にhtmlやcssなどのファイルが自動で生成されます。具体的にはnpm run buildを実行するとbuildフォルダが生成され、その中にindex.htmlやその他諸々のファイルが生成されます。つまり、buildフォルダをサーバに置けばサイトを公開することができます。 Reactの特徴 コンポーネント単位でページを作成することができる コンポーネントとは、WEBページの各パーツのことです。例えば、ヘッダーとかボタンとかその他諸々 コンポーネントを再利用することで簡潔なコードになる Reactの環境構築 Node.jsが入っている前提です。 npmを最新にする。 npm update npm create-react-appをインストール。 npm install -g create-react-app これでReactの開発環境が整いました。 Reactでカウントアプリ/タスク管理アプリを作ってみる Reactアプリを作成して表示する デスクトップに移動。 cd デスクトップのパス アプリを作成する。 npx create-react-app my-app 作成したアプリに移動。 cd my-app サーバを立てる。 npm start これでサーバが立ち上がります。ブラウザを立ち上げてlocalhost:3000を開くとWEBサイトが表示できているかと思います。 Reactアプリの構成 ここで、my-appがどのような構成になっているか見てみます。my-appをエディタで開いてみてください。 フォルダの中にはいくつかのファイルやフォルダがありますが、まず知っておくべきものはpublicとsrcの2つです。 public:Webで公開されるファイルを置いておくフォルダ src:Reactのソースコードやそれに関連するデータを置いておくフォルダ HelloWorldを表示してみる では、今表示されているページの代わりにHelloWorldという文字だけのページを表示してみましょう。 1. src/App.jsの<div className="App">...</div>を削除する。 2. 代わりに<h2>HelloWorld</h2>と書き込む。 ブラウザを確認してみると、HelloWorldが表示されているかと思います。このように、表示する内容を変更する場合にはreturn内を変更することになります。ここではHTMLの記法で記述することができます。 厳密にはJSXという記法になります。HTMLとの違いはほとんどありません。強いて言えば、JSXではタグは必ず閉じなければならないので<img src="...">は<img src="..."></img>とするか<img src="" />とする必要があります。 表示処理の流れ 先ほどApp.jsを編集しましたが、変更したデータはどのような流れで処理され表示されるのでしょうか。結論から言うと、先ほど変更したApp.jsのデータは、同じ階層にあるindex.jsに渡された後、ReactDOM.render関数によってpublic/index.htmlの<div id="root"></div>の子要素として追加されます。 カウントアプリを作ってみる 表示までの流れがわかったところで、実際にカウントアプリを作成してみましょう。今回作成するカウントアプリは、ボタンを押すと表示されている数が+1されるシンプルなものです。 function App() { ... }を削除 代わりに以下のコードを貼り付け class App extends React.Component { render() { return ( <div> <h2>タイトル</h2> <div>0</div> <button>COUNT UP</button> </div> ); } } 冒頭部分にimport React from 'react';を追記。 ブラウザにカウントアプリのページが表示されているか確認してください。ここでは、function App() { ... }を削除しclass App extends React.Component { ... }に置き換えましたが、どちらもreturn内に記述した要素が表示されるという点は同じです。なぜ置き換えたかや細かい違いは後ほど説明します。 さて、表示されたページにはタイトルとカウント、ボタンが表示されています。現在はボタンを押してもカウント数は変わりません。ではここからReactの記法を使って機能を追加していきましょう。 まずはタイトルを変更していきます。 1. render() { ... }の上に以下を追加 constructor(props) { super(props); this.title = "カウントアプリ"; } タイトルを{ this.title }に置き換え ブラウザを確認するとタイトルだった所がカウントアプリになっていると思います。これは、{ this.title }の中のthis.titleがカウントアプリに置き換えられたからです。this.titleはconstructor内で定義されています。 Reactではこのように、{}の中にJavaScriptの変数を記述するとReactがhtml要素としてレンダリングしてくれます。 コメントアウトは{/* コメント文 */}で記述できます。 constructor(props) { ... }やsuper(props);の記述はJavaScriptのclassの用法になります。気になる方は「javascript class 書き方」などと調べてみてください。また、Reactではconstructorとsuperの引数にpropsを渡すことになっています。はじめのうちはこういうものなんだなぁと思っていただいて大丈夫です。 Stateを使う 次に、ボタンを押したらカウントアップしてくれる機能を追加します。 super(props);の下に以下を追加。 this.state = { count: 0 }; render() { ... }の上に以下のメソッドを追加。 countUp() { this.setState({count: this.state.count + 1 }); } <div>0</div>を<div>{ this.state.count }</div>に置き換える。 <button>COUNT UP</button>を <button onClick={ () => { this.countUp() } }>COUNT UP</button>に置き換える。 ブラウザに戻り、ボタンを押してみてください。カウントが増加しているかと思います。ここでは、ReactのStateという機能を使ってcountの値を管理しています。Stateは、変数を管理する場所のようなイメージです。 使い方は以下の通りです。 constructor内でthis.state = { 変数名: 値, 変数名: 値, ... }として初期値を指定 this.state.変数名で値にアクセス this.setState({ 変数名: 新しい値 })で値を変更 countの値を増加させるにはsetStateメソッドを実行する必要があります。今回のアプリでは、ボタンを押すとcountUpメソッドが実行され、countUpメソッドの中でsetStateメソッドが呼ばれています。 ボタンが押された時に関数を実行したい場合は、onClick={ 実行したい関数 }を属性に追加します。今回はonClick={ () => { this.countUp() } }としています。 カウントアプリの作成は以上ですが、countDownのような関数を作成して、ボタンのonClick属性にあてることでカウントダウン機能を実装することもできます。 タスク管理アプリを作ってみる では次にタスク管理アプリを作成してみましょう。今回作成するタスク管理アプリはタスク一覧の表示、タスク追加の機能があります。 class App extends React.Component { ... }を以下に置き換える。 class App extends React.Component { render() { return ( <div> <h2>タスク管理アプリ</h2> <input /> <button>追加</button> <div>1. 本を買う</div> <div>2. ランニングする</div> </div> ); } } ブラウザでタスク管理アプリが表示されていることを確認してください。今表示されているタスクは直書きです。そのため、一覧機能や追加機能は実装されていません。 では、一覧機能を追加していきましょう。まずはStateを使って、タスクの内容をまとめる配列を管理していきます。 render() { ... }の上に以下を追加する。 constructor(props) { super(props); this.state = { tasks: [ "本を買う", "ランニングする", "牛乳を買う" ] } } 以下のコードを <div>1. 本を買う</div> <div>2. ランニングする</div> 以下に置き換える。 { this.state.tasks.map( (task, index) => { return <div>{ index + 1 }. { task }</div> }) } ブラウザを確認すると、Stateのtasksで管理しているタスクが表示されているかと思います。書き換えた内容を順を追って説明していくと、 1.で、タスクを管理するtasks配列をStateの変数として定義。 2.で、tasks配列の中身を取り出して表示。 となります。2.はやや複雑ですが分解して考えるとそれほど難しくありません。 ポイントは、 アロー関数表記 mapメソッド returnの値 の3つです。 アロー関数表記 まずはアロー関数表記ですが、これは従来の関数の書き方を簡潔にした表記方法です。 // 従来 function sayHi(name) { const msg = "hi " + name; console.log(msg); } sayHi("Bob"); // hi Bob // アロー関数表記 const sayHi = (name) => { const msg = "hi " + name; console.log(msg); }; sayHi("Bob"); // hi Bob 慣れてしまえば難しくはありません。今はこういう書き方があるんだなぁくらいに思ってもらえれば大丈夫です。 mapメソッド mapメソッドは、配列の各要素に対してある処理を行うメソッドで、 配列.map(処理が書かれた関数)のように書きます。 例えば、配列array1の各要素を2倍した値からなる配列array2を得る場合は以下のように書けます。 const array1 = [2, 4, 5, 8]; const array2 = array1.map( (value) => { return value * 2; }); console.log(array2); // [ 4, 8, 10, 16 ] mapメソッドが引数にとっている関数の第一引数には配列の要素が入ります。第二引数をとると、取り出した要素のインデックスを取得できます。 returnの値 最後にreturnの値についてです。Reactではhtml要素を一つの値のように扱うことができます。 例えば、 const element = <h2>タスク管理アプリ</h2> といった扱いができます。 以上を踏まえ、改めて2.で置き換えた箇所を見てみると、 { this.state.tasks.map( (task, index) => { return <div>{index + 1}. {task}</div> }) } tasks配列に対してmapメソッドを使用し、mapメソッドの引数に、アロー関数表記で処理したい内容を記述していることがわかります。mapメソッドのreturnではhtml要素が返されています。 では次に、タスクの追加機能を実装したいと思います。 StateにtaskContent: ""を追加する。 inputタグの属性に value={ this.state.taskContent }と onChange={(e) => { this.setState({ taskContent: e.target.value }) }} を追加。 buttonタグの属性に onClick={ () => { this.addTask() } }を追加。 render() { ... }の上に以下を追加。 addTask() { let tasks = this.state.tasks; tasks.push(this.state.taskContent); this.setState({tasks: tasks}); this.state.taskContent = ""; } ブラウザで、テキストフィールドに適当なタスク内容を記入して追加ボタンを押してみてください。タスクが追加されたのがわかります。 追記した内容を順に説明すると、 1.でテキストフィールドの値を保持するtaskContent変数をStateにて初期化 2.では、 taskContentの値をテキストフィールドに表示 テキストフィールドに入力された値をtaskContentに反映 3.でボタンをクリックした時にaddTask関数を実行するように指定 4.でaddTask関数を定義 addTask関数でtaskContentの値をStateのtasks配列に追加しています。 コンポーネントとProps ここまでで、一覧機能と追加機能を実装することができました。ここで、コンポーネント設計の考え方を導入してタスクの表示パーツをコンポーネント化してみましょう。コンポーネント化することでより簡潔なコードで記述することができます。 srcフォルダの中にcomponentsフォルダを作成。 作成したcomponentsフォルダの中にTask.jsというファイルを作成。 Task.jsに以下を貼り付ける。 import React from 'react'; class Task extends React.Component { constructor(props) { super(props); this.number = props.number; this.task = props.task; } render() { return ( <div> { this.number }. { this.task } </div> ); } } export default Task; App.jsの冒頭部分に import Task from './components/Task.js'; を追記してTask.jsをインポート。 mapメソッドの戻り値になっている<div>...</div>を <Task number={index + 1} task={task}/>に置き換える。 ブラウザを確認して問題なくページが表示されているか確認してください。 手順を順に解説していくと、 タスクを表示するパーツをTaskコンポーネントにして置き換えたい コンポーネントをまとめるcomponentsフォルダを作成し、その中にTask.jsを作成 Task.jsの中身もApp.jsと同様の構造で、Taskクラスコンポーネントをエクスポートしている そのエクスポートされたTaskクラスをApp.jsの冒頭部分でインポート App.jsのmapメソッドの戻り値としてを置く という流れになります。 ここで、<Task .../>の...に当たる部分を見てみます。number={index + 1} task={task}とありますが、ここでTaskにnumberとtaskというpropsを渡しています。propsとはコンポーネントに渡す値のことです。 この2つのpropsがどのように使われているかTask.jsを確認してみると、constructor内でthis.number = props.number; this.task = props.task; としてクラス内の変数に渡されていることが分かります。このようにコンポーネントに渡されたprops の値は、props.props名とすることでコンポーネント内で使用することができます。 今回の例では、コンポーネント化のメリットをあまり感じられなかったかもしれませんが、もう少し大きな開発になってくるとコードの見易さや管理のし易さ、再利用性の観点からメリットが大きくなります。 以上で、2つのアプリの実装が完了しました! 関数コンポーネントとは Reactのコンポーネントにはクラスコンポーネントと関数コンポーネントの2種類のコンポーネントがあります。 今まで書いてきたコンポーネントはクラスコンポーネントです。 class コンポーネント名 extends React.component { render() { return ( <div> 表示内容 </div> ); } } 一方、関数コンポーネントは次のような書き方です。 // アロー関数表記で書かれることもある function コンポーネント名() { return ( <div> 表示内容 </div> ); } 関数コンポーネントはクラスコンポーネントよりも簡潔に書けるといったメリットがあります。クラスコンポーネントで出来ることは基本的には関数コンポーネントでも実装できますが、少し難解な記法を使ったりするのでクラスコンポーネントに慣れてから関数コンポーネントを触ってみると理解しやすいかと思います。 はじめにfunction App() { ... }を削除してクラスコンポーネントに置き換えたのはこのためです。 以上でReactハンズオンは修了です。お疲れ様でした。 紹介しきれなかったこと ReactHooks ReactRouter Redux
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactの.envにシークレットトークンとかを決して含めてはいけない

やりたいこと Create React Appで作成したReactアプリケーションで環境変数を扱いたいという場面があり注意すべきことに出会ったのでそれについて書いていきます。 早速Create React Appのドキュメントを読んでみました。次のように書かれています。 Your project can consume variables declared in your environment as if they were declared locally in your JS files. By default you will have NODE_ENV defined for you, and any other environment variables starting with REACT_APP_. つまりREACT_APP_から始まる環境変数を使用することができるようです。 REACT_APP_NOT_SECRET_CODEという名前で変数を宣言すると、JS側からはprocess.env.REACT_APP_NOT_SECRET_CODEとして使用できるそうです。 環境変数の定義 シェルで一時的な環境変数を追加して使用することもできます。 Bashの場合は以下のようになります。 REACT_APP_NOT_SECRET_CODE=abcdef npm start またはプロジェクトのルートに.envファイルを作成して、そのファイルに環境変数を定義することもできます。 .env REACT_APP_NOT_SECRET_CODE=abcdef 以下のようにDOMAINという定義された変数を拡張して、.envファイルで使用するすることもできます。 .env DOMAIN=www.example.com REACT_APP_FOO=$DOMAIN/foo REACT_APP_BAR=$DOMAIN/bar 環境変数を変更すると、それを反映させるために、開発サーバーが起動している場合は再起動が必要になります。 注意ポイント。プライベートAPIキーなどは環境変数で使わないこと。 ドキュメントには注意書きで次のように書かれています。 WARNING: Do not store any secrets (such as private API keys) in your React app! Environment variables are embedded into the build, meaning anyone can view them by inspecting your app's files. 訳すと以下のような意味です。 警告:Reactアプリに秘密(プライベートAPIキーなど)を保存しないでください。 環境変数はビルドに組み込まれているので、アプリのファイルを調べれば誰でも見ることができます。 ビルド時には、process.env.REACT_APP_NOT_SECRET_CODEが環境変数REACT_APP_NOT_SECRET_CODEの現在の値に置き換えられます。 つまり.envに環境変数を書いて.envファイルをgitignoreしておくと、GitHubに変数を公開することを防ぐことができますが、ビルドしたファイルの内容を見れば誰でも変数の内容を見ることができるようです。 例 text.jsx render() { return ( <div> <small>You are running this application in <b>{process.env.NODE_ENV}</b> mode.</small> <form> <input type="hidden" defaultValue={process.env.REACT_APP_NOT_SECRET_CODE} /> </form> </div> ); } これをnpm startして、ブラウザで画面を見てみます。検証をして内容を見てみると以下のようになります。REACT_APP_NOT_SECRET_CODEの内容が丸見えですね。 <div> <small>You are running this application in <b>development</b> mode.</small> <form> <input type="hidden" value="abcdef" /> </form> </div> どうするか クライアント側から直接シークレットキー等を使用して外部のAPIを叩いているという状態を避けるためにどうするかという問題ですが、APIキーやシークレットはバックエンドにのみが持つべきだそうです。 クライアントがバックエンドAPIにリクエストを送り、バックエンドAPIがAPIキーを使って実際のAPIコールを行い、データをクライアントに送り返すように実装するのが良いのではないかと考え、そちらの方向で実装をするようにしました。またAPIキーの使用範囲の制限などをしていたり、特に公開されても問題のないキーであれば、それを使用してフロントエンド側から外部のAPIを直接叩いても問題ないと思います。 以上です。コメント、指摘等あれば宜しくお願い致します?‍♂️
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React学習アウトプット(redux-container)

今回はreduxにおけるcontainerについて。 containerの役割は、明示的にStoreの内容を変更するために行う作業。 ただし、今現在ではHooksを使う方が圧倒的に記述量、ファイル数が減るためHooksを使う方がおススメらしい。 まぁいつか使う時が来たときに備忘録も兼ねて書いていきます。 import { connect } from 'react-redux' import { compose } from 'redux' import Class from 'Class.jsx' まずは、import { connect } from 'react-redux'とimport { compose } from 'redux'が必要になる。 また、対象とするクラスコンポーネントが必要になる。 今回は例としてimport Class from 'Class.jsx'としている。 次にmapStateTpPropsとmapDispatchToPropsの2つが必要となる。 これら2つはuseStateとuseDispatchと同じ役割を果たす。 import { connect } from 'react-redux' import { compose } from 'redux' import Class from 'Class.jsx' const mapStateTpProps = state => { return { users: state.users } } const mapDispatchToProps = dispatch => { return { actions: { signIn() { dispatch(アクション) } } } } 最後にcomposeとconnectを使ってこれらのアクションが行えるようにする。 import { connect } from 'react-redux' import { compose } from 'redux' import Class from 'Class.jsx' const mapStateTpProps = state => { return { users: state.users } } const mapDispatchToProps = dispatch => { return { actions: { signIn() { dispatch(アクション) } } } } export default compose( connect( mapStateTpProps, mapDispatchToProps ) )(Class) composeの中でconnectを使い、connectの引数にはmapStateTpPropsとmapDispatchToPropsを渡す。最後に対象のクラスコンポーネントを渡す。 これで準備はOK。 後は対象のクラスコンポーネントを以下のようにする。 import React, {Component} from 'react' export default class Class extends Component { render() { console.log(this.props.users) console.log(this.props.actions) } } Componentをインポートし、this.propsでコンテナーで定義したものを参照できる。 感想としてはまぁ使う場面は少ないのかなと思います。 やはりクラスコンポーネントに変えてさらに継承も行わなきゃいけないし、継承を行うために色々記述しなければいけないので当分はHooksを使っていきます。 アウトプットは大事!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【material-ui】「要素hover→別要素pop」を簡単に記述するコンポーネント

環境 React.js TypeScript material-ui 目的 お気持ち: - シンプルなwebサイトにしたいが、詳細も載せたい。 - 別リンクに飛ぶ詳細は面倒。 - 要素にhoverした時に詳細がpopしてほしい。 - ↑を可能にするPopper(material-ui)はコード量が30行くらいと多い。 - pop付要素を手軽に実装できるようにラップしよう。 概要 Popper(material-ui)をラップし、手軽な実装を実現。 作成したコンポーネントAddPopper AddPopperの子要素にhoverすると、props.popに渡したReact要素が表示される。 使用例 Webサイトに表示した、カプースチンのソナタファンタジーいいよねという文字列の「カプースチン」と「ソナタファンタジー」の部分にhoverした時、補足説明が現れる(popする)ようにする。 app.tsx function App() { return ( <div className="App"> <AddPopper //捕捉説明① pop={<div>作曲家: Nicolai Kapustin(1937-2020, ウクライナ)</div>} > {/*このspanにhoverすると捕捉説明①が表示される。*/} <span>カプースチン</span> </AddPopper> の {/*補足説明②*/} <AddPopper pop={<div>Op.39 4楽章構成</div>}> {/*このspanにhoverすると捕捉説明②が表示される。*/} <span>ソナタファンタジー</span> </AddPopper> いいよね。 </div> ); } propsの定義 interface Props { pop: React.ReactElement<any> //hover時に表示する要素 type: 'span' | 'div' //被hover要素がinlineかどうかを指定 children: React.ReactElement<any> //この要素にhoverすると発火 placement?: PopperPlacementType; //popの位置を決定 } AddPopperのソースコード import { Fade, PopperPlacementType } from "@material-ui/core"; import { Popper } from "@material-ui/core"; import React, { useCallback, useState } from "react"; interface Props { pop: React.ReactElement<any>; children: React.ReactElement<any>; placement?: PopperPlacementType; type?: "span" | "div"; } export function AddPopper(props: Props) { //被hover要素の位置を受け取る。 const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); //hover時に発火 const openPop = useCallback((event: React.MouseEvent<HTMLElement>) => { setAnchorEl(event.currentTarget); }, []); //hoverが外れた時に発火 const closePop = useCallback(() => { setAnchorEl(null); }, []); //hoverの有無をbooleanで持つ const open = Boolean(anchorEl); //被hover要素に、カーソル設定とhover時背景色設定を追加している。 const Base = () => React.cloneElement(props.children, { style: { cursor: "default", backgroundColor: open ? "lightgrey" : undefined, }, }); //被hover要素とPop要素にマウスイベントを付加 return React.createElement( props.type ?? "div", { onMouseEnter: openPop, onMouseLeave: closePop }, [ <Base key="base" />, <Popper key="popper" open={open} anchorEl={anchorEl} transition placement={props.placement ?? "bottom-start"} > {({ TransitionProps }) => ( <Fade {...TransitionProps} timeout={350}> {props.pop} </Fade> )} </Popper>, ] ); } ソースコードの説明 基本的にはPopper(material-ui)の書き方に倣って書いている。 props.childrenとして受け取った要素(AddPopperの子要素)には、hover時のカーソルと、hover時の背景色が自動的に指定される。 被hover要素が、inlineの時はspanを、blockの時はdiv、という使い分けのために、React.CreateElementによってtype(タグ名)をpropsに応じて指定可能にしている。 props.placementでpopの位置を調整。defaultはbottom-startとなっている。参照->PopperPlacementTypeの定義
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React学習アウトプット(redux-operations)

今回のアウトプットはreduxにおけるoperationsになります。 operationsの役割としては、storeの情報を外部から取得した情報(APIとか)を元に変更する時に非同期処理でasync awaitを使えるためにoperationsを行うらしいです。 方法としては簡単! ただ無名関数をreturnすれば良いだけ! 以下コード例になります。 import { signInAction } from './action' export const signIn = () => { // 非同期処理の関数をリターン return async (dispatch, getState) => { await axios.get('https://api.github.com/users/aki-743').then((res) => { dispatch(signInAction({ isSignedIn: true, username: res.login }) })) } } このようにして非同期処理を実行しつつ、Storeの内容を変更することが可能。 ここらへんはVueと少し似ているところがあったので理解はし易かった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React DatePickerのcustomInputにrefを適用する

react-datepickerのcustomInputにrefを適用する方法です。 まずはサンプルを。 import React from "react"; import DatePicker from "react-datepicker"; interface InputProps { inputRef: React.Ref<HTMLInputElement>; onClick?: React.MouseEventHandler<HTMLInputElement>; onChange?: React.ChangeEventHandler<HTMLInputElement>; value?: string | ReadonlyArray<string> | number; } const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => { return <input type="text" ref={props.inputRef} onClick={props.onClick} onChange={props.onChange} value={props.value} />; }) interface Props { inputRef: React.Ref<HTMLInputElement>; } const DP: React.FC<Props> = props => { const [selected, setSelected] = React.useState<Date | null>(null); const onChangeDatePicker = (date: Date | [Date, Date] | null) => { if (date && date instanceof Date) { setSelected(date); } }; return ( <DatePicker selected={selected} onChange={onChangeDatePicker} customInput={ <Input inputRef={props.inputRef} /> } /> ); }; const App: React.FC = () => { const ref = React.useRef<HTMLInputElement>(null); const onClick: React.MouseEventHandler<HTMLButtonElement> = event => { console.log(ref.current?.value); }; return ( <form> <DP inputRef={ref} /><br/> <button type="submit" onClick={onClick}>送信</button> </form> ); }; 順番に説明していきます。 customInputに指定するコンポーネント DatePicker の customInput に指定するコンポーネントは以下のように定義します。 interface InputProps { inputRef: React.Ref<HTMLInputElement>; // ① onClick?: React.MouseEventHandler<HTMLInputElement>; // ② onChange?: React.ChangeEventHandler<HTMLInputElement>; // ② value?: string | ReadonlyArray<string> | number; // ② } const Input = React.forwardRef<HTMLInputElement, InputProps>((props, ref) => { // ③ return <input type="text" ref={props.inputRef} // ④ onClick={props.onClick} onChange={props.onChange} value={props.value} />; }) ① プロパティにReact.Ref型の値を定義 プロパティにReact.Ref型の値を定義します。 名称はref以外にする必要があります。 ② プロパティにcustomInputとして動作するための値を定義 プロパティにonChangeonClickvalueを定義します。 これらを定義しなければDatePickerのcustomInputとして正常に動作しません。 ③ コンポーネントはforwardRefで定義 refを親子間で受け渡せるようforwardRefでコンポーネントを定義します。 ただし引数refは使用しません。 ④ refを適用したいコンポーネントにプロパティを指定 refを適用したいコンポーネントにforwardRefの引数ではなくプロパティのReact.Ref型の値を指定します。 DatePickerの定義 customInputに指定するコンポーネントを定義したら、これを使用してDatePickerを定義します。 interface Props { inputRef: React.Ref<HTMLInputElement>; // ⑤ } const DP: React.FC<Props> = props => { const [selected, setSelected] = React.useState<Date | null>(null); const onChangeDatePicker = (date: Date | [Date, Date] | null) => { if (date && date instanceof Date) { setSelected(date); } }; return ( <DatePicker selected={selected} onChange={onChangeDatePicker} customInput={ <Input inputRef={props.inputRef} /> // ⑥ } /> ); }; ⑤ プロパティにReact.Ref型の値を定義 プロパティにReact.Ref型の値を定義します。 名称はref以外にしたほうが無難です。 ⑥ customInputのコンポーネントにプロパティを指定 customInputのコンポーネントにプロパティのReact.Ref型の値を指定します。 useRefの定義 const App: React.FC = () => { const ref = React.useRef<HTMLInputElement>(null); // ⑦ const onClick: React.MouseEventHandler<HTMLButtonElement> = event => { console.log(ref.current?.value); // ⑧ }; return ( <form> <DP inputRef={ref} /><br/> // ⑨ <button type="submit" onClick={onClick}>送信</button> </form> ); }; ⑦ refを定義 useRefでrefを定義します。 ⑧ refの使用 イベントハンドラ等でrefを使用します。 ⑨ refを適用 refを適用します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スプレッド構文について

JavaScriptを触りはじめの時にスプレッド構文の理解をすることがとても苦手でした。 今でも完璧に理解できてはいないですが現段階で理解している部分を言語化してみたいと思います。 スプレッド構文の理解で苦しんでいる方の一助になれれば幸いです! スプレッド構文とは スプレッド構文 (...) を使うと、配列式や文字列などの反復可能オブジェクトを、0 個以上の引数 (関数呼び出しの場合) や要素 (配列リテラルの場合) を期待された場所で展開したり、オブジェクト式を、0 個以上のキーと値のペア (オブジェクトリテラルの場合) を期待された場所で展開したりすることができます。 MDNによると上記のように定義されていました。 正直どういうことか理解するの難しいですよね...(私は全く理解できませんでした) ※MDN(https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Spread_syntax) コードをみた方が理解できるかと思うので実際にコードと一緒に説明していきたいと思いますが、 ざっくりとした機能としては...(ピリオド3つ) を書くことで個々の値に展開ができ、それを順番に処理して結合させることができます。 ※ちなみに「codesandbox」という手軽に環境構築などができるサイトがあるので、そちらで実際にコードを打ちながら試してみると理解度が上がるかもしれません。 コードと一緒に解説 const arr1 = ["hoge", "foo"]; console.log(arr1); //["hoge", "foo"] //スプレッド構文を使ってこちらを展開してみます。 console.log(...arr1); // hoge foo //最初のコンソールでは配列で出力されてましたが、次のコンソールでは個々の値(hoge,foo)で出力されます。 上記のように配列の中身を順番に処理してくれます。 もう少し配列の例をあげてみます。 const sum = (x, y, z) => { console.log(x + y + z) }; const num1 = 1; const num2 = [2, 3]; sum(num1, ...num2) //6 //スプレッド構文でnum2の要素を順番に設定しているので2、3が設定されています。 //なのでsum(1, 2, 3)と同じことをしている状態です。 ※参照とコピーの違いについても解説したいのですが長くなってしまうので今回は省略します。 最後に スプレッド構文は私としてはかなり難しく感じました。色々とスプレッド構文で書いていくことで覚えられるようになると思うのでコードサンドボックスなどで是非試して見て欲しいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

useStateで定義した配列内のオブジェクトの値を変更する

はじめに 今回アプリを作成中useStateをオブジェクトにして複数のキーを持たせていたのですが、そのうち一つの値を変更するにはどうしたらいいのかというところでかなり苦戦したので自分の備忘録として記事にして残すことにしました。 状況 ここはとあるバー、useStateでの話です。 お客さんをid等で管理して、未成年はadult = falseとして お酒を出さないようにしていました。 ある日、一人の若い青年が日付が変わった頃に入店しました。 バーテンダーはその顔に見覚えがありました。 バーの常連、喜一郎おじいちゃんの孫、太郎君です。 しかし彼は未成年だったはず、今日はどうしたのだろう? カウンター席に座った太郎くんに今日はどうしたの?と話しかけます。 すると、ついさっき20歳になったからここのお酒を飲みたいというのです。 なるほど、日付が変わってすぐにここに来たのはそういうことか! そんなにお酒を飲んでみたかったのか、そう思うととてもほっこりしました。 さっそくadult = trueにしてお酒を出そうと思います。 const [customer, setCustomer] = useState([ ~ { id:154, name: "田中 喜一郎", adult: true }, ~ { id:16325, name: "田中 太郎", adult: false }, ~ ]); この時、idを引数にして、adultを変更するための関数を作成します。 const changeAdult = (id) => { setCustomer( customer.map((c) => { if (c.id === id) { return { ...c, adult: !c.adult }; } else { return c; } }) ); } あとは、buttonのonClickにこの関数を指定してあげれば 大丈夫です! <button onClick={() => changeAdult(c.id)}> 大人の階段 </button> 無事大人になった太郎くんは、初めてのお酒を美味しそうに飲んでいました。 また今度お爺ちゃんと飲みに来るそうです。 完
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

react-hook-formとyupで超お手軽フォームバリデーション実装

はじめに [※注意] 当方React学習歴半年未満のプログラミング初心者です。 現在エンジニア転職を目指しポートフォリオを制作中なのですが、 最近よく耳にするreact-hook-formをログインやサインインの機能に使ってみました。 これがすぐに理解でき、とても簡単にバリデーションを実装することができたので備忘録を兼ねてご紹介します。 拙い点がありましたら、ご指摘いただけると大変幸いです。 ※ 筆者都合によりベース環境にはNext.jsとChakra UIを使用しています。 ※ TypeScriptも使用していますが勉強中により、一部anyで逃げている箇所があります... 対象読者 これからポートフォリオなどのフォーム関連機能の実装に取り掛かろうとしている方 簡単にアプリのフォームバリデーションを実装したい方 バリデーションで正規表現を書きたくない方 何を作るのか (画像はFigmaで作っていたモックです。) 以下の様なごく一般的なサインアップフォームの実装となります。 ライブラリを用いない場合、恐らくパスワードの一致確認、入力確認、Emailのパターンマッチ確認、名前の文字数確認... など単純ですが中々に面倒ではありそうです。 ※なお、今回バリデーションを通ったあとのAPI連携処理等は書いていません。 準備 yarn add react-hook-form @hookform/resolvers yup react-hook-form 言わずもがなのフォームバリデーションライブラリです。 コード量を減らし、再レンダリングも抑制してくれつつ、高性能なバリデーションを可能とします。 @hookform/resolvers yupResolverをimportして、yupで作成したバリデーションとreact-hook-formを接続します。 yup 各フォーム項目ごとに異なるバリデーションを定義していきます。 宣言的で大変わかりやすいバリデーションが可能です。 本当はreact-hook-formのみでも可能なのですが、 コードがスッキリするかつemailなどの正規表現をyupが予め用意してくれている事もあり 今回は使用することにしました。 react-hook-formは結構最近にversionが7にアップデートされたそうです。 書き方が若干変わっているので一部公式のCodesandBoxなどが古い書き方のままになっています。 こちらの大変ありがたい記事は事前に見ておくといいでしょう。 実装 スタイルのみの実装 ※chakra UIを使用している点とコーディングが未熟な点については何卒ご了承ください。 単一のデータ入力の子コンポーネントTextFormと、それを纏めてsubmitボタンを設置するような 親コンポーネントSignupFormを作成します。 TextForm.tsx import { Badge, FormControl, FormLabel, Input, Text } from '@chakra-ui/react' import type { VFC } from 'react' export type TextFormProps = { label: string placeholder: string name: string type: string isRequired: boolean } const TextForm: VFC<TextFormProps> = (props: TextFormProps) => { return ( <FormControl id={props.name} w="400px"> <FormLabel m={1}> <Text display="inline" fontSize="13px" fontWeight="bold"> {props.label} </Text>{' '} {props.isRequired && ( <Badge bg="red.400" color="white" py="3px" px="5px" borderRadius="7px"> 必須 </Badge> )} </FormLabel> <Input type={props.type} placeholder={props.placeholder} borderColor="gray.500" borderRadius="10px" color="gray.700" _placeholder={{ fontSize: '14px', }} /> </FormControl> ) } export { TextForm } SignupForm.tsx import { VStack } from '@chakra-ui/react' import type { VFC } from 'react' import { TextForm } from '@/components/forms/unit' const SignupForm: VFC = () => { return ( <form name="SignupForm" noValidate> <VStack py="30px" bg="gray.100" w="650px" spacing="8" borderRadius="20px"> <TextForm label={'メールアドレス'} placeholder={'メールアドレスを入力'} name="email" type="email" isRequired={true} /> <TextForm label={'ユーザ名'} placeholder={'ユーザ名を入力'} name="username" type="text" isRequired={true} /> <TextForm label={'パスワード'} placeholder={'パスワードを入力'} type="password" name="password" isRequired={true} /> <TextForm label={'パスワード(確認用)'} placeholder={'上記と同じパスワードを入力'} type="password" name="password_confirm" isRequired={true} /> <NormalButton type="submit" width="200px" text="登録" bg="green.300" color="white" hover={{ bg: 'green.400' }} /> </VStack> </form> ) } export { SignupForm } (デザインと全然違いますがいったんお許しください。) まだ何のロジックもない側だけのフォームです。 formタグのnoValidateではブラウザが標準で出してくる バリデーションメッセージを無効にしています。(ちょっと鬱陶しいので) バリデーション実装 TextForm.tsx export type TextFormProps = { label: string placeholder: string name: string type: string isRequired: boolean + errorMessage?: string + registers?: any } const TextForm: VFC<TextFormProps> = (props: TextFormProps) => { return ( <FormControl id={props.name} w="400px"> <FormLabel m={1}> <Text display="inline" fontSize="13px" fontWeight="bold"> {props.label} </Text>{' '} {props.isRequired && ( <Badge bg="red.400" color="white" py="3px" px="5px" borderRadius="7px"> 必須 </Badge> )} </FormLabel> <Input type={props.type} placeholder={props.placeholder} borderColor="gray.500" borderRadius="10px" color="gray.700" _placeholder={{ fontSize: '14px', }} + {...props.registers} /> + <Text color="red.500" fontSize="14px"> + {props.errorMessage} + </Text> </FormControl> ) } 当該のフォームがバリデーションに引っかかった際に格納されるerrorMessageプロパティと、 それがあった際の出力処理 及び親コンポーネント側でのreact-hook-formの生成処理で 各formに設定しなければならないregisterというプロパティを追加しました。 SignupForm.tsx import { VStack } from '@chakra-ui/react' + import { yupResolver } from '@hookform/resolvers/yup' import type { VFC } from 'react' + import { useForm } from 'react-hook-form' + import * as yup from 'yup' import { NormalButton } from '@/components/common/unit' import { TextForm } from '@/components/forms/unit' + const REQUIRE_MSG = '必須入力項目です' + const VIOLATION_EMAIL = '正しい形式で入力してください' + const VIOLATION_NAME_COUNT = '名前は16文字以下で入力してください' + const VIOLATION_PASSWORD_COUNT = 'パスワードは16文字以下で入力してください' + const VIOLATION_PASSWORD_CONFIRM = '入力したパスワードが一致しません' const SignupSchema = yup.object().shape({ email: yup.string().required(REQUIRE_MSG).email(VIOLATION_EMAIL), username: yup.string().required(REQUIRE_MSG).max(16, VIOLATION_NAME_COUNT), password: yup.string().required(REQUIRE_MSG).max(16, VIOLATION_PASSWORD_COUNT), password_confirm: yup .string() .required(REQUIRE_MSG) .oneOf([yup.ref('password'), null], VIOLATION_PASSWORD_CONFIRM), }) + const SignupForm: VFC = () => { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: yupResolver(SignupSchema), + }) + const onSubmit = (data: any) => { + // eslint-disable-next-line no-console + console.log(data) + } return ( <form name="SignupForm" onSubmit={handleSubmit(onSubmit)} noValidate> <VStack py="30px" bg="gray.100" w="650px" spacing="8" borderRadius="20px"> <TextForm label={'メールアドレス'} placeholder={'メールアドレスを入力'} name="email" type="email" isRequired={true} + registers={register('email')} + errorMessage={errors.email?.message} /> <TextForm label={'ユーザ名'} placeholder={'ユーザ名を入力'} name="username" type="text" isRequired={true} + registers={register('username')} + errorMessage={errors.username?.message} /> <TextForm label={'パスワード'} placeholder={'パスワードを入力'} type="password" name="password" isRequired={true} + registers={register('password')} + errorMessage={errors.password?.message} /> <TextForm label={'パスワード(確認用)'} placeholder={'上記と同じパスワードを入力'} type="password" name="password_confirm" isRequired={true} + registers={register('password_confirm')} + errorMessage={errors.password_confirm?.message} /> <NormalButton type="submit" width="200px" text="登録" bg="green.300" color="white" hover={{ bg: 'green.400' }} /> </VStack> </form> ) } export { SignupForm } 追加したuseFormは必ず必要なもので、yupとyupResolverについては バリデーションを楽にしてくれるものと考えています。 SignupSchemaにこのフォームでの各項目に対するバリデーションを指定していきます。 ほぼ説明する必要もないほどわかりやすいんじゃないでしょうか。 抜粋すると、email項目は入力されているかつ、アドレスの正規表現に沿っているか、 password項目は入力されているかつ、16文字以下であるか、を判断しています。 引っかかった箇所の引数に指定しているエラーメッセージが画面に表示されます。 こちらのメソッドはyupのGitHubリポジトリより確認できました。 useFormの引数をこのように指定し、3つの項目を受け取ります。 register: 入力された値をhooksに伝播させます。 handleSubmit: form内でsubmitが起きた際の処理を記述します。 errors: 当該のformでバリデーションエラーが起きた際のエラーメッセージ等を格納します そして子コンポーネントにregister関数の実行とerrorMessageを追加で渡します。 筆者はFormコンポーネントを切り出している影響でregisterを公式サイトのようには書けないのでこの形で子に渡しました。 register('★★')の★★部分はname属性になります。ここは頭の方で定義したSignupSchema のプロパティ名と一致させなければいけない点に注意です。 最後にこのコードの完成動画を載せておきます。 https://twitter.com/shin_k_2281/status/1380922999818252288 さいごに フォーム系ライブラリを扱うのはreact-hook-formが初めてでしたが 非常に強力だったので今後も使用していこうと思います。 恐らく今回の使用例ではバージョンアップされたreact-hook-formの力を 最大限発揮できていないと思うので、継続的に調べつつやっていきたいと思います。 最後まで見ていただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[備忘録]APIのレスポンスなどの非同期処理が早過ぎる場合でも、ローディングを1秒間は表示する方法

APIレスポンスが早すぎてローディングが一瞬しか出ない問題 APIのレスポンスに限らず、Promiseの処理が一瞬で終わってしまい、ローディングがほとんど表示されないため、正常に処理が終了したのかわかりにくい場合があると思う。 その際に、いい感じに書く方法をメモっておく。 ReactNativeのRefreshControlの例 重要なのは、const promise = refreshFunc()とsetTimeoutの部分。 ここで、refreshFunc()は非同期処理である。 非同期処理のPromiseオブジェクトを変数に入れておくことで、非同期処理の実行は先に行うが、 どんなに早く非同期処理が終了しても、setTimeoutの時間が経過した後でしか結果をチェックしないようにする。 export const ScrollViewWithRefresh = ({refreshFunc, children, ...props}) => { const [isRefreshing, setIsRefreshing] = useState(false) return ( <ScrollView refreshControl={ <RefreshControl refreshing={isRefreshing} colors={[primary]} tintColor={primary} onRefresh={async () => { setIsRefreshing(true) const promise = refreshFunc() setTimeout(() => { promise.then(() => setIsRefreshing(false)) }, 1000) // 最短でも1秒間はローディングを表示する }} /> } {...props} > {children} </ScrollView> ) } ググる力がなさすぎて、いい方法が見つからなかったので、どうにか自分で書いてみた。 もっといい方法があれば教えて下さい。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

非相対パスでimportした

何をしたのか これを参考に非相対パス(~/*)の形でimportをできるように設定した。 モジュール解決とは何か importが何を参照するのかを解決するために、コンパイラを利用するプロセス 相対importと非相対import 相対importは / や ./ や ../ などで始まるやつ import {} from "../foo" import {} from "./bar" 非相対importは基本的にこれ以外のやつ import React from "react" import Sute from "hage" めっちゃざっくり非相対importはどのように解決されるか 大きく2つの方法がある。それはNodeとクラシックである。デフォルトはNode。 tsconfigの"module"や"moduleResolution"で設定可能。 非相対importの場合は基本的にまずそのディレクトリの node_modules を探してその中にあるパッケージを探す。 そして見つからなかったら、ディレクトリチェーン上(現在のディレクトリの1つ上、そのまた上...)から node_modules を見つけようとする。 TypeScriptだとどうなるか TSも基本的には同じ。ただ、TS特有のファイルがあったりするので若干違う。省略。 そして、設定によっても若干変わるところがある。 baseUrl tsconfigで設定できる。 baseUrlはコンパイラにモジュールの入手先を教える。 tsでの非相対importはbaseUrlからの相対的であると仮定する。 相対importに対しての影響はない。 paths tsconfigで設定できる。 パスをマッピングしてくれる。 相対importだと深くなってしまうところを、非相対importでbaseUrlから相対的な設定をすることができる。 tsconfig.json { "baseUrl": "./src" "paths": { "~/*": ["*"] } } import モジュール from "~/src直下のディレクトリやファイル/..." 参照
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む