20201122のReactに関する記事は9件です。

【ReactNative・React Navigation】JWTを用いた認証と画面の出しわけの基本

認証で管理するStateの例

authSlice.ts
import { 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-storage

JWTの保存、読み込み、削除など一通り用意しておき、認証時の処理に差し込む。

services/deviceStrage.ts
import 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-screen

Android

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;
}

@end

Splashコンポーネントの作成

Splashコンポーネントといっても、画面には何も表示する必要はなく、
JWTの読み込みや、アプリ起動時に必要な処理を行う。

また、処理が終了した際にスプラッシュを非表示にする。

SplashScreen.tsx
import { 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.tsx
export 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>
  );
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テストツールjestのインストール〜実行の流れ

jestインストール〜テスト実行まで

参照先:https://github.com/facebook/jest

①jestをインストール

npm install --save-dev jest

②基本設定ファイルの作成

1.node_modules/.binに対してinitで作成。

./node_modules/.bin/jest --init

2.基本的に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-env

2.{}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 test

2.下記の表示が出れば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.

以上になります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

始めてwebRTCに挑戦した結果…爆散した(後編)

webRTCに挑戦した結果爆散した…

こちらの記事は前編の続きです!ぜひ前編を見てからこの記事を読んでやってください。
全編

とにかくReactでカメラを起動させないと…

前回とりあえずシンプルなJavaScriptで書いたコードをまんまReactで書いたんですけど…
あれ〜カメラ動かんやんけ〜てなことになりましたね!

あ、本日の記事は少しおまけをつけときました。

あの後親切なお方からアドバイスをもらいました…

どうやら前回の記事のときはJSXで作ったいわゆる仮想DOMを取得するときにdocument.getElementById('video');で取得しようとしていました。しかし、ここをdocument.querySelector('video');とすることでうまくいきました。

コード全体は前回の記事を遡ってくださいとおもったのですが、一応それだとめんどいはアホと言われかねないので書いときます。

Camera.js
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>
        )
    };
}

ちなみに今回はメディアをしているするときに
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.js
import 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.js
import 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を押してエンターで続けてください。

やった〜かめらがようやっと起動しました〜〜〜〜〜
カメラを起動させるのにどれだけ時間をかけるつもりだよ…

さてこのあとは…

ここまででやっとスタートラインに立ちました…
ここからメディアをいじって何かしらサービスを作っていきたいと思います。
しかし、私非常に疲れてしまいましたのでその記事はまた次回にするとしましょう〜

最後に

今回はライブラリを使わない方法と使う方法二つをご紹介しましたが、改めてライブラリというものの偉大さを切実に実感しましたね〜
だってぽちぽち、インポート!終了!ですからね!
サービスでどのような機能を作るかをよく考えてそれを実装できそうなライブラリがあるなら積極的に使っていきたいな〜と思ういい機会でした。
また何かアドバイス等ございましたらコメント欄にお願いします!

余談?かな今回はクラスコンポーネントを利用していますがもちろんファンクショナルコンポーネントでもちゃんと動きます!動作確認済みです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

始めて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>

これでうまくいくはずです…!
うまくいった!
スクリーンショット 2020-11-18 14.10.07.png
まぁこないな感じでやれますわ〜

しかし今のご時世フレームワークを用いてユーザ体験の向上やサーバサイドレンダリングやら静的サイトジェネレーターやらを行うのが当たり前と言われておりますが(筆者の周りでわ…)応用編として今回は私くしが実際にお勉強中のReactでやってみようと思います。
そしてあわよくば今現在大学の課題で作っているカメラに写っている人の数を検出して人数を表示するというところにまで持っていきたい!
とまぁ自分語りはここら辺にしてやっていきましょう〜
これは完全に僕の好みというかReact使う人たちは大体そうだと思いますけど、今回はCameraコンポーネントを作成しApp.jsにimportして作ろうと思います。

Camera.js
import 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やめる!

[というわけで次回へ…]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初心者向け]Reactの基礎を徹底解説してみた

はじめに

今回はReactの使い方についてまとめていきます。

環境構築については、以下の記事を参考にしてください。

WindowsでReactの環境構築をしてみる

コンポーネントについて

Reactはコンポーネント(部品)という単位で実装していきます。

Webサイトやアプリは、ボタンやタブなどのコンポーネントの集まりと考えることができます。

例えば、このQiitaだと以下のようなコンポーネントが存在すると考えることができます(適当です)。

  • ボタンコンポーネント

image.png

  • ナビゲーションコンポーネント

image.png

つまり、コンポーネントとは見た目機能を組み合わせたものだと考えることができます。

このように、コンポーネントという単位でプログラムを管理すると、再利用することができたり、変更してもバグが起きにくいという利点がありますよね。

それでは実際にコンポーネントの種類を見ていきましょう。

