はじめに
React×Typescript でルーティングする際、前画面の値を次の画面に渡す方法を解説する。
いろいろ調べたが、「これが正解」というのが出てこない。私が理解した方法の解説となる。
今回は4つの方法を解説する。
4つの解決策
- URL パラメータを使う(パスパラメータ)
- URL パラメータを使う(クエリパラメータ)
<Link>
コンポーネントに state を渡すuseHistory()
を使う
React-Router-Dom のバージョンについて(2022/4/23 追記)
本記事は react-router-dom のバージョン 5 を利用しています。
バージョン 6 からは、useHistory
ではなくて useNavigate
が推奨となっているようなので注意して下さい。
前提
以下の記事で作成したアプリを利用して検証する。
今回は 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
を入れなくても、ちゃんと<MyPageDetail />
が表示される。
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を文字列で定義している以上、安全じゃないよね」 ということが書かれている。
確かに、間違えてid
を間違えてib
としてしまい、path={`${url}/mypage-detail/:ib?`}
のように書いたとしてもコンパイルエラーにならないため、この点はバグを生みそう。
Template Literal Types などを使って解決する必要があるらしい。
URL パラメータで渡す方法(クエリパラメータ)
クエリパラメータの場合は、<Route>
の props にあるlocation
を利用する。
useLocation()
を利用することで、location オブジェクトを取得できる。
const { search } = useLocation()
const queryParams = new URLSearchParams(search)
const name = queryParams.get("name") // nameはクエリパラメータ名の例
期待する動作
localhost:8000/mypage/mapage-detail/100?name=taro&age=20
というように、パスパラメータ、クエリパラメータを入れてアクセスすると、MyPageDetail が表示される。画面上にもパラメータの値が表示される。(今回のポイント)- それ以外はパスパラメータの節と同様。
ソースコード
ルーティング側の設定
パスパラメータのときと同じ。 クエリパラメータはルーティングで設定する必要はない。
クエリパラメータの利用
利用する側のコンポーネントのソースは以下。
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
オブジェクトを利用する。
hash: ""
key: "6sdwd9"
pathname: "/mypage/mapage-detail/20/"
search: "?name=taro&age=20"
state: undefined
となっていた。
ここで使っていなかったstate
部分を使う。
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 に値が設定されたことがわかる。
ソースコード
遷移元
ポイントは<Link>
タグにstate
を設定していることのみ。事前に定義したmyState
を設定。
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
)することが必要。
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()を使う方法
location
やmatch
と横並びで、<Route>
コンポーネントの props に存在するhistory
オブジェクトを使う。
history はuseHistory()
という Hooks 関数で利用することができる。
const onClick = () => history.push("/mypage/mypage-detail", myState)
return <button onClick={onClick}>MyPageDetailへ</button>
history オブジェクトの中身
history.push
history.push( path, state)
は第一引数に path、第二引数に state を持つ関数。
公式ホームページに
新しいエントリを履歴スタックにプッシュします
とあるとおり、ページ遷移の履歴(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 はページ遷移のたびに変わるもの。」
おわりに
以下 4 つの方法で、ページ遷移先に値を渡す方法を理解した。
- URL パラメータを使う
- パスパラメータ
- クエリパラメータ
<Link>
コンポーネントに state を渡すuseHistory()
を使う
パスが文字列である件でも書いたように、<Link>
を使う場合でもhistory.push()
を使う場合でも、パスやパラメータの設定が文字列となっており、タイプミスを引き起こしそうな点は気になるので引き続き調査する。
関連記事
react-router-dom を使ったルーティング方法
参考文献
以上です。