Sweet Brissie life

ブリスベンでのサトウキビ博士研究生活の甘くない備忘録

React Native + Expo 記録アプリ② Hooks導入

ご無沙汰してます、React Native記事です。

下記記事の続き、ということで、②となってます。え?もう4か月?嘘でしょ?

tkd708.hatenablog.com

 

今回は自分のアプリでもHooksを導入してみたよ、という事例だけの話。

敢えて言及するのもUnstatedも相変わらず使えてるよ、てことくらいかな。

 

今記事で出てくるコードのjsファイルは下記。

Life-Report/DailyReportScreenMoney.js at master · tkd708/Life-Report · GitHub

 

 

 

Hooksとは

さて、Hooksの概要や基本的な例は公式を参照。

「要するにフックとは?フックとは、関数コンポーネントに state やライフサイクルといった React の機能を “接続する (hook into)” ための関数です。フックは React をクラスなしに使うための機能ですので、クラス内では機能しません。」

ja.reactjs.org

ステートフックの利用法 – React

副作用フックの利用法 – React

 

だそうです。

正直自分はまだHooksのことをちゃんと理解できている気がしないので、解説やらは丸投げです。

React Hooks超入門 - Qiita

React HooksのuseStateがどういう原理で実現されてるのかさっぱりわからなかったので調べてみた

 

 

Hooks導入例

なので早速コードをつらつら紹介していくと、DailyReportScreenMoneyってのが今回Hooksを導入していったFunction Componentになります。

 

4行並んでる const~の部分が、Function ComponentにStateを導入している部分で、useStateの中身は初期値になります。下半分にあるuseEffect~のところで、指定したいずれかのStateが更新されるたびにアロー関数で指定したMethod(これ自体はcontainerで記述されてます。)を実行する、ということになっています。ちなみにuseEffectの配列の部分を空にすることで、これまでcomponentDidMount()で実行していたMethodを導入するような使い方が一般的だと思うのですが、空でなくともMount後に実行されます。なので「Mount後+配列に指定のStateが更新された時」にアロー関数の内部が実行されるということになります(はずです?)。


const DailyReportScreenMoney = ({ container }) => {

    const isFocused = useIsFocused();
    const [categorySelectedsetCategory] = useState('Daily total')
    const [lowerDatesetLowerDate] = useState(moment(new Date()).add(-28'days').format('YYYY-MM-DD'))
    const [upperDatesetUpperDate] = useState(moment(new Date()).add(0'days').format('YYYY-MM-DD'))

    // initial trigger [] is included   
    useEffect( () => {
        isFocused && container.getDailyExpenses("Money"categorySelectedlowerDateupperDate)
    },
        [isFocusedcategorySelectedlowerDateupperDate])

 

 

この部分が関連しているのはUIでは下記画像のような感じなのですが、"Daily total"となってる部分がcategorySelected、FromとToの日付がそれぞれlower/upperDateになっていて、ユーザーがそれらを変更したら対応するデータを container.getDailyExpenses()でsqliteのデータベースから取ってくる(後ほどそれをチャートに渡す)という運びです。あともう一つのisFocusedってのはUI下部のタブ選択(MoneyかTime)のことで、それぞれでレンダーし直すようにuseEffectの中に入れました(これの導入のためにNavigationも前回までのv4から最新のv5に、、、また別記事で)。

f:id:tkd708:20200320224438j:plain

 

 

データベースから取ってくる、と言いましたがこの辺のsqliteを介する作業は全部Unstatedでcontainerに中央集約です。

前回記事→{React Native} 記録アプリ expo-sqlite + react-native-chart-kit (中間報告) - Sweet Brissie life

 

 

Function Componentに対するUnstatedの使い方

 

Hooks導入時のFunction ComponentとUnstatedとの共存も、下記のように通常のClass型コンポーネントの時と同様にしてラップすることができました。原理分かってる人からすれば当然なのかもですが、Unstatedの例では基本Class型をラップしたものしか見たことが無かったので、一応。

const DailyReportMoneyWrapper = () => (
    <Subscribe to={[AppContainer]}>
        {container => <DailyReportScreenMoney container={container} />}
    </Subscribe>
)

export default DailyReportMoneyWrapper

 

上記の繰り返しになりますが、Function Componentを指定する際に下記のようにcontainerを渡してやることでコンポーネント内でcontainerにアクセスできます。Class型の時はthis.props.containerみたいに書いてましたけど、この場合は単にcontainerでおっけーですね。


const DailyReportScreenMoney = ({ container }) => {
 

 

Methodだけでなく、今回はチャート表示に必要なデータセットを下記のようにcontainerのstateから引っ張ってきて訂正して入れています。配列がNullだとチャートがエラー吐いてしまうのでlength==0の時はとりあえず適当にデータポイント突っ込むことにしました。


    let labelArray = container.state.dailyExpenses_dates.length !== 0
        ? container.state.dailyExpenses_dates.map(date => moment(date).format('MMM-DD'))
        : ["NA"]
    let dataArray = container.state.dailyExpenses_amounts.length !== 0
        ? container.state.dailyExpenses_amounts
        : [0]
    let remarkArray = container.state.dailyExpenses_remarks.length !== 0
        ? container.state.dailyExpenses_remarks
        : [""]

 

 

まとめ

このFunction Componentの一連の流れをまとめると、

1. useStateでユーザーの入力値を保持しておくStateを定める

2. useEffectで1.で指定したStateが更新された際にcontainerで指定されたmethodを実行

3. 2.のmethodによりsqliteとやりとり、取り出したデータセットがcontainerのStateに入る

4. containerのStateに入ったデータセットにアクセスしてチャートに必要なデータを用意

5. 4.のデータをチャートに渡して図示

こんな感じです。1.と2.がHooks関連、2.~4.はUnstated関連の動作ですね。

これらによってユーザーの入力値に応じてチャートの表示が更新される(5.)という機能を実装しています。

 

 

Hooks導入というタイトルにした通り、これはもともと1.と2.をClass型コンポーネントで実装していた部分(this.state.~なやつ)をHooksで書き換えただけなんですね。公式の説明にも「フックは既存のコードと併用することができるので、段階的に採用していくことが可能です。」とあります。

 

Hooks導入の動機は「ステートフルなロジックをコンポーネント間で再利用するのは難しい」「複雑なコンポーネントは理解しづらくなる」からみたいですが、そう状況にもまだあまり遭遇していません。が、これはReactのコンポーネント思想に真っ向から立ち向かうかのような書き方をしてきたからですね...前回と同じく、基本的に1ページにつき1jsファイルという状況です。 まぁまだどんな部分をどれくらい使いまわしていくものか肌感覚で分かっていないから仕方ないですかね。

 

一応、徐々に実感したのは、render()が肥大化してきたらそれは分割してfunction/methodとして書いた方がよくて、頻出のものならコンポーネントとして独立させていくべきだろうという点。あとはcontainerでstate集中管理してるからそれらを扱うfunctionがcontainer.jsにどんどん増えてくこと。これらはHooks使いながら独立させていくべきなんだろうかと想像してます。

 

今後のアプリ開発の中で、「関連する機能に基づいて、1 つのコンポーネントを複数の小さな関数に分割することを可能に」するHooksの恩恵を感じながら、より開発・管理しやすい構造にしていくことができればな、という次第です。

フックの導入 – React

 

 

 

さて、以上はHooks導入の一例に過ぎませんが、本当はもっといろいろとできることや注意点やあるみたいです。

まだ自分も試してみたばかりで勉強中ですので、アドバイスいただければ幸いです!