今回は、npx create-react-appというコマンドを使ってReactアプリを作成し、そのアプリに各々のコードを記述して動きを見ていきます。

Functional コンポーネントについて

Functionalコンポーネントについて見ていきましょう。

create-react-appを行うと、以下のようなファイル構成になります。

image.png

この中のApp.jsxに以下のようにコードを記述してください。

App.jsx
import 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.js
import 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.jsApp.jsxファイルからエクスポートされたAppコンポーネントを読み込み、それをReactDOMに渡すことでrender(描画)しています。

yarn startを行い、実行結果を見てみましょう。

image.png

このように、Reactアプリを起動させるとindex.jsファイルが実行され、ReactDOMがrender(描画)されていることが分かります。

クラスコンポーネントについて

クラスコンポーネントは以下のように作成します。

App.jsx
import 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 App

Javascriptのクラスを作成する際に、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アプリを起動すると以下のようになります。

image.png

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.jsx
import 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.jsx
import React from 'react';

const Name = (props) => {
    return (
        <h1>{props.name}</h1>
    )
}

export default Name

結果は以下のようになります。

image.png

それでは次にクラスコンポーネントについて見ていきましょう。

以下の特徴があります。

  • ライフサイクルをもつ
  • 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.jsx
import 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 App
LikeButton.jsx
import React from 'react';

const LikeButton = (props) => {
    return (
        <div>
            <button id="counter">いいね数: {props.count}</button>
        </div>
    )
}
export default LikeButton

結果は以下のようになります。

temp1.gif

それではコードの解説をしていきます。

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.jsx
import 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 App
LikeButton.jsx
import React from 'react';

const LikeButton = (props) => {
    return (
        <div>
            <button onClick={props.UpdateCounter}>いいね数: {props.count}</button>
        </div>
    )
}

export default LikeButton

componentDidUpdateの実装

それでは次にstateが更新されてrenderが走った後に呼び出されるcomponentDidUpdateを実装していきましょう。

このライフサイクルメソッドは、stateがある閾値を超えたときに別の動作をするようにしたいときなどに実装すると便利ですね。

今回は、いいね数が5という閾値を超えた際に、いいね数を0にするという機能を実装していきます。

componentDidUpdateはstateが更新され、それによりrenderが走った後に呼び出されます

つまり、その更新されたstateに対して更新されるたびにif文やswich文で比較を行い、指定した値になったときに指定した関数を実行する、といった処理を実装していきます

今回はthis.state.countの値をcomponentDidUpdateの中で5と比較し、5以上になったときにthis.state.countの値をsetStateメソッドを用いて0にするという機能を実装していきます。

以下のコードです。

App.jsx
import 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 App
LikeButton.jsx
import 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以上かどうかを確認することになります。

実行結果は以下のようになります。

temp2.gif

componentWillUnmountの実装

このメソッドは主に次の機能を実装したいときに使います。

  • タイマーを解除する
  • イベントリスナーを解除する
  • 非同期処理を中止する

今回は、イベントリスナーを解除してメモリを開放する機能を実装していきましょう。

以下のコードです。

App.jsx
import 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 App
LikeButton.jsx
import 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」は「4種類」??

JavaScript の this を理解する多分一番分かりやすい説明

つまり、簡単に書くとthisの使い方めっちゃむずいということです。

こんなものはトラブルの元です。

できるだけ使わないようにしようという動きも多く、そのためクラスコンポーネントはできるだけ使いたくないと思う人も多いようです。

複数のライフサイクルメソッドに処理がまたがるのが嫌だ

クラスコンポーネントのライフサイクルメソッドは、時間の流れで処理を分割します。

そうすると、同じような機能が複数のライフサイクルメソッドに跨ってしまうことがありますよね。

そのように記述するよりも、同じ機能は同じ場所と言う方が分かりやすいと考える人も多いようです。ちなみに私もそう思います。

React-Hooksの実装

それでは実際いHooksを実装してみましょう。

Hooksでstateの管理

まず、Hooksでstateの管理をしていきます。

HooksはFunctionalコンポーネントにおけるstateの管理やライフサイクルメソッドの実装に用いるんでしたね。

Hooksでstateを用いるときは、useStateというメソッドを使います。

実際にコードで確認してみましょう。

今回は、ScissorsコンポーネントというFunctionalコンポーネントを作成して、それを親コンポーネントであるAppコンポーネントから呼び出します。

そのScissorsコンポーネントにcuttingとうstateを持たせて、ボタンを押すことで変化させるという機能を実装します。

以下がコードです。

App.jsx
import React from 'react';
import Scissors from './Scissors'

const App = () => {

  return(
    <div>
      <Scissors />
    </div>
  )
}
export default App

これはシンプルにScissorsコンポーネントを呼び出しているだけですね。以下がそのScissorsコンポーネントです。

