Slide 1

Slide 1 text

Unlocking new capabilities for PWA PWA Night CONFERENCE 2020 @Takepepe

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

1. Web Experimental Features

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Web NFC

Slide 13

Slide 13 text

2. Devlop Trial

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

2-2. Prepare

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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 */ }

Slide 28

Slide 28 text

3. API Overview

Slide 29

Slide 29 text

3-1. About NFC Tag

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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.

Slide 34

Slide 34 text

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.

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

3-2. NDEFReader

Slide 40

Slide 40 text

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}.`) })

Slide 41

Slide 41 text

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}.`) })

Slide 42

Slide 42 text

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}.`) })

Slide 43

Slide 43 text

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}.`) })

Slide 44

Slide 44 text

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}.`) })

Slide 45

Slide 45 text

3-3. NDEFWriter

Slide 46

Slide 46 text

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}.`) })

Slide 47

Slide 47 text

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}.`) })

Slide 48

Slide 48 text

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}.`) })

Slide 49

Slide 49 text

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.") })

Slide 50

Slide 50 text

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.") })

Slide 51

Slide 51 text

3-4. NDEFMessage

Slide 52

Slide 52 text

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)}`) }) }

Slide 53

Slide 53 text

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)}`) }) }

Slide 54

Slide 54 text

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)}`) }) }

Slide 55

Slide 55 text

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)}`) }) }

Slide 56

Slide 56 text

3-5. NDEFRecord

Slide 57

Slide 57 text

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() }

Slide 58

Slide 58 text

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() }

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

4. Handling APIs in App

Slide 64

Slide 64 text

4-1. Permission Request

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

4-2. Permission Handling

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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 }

Slide 75

Slide 75 text

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 }

Slide 76

Slide 76 text

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 }

Slide 77

Slide 77 text

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 }

Slide 78

Slide 78 text

4-3. Media Recorder

Slide 79

Slide 79 text

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') }

Slide 80

Slide 80 text

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') }

Slide 81

Slide 81 text

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') }

Slide 82

Slide 82 text

4-4. SerialNumber & IndexedDB

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

4-5. Side Effect Handling

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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)) }

Slide 91

Slide 91 text

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)) }

Slide 92

Slide 92 text

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

Slide 93

Slide 93 text

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

Slide 94

Slide 94 text

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

Slide 95

Slide 95 text

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)) }

Slide 96

Slide 96 text

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)) }

Slide 97

Slide 97 text

まとめ

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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