20191020のReactに関する記事は7件です。

Next.js 9.1: srcディレクトリとpublicディレクトリがサポートされました

Next.js 9.1がリリースされたので変更内容をざっくりまとめました。
詳しい内容はNextjs.orgのブログで公開されています。

まとめ

新機能

  • srcディレクトリのサポート
  • publicディレクトリのサポート

プレビュー(フラッグ有効で利用化)

  • Built-in CSSのサポート
  • 静的なエラーページ
  • Module / Nomodule

srcディレクトリのサポート

Next.js 9.1からはpagesディレクトリをsrcディレクトリにも設置できるようになりました。

今まではルートディレクトリにしかおけず、src/pages/を有効化するにはNext.jsのルートディレクトリをまるごとsrc/に変更する手法が使われていました。(next dev src/のようにしていた)
9.1からはこういった対応が不要で、src/pages/ディレクトリにおいたファイルが認識されるようになりました。

publicディレクトリのサポート

静的ファイルを置くディレクトリがstatic/からpublic/に変更になりました。
favicon.icopublic/favicon.icoに設置すると、example.com/favicon.icoで配信されるようになります。

従来はstatic/favicon.icoに設置したファイルはemample.com/static/favicon.icoで公開されており、ドメインルートでファイルを配信するのに一手間必要でした。この一手間が減り、より直感的な仕様になりました。

Built-in CSSのサポート

Next.jsでは同じくZeit製のCSS in JSライブラリである「styled-jsx」の利用が推奨されています。
しかし、Next.js中でCSSのインポートを行う、「next-css」パッケージが半分以上のNext.jsプロジェクトで利用されているらしく、その結果build-inでサポートしようという動きがあるそうです。

静的なエラーページ

Next.jsのエラーページは内部的に/_errorと呼ばれており、src/_error.jsにReactコンポーネントファイルを作成することでエラーページのカスタマイズが可能です。
エラーページを動的にレンダリングをする必要はないので、静的化を行っています。

Module / Nomodule

Next.jsはいわゆるモダンなJSでコードを書き、実際のブラウザではBabelを使ってトランスパイルされたコードが実行されます。
このコードはサポートするブラウザすべてで動作するJSファイルですが、モダンブラウザではトランスパイルが必要ない場合があります。
module/nomoduleパターンでは古いブラウザにはポリフィル付きのES5コードを提供する一方で、モダンブラウザにモダンJSを配信するために、信頼できるメカニズムを提供します。
この機能は実世界でのデータを収集するため、複数の大規模Next.jsアプリケーションで本番環境でのテストを行っているそうです。詳細は近々共有されるようです。

改善されたBundle Splitting

Next.jsでビルドを行うといくつかのJSファイルが生成されます。その中に含まれるcommons.jsには各ページ間で利用されている共通のモジュールが挿入されます。
今までのこの共通のモジュールの判定は比率ベースで行われており、すべてのページの50%で使われているモジュールがcommons.jsに含まれていました。しかし、今回新しく複数のJSファイルに分割することで最適化されたBundle Splittingが行われるようになるようです。

こちらもmodule/nomoduleの変更と同じく現在テスト中とのことで、結果は後々公開されるようです。

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

リッチテキストエディタライブラリSlateJSの状態・DOM管理方法

https://scrapbox.io/tuttieee/SlateJS%E3%82%B3%E3%83%BC%E3%83%89%E3%83%AA%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0 から転載

リッチテキストエディタライブラリSlateJSを使っていて、
このライブラリがどのようにデータを管理し、DOMに反映し、まだDOMの変更をデータに反映しているのか
また、Controlled Componentとはどう違うのか
が気になったので、中身を読んでみたメモ。

