RxSwift研究読本で、RxSwiftのお勉強

booth.pm

github.com

RxSwiftの概要

RxSwiftとは何か

iOS開発のイベント処理

RxSwiftは複雑になりがちなイベント処理をデータの流れ(ストリーム)として統一的に扱うReactiveX(Reactive Extensions: Rx)をベースとしたライブラリです。 イベント発生時に流れてくるストリームに反応(リアクション)して処理を行うことを実現します。

Rxとは、オブザーバーパターンイテレータパターン関数型プログラミングのアイデアを組み合わせたもの

 キーワード

  • ストリーム
    • データがイベントとして連なった流れ
  • Observable
    • RxSwiftにおけるストリームを生産するもの。クラスObservableで提供される
  • オペレータ
    • ストリームに対して処理を行うメソッド。mapやfilterなど
  • ストリームの購読
    • ストリームから伝播されてくるイベントを順次処理する仕組み

RxSwiftによるMVVMパターン

f:id:panchan817:20200615161358p:plain

  • View(ViewController)
    • UIロジックを担当
    • Viewの生成、表示非表示、タッチイベントの設定など
  • ViewModel
    • プレゼンテーションロジックを担当
    • ビジネスロジックの結果をUIに表示するための処理
    • 具体的なUI表示への関心を分離している
  • Model

MVVMとはViewとViewModelの分離とデータバインディングのこと
→Modelを細分化するようなアーキテクチャと両立するもの。

UML図で理解するRxSwift

Subject

  • Observable, Observer両方の機能を有している
    • Observableクラスを継承し、ObserverTypeプロトコルを採用している
import RxSwift

let subject = PublishSubject<String>()

subject.subscribe(onNext: {
    print("onNext: \($0)")
})

subject.onNext("A")
subject.onNext("B")
subject.onNext("C")
subject.onCompleted()
subject.onNext("D")

//--output--
//onNext: A
//onNext: B
//onNext: C
  • Subjectは購読される
  • Subjectのインスタンスはイベントを任意のタイミングで発火できる

ControlProperty

RxExampleによるRxとMVVMの解説

disposeBagとは

  • まとめてObservableを処分するための仕組み
    • disposeBagにObservableを保持させ、そのdisposeBagを破棄することでObservableをまとめて破棄できる!
    • ViewControllerが破棄されるときに、そのプロパティも自動で破棄されるため、disposeBagの仕組みが働くようになっている

IBOutletからObservableの作成

let observable: Observable<String> = textField.rx.text.orEmpty.asObservable()

ViewModelの出力をViewにバインド

subscribe

ViewController

viewModel.signupEnable.subscribe( onNext: { [weak self] valid in 
    self?.signupOutlet.isEnable = valid
}).disposed(by: disposeBag)

ViewControllerでviewModelが持つObservableを購読し、イベントを発火している。

bind(to:)

ViewController

viewModel.validatedUsername
    .bind(to: usernameValidationOutlet.rx.validationResult)
    .disposed(by: disposeBag)

viewModelが持つObservableを購読し、RxCocoaのControlPropertyにストリームを流している?

雑メモ

Observable.create

https://github.com/ReactiveX/RxSwift/blob/master/RxSwift/Observables/Create.swift 引数にsubscribe((AnyObserver) -> Disposable)を持つ。 引数に指定したsubscribeをsubscribeHandlerとして保持するAnonymousObservableを返す AnonymousObservableはrunメソッドを持ち、その中でsubscribeHandlerを呼ぶ runメソッドはAnonymousObservableがsubscribeされたときに実行される。 つまり、 Observable.create{ observer in ... }.subscribe( onNext: ... ) としたとき、{ observer in ... }は、subscribe( onNext: ... )を通った後、observableのrunメソッドの実行結果として実行される!!

Fluxアーキテクチャのお勉強

アプリ開発するんだから、実装より先にアーキテクチャの部分を決めて行かないといけないよね。

