Yuri’s Tech Note

技術系アウトプット

react-native-domを自社アプリに組み込んでみた

ログイン画面を表示して実際にログインができることをゴールに見据えて挑戦しました。

INDEX

  • できたこと
  • できなかったこと
  • わかったこと
  • 組み込んだ方法
  • react-native-domのプロダクション採用について

できたこと

アプリで使用されている画面をそのまま表示

f:id:yuri_iOS:20180929160229p:plain
コンポーネントを表示すること自体は問題ありませんでした。
ただ、react-native-vector-iconsはテキストと認識され正しくアイコンが表示されません。
(アプリのロゴとパスワードリセットテキストの手前)

これが該当部のエレメントです。
rct-raw-textというタグ名で"□"というテキストになっています。

<rct-raw-text style="position: static; background-color: rgba(0, 0, 0, 0); contain: style; box-sizing: border-box; user-select: inherit; overflow: visible; display: inline; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased;"></rct-raw-text>


また、スタイルでデバイスのサイズを取得して画像にサイズを当てている部分については、webではソースを読み込んだ時点でのwindowのサイズが使われます。*1
下の画像をよく見ると、背景画像の領域が左右で異なっています。
読み込み後にwindowを広げると黒い背景が現れています。
f:id:yuri_iOS:20180929163731p:plain

justifyContentやalignItemsのcenter指定はwindowのサイズを変えても変化に応じて柔軟にセンターに配置されます。

Reduxのデータを扱う

Reduxの組み込み、具体的には下記を確認しました。
・store
・bindしたアクションの実行(actions.function())
・dispatch
・redux-logger
f:id:yuri_iOS:20180929162109p:plain
ただし、アプリで使用しているreducerをそのまま使用することはできず、web用のreducerが別途必要になりました。
これについては後述します。


できなかったこと

TextInputが使用できません。
具体的にはフォーカス状態にならないため文字入力ができません。
これは既にissueにも上がっています。
TextInput support · Issue #37 · vincentriemer/react-native-dom · GitHub

これは「できないこと」の氷山の一角だとは思いますが、これに気が付いた時点でreact-native-domでアプリのweb版を作ることは断念しました。
(もともと本命だったreact-native-webでやります)


わかったこと

package.jsonのdependencies見ると全部で55のモジュールを使用しているのですが、そのうち下記モジュールに関してモジュールをimportするだけでwebがビルドできなくなります。

・react-native-adjust
・react-native-fabric
・react-native-permissions
・react-native-scrollable-tab-view
・react-native-simple-toast
・react-native-zendesk-support

エラーにはいくつかタイプがありますが下記2つが多いです。

Module AppRegistry is not a registered callable module (calling runApplication)
blob:http://localhost:8081/8b78e274-51be-41e7-8e5e-f0246cb9df42:15 Uncaught DOMException: Failed to execute 'importScripts' on 'WorkerGlobalScope': The script at 'http://localhost:8081/dom/entry.bundle?platform=dom&dev=true&hot=true' failed to load.

おそらくimportした時点でネィティブにアクセスして何かしらの処理をしているモジュールが共存できなさそうです。
なのでこれらのモジュールを読み込んでいるファイルは軒並みwebは分ける必要があります。


組み込んだ方法

上記のネィティブにアクセスするモジュールがwebで読み込まれるファイルに記載されていてはいけないということから、いくつかのファイルをweb用に新しく作成しました。

1. Entry
大元のコンポーネントを登録する処理を書くファイル

import { AppRegistry } from 'react-native';
import Root from './src/containers/rootForWeb';

AppRegistry.registerComponent('TeamHub', () => Root);

また、dom/entry.jsにweb起動時に最初に読み込むファイルを指定できるので上記ファイルを読み込むよう修正

2. Provider
先ほどのAppRegistryに登録されるコンポーネント

const store = configureStore();
loadTranslations();

export default class Root extends Component {
  constructor(props) {
    super(props);
    if (I18n.locale === 'ja-JP') {
      require('moment/locale/ja');
    }
  }

  render() {
    return (
      <Provider store={store}>
        <WebApp />
      </Provider>
    );
  }
}

3. App
仮にログイン画面をそのまま返すようにしてあります。

export class WebApp extends Component {
  constructor(props) {
    super(props);
    props.actions.requestApp();
  }

  render() {
    return (
      <Login {...this.props} />
    );
  }
}

export default connect(
  state => ({ ...state }),
  dispatch => ({
    actions: bindActionCreators({
      ...AppActions,
      ...AuthActions,
    }, dispatch),
  }),
)(WebApp);

4. reducer
storeに入れるモジュールを結合しているのでどこか1つでもimport時にネィティブにアクセスするモジュールがあってはビルドできません。
基本モバイルと同じですが、ログインなどのアクションに応じて記録を取るためにAdjustなどを使用しているauthなどは別を用意して一部web用のファイルを読み込むように修正しました。

import { combineReducers } from 'redux';
import { modelReducer, modeled } from 'react-redux-form';

import app from './app';
import route from './route';
import auth from './authForWeb';

export default combineReducers({
  app,
  route,
  auth: modeled(auth, 'auth')
});

5. auth
上記でも触れた通り、ログイン時の処理は裏側でモバイルアプリ特有の処理もたくさん走っているのでauthはまるごとweb用に新しく用意しました。


react-native-domのプロダクション利用について

TextInputの改善なくしてプロダクション採用はありえません。
しかし、バージョン0.57が出た時は対応も早かったですし、コミット状況からも開発はまだまだしっかり行われていると思うので期待寄りの保留としようと思っています。

*1:const { width, height } = Dimensions.get('window');を使用した画像のサイズ指定は読み込み時のwindowサイズで固定