White Box技術部

WEB開発のあれこれ(と何か)

TypeScriptでwindowにプロパティを追加するいくつかの方法

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月くらいにやっていたのですが、気付けばもう年も変わっているという、、、

今年は忘れないうちにアウトプットしていきたいところです。