React Native
のコードをWebアプリに再利用するための考察

@tkow

自己紹介

  • Leverages株式会社新卒二年目
  • 新規事業の開発責任者
  • Kotlin,Rust,Typescriptが好き
  • 元teratail開発メンバー
  • インフラ設計も好き
  • 機械学習もやる人(NLP)
  • 要は守備範囲が広い人

Comming Soon

エンジニア特化型転職支援サービス

terascout

絶賛開発中

技術構成

Backend

全面的にfirebase

(cloud functions および firebase hosting)

firebase hositing でnextjsをhosting

frontend

React Native(with haul)

typescript

redux(redux-saga,recompose,redux-actions)

CI

fastlane

appcenter

Webで管理画面を作りたい

管理画面に置いても同じデータ構造を使うのでロジックとデータ構造を使いまわしたい

切り出せたもの

Redux

Type Definition (firestoreのデータ構造)

i18n辞書定義ファイル(ts,json,js)

その他 native web関係ない処理関数

Reduxについて

firebase由来のデータ構造のRedux stateとmutationは
全てで共通化できる

 

 

 

ex)

redux
├── User
│   ├── actions.ts
│   ├── index.ts
│   ├── reducers.ts
│   └── sagas.ts
├── actions.ts
├── reducers.ts
├── rootSaga.ts
├── sagas
└── state.ts

Web でも、Nativeでも共通化可能

Reduxについて

import * as model from 'src/redux/$modelPath'で

でinitialState,Action,Reducerが全部exportされる状態を作る

ex)

redux
├── User
│   ├── actions.ts
│   ├── index.ts
│   ├── reducers.ts
│   └── sagas.ts
├── actions.ts
├── reducers.ts
├── rootSaga.ts
├── sagas
└── state.ts

import {default as _actions } from './actions'

import { sagaActions } from './sagas'

export const actions = {..._actions, ...sagaActions}

export {

default as reducers,

initialState

} from './reducers'

export {default as sagas} from './sagas'

Reduxについて

ただし、reduxのstateの中には、webや、モバイルでだけ
使いたいものも存在している。

 

 

 

ex)

redux
├── Search
└── User

このStateは、Web管理画面でしか使いたくない

Reduxについて

reducers.ts,actions,rootSaga.ts,state.tsで使用したい
Stateだけ、結合してあげれば、treeShakingで脱落する

ex)

 

redux

├── Search
├── User

├── actions.ts
├── reducers.ts
├── rootSaga.ts
└── state.ts

ex) actions.ts

export { actions as User } from './User';

 

ex) reducers.ts

import { reducers as user } from './User';

export default combineReducers({

  user,

  ...others

});

Searchを未使用のまま放置できる

Reduxについて

WebとNativeで使用するreduxを別リポジトリで管理

(ついでに、firebaseのtaskも突っ込んでinitialStateを使って

firebase の開発用のバッチなどに利用できるようにした)

repo

├─ redux

│   ├── SomeCommon
   └── User

└─ FirebaseTasks

submodule管理

app               mainプロジェクト

├─ redux

│   ├── Search
   └── MainState

└─ repo

Reduxについて

そのまま import {someReduxStructure} from repo/$statePath

をしても良いがSymbolic Linkを利用するとあたかも、その階層にファイルが存在するかのようにEditorで編集できる。ex) vscode

SubModuleの実体

メインレポの

シンボリック
リンク

Reduxについて

firebase由来のデータ構造のRedux stateとmutationは
全てで共通化できる

 

 

 

ex)

redux
├── User
│   ├── actions.ts
│   ├── index.ts
│   ├── reducers.ts
│   └── sagas.ts
├── actions.ts
├── reducers.ts
├── rootSaga.ts
├── sagas
└── state.ts

Web とNativeで専用の非同期処理のライブラリが異なる可能性があるため共通化には工夫が必要

Reduxについて

Pattern 1: sagasの処理を抽象化して実体をDIする(推奨)

export function*

updateDocument

service/Firebase.web.ts

service/Firebase.native.ts

export function*

updateDocument

違うライブラリや型を使用するが同名のinput outputが同じになるようにラッパーを実装する。

import { updateDocument } from 'service/Firebase';

haul(RN bundler with webpack)or Webpack(for Web)やBabelのresolve extension機能を使って、importを制御

redux/someState/sagas.ts

Reduxについて

Pattern 1: sagasの処理を抽象化して実体をDIする(推奨)

import { updateDocument } from 'service/Firebase';

haul(RN bundler with webpack)or Webpack(for Web)やBabelのresolve extension機能を使って、importを制御

 function* updateUserProfile () {

     yield takeLatest(UPDATE_PROFILE, function* ({ payload: user }: Action<User>) {

     ...

     yield updateDocument(payload);

   }

}

ex)User/sagas.ts

sagasのコードは一切変えずに挙動を変更できる。

Reduxについて

Pattern 2: sagas.web.ts,sagas.native.tsでそもそも分岐させる。

(非推奨)

sagaの実装が異なるため拡張子分岐で普通にimportできる。

export function*

updateDocument

redux/State/saga.web.ts

redux/State/saga.native.ts

export function*

updateDocument

ただしこれには、以下のような罠があるので注意

- typescriptを使ってる場合、alias設定でしか拡張子分岐ができないため、相対パスが使えなくなる(wrapper分岐よりも分岐が増える)

- 以上によりaliasコンフィグが増えるほどjestのalias設定も荒れる

(そもそも分岐しないといけないものには共通化しない決断も必要)

詳しくは https://github.com/Microsoft/TypeScript/issues/21926#issuecomment-437341322