Slateの状態管理・React連携

  • slate, slate-reactという2つのパッケージがメイン
    • slate: エディタの内部状態を管理する。Editorクラス(slate/src/controllers/editor.js)がメインのインタフェース。
    • slate-react[React]バインディング。slateEditorクラスに格納された内部表現を使って[VirutalDOM]を構築する(src/components/*.js)。また、イベントをハンドリングしてEditorクラスのコマンドを叩く。総じて、通常のReactのControlled Componentを作るときにやる処理を巻き取ってくれる(VDOMの宣言+イベントハンドリング→state更新)。Controlled Componentにおけるstate更新が、slateではEditorのコマンド実行になる。
  • slateパッケージ
    • Editorクラス(controllers/editor.js): Valueを保持したり、プラグイン機構を提供したりと、slateエディタの基盤となる汎用的なフレームワークを提供する。具体的な処理のほとんどはプラグインに抜き出されている。
    • plugins: プラグイン機構はユーザに解放されているが、コア機能の多くもプラグインを利用してslateに組み込まれている。
    • models: データモデルの定義もslateパッケージにある。
    • commands: 組み込みコマンド
  • slate-reactパッケージ
    • src/components/editor.jsEditorクラスが、Reactでslateを使うときのエントリポイント。<Editor>として使うときのクラス。
      • 内部でslateEditorクラスのインスタンスを保持している(this.controller)。
      • renderでは、RenderEditorで外側を修飾した後は、Contentクラスに移譲。
    • src/components/content.js
      • renderでNodeコンポーネントにdocumentを渡して描画する。Nodeは再帰的にValueツリーをrenderする。
        • ユーザが適宜カスタマイズできるよう、renderBlock, renderInline, renderTextNodeで呼ばれる。
      • 子Nodeに起きたイベントは全て、Content(ルートノード)で拾う。Contentクラスに全てのイベントハンドラが定義されている(this.handlers)。this.handlersは結局イベントの種類情報(onInputBeforeなど)を付与して、this.props.onEventを呼ぶ
    • src/components/editor.jsEditor.onEvent: 上記Contentからコールバックされる。onEventはさらにthis.controller.runを呼ぶ。
    • this.controller.runslateEditor.run。登録されたプラグインからイベントハンドラを呼び出す。
    • slate-reactpluginsでDOMイベントのハンドラを定義してある。これらのプラグインが上記this.controllerに登録されているため、エディタを構成するDOMで起きたDOMイベントは全てこのハンドラに送られる。
      • 流れを整理すると、
        1. NodeでDOMイベント発生
        2. Reactのイベントバブリングにより、Content(のContainerコンポーネント)にDOMイベントが送られる
        3. Content.handlersで捕捉
        4. Contentの親であるEditoronEventにコールバック
        5. Editor.onEventからthis.controller.run=slate.Editor.runをコール
        6. slate.Editor.runがプラグインのイベントハンドラをコール
    • さて、slate-reactのプラグインのうち、AfterPlugin(src/plugins/dom/after.js)を見てみる。これは上記のControlled ComponentでのアナロジーにおけるsetState相当のことを担当している。
      • AfterPluginonBeforeInputが、各種のイベントに応じてeditorのコマンドをコールしている。
        • 例えば、'insertText'イベントの時はeditor.insertTextAtRange
        • このイベントはネイティブDOMイベントである。ネイティブのbeforeInputイベントのevent.eventTypehttps://triple-underscore.github.io/input-events-ja.html#dom-inputevent-inputtype を参照。多種多様なイベントが定義されており、HTML5のレイヤでcontenteditableを利用したリッチテキスト編集サポートが意識されている気がする。

単純なinput, textareaのControlled Componentとの違い

  • Controlled Componentは、以下の3要素からなる
    • 1. データを何らかのstoreに保持(component state, useState hook, redux storeなど)
    • 2. データ→VDOMのマッピングの宣言(render関数)
    • 3. VDOMのイベントハンドラからデータを更新(this.setState、hookのsetXX、dispatch(action)など)
  • slateのモデルでは、この1, 2, 3が以下に対応する
    • 1. データをslateパッケージのEditorに保持。その際のデータモデルもslateに定義される
    • 2. データ→VDOMのマッピングの宣言(render)はslate-reactパッケージのEditor componentとその子たちで行われる。ユーザは適宜render*でrenderに介入できる
    • 3. 2. で定義したVDOMのイベントハンドラから、プラグイン機構を介して、slate-reactのプラグインに定義されたイベントハンドラに処理が移譲される。これらのイベントハンドラからslateEditorの各種コマンドが呼ばれる。コマンドはデータを書き換える操作を行う。
      • コマンドのデータ書き換えは、さらにOperationという操作を通して行われるが、ここでは触れない
  • 以上のように、Controlled Componentではユーザが自分で全てを書ける部分を、slateが引き取ってくれている。contenteditableを使う場合、この部分の処理は圧倒的に煩雑になるのであろう。
  • この設計の帰結として、自由度がControlled Componentより低くなってしまっている。
    • データ管理・変更
      • Controlled Componentであれば、stateの持ち主はルートに近いノード。また、そこからstateを直接変更してもVDOMはそれを正しく反映する
      • slate-reactの場合、(Controlled Component風にvalue, onChangeが公開されているとはいえ)、勝手にvalueを変更するとエディタが壊れる。valueの変更はcommandで行うしかない。valueの持ち主は親Componentのように見えるが、持ち主にvalueを直接変更する自由はない。
        • slate-reactのvalue, onChangeはあくまでイベントハンドリングや保存用データを取得するためのハッチであって、Controlled Componentのようにvalueの直接的な変更権まで親に移譲されるものではないように思われる。 VDOMのrender
      • Controlled Componentであれば、データをどのようにViewにマッピングするか(renderの設計)は完全に自由
      • slateではrender*フックを使えるのみ

SlateJSの"command"という設計について

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

React + Next.jsで画像をimportする

困ったこと

Reactで画像のパスが読み込めない

logo.tsx
const logo = require('../../assets/images/logo.png')
// =>
// [ error ] ./src/assets/images/logo.png 1:0
// Module parse failed: Unexpected character '�' (1:0)
// You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See 
// https://webpack.js.org/concepts#loaders
// (Source code omitted for this binary file)

原因

画像ファイルのパスをパースできない

解決

  1. url-loaderを追加する
  2. next.config.js にその設定を追加する
yarn add url-loader
next.config.json
module.exports = withSass(withTypescript({
    [...]
    // ここから
    config.module.rules.push({
      test: /\.(png|jpg|gif|svg)$/,
      use: {
        loader: 'url-loader',
        options: {
          limit: 100000
        }
      }
    })
    // ここまで

    return config;
  }
}));

画像の読み込み方について

importよりrequire使った方がよさそう。

logo.tsx
const logo = require('../../assets/images/logo.png')

参考ページ

https://whoisryosuke.com/blog/2018/nextjs-tip-using-media-in-css/

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

ReactでHelloWorldしてから、ちょっとずつ足していく #1

はじめに

tic-tac-toeのチュートリアルをTypescriptで、というのは結構たくさんあって参考にしながら、最終までなんとかやってみました。
きっかけは、@m0aさんのTypeScriptを使ってreactのチュートリアルを進めると捗るかなと思った(実際捗る)で、わからないなりに最後までやってみて、なんとか動くところまでいけました。

Typescriptへの書き換えも楽しくなってきたので、次はもう少し理解を深めようかということでHello!Worldからやってみるかと思います。

新しい環境を作る

npx create-react-app [folder] --typescript

して、フォルダを準備します。このとき、informationSiteとかそんな感じのフォルダ名をつけようとするとエラーになるので、information_siteといった具合に修正することになりました。

既存ファイルを修正する

今回はApp.tsxを修正していくことにしました。
デフォルトでできるファイルにもApp.tsxを修正したらリロードしてね的なことが書いてあるので素直に従います。

JSXを埋め込む作業で、実際はfunctionを作ってそこで要素のレンダリングをして描画という流れになっていますが、こちらについては読んで納得したので、飛ばします。

  • 親要素から子要素を呼び出す
  • 子要素は受け取ったパラメータに沿って内容をレンダリングして、その結果を返す
  • 親要素は受け取ったレンダリング内容をまとめてReactDOMに返す
  • ReactDOMは、戻ってきた内容に従って、更新の有無を判断して描画する

という流れだということはなんとなくわかってきました。

関数呼び出し

次は関数の呼出の形に修正します。

Typescriptでは、interfaceを定義することでその要素にどのような内容が含まれるかを明示できます。

C言語の構造体のようなものという理解なので、とりあえず、今回は名前要素の書換をするということで、Welcomeで使うWelcomeProps(Welcome Property)を定義しました。

interface WelcomeProps {
  name: string;
}

function Welcome(props: WelcomeProps) {
  return <h1>Hello, {props.name}</h1>;
}

const App: React.FC = () => {
  return (
    <div className="App">
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
      <Welcome name="Everyone" />
    </div>
  );
}

export default App;

親要素からは、タグについているプロパティがPropsとして渡ってくるので、その内容が合ってないとエラーになります。

例えば、Welcomeのnameプロパティをcheckプロパティに書き換えてみます。

<Welcome check="Sara" />

当然のことながら(?)コンパイル時点で次のようなエラーメッセージが出て終わります。

TypeScript error in src/App.tsx(21,8):

Type '{ check: string; }' is not assignable to type 'IntrinsicAttributes & WelcomeProps'.

Property 'check' does not exist on type 'IntrinsicAttributes & WelcomeProps'. TS2322

プロパティ 'check' は、WelcomePropsに含まれていないので、事前チェックに引っかかります。

型チェックがあることでここで止まってくれますが、型チェックされる機会がないと「なぜか動かない」がまれによくあり、見つけたときの「なんでこんなところ間違ってんだ…」に悩まされずに済むのではないかと思います。

コピペするとよくあるアレです。

もう少しだけ進んだ要素

次に、stateとライフサイクル に進みます。

チュートリアルでは時計を作り続けていますが、使っているファイルはそのままHello, Worldです。

特に意味はありません。

propsを設定するのはわかったので、次はstateを使ってみようと軽い気持ちで修正をいれました。

動作イメージとしては

  • Welcomeのnameプロパティに何も設定しなかったときにeveryOneといれて、適当にお茶を濁す
  • そうでなければ、もらったプロパティをそのまま名前として設定する

です。そのような動作を付け加えました。

import React from 'react';
// import logo from './logo.svg';
import './App.css';

interface WelcomeProps {
  name: string;
}

interface WelcomeState {
  name: string;
}

class Welcome extends React.Component<WelcomeProps> {
  constructor(props: WelcomeProps) {
    super(props);

    this.state = {name: props.name ? props.name : 'everyOne', };
  }

  render() {
    return <h1>Hello, {this.state.name}</h1>;
  }
}

const App: React.FC = () => {
  return (
    <div className="App">
      <Welcome name=""/>
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
      <Welcome name="Everyone" />
    </div>
  );
}

export default App;

TypeScript error in src/App.tsx(27,35):

Property 'name' does not exist on type 'Readonly<{}>'. TS2339

動きません。

WelcomeStateなんていう、stateに使いそうな定義まで書いてるのに!
とはいえ、「書けばいいんじゃないかな」くらいの話で雰囲気で書いてます。

そして、理解しないまま書いている以上、このエラーメッセージの意味がわかっていないので、リファレンス調べました。

React.Componet # インスタンスプロパティ

クラス化するときに渡せるパラメータは、2つあって、それはどうやらプロパティ(props)とステート(state)ということらしいです。

class Welcome extends React.Component<WelcomeProps> {  

このときの引数は、本来は

class Welcome extends React.Component<props,state> {  

となるべきでした。

しかし、このとき書いた内容は、stateが省略されていたので、代わりに{}(空オブジェクト)を指定してくれています。

エラーメッセージが「Readonly{}にはnameなんてプロパティないよ」ということで、そりゃ確かに空オブジェクトにnameなんていうプロパティはあるわけないなあとエラーの意味が理解できました。

次のように書き換えます。

class Welcome extends React.Component<WelcomeProps, WelcomeState> {

今度は問題なくコンパイルされて、画面も表示されています。

せっかく登録していたinterface WelcomeStateにも使い道がでてきてよかった。

残り部分

残りの部分は、現状それほど使う場所でもないので、斜め読みしながらまとめました。

そのうち必要になるとは思いますが、ここまでの内容を何度も見直しながらまとめたので、これ以上詰め込まなくてもいいやというのが本音。

ライフサイクルメソッド

今回は必須メソッドであるconstructorとrenderしか扱いませんでしたが、コンポーネントには他にも開始終了のタイミングで呼び出されるメソッドやいくつかのメソッドが存在します。

  • componentDidMountはその要素がDOMツリーに追加されて描画が始まるときに呼ばれる
  • componentWillUnmountはその要素がDOMツリーから削除されるときに呼ばれる

DOMツリーという言葉があるかわかりませんが、各クラスなどはすべてReactが作った土台というか幹から枝葉のようにぶらさがるイメージなので、このように表現しました。

それぞれ、初期化と後処理に使えるメソッドだけど、今のところは存在だけ覚えておきます。

AndroidのonCreateとonDestroyみたいなものだと考えて、onResumeやonPauseないのかはあとで調べます。

非同期、直接変更不可、マージ

stateに対する注意点は、とにかく変更するならばsetStateするしないとダメなようです。

特にブラウザ上で見るモノは描画以外に、データ通信など非同期で行われるイベントがたくさんあるので、同期を取っているとレスポンスが悪くなります。

その辺りのこともあって、非同期処理が基本でさらには現在処理している部分以外でもstateにデータをくっつける可能性がある

まとめ

  • Typescriptは、interfaceで型定義できるので、そちらをちゃんと記述する
  • エラーが出てもメッセージをよく読む
  • state使って処理するならば、なにがあってsetStateを使う

次は、イベント処理からですが、続くかどうかはわかりません。

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

Reactのdebounceとthrottleのhooksをそれぞれ試してみた

Reactのdebounceとthrottleをhooksがないかとそれぞれググってみて検索の上の方に出てきたのをただ試してみただけの投稿です、よろしくお願いします :bow:

私が試したコードはこちらです

https://github.com/okumurakengo/react_debounce_throttle

debounce

debounceはこちらを試しました

xnimorz/use-debounce - github

yarn add use-debounce
import React, { useState } from "react";
import { useDebounce } from "use-debounce";

const App = () => {
  const [text, setText] = useState("");
  const [value] = useDebounce(text, 1000);

  return (
    <>
      <input onChange={e => setText(e.target.value)} />
      <p>Actual value: {text}</p>
      <p>Debounce value: {value}</p>
    </>
  );
};

export default App;

PrLe77StJV.gif

debounceが簡単に試せました :grinning:

throttle

throttleはこちらを試しました

bhaskarGyan/use-throttle - github

yarn add use-throttle
import React, { useState } from "react";
import { useThrottle } from "use-throttle";

const App = () => {
  const [text, setText] = useState("");
  const value = useThrottle(text, 1000);

  return (
    <>
      <input onChange={e => setText(e.target.value)} />
      <p>Actual value: {text}</p>
      <p>Throttle value: {value}</p>
    </>
  );
};

export default App;

QQNGWIB61F.gif

throttleが簡単に試せました :grinning:


以上です。みていただいてありがとうございました。m(_ _)m

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

【React】Context APIとHooksでReduxを駆逐する!

はじめに

進撃の巨人もついに最終回が見えてきましたね。来年には完結しそう。

さてタイトルはReduxを駆逐する!とか書いてますが、エレンしたかっただけです。すいません。
今回は業務でContext APIとHooksを使ってみて、「あれ?Reduxいらなくね?」「Hooksは神」と感じることが多かったので筆をとろうと思った次第です。

筆者のReact歴

React初めて半年のペーペーです。
Reduxは個人で簡単なCRUDアプリを作った時しか使ってなくて業務では使ってないので、Reduxじゃないと辛いケースやReduxを使っていて辛いケースについてはあまり詳しくないです。
なので偏りはあるかもしれません(大規模になってくるとReduxじゃなきゃやってられない、みたいな話も聞きますが実際どうなんだろう)。

Context APIとHooksの概要

Context API

Contextを使うとコンポーネントツリーの中でグローバルにデータを管理することができます。
親コンポーネントで定義したContextを子や孫コンポーネントで簡単に参照できます。

Hooks

今までStateを管理したり、ライフサイクルメソッドを使ったりできるのはクラスコンポーネントだけでしたが、それを関数コンポーネントでも可能にしたのがHooksです。
関数コンポーネントとHooksを使うとクラスコンポーネントに比べて記述量が少なく、可読性の高いシンプルなコードが書けます(特にクラスコンポーネントだとthisの取り扱いがややこしい…)。

Context APIとHooksの使い方の実例

今回記事を書くにあたって「自己紹介サイト」を作ってみたので、そのコードをもとにContext APIとHooksの使い方を解説していきます。

自己紹介サイトは以下のような感じです。
画面収録 2019-10-19 20.04.05.mov.gif

コードは以下で見れます。
https://github.com/yutaroadachi/i_am

Contextを使うときはコンポーネントツリーのトップでContextを定義します。

IAm.es6.jsx
export const IAmContext = createContext();

Contextを下の階層で使うにはContext.Providerを使います。
valueにグローバルに管理したいデータを渡します。

ここではさらにuseReducer Hooksを使ってStateとReducerを作成し、それらをvalueに渡しています。
useReducerの第一引数にはStateを書き換えるReducerを、第二引数にはStateの初期値を渡します。
ここではReducerにsaveとdeleteアクションを定義し、初期値は空文字にしています。

IAm.es6.jsx
const nameReducer = (name, action) => {
    switch (action.type) {
      case "save":
        return action.name;
      case "delete":
        return "";
      default:
        return name;
    }
  };
  const hobbyReducer = (hobby, action) => {
    switch (action.type) {
      case "save":
        return action.hobby;
      case "delete":
        return "";
      default:
        return hobby;
    }
  };

  const [name, nameDispatch] = useReducer(nameReducer, "");
  const [hobby, hobbyDispatch] = useReducer(hobbyReducer, "");

const iAmContext = { name, nameDispatch, hobby, hobbyDispatch };

  return (
    <div>
      <IAmContext.Provider value={iAmContext}>
        <IAmHeader />
        <IAmBody />
      </IAmContext.Provider>
    </div>
  );
};

コンポーネントツリーの中でContextを参照したいときは参照したいコンポーネントの中でuseContext Hooksを使います。以下のたった1行のコードを書くだけでOKです。

IAMBody.es6.jsx
const iAmContext = useContext(IAmContext);

ここではコンポーネントツリーのトップでvalueにオブジェクトとしてデータを渡しているのでiAmContextを普通のオブジェクトのように扱うことができます。

// name Stateの値を参照
iAmContext.name

// name Stateの値を削除
iAmContext.nameDispatch({
      type: "delete"
    });

以上のようにContext APIとHooksを使えば簡単にグローバルにデータを管理できます。

Context APIとHooksを使うメリット、デメリット

ここからは業務で実際に使ってみて感じたContext APIとHooksを使うメリット、デメリットについて書いていきます。

メリット

  • 記述量が少なくて済む
  • 可読性が高く、シンプルなコードが書ける
  • Contextがローカライズされているため、コンポーネント指向で書ける

デメリット

  • Contextをコンポーネントツリーの中で乱立させるとコードの見通しが悪くなる
  • Reduxのような非同期の鉄板ライブラリがない(redux-thunkやredux-saga)

総じてシンプルなコードが書けるのがメリットだと思います。
あと個人的にはReduxのようにstoreを集中管理するのではなく、コンポーネントツリー(ドメイン)ごとに管理する方がコンポーネント指向で書けて好みです。

デメリットも設計に気をつけたり、複雑なことをやろうとしすぎたりしなければ問題にならない気がします。

Contextをコンポーネントツリーのデータベースのように扱い、子や孫以下の階層ではそれを単純に参照し、フォームなどで編集するときはフォーム専用のLocal Stateを定義することで切り分けて管理すれば書きやすく、読みやすいコードになるんじゃないかなと思います。

おわりに

Context APIとHooksを使えば、大体のことがシンプルに書けるので、これからは関数コンポーネントで書くのがReactの鉄板になりそうです。Hooks本当に書きやすい。

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

Reactで直接URLをたたくと404になる時の対応

Reactで直接URLをたたくと404になる時の対応

前提

  • react-router-domでルーティングしている
index.html
<html>
  <body>
    <div id="root"></div>
  </body>
</html>
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));
App.js
import React, { Component } from 'react'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <div className="App">
          <Switch>
            <Route exact path='/' component={HogeComponent} />
            <Route exact path='/Fuga' component={FugaComponent} />
          </Switch>
        </div>
      </BrowserRouter>
    );
  }
}

状況

  • うまくいくケース
    • https://hogeapp.com/にアクセス → HomeComponentが表示される
    • /Fugaのリンクを踏む
    • https://hogeapp.com/Fugaに遷移 → FugaComponentが表示される
  • うまくいかないケース
    • https://hogeapp.com/Fugaが表示された後リロードする → 404ページが表示される
    • ブラウザでhttps://hogeapp.com/Fugaを直接叩く → 404ページが表示される

原因

  • リソースが存在しないと直接URLにアクセスしても404になるから
  • うまくいった時の流れ
    • https://hogeapp.com/にアクセス
    • index.htmlを読み込む
    • react-router-domのルーティングによりHomeComponentが表示される
    • /Fugaのリンクを踏む
    • react-router-domのルーティングによりFugaComponentが表示される
  • うまくいかないときの流れ
    • https://hogeapp.com/FugaのURLに直接アクセス
    • /Fugaリソースを探そうとするが、見つからない
    • 404ページ表示

対策

firebase.json
{
  "hosting": {
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}
  • 上記のように全要素index.htmlに飛ばすならreact-router-domのルーティングで404コンポーネントを返却するようにする
    • もともとWebサーバー側で設定していた404.htmlは機能しなくなるので
App.js
import React, { Component } from 'react'
import { BrowserRouter, Switch, Route } from 'react-router-dom'
class App extends Component {
  render() {
    return (
      <BrowserRouter>
        <div className="App">
          <Switch>
            <Route exact path='/' component={HogeComponent} />
            <Route exact path='/Fuga' component={FugaComponent} />
            <Route component={NotFound} />
          </Switch>
        </div>
      </BrowserRouter>
    );
  }
}

参考

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