devlog

フロントエンドエンジニアの技術ブログ

react-router-dom で ルーティングを実装する

React で ルーティングをする為のライブラリでデファクトスタンダードとなっているのが react-router です。 そして、Web 上で利用できるように DOM にバインディングができるライブラリが react-router-domです。

reacttraining.com

github.com

ちなみに、react-router と react-router-dom の違いが気になったのですが、この記事がとても参考になりました。

qiita.com

react-rourer-dom を追加する

react-rourer-dom と TypeScript を使用する為、型定義ファイルを追加します。

yarn add react-router-dom @types/react-router-dom

BrowserRouter でアプリケーションをラップする

History API を利用して URL と連動した React アプリケーションとなるように <BrowserRouter></BrowserRouter> で全体をラップします。 ここでは、公式ドキュメントのクイックスタートを倣って BrowserRouter as Router として Router という別名で使用します。

// src/index.tsx

import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter as Router} from 'react-router-dom'
import App from './App';

ReactDOM.render(
  <Router>
    <App />
  </Router>,
  document.getElementById("root")
);

route を設定する

route の設定を行う為に react-router-dom から、Switch と Route と Redirect コンポーネントをインポートします。 それぞれの コンポーネント の役割がこちらです。

コンポーネント 役割
Switch route の設定に応じてコンポーネントをレンダリングするエリア
Route パスに紐づくコンポーネントを定義する
Redirect リダイレクトを担うコンポーネント
// src/App.tsx

import React from 'react';
import {Switch, Route, Redirect} from 'react-router-dom'
import Home from './components/Home'
import About from "./components/About";

const App: React.FC = () => {
  return (
    <div className="App">
      <Switch>
        <Route path="/about">
          <About />
        </Route>
        <Route path="/">
          <Home />
        </Route>
        <Redirect to="/" />
      </Switch>
    </div>
  );
}

export default App;

設定したルーティングの内容で画面遷移を行う

実際に画面に遷移を行う為には、<a></a> ではなく、<Link></Link>コンポーネントを使用します。

// src/components/Home/index.tsx

import React from 'react'
import {Link} from 'react-router-dom'

const Home: React.FC = () => (
  <>
    <h1>This is Home</h1>
    <Link to="/about">About</Link>
  </>
);

export default Home

動的なルーティング

動的なルーティングを行う場合は <Route path="/dynamic/:id"></Route> のように :○○ とします。

import React from 'react';
import {Switch, Route, Redirect} from 'react-router-dom'
import Home from './components/Home'
import About from "./components/About";
import Dynamic from "./components/Dynamic";

const App: React.FC = () => {
  return (
    <div className="App">
      <Switch>
        <Route path="/dynamic/:id">
          <Dynamic />
        </Route>
        <Route path="/about">
          <About />
        </Route>
        <Route path="/">
          <Home />
        </Route>
        <Redirect to="/" />
      </Switch>
    </div>
  );
}

export default App;

/dynamic/:id に紐づくコンポーネントはこのようにしています。

// src/components/Dynamic/index.tsx

import React from 'react'
import {useParams, Link} from 'react-router-dom'

const Dynamic: React.FC = () => {
  const dynamicId = useParams<{id: string}>().id

  return (
    <>
      <h1>This is Dynamic: {dynamicId}</h1>
      <Link to="/">Home</Link>
    </>
  )
}

export default Dynamic

パスの :id をコンポーネント側で取得する際は、useParams() を使用します。 取得したいパラメータを useParams<{id: string}>() のようにジェネリクスで指定します。

こちらの記事のように RouteComponentProps という型定義をインポートして型結合する方法でも対応できます。

www.devlog.site

ライブラリの TypeScript の型定義ファイルを探す

予めライブラリ側で型定義ファイルが用意されている事もありますが、そうでない場合は DefinitelyTyped で探す事になります。 DefinitelyTyped は、OSS として個人が作成した型定義ファイルを集めているものです。ライブラリが公式に提供している型定義ファイルとは異なる為、完全に動作する保証はないです。

(それでも無い場合は、、、自作、、、、)

DefinitelyTyped の公式サイト上で探す

トップページ上部にある「TypeSearch」というリンクをクリック f:id:tkm_mur:20200107180421p:plain

遷移後の画面で探したい型定義ファイルのライブラリ名を入力します。 f:id:tkm_mur:20200107180650p:plain

CLI で探す

npm info @types/○○

もしくは、

yarn  info @types/○○

のように ○○ にライブラリ名をいれて叩けば型定義ファイルが提供されるいてるか調べる事ができます。

もしライブラリが公式に提供している場合は、上のコマンドを実行した際に「公式が提供しているので必要ない」といったメッセージが表示されます。 キャプチャは、DefinitelyTyped で axios の型定義を探した場合のものです。

