エンジニアを目指す日常ブログ

日々勉強したことのメモ。独学ですので間違っていたらコメント等で教えてください。

Node.jsのコマンド実行時にメモリが足りないエラーが発生

はじめに

以下手順にしたがってgatsbyによるWebページ作成を試していたところ、想定外のエラーが発生した。結果的にVagrantのメモリを大きくすることで解決したので、対処法をメモ。

Gatsbyの導入|Gatsby入門

発生したエラー

以下コマンドで gatsbyプロジェクトを作成したところ、エラーが発生。

$ gatsby new hello-world https://github.com/gatsbyjs/gatsby-starter-hello-wo
npm ERR! code ENOMEM
npm ERR! syscall spawn
npm ERR! errno -12
npm ERR! spawn ENOMEM
npm ERR! code ENOMEM
npm ERR! syscall spawn
npm ERR! errno -12
npm ERR! spawn ENOMEM

npm ERR! A complete log of this run can be found in:

 ERROR

Command failed with exit code 244: npm install


  Error: Command failed with exit code 244: npm install

対処方法

code ENOMEM syscall spawn errno -12で検索すると、メモリが足りないエラーのようだった。

上記のコマンドはVagrant + VirtualBoxを用いて仮想Linuxサーバ上で実行しているため、仮想環境に割り当てているメモリが足りないのだと思われた。

そこで、仮想環境の設定ファイルVagantfileを変更する。

手順

Vagrantを終了

$ vagrant halt

設定ファイルの修正

vb.memoryの値を1024から2048に変更した。

Vagrantfile

  config.vm.provider "virtualbox" do |vb|
    vb.memory = "2048"
  end

Vagrantの立ち上げ

vagrant up
vagrant ssh

再度同じコマンドを実行すると、最後まで完了させることができた。

おわりに

メモリが足りないエラーが出たのが初めてだったので少し苦戦した。

参考資料

Vagrantに関するメモはこちら。

tomiko0404.hatenablog.com

インストールしたNode.jsモジュールが実行できなかったときの対処メモ

はじめに

Node.jsのモジュールgatsby-cliをインストールしたが、利用できない(gatsby --helpなどのコマンドを打つとエラーが出る)状態だったので、対処法をメモ。

発生したエラー

以下のコマンドでモジュールをインストールし、

$ npm install -g gatsby-cli

以下のコマンドでgatsbyコマンドを実行しようとすると、エラーが発生した。

$ gatsby -help

エラーの内容は以下。

/home/vagrant/.nvm/versions/node/v10.14.2/lib/node_modules/gatsby-cli/node_modules/gatsby-telemetry/lib/telemetry.js:39
  store = new _eventStorage.EventStorage();
        ^

SyntaxError: Unexpected token =
    at new Script (vm.js:79:7)
    at createScript (vm.js:251:10)
    at Object.runInThisContext (vm.js:303:10)
    at Module._compile (internal/modules/cjs/loader.js:657:28)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:700:10)
    at Module.load (internal/modules/cjs/loader.js:599:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:538:12)
    at Function.Module._load (internal/modules/cjs/loader.js:530:3)
    at Module.require (internal/modules/cjs/loader.js:637:17)
    at require (internal/modules/cjs/helpers.js:22:18)

対処した内容

調べてみると、Node.jsのバージョンが古いと、上記のエラーになることがあるとわかった。

以下の記事を参考に、バージョンを確認すると

$ node -v
v10.14.2

v10.14.2であることがわかった。

tomiko0404.hatenablog.com

以下コマンドで、公式の最新バージョンを確認。

$ nvm ls-remote
...省略...
       v16.11.1
       v16.12.0
       v16.13.0   (LTS: Gallium)
       v16.13.1   (Latest LTS: Gallium)
        v17.0.0
        v17.0.1
        v17.1.0
        v17.2.0
        v17.3.0

