DjangoのテンプレートにReactを埋め込むとかいう、妙なことをしているときに、とある理由からwindowにReactやらを追加しておく必要ができたのですが、TypeScriptだとそのまま突っ込むと型の関係で怒られました。
調べてみるとほぼこのタイトルの記事があったのですが、他にもいくつか解決策が出たので残しておきます。
ちなみに、怒られたのはuseStateがReact自身の同一性を見ているからでした。 https://reactjs.org/warnings/invalid-hook-call-warning.html#duplicate-react
1. windowを作り直す
最初は上記の記事にあるように、window.tsファイルを作成して実装しました。
たしかこんな感じで書いたらいけるはずです。
- window.ts
interface MyWindow extends Window { HogeForm: Comment } declare var window: MyWindow; export default window;
- index.ts
import React from 'react'; import { render } from 'react-dom'; import HogeForm from '../containers/HogeForm'; import window from './window'; render(<HogeForm />, document.getElementById('hoge_container'));
これで、hoge_containerタグの場所の表示を置き換えて表示しました。HogeFormの型はCommentでいけます。
この場合、表示するクラスやらが増える度にwindow.tsを修正することになります。
2. as anyを使う
管理ファイルが増えるのも煩わしいなぁとぼやいていたら、同僚にanyを使ったらやり方を教えてもらいました。
import React from 'react'; import ReactDOM from 'react-dom'; (window as any).React = React; (window as any).ReactDOM = ReactDOM; import { HogeForm } from '../containers/HogeForm'; (window as any).HogeForm = HogeForm;
これだとファイルも増えないので、わかりやすさは上がった気がします。 でもTypeScript使っててanyって明示するのもなぁと、同僚と唸ることに。
3. expose-loaderを使う
といったわけで、最終的にはexpose-loaderを使って解消しました。
import 'expose-loader?React!react'; import 'expose-loader?ReactDOM!react-dom'; import 'expose-loader?Containers!./containers/';
ライブラリを追加する必要はありますが、コード自体はだいぶスッキリしたので、対価には見合っているかなと。 ただ、直接関数をロードすると、呼び出す側が正しく認識できなかったので、いったんObjectにするようにしています(ここではContainers)。
これによって使う側がどう変わるかと言うと、django-react-templatetagsでの書き方が、
- expose-loader導入前
{% load react %} {% react_render component="HogeForm" props=hoge_data %}
- expose-loader導入後
{% load react %} {% react_render component="Containers.HogeForm" props=hoge_data %}
のようになります。
といったのを9月くらいにやっていたのですが、気付けばもう年も変わっているという、、、
今年は忘れないうちにアウトプットしていきたいところです。