f:id:tkm_mur:20200107182012p:plain

React コンポーネントをカスタムフックで Presentational Component と Container Component に分離する

Presentational Component(コンポーネント) と Container Component(コンテナー)

コンポーネント を Presentational Component(コンポーネント) と Container Component(コンテナー) の2種類に分類する事で、再利用性を高める事ができる考え方です。 これは、Presentational and Container Components という記事で説明されています。

ただし、記事の冒頭にこのように記載されています。

Update from 2019: I wrote this article a long time ago and my views have since evolved. In particular, I don’t suggest splitting your components like this anymore. If you find it natural in your codebase, this pattern can be handy. But I’ve seen it enforced without any necessity and with almost dogmatic fervor far too many times. The main reason I found it useful was because it let me separate complex stateful logic from other aspects of the component. Hooks let me do the same thing without an arbitrary division. This text is left intact for historical reasons but don’t take it too seriously.

フックの登場によってコンポーネントをこのような考え方で分割する事はおすすめしないという事です。 せっかく学んだので記事にしますが...フックがある以上このように考える必要はないのかなと... 思っています。

それぞれのざっくりとした役割を私はこのように解釈しています。

Presentational Component(コンポーネント)

見た目を担うコンポーネント

Container Component(コンテナー)

Presentational Component(コンポーネント)または、Container Component(コンテナー)を内包してデータや動作を提供する

カウンターコンポーネントを Presentational Component(コンポーネント) と Container Component(コンテナー)に分けて実装する

カウンターコンポーネントの Presentational Component(コンポーネント)

import React from 'react';

interface CounterProps {
  count: number;
  increment: () => void;
  decrement: () => void;
}

const Counter: React.FC<CounterProps> = ({count, increment, decrement}) => {
  return (
    <div>
      <div>Counter: {count}</div>
      <div>
        <button onClick={increment}>+1</button>
        <button onClick={decrement}>-1</button>
      </div>
    </div>
  );
};

export default Counter

カウンターコンポーネントの Container Component(コンテナー)

import React, {useState} from 'react'
import CounterComponent from '../../components/Counter/Counter'

const useCounter = (initialCount: number): [number, () => void, () => void] => {
  const [count, setCount] = useState(initialCount);

  const increment = () => {
    setCount(prevCount => prevCount + 1)
  }

  const decrement = () => {
    setCount(prevCount => prevCount - 1);
  };

  return [count, increment, decrement];
};

const CounterContainer: React.FC = () => {
  const [count, increment, decrement] = useCounter(0)
  return <CounterComponent count={count} increment={increment} decrement={decrement} />
}

export default CounterContainer

Qiita投稿 - Nuxt.js の generate で生成したページに 「/index.html」でアクセスすると JavaScript が実行されない問題

Qiitaへ「 Nuxt.js の generate で生成したページに 「/index.html」でアクセスすると JavaScript が実行されない問題」という記事を投稿しました。

qiita.com

manifest.json とは何か?

manifest.json とは

PWA として Web アプリケーションをインストールした時に、アプリケーションの振る舞い(アプリケーションの名前や説明、モバイル端末のホーム画面に表示するアイコンや、アドレスバーの色、スプラッシュスクリーンなど)を定義する JSON ファイルの事。 JSON ファイルではあるが、// でコメント記述する事ができる。

manifest.json の構成

{
  "short_name": "TODO",
  "name": "My TODO App",
  "icons": [
    {
      "src": "/images/icons-192.png",
      "type": "image/png",
      "sizes": "192x192"
    },
    {
      "src": "/images/icons-512.png",
      "type": "image/png",
      "sizes": "512x512"
    }
  ],
  "start_url": "/todo/?source=pwa",
  "background_color": "#FFFFFF",
  "display": "standalone",
  "scope": "/todo/",
  "theme_color": "#FFFFFF"
}

この他に様々なキーで振る舞いを定義をする事ができる。
詳細は、manifest.json - Mozilla | MDN を参照。

manifest.json の読み込み

HTMLの <head> ~ </head> 内において <link> タグを用いて読み込む。

<link rel="manifest" href="/manifest.json">

Nuxt.js の Assetsに格納されている画像を Vue 単一ファイルコンポーネントのdataプロパティで読み込む方法

Nuxt.js の Assetsに格納されている画像を Vue 単一ファイルコンポーネントのdataプロパティで読み込むには、require('@/assets/image/hoge.png')といった具合に、require()を使用する。

<template>
  <img :src="url" alt="" />
</template>

<script>
export default {
  data() {
    return {
      url: require('@/assets/image/hoge.png')
    }
  }
}
</script>

@ 2019 devlog