# 今回作るアプリ
今回はレシピ管理アプリみたいなやつを作ろうと思う。

- レシピ一覧
- iOSでいうところのTableView。セルをタップすると詳細画面に遷移する
- レシピ作成
- データはとりあえずローカルに保存する。でも将来はサーバーに持ちたい。

# ReactNativeのアーキテクチャってどんなん?
Reactと同じようにReduxを用いたFluxパターンが王道っぽい。
まあ、勉強だと思ってReduxを採用しましょう。
ちなみに筆者はReactも初心者なのでReduxをちゃんと理解していません。

# Fluxってなんなん?
https://github.com/facebook/flux/tree/master/examples/flux-concepts
以下、意訳。
> Fluxはデータフローを管理するためのアーキテクチャパターンです。一番大事なのは、Fluxはデータの流れが単方向であることです。

ふむふむ

> Fluxには4つの部品があります。

## Dispatcher
> DispatcherはActionを受け取り、そのアクションをDispatcherに登録したStoreへ送ります。全てのStoreが全てのActionを受け取ります。アプリケーションの中でDispatcherはシングルトンであるべきです。

ふむふむ。Actionとは?Storeとは?

## Store
> Storeはアプリケーションのデータを保持しておくものです。StoreはActionを受け取るためにDispatcherを登録します。StoreのデータはActionを受け取った時にしか変更されません。publicでいいのはゲッターだけで、publicなセッターをStoreに持つべきではありません。StoreはなんのActionを受け取るか決めます。Storeのデータが変更されるといつも、changeイベントが発火します。アプリケーション内にStoreはたくさんあります。

ふむふむ。
どこか(Viewとか?)でActionがDispatcherに送られて、DispatcherがそのActionを然るべきStoreに振り分けるみたいなイメージかな?

## Actions
>アクションはアプリケーション内部のAPI定義です。どんな種類のインタラクトが発生したかを捕捉します。Actionはtypeフィールドといくつかのデータを持った単純なオブジェクトです。Actionはセマンティックで、発生したアクションを説明するものであるべきです。Actionの実装の詳細を説明するものではありません。「ユーザーIDを消す」「ユーザーデータを消す」みたいなものではなく、「ユーザーを消す」というようなものを使いましょう。全てのStoreは同じAction、例えば「ユーザーを消す」Actionをハンドリングして、データを消すのか、クレデンシャルを更新するのかなどを判別します。

よくわからんけど、Storeでなんらかの操作を行うそのトリガーとその引数、みたいなことであってるかな。

## Views
>StoreからのデータはViewに表示されます。 Viewはあなたが望むどんなフレームワークも使用できます(ここでのほとんどの例では、Reactを使用します)。 ViewがStoreからのデータを使用する場合、そのStoreからの変更イベントをサブスクライブする必要もあります。 その後、Storeが変更を発行すると、Viewは新しいデータを取得して再レンダリングします。 コンポーネントがStoreを使用し、それをサブスクライブしない場合、微妙なバグが存在する可能性があります。 Actionは通常、ユーザーがアプリケーションのインターフェイスの一部を操作するときにViewから送出されます。

ViewがStoreをサブスクライブ(購読/監視)する。Storeのデータが更新される。それを検知して新しいデータで再レンダリングする。なるほど。

## Example
>
- ViewはTodoStoreをサブスクライブ(購読/監視)する。
- todoを追加するActionを定義する。

```
   {
     type: 'add-todo',
     title: 'タイトル',
   }
```

>
- ユーザーが新しいTodoのタイトルを入力して完了すると、ViewはDispatcherにタスクのタイトルを含む「add-todo」アクションををディスパッチするよう指示する。
- すべてのストアがディスパッチされたアクションを受け取る。
- 自分に関係のあるActionだったら、そのActionを処理する。
- この場合TodoStoreはアクションを処理し、別のTodoを内部データ構造に追加してから、changeイベントを発行する。
- Viewはchangeイベントをリッスンしています。 イベントを取得し、TodoStoreから新しいデータを取得して、Todoのリストを再レンダリングする。

