20210429のReactに関する記事は14件です。

React x Tailwind をサポートするライブラリ

tailwind はデザインの柔軟性が高くて使ってみたいと思うが、自分で一から UI コンポーネントを作る必要があるので実践的ではない? 以下のライブラリを使うといくらか助けになりそう。 Kimia-UI https://github.com/enochndika/kimia-UI tailwind の utility class でスタイリングされたコンポーネントを提供。 React Spectrum Libraries https://react-spectrum.adobe.com/index.html UIコンポーネントに求められるロジックをカスタムフックで提供。 基本 Kimia-UI のコンポーネントのスタイリングをカスタマイズしつつ使えば良さそう。 足りなかったら、React Spectrum Libraries を使いながら UI コンポーネントを作っていくのが良さそう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

続Laravel5.5でもVue.jsっぽくReact.js x TypeScriptを部分的に導入する

概要 Laravel5.5でもVue.jsっぽくReact.jsを部分的に導入するの続きで、TypeScriptを導入します。 TypeScript導入 $ npm install -D typescript @types/node @types/react @types/react-dom tsconfig.json作成 $ ./node_modules/.bin/tsc --init tsconfig.json { "compilerOptions": { "target": "es5", "module": "commonjs", "noImplicitAny": false, "jsx": "react", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "exclude": [ "vendor" ] } Laravel-Mixアップグレード webpackが4系じゃないとコンパイルがうまくいかないらしい package.json . . . "laravel-mix": "^5.0", . . . $ npm i ts-loaderインストール $ npm i ts-loader@^8 -D webpack.mix.js編集してtsを使用 webpack.mix.js mix.ts('resources/assets/js/app.tsx', 'public/js'); resources/assets/js/app.tsx作成 resources/assets/js/app.tsx import * as React from 'react'; import { render } from 'react-dom'; type Props = { title: string | null } const Example: React.FC<Props> = ({ title }) => ( <div className="container"> <div className="row"> <div className="col-md-8 col-md-offset-2"> <div className="panel panel-default"> <div className="panel-heading">Example Component</div> <div className="panel-body"> {title} </div> </div> </div> </div> </div> ); const Element = document.getElementById('example'); if (Element) { const title = Element.getAttribute('title'); render(<Example title={title} />, document.getElementById('example')); } resources/views/welcome.blade.phpでprops設定 welcome.blade.php . . . <body> <div class="flex-center position-ref full-height"> <div id="example" title="ts-react"></div> </div> <script src="{{ asset('js/app.js')}}"></script> </body> コンパイルと画面表示 $ npm run dev $ php artisan serve 参考 Laravel bladeからReactコンポーネント(Material UI)にバリデーションエラーメッセージを渡してみる [Laravel × React] Laravel×React(Typescript)の環境構築
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[簡単]モダン構成なSPAで作るチュートリアル①(環境構築編)

SPAで作るタスク管理アプリのチュートリアル 勉強会に参加したりするとReact.jsとかVue.jsやってみたくて勉強はしてるけど、LaravelとかRailsと、どうつなぎ込んでいいか分からんって人が多いみたいやな。 転職用のポートフォリオでもSPA化すると評価は上がるらしいな。知らんけど。 独立した技術として本とかUdemyで触ったりしてるけど、SPA化するような本とか記事は少ないみたいやから困ってる人もいるみたいやね。 かく言う私も初めてSPAのプロジェクトに参加したときは、なんのこっちゃ分かれへんかったけど、自分でタスク管理アプリ作ってJSONで繋ぎこむ部分が理解出来たら、あとはフロントとバックエンドは別世界で考えられるからちょっとワカル状態なんやな。 予定では ①環境構築(Docker/Laravel/React.js/Material-UI) ②React側でルーティング設定 ③Reactで一覧テーブル作成 ④seederで作ったDBのデータをReactに渡して一覧に表示 ⑤新規登録機能 ⑥編集・削除機能 みたいな感じで進めていくつもりや Laravelに関してはJSON返すだけやから、普段Railsの人がこのチュートリアルやってみたら、 Railsに置き換えることは簡単やと思うわ。 React.jsとVue.jsの違いは語れるほど理解できてないから何も言わんとくわな。 下のGIFみたいな感じで作っていくで。 転職用のポートフォリオ考えてる人とかはこのチュートリアルやってから、 自分のアプリ作っていったらええんちゃうかな。 前提 Gitをインストール https://git-scm.com Mac GitHub SSH接続設定をしておく Dockerを使えるようにインストール https://www.docker.com/products/docker-desktop [注意]npmやyarnのインストールで下記のエラーが出たらコンテナ内のリソース配分の問題かもしれんから、 preferencesからCPUとかメモリの割当を増やしてみてほしい 各種バージョンについて バージョンはこんな感じ - php    v8.0.3 - Laravel  v8.40.0 - MySQL   v8.0 - npm    v6.14.4 - Node.js  v14.2.0 - React.js   v17.0.2 - Material-UI  v4.11 記事書いた時点ではそれなりにモダンな選定やと思われるな Laravelの環境構築 環境構築はこちらの記事を使わせてもらうな。 【忙しい人向け】カップ麺より早く作るDockerでLaravel開発環境構築 (@ucan-lab さん) エイリアスを設定してくれていたりで、かなり使いやすいし、 爆速で構築できる且つ環境一致させるのに最適なので、ぜひ使ってみるとええで。 今回の記事はSPAでバックエンドとフロントエンドでデータのやり取りするところをメインにしたいから、サクッと環境作れるのはマジ神やな。 また@ucan-labさんが解説も詳細に書いてくれているので、dockerに強くなりたい人とかチュートリアル中にわからんコマンドとかあったら読んでみるとええでと思うで。(https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4) 文中に登場するmakeコマンドはエイリアスやからMakefile見てもらえば実行内容は分かるようにしてくれてはるわ。 まぁ、dockerは別にええわって人はなれてる環境で構築してくれても大丈夫やわ。 その場合makeコマンドのところはMakefile見て良しなに置き換えてくれな。 手順 $ git clone https://github.com/ucan-lab/docker-laravel.git $ cd docker-laravel # バージョンを合わせる場合は下記コマンドの前に*バージョン対応を参照 $ make create-project ローカルで既にPortが使われてたりするとエラーが出るからそのへんはええ感じにかぶらんように変えてくれておけやで *バージョン対応 Makefileのまま環境構築すると最新版のLaravelとなるため、バージョンを合わせたい場合はgit cloneしたディレクトリ直下のMakefileで下記変更を行ってからmake create-projectを行う #Makefile 6行目をバージョン指定に変える - docker-compose exec app composer create-project --prefer-dist laravel/laravel . # こちらは削除 + docker-compose exec app composer create-project --prefer-dist "laravel/laravel=8.4" . #こちらに変える Laravelの初期画面が表示されたらOKな ホンマにカップ麺食う暇もなかったな LGTM100個ぐらい押したいところやわ。 DBクライアントを利用する場合 コマンドラインで進められる方はスルーしてもOKな MySQLが8.0のためTablePlus等を利用する(SeaquelProは8.0非対応のはず) 手順通り行っった場合は以下が接続情報となるわ Host: 127.0.0.1 Password:secret Port: 3306 User: phper Database: laravel_local レコードはまだ存在しないもののuserテーブルとか出来てるの確認できたらOKやね ここまでできたら変更差分を分かりやすくするためにブランチを切ってコミットしておくとええな $ git checkout -b taskApp $ git add . $ git commit -m "TaskAppInit" Reactの環境構築 UIのパッケージをインストールし、Reactの導入 npm関連で手順通りやってThis is probably not a problem with npm. There is likely additional logging output above.が出る場合はdockerのリソースの問題かもしれないから書いておくな # npmを使えるようにしておく $ make npm # UIパッケージ導入 $ docker-compose exec app composer require laravel/ui # React.js導入 $ docker-compose exec app php artisan ui react --auth # 下記の様の出たらOK React scaffolding installed successfully. Please run "npm install && npm run dev" to compile your fresh scaffolding. Authentication scaffolding generated successfully. npmでビルド $ make npm-install $ make npm-dev コマンドにより生成された不要なファイルを削除しておく resources/viewsの下はシングルページとするためapp.blade.phpのみとする 下記4項目を削除する /auth以下 layouts - (app.blade.phpをviews直下に移動させlayoutsディレクトリ自体を削除) home.blade.php welcome.blade.php # 削除後にresources/viewsディレクトリ下は下記のようになっていればOK resources ┗ views ┗ app.blade.php (layouts下に出来ていたものをviews下に移動する) views/app.blade.phpを修正 既存のコードに対してid="app"の中身を空っぽにするで # views/app.blade.php <!doctype html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- CSRF Token --> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Laravel') }}</title> <!-- Scripts --> <script src="{{ asset('js/app.js') }}" defer></script> <!-- Fonts --> <link rel="dns-prefetch" href="//fonts.gstatic.com"> <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet"> <!-- Styles --> <link href="{{ asset('css/app.css') }}" rel="stylesheet"> </head> <body> <div id="app"> # id="app"の中身を空する変更 </div> </body> </html> resources/js/components/Example.jsを修正 id="app"としたため、getElementByIdを下記のように変更 example→app Example.js // 20行目以下を修正 export default Example; if (document.getElementById('app')) { ReactDOM.render(<Example />, document.getElementById('app')); } routes/web.phpを修正 シングルページアプリケーションなのでどんなURLだったとしても/views/app.blade.phpを表示するように設定します。 web.php // 既存コードの16行目以下を削除 // Route::get('/', function () { // return view('welcome'); // }); // Auth::routes(); // Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home'); // 追記 Route::get('{any}', function () { return view('app'); })->where('any','.*'); Reactを読み込む準備できたのでビルドする $ make npm-dev 画面をリロードしてExample Componentという文字列が表示されたらReactの導入はOK これは何が表示されているかというとresources/js/components/Example.jsの中身が表示されている laravel側のid="app"にExampleコンポーネントの中身を表示しているんやな 試しに下記変更を加えてみるな Example.jp className="card-headerの文字列 Example Component → React導入できたわな?? Example.js import React from 'react'; import ReactDOM from 'react-dom'; function Example() { return ( <div className="container"> <div className="row justify-content-center"> <div className="col-md-8"> <div className="card"> <div className="card-header">React導入できたわな??</div> <div className="card-body">I'm an example component!</div> </div> </div> </div> </div> ); } export default Example; if (document.getElementById('app')) { ReactDOM.render(<Example />, document.getElementById('app')); } ビルド(make yarn-dev)すると下記のように表示されるはずやな Reactで修正したのに「あれ、、変わらん」みたいなことあるけど、だいたいビルド忘れてるか、キャッシュリロードできてないかどっちかのはずやわ。 ここまで来たらコミットしとこうな $ git add . $ git commit -m "React導入" Material-UIを導入 日本語の公式ドキュメントがある - MATERIAL-UI そもそも何やねんって話なんやけどGoogleが提唱してるマテリアルデザインを実現するためのUIフレームワークで、 手っ取り早く見た目がええ感じになるから使っていくな。 https://qiita.com/nogson/items/804dd3a879f482fb7018 ドキュメント通りに進めればOKやから下記のコマンドで進めていくで # コンテナ内でインストール docker-compose exec web npm install @material-ui/core ボタンをインポートしExample.jsの中で表示してみる Example.js import React from 'react'; import ReactDOM from 'react-dom'; import { Button } from '@material-ui/core'; //Buttonをインポート function Example() { return ( <div className="container"> <div className="row justify-content-center"> <div className="col-md-8"> <div className="card"> <div className="card-header">React導入できたわな</div> <div className="card-body">Im an example component!</div> //ボタンを追記 <Button color="primary" variant="contained">Hello World</Button> </div> </div> </div> </div> ); } export default Example; if (document.getElementById('app')) { ReactDOM.render(<Example />, document.getElementById('app')); } リロードしてHELLO WORLDボタンが表示されたらMaterial-UIの導入はOK ドキュメントがしっかりしているので参考にしながら弄ってみるといいかもしれないな 追加したボタンを以下のように書き換えてみる Expamle.js <Button color="secondaly" variant="contained">ワイがMaterial-UIのボタンやな??</Button> 基本的にインポートしたものはタグ的な感じで使えて colerとかで簡単に見た目変えたりできるねん。 便利やな。 マウスオーバーすると色が変わるようになってたり 色々タグやテンプレートがあったりするからイジってみるとええちゃうかな。 コミットしておくな git add . git commit -m "Material-UIの導入" 今日はここまでやわ。 次回はReactでルーティングの設定 react-router-domというライブラリ使って書きみたいなページ遷移する部分を実装していくで ほな、LGTMよろしゅーやで。 ソースはGitHubに乗せておくから分からんときはコミット履歴見たら解決できるかもな。 https://github.com/morry48/LaravelReactTaskApp
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

�Reactで�ポートフォリオサイトを作成

はじめに アプリを作成する上での今回のテーマは次の通りです。 1. 何かしらのデザインツールを使用してデザインを作成する。 2. Atomic Designの概念を理解し、コンポーネント指向でアプリを作成する。 これらを意識してデザイン作成を行う事で、Atomic Designとコンポーネントに関しての理解を深める事や、将来的にデザイナーさんやその他の方達とスムーズにコミュニケーションを取る為の最低限のデザインに関する知識を身につける事が今回のメインのテーマでした。 デザイン 今回はデザインを作成するにあたってFigmaを使用しました。苦労した点としては、デザインツール自体の利用が初めてだったので使い方を覚えるところから始めました。基本的に使いながら覚える事は大事ですが、どこにどんな機能があるかや最低限の良く利用する便利な機能などのインプット学習は、ある程度必要だと思いました。 特に良く使用する機能として『グループ化』、『コンポーネント機能』があります。 ここで大事になってくるのがAtomic Designに基づいたデザインでの構築です。 Figmaを使ってデザインを作成しているうちに、Atoms, Molecules, Organisms, Components, Templatesといった概念が身についていきました。 超初心者がAtomic Designをより理解する為のアドバイス こちらがFigmaで実際に作成したアプリの一部です。 コンポーネント化した部分はヘッダーとプロジェクトのアイテム部分です。 本来であればコンタクトフォームの送信ボタンなども使い回せるようにしたかったのですが、このアプリでは他に使い道もないのでやめておきました。 アプリの作成 今回はReactとTypeScriptを使ってアプリを作成しました。 以下、使用した主な技術とバージョンです。 "@material-ui/core": "^4.11.3", "@reduxjs/toolkit": "^1.5.1", "node-sass": "4.14.1", "react": "^17.0.2", "react-hook-form": "^7.1.1", "react-scripts": "4.0.3", "typescript": "~4.1.5" style styleは基本的に全てmodule.scssで構成しました。 ただ、コンポーネント化したものをスタイルを変えて使いたい時はstyled-componentsを使う方が便利なのかなとも思いました。特にMaterial-UIを使用していると相性も良さそうなのでこの辺は次回への課題とします。 データの取得 お問い合わせフォームには、ReactHookFormを使用してデータを取得できるようにしました。 理由は楽チンだからです。 Amplifyを使ったデプロイ まだ中身はスカスカアプリですが、まずは経験という意味も込めてとりあえず強引にデプロイまで持っていく事にしてみました。 AWSにはとてもたくさんの機能があるため、初心者には知らない言葉や機能が多すぎてとてもハードルが大きく感じます。ですが、Amplifyを使ったデプロイをする事だけであれば、ゴールがとても明確かつすぐに達成可能なので挫折しないで済みます。 設定の段階でAmplifyとgithubを連携してあるので、デプロイした後でも開発環境でちょっとした変更を加えた場合は、メインのブランチにマージすれば自動で反映される仕組みになっているのでちょっとした感動すら覚えました。 つい数日前までAWSで何ができるのかさえ全く分からなかった自分が、ちょびっとだけAWSを使ってできる機能を知る事ができたのでチャレンジしてみて良かったです。 参考ページ Amplify CLI をインストールしてユーザーを設定する手順 AmplifyでReactアプリのデプロイ、CI/CDをする公式チュートリアルをやってみた まとめ デザインを仕上げるだけで二週間以上かかってしまい、なかなか進まずに苦しい時期がありました。 しかし、実際にコードを書き始める前にこのプロセスを踏んだ事で、目に見える部分だけでなく全体像を把握する事ができました。さらに、丁寧に設計ができたおかげでコードを書き始めてからはとても早かったので、非常に重要な工程だという事が良く分かり勉強になりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【勉強】Next.jsで簡単な掲示板作成

はじめに 私はモダンな技術の実務経験が無いため、Javascriptを勉強しています。 この記事では以前、Next.jsを勉強した際に作成した簡易掲示板について記載しました。 なぜ掲示板か 教材に掲示板を選んだ理由は、初めての個人開発ということもあり、 CURD全ての機能が備わっている物が良かったからです。 個人的に勉強はしたいが何を題材にすればよいかわからない方におすすめです。 なぜNext.js? 数あるJavascriptのフレームワークの内、Next.jsを選んだ理由についてです。 どうしてもNode.jsを使いたかったので、それと相性がいいこととが第一条件で、 一番の決め手は知人に進められたからです。 個人開発なので使う技術は普段触れない物で良いと思うのですが、 今思えば無知な状態からNext.jsではなくReactを一通り学んだ後にNext.jsに触ればよかったです……。 実際に作った画面 機能 投稿表示 新規投稿 投稿削除 環境 名前 役割 scss デザイン全般 Next.js(React.js) フロントエンド Node.js サーバーサイド MySQL データベース GitHub https://github.com/okioka/NextBBS ※ローカルですが実際にPCで動かすこともできます。 感想 大変だったこと コンポーネント指向を理解することが難しかったです。 JavaServletでしかWebの開発をしたことがなかったことから、 今まではHTML、CSS、JavaScriptで分けて考えることが自分の中の常識としてありましたが、 コンポーネント指向では一つにして考えるので慣れるまで時間がかかりました。 モチベーションを保つ事が大変でした。 今回は小規模で終わりが見えていたのでモチベーションの維持ができましたが、 個人開発で大規模な開発をする時に、まずはどうやってモチベーションを 維持するか考えないといけないです。 やはり自分が好きなもの、本当に欲しいと思っている物を作ることが モチベーション保つ秘訣だと思いました。 学びがあったこと 今自分が触れている技術と比べて新しいので資料が少なかったです。 公式ドキュメントなどを見て調べる習慣がつきました。 調べてもわからないことは聞くべきだと思います。 仕事ではわからないことを聞くことは当たり前ですが、 個人的な勉強で聞くことをしていませんでした。 わからないことは知り合いに聞いたり技術書を読むとで解決することができました。 自分の考えだけで解決しようとするのではなく、 自分では思いつかないような考え方に日頃から触れるようにしましょう。 おわりに 最後まで読んでいただきありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsセットアップ / TypeScript化 / Material UI導入 / i18n対応までの手順【前半】

Next.jsを使ったサイトのサンプルフォーマットとなるようなものを作りました. 作った画面 Marterial UIのサンプルページを少し加工して ヘッダ部の「こんにちは!こちらは日本語です。」 ボディ中央部の「サンプルプロジェクト」 以上2箇所の文言をi18n対応しています。 すぐ横にある「言語を変更」ボタンクリックで英語表記に変えることができます。   Githubリポジトリ 手順説明 Next.jsのセットアップ TypeScript対応 Material UIの実装 next-i18nextを使ったi18n(多言語化)対応【こちらは後半にて】 Next.jsのセットアップ 任意のディレクトリを作成 mkdir next-materialui-n18n-sample cd next-materialui-n18n-sample npm処理化でpackage.jsonを作る npm init -y Wrote to /Users/***/package.json: { "name": "next-materialui-n18n-sample", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "MIT" } Next.jsのインストール npm install --save next react react-dom package.jsonのscriptsコマンド修正 { "scripts": { "dev": "next", "build": "next build", "start": "next start" }, } ページの作成 mkdir pages vim pages/index.js index.js export default () => <div> Welcome to next.js! </div> 起動確認 npm run dev localhost:3000にアクセス TypeScript化 参考URL   必要パッケージのインストール npm install -D typescript @types/react @types/react-dom @types/node プロジェクトルートにtsconfig.jsonの作成 touch tsconfig.json tsconfig.jsonの作成 { "compilerOptions": { "sourceMap": true, "noImplicitAny": true, "module": "esnext", "target": "es6", "jsx": "preserve", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "resolveJsonModule": true, "isolatedModules": true, "moduleResolution": "node" }, "include": ["src/**/*", "pages/index.tsx"], "exclude": ["node_modules"] } index.jsを.tsxファイルに変換 mv pages/index.js pages/index.tsx とりあえずこれで実行は可能 npm run dev localhost:3000にアクセス Material UIの導入 Material UIとは? Googleのマテリアルデザインをベースに開発されたUIコンポーネントのライブラリ ReactやNext.jsにはかっこいいUI部品などはついておらず、自分でCSSを書かないかぎりは真っ白なシンプルなHTMLになってしまうので、こうしたコンポーネントライブラリを入れておけば開発が楽になる。 参考URL   パッケージのインストール npm install --save @material-ui/core @material-ui/icons srcフォルダの作成、theme.tsxファイルを作成 mkdir src touch src/theme.tsx theme.tsx import { createMuiTheme } from '@material-ui/core/styles'; import red from '@material-ui/core/colors/red'; // Create a theme instance. const theme = createMuiTheme({ palette: { primary: { main: '#556cd6', }, secondary: { main: '#19857b', }, error: { main: red.A400, }, background: { default: '#fff', }, }, }); export default theme; pages/_app.tsxの作成 _app.tsx import React from 'react'; import Head from 'next/head'; import { AppProps } from 'next/app'; import { ThemeProvider } from '@material-ui/core/styles'; import CssBaseline from '@material-ui/core/CssBaseline'; import theme from '../src/theme'; export default function MyApp(props: AppProps) { const { Component, pageProps } = props; React.useEffect(() => { // Remove the server-side injected CSS. const jssStyles = document.querySelector('#jss-server-side'); if (jssStyles) { jssStyles.parentElement!.removeChild(jssStyles); } }, []); return ( <React.Fragment> <Head> <title>My page</title> <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" /> </Head> <ThemeProvider theme={theme}> {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} <CssBaseline /> <Component {...pageProps} /> </ThemeProvider> </React.Fragment> ); } pages/_document.tsxの作成 _document.tsx import React from 'react'; import Document, { Html, Head, Main, NextScript } from 'next/document'; import { ServerStyleSheets } from '@material-ui/core/styles'; import theme from '../src/theme'; export default class MyDocument extends Document { render() { return ( <Html lang="en"> <Head> {/* PWA primary color */} <meta name="theme-color" content={theme.palette.primary.main} /> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" /> </Head> <body> <Main /> <NextScript /> </body> </Html> ); } } // `getInitialProps` belongs to `_document` (instead of `_app`), // it's compatible with server-side generation (SSG). MyDocument.getInitialProps = async (ctx) => { // Resolution order // // On the server: // 1. app.getInitialProps // 2. page.getInitialProps // 3. document.getInitialProps // 4. app.render // 5. page.render // 6. document.render // // On the server with error: // 1. document.getInitialProps // 2. app.render // 3. page.render // 4. document.render // // On the client // 1. app.getInitialProps // 2. page.getInitialProps // 3. app.render // 4. page.render // Render app and page and get the context of the page with collected side effects. const sheets = new ServerStyleSheets(); const originalRenderPage = ctx.renderPage; ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => (props) => sheets.collect(<App {...props} />), }); const initialProps = await Document.getInitialProps(ctx); return { ...initialProps, // Styles fragment is rendered after the app and page rendering finish. styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()], }; }; index.tsxファイルの編集 →ひとまず下記のサンプル通りに修正 index.tsx import { useState } from 'react'; import Head from 'next/head'; import Link from 'next/link'; import { useTheme, makeStyles, Theme } from "@material-ui/core/styles"; import { Toolbar, Typography, AppBar, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, } from "@material-ui/core"; const useStyle = makeStyles({ root: (props: Theme) => ({ paddingTop: props.spacing(10), paddingLeft: props.spacing(5), paddingRight: props.spacing(5), }) }); export default function Home() { const [ dialogOpen, setDialogOpen ] = useState(true); const classes = useStyle(useTheme()); return ( <div className={classes.root}> <Head> <title>My page title</title> <meta name="viewport" content="initial-scale=1.0, width=device-width" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" /> </Head> <Dialog open={dialogOpen} onClose={() => {setDialogOpen(false)}}> <DialogTitle>Dialog Sample</DialogTitle> <DialogContent> <DialogContentText> Easy to use Material UI Dialog. </DialogContentText> </DialogContent> <DialogActions> <Button color="primary" onClick={() => {setDialogOpen(false)}} >OK</Button> </DialogActions> </Dialog> <AppBar> <Toolbar> <Typography variant="h6" color="inherit"> TypeScript + Next.js + Material UI Sample </Typography> </Toolbar> </AppBar> <Typography variant="h1" gutterBottom={true}> Material-UI </Typography> <Typography variant="subtitle1" gutterBottom={true}> example project </Typography> <Typography gutterBottom={true}> <Link href="/about"> <a>Go to the about page</a> </Link> </Typography> <Button variant="contained" color="secondary" onClick={() => { setDialogOpen(true)}} >Shot Dialog</Button> <style jsx={true}>{` .root { text-align: center; } `}</style> </div> ); } ひとまずこれで起動できる npm run dev 参考リポジトリ i18n対応について こちらは長くなりそうなので…後半で書きたいと思います!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsセットアップ / TypeScript化 / Material UI導入 / i18n対応までの手順【前編】

Next.jsを使ったサイトのサンプルフォーマットとなるようなものを作りました. 作った画面 Marterial UIのサンプルページを少し加工して ヘッダ部の「こんにちは!こちらは日本語です。」 ボディ中央部の「サンプルプロジェクト」 以上2箇所の文言をi18n対応しています。 すぐ横にある「言語を変更」ボタンクリックで英語表記に変えることができます。   Githubリポジトリ 手順説明 Next.jsのセットアップ TypeScript対応 Material UIの実装 next-i18nextを使ったi18n(多言語化)対応【こちらは後半にて】 Next.jsのセットアップ 任意のディレクトリを作成 mkdir next-materialui-n18n-sample cd next-materialui-n18n-sample npm初期化でpackage.jsonを作る npm init -y Wrote to /Users/***/package.json: { "name": "next-materialui-n18n-sample", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "MIT" } Next.jsのインストール npm install --save next react react-dom package.jsonのscriptsコマンド修正 { "scripts": { "dev": "next", "build": "next build", "start": "next start" }, } ページの作成 mkdir pages vim pages/index.js index.js export default () => <div> Welcome to next.js! </div> 起動確認 npm run dev localhost:3000にアクセス TypeScript化 参考URL   必要パッケージのインストール npm install -D typescript @types/react @types/react-dom @types/node プロジェクトルートにtsconfig.jsonの作成 touch tsconfig.json tsconfig.jsonの作成 { "compilerOptions": { "sourceMap": true, "noImplicitAny": true, "module": "esnext", "target": "es6", "jsx": "preserve", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": false, "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, "resolveJsonModule": true, "isolatedModules": true, "moduleResolution": "node" }, "include": ["src/**/*", "pages/index.tsx"], "exclude": ["node_modules"] } index.jsを.tsxファイルに変換 mv pages/index.js pages/index.tsx とりあえずこれで実行は可能 npm run dev localhost:3000にアクセス Material UIの導入 Material UIとは? Googleのマテリアルデザインをベースに開発されたUIコンポーネントのライブラリ ReactやNext.jsにはかっこいいUI部品などはついておらず、自分でCSSを書かないかぎりは真っ白なシンプルなHTMLになってしまうので、こうしたコンポーネントライブラリを入れておけば開発が楽になる。 参考URL   パッケージのインストール npm install --save @material-ui/core @material-ui/icons srcフォルダの作成、theme.tsxファイルを作成 mkdir src touch src/theme.tsx theme.tsx import { createMuiTheme } from '@material-ui/core/styles'; import red from '@material-ui/core/colors/red'; // Create a theme instance. const theme = createMuiTheme({ palette: { primary: { main: '#556cd6', }, secondary: { main: '#19857b', }, error: { main: red.A400, }, background: { default: '#fff', }, }, }); export default theme; pages/_app.tsxの作成 _app.tsx import React from 'react'; import Head from 'next/head'; import { AppProps } from 'next/app'; import { ThemeProvider } from '@material-ui/core/styles'; import CssBaseline from '@material-ui/core/CssBaseline'; import theme from '../src/theme'; export default function MyApp(props: AppProps) { const { Component, pageProps } = props; React.useEffect(() => { // Remove the server-side injected CSS. const jssStyles = document.querySelector('#jss-server-side'); if (jssStyles) { jssStyles.parentElement!.removeChild(jssStyles); } }, []); return ( <React.Fragment> <Head> <title>My page</title> <meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" /> </Head> <ThemeProvider theme={theme}> {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */} <CssBaseline /> <Component {...pageProps} /> </ThemeProvider> </React.Fragment> ); } pages/_document.tsxの作成 _document.tsx import React from 'react'; import Document, { Html, Head, Main, NextScript } from 'next/document'; import { ServerStyleSheets } from '@material-ui/core/styles'; import theme from '../src/theme'; export default class MyDocument extends Document { render() { return ( <Html lang="en"> <Head> {/* PWA primary color */} <meta name="theme-color" content={theme.palette.primary.main} /> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" /> </Head> <body> <Main /> <NextScript /> </body> </Html> ); } } // `getInitialProps` belongs to `_document` (instead of `_app`), // it's compatible with server-side generation (SSG). MyDocument.getInitialProps = async (ctx) => { // Resolution order // // On the server: // 1. app.getInitialProps // 2. page.getInitialProps // 3. document.getInitialProps // 4. app.render // 5. page.render // 6. document.render // // On the server with error: // 1. document.getInitialProps // 2. app.render // 3. page.render // 4. document.render // // On the client // 1. app.getInitialProps // 2. page.getInitialProps // 3. app.render // 4. page.render // Render app and page and get the context of the page with collected side effects. const sheets = new ServerStyleSheets(); const originalRenderPage = ctx.renderPage; ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => (props) => sheets.collect(<App {...props} />), }); const initialProps = await Document.getInitialProps(ctx); return { ...initialProps, // Styles fragment is rendered after the app and page rendering finish. styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()], }; }; index.tsxファイルの編集 →ひとまず下記のサンプル通りに修正 index.tsx import { useState } from 'react'; import Head from 'next/head'; import Link from 'next/link'; import { useTheme, makeStyles, Theme } from "@material-ui/core/styles"; import { Toolbar, Typography, AppBar, Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, } from "@material-ui/core"; const useStyle = makeStyles({ root: (props: Theme) => ({ paddingTop: props.spacing(10), paddingLeft: props.spacing(5), paddingRight: props.spacing(5), }) }); export default function Home() { const [ dialogOpen, setDialogOpen ] = useState(true); const classes = useStyle(useTheme()); return ( <div className={classes.root}> <Head> <title>My page title</title> <meta name="viewport" content="initial-scale=1.0, width=device-width" /> <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" /> </Head> <Dialog open={dialogOpen} onClose={() => {setDialogOpen(false)}}> <DialogTitle>Dialog Sample</DialogTitle> <DialogContent> <DialogContentText> Easy to use Material UI Dialog. </DialogContentText> </DialogContent> <DialogActions> <Button color="primary" onClick={() => {setDialogOpen(false)}} >OK</Button> </DialogActions> </Dialog> <AppBar> <Toolbar> <Typography variant="h6" color="inherit"> TypeScript + Next.js + Material UI Sample </Typography> </Toolbar> </AppBar> <Typography variant="h1" gutterBottom={true}> Material-UI </Typography> <Typography variant="subtitle1" gutterBottom={true}> example project </Typography> <Typography gutterBottom={true}> <Link href="/about"> <a>Go to the about page</a> </Link> </Typography> <Button variant="contained" color="secondary" onClick={() => { setDialogOpen(true)}} >Shot Dialog</Button> <style jsx={true}>{` .root { text-align: center; } `}</style> </div> ); } ひとまずこれで起動できる npm run dev 参考リポジトリ i18n対応について こちらは長くなりそうなので…後編で書きたいと思います! 後編の記事はこちら
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React + Firebaseを用いたアプリ開発。

食事管理アプリ 1日の合計カロリーを計算できるアプリを作りました。 URL:https://react-firebase-7e85f.web.app/ 開発経緯 アプリのコンセプト 簡単に使用する事ができ、ダイエットのサポート。 最近のフィットネスブームでボディメイクに興味を持つ人が増えてきました。私もその1人です。運動を行い食生活を改善することによりMAX体重125kgから85kgまで体重を落とすことに成功しました。 体重を落とすために1番必要なのは食生活の改善です。1日にどのくらいのカロリーをとり、その内脂質、タンパク質、炭水化物の量はどのくらいか計算しなければいけません。多くの人がこの食生活の部分を適当に行ってしまい、挫折してしまいます。  そこで多くの人が面倒で避けてしまう食生活の改善を簡単に行えるサイトを作りたいと考え本アプリを作成いたしました。 アプリの概要 前回作成したFai Fai Dietの改良版として本アプリを作成いたしました。 改善点としては * 履歴を残せるようにする。 * 食材を登録できるようにする。 * ユーザー別に認証する。 以上、3点を本アプリでは改善いたしました。 使用イメージ 認証機能の追加 emailとpasswordでアカウントを作成したりログインしたりできる機能を実装いたしました。 食材の登録 自分の好きな食材の写真、栄養を保存できるようにしました。 食材の編集、削除 登録した食材の編集、削除も可能です。 カロリー計算、登録 食べた量に対して栄養を計算し直す事ができます。 総合摂取カロリーの計算 1日食べたものを登録することによってその日の総合カロリーを計算します。 履歴を残せる 1日の総合摂取カロリーを履歴として残せるようにしました。 機能一覧 ユーザー登録機能 ログイン機能 食材の栄養素の登録 食材の栄養素の編集 食材の栄養をの削除 摂取した食材の量に応じて栄養素の自動計算 合計カロリーの自動計算 履歴表示機能 使用言語 フロントエンド HTML/CSS/JavaScript/React/Redux データベース firebase その他ツール Visual Studio Code デプロイ環境 firebase 改善した点 ユーザー別に認証する 各ユーザーの情報が登録してあるコレクションのサブコレクションとしてそのユーザーが摂取した食材を保存するコレクション、そのユーザーの今までの履歴を保存するコレクションを登録することによってユーザーごとにデータを管理する事ができました。 食材を登録できるようにする ログインしたユーザーすべての人が共有できるようにしました。食材でだけではなく料理も登録できるようにしてあり自分が考えて作ったダイエット飯を共有できるようにするためです。この意図は、1人でやると日々食べるものが同じになってしまいます。そこで減量飯をシェアする事ができたら飽きる事なくダイエットを継続出来るのではないかと考えたからです。 履歴を残せるようにする 登録ボタンを押した時点での総合摂取カロリーと日付を保存し表示で記すようにしてあります。 苦労した点 データベースの削除 ドキュメントをまとめて削除するところがつまづきました。 最初はコレクションに直接deleteメソッドをしようしエラーが出てしまいました。 db.colection('').delete() ///エラー このエラーはbatchメソッドを使用し解消いたしました。 →こちらのQiitaの記事でアウトプットいたしました。 学んだこと firebase へのデータの追加、取得、削除 一連のメソッドを学べました。 JavaScriptの非同期処理の考え方を学べました。 この経験を生かし最終的にはWEBエンジニアとして実務を全うできることを目指します。 最後まで読んでいただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TS+Reactでcreate-react-appなしでHerokuにホストするまで

~注意~ 本記事はもともと2020年秋ごろにフロントエンドの勉強をしていた際に備忘録として執筆されたものであり、Webpackその他ツールの記述方法は当時の仕様に準じたものです。 特にwebpackに関しては、執筆時点から現在の間に4.x→5.xのメジャーアップデートがあったため、ここに書いていることをそのまま実行しても動作しない可能性があります。あらかじめご了承ください。 基礎戦略 修正ビルドのたびにキャッシュを拾わないよう、バンドルファイル名にコンテントハッシュを入れる そのためHtml Webpack Pluginを使い、動的にバンドルファイルを追跡するindex.htmlがbuildフォルダ内に生成されるようにする 開発環境の場合は、コンテントベースをbuildフォルダにしたWebpack Dev Serverを立ち上げる 本番環境の場合は、expressでサーバを立て、buildをパブリックフォルダとして指定し、res.send()でindex.htmlをブラウザへと送る。 という感じでやっていきましょう。 全体のツリー図 おおざっぱにこんな感じで行きましょう root/  ├ (将来buildフォルダができる)/  ├ src/Reactファイルの皆さん  ├ template.html  ├ server.js  ├ package.json  ├ webpack.common.js  ├ webpack.dev.js  └ webpack.prod.js ちなみに……。 クライアントフォルダ(バンドルファイルを出力するフォルダ)と サーバーフォルダ(server.jsを立ち上げてそれを送信するフォルダ)は 別にするのが良いという意見もあります。 しかし、その場合両方でpackage.jsonに諸々インストールしなければならず、 大変めんどいため、本稿では完全無視で行きます。 また、実際のツリーには当然node_modulesとか.gitignoreとかが含まれますが、 本稿では触れないために省略しております。 tsconfig.json TypeScriptをコンパイルする場合、tsconfig.jsonを作っておく必要があります。 内容はこんな感じです。 tsconfig.json { "compilerOptions": { //ソースマップは作っておく "sourceMap": true, // TSはECMAScript 5に変換 "target": "es5", // TSのモジュールはES Modulesとして出力 "module": "es2015", // JSXの書式を有効に設定 "jsx": "react", "moduleResolution": "node", "lib": [ "es2020", "dom" ], "allowSyntheticDefaultImports": true } } "allowSyntheticDefaultImports": trueを設定しないと import React from "react"のような文が書けなくなり、 import * as React from "react"と書かなければならなくなるので、要注意です。 Package.json 戦略実行のためのスクリプトはこんな感じになります。 package.json "scripts": { "start:dev": "webpack-dev-server --config webpack.dev.js --open", "build": "webpack --config webpack.prod.js", "start:prod": "node server.js" } Webpack Dev Serverを立てる時はwebpack.dev.js、 WebPackでビルドする時は、webpack.prod.jsの設定を参照するよう指定します。 本番環境のスタートはnodeでserver.jsを実行するだけです。 あとHerokuに上げるにはenginesの指定も必要になります。 今のうちにやっておきましょう。 package.json "engines": { "node": "13.x", "npm": "6.x" } webpack.common.js 本番用(webpack.dev.js)と開発用(webpack.prod.js)でファイルを分けますが、 重複部分はwebpack.common.jsに書き出して、両ファイルでmergeする形にします。 webpack.common.js const HtmlWebpackPlugin = require("html-webpack-plugin") const { CleanWebpackPlugin } = require("clean-webpack-plugin") module.exports = { entry: "./src/index.tsx", plugins: [ new CleanWebpackPlugin(), //無限増殖していくbundle.[contentHash].jsをお掃除 new HtmlWebpackPlugin({ template: "./template.html" })], //template.htmlを雛形に、buildフォルダ内にindex.htmlを生成 resolve: { extensions: [".ts", ".tsx", ".js", ".json"] } } この際、resolve内で拡張子ts、tsxを指定しないと TypeScriptがコンパイルされないので注意しておきましょう。 webpack.dev.js webpack.dev.js const path = require("path") const common = require("./webpack.common"); const { merge } = require("webpack-merge"); module.exports = merge(common, { mode: "development", output: { // 出力ファイルのディレクトリ名 path: path.resolve(__dirname, "build"), // 出力ファイル名 filename: "bundle.js" }, devServer: { contentBase: path.resolve(__dirname, "build"), port: 5000, historyApiFallback: true, }, module: { rules: [ { test: /\.html$/, // 拡張子 .html の場合 use: ["html-loader"] }, { // 拡張子 .ts もしくは .tsx の場合 test: /\.tsx?$/, // TypeScript をコンパイルする use: "ts-loader" }, { test: /\.(svg|png|jpg|svg)$/, use: { loader: "file-loader", options: { name: "[name].[ext]", outputPath: "imgs" } }, } ] }, }) ファイル別にローダーを実行しながら/build/bundle.jsにバンドルする感じです。 Webpack Dev Serverのコンテントベースも/buildファイルに指定しましょう。 あとはhistoryApiFallback: trueを忘れると、 開発環境でルーターが機能しないのがちょっとしたハマりどころです。 webpack.prod.js webpack.prod.js const path = require("path") const common = require("./webpack.common"); const { merge } = require("webpack-merge"); module.exports = merge(common, { mode: "production", output: { // 出力ファイルのディレクトリ名 path: path.resolve(__dirname, "build"), // 出力ファイル名 filename: "bundle.[contentHash].js" }, module: { rules: [ { test: /\.html$/, // 拡張子 .html の場合 use: ["html-loader"] }, { // 拡張子 .ts もしくは .tsx の場合 test: /\.tsx?$/, // TypeScript をコンパイルする use: "ts-loader" }, { test: /\.(svg|png|jpg|svg)$/, use: { loader: "file-loader", options: { name: "[name].[contentHash].[ext]", outputPath: "imgs" } }, } ] } }) /buildフォルダ内のhtmlファイルと画像ファイルにコンテントハッシュが付いた以外 基本webpack.dev.jsと変わらないので、難しい部分はありません。 むしろDev Serverの設定がないぶん楽ちんになっています。 server.js ビルドした後、buildフォルダ内のindex.htmlを 送信するためのサーバを立ち上げます。 server.js const express = require('express'); const app = express(); const path = require("path") app.use(express.static('build')); //ここでbuildフォルダをパブリックフォルダに指定する app.get('*', (req, res) => { //buildフォルダ内のindex.htmlをリクエストに対して返信 res.sendFile(path.join(__dirname, "build", "index.html")) }); const port = process.env.PORT || 5000 app.listen(port, () => { console.log(`Listening on port ${port}`); }); Herokuで実行するときはPORTは環境変数から拾ってくるので、 「環境変数があれば環境変数、なければ5000」という感じで設定しておくのが吉です。 まとめ Mini Css Extract Pluginとか使いたかったけど、 それはまた後々と言う感じで……。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel5.5でもVue.jsっぽくReact.jsを部分的に導入する

背景 タイトルまんま 導入 $ composer create-project --prefer-dist laravel/laravel react-laravel "5.5" $ cd react-laravel $ php artisan preset react $ npm install && npm run dev ここでコンパイルまでは行って、サンプルまで表示できる。 LaravelからReact.jsに変数を渡す welcome.blade.php . . . <body> <div class="flex-center position-ref full-height"> <div id="example" title="title"></div> </div> <script src="{{ asset('js/app.js')}}"></script> </body> Example.js import React from 'react'; import { render } from 'react-dom'; const Example = ({ title }) => ( <div className="container"> <div className="row"> <div className="col-md-8 col-md-offset-2"> <div className="panel panel-default"> <div className="panel-heading">Example Component</div> <div className="panel-body"> {title} </div> </div> </div> </div> </div> ); const Element = document.getElementById('example'); if (Element) { const title = Element.getAttribute('title'); render(<Example title={title} />, document.getElementById('example')); } いざコンパイルと画面表示 $ npm run dev $ php artisan serve 参考 Laravel bladeからReactコンポーネント(Material UI)にバリデーションエラーメッセージを渡してみる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

create-react-appでTodoアプリを作成

はじめに create-react-appはReactで簡単にアプリを作成するためのツールです。 Reactのみで簡単なTodoアプリを作成したので備忘録としてまとめたいと思います。 なお、多少の環境構築が必要なので、よろしければこの記事を参考にしてください。 また今回はJSX、props、useStateの知識を扱います。この記事では説明を省きますので ご了承ください。 挙動イメージ 1. Todoを入力したら未完了のTodoに反映される 2. 未完了のTodoは完了のTodoに移動できる or 削除できる 3. 完了のTodoは未完了のTodoに移動できる 目次 1.create-react-app 2.HTMLとCSSで構造を作成 3.Reactを意識したモックに変更 4.Todoの入力機能 5.Todoの追加機能 6.Todoの削除機能 7.Todoの完了機能 8.Todoの戻す機能 9.コンポーネント化 1. create-react-app 環境構築が終了したらターミナルでcreate-react-appを実行しましょう。 ターミナル projects $ npx create-react-app 任意のフォルダ名(今回はreact-todo) projects $ cd react-todo // サーバー立ち上げ react-todo $ npm start ブラウザが立ち上がってReactの初期画面が表示されれば成功です! 2. HTMLとCSSで構造を作成 まずは、イメージの通りに骨組みを作成します。 srcディレクトリの必要なファイルは以下通りです。 App.jsはコンポーネントと明示的にわかるようにApp.jsxと拡張子を変更しましょう。 src |-App.jsx |-index.js |-App.css 次にindex.jsを編集します。 index.js import React from 'react'; import ReactDom from 'react-dom'; import { App } from './App'; ReactDom.render(<App />, document.getElementById('root')); 上から2行はおまじないです。 3行目はApp.jsxのAppという関数をimportしますという意味です。 最後にrender関数でAppの中身をindex.htmlのidがrootのdivタグに格納しています。 準備が整ったのでメインのApp.jsxを編集します。 App.jsx import React from 'react'; // App.cssを読み込み import './App.css'; export const App = () => { return ( <> <div className="outline"> <div className="inputArea"> <input placeholder="todoを入力"/> <button>追加</button> </div> <div className="imcompleteArea"> <p className="title">未完了のtodo</p> <ul> <li className="todoList"> <p>テスト</p> <button>完了</button> <button>削除</button> </li> </ul> </div> <div className="completeArea"> <p className="title2">完了のtodo</p> <ul> <li className="todoList"> <p>テスト</p> <button>戻す</button> </li> </ul> </div> </div> </> ); }; 構成としては大きくinputArea、imcompleteArea、completeAreaに分かれます。 Todoが格納させるエリアにはリストタグで個々のtodoにボタンで操作できるようにしておきます。 ポイント return内が複数行になるときは<> </>で全体をくくる JSXでclassを付与するときはclassName記述する 次にcssですが、ここはお好みで調整可能です。 記述方法は通常のcssとかわりません。 App.css * { margin: 0; padding: 0; box-sizing: border-box; } li { list-style: none; } .outline { padding: 50px; } .inputArea { width: 300px; height: 70px; background-color: lightblue; padding: 20px; margin-bottom: 50px; border-radius: 15px; } input { border-radius: 16px; border: none; outline: none; padding: 6px 16px; margin-right: 10px; } button { border-radius: 16px; border: none; padding: 4px 16px; } button:hover { background-color: #FF7FFF; color: #FFF; cursor: pointer; } /* 未完了のタスクエリア */ .imcompleteArea { width: 800px; min-height: 300px; background-color: lightgray; padding: 20px; margin-bottom: 50px; border-radius: 15px; } .title { text-align: center; font-weight: bold; } .todoList { margin-top: 20px; display: flex; align-items: center; justify-content: space-evenly; } /* 完了のタスクエリア */ .completeArea { width: 800px; min-height: 300px; background-color: lightgreen; padding: 20px; border-radius: 15px; } .title2 { text-align: center; font-weight: bold; } 以上で骨組みはできました。 3. Reactを意識したモックに変更 現状のApp.jsx内では未完了と完了のtodoはpタグに直接書き込まれています。 しかし、本来は動的にtodoの内容は反映してほしいのでuseStateを用いて状態を管理していきたいと思います。 App.jsx // Reactの部分を{ useState }に編集 import { useState } from 'react'; import './App.css'; export const App = () => { const [incompleteTodos, setIncompleteTodos] = useState(["宿題", "洗濯"]);  return ( : : ); }; const [incompleteTodos, setIncompleteTodos] 未完了のtodoの状態を管理するための配列です。incompleteTodosが初期値。setIncompleteTodosが更新するための変数です。 useState(["宿題", "洗濯"])は初期値のincompleteTodosに宿題と洗濯という2つの値を渡しています。 次にreturn内を編集していきます。まずは未完了のtodoからです。 App.jsx return ( : : <div className="imcompleteArea"> <p className="title">未完了のtodo</p> <ul> <li className="todoList"> <p>テスト</p> <button>完了</button> <button>削除</button> </li> </ul> </div> : : ); ↓ // 変更 <div className="imcompleteArea"> <p className="title">未完了のtodo</p> <ul> {incompleteTodos.map((todo) => { return ( <li key={todo} className="todoList"> <p>{todo}</p> <button>完了</button> <button>削除</button> </li> ); })} </ul> </div> 順番に解説していきます。 まずmap関数を用いて先程定義したincompleteTodosの中身を繰り返し処理で表示させます。その際の引数はtodoとします。 key={todo}と記述したのは繰り返し処理によるレンダリングでどのtodoかを判別するためです。最後にpタグの中身は{ }でtodoを囲んで表示させます。 ポイント return内でJavascriptを扱う際は{ }で囲む 同じようにして完了のtodoもuseStateを用いて状態を管理します。 completeAreaを以下のように編集してください。 App.jsx export const App = () => { const [incompleteTodos, setIncompleteTodos] = useState(["宿題", "洗濯"]); // 追記 const [completeTodos, setCompleteTodos] = useState(["掃除"]);    return ( : : <div className="completeArea"> <p className="title2">完了のtodo</p> <ul> {completeTodos.map((todo) => { return ( <li key={todo} className="todoList"> <p>{todo}</p> <button>戻す</button> </li> ); })} </ul> </div> ); }; これで準備が整いました! それでは順番に機能を実装していきたいと思います。 4. Todoの入力機能 まずは、入力されたtodoの状態を管理するためのuseStateを用意します。 初期値は''で空にしておきましょう。 App.jsx export const App = () => {  // 追記 const [todoText, setTodoText] = useState(''); }; 入力されたtodoTextはinputタグのvalueとして設定します。 { }で変数を囲むのを忘れないでください。 <div className="inputArea"> <input placeholder="todoを入力" value={todoText} /> <button>追加</button> </div> この状態だと入力欄にテキストを打ち込んでも反応しません。それは初期値の''が常に渡されているためです。 そこでonChangeメソッドを用いてtodoTextの値を更新する関数を作成する必要があります。 inputタグを次のように編集しましょう。 <div className="inputArea"> <input placeholder="todoを入力" value={todoText} onChange={onChangeTodoText}/> <button>追加</button> </div> inputにtodoが入力されたら、onChangeメソッドでonChangeTodoTextという関数を呼び出しています。この関数の中でtodoの値を更新する処理を記述していきます。 では、onChangeTodoText関数を定義します。関数はreturnの前に定義しましょう。 export const App = () => { // 関数の定義  const onChangeTodoText = (event) => setTodoText(event.target.value); }; onChangeTodoTextは引数を取ります。今回はeventを取ります。 event.target.valueは入力されたtodoの値のことです。これをsetTodoText関数の引数として渡すことで入力された値がsetTodoTextとして更新されます。 5. Todoの追加機能 4までの内容でtodoを入力することはできました。 次に追加ボタンを押してtodoを未完了エリアに移動させる機能を実装します。 buttonタグにonClickメソッドでボタンを押したときに関数を呼び出します。 今回はonClickAddという関数名にします。 <div className="inputArea"> <input placeholder="todoを入力" value={todoText} onChange={onChangeTodoText} /> <button onClick={onClickAdd}>追加</button> </div> ではonClickAddの中身を記述しましょう。 これもreturnの前に記述します。 const onClickAdd = () => { if (todoText === '') return; const newTodos = [...incompleteTodos, todoText]; setIncompleteTodos(newTodos); setTodoText(''); } 2行目のifは空の入力は無効にするという意味です。 3行目の[...incompleteTodos, todoText]は配列のコピーをしています。 [...変数名]と記述するとその変数の値をコピーできます。すでにある未完了のtodoに新しく入力したtodoTextを加えて、新しくnewTodosという変数に格納しています。 4行目は未完了のtodoのstateを更新したいのでnewTodosをsetIncompleteTodos関数の引数に渡して値を更新します。 5行目は追加ボタンを押した際に入力欄をリセットしたいのでsetTodoText関数を空にします。 6. Todoの削除機能 未完了エリアのtodoを削除する機能を実装します。方針は次の通りです。 1. 削除ボタンにonClickメソッドを設定して関数を呼び出す 2. 削除ボタンを押したら未完了エリアから選んだtodoが削除される 削除ボタンにonClickメソッドを設定 todoを削除する関数をonClickDeleteとします。 <div className="imcompleteArea"> <p className="title">未完了のtodo</p> <ul> {incompleteTodos.map((todo, index) => { return ( <li key={todo} className="todoList"> <p>{todo}</p> <button }>完了</button> <button onClick={() => onClickDelete(index)>削除</button> </li> ); })} </ul> </div> ポイントとしてはmap関数に新しくindexを引数に設定しています。 これはtodoを削除する際に何番目のtodoかを判別する際に必要です。関数の中でもindexは必要なのでonClickDeleteの引数にもindexを渡します。 関数の定義 例によってreturnの前に記述しましょう。 const onClickDelete = (index) => { const newTodos = [...incompleteTodos]; // newTodosから指定のindex番号を1つ削除する newTodos.splice(index, 1); setIncompleteTodos(newTodos); }; 2行目で現在の未完了のtodoをnewTodosという変数に格納します。 3行目でspliceメソッドを使用しています。spliceは配列の要素を操作できるメソッドで要素の追加や削除することができます。第一引数に削除対象のindexを渡して第二引数は指定したindexから何番目までの要素を操作するかを設定します。(今回は1なのでつまり、指定したtodoのみを削除します。) 4行目で削除し終えた未完了のtodoの状態を更新するためにsetIncompleteTodos関数にnewTodosを渡して値を更新します。 7. Todoの完了機能 未完了のtodoを完了エリアに移動させます。方針は以下の通りです。 1. 完了ボタンにonClickメソッドを設定して関数を呼び出す 2. 完了ボタンを押したら未完了エリアからtodoが削除される 3. 完了ボタンを押したら削除されたtodoが完了エリアに追加される 完了ボタンにonClickメソッドを設定 関数名はonClickCompleteとします。 <div className="imcompleteArea"> <p className="title">未完了のtodo</p> <ul> {incompleteTodos.map((todo, index) => { return ( <li key={todo} className="todoList"> <p>{todo}</p> <button onClick={() => onClickComplete(index)}>完了</button> <button onClick={() => onClickDelete(index)}>削除</button> </li> ); })} </ul> </div> onClickCompleteにも、どのtodoを完了にするか判別するために引数としてindexを渡しましょう。 関数を定義 const onClickComplete = (index) => { const newIncompleteTodos = [...incompleteTodos]; // newTodosから指定のindex番号を1つ削除する newIncompleteTodos.splice(index, 1); const newCompleteTodos = [...completeTodos, incompleteTodos[index]]; setIncompleteTodos(newIncompleteTodos); setCompleteTodos(newCompleteTodos); }; 2~4行目で未完了エリアから選んだtodoを削除しています。[...incompleteTodos]で現在の未完了のtodoをコピーして、変数newIncompleteTodosに格納します。次にspliceメソッドでtodoを削除します。 6~8行目で選んだ未完了エリアと完了エリアにtodoを更新してます。 [...completeTodos, incompleteTodos[index]]で完了エリアに現在の完了のtodoであるcompleteTodosをコピーし、選んだ未完了のtodoであるincompleteTodos[index]を追加し 変数newCompleteTodosに格納します。 最後に未完了エリアのtodoの値と完了エリアのtodoの値をuseStateで更新しています。 8. Todoの戻す機能 機能としてはこれで最後です。方針は以下の通りです。 1. 戻すボタンにonClickメソッドを設定し関数を呼び出す 2. 戻すボタンを押したら完了エリアから選んだtodoを削除する 3. 戻すボタンを押したら未完了エリアに選んだtodoを追加する 完了機能の逆ですね 戻すボタンにonClickメソッドを設定 関数名はonClickBackとします。 <div className="completeArea"> <p className="title2">完了のtodo</p> <ul> {completeTodos.map((todo, index) => { return ( <li key={todo} className="todoList"> <p>{todo}</p> <button onClick={() => onClickBack(index)}>戻す</button> </li> ); })} </ul> </div> 例によってmap関数とonClickBack関数にindexを引数として渡します。 関数を定義 const onClickBack = (index) => { const newCompleteTodos = [...completeTodos]; newCompleteTodos.splice(index, 1); const newIncompleteTodos = [...incompleteTodos, completeTodos[index]]; setCompleteTodos(newCompleteTodos); setIncompleteTodos(newIncompleteTodos); }; 2~3行目で完了エリアから選んだtodoを削除します。 5~7行目は完了機能の逆なので説明は割愛します。 これで一応機能は実装しました。最終的なコードはこんな感じです。 App.jsx import { useState } from 'react'; import './App.css'; export const App = () => { const [todoText, setTodoText] = useState(''); const [incompleteTodos, setIncompleteTodos] = useState([]); const [completeTodos, setCompleteTodos] = useState([]); const onChangeTodoText = (event) => setTodoText(event.target.value); const onClickAdd = () => { if (todoText === '') return; const newTodos = [...incompleteTodos, todoText]; setIncompleteTodos(newTodos); setTodoText(''); } // タスクを削除する const onClickDelete = (index) => { const newTodos = [...incompleteTodos]; // newTodosから指定のindex番号を1つ削除する newTodos.splice(index, 1); setIncompleteTodos(newTodos); }; // 未完了のタスクを完了タスクへ const onClickComplete = (index) => { const newIncompleteTodos = [...incompleteTodos]; // newTodosから指定のindex番号を1つ削除する newIncompleteTodos.splice(index, 1); const newCompleteTodos = [...completeTodos, incompleteTodos[index]]; setIncompleteTodos(newIncompleteTodos); setCompleteTodos(newCompleteTodos); }; // 完了のタスクを未完了のタスクへ const onClickBack = (index) => { const newCompleteTodos = [...completeTodos]; newCompleteTodos.splice(index, 1); const newIncompleteTodos = [...incompleteTodos, completeTodos[index]]; setCompleteTodos(newCompleteTodos); setIncompleteTodos(newIncompleteTodos); }; return ( <> <div className="outline"> <div className="inputArea"> <input placeholder="todoを入力" value={todoText} onChange={onChangeTodoText} /> <button onClick={onClickAdd}>追加</button> </div> <div className="imcompleteArea"> <p className="title">未完了のtodo</p> <ul> {incompleteTodos.map((todo, index) => { return ( <li key={todo} className="todoList"> <p>{todo}</p> <button onClick={() => onClickComplete(index)}>完了</button> <button onClick={() => onClickDelete(index)}>削除</button> </li> ); })} </ul> </div> <div className="completeArea"> <p className="title2">完了のtodo</p> <ul> {completeTodos.map((todo, index) => { return ( <li key={todo} className="todoList"> <p>{todo}</p> <button onClick={() => onClickBack(index)}>戻す</button> </li> ); })} </ul> </div> </div> </> ); }; それでは、ここからコンポーネント化をしてコードをスッキリさせたいと思います。 9. コンポーネント化 今回はインプットエリア、未完了エリア、完了エリアの3つにコンポーネント化していきます。 まずはインプットエリアから始めます。src配下にcomponentsディレクトリを作成し、InputTodo.jsxというファイルを作成します。 src |-components |-InputTodo.jsx |-index.js |-App.jsx |-App.css inputAreaのコンポーネント化 それではInputTodo.jsxに必要な記述をしていきます。 App.jsxのreturn内のinputAreaの中身をInputTodo.jsxのreturn内に切り抜きします。 また、App.jsxでこのファイルを使用できるようにexportします。 InputTodo.jsx import React from 'react'; export const InputTodo = () => { return ( <div className="inputArea"> <input placeholder="todoを入力" value={todoText} onChange={onChangeTodoText} /> <button onClick={onClickAdd}>追加</button> </div> ); }; 次にApp.jsxを編集します。 InputTodo.jsxをimportします。 App.jsx import { useState } from 'react'; import './App.css'; // 追記 import { InputTodo } from './components/InputTodo'; : : return ( <div className="outline"> <InputTodo /> <div className="imcompleteArea"> : : ); このままではInputTodo.jsx内にtodoTextやonChangeTodoTextなどの変数や関数が設定されてないのでエラーが起きてしまいます。なのでpropsを用いてInputTodo.jsxに必要な要素を渡します。 App.jsx : : return ( : : <InputTodo todoText={todoText} onChange={onChangeTodoText} onClick={onClickAdd} /> ); InputTodoに3つのpropsを設定します。 それぞれtodoText、onChange、onClickとします。 InputTodo.jsx import React from 'react'; export const InputTodo = (props) => { const { todoText, onChange, onClick } = props; return ( <div className="inputArea"> <input placeholder="todoを入力" value={todoText} onChange={onChange} /> <button onClick={onClick}>追加</button> </div> ); }; propsを引数に取り、それぞれの値として渡します。const { todoText, onChange, onClick } = props;は分割代入といってメソッドを書くときに簡略化することができます。例えば本来ならprops.todoTextやprops.onChangeと書かないといけないところをtodoTextやonChangeと省略できます。 これでインプットエリアのコンポーネント化は完了です。 imcompleteAreaのコンポーネント化 コンポーネント用のファイルを作成します。components配下にIncompleteTodos.jsxを作成します。 src |-components |-InputTodo.jsx |-IncompleteTodos.jsx |-index.js |-App.jsx |-App.css 同じようにincompleteAreaの中身を切り取ります。 IncompleteTodos.jsx import React from 'react'; export const IncompleteTodos = () => { return ( <div className="imcompleteArea"> <p className="title">未完了のtodo</p> <ul> {incompleteTodos.map((todo, index) => { return ( <li key={todo} className="todoList"> <p>{todo}</p> <button onClick={() => onClickComplete(index)}>完了</button> <button onClick={() => onClickDelete(index)}>削除</button> </li> ); })} </ul> </div> ); }; 次にApp.jsxを編集します。 App.jsx import { useState } from 'react'; import './App.css'; import { InputTodo } from './components/InputTodo'; // 追記 import { IncompleteTodos } from './components/IncompleteTodos'; : : rerun ( : : <IncompleteTodos /> : ); それでは必要なpropsを設定します。 App.jsx : : return ( : <IncompleteTodos todos={incompleteTodos} onClickComplete={onClickComplete} onClickDelete={onClickDelete} /> : ); IncompleteTodos.jsx内で使用する変数と関数をそれぞれ渡します。 IncompleteTodos.jsx import React from 'react'; export const IncompleteTodos = (props) => { const { todos, onClickComplete, onClickDelete } = props; return ( <div className="imcompleteArea"> <p className="title">未完了のtodo</p> <ul>      // incompleteTodosをtodosに変更 {todos.map((todo, index) => { return ( <li key={todo} className="todoList"> <p>{todo}</p> <button onClick={() => onClickComplete(index)}>完了</button> <button onClick={() => onClickDelete(index)}>削除</button> </li> ); })} </ul> </div> ); }; 先程と同じように分割代入でpropsを渡します。 それでは最後に完了エリアのコンポーネント化をしていきます。 completeAreaのコンポーネント化 コンポーネント用のファイルを作成します。components配下にCompleteTodos.jsxを作成します。 src |-components |-InputTodo.jsx |-IncompleteTodos.jsx |-CompleteTodos.jsx |-index.js |-App.jsx |-App.css ここも同様にcompleteAreaの中身を切り取ります。 CompleteTodos.jsx import React from 'react'; export const CompleteTodos = () => { return ( <div className="completeArea"> <p className="title2">完了のtodo</p> <ul> {completeTodos.map((todo, index) => { return ( <li key={todo} className="todoList"> <p>{todo}</p> <button onClick={() => onClickBack(index)}>戻す</button> </li> ); })} </ul> </div> ); }; 次にApp.jsxを編集します。 App.jsx import { useState } from 'react'; import './App.css'; import { InputTodo } from './components/InputTodo'; import { IncompleteTodos } from './components/IncompleteTodos'; // 追記 import { CompleteTodos } from './components/CompleteTodos'; : return ( : : <CompleteTodos /> : ); それでは必要なpropsを設定します。 App.jsx : : return ( : <CompleteTodos todos={completeTodos} onClickBack={onClickBack} /> ); それではpropsを渡しましょう。 CompleteTodos.jsx import React from 'react'; export const CompleteTodos = (props) => { const { todos, onClickBack} = props; return ( <div className="completeArea"> <p className="title2">完了のtodo</p> <ul> {todos.map((todo, index) => { return ( <li key={todo} className="todoList"> <p>{todo}</p> <button onClick={() => onClickBack(index)}>戻す</button> </li> ); })} </ul> </div> ); }; 以上でコンポーネント化完了です! 終わりに 記事が長くなってしまいましたが、最後まで読んでいただきありがとうございました! React学習の助けになれば幸いです。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React 使用時の注意点

Webページ制作中に躓いたポイントを記載 Nodistの使用 Nodeのバージョンを切り替えれるツール(PythonのPyenvのようなツール) v0.9.1で開発が停止?しており、最新のNodeに対応していないため、環境構築時にエラーが頻発する。 複数バージョンを扱う場合は、WSL2かDockerの使用を推奨。 筆者はホストPCとのデータ受け渡しが楽なWSL2+VSCODE(RemoteWSL)環境を使っている。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React環境構築 勉強用

環境構築 Homebrewのインストール /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" Homebrewのバージョン確認 brew --version nodebrewのインストール nodeのバージョンを管理 brew install nodebrew nodebrewのバージョンをインストール stable ←安定版をインストール nodebrew install stable インストールしたバージョンを使う nodebrew use 「インストールしたバージョン」 nodebrew use v14.15.3 nodeのバージョンの一覧表示 nodebrewで入れているnodeのバージョンの一覧表示 (例) current: v14.15.3 ←今使っているバージョン nodebrew ls Node.jsのバージョン確認 node -v npmのバージョン確認 npm -v
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GWに向け簡単にオリジナル旅程サイトを作成

はじめに みなさんこんにちは、新型コロナウイルスのせいで楽しみにしていたGWどこにも出かけることができなく1日PCと向き合っていますOzoraです。 家にいても暇なのでGithub pageとGatsbyを使って簡単にオリジナルの旅程をまとめたページを公開できるレポジトリをGithubで公開しました。 ただ旅程サイトを作っても退屈ですので、Github actionで、pushからだけでなく旅行先からスマホで直接githubのWebページから編集し、自動でデプロイされるような構成にしてみました。 これを機にGithub Actionを今まで使ったことがない人はぜひぜひ体験してみてください〜 自分が作成したサイトのサンプルはこちらでご覧いただけます。 使い方 基本的にはレポのREADMEに記載していますが、こちらでも説明すると、 https://github.com/ozora-ogino/gatsby-trip-plan をForkします ローカルにクローンして/content/blogに旅行予定をMarkdownファイルで作成 Github Access Tokenを作成してレポジトリの設定欄のSecretsにGH_TOKENという名前で設定します git push というような流れになります。 旅行先からスマホで自分専用の旅行予定がまとまったサイトがあれば便利ですし、何より友達や家族にドヤ顔できますのでぜひ利用してみてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む