Typeの共有について

Firebaseのデータ構造はwebとNativeで共通なので、

どちらか一方の変更が追従しない事態は避けたい

  =>共通レポジトリで管理

I18nについて

ユーザーのデバイスの外国語設定に対応して表示言語を

変更するための仕様。辞書ファイルをjson objectで持てれば良い。

(TS,でもJSでもyamlでも最終的にjson変換可能ならゴリ押しで

共通化ができる)ライブラリによって必要なjsonのフォーマットが違うためマネジメントが大変なのでぶっちゃけ日本でしかアプリを出さない場合は、使わなくてすがなるべく対応しましょう。

I18nについて

terasoutチームでは複数分割したobjectをts fileで保持し

ライブラリが欲している仕様に合わせて型補完が効くように自動で型情報を生成してくれるbuildスクリプトを書いています。

参考: https://github.com/quipper/i18n-dts

(quipper株式会社さんのレポで非常に参考になります。)

react-native-i18n(react-native-languages)

next-i18next

完全なjsonとしてUnion型情報を生成

分割したtsごとにjsonを生成し型Union情報を生成

i18n/ja/index.ts

i18n/ja/$namespace
(.ts or /index.ts)

I18nについて

型がついてるととても嬉しい。なぜならば、Editor
が勝手に登録済みkeyを列挙してくれるようになるから。

おまけ

react-navigationにセットしてあるroutingも全部補完されるようにしてます。

typoしやすいstring型のデータを突っ込む時にunion literalで補完や警告を効かせるととても開発が快適になります。

どうやってるか知りたい方はこちら ↓
https://qiita.com/tkow/items/90c448416fa3945f62ab

おまけ

一応componentも再利用するためにreact-native-webも使えるかどうかも検証しました

ここがNativeComponentのTextViewを使い回し

参考:https://github.com/tkow/react-native-web-test
reduxと結合する前のコンポーネントの管理を別軸に切り離す設計を考慮する必要があり、一旦お蔵入りに

よくある質問

・なんでSwift,Kotlinで開発しないの?

A, Swift、Kotlinでもいいけど人が揃えられませんでした。また、未経験少人数だとレイアウトやロジックの共通管理が難しいため。

・なんでFlutterで開発しないの?

A, 技術選定当初は今ほど話題になってなかったので、また、
Dartは学習コストが低いものの、未知かつライブラリなどの対応状況から現状まだ熟していない印象。これからは検討の余地があります。
 

・ぶっちゃっけどうだった?

A, なんでもそうですが、いいところ悪いところはあります。

ただ、一度デプロイフローを確立すると想定以上に良いところ

が多かった印象(AndroidとWeb開発経験有りからの見解)。

React Native良かったところ

・iOSとAndroidのデプロイフローがネイティブと同一であるため、同一のツールが使える。(Fastlane,Crashlytics,Appcenter)


ReactNativeはiOS、Androidのネイティブアプリの起動中に差し込まれたjavascriptを実行するという単純な仕組みで動いているため、ビルド手順はネイティブアプリと同じです。(つまり、ネイティブアプリの知識があると有効です)

 

補足:

実行されたJavascriptの中でNativeのAPIが呼び出されると制御がNativeに移り、javascriptは実行待機を行います。

React Native良かったところ

・iOSとAndroidのネイティブコードを書くことが出来る

採用した理由はこれ、最悪ネイティブコードによってゴリ押しができる防衛ラインを確保した。

 

また、Nativeアプリ開発の知見が活きる。

 

ただし、思った以上にJSのAPIが優れているため、ほぼ使わないで済みました。

 

 

 

React Native良かったところ

・もちろんjsの資産が使える

React、Redux、Jest、ReactDevtools、Styled-Components

 

これによって、React Native APIの仕様に沿って設計すれば、JSの資産だけでネイティブアプリを作成することも十分可能に(Expo)。

 

特にネイティブアプリのスタイリングにはかなりの専門知識が必要だが、JSのスタイリングに置き換えることで、レイアウトデザインの敷居は下がっていい感じになっている。

React Native悪かったところ

・学習コスト

結局iOS,Androidに詳しい人が最低一人ずついないと開発は進まない。情報が少ない。(開発者がほぼ一人の場合(僕みたいな人)はネイティヴアプリのビルドやリリース手順まで全部に詳しくなる必要があり地獄圧倒的成長が得られます。)

質が悪いライブラリが出回ってる

勝手にアニメーションが付いてたり、固定レイアウトだったり、融通効かないのが多いので自分で作り変える事が多かった。=> forkしたりソースコードを改変して対処

・バージョンアップが大体破壊的変更

最もバージョンアップが難しいフレームワークと言っても過言ではないのではないかというくらい圧倒的破壊的な変更が起こります。大事なbugfixとメジャーアップデートを同時に含めた運営を時に恨めしく思うことも(ごめんなさい)。

React Nativeは使える?

一回目の開発コストは高いが、再利用可能なUIをコンポーネント化してしまえば、次からの再利用コストはとても低くできる。

WEBとのシームレスな結合という意味ではFlutter

より進んでいる(Googleが手のひら返さなければ)

少なくても現段階では優れたアプローチを取っているためスマホアプリが主流であるうちはしばらく使えると思います。また、十分今後も方針転換に対応できる範囲の技術セットが身につきます。(Nativeの仕組みを勉強する羽目できるため)

結論:React Nativeは使える

(※ 一人開発を先導できる人がいれば)

We are Hiring !

React Nativeを思う存分使い倒したい人
ネイティブアプリケーション開発に興味がある人