最新のLTS(Long Term Support)バージョンはv16.13.1とのことなので

$ nvm install 16.13.1

でNode.jsをインストールし直した。

npmも古いですよの警告が出たため、

npm notice New minor version of npm available! 8.1.2 -> 8.3.0

npmも最新版に更新。

$ npm install -g npm@8.3.0

改めてモジュールの実行を試みると、問題なく実行できた。

$ gatsby --help
╔════════════════════════════════════════════════════════════════════════╗
║                                                                        ║
║   Gatsby collects anonymous usage analytics                            ║
║   to help improve Gatsby for all users.                                ║
║                                                                        ║
║   If you'd like to opt-out, you can use `gatsby telemetry --disable`   ║
║   To learn more, checkout https://gatsby.dev/telemetry

おわりに

Node.jsは1年以上前にインストールしてそのままになっていたので、古くなってしまっていた。

モジュールによってはNode.jsのバージョンが古いと使えないものがあるとわかった。

【React+Node.js/socket.io】チャットアプリに入室機能とユーザ名表示機能を追加する

はじめに

前回、ReactとNode.js Expressを使って、チャットアプリの超基礎となる部分を作成した。

tomiko0404.hatenablog.com

今回は、機能を追加し、さらにチャットアプリに近づける。

youtu.be

追加する機能

発言者名と発言時刻を表示する機能

発言者名と発言の時刻を取得して表示する機能。 発言者名は、入室機能によってブラウザごとに設定する。

発言者名、発言時刻表示機能イメージ
発言者名、発言時刻表示機能

入室機能(簡易)

チャット画面に入る前に、ユーザ名を設定する機能。

入室機能イメージ
入室機能

見た目の改善・コンポーネント

マージンを設定したり、ボタンのデザインを作成。

前提

前回の記事の続きとなります。

tomiko0404.hatenablog.com

最終的なソースコード

ソースコードはこちら。 github.com

修正内容の解説

発言時刻を表示する機能

メッセージ送信機能への変更

前回のApp.tsxのうち、画面サーバ(クライアント側)からWebサーバに送信する内容を増やす。

    // 「送る」ボタンを押したときの処理。サーバにmessageを送信する。
    //  messageContentオブジェクト(中身は名前/メッセージ/発言時刻)
    const onClickSend = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
        e.preventDefault();
        const date = new Date();
        const messageContent: MessageContent = {
            name: name,
            message: message,
            postat: date.toLocaleString("ja"),
        };

        socket.emit("chat message", messageContent);
        setMessage("");
    };

時刻を取得するのはJavasciptのDateクラス

const date = new Date();

で実施している。

Webサーバ側の変更

Webサーバ側(index.js)は、オブジェクトを受けとってブロードキャストする動作は変わらないので、特に変更なし。

メッセージ受信、表示機能への変更

あとは画面サーバ側で、ブロードキャストされた値をそれぞれ表示してあげるだけで良い。

    useEffect(() => {
        socket.on("chat", (messageContent) => {
            setMessageList((messageList) => [...messageList, messageContent]);
        });
    }, []);

の部分でサーバから送信されたmessageContentに必要な値が格納されている。

入室機能(簡易)

入室画面とメインチャット画面を作成する

これまで作成してきたメインのチャット画面に加え、入室画面を作成する。

