『react-use-event-listeners』というカスタムフックをリリースしました

しました。

www.npmjs.com

いますぐ yarn add react-use-event-listeners

これはなに

ReactのhooksスタイルでDOMに対してaddEventListenerするやつです。

Reactで合成イベント(SyntheticEvent)ではないイベントを使いたいときにaddEventListenerでイベント登録しますが、それをラクにやれるカスタムフックです。

使いかた

  useEventListeners(
    {
      eventTarget: currentEventTarget,
      listeners: [
        ['click', countUp],
        ['pointerover', handleOver],
      ]
    },
    [currentEventTarget]
  )

こんな感じに書く。

例によってカウントアップアプリ

実例とコード

API

現行バージョンのv1では

  • 第1引数
    • オブジェクトでeventTargetlistenersを渡す
  • 第2引数
    • ビルトインのuseEffectの第2引数と同じ配列(DependencyList)
      • 再実行&クリーンナップの条件になる値

というAPIになっています。

いつ使うの

ReactはSyntheticEvent(合成イベント)があるので基本的にはイベントをなんかやるときは<div onClick={handleOnClick}>foo</div>形式でやればOK。この形式でやっていくのがほとんどなはず。

ただaddEventListener形式でイベントをなんかやりたいときは時々あると思っていて、例えばwindowとかイベント対象のNodeが不定な場合(propsから渡ってくるとか)のときはaddEventListener/removeEventListenerしていくことになるという理解でいる。

なのでそういうSyntheticEventじゃムリな場面のときに使えるはずと思って作っています。

↑に貼ったcodesandboxのデモはそういう意味では全く適していなくて、↑のデモのような場面ではSyntheticEventでOK😅

実装について

『react-use-event-listeners』は内部ではお手製の「register-event-listeners」というライブラリを使っています。

github.com

addEventListener/removeEventListenerのUtil関数

1つのイベントターゲットに対して複数のイベントをまとめて登録できて、unRegister() すれば登録していたイベントがremoveEventListenerされるというやつです。

これを用いてuseEffectのクリーンナップ処理にunRegister関数を渡しているので、『react-use-event-listeners』を使っているコンポーネントがunmountされるとremoveEventListenerもされます。

以上です

使えそうな場面があったら

yarn add react-use-event-listeners

して使ってみてください。

あと依存しているregister-event-listenersですが、Tuple型の書きかたが悪いのかTS力が足りないだけなのかコンパイラ側のせいなのかが不明ですがハンドラーの型推論がうまいこと効いていないので困っています。

コントリビュートはいつでも歓迎です。

TypeScriptで"ユニオン型あるいはなにかしらの型"を表現する方法について

型定義書いていて『Union型に当てはまる場合はそのどれかだが、どれにも当てはまらなかったら"この型"』みたいなものを書きたくなるときがある。

例えば

type UnionA = 'tarou' | 'jirou' | 'saburou'

というUnion型があって、このUnionA型に当てはまらなかったらstringみたいなことを表現したくなったとする。

逆に言うとUnionAの形式で補完が効いてほしいけど、補完以外のstringも受け付けたいみたいな欲求かもしれない。

const foo = '|' // ここで補完出すと 'tarou' | 'jirou' | 'saburou'が出て欲しいけど、別に'hogehoge~'みたいなstringでもOKみたいなわがまま型にするには…

人間的にはこう書けばいけそうな気がして書くがうまくいかない…

type UnionOrString = UnionA | string

const foo: UnionOrString = '|' // 補完が効かない!

そんな〜って気持ちになって破滅する。

LiteralUnion

たまたまSindre氏の型定義集のライブラリ眺めてたところ上記の問題を解決できる便利型が転がっていた。

github.com

export type LiteralUnion<
    LiteralType extends BaseType,
    BaseType extends Primitive
> = LiteralType | (BaseType & {_?: never});

https://github.com/sindresorhus/type-fest/blob/71613595b71473b2094b0803f21080056242c571/index.d.ts#L139-L169 より抜粋

これを使うと…

f:id:hitonatsu:20190326151311p:plain
やった〜

Playground · TypeScript

Issue

github.com

Issueはここみたいだけどコンパイラ的に解釈できないのでこういう型をいまのところは経由しないとダメみたい。

わりとこのわがままなUnion型をやりたい人は多そうな気がしているけど、ググラビリティが低すぎてみんなたどり着けていないのでは……という気がしている。

この記事もそういう意味ではたどり着けないのかもしれない。ユニオン型orなにかの型みたいなことを、もっとわかりやすく直接脳で解釈できるような言葉で書くの難しい気がする。