- 投稿日:2021-10-25T20:45:10+09:00
【React×Firebase】QiitaAPIv2を使ってシンプルなアプリを作りました
Reactの勉強がてらQiitaAPIv2を使ってアプリを作ったので、 過程でハマったポイントや初心者の方が戸惑いそうだと感じた所を学習記録として残すことにしました。 ReactやFirebaseをこれから使ってみよう・勉強中という方向けのレベル感です。 ソースコード デプロイ先(ゲストログインで全機能利用出来るようにしています) 記事の前提 筆者はWeb制作の経験が少しあるくらいで、Web開発の勉強中の初学者です。 今回のアプリは機能実装に重きを置いているのでデザイン面はかなり適当です。(ご容赦ください) この記事で書くこと 作ったアプリの紹介 以下に記述する技術に関してハマったポイントの解説 この記事で書かないこと アプリの詳細な制作過程 各技術の使用方法・詳細な解説 パッケージのバージョン等、環境によって変わる話は極力無い様にしています。 使用技術(ざっくり) 言語:HTML、CSS/Sass、JavaScript/TypeScript FW:React バックエンド:firebase(Hosting/Authentication/Firestore) 環境構築 今回はwebpackやbabel周りの勉強のためにcreate-react-appを使わずに環境構築しました。 日本語の解説記事も豊富なので大体は調べながらなんとかなると思います。 ハマったポイント webpack5になってwebpack.config.jsの記述方法がかなり変わっている様で、結構ハマりました。 英語記事を含め設定方法を詳しく解説している記事はv4のモノが割と多く、 v5で記述方法の変更があったことを知らなかったのが原因です。 公式Docのこの辺やこの辺をガッツリ読めば大体解決しました。(英語頑張って読みましょう) React Reactは公式のチュートリアルがめちゃくちゃ丁寧なので、 チュートリアルをやって公式Docを通読すれば致命的にはハマるところは無さそうです。 しいて挙げるとすると、stateが更新されるタイミングとかが勘違いしそうなくらいです。 その辺は触ってみていくつか簡単なアプリを作ってみるとすぐ分かると思います。 ちなみに今回はHooksを使用し、Reduxは採用していません。 TypeScript 今回初めて触りました、基本的にはJSに型宣言がくっついただけと考えて良いと思います。 特別ハマったポイントはなかったですが、JS以外のプログラミング言語を触ったことがない方はそもそも型宣言がどういったものなのかを理解する所から始めたほうが良さそうです。 こちらの記事がTypeScriptの型について詳しく解説されているので参考になります。 文法や機能については公式HandBookやDeep Diveを読みましょう。 どちらも日本語翻訳されたものがあります。 HandBook日本語翻訳 Deep Dive日本語翻訳 QiitaAPIv2 公式ドキュメントを読めば問題なく使えると思います。 今回使用した機能は記事の一覧取得のみでしたので、特にハマるポイントはありませんでした。 「ハマった!」と思ったら、そもそもAPIリクエスト周りの記述や、Reactのレンダリングのタイミング等を勘違いしている可能性も考えてみましょう。 Sass 冒頭でも述べた通り、今回デザイン的な部分はあまりこだわっていません。(そもそもデザインスキル無いです…) 設計はBEMをベースに「コンポーネント名」=「Block名」で固定し、各コンポーネントのスタイルを1ファイルで管理するようにしています。 イケてるUIにしたい場合はやはりMaterialUI等のライブラリを使用していくのが効率良さそうに感じました。 Firebase firebaseもv8とv9でかなり違いがあるので注意が必要です。公式ドキュメントをしっかり読みましょう。 日本語で詳しく解説している記事はv8の記述が多いですが、v9でもv8の記述方法が使えたりするので上手く書き換えたりして対応するとよいかと思います。 ハマったポイント react-firebase-hooksが動かない。 react-firebase-hooksというfirebaseの操作をhooksで実現できるパッケージですが、これがなぜか動かずハマりました。 ひたすら調べた結果Stack Over Flowでこんな投稿を見つけました。 どうやらreact-firebase-hooksはv9に対応していないようです。 この記事で回答されているようにv8の記述形式に書き換えれば問題なく動きました。 テストについて jest + react-testing-libraryでテストを書きました。 機能や書き方についてはドキュメントを読めばOKです。 Jest testing-library 難しかったポイント 何をテストすれば良いか分からない 今回テストを書くのが初めてだったため、「何をテストするか」「どこまでテストするか」が全然わかりませんでした。 Reactのテストについて色々調べて自分なりに考えた結果以下のようになりました。 意図したとおりに描画されているか クリックイベントなどのユーザーからのアクションに対して意図したとおりに画面が変化するか 詳細な実装についてはテストしない 正直未だにどうするのが良いかあまり分かっていない気がします。 私と同じように初めて書く方は、とにかく調べて様々な意見を見て自分で考えるのが今後のためにも良さそうです。 testing-libraryやEnzymeって何?? Reactアプリのテストについて調べるとほとんどの場合jestに関する記事がヒットすると思います。 しかし調べていくとどうやらreact-testing-libraryやEnzymeというものも使用している様で混乱しました。 どちらが良いとかはさておき、こちらの記事で違いが分かりやすく解説されていましたので参考になりました。 今回はreact-testing-libraryを使用しました。使い方についてはこちらの記事が丁寧で参考になりました。 まとめ 今回私にとっても初めて触る技術が多かったのですが、どの技術についても公式ドキュメントをしっかり読めばほとんどの事は解決しました。 また、当然ですが英語の方が圧倒的に情報量が多いので英語の記事やドキュメントを抵抗なく読めればテンポよく学習できると思います。 逆に英語記事が読めないとかなり大変だと思いますので、頑張って英語の勉強をしましょう! 今後やりたいこと(おまけ) 今回は学習しながらの制作でデザイン面についてはかなり妥協したので、MaterialUI等を導入していい感じのUIを作れるようにしたい。 実装の仕方自体もっと良い方法があるだろうと感じている部分が多々あるので、プログラミングスキルを上げて見直していきたい。 最低限の機能しかないので、よりユニークな機能を考えて実装していきたい。
- 投稿日:2021-10-25T18:46:21+09:00
TypescriptでReactの環境構築
TypescriptでReactの環境構築 TypescriptでReactの環境構築をまとめたいと思います。 プロジェクトの作成 create-react-appコマンドを使って、プロジェクトを作成します。 TypeScriptを使用するのでオプションで--template typescriptを加えます。 react-typescript-app というプロジェクトを作成しました。 $ npx create-react-app react-typescript-app --template typescript $ cd react-typescript-app $ yarn start 無事動作することが確認できました! TypeScriptの環境構築 TypeScriptの設定などは下記を見て設定します。
- 投稿日:2021-10-25T18:39:30+09:00
【Mac】ホットリロードできない時の対処法
環境 ・macOS Big Sur バーション11.5.2 ・シェル zsh ・Ruby 3.0.2 ・rails 6.1.4 ・My SQL 8.0.23 ・Docker 20.10.8 ・react 17.0.2 ・typescript 4.4.4 希望 Docker+rails+react+typescriptで環境構築し、ホットリロードまでしようとしていました. したこと ①webpacker.ymlのwatch_options:の下に aggregateTimeout: 300 poll: 500 を追加しました。 こんな感じです。 webpacker.yml watch_options: ignored: '**/node_modules/**' aggregateTimeout: 300 poll: 500 これでできるはずだができず、、、 次に ②.envファイルを作り .env CHOKIDAR_USEPOLLING=true を追記。 これも複数の記事を参考にし実行しましたがホットリロード(リロードなしで即テキストエディタの内容が反映される)はできず。 よってリロード画面を開発者モードで見ることに。 そこでこんなエラーを発見。 エラー Access to XMLHttpRequest at 'http://localhost:3035/sockjs-node/info?t=1635142686037' from origin 'http://0.0.0.0:3000' has been blocked by CORS policy: The request client is not a secure context and the resource is in more-private address space local. 調べるとCORS policyというものでURLがブロックされているとのこと。 解決策 chrome://flags/#block-insecure-private-network-requests 以上のサイトにアクセスし、 Block insecure private network requests をDisabledにする。 ↓ 無事ホットリロードできました!めでたしめでたし。 参考にしたサイト Docker + Rails6 + React + TypeScript の環境構築
- 投稿日:2021-10-25T17:00:23+09:00
Xampp環境で開発したReact x Laravel x mysql のアプリケーションをherokuにデプロイする。
はじめに 開発したアプリケーションをherokuにデプロイしようと思ったのですが、使用した技術的にもあまり適当な情報がなかったので備忘録としてまとめるついでに誰かの役にたてばなと思います。環境がLinuxですのでMacの方は適宜読み替えてください。様々なサイトを参考にさせてもらっており、参考文献として最後に載せています。 環境 Ubuntu20.04LTS React Laravel(php) mysql xampp 前提 linuxのコマンドが触れる gitを扱える デプロイの概念がなんとなく分かる laravelアプリケーションフォルダ直下をgitで管理している herokuの登録とClearDBの設定 こちら[1]から登録します。2段階認証みたいなやつはなんか飛ばしちゃいましたが、やっといた方が良い気がする。ログインまで行けたらCreate new appを選択してアプリケーションの作成をします。名前を入力して、リージョンはアメリカとヨーロッパしかないのでアメリカを選択。Overviewを押した後にConfigure Add-onsを押すとAdd-onsと書いてある検索欄が出てくるのでそこにClearDBと入力して出てきたものをクリック。更にIgnite-freeをクリックすると赤い警告のようなものが出てくるので、そこにある下の方のリンクをクリックします。 ここで一旦ここまでの流れを解説します。herokuではデフォルトのDBはpostgreSQLなのでアドオン(拡張機能)を用いてmySQLを使用出来るようにする必要があります。そのアドオン名がClearDBです。その後利用プランを無料のものに選択ししましたが、このアドオンの利用には条件があります。それがクレジットカードの登録です。赤い警告ではクレジットカード登録しろといっているのでリンクをクリックして登録しようとしているという流れです。 リンクをクリックするとクレジットカードの番号、名前、住所などの入力画面が出てくるのですべて英語で入力してください。こちらのサイト[2]で日本語住所を変換すれば簡単に英語になるようですが、順番とか間違えない人だったらそのまま英語で入力しても大丈夫です。完了したら先程の画面に戻ってClearDBの下にあるSubmit Order Formを押したら完了です。 環境変数の設定 Settingsを押した後、Reveal Config Varsを押すと環境変数の追加画面が出てきます。左側のKEYの部分に環境変数名を、 VALUEの部分にその値を入力すれば環境変数を設定することが出来ます。環境変数CLEARDB_DATABASE_URLのURLの部分に色々な情報が含まれているので、それを環境変数として抜き出して使いやすくします。形式は下の形です mysql://ユーザー名:パスワード@ホスト名/データベース名?reconnect=true そして以下のような対応で環境変数を作成します。 KEY:VALUE DB_CONNECTION:mysql DB_HOST:ホスト名 DB_PORT:3306 DB_DATABASE:データベース名 DB_USERNAME:ユーザー名 DB_PASSWORD:パスワード 次にアプリケーション用の環境変数APP_KEYとAPP_URLの設定を行います。laravelの環境で以下のコマンドを実行します。 php artisan key:generate --show このコマンド後に表示されたkeyがAPP_KEYになります。base64の部分も含めます。ちなみにbase64はデータの表現方法の一つで、暗号ではないのでこのkeyは盗まれないようにしましょう。ただこのコマンドはキーを生成してそれをコンソール上に表示しているようですが、そもそもlaravelの設定時にAPP_KEYを.envファイルへ書き込んだ気がします(事実書いてあった)。こっちでも良さそうな感じあるんですが、一応参考サイト通りに生成してそっちを書いておきました。 APP_URLは以下の形式です https://Herokuのアプリケーション名.herokuapp.com この2つの環境変数を追加しましょう。メール機能などがあればもっと環境変数が必要になりますが、僕のアプリケーションにはないので省略します。 ビルドやマイグレーション時の設定 先程のページの下にあるAdd Buildpackボタンを押した後、ポップアップが出てくるのでnodejsを選択した後にSave Changesを選択。もう一度phpで同じことをします。ここらへんはよくわかってないですが、ビルドに要るんだろうなあという感じです。 次にローカルのpackage.jsonの編集を行います。下記のスクリプトをpackage.jsonに追加します。スクリプトは既に存在するはずなので追記するだけでいいです。 "scripts": { "heroku-postbuild": "npm run prod" }, 次にアプリケーションを動かすためのコマンドを記述するファイルであるProcfileを作成していきます。laravelアプリフォルダ直下にProfileというファイル(拡張子なし)を作成して以下の内容を記述します。 Procfile web: vendor/bin/heroku-php-apache2 public/ とりあえずこうすればいいんでしょうが、よく分からなかったので調べてみたところProcfileの書き方は process type : command という感じらしいので、今回はwebという process typeでなんかコマンド実行してるっぽいですね。xamppもapacheで構成されてますし、なんかそこらへんのコマンドなんでしょう。もやもやしますが、深堀りしすぎると性格上脱線しやすいのでここら辺にしておきます。 次にvarchar型の文字数の制限をします。laravel,heroku,デプロイで検索すると複数ヒットしますが、どうやらvarchar型の文字数制限を191にしないとマイグレーション時にエラーが出てしまうようです。このエラー対策としてapp\Providers\AppServiceProvider.phpを下記のように書き換えましょう。 use Illuminate\Support\Facades\Schema;//追加 public function boot() { Schema::defaultStringLength(191);//追加 } Heroku CLIでの操作 Heroku CLIのインストールの前に少しだけ解説をしておくと、CLIはCommand Line Interfaceの略で、全部文字でやろうね的なやつです。これと対比されるのがGUIで、視覚的でこちらの方が一般的だと思います。とりあえずここで理解しておいてほしいのはHeroku CLIはherokuを文字(コマンド)で操作するためのものだということです。要するにターミナルでheroku操作したいからHeroku CLIインストールしようという流れです。 こちらのページ[3]から自分が使ってるOSのコマンドをコピペしてインストールしてください。僕はUbuntuなので下記コマンドになります。 sudo snap install --classic heroku ダウンロードが終わったらログインをします heroku login ここで参考サイトとは異なりメッセージが表示される コマンド 'heroku' は '/snap/bin/heroku' で利用できます '/snap/bin'がPATH環境変数に含まれていないためコマンドを特定できませんでした。 heroku: コマンドが見つかりません どうやらパスが通ってないようなので.bashrcにパスを追加して更新。 export PATH="/snap/bin:$PATH" macだとzshになってたりすると思いますが、適宜置き換えてください。 もう一度ログインするとコマンド自体は成功 › Warning: Our terms of service have changed: https://dashboard.heroku.com/terms-of-service heroku: Press any key to open up the browser to login なんか警告出てるので見てみると、利用規約の変更があったっぽい。読んだが特に問題なさそうだったのでスルーしてもう一度実行 › Warning: heroku update available from 7.59.0 to 7.59.1. heroku: Press any key to open up the browser to login or q to exit: 今度はアップデート出来るよと出てるので調べてみると、単純にアップデートすればいいらしい。 heroku update › Warning: update with: snap refresh heroku heroku: Updating CLI... not updatable としたらまたエラー。調べてみると似たような内容の記事が出てきたのでコマンドを実行 sudo snap refresh All snaps up to date. うまくいったかと思いもう一度ログインを試みるも、さっきと同じエラーが出た。もう一度検索してみると違うコマンドが載っていたのでそちらを実行 source <(curl -sL https://cdn.learnenough.com/heroku_install) curlで取ってきて更新してるっぽい。これで警告はでなくなった。警告放置しておくと後々面倒になることが多いので良かった。最終的には一番下のコマンドを実行すれば良かったっぽい。 警告もなくなったので続きにもどる。最初のコマンドの入力したあとEnterキーなどを押すとサイトが開いた。自分の場合はメールアドレスやパスワードなどは入力しなくてもログインボタンを押すだけでログインできた。恐らくクッキーで保存してるからだと思うので、してない人は入力すればいけるはず。ログインボタンを押すとコンソールの方にログインしたと出力が出来たので、とりあえずCLIでログインすることには成功。 デプロイとマイグレーション herokuではデプロイする場合、herokuリモートリポジトリのmaterブランチにローカルリポジトリのmasterブランチからプッシュすることでデプロイすることが出来ます。一応masterブランチ以外からでもデプロイ出来るようですが、基本的にはデプロイ前にはマージしてmasterブランチを更新しておいた方が良さそうです。 設定も色々変えているので、masterへのマージとプッシュしてあることを確認したら、下のコマンドを実行します。 heroku git:remote -a herokuのアプリケーション名 set git remote heroku to https://git.heroku.com/herokuのアプリケーション名.git git push heroku master これで一応アプリケーションのデプロイは完了です。最後のコマンドの後に出てくるURLをクリックすることでも、自分のアプリケーションのサイトに飛べますし、下記コマンドでも開けます(herokuにアプリケーションが複数ある場合は違うコマンドかも) heroku open しかしアプリケーションをwebで開いてみると、真っ白になっている。デベロッパーツールのコンソールを見てみると下記のエラーが Mixed Content: The page at 'https://frote.herokuapp.com/' was loaded over HTTPS, but requested an insecure script 'http://frote.herokuapp.com/js/App.js'. This request has been blocked; the content must be served over HTTPS. どうやらHttp接続をしているようだが、どこかよく分からない。言われているのがビルド後のReactファイルなのでコードの中身ではなさそうと判断し、色々と情報を調べているとこのサイト[6]の一番最後に同様のエラーに対する対処が書いてあった。この手のサイトあまり信用していないのですが、あまりに情報がないのでとりあえず実行。下記のような環境変数を追加 Key : Value ASSET_URL : https://herokuのアプリケーション名.herokuapp.com これを追加したところ表示された。正直なんでかよく分かっていないので調べたが全然情報が出てこない。恐らくhttps通信にすることが出来たのだろうが、そもそも何が原因だったのか。とりあえず動いたので一旦このままにしておくが、後々原因を究明したほうが良さそう。何か分かる人いたら教えてください。 とりあえず表示までは出来たのでDBを使用するためにマイグレーションしていく。下記コマンドでマイグレーション heroku run php artisan migrate コマンド入力後にDo you really wish to run this command? (yes/no)と聞かれるのでyesと入力。しかしエラーが出て、試行錯誤しているうちに既にテーブルが作成されているためマイグレーションがうまく行かなくなってしまった(エラーを保存し忘れていました。すみません)。コマンドを見る感じheroku runのあとにartisanコマンドが出来そうなので、下記コマンドでデータベースの再構築を試みる。 heroku run php artisan migrate:refresh しかし、同様にテーブルが存在するので無理だとエラーが出る。色々と調べてみるとこちら[7]でrefreshでなくfleshならうまくいったと書いてあったので実行してみる。 heroku run php artisan migrate:fresh このコマンド後にマイグレーションに成功。2つのコマンドの違いを調べたところrefleshはロールバックしてから実行するのに対し、freshは削除してから実行しているよう[8]。何回も実行していたのでロールバックしても同じような状態だったのかもしれない。この後に動作確認をしたが、プログラム通りではない動作は存在しなかった(本番環境になったことで見えてきた修正点はあったが)。とりあえずデプロイ自体は完成したよう。 その他調整 アプリケーション名の変更 アプリケーション名を開発環境だと適当にしていたので、本番環境で名前を変えたくなった。環境変数に以下を追加で解決。 key:value APP_NAME : アプリケーション名 herokuをスリープしないようにする herokuは無料枠だと30分でスリープしてしまう。その対策法として一定時間ごとにcurlコマンドでアクセスするというのがあったのでやってみた。 アドオンを追加 heroku addons:add scheduler:standard Herokuのブラウザアプリにアクセスし、shedulerをクリック。Add Jobをクリックし、Run Commandの部分に下記コマンドを入力 curl herokuアプリケーションのURL で完了。(デフォルトで10分ごとに実行するようになっているはず) デプロイ後のアプリケーションの感想 遅い!herokuは遅いから辞めておけという記事を見て、まあ言うほどでもないやろと思っていたが結構遅い。おかげでローディング時のUIの大切さの理解や遅すぎて動作がはっきり見えるためデバッグしやすいなどの利点もあったが、これは不便さを感じるレベルで無理ですね。まあ無料でこんな簡単に利用してる側が文句言うのはおかしな話なので感謝しなければならないのですが。リージョンに東京がないためアメリカにしているのが結構効いてそうですね。やはりちゃんとしたサービスを展開したいならAWSなどの方が良さそう(やったことないのでどうなのか分かりませんが)。とはいえ完全無料でここまで出来るので、勉強がてらデプロイの理解などをするにはちょうど良さそう。 一応デプロイしたアプリケーションtoDoリストアプリケーションFroteはこちら[9]になります。 おわりに 超簡単という感じでは無かったですが、ちゃんと調べればデプロイは手軽に出来ると思います。ただ自分の真剣に作ったアプリケーションをherokuにデプロイしようとはちょっと思えませんでしたね。アプリケーションの修正とCI/CDだけしたらAWSに移行しようと思います。 参考文献 [1]:heroku [2]:君に届け [3]:The Heroku CLI [4]:Herokuコマンド アップデート [5]:Heroku コマンド を最新バージョンにアップデートする方法 [6]:HerokuにReactjsアセットを使用してLaravel8Webサイトをデプロイする [7]:マイグレーション実行によってHeroku上に作成されたテーブルを消して、再度マイグレーションする方法 [8]:(Laravel) migrationやり直しコマンドあれこれ [9]:Frote [10]:Laravelをherokuにデプロイする方法・手順の解説(MySQL使用)〜コマンドを使わず、Herokuのサイト上で設定するよ!〜
- 投稿日:2021-10-25T15:58:39+09:00
React + Material-UI の Styles で メディアクエリ
はじめに いちいちコンポーネントで書く必要性は見当たらないが、一応可能 配列+バッククォートとか余計な書き方しなくてもいけるということに公開してすぐ気づいた人、、 ただ、& みたいな書き方はしなくても良い コード import { Grid, List, ListItem } from "@material-ui/core"; import { makeStyles } from "@mui/styles"; const useStyles = makeStyles(() => ({ root: { // Root共通 display: "flex", height: "100vh", // 各メディアクエリ設定 "@media (min-width:1280px)": { "& .main > sub": { width: "20%", }, }, "@media (min-width:800px) and (max-width:1279px)": { "& .main > sub": { width: "30%", }, }, "@media (max-width:799px)": { "& .main > sub": { width: "40%", }, }, }, })); function App() { // classオブジェクト生成 const classes = useStyles(); return ( <Grid className={classes.root}> <List className={["main"]}> <ListItem className={["sub"]}>hoge1</ListItem> <ListItem className={["sub"]}>hoge2</ListItem> <ListItem className={["sub"]}>hoge3</ListItem> <ListItem className={["sub"]}>hoge4</ListItem> <ListItem className={["sub"]}>hoge5</ListItem> </List> </Grid> ); } export default App; 終わりに "& .main > sub" については "& .main > li" とかにして、subのclass名を取った方がスマートではあるが、わかりやすさ優先
- 投稿日:2021-10-25T14:14:51+09:00
Reactでお絵かき掲示板(の表示部分)を作る話
この文章は、決意表明をすることにより、自分を追い詰めるものである。あと、zennに書いたもののコピーである。 実は昔こんなの書いてるんですよ。 なぜReactにしたのか Vue VS React じつはどっちでやるか決めるのにかなりの時間がかかりました。VueもさわったしReactもさわったし、なんとなくそれなりに触りだけ弄ったりしました。結果、Reactで行こうと決めました。以下理由。 Vueは世界上に2と3の記事が混在していて結構煩雑 あっこれ2の記事だ、ってなったりする。だいぶ書き方違うのでなんで動かないのか調べたら文法だったり、Amazonで本買ったらVue2の本だったり(悔しい) Vueはすぐそれっぽいものができる故に、逆に理解しにくい とりあえず形ができるので構造を深く考えるまでに至らない。Reactは公式も言ってる通り、見た目難しい。見た目難しいということは、調べればわかるよ、と公式が言っているということである。 Reactのほうが食えそう 知り合いにVueで食ってる人は見たことないけどReactで食ってる人は何人かいたため。 ビジョン 現状の俺作お絵かき掲示板 ROIS をベースにしようと思うので、こうなる。 レンタルサーバーに設置できるようにしたいので、通信まわりはPHP なかなかサーバーサイドでnode.jsが走ってるレンタルサーバーはなかったのだ。「ロリポップが対応してるよ!」って記事が出てきてビビったことがあるけどよく読んだらマネージドクラウドでした。 データベースはSQLite これも手軽にレンタルサーバーに設置するため。テキストのログより検索性が高いし、ローカルのXAMPPで動いてるのをそのままFTPでアップロードしてオッケーな軽い感じなのが良い。SQLiteの情報を調べているとPHPのバージョンが4時代のものだったりするのが非常に難点。いまPHPはバージョン8、なんと倍ですよ! Reactを使うのはスレッド表示や検索表示のみ 「仮想DOMが他のDOMと排他的」って感じだから。要するにお絵描きのhtml5+javascriptに干渉しないようにするため。絵がバグる原因がReactとかイヤでしょ。 サーバーのPHPはjsonを返す 妥当。 Reactでやる意味あるの? うん、たぶん実装めんどくさいしPHPで直接書くほうが現段階では色んな意味で速い。プロジェクトの規模としてもそんなにメリットはないと思うけど、俺の学習でやるわけなので、生暖かく見守りながらアドバイスください。
- 投稿日:2021-10-25T13:05:57+09:00
【React】値を操作するコントローラーパッケージ(LEVA・dat.GUI)
概要 ReactでWebアプリケーションを作成していると、値を操作するコントローラーが欲しくなるときがあります。 ちょっと動作を確認したいときに、LEVA・dat.GUIを使用すると簡単にコントローラーが実装できます。 追記(2021.10.28) LEVAに関して、もう少し調査をして扱えるインターフェイスがdat.GUIと遜色ないとわかったので、改めてまとめました。 どちらがいいのか インターフェイスの種類は、dat.GUIの方が充実してる印象です。 LEVAは、インターフェイスの種類では劣りますが、使い方がdat.GUIより簡単です。 使いたいインターフェイスの種類によって、パッケージを使い分けするのがいいと思います。 LEVA ドキュメント インストール npm i leva インターフェースの種類 ※ インターフェイスの名前は自由に付けられます。 コード import { rgba } from 'csx'; import { useControls } from 'leva'; import React, { useEffect, useRef, VFC } from 'react'; import { css } from '@emotion/css'; export const LevaController: VFC = () => { const leftContainerRef = useRef<HTMLDivElement>(null) const rightContainerRef = useRef<HTMLDivElement>(null) const props = useControls({ LeftColor: '#C23841', RightColor: { r: 61, g: 199, b: 190, a: 0.5 }, Text: 'hogehoge', Toggle: true, Number: { value: 4, min: 0, max: 10, step: 1 }, Interval: { min: 0, max: 10, value: [4, 5] }, Position: { x: 0, y: 0 }, BoxSize: { value: [10, 20], joystick: false } }) const colorToString = (color: { r: number; g: number; b: number; a: number }) => { return rgba(color.r, color.g, color.b, color.a).toString() } useEffect(() => { leftContainerRef.current!.style.backgroundColor = props.LeftColor rightContainerRef.current!.style.backgroundColor = colorToString(props.RightColor) }, [props]) return ( <div className={styles.container}> <div ref={leftContainerRef} className={styles.leftContainer}></div> <div ref={rightContainerRef} className={styles.rightContainer}></div> </div> ) } const styles = { container: css` position: relative; width: 100%; height: 100%; display: flex; `, leftContainer: css` position: relative; width: 50%; height: 100%; `, rightContainer: css` position: relative; width: 50%; height: 100%; ` } useControlsを呼びだすことによって、自動的にコントローラーが表示されるようになります。 Colorのインターフェイスで透過を扱いたい場合は、rgbaで初期値を指定します。 RightColor: { r: 61, g: 199, b: 190, a: 0.5 } コントローラー上ではHexColorとして表示されますが、受け取れる値(props.RightColor)は、rgbaのオブジェクトとなります。 外部からコントローラーの値を変更したい場合 dat.GUI ドキュメント インストール npm i dat.gui npm i -D @types/dat.gui インターフェースの種類 ※ インターフェイスの名前は自由に付けられます。 コード dat.GUIはReact用のパッケージではないので、すこしだけ実装量が増えます。 コードが長くなるのでカスタムフックにしています。 import React, { useEffect, useRef, VFC } from 'react'; import { css } from '@emotion/css'; import { useDatGuiController } from './useDatGuiController'; export const DatGuiController: VFC = () => { // コントローラーの作成 const { values } = useDatGuiController() const containerRef = useRef<HTMLDivElement>(null) useEffect(() => { containerRef.current!.style.backgroundColor = values.colorHex }, [values.colorHex]) return ( <div ref={containerRef} className={styles.container}> <div className={ styles.text }>{`group x:${values.group.x}, y:${values.group.y}, z:${values.group.z}`}</div> </div> ) } const styles = { container: css` position: relative; width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; `, text: css` color: #fff; font-size: 5rem; ` } useDatGuiController.tsx import * as dat from 'dat.gui'; import { useEffect, useState } from 'react'; const props = { colorHex: '#1e1e1e', colorRGBA: [0, 128, 255, 0.3], text: 'hoge', number: 5, check: true, select: 'foo', group: { x: 0, y: 0, z: 0 } } export const useDatGuiController = () => { const [values, setValues] = useState(props) useEffect(() => { const gui = new dat.GUI() // object key name = prop name gui.addColor(props, 'colorHex').onChange(e => setValues(prev => { return { ...prev, colorHex: e } }) ) gui.addColor(props, 'colorRGBA').onChange(e => setValues(prev => { return { ...prev, colorRGBA: e } }) ) gui.add(props, 'text').onChange(e => setValues(prev => { return { ...prev, text: e } }) ) gui.add(props, 'number', 0, 10, 0.1).onChange(e => setValues(prev => { return { ...prev, number: e } }) ) gui.add(props, 'check').onChange(e => setValues(prev => { return { ...prev, check: e } }) ) gui.add(props, 'select', ['foo', 'bar', 'hoge']).onChange(e => setValues(prev => { return { ...prev, select: e } }) ) gui.add( { button: () => console.log('clicked!') }, 'button' ) // グループ化 const group = gui.addFolder('group') group.add(props.group, 'x', -100, 100, 1).onChange(e => setValues(prev => { return { ...prev, group: { ...prev.group, x: e } } }) ) group.add(props.group, 'y', -100, 100, 1).onChange(e => setValues(prev => { return { ...prev, group: { ...prev.group, y: e } } }) ) group.add(props.group, 'z', -100, 100, 1).onChange(e => setValues(prev => { return { ...prev, group: { ...prev.group, z: e } } }) ) return () => { group.destroy() gui.destroy() } }, []) return { values } } new dat.GUI()をすることで、自動的にコントローラーが表示されるようになります。 インスタンスを作成して、それぞれインターフェイスを追加していきます。 const gui = new dat.GUI() // object key name = prop name gui.addColor(props, 'colorHex').onChange(e => setValues(prev => { return { ...prev, colorHex: e } }) ) 色を扱う場合はaddColor、グループ化する場合はaddFolder、その他はadd関数を使用します。 第1引数にはObject(props)をとります。初期値になっています。第2引数には対応するkey名を指定します。 Colorインターフェイスでは、透過も扱えますが、コントローラー上で値を変更することができませんでした。
- 投稿日:2021-10-25T11:13:32+09:00
Reactのcreate-react-appの後npm startでエラー
結構しょーもないことに時間食ってしまったので・・・ 全く仕組みを理解していない自分が悪く、調べ方も悪かったので起動するまでの段で時間を食ってしまってテンションが下がったので恥を書き込んでおく反省投稿です・・・。 Windows上でReactの勉強をしようとNode.jsの最新版をインストールし、本を見ながら、 npx create-react-app my-app とかした後、 npm start したらエラーが出て起動できなかったと。 エラーはこんなのです。 :\Users\user\reacttest\my-app>npm start > my-app@0.1.0 start c:\Users\user\reacttest\my-app > react-scripts start There might be a problem with the project dependency tree. It is likely not a bug in Create React App, but something you need to fix locally. The react-scripts package provided by Create React App requires a dependency: "webpack": "4.44.2" Don't try to install it manually: your package manager does it automatically. However, a different version of webpack was detected higher up in the tree: c:\Users\user\node_modules\webpack (version: 4.8.1) Manually installing incompatible versions is known to cause hard-to-debug issues. If you would prefer to ignore this check, add SKIP_PREFLIGHT_CHECK=true to an .env file in your project. That will permanently disable this message but you might encounter other issues. To fix the dependency tree, try following the steps below in the exact order: 1. Delete package-lock.json (not package.json!) and/or yarn.lock in your project folder. 2. Delete node_modules in your project folder. 3. Remove "webpack" from dependencies and/or devDependencies in the package.json file in your project folder. 4. Run npm install or yarn, depending on the package manager you use. In most cases, this should be enough to fix the problem. If this has not helped, there are a few other things you can try: 5. If you used npm, install yarn (http://yarnpkg.com/) and repeat the above steps with it instead. This may help because npm has known issues with package hoisting which may get resolved in future versions. 6. Check if c:\Users\user\node_modules\webpack is outside your project directory. For example, you might have accidentally installed something in your home folder. 7. Try running npm ls webpack in your project folder. This will tell you which other package (apart from the expected react-scripts) installed webpack. If nothing else helps, add SKIP_PREFLIGHT_CHECK=true to an .env file in your project. That would permanently disable this preflight check in case you want to proceed anyway. P.S. We know this message is long but please read the steps above :-) We hope you find them helpful! npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! my-app@0.1.0 start: `react-scripts start` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the my-app@0.1.0 start script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm ERR! A complete log of this run can be found in: npm ERR! C:\Users\user\AppData\Roaming\npm-cache\_logs\2021-10-25T00_51_59_790Z-debug.log c:\Users\user\reacttest\my-app> 解決前にやったこと ログを見ながらその通りにやった(つもりだったが間違っている) ・pacage-lock.jsonの削除 ・node_modulesフォルダーの削除 ・package.jsonからwebpackを削除、、、しようと思ったけど記述がなかったので放置 ・npm install で、だめで、stack overflowとか調べにいくと同じような感じのことが色々書かれてやってみるものの駄目。 結局・・・ ホームディレクトリ(C:\Users\user)以下にnode_modulesフォルダーが残っていて、ここに大量に古いモジュールがインストールされていた。 ログを見たらきちんとC:\Users\user\node_modulesと書いてある・・・。 それを勝手にcreate-react-appしたフォルダーと勘違いしてそちらを操作していたorz 解決 cd C:\Users\user rd /S /Q node_modules の後、再度 cd C:\Users\user\reacttest\my-app npm start 無事起動しました・・・。なんかこれで2時間くらいハマってました・・・orz やっと一歩目が踏み出せたので引き続きやっていきます・・・。
- 投稿日:2021-10-25T10:32:51+09:00
Reactを再考する#2
まえがき 第一回に続いてReactを掘り下げてみようシリーズです。 第二回は、useContextです 前提 React 16.8~ Reactの基本的な説明はしません TypeScriptで書いてます なぜuseContext? 前回のuseStateと比較してみます。 useState ステートをルートコンポーネントで一元管理するのは限界がある propsバケツリレーがとにかく面倒 useContext propsを経由せずにコンポーネント間でステートを伝達できる 工夫次第でコンテキストを気にせずデータを共有できる とりあえず使ってみる ./src/index.tsx import React, { createContext } from "react"; import { render } from "react-dom"; import App from "./App"; import colors from "./color-data.json" interface Color { title: string color: string } export const ColorContext = createContext([] as Color[]) render( <ColorContext.Provider value={colors}> <App /> </ColorContext.Provider>, document.getElementById("root") ); ./src/App.tsx import React, { useContext } from "react"; import { ColorContext } from "./index" export default function App() { const colors = useContext(ColorContext) return ( <ul> {colors.map((color, i) => ( <li key={i}>{color.title}</li> ))} </ul> ); } propsを経由せずにcolors変数を子コンポーネントに渡せてますね。 ネストが深くても、そのコンポーネントからuseContextを呼び出せばいいのでpropsバケツリレーからも開放されます。 Store的に使う 個人的にお気に入りの手法ですw ReduxにStoreという概念がありますが、こっちの方が全然ラクだと思います。 ./src/ColorProvider.tsx import React, { createContext, useContext, useState } from "react"; import colorData from "./color-data.json"; interface IColor { title: string; color: string; } interface ColorContextType { colors: IColor[]; addColor: (title: string, color: string) => void; } const ColorContext = createContext({} as ColorContextType); /** ポイント①: コンテキストをカスタムフックとしてエクスポートする */ export const useColors = () => useContext(ColorContext); const ColorProvider: React.FC = ({ children }) => { const [colors, setColors] = useState(colorData); /** ポイント②: ステートの用途を限定して公開する */ const addColor = (title: string, color: string) => { setColors([ ...colors, { title, color } ]); }; return ( <ColorContext.Provider value={{ colors, addColor }}> {children} </ColorContext.Provider> ); }; export default ColorProvider; ./src/index.tsx import React from "react"; import { render } from "react-dom"; import ColorPrivider from "./ColorPrivider"; import App from "./App"; /** Appコンポーネント配下全てでコンテキストが利用できる */ render( <ColorPrivider> <App /> </ColorPrivider>, document.getElementById("root") ); ./src/App.tsx import React, { useContext } from "react"; import { useColors } from "./ColorProvider" import FormWithCustomHook from "./FormWithCustomHook" export default function App() { /** コンテキストからデータを取得 */ const { colors } = useColors() return ( <> <FormWithCustomHook /> <ul> {colors.map((color, i) => ( <li key={i}>{color.title}</li> ))} </ul> </> ); } ./src/FormWithCustomHook.tsx(前回の例を流用) import React from "react"; import { useInput } from "./hooks"; import { useColors } from "./ColorPrivider"; export default function FormWithCustomHook() { const [titleProps, resetTitle] = useInput(""); const [colorProps, resetColor] = useInput("#000000"); /** コンテキストからデータを取得 */ const { addColor } = useColors(); const submit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); addColor(titleProps.value, colorProps.value); resetTitle(); resetColor(); }; return ( <form onSubmit={submit}> <input {...titleProps} type="text" placeholder="color title..." required /> <input {...colorProps} type="color" required /> <button>ADD</button> </form> ); } ポイント①: コンテキストをカスタムフックとしてエクスポートする コード量的には少し減るかなぐらいですが、コンテキストをカスタムフックで隠蔽できることがデカイです。 useStateなどのフックと同じ感覚で使用でき、コンテキスト云々を意識する必要がなくなります。 通常 import { useContext } from "react"; import { ColorContext } from "./index" export default function App() { const colors = useContext(ColorContext) // ... } カスタムフック import { useColors } from "./ColorPrivider"; export default function App() { const { colors } = useColors() // ... } ポイント②: ステートの用途を限定して公開する これは賛否ありそうですが、ある程度の規模以上のAppであれば考える必要があるかなと。。 setColorsはステートcolorsを更新するための関数なので、公開するとどんな操作も可能になります。 こちらが意図しない操作で、思わぬバグを引き起こす可能性があります。 /** 特定の文字列から始まる色を除外する */ const unintentionalAction() => { setColors(colors.filter(color => !color.color.startsWith("#ff"))); } コンテキストは後に出てくるuseReducerとの組み合わせで複雑なステート管理を実現できたりするので、 かなり重要な機能の一つかなと思います。。 次回 コンポーネントを描画->ステートを更新->コンポーネントを再描画->... というサイクルはもうなんとかなりそうですね。。 じゃあコンポーネントが描画された後に何かしたいときはどうすればいいのか。。 (APIを叩くとか、DOMを書き換えるとか、、) というわけで次回は、、 useEffectとuseLayoutEffect useMemoとuseCallback です。なんか盛り沢山ですが色違いみたいなもんです。。 参考文献 Reactハンズオンラーニング 第2版 ――Webアプリケーション開発のベストプラクティス
- 投稿日:2021-10-25T09:23:59+09:00
【React】ドラッグ&ドロップで要素を伸び縮みするUIを作ってみる【TypeScript】
テーマ とあるReactアプリでドラッグ&ドロップで要素を伸び縮みさせるUIを作って欲しいという依頼があった これだけの要件であればreact-dndを使うと冗長そうなので、今回は使わずに自前で実装してみた デモ 黒い部分をドラッグ&ドロップすると縦に伸び縮みします See the Pen React Drag&Drop Resize by mikan3rd (@mikan3rd) on CodePen. 解説 draggable属性 要素がドラッグ可能であることを示すためにdraggable={true}が必要です https://developer.mozilla.org/ja/docs/Web/HTML/Global_attributes/draggable onDrag / onTouchMove ドラッグ中に発生し続けるイベントです onDragイベントはスマートフォン/タブレット端末には対応していないため、代わりにonTouchMoveイベントで代用しています handleChangeHeightでやっていること React.useRef()を使い、targetRefに伸び縮みさせる要素を渡すようにしています https://ja.reactjs.org/docs/hooks-reference.html#useref ドラッグ中の位置(引数のclientY) と現在の要素の底の位置(getBoundingClientRect()で取得したbottom)の差分を計算し、現在の要素の高さと足し算することで次の要素の高さを算出します -React.useState()のstateでheightを更新すると再レンダリングが大量に発生して遅いため、targetRef.current.style.heightで直接高さを変更して伸び縮みさせるようにしています onDragStart / dataTransfer.setDragImage() デフォルトだとドラッグ時にドラック中の要素がカーソルに合わせて薄く表示されるようになっています これはonDragStart時にdataTransfer.setDragImage()で変更することができます https://developer.mozilla.org/ja/docs/Web/API/DataTransfer/setDragImage 非表示にしたい場合、dataTransfer.setDragImage()の引数にnullなどを指定することができないので、透明な画像を用意して指定しています https://stackoverflow.com/questions/7680285/how-do-you-turn-off-setdragimage 以上! 自分でUIを作ると色々勉強になりますね、皆さんもぜひ試してみてください! ちなみにReact.useRef()は再レンダリング対象にさせたくない変数としても使えるので覚えておくと便利
- 投稿日:2021-10-25T08:59:44+09:00
create-react-app で "Error: EPERM: operation not permitted, uv_cwd" とエラーが起きる原因と対処法
概要 書類フォルダ内に npx create-react-app XXX でフォルダを作成したところ、以下のエラーが起きた Error: EPERM: operation not permitted, uv_cwd 原因 原因はいくつかあるが、その中の一つである ターミナルから書類フォルダへのアクセス許可されていなかった という原因に当てはまった 解決 下記の設定画面に移動 環境設定 > セキュリティとプライバシ > ファイルとフォルダ ターミナルの"書類フォルダ"のアクセス許可にチェックする 無事、create-react-appを行うことができた