ぐるっと回って帰ってくる、みたいなことね!!つまり単方向のデータフローだ!!!
この中でDispatcherは全てのActionを受け取って全てのストアにそれをぶん投げる、Actionの問屋みたいなことね!
(一元管理したいのでシングルトンであることが望ましい、と)

## Data Flow
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248316/29c139b1-c955-3888-e6ea-4b5bc4ed6879.png)
よくみる画像を拝借。うんうん。こうやってみると、わかりやすい。

# なんでReact系はFluxアーキテクチャがいいんだろう?
モバイルアプリとかではあまり聞かないよね。サクッと調べたらVue.jsはMVVMが有名っぽい?うーん。詳しい人がいたら教えて欲しい。

## Reduxってなんだ?
さて。Reduxってなんだ?
https://redux.js.org/basics/basic-tutorial
公式のチュートリアルを追ってみよう

> ReducerとかミドルウェアとかStoreへの派手な話に騙されないで。Reduxってマジで単純なんだ

信じよう。

## Actions
> Actionは、アプリケーションからStoreにデータを送信する情報のペイロードです。 Storeの唯一の情報源です。 それらは、store.dispatch()を使用してStoreに送信します。

ここの役割というか定義はFluxと同じっぽい。

```
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
```

## Reducers
これはなんだ?

> Reducerは、Storeに送信されたActionに応じてアプリケーションの状態がどのように変化するかを指定します。 Actionは何が起こったかを説明するだけで、アプリケーションの状態がどのように変化するかを説明しないことに注意してください。

つまり?
https://redux.js.org/basics/reducers

全然わかんないじゃん!?
誰か助けて。。。ということで
https://qiita.com/tkow/items/9da7062f9bfa99e848c3

つまり、ReducerがActionを受け取って、そのタイプに応じて適切な関数によって値を変化させる、ということっぽい。
めちゃめちゃざっくり。

 

 

 

Redux 入門(サンプルコードを読む)

React/Redux初心者の私が、Reduxのサンプルコードを見てみる回です。 https://github.com/reduxjs/redux/tree/master/examples

対象としては「用語はわかるけどどうやって使うのかいまいちわからん」な人です。 つまり私です。

ディレクトリ構成

スクリーンショット 2020-06-02 2.46.05.png

見てみる。

手元でビルドできない。

import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'

const App = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
    <Footer />
  </div>
)

export default App

まあ、Todo作成する部分と、表示する部分と、フッターがあるみたい。 表示する部分から見てみよう

import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
import { VisibilityFilters } from '../actions'

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case VisibilityFilters.SHOW_ALL:
      return todos
    case VisibilityFilters.SHOW_COMPLETED:
      return todos.filter(t => t.completed)
    case VisibilityFilters.SHOW_ACTIVE:
      return todos.filter(t => !t.completed)
    default:
      throw new Error('Unknown filter: ' + filter)
  }
}

const mapStateToProps = state => ({
  todos: getVisibleTodos(state.todos, state.visibilityFilter)
})

const mapDispatchToProps = dispatch => ({
  toggleTodo: id => dispatch(toggleTodo(id))
})

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

おっ、Viewを持っていない? Viewの本体はこっちみたいだ。

import React from 'react'
import PropTypes from 'prop-types'
import Todo from './Todo'

const TodoList = ({ todos, toggleTodo }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => toggleTodo(todo.id)}
      />
    )}
  </ul>
)

TodoList.propTypes = {
  todos: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.number.isRequired,
    completed: PropTypes.bool.isRequired,
    text: PropTypes.string.isRequired
  }).isRequired).isRequired,
  toggleTodo: PropTypes.func.isRequired
}

export default TodoList

connectってなんだろう?

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

