Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Unlocking new capabilities for PWA

Takepepe
February 01, 2020

Unlocking new capabilities for PWA

Takepepe

February 01, 2020
Tweet

More Decks by Takepepe

Other Decks in Technology

Transcript

  1. Unlocking new capabilities for PWA
    PWA Night CONFERENCE 2020 @Takepepe

    View Slide

  2. About Me
    ■ Takefumi Yoshii / @Takepepe
    ■ DeNA / DeSC Healthcare
    ■ Frontend Developer
    ■ TypeScript Meetup JP member
    2

    View Slide

  3. Agenda
    ■ 1. PWA や Web App で利用可能な、実験的機能について
    ■ 2. とある実験的機能 を利用してアプリを作った話
    ■ 3. とある実験的機能 の API 概要
    ■ 4. API 取り扱いポイント・React Redux に統合する話
    3

    View Slide

  4. 1. Web Experimental Features

    View Slide

  5. 1-1. Bridging the NativeApp Gap
    ServiceWorker のキャッシュ戦略により、
    PWA のみならず、Webブラウジングに
    快適な UX がもたらされています。
    1. Web Experimental Features

    View Slide

  6. 1-1. Bridging the NativeApp Gap
    1. Web Experimental Features
    その快適なレスポンスから、
    Web アプリケーション開発技術で、
    Native アプリと同等機能を提供することに、
    多くの期待が寄せられています。

    View Slide

  7. 1-1. Bridging the NativeApp Gap
    1. Web Experimental Features
    しかし、Web ブラウザに許容されている
    機能は限定的です。端末固有の機能に
    アクセスする Low-Level API の多くは、
    JavaScript には解放されていません。

    View Slide

  8. 1-1. Bridging the NativeApp Gap
    1. Web Experimental Features
    その状況を前進するべく、
    Google Chrome では Low-Level API への橋渡しを
    積極的に行なっています。
    Experimental Features(実験的機能)
    には、そんな機能が多数控えています。

    View Slide

  9. 1-2. Origin Trial
    1. Web Experimental Features
    chrome://flags で実験的機能を有効にすれば、
    その機能を試すことができます。
    そして、実験的機能を期限付き・ドメイン制限で
    提供する「Origin Trial」もあります。
    https://developers.chrome.com/origintrials

    View Slide

  10. 1-2. Origin Trial
    1. Web Experimental Features
    昨年 5月に開催された「Google I/O」
    昨年 11月に開催された「Chrome Dev Summit」で、
    この「Origin Trial」について紹介がありました。
    可能性の広がりに、期待が高まりますね!

    View Slide

  11. 1-2. Origin Trial
    1. Web Experimental Features
    今日ご紹介するのは、
    現在の私たちの生活には欠かすことのできない
    あの技術についてです。
    最も直近に解放された API でもあります。

    View Slide

  12. Web NFC

    View Slide

  13. 2. Devlop Trial

    View Slide

  14. 2-1. Web NFC Media MEMO
    Web NFC のトライアルとして、
    デモアプリを作成しました。
    次の検証環境で動作確認をしています。
    ■ Pixel 3a ■ Android 9
    ■ Google Chrome Canary v81
    2. Devlop Trial
    https://vimeo.com/388157513

    View Slide

  15. 2-1. Web NFC Media MEMO
    デモアプリは配信もしていますので、
    NFC 機能付き Android をお持ちの方は
    試していただけると嬉しいです。
    github にコードも公開しています。
    https://webnfc-media-memo.netlify.com/
    https://github.com/takefumi-yoshii/webnfc-media-memo
    2. Devlop Trial

    View Slide

  16. 2-1. Web NFC Media MEMO
    このアプリを起動している時、
    端末で検出された NFC Tag の処理は
    フォアグラウンドのブラウザに委ねられます。
    2. Devlop Trial

    View Slide

  17. 2-1. Web NFC Media MEMO
    2. Devlop Trial
    テキストメモ機能を利用して
    NFC Tag に書き込んだテキストは、
    NFC Tag に永続化されます。

    View Slide

  18. 2-1. Web NFC Media MEMO
    2. Devlop Trial
    テキストリーダー画面でタッチすると、
    書き込んだ文字列が表示されます。
    2byte 文字も可能です。

    View Slide

  19. 2-1. Web NFC Media MEMO
    2. Devlop Trial
    音声メモ機能は、
    録音した後に NFC Tag にタッチすると、
    保存することができます。

    View Slide

  20. 2-1. Web NFC Media MEMO
    そして、保存済みの
    NFC Tag にタッチすると、
    アプリ上で音声メモを自動再生します。
    2. Devlop Trial

    View Slide

  21. 2-1. Web NFC Media MEMO
    動画データも録画・再生が可能です。
    今のところ、録画の尺は設けていません。
    2. Devlop Trial

    View Slide

  22. 2-1. Web NFC Media MEMO
    NFC Tag にはこの他にも、
    様々なデータの保存が可能です。
    (ウソです!種明かしは最後に)
    2. Devlop Trial

    View Slide

  23. 2-2. Prepare

    View Slide

  24. 2-2. Prepare
    2. Devlop Trial
    一番早く API を試すことが出来る方法を紹介します。
    NFC 機能が搭載された Android端末に、
    Google Chrome Canary をインストール。
    chrome://flags を URL バーに入力し、
    Web-NFC を有効にします。
    (端末の NFC機能を有効にすることも忘れずに)

    View Slide

  25. 2-2. Prepare
    2. Devlop Trial
    localhost で挙動を確認するため、
    この Android 端末を PC に接続します。
    PC の Google Chrome を起動し、
    More tools > Remote devices から、
    接続している該当端末を選択。

    View Slide

  26. 2-2. Prepare
    2. Devlop Trial
    Port forwarding に、開発サーバーの localhost を指定。
    モバイル端末の Developer Console にアクセスします。

    View Slide

  27. 2-2. Prepare
    Developer Console から、Web NFC が利用可能かを確認します。
    定義が存在すれば、Web NFC を試す環境が整いました。
    2. Devlop Trial
    if ('NDEFReader' in window) { /* ... Scan NDEF Tags */ }
    if ('NDEFWriter' in window) { /* ... Write NDEF Tags */ }

    View Slide

  28. 3. API Overview

    View Slide

  29. 3-1. About NFC Tag

    View Slide

  30. 3-1. About NFC Tag
    API 詳細の話の前に、
    NFC Tag について少し紹介します。
    NFC Tag には様々な種類があります。
    3. API Overview

    View Slide

  31. 3-1. About NFC Tag
    Suica / Edy などに台頭する電子マネーは、
    NFC-F(Type-F)にあたります。
    データ管理やセキュリティ機能が
    強化されているものです。
    3. API Overview

    View Slide

  32. 3-1. About NFC Tag
    NFC-F については、今回調べたなかでは、
    Web NFC で読み書きはできませんでした。
    (ただし 検出は可能)
    3. API Overview

    View Slide

  33. 3-1. About NFC Tag
    Origin Trials の description を引用します。
    Low-level I/O operation などは、対応未定とのこと。
    3. API Overview
    Low-level I/O operations (e.g. ISO-DEP, NFC-A/B, NFC-F) and Host-based
    Card Emulation (HCE) are not supported within the current scope.

    View Slide

  34. 3-1. About NFC Tag
    現状試すことができる API は限定的ですが、
    NFC Tag を選べば、デモアプリの様なことが可能です。
    3. API Overview
    Low-level I/O operations (e.g. ISO-DEP, NFC-A/B, NFC-F) and Host-based
    Card Emulation (HCE) are not supported within the current scope.

    View Slide

  35. 3-1. About NFC Tag
    デモアプリで利用想定しているのは、
    NFC-A(Type-A)のものです。
    これであれば「Web NFC」でも
    読み書きをすることが可能です。
    カードタイプ・シールタイプなどがあります。
    3. API Overview
    例(カードタイプ / ¥600 / 12枚、シールタイプ / ¥790 / 11枚)

    View Slide

  36. 3-1. About NFC Tag
    NPX 社の「NTAG 215 チップ(NFC-A )」
    が採用された NFC Tag は多く流通しており、
    安価に入手することができます。
    3. API Overview
    例(カードタイプ / ¥600 / 12枚、シールタイプ / ¥790 / 11枚)

    View Slide

  37. 3-1. About NFC Tag
    形だけでなく、容量も様々なものがあります。
    そんなに大容量ではありません。
    NTAG 213: 144Byte
    NTAG 215: 504Byte
    NTAG 216: 888Byte
    3. API Overview
    例(カードタイプ / ¥600 / 12枚、シールタイプ / ¥790 / 11枚)

    View Slide

  38. 3-1. About NFC Tag
    読み込み可能な NFC Tag の仕様について、
    より詳細な情報は以下を参照しましょう。
    https://w3c.github.io/web-nfc/#x4-1-ndef-compatible-tag-types
    3. API Overview

    View Slide

  39. 3-2. NDEFReader

    View Slide

  40. 3-2. NDEFReader
    簡単な読み込み例を確認していきましょう。
    3. API Overview
    const reader = new NDEFReader()
    reader.scan().then(() => {
    console.log("Scan started successfully.")
    reader.onreading = event => {
    console.log(`NDEF message read.${event.serialNumber}`)
    }
    }).catch(error => {
    console.log(`Error! Scan failed to start: ${error}.`)
    })

    View Slide

  41. NDEFReader が NFC 読み込みに必要なインスタンスです。
    scan 関数は Promise を返します。
    3-2. NDEFReader
    3. API Overview
    const reader = new NDEFReader()
    reader.scan().then(() => {
    console.log("Scan started successfully.")
    reader.onreading = event => {
    console.log(`NDEF message read.${event.serialNumber}`)
    }
    }).catch(error => {
    console.log(`Error! Scan failed to start: ${error}.`)
    })

    View Slide

  42. 3-2. NDEFReader
    onreading ハンドラの callback は
    NDEFReadingEvent を受け取ります。
    3. API Overview
    const reader = new NDEFReader()
    reader.scan().then(() => {
    console.log("Scan started successfully.")
    reader.onreading = event => {
    console.log(`NDEF message read.${event.serialNumber}`)
    }
    }).catch(error => {
    console.log(`Error! Scan failed to start: ${error}.`)
    })

    View Slide

  43. 3-2. NDEFReader
    serialNumber は、物理タグ出荷時に
    付与されている一意の ID です。
    3. API Overview
    const reader = new NDEFReader()
    reader.scan().then(() => {
    console.log("Scan started successfully.")
    reader.onreading = event => {
    console.log(`NDEF message read.${event.serialNumber}`)
    }
    }).catch(error => {
    console.log(`Error! Scan failed to start: ${error}.`)
    })

    View Slide

  44. 3-2. NDEFReader
    serialNumber を判別することで
    「一意の NFC Tag を読み込んだ」ことを判別することができます。
    3. API Overview
    const reader = new NDEFReader()
    reader.scan().then(() => {
    console.log("Scan started successfully.")
    reader.onreading = event => {
    console.log(`NDEF message read.${event.serialNumber}`)
    }
    }).catch(error => {
    console.log(`Error! Scan failed to start: ${error}.`)
    })

    View Slide

  45. 3-3. NDEFWriter

    View Slide

  46. 3-3. NDEFWriter
    書き込みもいたって簡単です。
    文字列を NFC Tag に書き込んでみます。
    3. API Overview
    const writer = new NDEFWriter()
    writer.write("Hello World").then(() => {
    console.log("Message written.")
    }).catch(error => {
    console.log(`Write failed :-( try again: ${error}.`)
    })

    View Slide

  47. 3-3. NDEFWriter
    書き込みには、
    文字列を渡すだけの方法があります。
    3. API Overview
    const writer = new NDEFWriter()
    writer.write("Hello World").then(() => {
    console.log("Message written.")
    }).catch(error => {
    console.log(`Write failed :-( try again: ${error}.`)
    })

    View Slide

  48. 3-3. NDEFWriter
    こちらの戻り値も Promise です。
    NFC Tag が検出されたタイミングで、書き込みを試みます。
    3. API Overview
    const writer = new NDEFWriter()
    writer.write("Hello World").then(() => {
    console.log("Message written.")
    }).catch(error => {
    console.log(`Write failed :-( try again: ${error}.`)
    })

    View Slide

  49. 3-3. NDEFWriter
    書き込むのは文字列の他に、
    NDEFMessage オブジェクトを指定する方法があります。
    3. API Overview
    const message: NDEFMessage = {
    records: [{ recordType: "url", data: "https://w3c.github.io/web-nfc/" }]
    }
    const writer = new NDEFWriter()
    writer.write(message).then(() => {
    console.log("Message written.")
    }).catch(_ => {
    console.log("Write failed :-( try again.")
    })

    View Slide

  50. 3-3. NDEFWriter
    書き込むのは文字列の他に、
    NDEFMessage オブジェクトを指定する方法があります。
    3. API Overview
    const message: NDEFMessage = {
    records: [{ recordType: "url", data: "https://w3c.github.io/web-nfc/" }]
    }
    const writer = new NDEFWriter()
    writer.write(message).then(() => {
    console.log("Message written.")
    }).catch(_ => {
    console.log("Write failed :-( try again.")
    })

    View Slide

  51. 3-4. NDEFMessage

    View Slide

  52. 3-4. NDEFMessage
    NFC Tag の読み込み時にも、NDEFMessage を受け取ります。
    この内容を読み取ってみます。
    3. API Overview
    function consoleNDEFRecords(message: NDEFMEssage) {
    if (message.records.length === 0) return
    message.records.map(record => {
    const decoder = new TextDecoder()
    console.log(`Text: ${decoder.decode(record.data)}`)
    })
    }

    View Slide

  53. 3-4. NDEFMessage
    NFC Tag に書き込まれた文字列は、直接読み取ることができません。
    TextDecoder インスタンスを利用し、デコードをする必要があります。
    3. API Overview
    function consoleNDEFRecords(message: NDEFMEssage) {
    if (message.records.length === 0) return
    message.records.map(record => {
    const decoder = new TextDecoder()
    console.log(`Text: ${decoder.decode(record.data)}`)
    })
    }

    View Slide

  54. 3-4. NDEFMessage
    1byte文字列であれば、このままでも問題ありませんが、
    2byte文字を含む文字列の場合、encoding の指定が必要です。
    3. API Overview
    function consoleNDEFRecords(message: NDEFMEssage) {
    if (message.records.length === 0) return
    message.records.map(record => {
    const decoder = new TextDecoder(record.encoding)
    console.log(`Text: ${decoder.decode(record.data)}`)
    })
    }

    View Slide

  55. 3-4. NDEFMessage
    この様に、NFC-A の NFC Tag であれば、
    簡単に読み書き可能であることがわかります。
    3. API Overview
    function consoleNDEFRecords(message: NDEFMEssage) {
    if (message.records.length === 0) return
    message.records.map(record => {
    const decoder = new TextDecoder(record.encoding)
    console.log(`Text: ${decoder.decode(record.data)}`)
    })
    }

    View Slide

  56. 3-5. NDEFRecord

    View Slide

  57. 3-5. NDEFRecord
    NDEFRecord について、もう少し詳しくみていきましょう。
    Web IDL interface はつぎの様に定義されています。
    3. API Overview
    interface NDEFRecord {
    constructor(NDEFRecordInit recordInit)
    readonly attribute USVString recordType
    readonly attribute USVString? mediaType
    readonly attribute USVString? id
    readonly attribute DataView? data
    readonly attribute USVString? encoding
    readonly attribute USVString? lang
    sequence? toRecords()
    }

    View Slide

  58. 3-5. NDEFRecord
    NDEFRecord のなかで必須プロパティであるのは、recordType です。
    NDEFRecord の解析はこれを確認するところから始まります。
    3. API Overview
    interface NDEFRecord {
    constructor(NDEFRecordInit recordInit)
    readonly attribute USVString recordType
    readonly attribute USVString? mediaType
    readonly attribute USVString? id
    readonly attribute DataView? data
    readonly attribute USVString? encoding
    readonly attribute USVString? lang
    sequence? toRecords()
    }

    View Slide

  59. 3-5. NDEFRecord
    recordType は任意の文字列と
    することができますが、
    Well-known type records
    として取り決められている
    のは次の3種類です。
    ■ text
    ■ url
    ■ smart-poster
    3. API Overview

    View Slide

  60. 3-5. NDEFRecord
    recordType: "url" の record を
    保持した NFC Tag を検出した
    端末は、何もアプリケーション
    を起動していない場合、
    ブラウザの起動を促します。
    3. API Overview

    View Slide

  61. 3-5. NDEFRecord
    そのため、Web NFC を利用し
    recordType: "url" を指定した
    NDEFRecord を書き込めば、
    任意の NFC Tag を
    ブラウザランチャーと
    することもできます。
    3. API Overview

    View Slide

  62. 3-5. NDEFRecord
    recordType に
    アプリケーション特有の
    接頭辞を付与し、検出した
    タグの処理を絞り込む例も
    紹介されています。
    ■ https://w3c.github.io/web-nfc/
    3. API Overview

    View Slide

  63. 4. Handling APIs in App

    View Slide

  64. 4-1. Permission Request

    View Slide

  65. 4-1. Permission Request
    Web NFC を使うアプリは、
    何よりもはじめにパーミッションを
    得る必要があります。
    4. Handling APIs in App
    const reader = new NDEFReader()
    await reader.scan()

    View Slide

  66. 4-1. Permission Request
    注意しなければいけないのは、
    ページロードと同時に
    API をコールしないことです。
    4. Handling APIs in App
    const reader = new NDEFReader()
    await reader.scan()

    View Slide

  67. 4-1. Permission Request
    このタイミングでコールすると、
    パーミッション・プロンプトが
    表示されません。
    4. Handling APIs in App
    const reader = new NDEFReader()
    await reader.scan()

    View Slide

  68. 4-1. Permission Request
    パーミッション・プロンプトを
    表示させるため、押下コールバックで
    API をコールする設計にしましょう。
    4. Handling APIs in App
    const reader = new NDEFReader()
    await reader.scan()

    View Slide

  69. 4-1. Permission Request
    そして、NDEFReader インスタンスを
    解放しない留意が必要です。scan が実行
    されていないと、別アプリが検出に反応し、
    フォアグラウンドを奪われてしまいます。
    4. Handling APIs in App
    const reader = new NDEFReader()
    await reader.scan()

    View Slide

  70. 4-1. Permission Request
    そもそも端末設定で、NFC 機能への
    アクセスを拒否している場合もあります。
    それらの考慮と併せ、利用できないケースを
    ユーザーに明示しましょう。
    4. Handling APIs in App
    const reader = new NDEFReader()
    await reader.scan()

    View Slide

  71. 4-2. Permission Handling

    View Slide

  72. 4-2. Permission Handling
    サンプルアプリでは カメラ・マイクを利用しており、
    こちらもパーミッションが必要になります。
    下記関数を利用することで MediaStream が立ち上がりますが、
    「カメラ・マイク」のパーミッションプロンプトも同時に表示されます。
    4. Handling APIs in App
    function getUserMedia(constraints: MediaStreamConstraints) {
    return navigator.mediaDevices.getUserMedia(constraints)
    }

    View Slide

  73. 4-2. Permission Handling
    4. Handling APIs in App
    アクセス権限状態の識別は重要です。
    一度でも機能へのアクセスをブロックすると、
    それ以降、その機能にアクセスすることができません。
    (設定解除への誘導が必要)
    function getUserMedia(constraints: MediaStreamConstraints) {
    return navigator.mediaDevices.getUserMedia(constraints)
    }

    View Slide

  74. 4-2. Permission Handling
    サンプルアプリでは、Permission API を利用し、
    Origin の権限状態を取得しています。
    ブロックされている状況などを把握するために、
    Permission API は有効です。
    4. Handling APIs in App
    const status = await navigator.permissions.query({ name: 'nfc' })
    console.log(status.state) // "granted" | "denied" | "prompt"
    status.onchange = () => {
    // dosomething ex:) re render view
    }

    View Slide

  75. 4-2. Permission Handling
    サンプルアプリでは、Permission API を利用し、
    Origin の権限状態を取得しています。
    ブロックされている状況などを把握するために、
    Permission API は有効です。
    4. Handling APIs in App
    const status = await navigator.permissions.query({ name: 'nfc' })
    console.log(status.state) // "granted" | "denied" | "prompt"
    status.onchange = () => {
    // dosomething ex:) re render view
    }

    View Slide

  76. 4-2. Permission Handling
    query 関数で状態を知る方法と同じ様に、
    request 関数でパーミッション・プロンプトを表示することもできます。
    いずれも、ブラウザによってサポート状況がまちまちなので
    利用する前に確認しましょう。
    4. Handling APIs in App
    const status = await navigator.permissions.query({ name: 'nfc' })
    console.log(status.state) // "granted" | "denied" | "prompt"
    status.onchange = () => {
    // dosomething ex:) re render view
    }

    View Slide

  77. 4-2. Permission Handling
    権限状況をユーザーに知らせる View があると親切です。
    権限変更された時に発火されるハンドラも指定できます。
    Native API の多くは、Permission を求めるものが多いです。
    利用するアプリケーションでは、手厚くサポートしていきましょう。
    4. Handling APIs in App
    const status = await navigator.permissions.query({ name: 'nfc' })
    console.log(status.state) // "granted" | "denied" | "prompt"
    status.onchange = () => {
    // dosomething ex:) re render view
    }

    View Slide

  78. 4-3. Media Recorder

    View Slide

  79. 4-3. Media Recorder
    4. Handling APIs in App
    メディアの記録は、MediaRecorder を利用しています。
    録音・録画がとても簡単に実現できます。
    const mediaRecorder = new MediaRecorder(stream, options)
    mediaRecorder.onstart = () => {
    console.log('start rec')
    }
    mediaRecorder.onstop = () => {
    console.log('stop rec')
    }
    mediaRecorder.ondataavailable = event => {
    console.log('data available')
    }

    View Slide

  80. 4-3. Media Recorder
    メディアの記録に必要な MediaStream を立ち上げた後に、
    MediaRecorder インスタンスを生成します。
    4. Handling APIs in App
    const mediaRecorder = new MediaRecorder(stream, options)
    mediaRecorder.onstart = () => {
    console.log('start rec')
    }
    mediaRecorder.onstop = () => {
    console.log('stop rec')
    }
    mediaRecorder.ondataavailable = event => {
    console.log('data available')
    }

    View Slide

  81. 4-3. Media Recorder
    「ondataavailable」ハンドラでは、録画データを受け取ることができます。
    この録画データを、任意の方法で保存します。
    4. Handling APIs in App
    const mediaRecorder = new MediaRecorder(stream, options)
    mediaRecorder.onstart = () => {
    console.log('start rec')
    }
    mediaRecorder.onstop = () => {
    console.log('stop rec')
    }
    mediaRecorder.ondataavailable = event => {
    console.log('data available')
    }

    View Slide

  82. 4-4. SerialNumber & IndexedDB

    View Slide

  83. 4-4. SerialNumber & IndexedDB
    デモアプリでは、NFC Tag にメディアの記録をしているかの様に、
    演出を施していました。
    これは種明かしをすると、NFC Tag の「serialNumber」に紐付け、
    データを永続化しているだけです。
    4. Handling APIs in App
    localforageStore.getItem(id)
    localforageStore.setItem(id, blob)

    View Slide

  84. 4-4. SerialNumber & IndexedDB
    4. Handling APIs in App
    その一意の ID を key に、IndexedDB に
    メディア SRC である Blob を書き込んでいます。
    デモアプリでは、localforage を利用しています。
    IndexedDB のラッパーライブラリです。
    localforageStore.getItem(id)
    localforageStore.setItem(id, blob)

    View Slide

  85. 4-4. SerialNumber & IndexedDB
    IndexedDB の取り扱いはやや煩雑です。
    localStorage と同じ感覚で使えるこちらのライブラリ。
    Promise を返してくれるため、設計に盛り込みやすいです。
    (今回はスピード重視で選定しました)
    4. Handling APIs in App
    localforageStore.getItem(id).then(...)
    localforageStore.setItem(id, blob).then(...)

    View Slide

  86. 4-5. Side Effect Handling

    View Slide

  87. 4-5. Side Effect Handling
    さて、ここまでで紹介した複数の API。
    コールバックハンドラや Promise がほとんどで、
    非同期処理のサラダボウルですね。
    ひとつひとつの使い方が単純でも、順序や制御など、
    アプリケーションへの統合に一工夫が必要になります。
    4. Handling APIs in App

    View Slide

  88. 4-5. Side Effect Handling
    4. Handling APIs in App
    副作用が混在する Application は、
    副作用に特化したライブラリが便利です。
    私が手に馴染んでいる React では、
    今回の様な アプリ色が濃いものの場合、
    Redux と redux-saga を使っています。

    View Slide

  89. 4-5. Side Effect Handling
    4. Handling APIs in App
    Store 構成は「共有機能ドメイン」と「各ページドメイン」
    に分けて、Reducer や Action をそれぞれ設けています。
    これらのドメインをより集め、ひとつの Store とします。
    各ページドメインは「共有機能ドメインの利用者」という位置付けです。
    この構成により、各々の実装が単純になっています。

    View Slide

  90. 4-5. Side Effect Handling
    MediaRecorder を例に見てみましょう。
    録画・録音の責務は、機能ドメインで一限管理します。
    4. Handling APIs in App
    mediaRecorder.onstart = () => {
    store.dispatch(creators.onStartRecording())
    }
    mediaRecorder.onstop = () => {
    store.dispatch(creators.onStopRecord())
    }
    mediaRecorder.ondataavailable = event => {
    const blob = new Blob([event.data], { type: 'video/webm' })
    store.dispatch(creators.onDataAvailable(blob))
    }

    View Slide

  91. 4-5. Side Effect Handling
    4. Handling APIs in App
    JavaScript Native API コールバックハンドラの中で、
    Redux Store の dispatch を実行しています。
    mediaRecorder.onstart = () => {
    store.dispatch(creators.onStartRecord())
    }
    mediaRecorder.onstop = () => {
    store.dispatch(creators.onStopRecord())
    }
    mediaRecorder.ondataavailable = event => {
    const blob = new Blob([event.data], { type: 'video/webm' })
    store.dispatch(creators.onDataAvailable(blob))
    }

    View Slide

  92. 4-5. Side Effect Handling
    各ページは、共有機能ドメインの Action を購読したり、
    共有機能ドメイン に Action を発行します。
    4. Handling APIs in App
    switch (action.type) {
    case PermanentStorageTypes.ON_SUCCESS_PUT:
    return handleStateByMode(state, 'ready')
    case MediaRecorderTypes.ON_START_RECORDING:
    return handleStateByMode(state, 'recording')
    case MediaRecorderTypes.ON_DATA_AVAILABLE:
    return { ...state, blob: action.payload.blob }
    default:
    return state
    }
    Page Reducer
    Subscribe Actions

    View Slide

  93. 4-5. Side Effect Handling
    各ページは、共有機能ドメインの Action を購読したり、
    共有機能ドメイン に Action を発行します。
    4. Handling APIs in App
    const handleClickIcon = React.useCallback(() => {
    switch (mode) {
    case 'ready':
    dispatch(startRecording({ audio: true, video: true }))
    break
    case 'recording':
    dispatch(stopRecording())
    }
    }, [mode])
    Page Component
    Dispatch Actions

    View Slide

  94. 4-5. Side Effect Handling
    今回のデモアプリで redux-saga が最も役にたったシーンは、
    メディアの録画開始時に表示されるカウントダウン機能です。
    これは演出以外にも、重要な役割を担っています。
    カメラの起動直後(MediaStream 開始直後)暗い場所などでは
    露出補正のため数秒間暗くなってしまう瞬間があります。
    このタイミングで録画を始めてしまうと、良い画が撮れません。
    4. Handling APIs in App

    View Slide

  95. 4-5. Side Effect Handling
    カウントダウンも一種の副作用であり、非同期処理といえます。
    カウントダウン中に生じる別の副作用(画面遷移など)が問題になり得ます。
    4. Handling APIs in App
    let count = 4
    while (count) {
    const [normal, abnormal] = yield race([
    call(countDownStep),
    call(countDownCancel)
    ])
    if (abnormal !== undefined) break
    count -= normal
    yield put(creators.onCountDown(count))
    }

    View Slide

  96. 4-5. Side Effect Handling
    この様な問題も saga-effect の組み合わせで克服することができます。
    イレギュラーなケースもカバーしてくれる、心強い味方です。
    4. Handling APIs in App
    let count = 4
    while (count) {
    const [normal, abnormal] = yield race([
    call(countDownStep),
    call(countDownCancel)
    ])
    if (abnormal !== undefined) break
    count -= normal
    yield put(creators.onCountDown(count))
    }

    View Slide

  97. まとめ

    View Slide

  98. 機能が複雑・複合的になると、
    非同期処理が入り乱れることが分かりました。
    Redux・redux-saga の例を紹介しましたが、
    非同期処理に強いライブラリは様々なものがありますので、
    手に馴染むものを見つけてみてください。
    まとめ

    View Slide

  99. まとめ
    Google Chrome の Experimental Features は、
    面白いものが盛りだくさんです。
    Native API にどんどんリーチできる様になっています。
    アイディア次第で「便利・おもしろい」
    アウトプットができそうです。

    View Slide

  100. まとめ
    Experimental Features のトライアルが、
    新しいサービスの種になるかもしれません。
    ぜひ、挑戦してみてください。

    View Slide

  101. ご静聴ありがとうございました

    View Slide