- 投稿日:2020-11-22T22:56:28+09:00
【ReactNative・React Navigation】JWTを用いた認証と画面の出しわけの基本
認証で管理するStateの例
authSlice.tsimport { createSlice } from '@reduxjs/toolkit'; const authinInitialState = { isLoanching: true, isLoading: false, error: null, token: null, }; export const authSlice = createSlice({ name: 'login', initialState: authinInitialState, reducers: { loginStart: (state) => { state.isLoading = true; }, loginSuccess: (state, { payload }) => { state.isLoanching = false; state.isLoading = false; state.error = null; state.token = payload; }, loginFailure: (state, { payload }) => { state.isLoanching = false; state.isLoading = false; state.error = payload; state.token = null; }, logout: (state) => { state.isLoading = false; state.error = null; state.token = null; }, }, }); export const { loginStart, loginSuccess, loginFailure, logout } = authSlice.actions;JWTを端末に保存するためのAsyncStrage
https://react-native-async-storage.github.io/async-storage/docs/install
yarn add @react-native-async-storage/async-storageJWTの保存、読み込み、削除など一通り用意しておき、認証時の処理に差し込む。
services/deviceStrage.tsimport AsyncStorage from '@react-native-async-storage/async-storage'; const JWT_KEY = 'jwt_key'; export const deviceStorage = { async saveItem(key: string, value: string) { try { await AsyncStorage.setItem(key, value); } catch (error) { console.log('AsyncStorage Error: ' + error.message); } }, async saveJWT(token: string) { try { await AsyncStorage.setItem(JWT_KEY, token); } catch (error) { console.log('AsyncStorage Error: ' + error.message); } }, async loadJWT() { try { const value = await AsyncStorage.getItem(JWT_KEY); return value; } catch (error) { console.log('AsyncStorage Error: ' + error.message); return error; } }, async deleteJWT() { try { await AsyncStorage.removeItem(JWT_KEY); } catch (error) { console.log('AsyncStorage Error: ' + error.message); } }, };スプラッシュ画面がすぐに終了しないようにする
ライブラリを使用する
https://github.com/crazycodeboy/react-native-splash-screenスプラッシュ画面は、JWTの読み込みが完了したタイミングで明示的に終了させる。
yarn add react-native-splash-screenAndroid
ReactNative(0.63)以降は、自動でライブラリとリンクするので、特に設定は必要ない。
以下、onCreate
(アプリ起動時の処理)のみ追加する。これによって、明示的に指示しない限りスプラッシュスクリーンが終了しなくなる。
package com.myapp; import com.facebook.react.ReactActivity; 追加 ++ import org.devio.rn.splashscreen.SplashScreen; public class MainActivity extends ReactActivity { 追加 ++ @Override ++ protected void onCreate(Bundle savedInstanceState) { ++ SplashScreen.show(this); ++ super.onCreate(savedInstanceState); ++ } @Override protected String getMainComponentName() { return "myApp"; } }iOS
同じく起動時の処理に追加する。
これによって、明示的に指示しない限りスプラッシュスクリーンが終了しなくなる。
#import "AppDelegate.h" #import <React/RCTBundleURLProvider.h> #import <React/RCTRootView.h> 追加 ++ #import "RNSplashScreen.h" @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // ...other code [self.window makeKeyAndVisible]; 追加 ++ [RNSplashScreen show]; return YES; } @endSplashコンポーネントの作成
Splashコンポーネントといっても、画面には何も表示する必要はなく、
JWTの読み込みや、アプリ起動時に必要な処理を行う。また、処理が終了した際にスプラッシュを非表示にする。
SplashScreen.tsximport { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import SplashScreen from 'react-native-splash-screen'; import { deviceStorage } from 'src/services/deviceStrage'; export default function () { const dispatch = useDispatch(); useEffect(() => { deviceStorage .loadJWT() .then((jwt) => { dispatch(loginSuccess(jwt)); // isLoanchingをfalseにする }) .catch((error) => { dispatch(loginFailure(error)); // isLoanchingをfalseにする }) .finally(() => { SplashScreen.hide(); // }); }, [dispatch]); return null; }React Navigation の設定
https://reactnavigation.org/docs/auth-flow/
アプリ起動時、未ログイン(JWTなし)、ログイン後(JWTあり)の画面を出し分ける。
例はReduxでTokenを管理している場合。
navigation.tsxexport const RootNavigator = () => { const { isLoanching, token } = useSelector((state: RootState) => state.auth); if (isLoanching) return <SplashScreen />; return ( <NavigationContainer> <RootStack.Navigator headerMode="none"> {!token ? ( <> <RootStack.Screen name="SignIn" component={SignInScreen} /> <RootStack.Screen name="SignUp" component={SignUpScreen} /> </> ) : ( <> <RootStack.Screen name="AppBottomTab" component={AppBottomTabNavigator} /> <RootStack.Screen name="ChatRooms" component={ChatRoomScreen} /> </> )} </RootStack.Navigator> </NavigationContainer> ); };
- 投稿日:2020-11-22T16:39:22+09:00
テストツールjestのインストール〜実行の流れ
jestインストール〜テスト実行まで
参照先:https://github.com/facebook/jest
①jestをインストール
npm install --save-dev jest②基本設定ファイルの作成
1.node_modules/.binに対してinitで作成。
./node_modules/.bin/jest --init2.基本的にEnter連打でok
The following questions will help Jest to create a suitable configuration for your project ✔ Would you like to use Jest when running "test" script in "package.json"? … yes ✔ Would you like to use Typescript for the configuration file? … no ✔ Choose the test environment that will be used for testing › node ✔ Do you want Jest to add coverage reports? … no ✔ Which provider should be used to instrument code for coverage? › v8 ✔ Automatically clear mock calls and instances between every test? … no ✏️ Modified /Users/masahiro/Documents/react-router-test/project/package.json ? Configuration file created at /Users/masahiro/Documents/react-router-test/project/jest.config.js③babelのインストール
1.import,exportをテスト環境で使用する場合、babelのインストールが必要。
下記コマンドでインストールするnpm install --save-dev babel-jest @babel/core @babel/preset-env2.{}package.jsonの"devDependencies"に下記が追加されていればok
"devDependencies": { "@babel/core": "^7.12.7", "@babel/preset-env": "^7.12.7", "babel-jest": "^26.6.3", "jest": "^26.6.3" } }④基本ファイルの作成
1.babel.config.jsファイルを作成し、下記のコードを追加
module.exports = { presets: [['@babel/preset-env', {targets: {node: 'current'}}]], };⑤testフォルダの作成
1.
__test__
フォルダの作成し、適当なコードを入力import QuizFetcher from '../../../src/data_fetchers/QuizFetcher'; describe('QuizFetcherのテスト', () => { it('クラスチェック', () => { console.log('@@@@@@'); }); });⑥テスト実行
1.npm testを実行
npm test2.下記の表示が出ればok
PASS __tests__/src/data_fetchers/QuizFetcher.js QuizFetcherのテスト ✓ クラスチェック (11 ms) console.log @@@@@@ at Object.<anonymous> (__tests__/src/data_fetchers/QuizFetcher.js:5:13) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 0.775 s, estimated 1 s Ran all test suites.以上になります。
- 投稿日:2020-11-22T15:40:16+09:00
始めてwebRTCに挑戦した結果…爆散した(後編)
webRTCに挑戦した結果爆散した…
こちらの記事は前編の続きです!ぜひ前編を見てからこの記事を読んでやってください。
全編とにかくReactでカメラを起動させないと…
前回とりあえずシンプルなJavaScriptで書いたコードをまんまReactで書いたんですけど…
あれ〜カメラ動かんやんけ〜てなことになりましたね!あ、本日の記事は少しおまけをつけときました。
あの後親切なお方からアドバイスをもらいました…
どうやら前回の記事のときはJSXで作ったいわゆる仮想DOMを取得するときに
document.getElementById('video');
で取得しようとしていました。しかし、ここをdocument.querySelector('video');
とすることでうまくいきました。
コード全体は前回の記事を遡ってくださいとおもったのですが、一応それだとめんどいはアホと言われかねないので書いときます。Camera.jsclass Camera extends Component { render() { const media = document.getElementsByClassName('video'); navigator.mediaDevices.getUserMedia({vide:true; audio:false;}) .then (function(stream) { media.srcObject = stream; }) .catch (function(err){ console.error ('mediaDevices.getUserMedia() error:',err); return; }) return ( <div> <h1>Webcam</h1> /* なんとなくタイトルをつけてるだけです */ <video className="video" autoplay /> </div> ) }; }ちなみに今回はメディアをしているするときに
navigator.mediaDevices.getUserMedia()
でメディアを指定していますがここはnavigator.getUserMedia
でも動きます。
しかし、これはMDNでは非推奨の書き方になりますので今回は前者の書き方を使用しました。なんで非推奨なのかはよくわかってません…ごめんなさい…では今後この記事を読んだ人に悪影響を与えかねんので調べました。
メディア周りをいじるときnavigator.getUserMedia()
は動くブラウザと動かないブラウザが混在しておりブラウザが変わったときに挙動が異なってしまうということが起きます。これはnavigator.mediaDevices.getUserMedia()
を導入したことによってこれに対応するためにブラウザがいろいろ対応しているのですが、その際に今まで使用していたnavigator.getUserMedia()
はもういらん!といって非対応にしてしまったらしいです。よって開発者側は全てのブラウザに対応させるためにnavigator.mediaDevices.getUserMedia()
を用いることが推奨されてます。
参照:MDN mozillaへ〜便利な外部ライブラリ(react-webcam)かぁ〜
調べていくとどうやらReactさんでカメラを動かすにはReactWebCamという外部ライブラリを利用するのが一番楽らしいdesu!
というわけなのでとりあえずReactWebCamをプロジェクト内にインストールします。$ npm install react-webcamこれでReactWebCamを利用できるようになりました。
でもどうやって使うんやろ〜?react-webcamを使ってみよう!
先ほどプロジェクトにライブラリをインストールしたのでこれから実際に動かしてみます。
まぁ詳しい使い方はGitHubにあるので読みましょう!
するとどうやらいつも通りimportして使えばい良いとのことなので時をもど…
何はともあれ先程のコードは一旦全てコメントアウト!
最悪消しても良いです…Camera.jsimport React,{ Component } from 'react'; import Webcam from 'react-webcam'; class Camera extends Component { render(){ // cameraSize const videoConstraints = { width: 1280, height:720, }; // 使用メディアの指定 const capture = { facingMode: { exact: "environment" // リアカメラの利用、もしフロントカメラを使いたい場合はuserにする } }; return( <h1>WebcamApp</h1> <Webcam audio={false} videoConstraints={videoConstraints} video={capture} /> ) }; } export default Camera;今回はとりあえずカメラが起動できて、きちんとキャプチャしてくれれば良いのでaudioはfalseにしてあります。
もし、音声を扱いたい場合はaudioをtrueにしてナンジャカンジャをいじいじしてあげましょう!
ちなみに今回はキャプチャしたものを表示するためのサイズを指定していますが指定しなくてもよしなにサイズを設定して表示してくれるみたいです。
でも自分の望んだサイズで表示されるとは限らない(というか大半の場合は表示されないと思う)のできちんと表示サイズを指定しましょう。
何はともあれとりあえずこれを表示してみようか…
これをApp.jsにインポートして一つのページとしてルーティングさせましょう。おまけ
今後ページを少しづつ増やしていくのでreact-routerでルーティングさせていく準備もやってしまおう!
ちなみにルーティングの記事はまたどこかで書きますがとりあえず今回はプロジェクトの中で以下を実行しておいてください$ npm install react-router-domそして、App.jsを編集します。
App.jsimport React from 'react'; import { BrowserRouter as Router,Switch, Route } from 'react-router-dom'; // pageComponents import Home from './pages/Home'; import Camera from './pages/Camera'; function App() { return ( <div className = "App"> <Router> <Switch> <Route exact path = "/" component={Home} /> <Route path = "Camera" component={Camera} /> </Switch> </Router> </div> ); } export default App;そうしますととりあえずルーティングは終わり。
起動してみます。$ npm startこれを実行するとlocalhostのおそらく3000番ポートが開かれると思います。
もしすでにlocalhost3000番が使われていたら、別ポートで開きますか?というようなメッセージがターミナル上に出てきますのでyを押してエンターで続けてください。やった〜かめらがようやっと起動しました〜〜〜〜〜
カメラを起動させるのにどれだけ時間をかけるつもりだよ…さてこのあとは…
ここまででやっとスタートラインに立ちました…
ここからメディアをいじって何かしらサービスを作っていきたいと思います。
しかし、私非常に疲れてしまいましたのでその記事はまた次回にするとしましょう〜最後に
今回はライブラリを使わない方法と使う方法二つをご紹介しましたが、改めてライブラリというものの偉大さを切実に実感しましたね〜
だってぽちぽち、インポート!終了!ですからね!
サービスでどのような機能を作るかをよく考えてそれを実装できそうなライブラリがあるなら積極的に使っていきたいな〜と思ういい機会でした。
また何かアドバイス等ございましたらコメント欄にお願いします!余談?かな今回はクラスコンポーネントを利用していますがもちろんファンクショナルコンポーネントでもちゃんと動きます!動作確認済みです。
- 投稿日:2020-11-22T15:38:27+09:00
始めてwebRTCに挑戦した結果…爆散した(前編)
初めに
この記事はweb開発初心者を前提として書いております。(筆者の体験談)
「馬鹿かこいつ!こんな基礎中の基礎も知らずにwebやるとか頭おかしんじゃね〜の!出直してこい!」とか思わず生暖かい目で見守ってやってください。
そして、僕と同じ状況のあなた!心配しないで!あなたよりポンコツはいくらでもいます!
現に今目の前の記事を書いてる人も相当のポンコツです!webカメ使って楽しいことしたい!
とある大学の授業で何かしら作品作ってって言われたからとりあえず私未踏の地WebRTCに挑戦したろ!
今までカメラとか一度も使ったことないけど多分videoタグ当たりをJSで軽くいじるんやろ〜
余裕余裕…じゃない!ん?
何をどうすればいいかよくわからん!
というわけでWebRTC初心者の私が勉強しながらも頑張った証を残そうと思います。
私と同じでWebRTCやってみたいけどよくわからないという人の助けになればいいな〜ひとまずWebRTCを行うためには…?
まぁweb開発で困ったことあったらみんなご存知のMDNを見れば大体解決できるのですが、私はそもそも何をしていいかわからないからどのドキュメントを読めばいいかわからない(何がわからないのかわからない!)という状態だったのでとりあえずGitHubに溢れかえっているWebRTCのコードを読みあさってみることにしました。すると!どうやらMediaツール周りのリクエストや制約云々をいじればいけるということがわかってきました!
Mediaツール周りのリクエスト
まぁ考えてみればwebカメラいじるんだからそりゃそこらへんいじるわな!
というわけでここまででわかったことはNavigator
というObjectでgetUserMedia
にて使用するメディアツールの情報を呼び出すことから始めましょう!DocumentObjectModel
DocumentObjectModel、通称DOMですね!
これは簡単に言えばJavaScriptからHTMLのhタグやらpタグやらに接続しいじくり回すためのオブジェクトモデルです。
より正確に知りたいという方ははいでましたMDNさんを参照してください!
そしてこのDOMというものはツリー構造をようしており、このツリーの最小の単位をNodeと言います。
このツリーのNodeに対して干渉していくことでイベントを起こすことができます。
また、このイベントというものはNodeに対してイベントハンドラーというものを割り当てて起こすことができます。
なぜ今このDOMが出てきたのか…
それはこのあとDOMを動かしながらでないと私たち(ユーザ)がみている画面をJavaScriptから動かすことができないからです。ちなみに今回はWebRTCということなのでwebカメラを動かします。そのための表示フィールドとでも言っておきましょうかそれをこのDOMを用いて作成し、表示していきましょう。ちょこっとWebcamを起動してみましょ
ちょっとここまでコードが出てこなかったので、「なんだこの記事ふざけやがって〜!」と思った方ここからコードを少し書きますよ〜
それでは実際に作っていきます。
とりあえずシンプルなJavaScriptでやってみよう!
webでカメラを利用して何かをするときは基本的にはhtmlのvideoタグをいじいじしながら開発をしていくことが多いみたいです。
ちなみに、videoタグはこれだけではなくwebに動画を埋め込む際にも利用されたりと、メディアをいじるときにはよく出てくるものらしい…
というわけで今回はvideoタグをHTMLで用意し、JavaScriptにvideoタグの要素を取得しDOMを操作していこうと思います。sample.html<body> <h1>Sample Camera App</h1> <video id = "video" autoplay></video> <script type = "javascript"> const media = document.getElementById('video'); // これは皆さんご存知HTMLのvideoタグのidを指定してDOM要素を取得する手順です。 navigator.mediaDevices.getUserMedia({video:true, audio: false}) // 今回は写れば良しということでマイクはOFFで行きます .then (function(stream) { media.srcObject = stream; }) .catch (function(err){ console.error('mediaDevices.getUserMedia() error:',err); return; }) </script> </body>これでうまくいくはずです…!
うまくいった!
まぁこないな感じでやれますわ〜しかし今のご時世フレームワークを用いてユーザ体験の向上やサーバサイドレンダリングやら静的サイトジェネレーターやらを行うのが当たり前と言われておりますが(筆者の周りでわ…)応用編として今回は私くしが実際にお勉強中のReactでやってみようと思います。
そしてあわよくば今現在大学の課題で作っているカメラに写っている人の数を検出して人数を表示するというところにまで持っていきたい!
とまぁ自分語りはここら辺にしてやっていきましょう〜
これは完全に僕の好みというかReact使う人たちは大体そうだと思いますけど、今回はCameraコンポーネントを作成しApp.jsにimportして作ろうと思います。Camera.jsimport React,{ Component } from 'react'; class Camera extends Component { render() { const media = document.getElementsByClassName('video'); navigator.mediaDevices.getUserMedia({vide:true; audio:false;}) .then (function(stream) { media.srcObject = stream; }) .catch (function(err){ console.error ('mediaDevices.getUserMedia() error:',err); return; }) return ( <div> <h1>Webcam</h1> /* なんとなくタイトルをつけてるだけです */ <video className="video" autoplay /> </div> ) }; } export default Camera;とりあえずこれでカメラを起動して画面にリアルタイムで表示してくれるはず…
なんですが、おや?
webカメラは起動したけど画面に表示してくれない…
何かエラーは出ているのかな〜
あれ⁉︎
エラーもない!
どうしよう!
Reactわけわから〜ん!
いやJavaScriptなのか?
なんだよこれ!
もうWebやめる![というわけで次回へ…]
- 投稿日:2020-11-22T11:33:10+09:00
[初心者向け]Reactの基礎を徹底解説してみた
はじめに
今回はReactの使い方についてまとめていきます。
環境構築については、以下の記事を参考にしてください。
コンポーネントについて
Reactはコンポーネント(部品)という単位で実装していきます。
Webサイトやアプリは、ボタンやタブなどのコンポーネントの集まりと考えることができます。
例えば、このQiitaだと以下のようなコンポーネントが存在すると考えることができます(適当です)。
- ボタンコンポーネント
- ナビゲーションコンポーネント
つまり、コンポーネントとは
見た目
と機能
を組み合わせたものだと考えることができます。このように、コンポーネントという単位でプログラムを管理すると、再利用することができたり、変更してもバグが起きにくいという利点がありますよね。
それでは実際にコンポーネントの種類を見ていきましょう。
今回は、
npx create-react-app
というコマンドを使ってReactアプリを作成し、そのアプリに各々のコードを記述して動きを見ていきます。Functional コンポーネントについて
Functionalコンポーネントについて見ていきましょう。
create-react-app
を行うと、以下のようなファイル構成になります。この中の
App.jsx
に以下のようにコードを記述してください。App.jsximport React from 'react' const App = (props) => { return ( <div> <h1>Hello World</h1> </div> ) } export default Appこのように、Javascriptの関数のようにReactのコンポーネントを作成して、外部にエクスポートしています。
Functionalコンポーネントは、アロー関数またはfunction()で定義した関数のどちらを用いて記述します。
関数内の
return
の後に、JSX
と呼ばれるHTMLのようなものを記述します。ここでは使用していませんが、引数で渡しているpropsに、親コンポーネントから呼び出す際に変数を格納されることができます。
ちなみに、このエクスポートされたApp関数は
index.js
で呼び出されます。index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import * as serviceWorker from './serviceWorker'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); serviceWorker.unregister();上記のように、
index.js
はApp.jsx
ファイルからエクスポートされたAppコンポーネント
を読み込み、それをReactDOM
に渡すことでrender(描画)しています。
yarn start
を行い、実行結果を見てみましょう。このように、Reactアプリを起動させると
index.js
ファイルが実行され、ReactDOMがrender(描画)されていることが分かります。クラスコンポーネントについて
クラスコンポーネントは以下のように作成します。
App.jsximport React from 'react'; class App extends React.Component { constructor(props) { super(props); this.state = { open: 'pocomaru' } } render() { return ( <div> <h1>Hello World</h1> <h1>{this.state.id}</h1> </div> ); } } export default AppJavascriptのクラスを作成する際に、
React.Component
というクラスを継承しています。その後に、
constructor
により、初期値のstate
をセットします。コンストラクタとは、オブジェクト作成時に実行されるメソッドのことでしたね。引数に渡している
props
は、エクスポートしたAppコンポーネントを呼び出すときに渡した値が代入される箱のようなものです。
super(props)
とすることで、親クラスを継承しています。その後に、
this.state
にオブジェクトを代入します。このthisは、このクラス自身のことです。今、Appクラスのopen stateがtrueに設定されています。このstateに設定されている値を用いる場合、
this.state.id
とすればアクセスできます。return以降はJSXと呼ばれる部分になります。この部分では、通常の書き方ではJavascriptコードを使用することができません。
そのため、
this.state.id
の値にアクセスするためには、中括弧{}で囲う必要があります。この状態でReactアプリを起動すると以下のようになります。
Functionalコンポーネントとクラスコンポーネントの違い
最初にFunctionalコンポーネントの特徴から解説します。以下の特徴を持ちます。
- stateを持たない
- propsを引数に持つ
順番に解説します。
stateとは
stateとは、その名の通り状態のことです。
例えば、扉コンポーネントというものを考えたとき、扉が
開いているのか
や閉じているのか
というコンポーネントの状態を示すのがstateです。ボタンコンポーネントでは、
押されたことがある
のか押されたことがないのか
というのもstateで管理できますし、何回押されたのか
などもstateで管理できます。Functionalコンポーネントはクラスコンポーネントと違って、このstateを持つことができません。
React-hooksなどを用いればstateを持たせることは可能ですが、ここでは割愛させて頂きます。
propsを引数に持つとは
Functionalコンポーネントは、propsを引数に持つことができます。ちなみにクラスコンポーネントもコンストラクタにpropsを渡すので、Functionalコンポーネントだけの機能ではありませんが。
propsとは、親コンポーネントから渡す値を格納する
箱
のようなものです。具体例で確認してみましょう。
App.jsx
という親コンポーネントからName.jsx
というコンポーネントを呼び出し、propsとしてnameを渡すことを考えてみましょう。以下のようになります。App.jsximport React from 'react'; import Name from './Name'; const App = (props) => { return( <div> <Name name='pocomaru'/> </div> ) } export default Appこのファイルでは、
Name.jsx
の中のName
関数コンポーネントをインポートしています。そのインポートした関数のname属性に
pocomaru
という値を渡しています。この渡した値には、
props.name
とすることでアクセスできます。Name.jsxの中身を確認してみましょう。Name.jsximport React from 'react'; const Name = (props) => { return ( <h1>{props.name}</h1> ) } export default Name結果は以下のようになります。
それでは次にクラスコンポーネントについて見ていきましょう。
以下の特徴があります。
- ライフサイクルをもつ
- stateを持つ
それぞれについて解説していきます。
ライフサイクルをもつ
クラスコンポーネントはライフサイクルを持ちます。
ライフサイクルとは、コンポーネントが生まれて、成長し、死ぬまでの循環のことです。
こんなことを言われても意味がわからないと思うので、具体的に説明します。
ボタンコンポーネントのライフサイクルについて考えてみましょう。
ある日、ボタンコンポーネントが誕生しました。マウントされるとも言いますね。
Reactにおいては、この
ボタンコンポーネントが誕生した瞬間
にconstructor
が走ります。stateに値を代入する処理などですね。そのようにしてconstructorが走った後は、renderが走ります。これによりReactが描画され、画面で見ることができます。
この後に、
componentDidMount
という関数が走ります。このcomponentDidMount`に何か操作をかいておけば、コンポーネントがマウントされた後に一度だけ使うことができます。ここまでが生まれたときの話です。次は、コンポーネントが成長していくときの話をしましょう。
コンポーネントが誕生した後は、度重なるrenderがユーザーの操作により発生します。
具体的にはrenderは、ユーザーの操作などによりstateが変更されるたびに呼び出されます。
これがライフサイクルの成長の過程であり、renderされる毎に
componentDitUpdate
という関数が実行されます。最後に、コンポーネントが死ぬときの話をしましょう。
コンポーネントが死ぬ直前、つまりはアンマウントされる直前に
componentWillUnmount
という関数が実行されます。このように、ライフサイクルに合わせて
componentDidMount
などのメソッドを実行することができ、これらはクラスコンポーネントでしか使用することができません。React-hooksを使えば、Functionalコンポーネントでも用いることができますが。それでは次に、ライフサイクルについて解説していきます。
ライフサイクルの種類
ライフサイクルには、以下の三種類があります。
Mounting
Updating
UnMounting
各々について解説していきましょう
Mountingとは
Mountingとは、コンポーネントが生まれるときの期間のことです。
このMountingのときに使われるライフサイクルメソッドは、マウントが行われる直前に実行される
componentWillMount
とマウントが実装された直後に実行されるcomponentDidMount
です。ちなみにマウントが行われる直前に実行される
componentWillMount
は非推奨なので使わないほうがよいです。Updatingとは
Updatingとは、コンポーネントが変更される期間のことです。
クラスコンポーネントはライフサイクルとstateを持つことができます。
つまり、コンポーネントが変更されるとはこのstateが変更されることを指します。
例えば、ボタンコンポーネントを実装するとします。このボタンコンポーネントの元々の色は赤色で、ボタンをクリックすると青色になるという機能をもたせたいとします。
どのように実装するかというと、このボタンコンポーネントにstateを持たせて管理することが考えられます(例えばcolorコンポーネントを持たせてクリックにより変更させるなど)。
Reactにおける重要な点の一つは、このstateが変更されたときにrenderが走るということです。つまり、stateが変更されるたびに画面の一部分を再描画していきます。
なぜこのような事になっているのかというと、stateの変更を画面に反映させるためです。例えば先ほどの例でボタンコンポーネントをクリックして、colorというstateを赤色から青色に変更させたとします。しかし、例えstateが変更されたとしても、render(描画)されなければ画面に反映されません。そのため、Reactではstateが変更されるたびにrenderが呼ばれるようになっています。
この
stateが変更されてrenderされる
という時がUpdatingの期間であり、その前後に対応したライフサイクルメソッドが走ります。Updatingにおけるライフサイクルメソッドは、以下の4つが考えられます。
componentWillReactiveProps マウントされたコンポーネントが新しいpropsを受け取る前
shouldComponentUpdate 新しいpropsやstateを受け取った時
componentWillUpdate 新しいpropsやstateを受け取った後、レンダリングする直前
componentDidUpdate 更新が行われた直後
Unmountingとは
コンポーネントが破棄される期間のことをUnMountingの期間といいます。
componentWillUnmount
というライフサイクルメソッドが、コンポーネントが破棄される直前に呼び出されます。それでは実際に、これらのライフサイクルメソッドを実装してみましょう。
componentDidmountの実装
componentDidmountは、次の用途で用いられます。
- Ajaxを使ったデータフェッチを行う(初回)
- DOMに対する処理を行う(初回)
- タイマーをリセットする
- イベントリスナのセット
今回は、イベントリスナのセットを行っていきましょう。
LikeButtonコンポーネントを作成して、クリックするといいね数が増えていくという機能を実装します。
以下のように、App.jsxとLikeButton.jsxを実装しましょう。
App.jsximport React from 'react'; import LikeButton from './LikeButton'; class App extends React.Component { constructor(props) { super(props); this.state = { count: 0 } } componentDidMount() { document.getElementById("counter").addEventListener('click', () => this.setState({count: this.state.count + 1})) } render() { return ( <div> <LikeButton count={this.state.count} /> </div > ) } } export default AppLikeButton.jsximport React from 'react'; const LikeButton = (props) => { return ( <div> <button id="counter">いいね数: {props.count}</button> </div> ) } export default LikeButton結果は以下のようになります。
それではコードの解説をしていきます。
constructor(props) { super(props); this.state = { count: 0 }この部分で、Appコンポーネントのstateとしてcountを持たせています。
このstateをLikeButtonコンポーネントに渡して、その中のButtonをクリックするたびに、このstateが更新されていけば良いわけですね。
今回は、
componentDidMount
を用いてイベントリスナーを設定しています。イベントリスナーとは、イベント(文字入力やボタンクリックなど)が行われたことを検知して発動する関数のことです。これにより、コンポーネントが誕生したときにイベントリスナーをコンポーネントに設定することができます。今回は、クリックというイベントが発生したときに、
() => this.setState({count: this.state.count + 1}
という関数が実行されるようになりました。クラスコンポーネントのstateを変更するときは、このように
setStateメソッド
を用いて、そのメソッド内で関数またはオブジェクトを指定することにより、stateを変更することができます。クラスコンポーネント内でstateにアクセスするときにはthis.state.***
としてアクセスします。ちなみに、このsetStateメソッド内で関数を渡すかオブジェクトを渡すかの違いによって、挙動が異なります。詳しくは以下の記事を参考にしてください。React の setState() の引数が関数の場合とオブジェクトの場合の違いについて整理する
また、以下のコードでLikeButtonコンポーネントを呼び出し、propsとして
this.state.count
を渡しています。<LikeButton count={this.state.count} />このようにして渡したstateを、LikeButtonコンポーネントでは
props.count
という形で利用しています。<button id="counter">いいね数: {props.count}</button>このbuttonタグのidに
counter
を指定しています。このタグは、親コンポーネントであるApp.jsxからも指定して呼び出すことができます。ここまでで、componentDidMountを用いてコンポーネントが誕生した直後にイベントリスナーを設定することができました。
補足 別の方法で同じ機能を実装
また、今回の機能はApp.jsxからpropsとしてcountを更新する関数を渡し、それを子供のコンポーネントのボタンタグの
onClick
に指定することでも実装できます。App.jsximport React from 'react'; import LikeButton from './LikeButton'; class App extends React.Component { constructor(props) { super(props); this.state = { count: 0 } } UpdateCounter = () => { this.setState({ count: this.state.count + 1 }) } render() { return ( <div> <LikeButton count={this.state.count} UpdateCounter={this.UpdateCounter} /> </div > ) } } export default AppLikeButton.jsximport React from 'react'; const LikeButton = (props) => { return ( <div> <button onClick={props.UpdateCounter}>いいね数: {props.count}</button> </div> ) } export default LikeButtoncomponentDidUpdateの実装
それでは次にstateが更新されてrenderが走った後に呼び出されるcomponentDidUpdateを実装していきましょう。
このライフサイクルメソッドは、
stateがある閾値を超えたときに別の動作をするようにしたい
ときなどに実装すると便利ですね。今回は、いいね数が5という閾値を超えた際に、いいね数を0にするという機能を実装していきます。
componentDidUpdateはstateが更新され、それによりrenderが走った後に呼び出されます
つまり、その
更新されたstate
に対して更新されるたびにif文やswich文で比較を行い、指定した値になったときに指定した関数を実行する、といった処理を実装していきます今回は
this.state.count
の値をcomponentDidUpdateの中で5と比較し、5以上になったときにthis.state.count
の値をsetState
メソッドを用いて0にするという機能を実装していきます。以下のコードです。
App.jsximport React from 'react'; import LikeButton from './LikeButton'; class App extends React.Component { constructor(props) { super(props); this.state = { count: 0 } } componentDidMount() { document.getElementById("counter").addEventListener('click', () => this.setState({count: this.state.count + 1})) } componentDidUpdate(){ if (this.state.count >= 5) { this.setState({ count: 0 }) } } render() { return ( <div> <LikeButton count={this.state.count} /> </div > ) } } export default AppLikeButton.jsximport React from 'react'; const LikeButton = (props) => { return ( <div> <button id="counter">いいね数: {props.count}</button> </div> ) } export default LikeButton以前の状態から追記したコードは以下だけです。
componentDidUpdate(){ if (this.state.count >= 5) { this.setState({ count: 0 }) } }このコードにより、stateの値が更新されるたびに
this.state.count
が5以上かどうかを確認することになります。実行結果は以下のようになります。
componentWillUnmountの実装
このメソッドは主に次の機能を実装したいときに使います。
- タイマーを解除する
- イベントリスナーを解除する
- 非同期処理を中止する
今回は、イベントリスナーを解除してメモリを開放する機能を実装していきましょう。
以下のコードです。
App.jsximport React from 'react'; import LikeButton from './LikeButton'; class App extends React.Component { constructor(props) { super(props); this.state = { count: 0 } } componentDidMount() { document.getElementById("counter").addEventListener('click', () => this.setState({count: this.state.count + 1})) } componentDidUpdate(){ if (this.state.count >= 5) { this.setState({ count: 0 }) } } componentWillUnmount() { document.getElementById('counter').removeEventListener('click', () => this.setState({count: this.state.count + 1})) } render() { return ( <div> <LikeButton count={this.state.count} /> </div > ) } } export default AppLikeButton.jsximport React from 'react'; const LikeButton = (props) => { return ( <div> <button id="counter">いいね数: {props.count}</button> </div> ) } export default LikeButtonこれでコンポーネントがアンマウントされたときにイベントリスナーを削除して、メモリを開放することができるようになりました。
追記したのは以下の部分です。
componentWillUnmount() { document.getElementById('counter').removeEventListener('click', () => this.setState({count: this.state.count + 1})) }このコードにより、イベントリスナーを削除することができています。
それでは次に、ReactのHooksについてまとめていきます。
Hookとは
Hookとは、端的に言えばクラスコンポーネントの機能をFunctionalコンポーネントでも使えるようにするためのものです。
具体的には、今まではクラスコンポーネントでしか持つことができなかったstateやライフサイクルをFunctionalコンポーネントで利用できるようになります。
今までReactの様々な機能にアクセスできるクラスコンポーネントと違い、一切のアクセスが許可されていなかったFunctionalコンポーネントをReactの機能へ接続(Hooks into)するという意味で、Hooksと呼ぶらしいです。
なぜHookを使うのか
察しの良い皆さんならこう思ったはずです。
「stateやライフサイクルメソッドが使いたいならクラスメソッド使えば良くね?」
確かに一理あります。
しかし、クラスコンポーネントには以下で示すような問題がありました。
- 処理が散らばりやすい
- thisを使いたくない
- 複数のライフサイクルメソッドに処理がまたがるのが嫌だ
- そもそもクラスコンポーネントの構文が複雑
- stateの扱い方が複雑
この中のいくつかの問題点を、さらに深く解説していきます。
処理が散らばりやすい
Reactを書いたことがある皆さんなら共感頂けると思いますが、クラスコンポーネントはメソッドをいくつも実装できるが故に処理が散り散りになりやすいんですよね。
子供のコンポーネントで行われる処理を、親のクラスコンポーネントのライフサイクルに定義して使いたくなりますよね。
具体例で話すなら、例えば
人間コンポーネント
を定義して、それに子供としてハサミコンポーネント
を渡したとします。このハサミコンポーネントの前に
紙が存在するとき
に、ハサミコンポーネントの切断
という機能を実装したいとします。人間コンポーネントをクラスコンポーネント、ハサミコンポーネントをFunctionalコンポーネントだとすると、
紙
はstateを持つ人間コンポーネントで管理することになりますし、紙の状態に応じた処理(ここでは切断する)もライフサイクルを持つ人間コンポーネントの中で書くことになりそうです。子供のコンポーネントがハサミコンポーネントのみならまだしも、他の様々なコンポーネントのstateやライフサイクルも人間コンポーネントで管理することになると、非常に煩雑になりそうですよね。
そもそも、
切断する
という処理はハサミコンポーネントの中の処理であるので、当然の思考としてハサミコンポーネントの中で管理するのが良さそうです。また、
紙
というstateも、紙の状態に応じたライフサイクルを用いた処理も、人間コンポーネントではなくハサミコンポーネントで管理するべきですよね。ここでハサミコンポーネントをクラスコンポーネントにしてしまっても良いのですが、それだと根源的な解決になりません。
そもそも、
ひとつの機能はひとつの場所に
という原則を守って書くことを強制してしまった方が良さそうです。そこで導入されたのがHookであり、Functionalコンポーネント自体にstateやライフサイクルを管理させるとうものです。
thisを使いたくない
Javascriptにおけるthisは、他の言語におけるthisとは違う挙動をします。
詳しくは以下の記事を御覧ください。
JavaScript の this を理解する多分一番分かりやすい説明
つまり、簡単に書くと
thisの使い方めっちゃむずい
ということです。こんなものはトラブルの元です。
できるだけ使わないようにしようという動きも多く、そのためクラスコンポーネントはできるだけ使いたくないと思う人も多いようです。
複数のライフサイクルメソッドに処理がまたがるのが嫌だ
クラスコンポーネントのライフサイクルメソッドは、時間の流れで処理を分割します。
そうすると、同じような機能が複数のライフサイクルメソッドに跨ってしまうことがありますよね。
そのように記述するよりも、
同じ機能は同じ場所
と言う方が分かりやすいと考える人も多いようです。ちなみに私もそう思います。React-Hooksの実装
それでは実際いHooksを実装してみましょう。
Hooksでstateの管理
まず、Hooksでstateの管理をしていきます。
HooksはFunctionalコンポーネントにおけるstateの管理やライフサイクルメソッドの実装に用いるんでしたね。
Hooksでstateを用いるときは、
useState
というメソッドを使います。実際にコードで確認してみましょう。
今回は、ScissorsコンポーネントというFunctionalコンポーネントを作成して、それを親コンポーネントであるAppコンポーネントから呼び出します。
そのScissorsコンポーネントに
cutting
とうstateを持たせて、ボタンを押すことで変化させるという機能を実装します。以下がコードです。
App.jsximport React from 'react'; import Scissors from './Scissors' const App = () => { return( <div> <Scissors /> </div> ) } export default AppこれはシンプルにScissorsコンポーネントを呼び出しているだけですね。以下がそのScissorsコンポーネントです。
Scissors.jsximport React, { useState } from 'react'; const Scissors = () => { const [cutting, changeCutting] = useState("NoCutting"); return( <div> <h1>{cutting}</h1> <button onClick={() => changeCutting("YesCutting!")}>change </button> </div> ) } export default ScissorsそれではHooksの部分を解説していきます。
以下の部分でuseStateをインポートしています。
import React, { useState } from 'react';これでインポートすることができました。
次の部分が、useStateを使う上で肝となる部分です。以下のコードです。
const [cutting, changeCutting] = useState("NoCutting");それでは解説します。
cutting
がstateであり、changeCutting
がstateを変更させる関数です。ちなみに、このchangeCutting
に引数を渡して実行すれば、cutting
の値がその引数に渡した値に変化します。つまり、このuseState関数は、
state
とstateを変更させる関数
を同時に定義していると考えることができますね。クラスコンポーネントでは、constructorの部分で
this.state = {}
という感じにstateを定義し、setState
を用いてそのstateを変更していたと思います。それでは、
useState
の引数の部分は何を意味するのでしょうか。正解は、
stateの初期値
になります。つまり、上のコードは
cutting
というstateを作成し、その初期値にNoCutting
を渡し、それと同時にそのstateを変更させる関数であるchangeCutting
を作成したことになります。またこのchangeCutting
は、実行したときにcutting
の値を引数に指定した値に変更させる関数になります。以下のコードで、ボタンをクリックしたときにこの
changeCutting
を実行しています。<button onClick={() => changeCutting("YesCutting!")}>change </button>buttonタグのonClickにアロー関数として
changeCutting
を実行したものを渡しています。onClickはボタンをクリックしたときに引数として渡された関数を実行するという機能なので、このように関数そのものを渡します。
実際にこのReactアプリの挙動を見てみましょう。
ここまでで、Hooksによるstate管理は終わりです。
Hooksによるライフサイクルの管理
Hooksによるライフサイクルメソッドの代替として、
useEffect
メソッドがあります。以下の三つのメソッドを代替することができます。
- coponentDidMount()
- componentDidUpdate()
- componentWillUnmount()
軽く解説します。
componentDidMountについて
componentDidMountはマウントが行われた直後に実行されるメソッドです。Ajaxを使ったデータフェッチ(初回)やイベントリスナのセットなどを行います。
componentDidUpdateについて
componentDidUpdateはstateの更新が行われた直後に行われるメソッドです。具体的には、stateが更新され、その後にrenderが走った後に呼び出されます。
stateがある閾値を超えたときに別の動作をするようにしたい
ときなどに使うと便利ですね。componentWillUnmountについて
componentWillUnmountは、コンポーネントが破棄される直前に呼び出されます。
具体的には、イベントリスナーを解除してメモリを開放するときなどに利用されます。
それでは、実際に
useEffect
メソッドを用いてこれらのライフサイクルメソッドを実装してみましょう。useEffectメソッドの実装
まず、最初にuseEffectメソッドの使い方解説を行います。
以下の記事を参考にしました。
第一引数のみ
第二引数を空っぽにすると、第一引数に指定したコールバック関数がrender毎に実行されます。
componentDidUpdate
と同じタイミングで呼ばれることになりますね。useEffect(() => { console.log("render") });しかし、これは公式のリファレンスで推奨されていない書き方なので、使わないようにしましょう。
第二引数を与える
空の配列が渡されたとき
useEffect(() => { console.log('mounting') }, [])第二引数に空の配列が渡された場合、マウント時のみ、第一引数に渡されたコールバック関数を実行します。
Reactはrenderが呼ばれるたびに
第二引数の値がrender前とrender後に変化したかどうか
を確認します。その時、変化していた場合にのみ第一引数に設定したコールバック関数を実行します。
今回は、第二引数に空の配列を指定しているため、マウント時のみ第一引数のコールバック関数が走るようになります。
componentDidMount
と同じタイミングになります。第二引数の配列に値が渡された場合
第二引数に値の配列が渡された場合、
renderの前後で値が変化した場合
に第一引数のコールバック関数が呼ばれます。const [Boolean, changeBoolean] = useState(true) useEffect(() => { console.log('changed') }, [Boolean])この場合、第二引数に渡した
Boolean
の値が変更されるたびに第一引数に渡したコールバック関数が呼ばれるようになります。マウント時とアンマウント時のみ実行
useEffectメソッドにおいては、第一引数のコールバック関数内においてreturnを行うと、そのreturnの中の処理がアンマウント時に実行されるようになります。
以下のコードです。
useEffect(() => { console.log("render") return () => { console.log("Unmounting!") } }, []}第二引数に空の配列を渡しているので、render毎に実行されることはありません。
つまり、
console.log("render")
の部分がマウント時のみ実行されるようになります。また、return後の関数が、アンマウント時に実行されるようになります。
終わりに
今回の記事はここまでになります。
お疲れさまでした。
参考
- 投稿日:2020-11-22T11:13:52+09:00
AWS Amplifyで環境変数を設定し、Reactアプリで読み込む
GitHubなどからWebアプリのCI/CDができるAWS Amplifyですが、
今回は環境変数の設定方法と、AmplifyでホストしているReactアプリから読み込む方法を備忘録として残しておきます。
(あと以外と日本語のドキュメントがなかった)ReactなどのWebアプリでは.envファイルなどに環境変数を定義しておいて、
process.env.{任意の名前}
で読み込むことがあると思いますが、
認証情報などを記載している場合はソース管理には載せられませんし、Amplifyで環境変数を設定して読み込む必要があります。ちなみにAmplifyではバックエンドの構築も可能ですが、環境変数の利用はフロントエンドのみのアプリでもできます。
ちょっとしたフロントエンドだけのアプリ作って環境変数の読み込みだけしたい場合、Amplify CLIを使ったバックエンドの構築などはせずに環境変数の設定、利用が可能です。環境変数の設定
環境変数の登録
Amplifyで環境変数を定義するにはまずAWS ConsoleでAmplifyのページに飛び、
サイドメニューから「環境変数」を選択します。
環境変数のメニューが開くので「変数の管理」から変数の登録・編集ができます。
今回はReactアプリで読み込むのでREACT_APP_
を変数名にプレフィックスとしてつけておきます。
また、複数ブランチをホストしている場合には、ブランチごとに変数を設定することが可能です。
ビルドの設定
続いてメニューから「ビルドの設定」を選択し、以下の画面を開きます。
ここではデプロイ時のビルドの設定をyaml形式で定義することができるので、ここで環境変数のエクスポートをします。
buildのcommandsに↓こんな感じで書いておきます。
出力するパスはアプリに応じて変更します。今回はルートに書きだしています。echo "REACT_APP_HOGE=$REACT_APP_HOGE" >> .envアプリからの読み込み
あとはアプリから
process.env.REACT_APP_HOGEで読み込めば完了です。
参考
- 投稿日:2020-11-22T08:55:43+09:00
Next.jsでログインフラグによるリダイレクト機能を実装してみた
前書き
Next.jsで自前のログイン機能を作成しようとして情報集めに苦労したのでその備忘録。
前提として、認証処理一式を実装できるライブラリは色々あります。但しSNSとの連携を前提としたモノしかなかった。(僕調べ)
- 個人学習向けの簡単なログイン機能を実装したい
- 自前の画面遷移時のリダイレクト判定処理を実装したい
みたいな方々には、もし良ければこんな実装もあるよっていう参考にしていただければ幸いです。
使用技術
- Next.js(メイン)
- axios:お馴染み、API叩く用に使用
- nookies:Cookie制御用の外部ライブラリ
- バックエンドAPI(なんらかのログイン成功フラグが返せればなんでも良い、自分はSpring Bootを使いました)
機能要件
大要件
- ログイン済みか否かで遷移させる画面を制御する
小要件
- ログイン済みフラグの取得のために、ログイン画面からIDとパスワードを入力してログイン用バックエンドAPIを叩く(固定値設定して等価判定しているだけの簡単なモノ)
- ログイン済みフラグをクライアント側で保持(Cookieへ)
- Cookieにログイン済みフラグがなければ、遷移時にログイン画面へリダイレクトさせる
実装
ポイント
_app.tsx
(Jsなら_app.jsx
) で画面制御処理を実装→全画面のコンポーネントで呼ばれるためコード
- ログイン画面コンポーネント
import Head from 'next/head'; import axios from 'axios'; import { useState } from 'react'; import { Container, Button, Form, Image } from 'react-bootstrap'; import { setCookie } from 'nookies'; import { useRouter } from 'next/router'; interface ILogin { userName: string; password: string; } const initialPayload: ILogin = { userName: '', password: '', }; const Login = () => { const router = useRouter(); const [payload, setPayload] = useState<ILogin>(initialPayload); const handleChange = (e) => { setPayload({ ...payload, [e.target.name]: e.target.value, }); }; const onClickLogin = () => { axios .post('/api/login', payload) .then((res) => { // ログインフラグをクッキーへ、「auth」というキーで登録 setCookie(null, 'auth', 'true', { maxAge: 30 * 24 * 60 * 60, // お好きな期限を path: '/', }); router.push('/'); }) .catch((e) => { console.log('認証エラー'); }); }; return ( <Container> <Head> <title>ログイン画面例</title> </Head> <div className='login-container'> <Image src='https://placehold.jp/150x150.png' roundedCircle style={{ marginBottom: '20px' }} /> <Form.Control type='text' placeholder='User Name' name='userName' value={payload.userName} onChange={handleChange}></Form.Control> <Form.Control type='password' placeholder='Password' name='password' value={payload.password} onChange={handleChange}></Form.Control> <Button variant='info' type='button' onClick={onClickLogin}> Login </Button> </div> </Container> ); }; export default Login;※イメージこんな画面(若干自前CSS足しているので上記コピーだけでは再現されません)
_app.tsx
(メイン)import { NextPageContext } from 'next'; import { AppProps } from 'next/app'; import { parseCookies } from 'nookies'; import { useEffect } from 'react'; import { useRouter } from 'next/router'; const MyApp = ({ Component, pageProps }: AppProps, ctx: NextPageContext) => { const router = useRouter(); const cookies = parseCookies(ctx); // 第二引数に空配列を指定してマウント・アンマウント毎(CSRでの各画面遷移時)に呼ばれるようにする useEffect(() => { // CSR用認証チェック router.beforePopState(({ url, as, options }) => { // ログイン画面とエラー画面遷移時のみ認証チェックを行わない if (url !== '/login' && url !== '/_error') { if (typeof cookies.auth === 'undefined') { // CSR用リダイレクト処理 window.location.href = '/login'; return false; } } return true; }); }, []); const component = typeof pageProps === 'undefined' ? null : <Component {...pageProps} />; return component; }; MyApp.getInitialProps = async (appContext: any) => { // SSR用認証チェック const cookies = parseCookies(appContext.ctx); // ログイン画面とエラー画面遷移時のみ認証チェックを行わない if ( appContext.ctx.pathname !== '/login' && appContext.ctx.pathname !== '/_error' ) { if (typeof cookies.auth === 'undefined') { // SSR or CSRを判定 const isServer = typeof window === 'undefined'; if (isServer) { console.log('in ServerSide'); appContext.ctx.res.statusCode = 302; appContext.ctx.res.setHeader('Location', '/login'); return {}; } else { console.log('in ClientSide'); } } } return { pageProps: { ...(appContext.Component.getInitialProps ? await appContext.Component.getInitialProps(appContext.ctx) : {}), pathname: appContext.ctx.pathname, }, }; }; export default MyApp;後書き
Next(Nuxtも)はSSRとCSRそれぞれを考慮する必要があるからめんどい、、けど良い勉強になった。
Nextはほぼ英語記事しかないので日本語の参考記事も増えたらいいなあ(やっぱり日本ではNuxt・・・?)参考
※リダイレクト周りはほぼ英語記事参考にしましたが散り散りで集められませんでしたすみません・・。
- 投稿日:2020-11-22T06:39:58+09:00
Next.jsまとめ
Next.jsを使ってブログ作ったが色々とだめすぎたので調べたことをまとめて作るために書く記事。
間違ってたりするかもだけど忘備録だからあんまり怒らないで。。。
てことで始めます。Node.jsのインストール
Next.jsはNode.jsのReactのフレームワークみたいなやつなので色々入れます。
まずはNode.js
基本的にNode.jsの公式サイトからダウンロードすれば問題ない。
これは普通にexeとかからインストールしよう。
Node.js
バカでもこれくらいはできるはず作業するディレクトリを作る
ディレクトリ内にNext.jsとReactをインストールしていくのでディレクトリを作成しましょう。
デスクトップとかにドメイン名のディレクトリとか作ったらいいんじゃないのかな
そんでcd 作ったディレクトリ名
みたいなのをコマンドプロンプトとかで実行してディレクトリ移動してねnode_modulesを設定する
npm init -yこれを実行したらpackage.jsonってファイルができると思います。
それが設定ファイルみたいなのになりますReactとNext.jsのインストール
npm install --save react react-dom nextこれを実行したらReactとNext.jsがインストールされます。
簡単ですね^^package.jsonに設定を記述する
設定を記述するとかなんかむずそうなこと言ってますが開発用のサーバーを起動する設定するだけです
package.json{ "name": "re-nixo-blog", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }package.jsonの中身はこんな感じになっているとおもうのでscriptsの中身を変更します
"test": "echo \"Error: no test specified\" && exit 1"
を削除して"dev": "next", "build": "next build", "export": "next export", "start": "next start -p 8010"に変更します
トップページを作成する
next.jsのページは
pages
というディレクトリにJavaScriptファイルを作成して作成します。
pagesというディレクトリをpackages.jsonと同じディレクトリに作成します。
そして、pagesの中にindex.jsというJavaScriptファイルを作成します。
これがトップページです。
トップページのコードを書いていきます。pages/index.jsimport Head from "next/head" import Link from "next/link" export default function Index() { var index = ( <html> <Head> <meta charSet="UTF-8"/> <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0"/> <title>トップページ</title> </Head> <body> <h1>HelloWorld</h1> </body> <style jsx>{` * { margin: 0; padding: 0; box-sizing: border-box; } h1 { text-align: center; } `}</style> </html> ) return index }これをindex.jsにコピペして、
npm run devを実行して、
localhost:3000
にアクセスするとHelloWorldと表示されると思います。静的ファイルを配置する
今回は画像を読み込ませます。
Next.jsはpublicまたはsrcという名前のディレクトリに静的ファイルを配置します。
どちらでも動きますが、publicのほうがなんかいいね!
ということで静的ファイルを配置します。
package.jsonと同じディレクトリにpublicというディレクトリを作成します。
そこに画像とかを配置してください。
そして、index.js
のbody
にhtmlと同じように書きます。
image.pngというファイル名の画像の場合は<img src="/image.png" width="100%" height="auto"/>のように記述します。
すると読み込まれます。サーバーで公開する方法
本当に苦戦しました。
npm run export
でエクスポートしたものを公開したらアクセスはできるものの直URLでアクセスすると404になったり再読込すると404になったりするのでサーバーでnpm run build
をしたあとにnpm run start
をし、リバースプロキシを使用し、公開することに成功しました。
えー、ずっと苦戦し、いろんな海外サイトを飛び回っていたら現在朝の6:31分です。
本当に疲れました。
なので忘れないように書いておきます。まず、サーバーにプロジェクトファイルをアップロードし、サーバーでも
npm install --save react react-dom nextを実行します。
そして、npm run build
をし、screenを開きログアウトしても閉じないようにし、npm run start
をします。
すると、localhost:8010でサーバーが開かれます。
8010というポートはpackage.jsonに書いた"start": "next start -p 8010"の8010です。ここを変えることでポートを変更することができます。
ポート8010で開いた、という体で進めます。
ポート開放が必要なのかわかりませんが一応しておきます。
ルーター側でもしておいたほうがいいかもsudo ufw allow 8010 sudo ufw reloadそして、VirtualHostの設定に、
virtual-host.conf<VirtualHost *:80> LoadModule ssl_module modules/mod_ssl.so LoadModule proxy_module modules/mod_proxy.so ServerName ドメイン名 ProxyRequests Off ProxyPass / http://localhost:8010/ ProxyPassReverse / http://localhost:8010/ </VirtualHost>のように記述します。
これだけだとエラーが出ます。(4時間苦戦した)
なのでこの記事の通り色々有効化します。
https://stackoverflow.com/questions/23931987/apache-proxy-no-protocol-handler-was-validsudo a2enmod ssl sudo a2enmod proxy sudo a2enmod proxy_balancer sudo a2enmod proxy_httpするとアクセスができるようになり、SSRも有効だと思います。
表示が早いね!本当に疲れました。
ではまた。。。
- 投稿日:2020-11-22T03:45:08+09:00
create-react-app + TypeScript: 初っ端からnpm startでエラーが出たときの対処法
バージョン情報 - TypeScript: 4.1.2 - create-react-app: 4.0.0エラー内容
Reactアプリをサクッと書きたいときに便利な
create-react-app
。
TypeScriptで書きたいなと思ったときには、npx create-react-app [app name] --template typescriptと書くことで簡単に作成してくれるのですが、create-react-appのバージョンを
4.0.0
にした途端、npm start
をしてみるとpath\to\app\node_modules\react-scripts\scripts\utils\verifyTypeScriptSetup.js:239 appTsConfig.compilerOptions[option] = value; ^ TypeError: Cannot assign to read only property 'jsx' of object '#<Object>'のように怒られてしまう問題が発生しました。
解決方法
- create-react-appによって作成された
tsconfig.json
を削除- その状態で
npm start
またはyarn start
する。(ここで自動的にtsconfig.json
が再作成される)- なぜか動くようになる!!!
何が違うのか気になったため2つのtsconfig.jsonを比較しましたが、何も違いませんでした。
なぜ動くんだ......(参考: https://github.com/facebook/create-react-app/issues/10117)