2021/01/08
Reactを部分導入する場合にReact root外の要素と連携する
Reactを既存のプロジェクトに部分的に導入したい場合があると思いますが、
その場合にReactのrootの外側にある要素と連携したい場合にどのようにすればいいか、調べて試してみました。
今回はLaunchCartの案件で、絞り込み検索を詳細にカスタマイズするためにJavaScriptで補助する必要が出てきました。
これまででしたら、フレームワークなどを使わずにjQueryなどを使って組んでいたのですが、フレームワークを使わないとどうしても後で修正したい場合に構造が複雑になって面倒になりがちだったので、
この機会にReact + TypeScriptを部分導入という形で使ってみることにしました。
↓こちらが、テスト用に作ってみたもの
DEMO
方法
React rootの外部の要素と連携する場合、参照する外部の要素が外部側で変更される場合を想定して、グローバル領域(window オブジェクト)に予め要素を外部側で格納し、それをReact側で受け取って処理することにします。
外部のHTMLやJSファイルで
outside react root
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <div id="refineSearch"> <ul> <li><label><input type="checkbox" name="category[]" value="1"> Category 1</label></li> <li><label><input type="checkbox" name="category[]" value="2"> Category 2</label></li> <li><label><input type="checkbox" name="category[]" value="3"> Category 3</label></li> <li><label><input type="checkbox" name="category[]" value="4"> Category 4</label></li> </ul> </div> <script> window.myElements = { searchCategoryInputElements: [].slice.call(document.querySelectorAll('#refineSearch [name="category[]"]')) } </script> |
TypeScriptの場合
TypeScript で window オブジェクトに任意のデータを格納したい場合、
declare を使ってTypeの拡張を行います。
window.d.ts
1 2 3 4 5 6 7 8 | export {}; declare global { interface Window { myElements: { searchCategoryInputElements: HTMLInputElement[] } } } |
連携させたいReactのコンポーネントにuseEffect()でEventListenerを設定
続いてReact側のイベントリスナーの設定を行います。
まず外部要素と連携したいReactのコンポーネント内にイベントリスナーで呼び出すコールバック関数を設定し、
useEffect() を使ってイベントリスナーの設定を行います。
React component
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | function App() { // React root 外の要素の情報を保存 const [selectedCategories, setSelectedCategories] = useState([] as string[]); // React root 外の要素の情報を取得(イベントリスナで呼び出すコールバック関数) function getSelectedCategories () { let selectedCategories: string[] = []; window.myElements.searchCategoryInputElements.forEach(searchCategoryInputElement => { selectedCategories.push(searchCategoryInputElement.value); }); setSelectedCategories(selectedCategories); } useEffect(() => { // 初回実行 getSelectedCategories(); // React root 外の要素のイベントリスナを設定 window.myElements.searchCategoryInputElements.forEach(searchCategoryInputElement => { searchCategoryInputElement.addEventListener('change', getSelectedCategories); }); return () => { // 再実行された場合は一度イベントリスナを削除 window.myElements.searchCategoryInputElements.forEach(searchCategoryInputElement => { searchCategoryInputElement.removeEventListener('change', getSelectedCategories); }); } }, [getSelectedCategories]); // ←第2引数にイベントのコールバック関数を指定 return ( <div> {selectedCategories.join(', ')} </div> ); } |
ポイントは、イベントリスナで呼び出されるコールバック関数をuseEffect()の第2引数で渡すことです。
useEffect()は第2引数を渡さないとレンダリング毎に実行されてしまい、今回のようにイベントリスナーを設定する場合、不要な処理が繰り返し行われてしまうことになってしまいますが、
第2引数を設定することで、レンダリング毎に不要にイベントリスナが設定されることを防ぐことができます。
参考にさせていただいたサイト
Author Profile
NINOMIYA
Webデザイナー兼コーダー出身のフロントエンド開発者です。 UXデザインやチーム開発の効率化など、勉強中です。
SHARE