SPA(Single Page Application)だけど別ウィンドウでも開きたい、かつ、起動元の親ウィンドウのデータを起動された子ウィンドウで参照したい場合の方法です。
候補は以下
- URL パラメータ渡し
- LocalStorage
- グローバル変数
前提
以下で作ったものをベースに作業(Vite 4.0.0 + React 18.0.9)しました。
今回は React Router をインストールして、別 URLに遷移するときに値渡しを試しました。
検証コード
遷移の定義。今回は /
から /child1
へ遷移しています。
const router = createBrowserRouter(
createRoutesFromElements(
<Route path="/" element={<App />}>
<Route path="child1" element={<Child1 />} />
</Route>,
),
)
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>,
)
親コンポーネント(App
)。今回検証した3パターン全部含めています。説明は後ほど個別に。
import { useState } from 'react'
import { Outlet } from 'react-router-dom'
import './App.css'
import reactLogo from './assets/react.svg'
function App() {
const [count, setCount] = useState(0)
return (
<div className="App">
<div>
<a href="https://vitejs.dev" target="_blank" rel="noreferrer">
<img src="/vite.svg" className="logo" alt="Vite logo" />
</a>
<a href="https://reactjs.org" target="_blank" rel="noreferrer">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
{/* URL パラメータに指定する */}
<div>
<button
onClick={() =>
window.open(
`http://localhost:5173/child1?counter=${count}`,
'Child1',
)
}
>
/Child1 へ - URLパラメータ渡し
</button>
</div>
{/* LocalStorage に指定する */}
<div>
<button
onClick={() => {
localStorage.setItem('params', JSON.stringify({ count }))
window.open('http://localhost:5173/child1', 'Child1')
}}
>
Child1 へ - LocalStorage 経由
</button>
</div>
<div>
<NewWindow
url="http://localhost:5173/child1"
name=""
params={{ count }}
></NewWindow>
</div>
</div>
<hr />
<div>
<Outlet />
</div>
</div>
)
}
function NewWindow(props: { url: string; name: string; params: any }) {
const w = window.self as typeof window & { openerParams: string }
w.openerParams = JSON.stringify(props.params)
const onClickHandler = () => w.open(props.url, props.name)
return <button onClick={onClickHandler}>Child1 へ - グローバル変数</button>
}
export default App
子コンポーネント。渡された値を表示するだけです。
import { useLocation } from 'react-router-dom'
export default function Child1() {
const location = useLocation()
const localStorageParam = localStorage.getItem('params')
const opener = window.opener
return (
<>
<div>Child1</div>
<table border={1}>
<tbody>
<tr>
<td>Window Name</td>
<td>{window.name}</td>
</tr>
<tr>
<td>URL params</td>
<td>{location.search}</td>
</tr>
<tr>
<td>LocalStorage</td>
<td>{localStorageParam}</td>
</tr>
<tr>
<td>グローバル変数</td>
<td>{opener.openerParams}</td>
</tr>
</tbody>
</table>
</>
)
}
ここから結果です。
1. URLパラメータ渡し
これは言うまでもないですね。
<button
onClick={() =>
window.open(
`http://localhost:5173/child1?counter=${count}`,
'Child1',
)
}
>
/Child1 へ - URLパラメータ渡し
</button>
結果です。左が親、右が起動後の子です。
親の count 値 5 が URL パラメータとして渡っており、react-router-dom の useLocation
から取得できています。
この方法は簡単につきます。
デメリットはパラメータが長い場合でしょうか。とはいえ IE 問題もなくなったので 4000-5000 文字くらいは URL文字列としてはいけたという記事もあったので、これで十分な場合な場合も多そう。あとは、利用者がURLコピーするような場合は長すぎはだめですね。
2. LocalStorage
こちらも言うまでもないですね。window.open 前にセットしています。
<button
onClick={() => {
localStorage.setItem('params', JSON.stringify({ count }))
window.open('http://localhost:5173/child1', 'Child1')
}}
>
Child1 へ - LocalStorage 経由
</button>
結果です。1と同じく左が親、右が起動後の子です。ちゃんと参照できます。
こちらの方法も簡単ですね。
デメリットは、LocalStorage なのでブラウザ使える人はだれでもアクセスできる点、使い方を決めておかないとゴミが残ったままになったり、意図しないタイミングで更新されて期待してない値になっちゃったりする可能性への対応が必要です。
3. グローバル変数
グローバル変数というだけで微妙…となりがちですが、一概に否定せずケースによっては使い所もあるのかなとは思っています。
実装としては、window オブジェクト自体にパラメータをセットしつつ、子から opener
経由で値を参照しています。react-window-opener の実装もこんな感じぽいです。
<div>
<NewWindow
url="http://localhost:5173/child1"
name=""
params={{ count }}
></NewWindow>
</div>
~~~
function NewWindow(props: { url: string; name: string; params: any }) {
const w = window.self as typeof window & { openerParams: string }
w.openerParams = JSON.stringify(props.params)
const onClickHandler = () => w.open(props.url, props.name)
return <button onClick={onClickHandler}>Child1 へ - グローバル変数</button>
}
結果です。ちゃんと渡ってますね。
他の2つよりはコードは書きますが独自プロパティをセットしているだけです。
デメリットはやはりグローバル変数を触る点と LocalStorage 同様ライフサイクルをちゃんと管理する必要がある。
まとめ
どの方法でもデータを渡すことはできました。管理の煩雑さを考えれば 1つ目がいいのかな、と。
他に良い方法があれば指摘お願いします。
コメント