なんだろう。 Reduxライブラリからimportしている。

https://react-redux.js.org/api/connect

The connect() function connects a React component to a Redux store.

おー。つまりVisibleTodoList.jsがStoreなんだね。 StoreがViewに依存する状態。

じゃあこの引数で渡しているのは何?

mapStateToProps, mapDispatchToPropsってなんだろう?

React ComponentとStoreをconnectする(きっと、StoreがComponentに対してイベントを送れる状態)時に引数で渡すこのふたつ。

const mapStateToProps = state => ({
  todos: getVisibleTodos(state.todos, state.visibilityFilter)
})

const mapDispatchToProps = dispatch => ({
  toggleTodo: id => dispatch(toggleTodo(id))
})

stateはまあわかる。

const mapStateToProps = state => ({
  todos: getVisibleTodos(state.todos, state.visibilityFilter)
})
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case VisibilityFilters.SHOW_ALL:
      return todos
    case VisibilityFilters.SHOW_COMPLETED:
      return todos.filter(t => t.completed)
    case VisibilityFilters.SHOW_ACTIVE:
      return todos.filter(t => !t.completed)
    default:
      throw new Error('Unknown filter: ' + filter)
  }
}

リストとTypeを渡すとフィルターしたTodoのリストを返すような関数を渡している。 それがPropsとしてViewに渡り、それをmapして描画している。 じゃあgetVisibleTodoListに引数を渡すのは誰なんだろう?

とりあえずその下、dispatchも見てみよう。

const mapDispatchToProps = dispatch => ({
  toggleTodo: id => dispatch(toggleTodo(id))
})

toggleTodoというのはActionだ。

export const toggleTodo = id => ({
  type: 'TOGGLE_TODO',
  id
})

StoreにtoffleTodoというActionをディスパッチしている。で、それもViewにconnectされる。 Viewの中で

const TodoList = ({ todos, toggleTodo }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => toggleTodo(todo.id)}
      />
    )}
  </ul>
)

というのがあるから、セルをタップするとそのTodoの状態が変わるのかな。 このActionがどうやってViewに伝わるのか。Reducerにわたるはずやけど。

と思っていたら、鍵はこれだった

import React from 'react'
import { render } from 'react-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux'
import App from './components/App'
import rootReducer from './reducers'

const store = createStore(rootReducer)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

いっちばん上でrootReducerからStoreを作って、下位コンポーネント全てにぶん投げている。 つまり単一のストアをアプリケーション全体で共有している状態。 そしてこのrootReducerというのは

import { combineReducers } from 'redux'
import todos from './todos'
import visibilityFilter from './visibilityFilter'

export default combineReducers({
  todos,
  visibilityFilter
})

こいつを指している。 これはさらに

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        {
          id: action.id,
          text: action.text,
          completed: false
        }
      ]
    case 'TOGGLE_TODO':
      return state.map(todo =>
        (todo.id === action.id)
          ? {...todo, completed: !todo.completed}
          : todo
      )
    default:
      return state
  }
}

export default todos

こいつらを表している。

rootReducerってなんだ?

React.js - rootReducerってなんですか?|teratail なるほど。。

createStoreとは?

reducerからStoreを作る。 Storeはreduser毎にstateを作成する?

Storeとは?

わからんち

Providerとは?

  1. Reactコンポーネント内でreact-reduxのconnect()関数を使えるようにすること
  2. ラップしたコンポーネントにstore情報を渡すこと

qiita.com

これってもしかして、その下位コンポーネントがStoreの役割を持てる(connectとかdispatcherを自分の関数のように扱える)ことと関係がある?ありそう。 Providerしたらその下位コンポーネントは渡したStoreの役割を持てるっぽい。

一旦まとめ

