- 投稿日:2020-11-28T23:54:38+09:00
【React】環境構築から起動まで
はじめに
React を習得するまでの軌跡をメモっていく備忘録的な記事です。
https://qiita.com/u_query/items/51b4140a450ee5d51dcc の続きですcomponent を表示してみる
create-react-appで作ったデフォルトのファイルは中身は全部消して一から作ります。
index.jsimport React from 'react'; import ReactDOM from 'react-dom'; const App = () => { return ( <p>Hello, World</p> ) }; ReactDOM.render( <App />, document.getElementById('root') );表示成功!
まとめ
とりあえずファイルの先頭で以下の2行を書けば React が動く。
import React from 'react'; import ReactDOM from 'react-dom';ReactDOM.renderメソッドを使って第一引数にコンポーネント、第二引数にdocument.getElementById('root')を入れる。
ReactDOM.render( <App />, document.getElementById('root') );コンポーネントは以下のように定義をする。
returnの中に JSX を書いていき、ブラウザにはこの部分が表示される。const App = () => { return ( <p>Hello, World</p> ) };
- 投稿日:2020-11-28T23:54:38+09:00
【React】component を作る
はじめに
React を習得するまでの軌跡をメモっていく備忘録的な記事です。
https://qiita.com/u_query/items/51b4140a450ee5d51dcc の続きですcomponent を表示してみる
create-react-appで作ったデフォルトのファイルは中身は全部消して一から作ります。
index.jsimport React from 'react'; import ReactDOM from 'react-dom'; const App = () => { return ( <p>Hello, World</p> ) }; ReactDOM.render( <App />, document.getElementById('root') );表示成功
まとめ
とりあえずファイルの先頭で以下の2行を書けば React が動く。
import React from 'react'; import ReactDOM from 'react-dom';ReactDOM.renderメソッドを使って第一引数にコンポーネント、第二引数にdocument.getElementById('root')を入れる。
ReactDOM.render( <App />, document.getElementById('root') );コンポーネントは以下のように定義をする。
returnの中に JSX を書いていき、ブラウザにはこの部分が表示される。const App = () => { return ( <p>Hello, World</p> ) };
- 投稿日:2020-11-28T23:34:35+09:00
【React】環境構築から起動まで
- 投稿日:2020-11-28T23:16:15+09:00
Reactを学んだ(3) ~React Router~
React Router
- SPAを実現してくれる。
<BrowseRouter/>
と<Link/>
と<Route/>
を同時に使う。yarn add react-router-dom
から始める。
<BrowserRouter />
- こんな感じで導入する。
- 遷移の履歴が
historyオブジェクト
の中に蓄積されるようだ。src/index.jsimport { BrowserRouter } from 'react-router-dom' import App from './App'; ReactDOM.render( <BrowserRouter> <App /> </BrowserRouter>, document.getElementById('root') );
<Link to="/about">About</Link>
- 公式ドキュメントはこちら
- stateが肝っぽい。
- 現在地を元に次の行き先を決めるためにはcallback関数を渡せば良いらしい。
<Route />
- This is a component which is going to decide which components are rendered based on the current URL path.
- ブラウザが指定するURLがpath=XXXにマッチした場合のみ、指定されたコンポーネントを描画してくれる。
- 以下のように使用する
import { Link } from 'react-router-dom' <div> <Route exact path='/' render={() => ( // exactを付けないと前方一致は全て当てはまってしまう... // propsを渡す必要があるならば、render=にcallback関数形式でコンポーネントを指定する <ListContacts onDeleteContact={this.removeContact} contacts={this.state.contacts} /> )} /> <Route path='/create' component={CreateContact}/> // propsを渡す必要がなければ、component=XXXで指定できる </div>
- また、以下のように
history.push('/xxx')
により、ページのリダイレクトが可能である。<Route path='/create' render={({ history }) => ( // 第一引数はhistroyオブジェクトが渡されるらしい(?) <CreateContact onCreateContact={(contact) => { this.createContact(contact) history.push('/') // history.push('/xxx')を指定する }} /> )}/>
- 投稿日:2020-11-28T19:32:58+09:00
ReactからLaravelのAPIサーバーを叩く + CORS概説
初めに
以前、LaravelをAPIサーバーとして利用する記事を書いたのですが、その続きとして、ReactとLaravelを使って、CORSとは何かを解説しながら、実際にLaravel側に設定を書くところまでをやってみたいと思います。
※ Laravelでは、CORSの設定は、fruitcake/laravel-corsを使うことが一般的かなと思いますが、本記事では、Middlewareの実装も行っています。
CORSって何?
Cross-Origin Resource Sharingの略で、
コルス
と呼びます。日本語訳するとオリジン間リソース共有
と呼びます。この設定をしていないと、フロントからAPIを叩いて、APIサーバーの値を取得したり、保存したりということが出来ません。Cross-Origin Resource Sharing (CORS) は、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。
Cross-OriginのOriginってそもそも何?
Originとは、URL中の
スキーム
ホスト
ポート
の組み合わせのことです。ブラウザはセキュリティ上の理由で、スクリプトによって開始されるHTTPリクエストを制限しています。これは「同一オリジンポリシー」に従う事とイコールで、APIを叩く際に、CORSの設定をしていない場合、同じオリジンに対してのみしかリクエストを行うことができません。これは以下のような脆弱性を防ぐことを目的としています。
CSRF(Cross-Site RequestForgeries)
直訳のまま、サイト横断的にリクエストを偽装する攻撃で、Web アプリケーションのユーザーが、意図しない処理をWebサーバー上で実行される脆弱性です。
本来はログインしたユーザーしか実行できない記事の書き込みを勝手にされたり、登録情報を強制的に変更されたりしてしまいます。
XSS(Cross Site Scripting)
ユーザーが Web サイトにアクセスすることで不正なスクリプトがWebブラウザ(Client)で実行されてしまう脆弱性です。
利用者のCookieが盗まれ、Cookie内にある利用者のセッション情報がそのまま使用されてしまう「なりすまし」の危険性があります。
繰り返しになりますが、上記の脆弱性を回避するために、APIを叩く際には、同一オリジンポリシーに従って、通常は異なるオリジンに対してリクエストを行うことができません。
CORSって何のためにあるの?
WebクライアントとAPIサーバーで分けて運用する場合など、異なるオリジンの間で通信を行う場合には、CORSの設定が必要です。
CORSは、ブラウザから情報を読み取ることを許可するオリジンを設定することを可能にします。
今回のケースでいうと、Reactアプリケーションから、Laravelで作ったAPIサーバーにリクエストを投げる際に、CORSの設定を行っていないと、エラーが返ってきてしまいます。
CORSの二つのリクエスト
CORSには、リクエストの種類として、
単純リクエスト(Simple Request)
とプリフライトリクエスト(Preflight Request)
に分類されています。単純リクエスト(Simple Request)
以下のメソッドが単純リクエストで許可されています。
- GET
- POST
- HEAD
詳細は以下を参照してみてください。
単純リクエストプリフライトリクエスト(Preflight Request)
HTTPメソッドには、サーバー情報に副作用を引き起こすメソッド(MIMEタイプを伴うPOSTやDELETE等)がありますよね。この場合、サーバーから対応するメソッドの一覧を取得し、サーバーの「認可」に基づいてリクエストを送信する必要があります。
方法としては、リクエストの始めに
OPTIONS
メソッドによるHTTPリクエストを他のドメインにあるリソースに向けて送り、実際のリクエストを送信しても安全かどうか確かめます。(=アクセスを許可するメソッドをレスポンスヘッダーに含める必要があります)以下のメソッドが、プリフライトリクエストでは許可されています。
- PUT
- PATCH
- DELETE
- CONNECT
- OPTIONS
- TRACE
詳細は以下を参照してみてください。
プリフライトリクエストCORSの設定がない状態でReactからAPIサーバーを叩いてみる
以下コンポーネントは、マウントされた時に、APIサーバーから値を取得する処理と入力した値をPOSTして保存する処理が書かれたコンポーネントになります。
実際に手元で試したい場合は、以下記事を参考にAPIサーバーも立てて試してみてください。
LaravelでサクッとAPIサーバーを立てる検証用コンポーネントexport default function Component(props) { const headers = { "Content-type": "application/json", } const [articles, setArticles] = useState([]); const [newArticle, setNewArticle] = useState({title: "", body: ""}); useEffect(()=> { const get = async () => { const res = await axios("http://127.0.0.1:8000/api/articles"); setArticles(res.data) } get(); },[]) const handleOnCreateNewArticle = async () => { const data = { title: newArticle.title, body: newArticle.body } await axios.post("http://127.0.0.1:8000/api/articles", data, { headers }) const res = await axios("http://127.0.0.1:8000/api/articles"); setArticles(res.data) } return ( <> <h1> {props.title} </h1> <h2>ArticleList</h2> {articles.map((article) => <div key={article.id}> <div>TITLE:{article.title}</div> <div>BODY:{article.body}</div> <hr /> </div> )} <input type="text" name="title" onChange={(e)=>{setNewArticle({...newArticle, title: e.target.value})}} value={newArticle.title} placeholder="New Title?"/> <input type="text" name="body" onChange={(e)=>{setNewArticle({...newArticle, body: e.target.value})}} value={newArticle.body} placeholder="New Body?"/> <button onClick={ handleOnCreateNewArticle }>Create</button> </> ) }実際にReactと、LaravelのAPIのローカルサーバーを起動して、検証してみます。
React:
http://localhost:3000
↓リクエスト
Laravel(APIサーバー):http://127.0.0.1:8000/api/articles
すると、以下メッセージがDevToolで確認できるはずです。
「Access-Control-Allow-Originヘッダーが存在しないので、CORSポリシーによってブロックされていますよ」といったメッセージが表示されていますね。Access to XMLHttpRequest at 'http://127.0.0.1:8000/api/articles' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.続いて、新規Articleを作成するためにPOSTメソッドでデータを送信してみます。すると、以下エラーが表示されます。プリフライトリクエストのチェックにパスしてませんと表示されています。
Access to XMLHttpRequest at 'http://127.0.0.1:8000/api/articles' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.以上で、CORSの設定をしていないとAPIを叩いた際にエラーとなることがわかりました。それでは、
Access-Control-Allow-Origin
レスポンスヘッダーを追記して、CORSの設定をしていきます。LaravelでCORS設定を行う
Middlewareを作成する
設定は簡単です。まずミドルウェアを作成しましょう。
php artisan make:middleware Cors
app/Http/Middleware/Cors.phpclass Cors { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { return $next($request) ->header('Access-Control-Allow-Origin', 'http://localhost:3000') ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') ->header('Access-Control-Allow-Headers', 'Content-Type'); } }ここで、レスポンスのHTTPヘッダーに3つのフィールドを追加しています。簡単に各フィールドの役割について見ていきます。
Access-Control-Allow-Origin
MDN:Access-Control-Allow-Origin
Access-Control-Allow-Origin レスポンスヘッダーは、指定されたオリジンからのリクエストを行うコードでレスポンスが共有できるかどうかを示します。
MDNの記述の通りですが、APIサーバー(ここではLaravel)へのアクセスを許可するオリジン(ここではReact)を指定しています。
上記の例だと、http://localhost:3000
は許可していますが、それ以外は、同一オリジンポリシーに従って、アクセス不可です、という設定になっています。Access-Control-Allow-Methods
許可するHTTPメソッドを指定します。
Preflightリクエストを送信する場合には、ここに許可するメソッドを追記します。上記の例では、GET
POST
PUT
DELETE
OPTIONS
を追記しています。Access-Control-Allow-Headers
許可するHTTPヘッダーを指定します。
上記の例では、Content-Type
ヘッダーが指定される値が設定されます。作成したミドルウェアをカーネルに追加
続いて、作成したミドルウェアをカーネルに追加しましょう。
app/Http/Kernel.phpprotected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'cors' => \App\Http\Middleware\Cors::class, ];ルーティング
最後にルーティングを以下のように書き換えます。
routes/api.phpRoute::group(['middleware' => ['api', 'cors']], function(){ Route::options('articles', function() { return response()->json(); }); Route::resource('articles', 'Api\ArticlesController'); });CORS設定後に実際にAPIを叩いてみる
以上で、CORSの設定は完了です。
先ほど、設定した3つのフィールドもヘッダーに含まれていることが確認できます。
実際にArticlesのデータが返ってきていることも確認できました。
以上で、CORSの設定からAPIを叩くところまで出来るようになると思います。
- 投稿日:2020-11-28T19:32:58+09:00
CORSを理解しながら、ReactからLaravelのAPIサーバーを叩く
初めに
以前、LaravelをAPIサーバーとして利用する記事を書いたのですが、その続きとして、ReactとLaravelを使って、CORSとは何かを解説しながら、実際にLaravel側に設定を書くところまでをやってみたいと思います。
※ Laravelでは、CORSの設定は、fruitcake/laravel-corsを使うことが一般的かなと思いますが、本記事では、Middlewareの実装も行っています。
CORSって何?
Cross-Origin Resource Sharingの略で、
コルス
と呼びます。日本語訳するとオリジン間リソース共有
と呼びます。この設定をしていないと、フロントからAPIを叩いて、APIサーバーの値を取得したり、保存したりということが出来ません。Cross-Origin Resource Sharing (CORS) は、追加の HTTP ヘッダーを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。
Cross-OriginのOriginってそもそも何?
Originとは、URL中の
スキーム
ホスト
ポート
の組み合わせのことです。ブラウザはセキュリティ上の理由で、スクリプトによって開始されるHTTPリクエストを制限しています。これは「同一オリジンポリシー」に従う事とイコールで、APIを叩く際に、CORSの設定をしていない場合、同じオリジンに対してのみしかリクエストを行うことができません。これは以下のような脆弱性を防ぐことを目的としています。
CSRF(Cross-Site RequestForgeries)
直訳のまま、サイト横断的にリクエストを偽装する攻撃で、Web アプリケーションのユーザーが、意図しない処理をWebサーバー上で実行される脆弱性です。
本来はログインしたユーザーしか実行できない記事の書き込みを勝手にされたり、登録情報を強制的に変更されたりしてしまいます。
XSS(Cross Site Scripting)
ユーザーが Web サイトにアクセスすることで不正なスクリプトがWebブラウザ(Client)で実行されてしまう脆弱性です。
利用者のCookieが盗まれ、Cookie内にある利用者のセッション情報がそのまま使用されてしまう「なりすまし」の危険性があります。
繰り返しになりますが、上記の脆弱性を回避するために、APIを叩く際には、同一オリジンポリシーに従って、通常は異なるオリジンに対してリクエストを行うことができません。
CORSって何のためにあるの?
WebクライアントとAPIサーバーで分けて運用する場合など、異なるオリジンの間で通信を行う場合には、CORSの設定が必要です。
CORSは、ブラウザから情報を読み取ることを許可するオリジンを設定することを可能にします。
今回のケースでいうと、Reactアプリケーションから、Laravelで作ったAPIサーバーにリクエストを投げる際に、CORSの設定を行っていないと、エラーが返ってきてしまいます。
CORSの二つのリクエスト
CORSには、リクエストの種類として、
単純リクエスト(Simple Request)
とプリフライトリクエスト(Preflight Request)
に分類されています。単純リクエスト(Simple Request)
以下のメソッドが単純リクエストで許可されています。
- GET
- POST
- HEAD
詳細は以下を参照してみてください。
単純リクエストプリフライトリクエスト(Preflight Request)
HTTPメソッドには、サーバー情報に副作用を引き起こすメソッド(MIMEタイプを伴うPOSTやDELETE等)がありますよね。この場合、サーバーから対応するメソッドの一覧を取得し、サーバーの「認可」に基づいてリクエストを送信する必要があります。
方法としては、リクエストの始めに
OPTIONS
メソッドによるHTTPリクエストを他のドメインにあるリソースに向けて送り、実際のリクエストを送信しても安全かどうか確かめます。(=アクセスを許可するメソッドをレスポンスヘッダーに含める必要があります)以下のメソッドが、プリフライトリクエストでは許可されています。
- PUT
- PATCH
- DELETE
- CONNECT
- OPTIONS
- TRACE
詳細は以下を参照してみてください。
プリフライトリクエストCORSの設定がない状態でReactからAPIサーバーを叩いてみる
以下コンポーネントは、マウントされた時に、APIサーバーから値を取得する処理と入力した値をPOSTして保存する処理が書かれたコンポーネントになります。
実際に手元で試したい場合は、以下記事を参考にAPIサーバーも立てて試してみてください。
LaravelでサクッとAPIサーバーを立てる検証用コンポーネントexport default function Component(props) { const headers = { "Content-type": "application/json", } const [articles, setArticles] = useState([]); const [newArticle, setNewArticle] = useState({title: "", body: ""}); useEffect(()=> { const get = async () => { const res = await axios("http://127.0.0.1:8000/api/articles"); setArticles(res.data) } get(); },[]) const handleOnCreateNewArticle = async () => { const data = { title: newArticle.title, body: newArticle.body } await axios.post("http://127.0.0.1:8000/api/articles", data, { headers }) const res = await axios("http://127.0.0.1:8000/api/articles"); setArticles(res.data) } return ( <> <h1> {props.title} </h1> <h2>ArticleList</h2> {articles.map((article) => <div key={article.id}> <div>TITLE:{article.title}</div> <div>BODY:{article.body}</div> <hr /> </div> )} <input type="text" name="title" onChange={(e)=>{setNewArticle({...newArticle, title: e.target.value})}} value={newArticle.title} placeholder="New Title?"/> <input type="text" name="body" onChange={(e)=>{setNewArticle({...newArticle, body: e.target.value})}} value={newArticle.body} placeholder="New Body?"/> <button onClick={ handleOnCreateNewArticle }>Create</button> </> ) }実際にReactと、LaravelのAPIのローカルサーバーを起動して、検証してみます。
React:
http://localhost:3000
↓リクエスト
Laravel(APIサーバー):http://127.0.0.1:8000/api/articles
すると、以下メッセージがDevToolで確認できるはずです。
「Access-Control-Allow-Originヘッダーが存在しないので、CORSポリシーによってブロックされていますよ」といったメッセージが表示されていますね。Access to XMLHttpRequest at 'http://127.0.0.1:8000/api/articles' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.続いて、新規Articleを作成するためにPOSTメソッドでデータを送信してみます。すると、以下エラーが表示されます。プリフライトリクエストのチェックにパスしてませんと表示されています。
Access to XMLHttpRequest at 'http://127.0.0.1:8000/api/articles' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.以上で、CORSの設定をしていないとAPIを叩いた際にエラーとなることがわかりました。それでは、
Access-Control-Allow-Origin
レスポンスヘッダーを追記して、CORSの設定をしていきます。LaravelでCORS設定を行う
Middlewareを作成する
設定は簡単です。まずミドルウェアを作成しましょう。
php artisan make:middleware Cors
app/Http/Middleware/Cors.phpclass Cors { /** * Handle an incoming request. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { return $next($request) ->header('Access-Control-Allow-Origin', 'http://localhost:3000') ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') ->header('Access-Control-Allow-Headers', 'Content-Type'); } }ここで、レスポンスのHTTPヘッダーに3つのフィールドを追加しています。簡単に各フィールドの役割について見ていきます。
Access-Control-Allow-Origin
MDN:Access-Control-Allow-Origin
Access-Control-Allow-Origin レスポンスヘッダーは、指定されたオリジンからのリクエストを行うコードでレスポンスが共有できるかどうかを示します。
MDNの記述の通りですが、APIサーバー(ここではLaravel)へのアクセスを許可するオリジン(ここではReact)を指定しています。
上記の例だと、http://localhost:3000
は許可していますが、それ以外は、同一オリジンポリシーに従って、アクセス不可です、という設定になっています。Access-Control-Allow-Methods
許可するHTTPメソッドを指定します。
Preflightリクエストを送信する場合には、ここに許可するメソッドを追記します。上記の例では、GET
POST
PUT
DELETE
OPTIONS
を追記しています。Access-Control-Allow-Headers
許可するHTTPヘッダーを指定します。
上記の例では、Content-Type
ヘッダーが指定される値が設定されます。作成したミドルウェアをカーネルに追加
続いて、作成したミドルウェアをカーネルに追加しましょう。
app/Http/Kernel.phpprotected $routeMiddleware = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'cors' => \App\Http\Middleware\Cors::class, ];ルーティング
最後にルーティングを以下のように書き換えます。
routes/api.phpRoute::group(['middleware' => ['api', 'cors']], function(){ Route::options('articles', function() { return response()->json(); }); Route::resource('articles', 'Api\ArticlesController'); });CORS設定後に実際にAPIを叩いてみる
以上で、CORSの設定は完了です。
先ほど、設定した3つのフィールドもヘッダーに含まれていることが確認できます。
実際にArticlesのデータが返ってきていることも確認できました。
以上で、CORSの設定からAPIを叩くところまで出来るようになると思います。
- 投稿日:2020-11-28T17:51:23+09:00
Reactでfor文を使う
したかったこと
JSX内で表の列をfor文を用いることで、繰り返し処理する。
理由)デザインを変える時に、一つ一つ変更するのが面倒だった。解決前のコード<tr> <th style={{fontWeight: 'bold',fontSize:40}}>1set</th> <th><input style={{height:52, width:80,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>kg</span></th> <th><input style={{height:52, width:70,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>回</span></th> </tr> <tr> <th style={{fontWeight: 'bold',fontSize:40}}>2set</th> <th><input style={{height:52, width:80,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>kg</span></th> <th><input style={{height:52, width:70,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>回</span></th> </tr> <tr> <th style={{fontWeight: 'bold',fontSize:40}}>3set</th> <th><input style={{height:52, width:80,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>kg</span></th> <th><input style={{height:52, width:70,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>回</span></th> </tr>解決策
・即時関数を使う。
・for文のループで出力された列を作成したリストにプッシュし、最後にリストを返す。(これをしないでfor文内で値を返すと、1ループ目で値を返してしまいました。)解決後のコード{(() =>{ const item=[]; for(let i=1; i <5; i++) { item.push( <tr> <th style={{fontWeight: 'bold',fontSize:40}}>{i}set</th> <th><input style={{height:52, width:80,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>kg</span></th> <th><input style={{height:52, width:70,borderStyle:"none", borderRadius:4}}/><span style={{fontSize:40}}>回</span></th> </tr> ) } return(item) })()}結果
- 投稿日:2020-11-28T17:43:04+09:00
firebase google認証でログインユーザーを選べない問題
概要
とある日、React.js x firebase でgoogle認の実装をしておりました。
documentを参照しながら、実装をしていたら、以下の問題がおきました。
一度google認証でサインインをすると、次回のサインインを別のユーザー(別のgoogleアカウント)で行うことができない。別のgoogleアカウントでもサインインしたいのに。。。
問題のコード
const signInByGoogle = () => { try { const googleProvider = new firebase.auth.GoogleAuthProvider(); firebase .auth() .signInWithPopup(googleProvider) .then((res) => { const user = res.user; if (user) { console.log(ok!!!) } }); } catch (e) { console.log('error', e.code); } }こんな感じのコードが公式のドキュメントにも書いてあります。このコードで通常にログインはできますし特に問題はありません。
しかし、他のgoogle アカウントを選択してログインすることができません。ではどうするのか。以下解決方法です。
解決方法
googleProviderのインスタンスのoptionに追加指定します。
const signInByGoogle = () => { try { const googleProvider = new firebase.auth.GoogleAuthProvider(); googleProvider.setCustomParameters({ prompt: 'select_account', // 追加 }); firebase .auth() .signInWithPopup(googleProvider) .then((res) => { const user = res.user; if (user) { console.log(ok!!!) } }); } catch (e) { console.log('error', e.code); } }どうやらgoogle側の仕様のようです。1つのアカウントでサインインし続けるのがまあ普通なんですけどね。
- 投稿日:2020-11-28T17:30:10+09:00
Reactを学んだ(2) ~Lifecycle Events~
Lifecycle Events
- 以下のようなものたちがある。
componentDidMount
is only called once in the lifecycle of any component, re-render will not reinitialize the component.componentDidUpdate
is triggered after re-rendering DOM.
- API Callをしたい時には、
componentDidMount()ライフサイクルメソッド
内で行い、それによって得られたデータの変更をstateに反映させる。これによって、
render()メソッドはレンダリングだけが責務になる
し、まずstateは最新ではないものがコンテンツの枠組みを最初に表示することができる
。以下のような感じ。
import React, { Component } from 'react'; import fetchUser from '../utils/UserAPI'; class User extends Component { constructor(props) { super(props); this.state = { name: '', age: '' }; } componentDidMount() { fetchUser().then((user) => this.setState({ name: user.name, age: user.age })); } render() { return ( <div> <p>Name: {this.state.name}</p> <p>Age: {this.state.age}</p> </div> ); } } export default User;
- 投稿日:2020-11-28T17:28:37+09:00
React + TypeScriptでコネクト4を作った(クラスコンポーネント)
はじめに
ブラウザゲームを作る経験がしたくて、「遊び大全」の中で一番好きなゲームがコネクト4を自分で作ってみました。
リアクティブに画面の状態が変わり、ゲームが進行するので、
初学者向けのReact + TypeScriptの練習としては良い題材だったと思います。この記事では作成の大まかな流れと学んだ知識などを書いています。
最初はチュートリアル記事にしようかと思ったのですが、いちいち説明しなくてもgithubにコメントを入れたので、それで伝わるかなーと判断しました。また最近Reactを触り始めたこともあり、関数コンポーネントでしか今まで制作物を作っていないので、今回はあえてクラスコンポーネントで作りました。
作った物
コネクト4です。
日本語でいう四目並べですね。
縦横斜めのいずれかで4つ連続でコマを先に並べた方が勝ちとなります。実際のゲームはこんな感じです。https://www.mathsisfun.com/games/connect4.html
※今回はオンライン機能の実装はしません。動いている所が見たい人は技術ブログに埋め込んでいるので、確認してください。
Githubのリポジトリ: https://github.com/gunners6518/connect4
作り方
仕様を決める
最初の必要機能などからざっくりしたアウトラインを作りました。
・UI:6×7のセルを作る
・セル:クリックしたら、その列の一番下が選択される 相手のプレイヤーの番になる
・ゲーム:縦、横、斜めで4つ揃ったら勝利UI:6×7のセルを作る
6×7のセル作成ですね。
セル1つ1つにplayer,indexというデータを持たせて、indexで配置が決まり、playerでどちらかに既に選択されているセルなら色が変わるようにしました。セル:クリック時のアクション
セルクリック時のアクションでは以下の機能をつけました。
・クリックしたセルのindexから列を判定して、その列で空いている一番下のセルが選択される処理。
・セル選択後にplayerTurnが変わる処理ゲーム:縦、横、斜めで4つ揃ったら勝利
ここは理論の部分でどうすれば良いのか苦労しました。
あーだこーだ悩んだ結果、
横:連続する4つのindexを持つセルが並んでいる
縦:index、index + 7、index + 7 * 2、index + 7 * 3が並んでいる
斜め:index、index + 7 – 1、index +7 * 2 – 2、index + 7 * 3 – 3が並んでいるで配列を切り出し、それらPlayerが全て一致しているかどうかをboolean型で返す関数を作り判定しました。
完成まで諸々
デザインを修正したり、文言を入れたり、コメントで簡単な説明を入れたりして完成です。
作成自体は3~4時間ほどかかりました。感想
ブラウザゲームを作る経験ができたのが嬉しかったです。
いつか遊び大全にあるゲームを全て作りたいな!!(ピアノはなしで)クラスコンポーネント縛りをしたのですが、好みは関数コンポーネントだなと改めて感じました。
現在はこのコネクト4をクラス→関数コンポーネントのリファクタリングをしているので、関数コンポーネントでの作り方、リファクタリングの方法などは今後まとめていく予定です。最後まで読んでいただきありがとうございました。
Twitterや技術ブログの方が高頻度でアウトプットしているので、興味ある方は是非。
- 投稿日:2020-11-28T17:14:21+09:00
Next.jsで「document is not defined」と怒られたときの対処法
Next.jsを使っていると様々なエラーに遭遇します。その中でも
document is not defined
window is not defined
これらのエラーは良く見るので対処方法を解説します。
エラー画面は以下のようなものです。
documentの未定義エラーの対処方法
「document is not defined」の対処方法は2つあります。
1.documentの定義判定
1つ目はグローバル変数にdocumentがあるのかの存在判定です。
document
が定義されている場合のみプログラムを実行するのでif (typeof document !== 'undefined') {}
でくくります。今回のプログラムではdocument
オブジェクトを使って、クッキーに値をセットしています。index.jsxexport default function Index() { //document変数が定義されているときのみ if (typeof document !== 'undefined') { //クッキーに値をセット document.cookie = "クッキー"; } return ( <div style={{textAlign: "center", marginTop: "50px"}}> {/* 値の表示 */} <h1>値:{typeof document !== 'undefined'?document.cookie:""}</h1> </div> ) }2.ブラウザ実行判定
2つ目の方法はブラウザで動いているかの判定です。ブラウザで動いているときのみ、
document
を使った処理を実行します。process.browser
がtrueの場合はブラウザで動いている証拠です。一方、undefined
の場合はサーバーでの実行になります。index.jsxexport default function Index() { //ブラウザ実行時のみ if (process.browser) { //クッキーに値をセット document.cookie = "クッキー"; } return ( <div style={{textAlign: "center", marginTop: "50px"}}> {/* 値の表示 */} <h1>値:{process.browser?document.cookie:""}</h1> </div> ) }なぜ「documentが定義されていない!」と怒られるのか?
なぜ
document
やwindow
が定義されていないと怒られているのかというと、サーバーサイドでブラウザ用のグローバル変数を使おうとしているためです。Next.jsはサーバーサイド、クライアントサイド両方で動くフレームワーク。そのため、定義したソースはサーバー、ブラウザ両方の環境で実行されます。
そして、
document
やwindow
はクライアントだけで定義されているグローバル変数です。サーバー環境で動かそうとすると「そんなグローバル変数は定義されていない!」とエラーが発生します。なので、
if (process.browser)
でブラウザのみの判定を入れればサーバー環境での実行時には無視され、クライアント環境だけで動くのです。
- 投稿日:2020-11-28T17:12:04+09:00
SIerエンジニアからWeb系フロントエンドエンジニアに転身するために今やっていること
こんにちは!SIerでJavaプログラマをしているゆうきデザイン(@yuki_design_gr)と言います。
Qiita初投稿として、自己紹介も兼ねて
"SIerエンジニアからWeb系フロントエンドエンジニアに転身するために今やっていること"
というテーマで書いてみようと思います。同じような境遇にいる人の道しるべの1つになりますように!
目次
1. 自己紹介
2. なぜWeb系を目指すのか
3. SIerエンジニアからWeb系フロントエンドエンジニアに転身するために今やっていること1. 自己紹介
東京在住の20代半ば。
学歴
東京外大韓国語専攻卒業
職歴
新卒で大手SIerに入社し、アカウント営業を担当(10ヶ月)
→SE(現職。Java・.NET・Oracleのコーディング実務1年半)
→Web系企業への転職準備中モットー
技術とデザインのことをポジティブに共有すること
目標
・世の中をポジティブにするWebサービスを作ること
・ビジネスを始めたい・サービスを作りたい友だちをIT・デザイン面でサポートすること趣味など
韓国語と英語は日常会話レベル
持久系のスポーツが好き(陸上・水泳。社会人になってからもたまにやってる)
実写の動画編集
映画・音楽・コーヒー
ミスチルが生まれた時から好きで、人生ピンチの時に助けてくれる存在(←いま)さあ、本題に入ります!
2. なぜWeb系を目指すのか
①新しいものを追いかけるのが好きだから
音楽や映画などのエンタメや好きで、
SI業界よりもトレンドの移り変わりが激しいWeb業界が楽しそうに見えるため。②Webサービスを作りたいという目標があるから
自己紹介でも書いたように
世の中をポジティブにするWebサービスを作る
という目標があり、
SI業界に身を置くよりも目標実現への近道だと思っています。③システムの裏側の処理よりも見た目に魅かれるから
Javaエンジニアをしていてプログラミングは基本的に全般楽しいですが、
JSPやCSS等のシステムの見た目の開発が楽しく、
また他のメンバーが気にかけないレイアウトのズレなどに何度も気づくことができました。そのため、まずはWebデザイナーやUI・UXデザイナーに興味を持ち、
デザイナーのための勉強会などに参加してきました。しかし、自分のプログラマとしての経験を活かす×システムの見た目に寄与できる
というフロントエンドの技術が一番しっくりくるなあと今は思っています。3. SIerエンジニアからWeb系フロントエンドエンジニアに転身するために今やっていること
①フロントエンド技術に触れること
結局はどの言語がベスト!とかはなさそうなので
今は色々触ってみてます。フロント: react, vue, rails
バックエンドやインフラ等: node, ruby, docker, laravel
その他: TypeScript, Sass, Bootstrap色々触れる今の時期が一番楽しいですね。
何かを極めた方が勉強になる気もしますが。個人的にはnode + react(またはvue・angular) + TypeScriptがアツい気がします!
全部jsで書けるなんて!②フロントエンド技術を用いたWebアプリを作ってみること
上記のそれぞれの言語を使って
簡単なSNSやTodoリストをチュートリアルを見ながら作成中です。
ネット上に公開するところまでやりたいです。③SNSやGitHub、Qiita等でのアウトプット
個人的に苦手であまりできていないアウトプット。
でも見てる人との交流が生まれたり、自分の特性や技術力を客観視できる機会と思って、
定期的に取り組んでいきたいです。
この投稿の内容は以上です。
ここまで読んでいただきありがとうございます。
これからも有益な情報をポジティブに発信していきたいです。ぜひ、Twitter(@yuki_design_gr)のフォローもよろしくお願いします。
- 投稿日:2020-11-28T13:53:35+09:00
Firebase Emulator Suiteをインストールして使ってみた
メイン( masalibの日記 )で書いているブログにはあまりアクセスがないのでこちらにも記載います。あまりQiitaでは書かないので文化が違っていたらすいません
目次
- Firebase Emulator Suiteとは
- 事前準備
- Firebase Emulatorのインストール
- Firebase Emulatorの起動
- Firebase Emulator Suiteを開く
- ローカルのReactで使ってみる
- Reactで確認する
- 感想
Firebase Emulator Suiteとは
ローカルでFirebaseをエミュレートしてくれる機能です。
今まではAuth機能の開発をおこなう時はプロジェクト+ dev みたいな形でプロジェクトを作って検証していました。それを今後はローカルのみで完結できるみたいです。テストのclも回せるみたいなのでテストツールを導入している所だと重宝されそう。
事前準備
Firebase Emulator Suiteは以下の条件がそろっていないと動きません
- Node.js バージョン 8.0 以降。
- Java バージョン 1.8 以降。
- Firebase CLI 7.8.0 以降
NodeはReactを作っているのでバージョンは余裕でクリアできるのですがjavaやFirebase CLIは新しいマシンだと全然使っていなかったのでインストールしないといけなかった。
javaのインストール
本編とは関係ないのでjavaをインストールしている人はスキップしてください。
- 本家からバイナリーをダウンロードする
https://jdk.java.net/archive/
- ダウンロードしたファイルをローカルのPCにダウンロードする。私の場合は
「C:\java\jdk-15」です- 「Windowsキー」+「R」キーでファイル名を指定して実行するの画面を表示して「control sysdm.cpl」を入力する。システムのプロパティが開く
- 詳細設定のタブをクリックして環境変数のボタンを押す
- JAVA_HOMEを設定
- pathの編集をおこなう
- コマンドラインを開いていたら閉じてから開く。
- 確認コマンドでjavaが認識している事を確認する
javac -version # => javac 15 java -version # => openjdk version "15" 2020-09-15 # => OpenJDK Runtime Environment (build 15+36-1562) # => OpenJDK 64-Bit Server VM (build 15+36-1562, mixed mode, sharing)どうでもいいけどPathの編集画面がかわったのね。
この記事を参考にjavaをインストールしました
https://www.javadrive.jp/start/install/index1.html
https://www.javadrive.jp/start/install/index4.htmlFirebase CLIのインストールと設定
- 管理者権限でコマンドラインを開く。
npmでインストールする
npm install -g firebase-tools
firebaseにloginにする。(ブラウザが立ち上がるので認証を許可する)
$ firebase login i Firebase optionally collects CLI usage and error reporting information to help improve our products. Data is # collected in accordance with Google's privacy policy (https://policies.google.com/privacy) and is not used to identify you. ? Allow Firebase to collect CLI usage and error reporting information? Yes i To change your data collection preference at any time, run `firebase logout` and log in again. Visit this URL on this device to log in: https://accounts.google.com/o/oauth2/auth?client_id=xxxxxxxxx・・・・ Waiting for authentication... + Success! Logged in as masalib@gmail.com確認のためにプロジェクトリストを表示する
√ Preparing the list of your Firebase projects ┌────────────────────────┬ │ Project Display Name │ Project ID ├────────────────────────┼ │ angular-study-chat │ angular-study-chat ├────────────────────────┼ │ Learn-Firebase-masalib │ learn-firebase-masalib ├────────────────────────┼ │ masalib-hosting │ masalib-hosting ├────────────────────────┼ │ testpwa │ testpwa-32138 └────────────────────────┴Firebase Emulatorのインストール
自分のプロジェクトに移動して初期化のコマンドを実行する
firebase init進む準備はできていますか?と聞かれるのでyを入力してEnterキーを押す
Are you ready to proceed Yこのフォルダに設定したいFirebase CLIの機能をSpaceキーを押して機能を選択し、Enterキーを押して設定します。Emulatorsが*になるようにします
? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confi rm your choices. Emulators: Set up local emulators for Firebase features >( ) Database: Deploy Firebase Realtime Database Rules ( ) Firestore: Deploy rules and create indexes for Firestore ( ) Functions: Configure and deploy Cloud Functions ( ) Hosting: Configure and deploy Firebase Hosting sites ( ) Storage: Deploy Cloud Storage security rules (*) Emulators: Set up local emulators for Firebase features ( ) Remote Config: Get, deploy, and rollback configurations for Remote Configプロジェクトのオプションを聞かれるの既存のプロジェクトを選択します。自分の場合は「learn-firebase-masalib」でした
```
=== Project SetupFirst, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.? Please select an option:
Use an existing project
Create a new project
Add Firebase to an existing Google Cloud Platform project
Don't set up a default project? Select a default Firebase project for this directory: (Use arrow keys)
angular-study-chat (angular-study-chat)
learn-firebase-masalib (Learn-Firebase-masalib)
masalib-hosting (masalib-hosting)
testpwa-32138 (testpwa)
```どのエミュレータをインストールするのかを聞かれるのでめんどくさいの全部選択します
=== Emulators Setup ? Which Firebase emulators do you want to set up? Press Space to select emulators, then Enter to confirm your choices. ( Press <space> to select, <a> to toggle all, <i> to invert selection) >( ) Authentication Emulator ( ) Functions Emulator ( ) Firestore Emulator ( ) Database Emulator ( ) Hosting Emulator ( ) Pub/Sub Emulatorエミュレータのport番号を聞かれるので答えていく
? Which port do you want to use for the auth emulator? 9099 ? Which port do you want to use for the functions emulator? 5001 ? Which port do you want to use for the firestore emulator? 8080 ? Which port do you want to use for the database emulator? 9000 ? Which port do you want to use for the hosting emulator? 5000 ? Which port do you want to use for the pubsub emulator? 8085 ? Would you like to enable the Emulator UI? (Y/n) y ? Which port do you want to use for the Emulator UI (leave empty to use any available port)? 8000 ? Would you like to download the emulators now? (y/N) y「Which port do you want to use for the Emulator UI (leave empty to use any available port)?」は8000と入力したgoogle先生のマニュアルだと4000だった。
Firebase Emulatorの起動
起動コマンドで起動する
firebase emulators:start
問題がなけば下記のような画面が表示されるFirebase Emulator Suiteを開く
エミュレーターにはUIが用意されているのでブラウザで http://localhost:8000 を開く
(google先生のマニュアルだと http://localhost:4000 です)Authの画面
ローカルのReactで使ってみる
Firebaseの設定ファイルの部分を変更する
import firebase from "firebase/app" import "firebase/auth" import "firebase/storage"; import 'firebase/firestore'; const app = firebase.initializeApp({ apiKey: process.env.REACT_APP_APIKEY, authDomain: process.env.REACT_APP_AUTHDOMAIN, databaseURL: process.env.REACT_APP_DATABASEURL, projectId: process.env.REACT_APP_PROJECT_ID, storageBucket: process.env.REACT_APP_STORAGE_BUCKET, messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID, appId: process.env.REACT_APP_APP_ID, measurementId: process.env.REACT_APP_MEASUREMENT_ID }); var db_obj = firebase.firestore(); if (process.env.REACT_APP_HOST === "localhost") { db_obj.useEmulator("localhost", 8080); console.log("useEmulator:firestore") } var auth_obj = firebase.auth(); if (process.env.REACT_APP_HOST === "localhost") { console.log("useEmulator:auth") auth_obj.useEmulator("http://localhost:9099") } var storage_obj = firebase.storage(); /* storage_obj.useEmulator is not a functionというエラーがでたのでコメントアウト if (process.env.REACT_APP_HOST === "localhost") { console.log("useEmulator:storage") storage_obj.useEmulator("http://localhost:9099") } */ export const db = db_obj; export const auth = auth_obj; export const storage = storage_obj; export default appReactで確認する
起動するとログにエミュレートのログが表示される
SingUPしてみると通信先がlocalhostになっている
Firebase Emulator Suiteで確認もできました
入力した内容も確認できます。
なおメールアドレスの有効化は2020/11/28の時点はできない
https://github.com/firebase/firebase-tools/issues/2872
感想
- メールアドレスの有効化ができないとちょっと困るので早く直してほしい
- 切り替えも楽なのでいいかも。アプリでも多分できると思う。
- 投稿日:2020-11-28T13:34:13+09:00
- Cyber Muse - 音楽投稿アプリをリリースしました!(個人開発)
初めに
音楽投稿アプリを開発しました!
ウェブサイト: https://cyber-muse.com
Apple Store: https://apps.apple.com/app/id1511131064
Google Play: https://play.google.com/store/apps/details?id=com.front_music開発言語はReactとReact Nativeを使用しています。
開発から運用まで全て個人で行っています。
開発から運用まで大変だったことなど色々書いていきたいと思います。React & React Nativeについての所感
気づけばReactを書き始めてから1年ぐらいがたってしまいました。Reactを学ぶきっかけとなったのはアルバイトです。それまではフロントエンドについてはあまり興味がなく、Pythonをよく書いていました。バイト先ではReact(JS)がメインだったので面接に受かってから学び始めました。バイトの頻度は週二回1日8時間くらいでした。
React
Reactは個人的にすごく書きやすい。個人的にhtml内のscriptの中やUIとは独立したjsファイルにjsを書くのが嫌いだったので。見にくくないですか?
Reactでつまづくとすれば、それはstateという概念だ。hooks周辺を理解するのは難しい。
useEffectについてひたすら調べまくりました。正直日本語でこれ以上に分かりやすい記事は他にないと思う。
https://iqkui.com/ja/a-complete-guide-to-useeffect/
あとは例外的ではあるがファイルアップロード関係かな。バイト先ではGraphQLというバックエンドとフロントを繋ぐものを使用していたのだが、これに使えるファイルアップローダーの無さといったらもう。そもそもGraphQLを通してファイルアップロードするのもどうかと思うんだが。バックエンドでPrismaとかDockerとか使ってたからしゃあなしか?
今回バックエンドについては特にかくつもりないので割愛します。
とまぁこんな感じでReactに慣れてきたらReact Nativeも書くようになりました。React Native
React Nativeで一番大変なのはコードを書く以前の準備だと思います。モバイルアプリの開発はSimulatorをたてた状態で行わないといけませんが、慣れないうちはこれが大変です。iOSで言えばpodあたりやXcodeの設定をしっかり把握していないとビルドが失敗します。バイト先はアプリの運用をしていたので、既に完成されているものをビルドしなければいけません。ビルド時間は5~10分くらいかかっていました。ですので、慣れていないと後少し!!というところでビルドが失敗したりしてドツボにハマったりします。
という感じで僕のモバイルアプリ開発の始めは苦しむことが多かったですが、今はiOSもAndroidもできているのでいいかなという感じです。
そう、これがReact Nativeの利点だと僕は思う。ネイティブ言語でiOSとAndroidの開発をしようと思ったら、swiftやjavaの両方を学ばなければいけないからだ。React Nativeを使えば、多少の差があることは確かだがUIでいじる必要があることはほとんどない。ビルドの仕方とリリースの仕方を学べばそれでいい。多少の差に言及しておくとそれはアプリ内でファイル操作をする時だ。写真を選択したり、アップロードしたり。知らないとバグって、は?ってなる。最後にReact NativeのUIについて述べる。UIはオープンソースのコンポーネントが色々あって便利だと思う。が、React Nativeのバージョンの違いとかで役に立たなかったりするやつがあるから注意。githubのReleasesのlatestが2年前とかのやつには手を出さないのが無難だ。あとは、cssのflexの有り難みがすごくわかる。キーワードはflexDirection, justifyContent, alignItemsだ。この3つがあれば画面が綺麗に整う。
所感の割りには色々書きすぎたと思うが以上がReact Nativeに関する正直な感想だ。
ウェブサイトのアニメーションについて
実は最近ウェブサイトをアップデートしてアニメーションを取り入れた。
https://cyber-muse.com
このウェブサイトのアニメーションは全てreact-springというモジュールを使用している。
https://www.react-spring.io/
このモジュールにスクロールの制御を加えると僕が作ったウェブサイトぐらいは作れるという感じで紹介しておこう。終わりに
初めの文章に取り消しせんが付いている通りです。
ReactとReact Nativeについて述べただけになってしまいました。ReactとReact Nativeを使えばこういうウェブサイトやアプリが作れる!!という参考になればいいと思います。
ウェブサイト: https://cyber-muse.com
Apple Store: https://apps.apple.com/app/id1511131064
Google Play: https://play.google.com/store/apps/details?id=com.front_music
- 投稿日:2020-11-28T13:09:21+09:00
Next.js v10でTypeScriptを使いつつEmotion v11を使おうとしたら5時間も解決しなかった話
これは何
- タイトルにあるように、5時間もハマってしまいました
- 一応解決出来たので備忘録として残しています
使用しているパッケージの情報
名前 バージョン next 10.0.1 react 17.0.1 typescript 4.0.5 @emotion/react 11.1.1 @emotion/babel-plugin 11.0.0 JSX Pragmaが動かない
事象
- インストールの記事などには
/** @jsx jsx */
という記述が紹介されている- しかしそのままだとコンソールエラーが出た
解決策
/** @jsxImportSource @emotion/react */
という記述にする
- CSS Propの詳細な説明のページに記載されている
- Reactのバージョンが新しくて(v17)JSXのランタイムの方式が違っていたため、従来の記述からは変える必要があった
Babelの設定が上手く働かない
前提
- 新しいJSX Pragmaが動くようにはなったものの、毎回書くのは面倒
- Babelの設定をしてJSX Pragmaの記載無しで動くようにしたい
事象
@emotion/babel-preset-css-prop
を使ってもJSX Pragma無しだと動かなかった解決策
@emotion/babel-preset-css-prop
の代わりに@emotion/babel-plugin
を入れつつ、.babelrc
を以下のようにしたら動いた
- babelrcの中身は公式のこのページより抜粋
.babelrc{ "presets": [ [ "next/babel", { "preset-react": { "runtime": "automatic", "importSource": "@emotion/react" } } ] ], "plugins": ["@emotion/babel-plugin"] }動くは動くけど、型定義がなくて怒られる
事象
- スタイル自体は当てられるけどエディタ上でエラーが出ている
- インストールしただけだとEmotionの型定義ファイルが無かった
解決策
next-env.d.ts
に以下の一行を足す
- Emotion v11についてのページに記載されている
next-env.d.ts/// <reference types="@emotion/react/types/css-prop" />
まとめ
- 過去にEmotionを適用したリポジトリを見ながら、ただ真似るだけで設定していたのが良くなかった
- 公式ドキュメントをしっかり読むのはやっぱり大事
- 投稿日:2020-11-28T08:48:54+09:00
React Native (Expo) + unstatedで状態管理するアプリを実際に作り始める時に参考にしたリポジトリ
はじめに
React Nativeの状態管理で最近はReduxよりunstatedを使うと便利という記事を見て、
使いたいとは思いつつも、どうやって実装するのか全体のイメージが湧きませんでした。その時にいくつかリポジトリを参考にしたのでメモ。
unstated-demo
一番最初の入門としてよくあるカウンターアプリの実装の参考にしたリポジトリ。
あとunstatedで取得したデータを一覧表示する所も参考になりました。https://github.com/dabit3/unstated-demo
react-native-social-app-sample
unstated+react-navigationの連携の実装で参考にしました。
unstated-demoよりも実用に近いかたちになっているので、
入門が終わってからはこちらのリポジトリを主に参考にしていました。Subscribe以外の場所でContainerにアクセスしたくなった時の、
HOC化の実装もされているので、イメージが湧いて助かりました。https://github.com/kdenz/react-native-social-app-sample
おわりに
unstated使えば、Reduxで分けていたStore, Reducer, Actionという処理が一つのファイルにまとまるので見通しがいいし、実装しやすくて良かったです。
ただReact Nativeと組み合わせた実装例が少ないので、今回の記事が参考になれば幸いです。
- 投稿日:2020-11-28T08:18:13+09:00
react-hook-formでサーバーサイドで行ったバリデーション結果がコントロール操作時にクリアされる
react-hook-form v6.10 ~ 6.11.5 までで確認した挙動です。
やろうとしたこと
react-hook-formを使用して情報登録用のフォームを開発していたが、
仕様上フォームの入力内容バリデーションはAPIを呼び出してサーバーサイドで実施しなければいけなかった。理由は単純で、フォームの入力内容がバリデーションを通った場合は入力項目を表示する確認画面を表示し、
バリデーションエラーがあった場合にはフォームの再入力を促すという画面構成だったからだった。+------+ | Form |<---+ +------+ | | Submit | +--------+ フォームが submit されたらバリデーションAPIで検証。 | 検証OKなら確認画面へ、バリデーションエラーがあった場合は | バリデーションエラーメッセージを表示したフォーム画面を再表示する。 v +---------+ | Confirm | +---------+ | Submit 確認画面で submit されたら登録完了。 | ~react-hook-formの
useForm()
が返すsetError()
でサーバー側のエラーをセットできるので利用していたが、
エラーがあったコントロールを操作しようとすると対応するバリデーションエラーメッセージが消えてしまった。バリデーションエラーメッセージはフォームの入力中に何度でも確認できるよう、
submit されない限り表示し続けるという要件だったので、入力を再開した時点でメッセージが消えるのは喜ばしい挙動ではなかった。対応
「コントロールの入力が開始された時点で対応するバリデーションエラーをリセットするべきである」というのが
react-hook-formの考えの様だった: https://github.com/react-hook-form/react-hook-form/issues/1881react-hook-formにはフロントエンドサイドでフォーム入力中にインタラクティブなバリデーションを行う機能があり、
APIはこの機能をベースに設計されている。入力を再開してバリデーションエラーが無くなったらエラーメッセージは非表示にするという方針のようだ。
『サーバーでバリデーションした結果を表示し続けたい』というような時は、
バリデーションエラーメッセージをsetError()
などで管理しないで独自のstateとして管理するしかないようだったので
以下のようなerrors
とsetError
に近いAPIを自作した。import { useCallback, useState } from "react" import type { UseFormMethods } from "react-hook-form" // react-hook-form の setError はジェネリクスに渡された型に // Array, object が含まれていると setError の第1引数にあらゆる型を受け入れるようになっている。 // あらゆる型を受け入れると Typescript の型チェックが働かなくなる。 // 強制的に型チェックを有効にするために与えられた型のプロパティ名に対して // unknown を持つダミータイプを提供する事で setError の型推論を強制的に復活させる。 type ForwardPropertyName<T> = { [K in keyof T]: unknown }; type SetPersistError<T> = UseFormMethods<ForwardPropertyName<T>>["setError"]; type ErrorsType<T> = UseFormMethods<T>["errors"]; type UsePersistErrorResult<T> = { persistErrors: ErrorsType<T>; setPersistError: SetPersistError<T>; }; /** * コンポーネントがアンマウントされるまではエラー情報を保持する * react-hook-form の{@code useForm}が返す{@code errors}, {@code setErrors}と * 同等のオブジェクトと関数を返す。 * * react-hook-form v6 の{@code errors}はフォームコントロールが変化すると * 全てのエラー情報を消去してしまう。 * バックエンドでバリデーションした結果を永続的に表示し続けたい場合は、独自のエラー管理を行わなければいけない。 * この関数はコンポーネントがアンマウントされるまではエラー情報を保持する * {@code persistErrors}と{@code setPersistError}を返す。 * この2つは{@code errors}と{@code setErrors}と同じように使用できる。 * * See: https://github.com/react-hook-form/react-hook-form/issues/1881 */ const usePersistError = <T>(): UsePersistErrorResult<T> => { const [persistError, setPersistError] = useState<ErrorsType<T>>({}); const setPersistErrorTyped: SetPersistError<T> = useCallback( (name, error) => setPersistError((prev) => ({ ...prev, [name]: error })), [setPersistError] ); return { persistErrors: persistError, setPersistError: setPersistErrorTyped }; }; export { usePersistError }; export type { SetPersistError, ErrorsType };使い方は
usePersistError()
で{persistErrors, setPersistError}
を取り出し、
サーバーサイドバリデーションの結果はsetPersistError()
を呼び出して追加する。
persistErrors
のプロパティにセットされているバリデーションエラー情報を画面に表示するという形になった。react-hook-formのドキュメントに記載されているサンプルコード元にすると以下のような使い方になる。
バリデーションエラーメッセージは
handleSubmit
のコールバックなどで手動でクリアすることになる。import React from "react"; import ReactDOM from "react-dom"; import { useForm } from "react-hook-form"; import { usePersistError } from "./usePersistError"; import "./styles.css"; interface IFormInputs { firstName: string; lastName: string; age: string; website: string; } function App() { const { register, handleSubmit } = useForm<IFormInputs>(); const { persistErrors, setPersistError } = usePersistError<IFormInputs>(); const onSubmit = (data: IFormInputs) => { // Clear error setPersistError("firstName", {}); setPersistError("lastName", {}); setPersistError("age", {}); setPersistError("website", {}); alert(JSON.stringify(data)); if (!data.firstName) { setPersistError("firstName", { message: "firstName is missing." }); } if (!data.lastName) { setPersistError("lastName", { message: "lastName is missing." }); } if (!data.age) { setPersistError("age", { message: "age is missing." }); } if (!data.website) { setPersistError("website", { message: "website is missing." }); } }; return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <label>First Name</label> <input type="text" name="firstName" ref={register} /> {persistErrors.firstName?.message && ( <p>{persistErrors.firstName.message}</p> )} </div> <div style={{ marginBottom: 10 }}> <label>Last Name</label> <input type="text" name="lastName" ref={register} /> {persistErrors.lastName?.message && ( <p>{persistErrors.lastName.message}</p> )} </div> <div> <label>Age</label> <input type="text" name="age" ref={register} /> {persistErrors.age?.message && <p>{persistErrors.age.message}</p>} </div> <div> <label>Website</label> <input type="text" name="website" ref={register} /> {persistErrors.website?.message && ( <p>{persistErrors.website.message}</p> )} </div> <input type="submit" /> </form> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
- 投稿日:2020-11-28T06:46:32+09:00
Reactを学んだ(1) ~StateとProps~
Reactの特徴
- Compositional
Declarative code
- いちいちいちいち処理の詳細まで書かない
- (e.g.)
const longNames = people.filter(name => name.length > 6)
- (c.f.) Imperative code ではない
Unidirectional Dataflowを使う
- dataflowはparent elementからchildrenの一方向に伝搬される
- 子は親の持つデータを参照できる。子は親のデータを変更しようと思ったらその旨を親に伝え、親がそれをデータの変更を行う
- Data-Binding ではない
- Data-Bindingを使うと、appのどこからでも保持していたデータを変更できる
Reactでelementを作る
React.createElement( <tag name>, <propsのhash(=オブジェクト)>, <content in tag> );
で、仮想DOMを初期化。
- e.g.
const element = React.createElement('div', null, 'My name is Tyler, and I love porcupines.');
- creteElementしただけでは、描画されない。これを、
ReactDOM.render(element), document.getElementById('root')
とかして、DOMに反映する。- propsにはhashを渡す。有効なキーは
className
とかhtmlFor
とか。
- DOM要素では、ある要素のclassにアクセスするには、
cName = elementNodeReference.className;
とするから。Reactでネストしたelementを作る方法
import React from 'react' import ReactDOM from 'react-dom' const people = [ { name: "John", id: 0 }, { name: "Paul", id: 1 } ] const element = React.createElement('ol', null, people.map((person) => ( // 子要素を配列にする時は、propsがそれぞれの要素でユニークなkeyを持っていないと怒られる React.createElement('li', {key: people.id}, person.name) )) ) ReactDOM.render( element, document.getElementById('root') )
- JSX(=syntax extention for JS)という拡張構文を使うことで、上記のコードもHTMLチックに、楽に書ける。
// 上記のcreateElementの箇所をJSXで書き換えると、 const element = <ol> {people.map((person) => ( <li key={person.id}>{person.name}</li> ))} </ol>Class ComponentとStateless Functional Component
作ったReact componentクラスをexportする方法
export default ListContacts初期化時にstateをプロパティーとしてComponentに持たせる方法
class User extends React.Component { constructor(props) { super(props); this.state = { username: 'Tyler' }; } }Babelでトランスコンパイルするなら以下でもいける。
class User extends React.Component { state = { username: 'Tyler' } }
- Avoid initializing that state with props! (e.g.
this.state = {user: props.user}
)stateの更新
this.state.hoge = new_hoge
という風に更新しても、re-renderingされない。- 引数の関数の返り値が元のstateにマージされる。
this.setState((prevState) => ( {count: prevState.count + 1} )
- なお、元のstateの値を使う必要がないなら、引数にハッシュを渡せばいい。
this.setState({ count: 2 })stateの型チェック
yarn add prop-types
でprop-typesというパッケージをインストールして以下のように使う。ListContacts.propTypes = { contacts: PropTypes.array.isRequired, onDeleteContact: PropTypes.func.isRequired, }
- Componentを定義したクラス内で定義するには、
static propTypes = { contacts: PropTypes.array.isRequired, onDeleteContact: PropTypes.func.isRequired, }form component
- フォームに打ち込んだ値をそのままフォームのvalueにするのではなく、一旦stateに収納して、それをsource of truthとして使う。
const { query } = this.state // state.queryをqueryに取り出して、 updateQuery(query) { this.setState(() => ({ query: query.trim() // valueがqueryというstateに収納される })) } render() {( <input className='search-contacts' type='text' placeholder='Search Contacts!' value={query} onChange={(event) => this.updateQuery(event.target.value)} // evnt.target.valueで入力値が取れる /> )}
- 以下にちょっとハマった。
<form onSubmit={(event) => { ////// onSubmit()はeventを引数にとる関数を渡す event.preventDefault(); ////// 書かなかいと、画面がrefreshされてstateが初期値に戻った... this.props.addItem(this.state.value) }}> </form>yarnコマンド
yarn start Starts the development server. yarn build Bundles the app into static files for production. yarn test Starts the test runner. yarn eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back!
- 投稿日:2020-11-28T06:46:32+09:00
Reactを学んだ(1)
Reactの特徴
- Compositional
Declarative code
- いちいちいちいち処理の詳細まで書かない
- (e.g.)
const longNames = people.filter(name => name.length > 6)
- (c.f.) Imperative code ではない
Unidirectional Dataflowを使う
- dataflowはparent elementからchildrenの一方向に伝搬される
- 子は親の持つデータを参照できる。子は親のデータを変更しようと思ったらその旨を親に伝え、親がそれをデータの変更を行う
- Data-Binding ではない
- Data-Bindingを使うと、appのどこからでも保持していたデータを変更できる
Reactでelementを作る
React.createElement( <tag name>, <propsのhash(=オブジェクト)>, <content in tag> );
で、仮想DOMを初期化。
- e.g.
const element = React.createElement('div', null, 'My name is Tyler, and I love porcupines.');
- creteElementしただけでは、描画されない。これを、
ReactDOM.render(element), document.getElementById('root')
とかして、DOMに反映する。- propsにはhashを渡す。有効なキーは
className
とかhtmlFor
とか。
- DOM要素では、ある要素のclassにアクセスするには、
cName = elementNodeReference.className;
とするから。Reactでネストしたelementを作る方法
import React from 'react' import ReactDOM from 'react-dom' const people = [ { name: "John", id: 0 }, { name: "Paul", id: 1 } ] const element = React.createElement('ol', null, people.map((person) => ( // 子要素を配列にする時は、propsがそれぞれの要素でユニークなkeyを持っていないと怒られる React.createElement('li', {key: people.id}, person.name) )) ) ReactDOM.render( element, document.getElementById('root') )
- JSX(=syntax extention for JS)という拡張構文を使うことで、上記のコードもHTMLチックに、楽に書ける。
// 上記のcreateElementの箇所をJSXで書き換えると、 const element = <ol> {people.map((person) => ( <li key={person.id}>{person.name}</li> ))} </ol>Class ComponentとStateless Functional Component
作ったReact componentクラスをexportする方法
export default ListContacts初期化時にstateをプロパティーとしてComponentに持たせる方法
class User extends React.Component { constructor(props) { super(props); this.state = { username: 'Tyler' }; } }Babelでトランスコンパイルするなら以下でもいける。
class User extends React.Component { state = { username: 'Tyler' } }
- Avoid initializing that state with props! (e.g.
this.state = {user: props.user}
)stateの更新
this.state.hoge = new_hoge
という風に更新しても、re-renderingされない。- 引数の関数の返り値が元のstateにマージされる。
this.setState((prevState) => ( {count: prevState.count + 1} )
- なお、元のstateの値を使う必要がないなら、引数にハッシュを渡せばいい。
this.setState({ count: 2 })stateの型チェック
yarn add prop-types
でprop-typesというパッケージをインストールして以下のように使う。ListContacts.propTypes = { contacts: PropTypes.array.isRequired, onDeleteContact: PropTypes.func.isRequired, }
- Componentを定義したクラス内で定義するには、
static propTypes = { contacts: PropTypes.array.isRequired, onDeleteContact: PropTypes.func.isRequired, }form component
- フォームに打ち込んだ値をそのままフォームのvalueにするのではなく、一旦stateに収納して、それをsource of truthとして使う。
const { query } = this.state // state.queryをqueryに取り出して、 updateQuery(query) { this.setState(() => ({ query: query.trim() // valueがqueryというstateに収納される })) } render() {( <input className='search-contacts' type='text' placeholder='Search Contacts!' value={query} onChange={(event) => this.updateQuery(event.target.value)} // evnt.target.valueで入力値が取れる /> )}
- 以下にちょっとハマった。
<form onSubmit={(event) => { ////// onSubmit()はeventを引数にとる関数を渡す event.preventDefault(); ////// 書かなかいと、画面がrefreshされてstateが初期値に戻った... this.props.addItem(this.state.value) }}> </form>yarnコマンド
yarn start Starts the development server. yarn build Bundles the app into static files for production. yarn test Starts the test runner. yarn eject Removes this tool and copies build dependencies, configuration files and scripts into the app directory. If you do this, you can’t go back!
- 投稿日:2020-11-28T04:28:04+09:00
React classコンポーネントから関数コンポーネントにModalを変換する
今回使用するModalはこちら。
クラスコンポーネントでの書き方
modal.jsximport React from 'react'; import Rodal from 'rodal'; // include styles import 'rodal/lib/rodal.css'; class App extends React.Component { constructor(props) { super(props); this.state = { visible: false }; } show() { this.setState({ visible: true }); } hide() { this.setState({ visible: false }); } render() { return ( <div> <button onClick={this.show.bind(this)}>show</button> <Rodal visible={this.state.visible} onClose={this.hide.bind(this)}> <div>Content</div> </Rodal> </div> ); } }関数コンポーネントでは、thisは使用できない。
パーツに分けて変換していこう!①Stateパーツ
【classComponent】this.state === 【functionComponent】 State
classComponent.jsxconstructor(props) { super(props); this.state = { visible: false }; }functinComponent.jsxconst [visible,setVisible] = useState(false);②handleパーツ
【classComponent】 this.setState === 【functionComponent】 handle
classComponent.jsxshow() { this.setState({ visible: true }); } hide() { this.setState({ visible: false }); }functinComponent.jsxconst modelHandleOpen = () => { setVisible(true); } const modelHandleClose = () => { setVisible(false); }③Contentsパーツ
classComponent.jsx<button onClick={this.show.bind(this)}>show</button> <Rodal visible={this.state.visible} onClose={this.hide.bind(this)}> <div>Content</div> </Rodal>functinComponent.jsx<button className="navItemPopUp" onClick={modelHandleOpen}>アバウト<br/> <span><InsertEmoticonIcon/></span></button> <Rodal visible={visible} onClose={modelHandleClose}> <div>プロフィールの内容</div> </Rodal>