Scissors.jsx
import 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関数は、statestateを変更させる関数を同時に定義していると考えることができますね。

クラスコンポーネントでは、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アプリの挙動を見てみましょう。

temp3.gif

ここまでで、Hooksによるstate管理は終わりです。

Hooksによるライフサイクルの管理

Hooksによるライフサイクルメソッドの代替として、useEffectメソッドがあります。

以下の三つのメソッドを代替することができます。

  • coponentDidMount()
  • componentDidUpdate()
  • componentWillUnmount()

軽く解説します。

componentDidMountについて

componentDidMountはマウントが行われた直後に実行されるメソッドです。Ajaxを使ったデータフェッチ(初回)やイベントリスナのセットなどを行います。

componentDidUpdateについて

componentDidUpdateはstateの更新が行われた直後に行われるメソッドです。具体的には、stateが更新され、その後にrenderが走った後に呼び出されます。

stateがある閾値を超えたときに別の動作をするようにしたいときなどに使うと便利ですね。

componentWillUnmountについて

componentWillUnmountは、コンポーネントが破棄される直前に呼び出されます。

具体的には、イベントリスナーを解除してメモリを開放するときなどに利用されます。

それでは、実際にuseEffectメソッドを用いてこれらのライフサイクルメソッドを実装してみましょう。

useEffectメソッドの実装

まず、最初にuseEffectメソッドの使い方解説を行います。

以下の記事を参考にしました。

【React】useEffectの第2引数って?

第一引数のみ

第二引数を空っぽにすると、第一引数に指定したコールバック関数が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後の関数が、アンマウント時に実行されるようになります。

終わりに

今回の記事はここまでになります。

お疲れさまでした。

参考

日本一わかりやすいReact入門#8...React Hooksでstateを扱おう

React Hooks超入門

最近Reactを始めた人向けのReact Hooks入門

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Amplifyで環境変数を設定し、Reactアプリで読み込む

GitHubなどからWebアプリのCI/CDができるAWS Amplifyですが、
今回は環境変数の設定方法と、AmplifyでホストしているReactアプリから読み込む方法を備忘録として残しておきます。
(あと以外と日本語のドキュメントがなかった)

ReactなどのWebアプリでは.envファイルなどに環境変数を定義しておいて、
process.env.{任意の名前}で読み込むことがあると思いますが、
認証情報などを記載している場合はソース管理には載せられませんし、Amplifyで環境変数を設定して読み込む必要があります。

ちなみにAmplifyではバックエンドの構築も可能ですが、環境変数の利用はフロントエンドのみのアプリでもできます。
ちょっとしたフロントエンドだけのアプリ作って環境変数の読み込みだけしたい場合、Amplify CLIを使ったバックエンドの構築などはせずに環境変数の設定、利用が可能です。

環境変数の設定

環境変数の登録

Amplifyで環境変数を定義するにはまずAWS ConsoleでAmplifyのページに飛び、
サイドメニューから「環境変数」を選択します。
tempsnip.png

環境変数のメニューが開くので「変数の管理」から変数の登録・編集ができます。
今回はReactアプリで読み込むのでREACT_APP_を変数名にプレフィックスとしてつけておきます。
また、複数ブランチをホストしている場合には、ブランチごとに変数を設定することが可能です。
1.png

ビルドの設定

続いてメニューから「ビルドの設定」を選択し、以下の画面を開きます。
2.png
ここではデプロイ時のビルドの設定をyaml形式で定義することができるので、ここで環境変数のエクスポートをします。
buildのcommandsに↓こんな感じで書いておきます。
出力するパスはアプリに応じて変更します。今回はルートに書きだしています。

echo "REACT_APP_HOGE=$REACT_APP_HOGE" >> .env

アプリからの読み込み

あとはアプリから

process.env.REACT_APP_HOGE

で読み込めば完了です。

参考

Environment variables - AWS Amplify

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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・・・?)

参考

※リダイレクト周りはほぼ英語記事参考にしましたが散り散りで集められませんでしたすみません・・。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.js
import 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.jsbodyに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-valid

sudo a2enmod ssl
sudo a2enmod proxy
sudo a2enmod proxy_balancer
sudo a2enmod proxy_http

するとアクセスができるようになり、SSRも有効だと思います。
表示が早いね!

本当に疲れました。
ではまた。。。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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>'

のように怒られてしまう問題が発生しました。

解決方法

  1. create-react-appによって作成されたtsconfig.jsonを削除
  2. その状態でnpm startまたはyarn startする。(ここで自動的にtsconfig.jsonが再作成される)
  3. なぜか動くようになる!!!

何が違うのか気になったため2つのtsconfig.jsonを比較しましたが、何も違いませんでした
なぜ動くんだ......

(参考: https://github.com/facebook/create-react-app/issues/10117)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む