- 投稿日:2019-10-20T23:21:31+09:00
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.ico
をpublic/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の変更と同じく現在テスト中とのことで、結果は後々公開されるようです。
- 投稿日:2019-10-20T19:22:10+09:00
リッチテキストエディタライブラリSlateJSの状態・DOM管理方法
リッチテキストエディタライブラリSlateJSを使っていて、
このライブラリがどのようにデータを管理し、DOMに反映し、まだDOMの変更をデータに反映しているのか
また、Controlled Componentとはどう違うのか
が気になったので、中身を読んでみたメモ。Slateの状態管理・React連携
- slate, slate-reactという2つのパッケージがメイン
slate
: エディタの内部状態を管理する。Editor
クラス(slate/src/controllers/editor.js
)がメインのインタフェース。slate-react
[React]バインディング。slate
のEditor
クラスに格納された内部表現を使って[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.js
のEditor
クラスが、Reactでslateを使うときのエントリポイント。<Editor>
として使うときのクラス。
- 内部で
slate
のEditor
クラスのインスタンスを保持している(this.controller
)。- renderでは、
RenderEditor
で外側を修飾した後は、Content
クラスに移譲。src/components/content.js
- renderで
Node
コンポーネントにdocument
を渡して描画する。Node
は再帰的にValueツリーをrenderする。
- ユーザが適宜カスタマイズできるよう、
renderBlock
,renderInline
,renderText
がNode
で呼ばれる。- 子Nodeに起きたイベントは全て、
Content
(ルートノード)で拾う。Content
クラスに全てのイベントハンドラが定義されている(this.handlers
)。this.handlers
は結局イベントの種類情報(onInputBefore
など)を付与して、this.props.onEvent
を呼ぶsrc/components/editor.js
のEditor.onEvent
: 上記Contentからコールバックされる。onEvent
はさらにthis.controller.run
を呼ぶ。this.controller.run
はslate
のEditor.run
。登録されたプラグインからイベントハンドラを呼び出す。slate-react
のplugins
でDOMイベントのハンドラを定義してある。これらのプラグインが上記this.controller
に登録されているため、エディタを構成するDOMで起きたDOMイベントは全てこのハンドラに送られる。
- 流れを整理すると、
Node
でDOMイベント発生- Reactのイベントバブリングにより、
Content
(のContainer
コンポーネント)にDOMイベントが送られるContent.handlers
で捕捉Content
の親であるEditor
のonEvent
にコールバックEditor.onEvent
からthis.controller.run
=slate.Editor.run
をコールslate.Editor.run
がプラグインのイベントハンドラをコール- さて、
slate-react
のプラグインのうち、AfterPlugin
(src/plugins/dom/after.js
)を見てみる。これは上記のControlled ComponentでのアナロジーにおけるsetState相当のことを担当している。
AfterPlugin
のonBeforeInput
が、各種のイベントに応じてeditor
のコマンドをコールしている。
- 例えば、
'insertText'
イベントの時はeditor.insertTextAtRange
- このイベントはネイティブDOMイベントである。ネイティブのbeforeInputイベントの
event.eventType
は https://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
のプラグインに定義されたイベントハンドラに処理が移譲される。これらのイベントハンドラからslate
のEditor
の各種コマンドが呼ばれる。コマンドはデータを書き換える操作を行う。
- コマンドのデータ書き換えは、さらにOperationという操作を通して行われるが、ここでは触れない
- 以上のように、Controlled Componentではユーザが自分で全てを書ける部分を、slateが引き取ってくれている。contenteditableを使う場合、この部分の処理は圧倒的に煩雑になるのであろう。
- Controlled Componentでもredux-formのようなラッパーがある。slateはこれらのラッパーと同じレイヤにいると考えられる。
- inputやtextareaであれば、ユーザが自分でControlled Componentを書けるし、楽をしたければライブラリを使える。一方、contenteditableは自分で書くという選択肢はほとんど候補に挙がらないほど複雑でライブラリ利用一択なのであろう。
- contenteditableの苦しみ: https://www.bokukoko.info/entry/2017/10/08/154950
- とは言っても、自分で頑張る選択肢ももちろんある。[LINEBlog]や[note]は頑張ったようだ。https://engineering.linecorp.com/ja/blog/contentable-development-of-line-blog-apps/ https://note.mu/ct8ker/n/n037f6ba3c318
- この設計の帰結として、自由度が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"という設計について
- HTML5に
execCommand
が定義されている。contenteditableと同時に使うといい感じらしい- slateの、「commandを通して対象を操作する」デザインはこの辺りから来ているのかもしれない。
Block
,Inline
,Text
といったデータ構造もDOMを真似ている。全体として、DOMのアナロジーで書けるようにデザインされている?- コマンドクエリ責務分離 も関係? https://docs.microsoft.com/ja-jp/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/apply-simplified-microservice-cqrs-ddd-patterns
- SlateJSのcommand/query: https://docs.slatejs.org/guides/commands-and-queries
- 投稿日:2019-10-20T18:18:39+09:00
React + Next.jsで画像をimportする
困ったこと
Reactで画像のパスが読み込めない
logo.tsxconst 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)原因
画像ファイルのパスをパースできない
解決
- url-loaderを追加する
- next.config.js にその設定を追加する
yarn add url-loadernext.config.jsonmodule.exports = withSass(withTypescript({ [...] // ここから config.module.rules.push({ test: /\.(png|jpg|gif|svg)$/, use: { loader: 'url-loader', options: { limit: 100000 } } }) // ここまで return config; } }));画像の読み込み方について
importよりrequire使った方がよさそう。
logo.tsxconst logo = require('../../assets/images/logo.png')参考ページ
https://whoisryosuke.com/blog/2018/nextjs-tip-using-media-in-css/
- 投稿日:2019-10-20T17:48:53+09:00
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に使いそうな定義まで書いてるのに!
とはいえ、「書けばいいんじゃないかな」くらいの話で雰囲気で書いてます。そして、理解しないまま書いている以上、このエラーメッセージの意味がわかっていないので、リファレンス調べました。
クラス化するときに渡せるパラメータは、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で型定義できるので、そちらをちゃんと記述する
- エラーが出てもメッセージをよく読む
- どんな引数を取るのか知らべるならば、リファレンスを調べてみる
- ドキュメントのAPI REFERENCE
- state使って処理するならば、なにがあってsetStateを使う
次は、イベント処理からですが、続くかどうかはわかりません。
- 投稿日:2019-10-20T16:57:10+09:00
Reactのdebounceとthrottleのhooksをそれぞれ試してみた
Reactのdebounceとthrottleをhooksがないかとそれぞれググってみて検索の上の方に出てきたのをただ試してみただけの投稿です、よろしくお願いします
私が試したコードはこちらです
https://github.com/okumurakengo/react_debounce_throttle
debounce
debounceはこちらを試しました
yarn add use-debounceimport 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;debounceが簡単に試せました
throttle
throttleはこちらを試しました
bhaskarGyan/use-throttle - github
yarn add use-throttleimport 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;throttleが簡単に試せました
以上です。みていただいてありがとうございました。m(_ _)m
- 投稿日:2019-10-20T00:53:57+09:00
【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の使い方を解説していきます。
コードは以下で見れます。
https://github.com/yutaroadachi/i_amContextを使うときはコンポーネントツリーのトップでContextを定義します。
IAm.es6.jsxexport const IAmContext = createContext();Contextを下の階層で使うには
Context.Provider
を使います。
valueにグローバルに管理したいデータを渡します。ここではさらに
useReducer
Hooksを使ってStateとReducerを作成し、それらをvalueに渡しています。
useReducer
の第一引数にはStateを書き換えるReducerを、第二引数にはStateの初期値を渡します。
ここではReducerにsaveとdeleteアクションを定義し、初期値は空文字にしています。IAm.es6.jsxconst 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.jsxconst 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本当に書きやすい。
- 投稿日:2019-10-20T00:51:55+09:00
Reactで直接URLをたたくと404になる時の対応
Reactで直接URLをたたくと404になる時の対応
前提
- react-router-domでルーティングしている
index.html<html> <body> <div id="root"></div> </body> </html>index.jsimport React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; ReactDOM.render(<App />, document.getElementById('root'));App.jsimport 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ページ表示
対策
- Webサーバーの設定で
index.html
へのリライト指定をする- 例えばFirebase Hostingなら
firebase.json
ファイルの hosting 内で rewrites セクションを定義できるfirebase.json{ "hosting": { "rewrites": [ { "source": "**", "destination": "/index.html" } ] } }
- 上記のように全要素index.htmlに飛ばすならreact-router-domのルーティングで404コンポーネントを返却するようにする
- もともとWebサーバー側で設定していた404.htmlは機能しなくなるので
App.jsimport 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> ); } }参考