1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

EventEmitterをTypeScriptで迷いなく使う

Last updated at Posted at 2020-10-03

EventEmitter、フラッシュメッセージ1を書く時とかに便利ですよね

IMG_0164のコピー.jpg

長々と親コンポーネントから、フラッシュメッセージを呼び出すためのメッセージをバケツリレーで渡す必要もないので、コードが綺麗にまとまります。

これにTypeScriptを使って型定義をしてあげるともっと快適に、型安全に使えたのでまとめてみました

舞台設定

例として、今回は2つのアクションをEventEmitterに登録する場合を考えてみました。

  • 名前を引数に渡して、「Hello 名前」を出力するだけのアクション
  • メッセージと(オプションで)関数を渡して、フラッシュメッセージを出すアクション

ポイントは2つの関数で、「渡す引数が個数も型も違う」ということです。

もし型を定義しないと

  • EventEmitterって、全部でどんなアクションがあったっけ?:thinking:
  • このアクションに渡す引数ってこれであってたっけ?:thinking:

みたいなことになりかねませんが、型を定義すればそんな杞憂ともおさらばです!

ではさっそく書いてみましょう!

用意するコード

2種類あります

  • EventEmitterに登録する全てのアクションの型定義
  • アクションがemitされた時に、トリガーされるロジック

なお、前提としてこれら二つはアプリ全体から参照できる場所で定義するものとします(create-react-appならsrc/App.js

EventEmitterのアクション型定義

まず確認しておきますと、EventEmitter.emitというメソッド自体は「一つ」です。

そのメソッドに対し、TypeScriptの型を複数個定義してあげることで、あたかも「複数のメソッドを定義」しているかのように振舞わせることができます

(大元のEventEmitter.emitのドキュメントはこちら

// myEventEmitter.ts
import { EventEmitter as OriginalEventEmitter } from 'events'

// アクション一覧。必ずオーバーロードしている型と一致させること!!
type EventType = 'flash' | 'greeting'

export interface FlashAction {
  label: string
  onClick: () => void
}

class EventEmitter extends OriginalEventEmitter {
  // 使うアクションの型をオーバーロード
  public emit(
    event: 'flash',
    msg: string,
    action?: FlashAction,
  ): boolean
  public emit(event: 'greeting', name: string): boolean

  // メソッド本体。元々のemitメソッドを踏襲している
  public emit(event: EventType, ...args: any[]) {
    return super.emit(event, ...args)
  }
}

export const eventEmitter = new EventEmitter()

EventEmitterアクションの発生時のアクション

私はロジックを、引数にeventEmitterを渡す形のカスタムフック2にまとめています

ここでeventEmitter.addListenerを書きます

:warning: 残念ながら現時点では、addListner内にまでさっき定義した型を適応する方法がありません...。なので、アクションの修正をする際には、「eventEmitter定義元」と「addListener」、両方を手動で直す必要があります

import { EventEmitter } from 'events'

import { useEffect } from 'react'

export const useGreeting = (eventEmitter: EventEmitter) => {
  useEffect(() => {
    eventEmitter.addListener('greeting', (name: string) => {
      console.log('hello', name)
    })
  }, [eventEmitter])
}
import { EventEmitter } from 'events'

import { useEffect, useState } from 'react'

import { FlashAction } from 'src/config/eventEmitter'

// フラッシュ表示は3秒間
const FLASH_INTERVAL_SEC = 3

export const useFlash = (eventEmitter: EventEmitter) => {
  const [flashAction, setFlashAction] = useState<FlashAction>()
  const [flashMsg, setFlashMsg] = useState('')
  const [isFlashOpen, setIsFlashOpen] = useState(false)

  // 自動でフラッシュメッセージが消えるロジック
  useEffect(() => {
    if (!isFlashOpen) return

    setTimeout(() => {
      setIsFlashOpen(false)
    }, FLASH_INTERVAL_SEC * 1000)
  }, [isFlashOpen])

  useEffect(() => {
    eventEmitter.addListener(
      'flash',
      (msg: string, action?: FlashAction) => {
        setFlashAction(action)
        setFlashMsg(msg)
        setIsFlashOpen(true)
      },
    )
  }, [eventEmitter])

  return { flashAction, flashMsg, isFlashOpen, setIsFlashOpen }
}

これらのロジックを上位階層に配置する

あとはこんな感じでロジックを呼び出します。

flashメッセージの表示には当然コンポーネントも必要なので、こちらも同じく上位階層に定義します
(今回は省略しますが、ニーズがあれば書きます)

import { eventEmitter } from './myEventEmitter.ts'
import { useFlash } from './useFlash'
import { useGreeting } from './useGreeting'

useGreeting(eventEmitter)

const {
  flashAction,
  flashMsg,
  isFlashOpen,
  setIsFlashOpen
} = useFlash(eventEmitter)

// こんな感じのコンポーネントがあるとして
return (
  <FlashMessage
    flashAction={flashAction}
    flashMsg={flashMsg}
    isFlashOpen={isFlashOpen}
    setIsFlashOpen={setIsFlashOpen}
  />
)

あとは任意のコンポーネントから以下のように呼び出せばアクションが起こせます! :beers:
当然型チェックつき! :beers:

import { eventEmitter } from './myEventEmitter.ts'

eventEmitter.emit('greeting', 'john')

// actionはあってもなくてもいい
eventEmitter.emit('flash', 'saved!')
eventEmitter.emit('flash', 'download ready!', () => { downloadCSV() })

終わりに

いかがでしたでしょうか?

型を定義するだけで、一気に使いやすくなるのですごいおすすめです!

皆さんもぜひTypeScriptで良いEventEmitを!

  1. ユーザーのアクションに対するフィードバックとして一時的に表示されるメッセージ。例として、設定の保存ボタンを押したあとに「保存されました」と表示するなどがある

  2. React hookを用いて作られた、再利用可能なひとまとまりのロジック。ここでは「ロジックのカプセル化」と「ラベリング」を目的として使っている。React公式では「独自フック」と定義されている。

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?