Kosaku Kurino

Kosaku Kurino

【React】Firebaseの認証状態チェックコードはどこに書くべきなのか

まえがき

React/React Native + Reduxでのアプリケーション開発において、Firebaseの認証状態チェックコードは、どこに書くべきなのか。

適当なコンポーネントのライフサイクルのcomponentDidMountに書く。 → これが一番よく目にする書き方

しかし、この書き方だと認証状態チェックコードが書かれたコンポーネントは常に表示しておかなければいけなかったり、ルーティングライブラリ(react-router, react-navigation)を導入した時、さらなる混沌が待っている。

目的

Firebaseの認証状態チェックコードを「開発状況」や「アプリケーションの構造」を考慮した上で、書く(べき)場所を定める。

対象者

React/React Nativeのアプリケーション開発で、Firebase Authenticationを使っている人 Firebaseの認証状態チェックのコードをコンポーネントに書いているがしっくりきていない人 redux-sagaを知っている人

最後のセクションでredux-sagaが出てきます。

本論(書き方)

1. 「適当なコンポーネント」に書く

ヘッダー等、常に表示させる「適当なコンポーネント」のcomponentDidMountに書く。

import React from 'react'
import * as firebase from 'firebase'

// firebase.initializeApp(config)

class MyComponent extends React.Component {
  componentDidMount() {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        this.props.handleStatusOk() // コンテナーで定義した関数
      }
      else {
        this.props.handleStatusNg() // コンテナーで定義した関数
      }
    })
  }

  render() {
    // 省略
  }
}

export default MyComponent

考察

この書き方は、MyComponentが常に表示されるのであれば問題ない。 MyComponentが表示されない場面があるのであれば、その場面において認証状態のチェックは行われないので注意が必要。

常に表示させる「適当なコンポーネント」がないのであれば、他のコンポーネントのcomponentDidMountにも記載する等工夫が必要になる。

適当なコンポーネント」はコンテナーでreact-reduxのconnectでラップされているはずなので、認証状態が変化したタイミングでアクションをディスパッチする関数を簡単に実行させることができる。

結論

アプリケーションの構造があらかた固まっている、かつ常に表示させるコンポーネントがある場合」のみこの書き方はあり。 構造が固まっていないのであれば、常に表示させる予定だったコンポーネントが表示されない場面がでてくる可能性も考え、おすすめしない。

独断と偏見が入り混じった結論です、ご了承ください。

2. 各コンポーネントを束ねた「親コンポーネント」に書く

アプリケーションで利用する各コンポーネントを全て束ねた「親コンポーネント」を用意し、1. 適当なコンポーネントに書くと同じ書き方で書く。

create-react-appでWebアプリケーションを作成したときにできるApp.jsのことである。

import React from 'react'
import { connect } from 'react-redux'
import * as firebase from 'firebase'

// firebase.initializeApp(config)

class App extends React.Component {
  componentDidMount() {
    firebase.auth().onAuthStateChanged((user) => {
      if (user) {
        // 直接アクションをディスパッチ
        this.props.dispatch({ type: "STATUS_OK" })
      }
      else {
        // 直接アクションをディスパッチ
        this.props.dispatch({ type: "STATUS_NG" })
      }
    })
  }

  render() {
    // 省略
  }
}

const mapStateToProps = (state) {
  return { state }
}

export default connect(mapStateToProps)(App)

考察

この書き方は、「親コンポーネント」を通して各コンポーネントが表示されるので、あまり構造を意識せず、常に認証状態をチェックをさせることができる。

ただし、認証状態が変化したタイミングでアクションをディスパッチする場合、各コンポーネントだけでなく、「親コンポーネント」もreact-reduxのconnectでラップする必要が出てくる。

例では直接アクションをディスパッチしているが、もちろん関数化して実行させてもいい。

結論

常に表示させるコンポーネントがない場合」、「アプリケーションの構造が固まっていない場合」この書き方はあり。

認証状態をチェックさせるためだけに各コンポーネント束ねる「親コンポーネント」を作りたくない、「親コンポーネント」はreact-reduxのconnectでラップしたくないなど、この書き方にはしっくりこない人がいるかもしれない。 → 自分は親コンポーネントをラップするところが気に食わなかった。

※独断と偏見が入り混じった結論です、ご了承ください。

3. redux-sagaを利用することで「タスク」に書く

タスク」にeventChannelを利用して書く。

import { take, put, all } from 'redux-saga/effects'
import { eventChannel } from 'redux-saga'
import * as firebase from 'firebase'

// firebase.initializeApp(config)

const data = (type ,payload) => {
  const _data = {
    type: type,
    payload: payload,
  }

  return _data
}

const authChannel = () => {
  const channel = eventChannel(emit => {
    const unsubscribe = firebase.auth().onAuthStateChanged(
      user => emit({ user }),
      error => emit({ error })
    )
    return unsubscribe
  })
  return channel
}

function* checkUserStateSaga() {
  const channel = yield call(authChannel)
  while (true) {
    const { user, error } = yield take(channel)

    if ( user && !error ) {
      yield put(data('REDUCER_SET_UID', user.uid))
      yield put(data('REDUCER_GET_PROFILE_REQUEST', null))
    }
    else {
      yield put(data('REDUCER_SET_UID', null))
    }
  }
}

export default function* rootSaga() {
  yield all([
    checkUserStateSaga(),
  ])
}

redux-sagaとは

redux-sagaを導入することで、「タスク」という概念をReduxに導入することができます。 「タスク」はプロセスのようなもので独立して、並列で動きます。

ここら辺の記事が参考になります。 redux-sagaを知らないかたは先読んで理解しておくことをおすすめします。

redux-sagaで非同期処理と戦う Redux-Sagaでテトリス風ゲームを実装して学んだこと Redux SagaのeventChannelを使ってみる

考察

この書き方は、認証状態チェックコードを「タスク」に書いているため、独立して認証状態のチェックが行われる。これによりビューとロジックの分割が可能になり、認証状態をチェックすることを気にせずにコンポーネントを書くことができる。

ただし、redux-sagaを導入しなければいけないため、別ライブラリ導入と学習コストの弊害が出てくる。

弊害と書いてますが、学習すればいいだけの話なんで知らない人は学習しちゃいましょう。

結論

認証状態チェックを独立して実行させたい場合」、「常に表示させるコンポーネントがない場合」、「アプリケーションの構造が固まっていない場合」この書き方はあり。

「認証状態チェックを独立して実行させたい場合」は「ビューとロジックを分割させたい場合」と同義

コンポーネントで認証状態のチェックを気にせず、ビューに特化した形で開発できるので、個人的にはredux-sagaを導入するメリットでかいと思っています。

しかし、認証状態をチェックさせるためだけに「タスク」という概念を導入したくない、Redux onlyのシンプルな構造のまま開発したい等、この書き方には反対の方も結構いるかもしれません。

※独断と偏見が入り混じった結論です、ご了承ください。

まとめ

常に表示するコンポーネントがあるのであれば、そのコンポーネントに書く。 常に表示するコンポーネントがないのであれば、redux-sagaを利用して「タスク」に書く。 redux-sagaを利用したくないのであれば、親コンポーネントに書く。