これまで

  • メインチャット画面:/ (例えば開発環境ではhttp:localhost:8000

変更後

  • ログイン画面:/(例えば開発環境ではhttp:localhost:8000
  • メインチャット画面 /main (例えば開発環境ではhttp:localhost:8000/main
ルーティング

ルーティングはReact-router-domを利用した。

tomiko0404.hatenablog.com tomiko0404.hatenablog.com

App.tsx

import "./App.css";

import { ChakraProvider } from "@chakra-ui/react";
import { BrowserRouter } from "react-router-dom";
import { Router } from "./router/Router";

function App() {
    return (
        <ChakraProvider>
            <>
                <BrowserRouter>
                    <Router />
                </BrowserRouter>
            </>
        </ChakraProvider>
    );
}

export default App;

/router/Router.tsx

import { Route, Switch } from "react-router";
import { LoginPage } from "../components/pages/LoginPage";
import { MainChat } from "../components/pages/MainChat";
import { Page404 } from "../components/pages/Page404";


export const Router = () => {
    return (
        <Switch>
            <Route exact path="/" render={() => <LoginPage />} />
            <Route exact path="/main" render={() => <MainChat />} />
        </Switch>
    );
};

入室画面は/pages/LoginPage.tsxに、メイン画面は/pages/MainChat.tsxに切り出す。

入室画面

入室画面では、stateとしてnameを利用する。

<input>に入力されているvaluenameに常にnameに設定する。「入室」ボタンがクリックされたら、onClickJoin関数により/mainのURLに遷移する。その際、stateとして{name: name}が渡される。

tomiko0404.hatenablog.com

/pages/LoginPage.tsx

import { useState } from "react";
import { useHistory } from "react-router";
import { NameInputArea } from "../organisms/NameInputArea";

export const LoginPage = () => {
    // 名前
    const [name, setName] = useState("");
    const history = useHistory();

    // 名前エリアの文字が変更されたときの処理
    const onChangeName = (e: React.ChangeEvent<HTMLInputElement>) => {
        setName(e.target.value);
    };

    // 名前が登録されたときの処理
    const onClickJoin = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
        history.push("/main", { name: name });
    };

    return (
        <>
            <NameInputArea onChange={onChangeName} onClick={onClickJoin} value={name} />
        </>
    );
};

/components/organisms/NameInputArea.tsx

import { VFC } from "react";
import { InputArea } from "../molecules/InputArea";

type Props = {
    value: string; // インプットに表示されているテキスト
    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
    onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
};
export const NameInputArea: VFC<Props> = (props) => {
    const { value, onChange, onClick } = props;
    return (
        <InputArea
            value={value}
            onChange={onChange}
            onClick={onClick}
            placeholder="名前を入力してください"
            buttonText="入室"
        />
    );
};

/components/molecules/InputArea.tsx

import { Input } from "@chakra-ui/input";
import { Flex } from "@chakra-ui/layout";
import { VFC } from "react";
import { PrimaryButton } from "../button/PrimaryButton";

type Props = {
    value: string;
    onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
    placeholder?: string;
    onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
    buttonText: string;
};

export const InputArea: VFC<Props> = (props) => {
    const { value, onChange, placeholder, onClick, buttonText } = props;
    return (
        <Flex align="center" justifyContent="center">
            <Input
                placeholder={placeholder}
                value={value}
                onChange={onChange}
            />
            <PrimaryButton onClick={onClick} disabled={!value}>
                {buttonText}
            </PrimaryButton>
        </Flex>
    );
};
メインチャット画面

前回のApp.tsx/pages/MainChat.tsxに切り出す。

また、入室画面から遷移してきたときのstateオブジェクトを受け取る必要があるので、以下を追加する。

App.tsx

import { useLocation } from "react-router";

type LoginState = {
    name: string;
};

export const MainChat = () => {
    const { state } = useLocation<LoginState>();
    const { name } = state;
    .... 省略 ...
}

見た目の改善

ChakraUIの機能を使って諸々の見た目を改善。 また、Atomicデザインを意識してある程度コンポーネント化した。

ChakraUIの利用にはコツがいるので、以下サイト

Chakra UIの歩き方 & Tips集

等を参考にした。

テキスト入力&送信部を中央添えにする部分は苦労したが、justifyContentを使いこなす必要がありそう。

        <Flex align="center" justifyContent="center">
            <Input />
            <PrimaryButton></PrimaryButton>
        </Flex>

おわりに

少しチャットらしいアプリにすることができた。 誰でも入室できてしまうので、socket.io の room機能など使いこなす必要がありそう。

【CSS】箇条書きのマーカーをレベルによって変える方法

はじめに

箇条書きを入れ子にした際、マーカーを入れ子構造がわかりやすいように変更する方法のメモ。

見本
入れ子構造の時にマーカーを変更する

CSSの記載方法

/* 1段目 */
.entry-content ul {
    list-style-type: disc;
}
/* 2段目 */
.entry-content ul > li > ul {
    list-style-type: circle;
}
/* 3段目 */
.entry-content ul > li > ul > li > ul {
    list-style-type:square ;
}
/* 4段目 */
.entry-content ul > li > ul > li > ul > li > ul {
    list-style-type:"\1F337" ;
}

list-style-typeオプションについて

オプションで指定する方法

  • disc(黒丸)
  • circle(白丸)
  • square(四角)

等から選ぶことができる。

list-style-type - CSS: カスケーディングスタイルシート | MDN

文字コードでマーカーを指定する方法

4段目は、マーカーを文字コードで指定している。 文字コードは以下のサイトを参考にした。

gray-code.com

上記サイトの16進数の列に&#x1F337;とある絵文字を使うときは、

list-style-type:"\1F337" ;

と指定する。

参考:HTMLの中身

<ul>
    <li>1段目 
        <ul>
            <li>2段目
                <ul>
                    <li>3段目
                        <ul>
                            <li>4段目</li>
                        </ul>
                    </li>
               </ul>
            </li>
        </ul>
    </li>
</ul>

Typescriptの特殊な型定義のメモ

はじめに

Javascript、Reactを勉強するにあたり、型定義が必要なTypescriptを利用しているが、何を定義したらよいか難しい場合があるので、調べた内容を記載する。

型定義のメモ

children: ReactNode;

childrenはReactNode型を指定する。

利用例

import {ReactNode, VFC } from "react";

type Props = {
    children: ReactNode;
};

export const MyButton: VFC<Props> = (props) => {
    const { children} = props;
    return (
        // 省略
    );
};

onClick関数

onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;

onChange関数

 onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;

自分で型を定義する場合

typesフォルダを作成し、その中に大文字始まりの型名をファイル名としてファイル作成する。拡張子は.tsとする。

src
├── App.tsx
├── components
├── index.css
├── index.tsx
└── types
    └── MessageContent.ts

MessageContent.ts

export type MessageContent = {
    name: string;
    message : string;
    postat : string;
}

実装の例

onClickとonChangeの例

メッセージを入力し、「送る」ボタンを押したら送信するフォーム。

App.tsx

import { useState } from "react";
import { InputArea } from "./molecules/InputArea";
import "./styles.css";

export default function App() {
  // インプットエリアに入力するメッセージ
  const [message, setMessage] = useState("");

  // インプットエリアの文字が変更されたときの処理
  const onChangeMessage = (e: React.ChangeEvent<HTMLInputElement>) => {
    setMessage(e.target.value);
  };

  // 「送る」ボタンを押したときの処理。サーバにmessageを送信する。
  const onClickSend = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    // メッセージを送る処理
    console.log(`messageを送る処理:${message}`);
    setMessage("");
  };

  return (
    <>
      <InputArea
        text={message}
        onChange={onChangeMessage}
        onClick={onClickSend}
      />
    </>
  );
}

/molecules/InputArea.tsx

import { VFC } from "react";

type Props = {
  text: string;
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
  onClick: (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void;
};

export const InputArea: VFC<Props> = (props) => {
  const { text, onChange, onClick } = props;
  return (
    <>
      <input
        value={text}
        onChange={onChange}
        placeholder="メッセージを入力してください"
      />
      <button onClick={onClick}>送る</button>
    </>
  );
};

おわりに

今のところ出てきたのはこれくらいだが、 JavaScript特有の型が様々あるので、調べるごとにメモする予定。

React×Typescriptでページ遷移時に値を渡す方法

はじめに

React×Typescriptでルーティングする際、前画面の値を次の画面に渡す方法を記載する。調べると「これが正解」というのが出てこなくて難しいが、理解した方法をメモ。

試した方法

  1. URLパラメータを使う(パスパラメータ)
  2. URLパラメータを使う(クエリパラメータ)
  3. <Link>コンポーネントにstateを渡す
  4. useHistory()を使う

前提

以下の記事で作成したアプリを利用して検証する。

tomiko0404.hatenablog.com

今回はreact-router-domのバージョン5を利用している。

URLパラメータで渡す方法

パスパラメータ

<Route>に渡すpathに以下のように:[パラメータ名]を記載することで、パスパラメータとして扱える。

<Route path="mapage/mypage-detail/:id" render={() => <MyPageDetail />} />

パラメータが任意の場合は?をつける。

<Route path="mapage/mypage-detail/:id?" render={() => <MyPageDetail />} />

期待する動作

  • localhost:8000にアクセスするとHomeページが表示される。(前回のまま)
  • localhost:8000/mypageにアクセスするとMyPageが表示される。(前回のまま)
  • localhost:8000/mypage/mapage-detailにアクセスするとMyPageDetailが表示される。(今回のポイント)
  • localhost:8000/mypage/mapage-detail/100というように、/100の部分にパスパラメータを入れてアクセスするとMyPageDetailが表示され、画面上にもパラメータの値が表示される。(今回のポイント)
  • リンクは常に表示される。

実際の動作
期待する動作

ソースコード

ルーティング側の設定

前回の記事で

<Route path={`${url}/mypage-detail`} render={() => <MyPageDetail />} />

としていた部分を

<Route path={`${url}/mypage-detail/:id?`} render={() => <MyPageDetail />} />

と書き換える。

Router.tsx全体

import { Switch, Route } from "react-router-dom";
import { Home } from "../pages/Home";
import { MyPage } from "../pages/MyPage";
import { MyPageDetail } from "../pages/MyPageDetail";

export const Router = () => {
    return (
        <Switch>
            <Route exact path="/" render={() => <Home />} />
            <Route
                path="/mypage"
                render={(props) => {
                   console.log(props)
                    const url = props.match.url;
                    return (
                        <Switch>
                            <Route exact path={url} render={() => <MyPage />} />
                            <Route path={`${url}/mypage-detail/:id?`} render={() => <MyPageDetail />} />
                        </Switch>
                    );
                }}
            />
        </Switch>
    );
};

今回はidを任意としているため、localhost:8000/mypage/mapage-detailにアクセスしてもが表示される。

idを必須とすればlocalhost:8000/mypage/mapage-detailは表示されない(ルーティングから外れる)。

パスパラメータの利用

useParamsというHooksが提供されており、パスパラメータを利用できる。 typescriptでは以下のように利用する。

MyPageDetail.tsx

import { useParams } from "react-router";

type Params = {
    id?: string;
};
export const MyPageDetail = () => {
    const { id } = useParams<Params>();

    return (
        <>
            <h1>MyPageDetail</h1>
            <h2>idは{id}</h2>
        </>
    );
};

idはURLパラメータから渡されてきた値なのでstring型になる。

ちなみに以下の記事で、「typescriptを使っていても結局Router.tsxのpathを文字列で定義している以上、安全じゃないよね」ということが書かれている。確かに間違えてpath={`${url}/mypage-detail/:ib?`}としてしまっても気づけずバグを生みそう。

Template Literal Typesなどを使って解決する必要があるらしい。

2021年の密かなトレンド? “型安全ルーティング”の概観

クエリパラメータ

クエリパラメータの場合は、<Route>のpropsにあるlocationを利用する。

useLocation()を利用することで、locationオブジェクトを取得できる。

const { search } = useLocation();
const queryParams = new URLSearchParams(search);
const name = queryParams.get("name"); // nameはクエリパラメータ名の例

Routeのpropsのhistoryを利用する
Routeのpropsのhistoryを利用する

期待する動作

  • localhost:8000/mypage/mapage-detail/100?name=taro&age=20」というように、パスパラメータ、クエリパラメータを入れてアクセスすると、MyPageDetailが表示される。画面上にもパラメータの値が表示される。(今回のポイント)
  • それ以外はパスパラメータの節と同様。

ソースコード

ルーティング側の設定

パスパラメータのときと同じ。クエリパラメータはルーティングで設定する必要はない。

クエリパラメータの利用

利用する側のコンポーネントのソースは以下。

MyPageDetail.tsx

import { useLocation, useParams } from "react-router";

type Params = {
    id?: string;
};

export const MyPageDetail = () => {
    const { id } = useParams<Params>();
    const { search } = useLocation();
    const queryParams = new URLSearchParams(search);
    const name = queryParams.get("name");
    const age = queryParams.get("age");

    return (
        <>
            <h1>MyPageDetail</h1>
            <h2>idは{id}</h2>
            <h2>nameは{name}</h2>
            <h2>ageは{age}</h2>
        </>
    );
};

<Link>からstateで渡す方法

期待する動作

  • localhost:8000/mypage/mapage-detail/100?name=taro&age=20」というように、パスパラメータ、クエリパラメータを入れてアクセスするのはURLパラメータの章と同様。
  • MyPageで定義した値(login、group)がMyPageDetailで利用でき、画面に表示できる。(今回のポイント)
  • それ以外はパスパラメータの節と同様。

locationオブジェクトのstate

useLocation()の中にあるstateを利用する。

先ほど、URLパラメータのみ設定していたとき、useLocationの中身は

hash: ""
key: "6sdwd9"
pathname: "/mypage/mapage-detail/20/"
search: "?name=taro&age=20"
state: undefined

となっている。

ここで使っていなかったstate部分を使う。

Routeのpropsの中身(location)
Routeのpropsの中身(location)

<Link>でstateを設定する

MyPage.tsxの中で

<Link to="/mypage/mapage-detail/20/?name=taro&age=20">
    link to MyPageDetail
</Link>

と定義している部分は、locationのオブジェクト名を使って以下のように書き換えられる。

<Link to={{ pathname: "/mypage/mapage-detail/20/", search: "?name=taro&age=20" }}>
    link to MyPageDetail
</Link>

結果(locationの中身)は先ほどと同じになる。

ここにstateを追加する。

<Link to={{ pathname: "/mypage/mapage-detail/20/", search: "?name=taro&age=20", state: myState }}>
    link to MyPageDetail
</Link>

このとき、locatoinオブジェクトのstateに値が設定されたことがわかる。

stateが追加された
stateが追加された

ソースコード

遷移元

ポイントは<Link>タグにstateを設定していることのみ。事前に定義したmyStateを設定。

Mypage.tsx

import { Link } from "react-router-dom";

type MyState = {
    login: boolean;
    group: number;
};

export const MyPage = () => {
    // stateに設定する値(仮)
    const myState: MyState = {
        login: true,
        group: 1,
    };

    return (
        <div>
            <h1>Mypage</h1>
            <Link
                to={{
                    pathname: "/mypage/mapage-detail/20/",
                    search: "?name=taro&age=20",
                    state: myState,
                }}
            >
                link to MyPageDetail
            </Link>
        </div>
    );
};

遷移先(利用側)

useLocation()で取得できるlocationオブジェクトからstateオブジェクトを取り出す。

const { state } = useLocation<MyState>();

その際、stateオブジェクトの型をとして定義(今回はMyState)することが必要。

MyPageDetail.tsx

import { useLocation } from "react-router";

type MyState = {
    login: boolean;
    group: number;
};

export const MyPageDetail = () => {
    const { state } = useLocation<MyState>();
    const { group, login } = state;

    return (
        <>
            <h1>MyPageDetail</h1>
            <h2>group:{group}</h2>
            <h2>login:{String(login)}</h2>
        </>
    );
};

useHistory()を使う方法

locationmatchと横並びで、<Route>コンポーネントのpropsに存在するhistoryオブジェクトを使う。 historyuseHistory()というHooks関数で利用することができる。

const onClick = () => history.push("/mypage/mypage-detail", myState);
return (
            <button onClick={onClick}>MyPageDetailへ</button>
);

historyの中身

historyの中身
historyの中身

history.push

history.push( path, state)は第一引数にpath、第二引数にstateを持つ関数。

公式ホームページに

新しいエントリを履歴スタックにプッシュします
React Router: Declarative Routing for React.js

とあるとおり、ページ遷移の履歴(history)に対してpathで指定した宛先を追加する機能。同時に、履歴の最新となる「pathで指定した宛先」に遷移できる。

history.goback

hisoty.goback()は、ブラウザの「戻る」と同じ動きをする関数。

期待する動作

<Link>からstateで渡す方法と同様だが、今回は<Link>ではなくボタンのonClick関数に遷移動作を定義する。

ソースコード

遷移元

ポイントは以下のようにonClick関数を定義する部分。

const history = useHistory();
const onClick = () => history.push("/mypage/mypage-detail", myState);
import { useHistory } from "react-router-dom";

type MyState = {
    login: boolean;
    group: number;
};

export const MyPage = () => {
    // stateに設定する値(仮)
    const myState: MyState = {
        login: true,
        group: 1,
    };

    const history = useHistory();
    const onClick = () => history.push("/mypage/mypage-detail", myState);
    console.log(history);

    return (
        <div>
            <h1>Mypage</h1>
            <div>
                <button onClick={onClick}>MyPageDetailへ</button>
            </div>
        </div>
    );
};

遷移先(利用側)

<Link>からstateで渡す方法のときと全く同じ。

const { state } = useLocation<MyState>();

のようにuseLocaton()を利用することでstateを取得できる。

historyとlocationの違い

以下の記事によれば、
historyは変わらないもの。locationはページ遷移のたびに変わるもの。」 blog.uhy.ooo

おわりに

以下4つの方法で、ページ遷移先に値を渡す方法を理解した。

  • URLパラメータを使う
    • パスパラメータ
    • クエリパラメータ
  • <Link>コンポーネントにstateを渡す
  • useHistory()を使う

途中でも書いたように、<Link>を使う場合でもhistory.push()を使う場合でも、パスやパラメータの設定が文字列となっており、タイプミスを引き起こしそうな点は気になるので引き続き調査する。

関連記事

react-router-domを使ったルーティング方法 tomiko0404.hatenablog.com

参考文献

www.udemy.com

zenn.dev

react-router-domを使ったルーティング方法メモ

はじめに

react-router-domを利用して基本のページ遷移を実施する。

react-router-domは2021/11/3にバージョン6がリリースされたようだが、情報が少なくて独学が難しいのでバージョン5から始める。

react-router-dom@バージョン5

インストール

以下をインストールする。

$ yarn add react-router-dom@5.x
$ yarn add @types/react-router-dom

超基本の形

基本的な使い方は以下。

期待する動作

  • localhost:8000にアクセスするとHomeページが表示される。
  • localhost:8000/mypageにアクセスするとMyPageが表示される。
  • リンクは常に表示される。

ディレクトリ構成

my-router
├── App.tsx
├── index.css
├── index.tsx
├── pages
│   ├── Home.tsx
│   └── MyPage.tsx
└── router
     └── Router.tsx

ソースコード

App.tsx

import { BrowserRouter, Link } from "react-router-dom";
import { Router } from "./router/Router";

function App() {
    return (
        <BrowserRouter>
            {/*  常に表示する部分 */}
            <Link to="/">Link to Home</Link>
            <br />
            <Link to="/mypage">Link to MyPage</Link>

            {/*  ルーティングする部分 */}
            <Router />
        </BrowserRouter>
    );
}

export default App;

Router.tsx

import { Switch, Route } from "react-router-dom";
import { Home } from "../pages/Home";
import { MyPage } from "../pages/MyPage";

export const Router = () => {
    return (
        <Switch>
            <Route exact path="/" render={() => <Home />} />
            <Route path="/mypage" render={() => <MyPage />} />
        </Switch>
    );
};

BrowserRouter

react-router-domを利用する合図のようなもの。全体をこれで囲む必要がある。

<a>タグの役割をするもの。

Swtich

ルーティング部分(URLごとに表示するコンポーネントを指定する部分)を囲む。

ネストする場合

期待する動作

  • localhost:8000にアクセスするとHomeページが表示される。
  • localhost:8000/mypageにアクセスするとMyPageが表示される。
  • localhost:8000/mypage/mapage-detailにアクセスするとMyPageDetailが表示される。
  • リンクは常に表示される。

ディレクトリ構成

pagesディレクトリに、MyPageの詳細ページを追加する。

├── pages
│   ├── Home.tsx
│   ├── MyPage.tsx
│   └── MyPageDetail.tsx

ソースコード

先ほどのApp.tsxにおいて

<Route path="/mypage" render={() => <MyPage />} />

の部分を、以下のように書き換える。

<Route
    path="/mypage"
    render={(props) => {
        const url = props.match.url;
        console.log(props)
        return (
            <Switch>
                <Route exact path={url} render={() => <MyPage />} />
                <Route path={`${url}/mypage-detail`} render={() => <MyPageDetail />} />
            </Switch>
        );
    }}
/>

注意点として、Router側をネストしているからといって<Link>には関係ないので、<Link>の中身はto = "/mypage/mypage-detail"と記載しなくてはならない。to="/mypage-detail"とするのはNG。

Mypage.tsx※MyPage内に、MyPageDetailへのリンクを表示

import { Link } from "react-router-dom";

export const MyPage = () => {
    return (
        <div>
            <h1>Mypage</h1>
            <Link to="/mypage/mypage-detail">link to MyPageDetail</Link>
        </div>
    );
};

url

renderに設定する無名関数にはpropsが渡されている。 ここで、props.match.urlにはRouteの引数に取ったpath="/mypage"の値が入っている。

propsの中身は、localhost:8000/mypageにアクセスしたときは以下で、

mypageのRouteのprops
mypageのRouteのprops

localhost:8000/mypage/mypage-detailにアクセスすると以下のようになっている。

"mypage/mypage-detailのRouteのprops"
mypage/mypage-detailのRouteのprops

locationは「今アクセスしているURL」に関わる情報が入っていそう。一方でmatchは、事前に<Route>で指定した値や、isExact(今アクセスしているURLと、事前に<Route>で指定したpathが等しいかどうか)等の比較に関わる値が入っていそう。

また、ネストが増えていくとコードが見にくくなっていくので、文のパラメータを別途配列で定義し、<Switch>文の中身をmap文で回すことも推奨されている。(参考文献参照)

<Link>タグではなくuseHistoryを使う方法

<a>タグではなく、ボタンをクリックした際などのイベントにページ遷移を紐づけたい場合は、useHistory()関数を利用する。

やり方は別記事に記載した。

tomiko0404.hatenablog.com

参考文献

www.udemy.com

react-router-dom@バージョン6

バージョン6のやり方を覚え次第追記予定。

おわりに

react-router-domを利用してルーティングを設定し、<Link>から遷移する基本のやり方をメモした。

関連記事

ページ遷移先に値を渡したい場合にやること

tomiko0404.hatenablog.com