流れとしては、 1. ルートのindex.jsが呼ばれる 2. rootReducer(reducersディレクトリ 直下のcombineReducer)を使ってStoreを作成する 3. storeが下位コンポーネントにぶん投げられる?(Provider) 4. VisibleTodoList.jsでViewとStoreをconnectする(その時にストアが更新されるたび呼び出される関数とstateを書き換えるdispatchを定義する) 5. Viewでdispatchを呼び出す 6. dispatchがアクションを発火させてreducerがstoreのstateを更新する 7. storeの更新を検知してViewのストアが更新されるたび呼び出される関数が走る 8. それに応じてViewが再レンダリングされる

参考リンク

github.com

redux.js.org

note.com

qiita.com

ReactNativeの話

Flutterのほうがイマドキっぽいのかもしれないが、今後伸びるのはReactNativeな気がする

なぜReactNative?

  • ネイティブアプリで新規開発するモチベーションってどんどん薄れていきそう。
  • FlutterやXmarinもあるけど、Web(React)エンジニアがとっつきやすいというのは大きいメリット。

ReactNativeって何?

ネイティブで動くやつ、って意味でNativeが付くんだろう。 当たり前だけどReactとは言えReactではなく、React風に書けるJavaScriptライブラリ。ビルドされると、ReactNativeで書かれたコンポーネントは各プラットフォーム毎にUIクラスを生成する。(ViewControllerとActivity、とか。) 昔のクロスプラットフォームHTML5で書いてWebViewで。。。みたいな、めちゃしょぼいやつだったけど、ReactNativeとか、あんまり詳しく無いけどXamarinとか、Flutterとか、実際にネイティブで動くクロスプラットフォームのライブラリが生まれてから、リッチなインタラクションを実現できるようになっていろんなプロダクトで採用されていると思う。

ReactNativeはどんなアプリで使われているの?

React自体がFacebook製というのもあって、FacebookInstagramはReactNativeだったはず。AirbnbはReactNativeだったけど今はネイティブになったらしい。 SNS系はそんなにUIに凝らない(アニメーションとか)けど、AirbnbとかはLottieみたいなアニメーションライブラリ出してたり、いろいろ力を入れている印象なので、ReactNativeでは物足りなくなったのかもしれない。ちなみにUber/UberEatsもReactNativeらしい。

ReactNativeをやってみる

ReactNativeの環境構築

https://reactnative.dev/docs/environment-setup 公式が、「Expo-CLIを使え!」と言ってくる。 チュートリアルの通りにコマンド打ち込んで、Expoインストール→プロジェクト作成しましょう。 (TypeScriptでやりたいので、そうした)

Expoって何?

ReactNativeのライブラリ。なんかいろいろ簡単にやらせてくれるらしい。 その分制約がある(ネイティブコードを弄れない、とか?) https://trilingual-engineer.com/expo-vs-react-native

アプリを起動してみる

プロジェクト直下のディレクトリ で

expo start

を実行すると、 スクリーンショット 2020-06-01 23.58.05.png

こんな画面に飛ばされるので、起動したいデバイス(シミュレーター とか実機とか)を選択する。 そしたら、そのデバイスにExpoアプリがインストールされて、その上でさっき作成したReactNativeのアプリが動く。 これはホットリロードなので、デバッグする時は保存すると勝手に反映される。はず。

何を勉強しよう?

何を勉強しようか。

 

システム開発に必要なスキル(カネはおいといて)

・開発方法?

WF?アジャイル?リソースマネジメント?チームのアウトプットを最大化させる。

 

開発プロセス

要件定義?基本設計?云々。どのフェーズでどんな作業が必要なのか。必要十分な作業工程?

 

・開発手法

ペアプロ?モブプロ?とかとか?

 

・でっかい設計

疎結合だなんだとか、ドメイン駆動とか、テスト駆動とか、なんだかんだとか

 

・小さい設計

デザインパターンとか、なんだかんだとか、でっかい設計を実現するための手法

 

・実装

コーディング。ゴリゴリ。可読性、保守性。