Slide 1

Slide 1 text

react-reconcilerで オレオレReact Nativeを作ろう! ああうえ(@_kwzr_) iOSDC2022 2022/09/12

Slide 2

Slide 2 text

自己紹介 e ああうえ(@_kwzr_Y e ピクシブ株式会社 モバイルアプリエンジニ3 e 今回はReactの話をしますが、普段はSwiftを書いていま e 趣味はお絵かきと、うさぎ

Slide 3

Slide 3 text

この発表のゴール Q iOS上で動くReact Native(のようなもの)を作れるようにな' Q react-reconcilerを使ってレンダラを実装する方法を知' Q React Nativeの仕組みについても知り、実装を比較できるように なる!

Slide 4

Slide 4 text

こんなのを作っていきます

Slide 5

Slide 5 text

前置き y React Nativeを自作する意味は、多くの場合ありません8 y こんな時に役立つ資料でw y Reactをソフトウェアの一部に組み込みたいと y ReactとReact Nativeがどう繋がっているのか知りたいと y このスライドで紹介するReactとReact Nativeの情報は2022年8 月時点のものです

Slide 6

Slide 6 text

Reactをソフトウェアに組み込みたい例 とは

Slide 7

Slide 7 text

例) Raycast S RaycastはSpotlightのようなMac向けのランチャーアプリで S ExtensionをReactで書くことができます

Slide 8

Slide 8 text

例) Raycast コード・実行例 export default function Command() { function handleSubmit(values: Values) { showToast(...) } return }

Slide 9

Slide 9 text

例) Figma/FigJam U Figma: ブラウザで使えるデザインツー0 U FigJam: ホワイトボードツー0 U Widgetと呼ばれるプラグインをReactで書くことができる

Slide 10

Slide 10 text

例) Figma/FigJam コード・実行例 function Widget() { const [count, setCount] = useSyncedState('count', 0) return { setCount(count - 1) }} /> {count} { setCount(count + 1) }} /> } widget.register(Widget)

Slide 11

Slide 11 text

こんな機能ってどう作るんだろう...?

Slide 12

Slide 12 text

react-reconciler を発見する ※RaycastやFigma・FigJamがreact-reconcilerを使ってるかは知らない

Slide 13

Slide 13 text

RaycastやFigmaみたいなことはできそうか 4 技術的にはできそう@ 4 Apple Review Guideline的にはどうなのか

Slide 14

Slide 14 text

プラグインとApple Review Guideline  残念ながら、プラグイン機能を持つアプリの配信はApple Review Guidelineで禁止されていB  iOS/macOSのApp Store向けには配信することができなV  詳しくは2.5.2, 3.2.2あたりを参照

Slide 15

Slide 15 text

何に使えるのか V 野良macOSアプY V React Nativeが対応していないプラットフォームでReactを書きた いと8 V クロスプラットフォームで一部のUIをReactで書きたいと8 V React Nativeを一部導入する際はCocoaPodsが必要だったり、 Objective-CやC++に触れる必要があったりする

Slide 16

Slide 16 text

オレオレReact Nativeを作る 基礎知識

Slide 17

Slide 17 text

Reactとは? E Webフロントエンド開発に用いられるUI構築ライブラ0 E JSXという記法を用いて、HTMLのようなタグを使ってUIを宣言的 に書ける const MyComponent = () => { return

Hello

World

}

Slide 18

Slide 18 text

React Nativeとは? G React Nativeは、Reactをモバイル開発で使えるようにしたライブ ラc G iOS・Androidに対してクロスプラットフォーム開発ができA G ホットリロードで高速に開発

Slide 19

Slide 19 text

React NativeのUI描画 h UIの描画自体は、ネイティブのものを利用している。ReactはUIの 構造を定義したり、状態管理に用いるだ1 h ネイティブアプリに近い体験を提供可s h 例えば、FlutterはSkiaというグラフィックライブラリでUIを 描画している

Slide 20

Slide 20 text

react-reconcilerとは — Webフロントで利用するReact DOMと、モバイルアプリで利用す るReact Nativeでは、UIの描画方法は違えど、共通していること があu — JSXを利用してUI記述することg — 状態変更によって描画の更新が起こるというこg — react-reconcilerはReactの共通ロジックの定義と、差分検出処理 を行うパッケージ

Slide 21

Slide 21 text

reconciler(リコンサイラ) $ 和訳: 調停者、平和をもたらす0 $ 日本語だと全然ピンとこないので、和訳せずにreconcilerと言いま す

Slide 22

Slide 22 text

react-reconcilerが行う差分検出処理とは ' 状態更新があってUIを描画し直すときのことを考えてみましょう

Hello

Hello

World

Slide 23

Slide 23 text

差分検出 p 全てのUIを描画し直すことは非効率。Helloを赤色に変更して、 Helloの下にWorldという文字の描画を追加してあげれば良‚ p どこのUIが更新されたのか、UIの構造を差分検出して一部のUIの みを描画し直す、というのが現代的なUI描画ではよくやられY p どの要素を追加したり更新したりするか、という差分検出処理を react-reconcilerが行っている

Slide 24

Slide 24 text

react-reconcilerの歴史 x 現在のReact(16以降)では、Fiber reconcilerが採H x 協調マルチタスクによる、UI描画のスケジューリング機能が実 装されてい4 x React 15以前ではstack reconcilerが使われていs x UIツリー全てを素朴にtraverseしていく

Slide 25

Slide 25 text

react-reconcilerの利用例 G ブラウザのCanvasで2Dや3D描画をする際に、Reactを使って書け るようにreact-reconcilerを使っているライブラリがあ9 G react-pixV G react-three-fiber

Slide 26

Slide 26 text

react-pixi const App = () => ( )

Slide 27

Slide 27 text

react-three-fiber export default function App() { return ( ) }

Slide 28

Slide 28 text

react-reconcilerをiOSで動かしたい ) JavaScriptが動けばどんな環境でもReactを動かすことができ' ) → オレオレReact Nativeを作ってみよう!

Slide 29

Slide 29 text

iOSでJavaScriptを動かす s JavaScriptCoru s 主にSafariなどで利用されているJavaScriptのエンジG s iOSでもAPIが提供されており、iOS 7から利用できI s SwiftからJavaScriptのコードを実行したり、JavaScriptのコード からSwiftで定義された関数などを呼び出すことができI s JavaScriptCoreの上でreact-reconcilerを動かすことで、Reactの コードをiOS上でレンダリング可能

Slide 30

Slide 30 text

参考: iOSでJavaScriptを動かす G 「JavaScriptCoreで遊ぼう」という本をめちゃくちゃ参考にさせ ていただきました$ G https://booth.pm/ja/items/1885814

Slide 31

Slide 31 text

JavaScriptコードを動かす(1) 単純な実行 6 JavaScriptCoreをimpor1 6 JSContextを作って、evaluateScriptしてあげる import JavaScriptCore let context = JSContext() context.evaluateScript("1 + 1") // 2

Slide 32

Slide 32 text

JavaScriptコードを動かす(2) 変数の公開 b setObject(_:forKeyedSubscript:) を利用するとSwiftの変数など を呼び出すことができる let text = "Hello" let context = JSContext()! context.setObject(text, forKeyedSubscript: "text" as NSString) print(context.evaluateScript("text")) // Hello

Slide 33

Slide 33 text

JavaScriptコードを動かす(3) 型情報の公開 w 型情報を公開して、JavaScript側で利用することもできP w JSExportにconformした型であれば、JSContextにsetObjectし て公開したときも、プロパティやメソッドにもアクセスできる

Slide 34

Slide 34 text

import JavaScriptCore // JSExportにconformしたprotocolを作成する // SwiftではなくObjective-Cのオブジェクトを公開できる機能なので、 // @objcをつける必要がある @objc protocol Foo: JSExport { var bar: String { get set } func baz() -> String } class FooImpl: NSObject, Foo { var bar: String = "Hello" func baz() -> String { return "World" } }

Slide 35

Slide 35 text

let foo = FooImpl() let context = JSContext()! // ここで変数を公開。型情報も公開されるので、 // プロパティやメソッドにもアクセスできる context.setObject(foo, forKeyedSubscript: "foo" as NSString) print(context.evaluateScript("foo.bar")) // Hello print(context.evaluateScript("foo.baz()")) // World

Slide 36

Slide 36 text

JavaScriptコードを動かせた 5 JS←→Swiftでやり取りする準備が整った

Slide 37

Slide 37 text

カスタムレンダラーの実装

Slide 38

Slide 38 text

カスタムレンダラーの実装 y react-reconcilerを使って、ReactをiOSのUIにレンダリングする カスタムレンダラーを作C y 今回紹介するコードはGitHubに上がっていますので、細かいとこ ろなどはそちらを参考にしてくださ) y https://github.com/kvvzr/reconciler-ios-sample

Slide 39

Slide 39 text

react-reconcilerの使い方 D READMEを読むと大体わか) D https://github.com/facebook/react/tree/main/packages/ react-reconciler#usage

Slide 40

Slide 40 text

react-reconcilerの使い方 Rendererの初期化 const Reconciler = require('react-reconciler') const HostConfig = { // You'll need to implement some methods here. // See below for more information and examples. } const MyRenderer = Reconciler(HostConfig) ...

Slide 41

Slide 41 text

react-reconcilerの使い方 Rendererの初期化 const Reconciler = require('react-reconciler') const HostConfig = { // You'll need to implement some methods here. // See below for more information and examples. } const MyRenderer = Reconciler(HostConfig) ... どのようにレンダリングするか書く

Slide 42

Slide 42 text

react-reconcilerの使い方 Swiftから呼ぶrender関数を定義 global.render = () => { if (renderContainer == null) { renderContainer = MyRenderer.createContainer( "root", ConcurrentRoot ); } MyRenderer.updateContainer( , renderContainer ) }

Slide 43

Slide 43 text

react-reconcilerの使い方 Swiftから呼ぶrender関数を定義 global.render = () => { if (renderContainer == null) { renderContainer = MyRenderer.createContainer( "root", ConcurrentRoot ); } MyRenderer.updateContainer( , renderContainer ) } レンダリングしたい rootコンポーネントを渡す

Slide 44

Slide 44 text

HostConfigの実装 b HostConfigは大きく、Core Methods、Mutation Methods、 Persistence Methods、Hydration Methodsに分かれていI b 全てのメソッドを合わせると約100個存x b Core Methodの一部を実装すればReactのコードをUIKitでレンダ リング可 b Mutation ModeかPersistence Modeの2つがあり、どちらかを選 ぶ必要がある

Slide 45

Slide 45 text

Mutation Methods p supportsMutationをtrueにすると呼ばれa p View構造が変わったときに呼ばれるメソッドs p 子Viewの追加・削除・可視状態などを制御できまg p React DOM, 旧React Nativeのレンダラーはこのモードを利 p 今回のサンプルではMutation Modeで実装していきます

Slide 46

Slide 46 text

Persistence Methods I supportsPersistenceをtrueにすると呼ばれ… I 既存のノードを変更していくのではなく、全ノードを複製して新 しいViewツリーを作るためのメソッド群が呼ばれるようにな… I (おそらく) Immutableに書けるので、処理がシンプルになる) I React Nativeの新しいレンダラーで利用されてい… I (おそらくShadowNodeを実装しないとパフォーマンスを発揮できないので、今回は実装しない)

Slide 47

Slide 47 text

Hydration Methods U SSR(Server Side Rendering)用のメソッドなので、UIKit上だけで のレンダリングであれば実装は不要

Slide 48

Slide 48 text

HostConfigのメソッド例 Viewの生成 i createInstance はJSXのtypeとpropsからView作ってを返す関2 i JSからSwiftのコードを呼び出してUIViewのインスタンスを返す ことも可能 // HostConfig.js const HostConfig = { createInstance(type, props) { ... } ... }

Slide 49

Slide 49 text

HostConfigのメソッド例 JS側にSwiftのメソッドを公開する // HostConfig.swift @objc protocol HostConfig: JSExport { func createInstance(_ type: String, _ props: JSValue) -> UIView } class HostConfigImpl: NSObject, HostConfig { func createInstance(_ type: String, _ props: JSValue) -> UIView { ... } ... } let hostConfig = HostConfigImpl() jsContext.setObject(hostConfig, forKeyedSubscript: "HostConfigSwift" as NSString)

Slide 50

Slide 50 text

HostConfigのメソッド例 Swiftのメソッドを呼び出す I UIViewのインスタンスを返す準備ができた // HostConfig.js const HostConfig = { createInstance(type, props) { return HostConfigSwift.createInstance(type, props) } ... }

Slide 51

Slide 51 text

Viewの生成 – createInstanceの実装では、typeとpropsを見て、UIViewを作っ て返• – typeにはJSXのタグ(であればtext)が入ってくるので、 typeによって作成するUIViewを分けてあげ7 – propsには、コンポーネントのプロパティが入ってくる。helloと書いた場合にはpropsには {fontSize: 10, children: “hello”}のような値が入ってくる

Slide 52

Slide 52 text

Viewの生成 createInstanceの実装 # 以下の例のように実装してあげます func createInstance(_ type: String, _ props: JSValue) -> UIView { switch type { case "text": let label = UILabel() if let text = props.objectForKeyedSubscript("children"), !text.isUndefined { label.text = text.toString() } return label ... } }

Slide 53

Slide 53 text

Viewの生成 const MyComponent = () => { return Hello });

Slide 54

Slide 54 text

状態変更が起こった時 5 初回のViewの描画はできた。次は状態変更が起こった場合のこと を考えていきます const MyComponent = () => { const [count, setCount] = useState(0) return ( setCount(count + 1)} /> ) }

Slide 55

Slide 55 text

状態変更が起こった時 E propsが更新されたときにはcommitUpdateが呼ばれま8 E ここに、Viewのプロパティや見た目などを更新していくコードを 書いていきま8 E ※JS側からの呼び出し方は変わらないので省略してSwiftコードの み紹介します

Slide 56

Slide 56 text

状態変更が起こった時 func commitUpdate( _ instance: JSValue, _ updatePayload: JSValue, _ type: String, _ prevProps: JSValue, _ nextProps: JSValue ) { guard let view = instance.toObject() as? UIView else { return } ... }

Slide 57

Slide 57 text

状態変更が起こった時 func commitUpdate( _ instance: JSValue, _ updatePayload: JSValue, _ type: String, _ prevProps: JSValue, _ nextProps: JSValue ) { guard let view = instance.toObject() as? UIView else { return } ... } instanceにはさっき作ったViewが入ってくる

Slide 58

Slide 58 text

状態変更が起こった時 func commitUpdate( _ instance: JSValue, _ updatePayload: JSValue, _ type: String, _ prevProps: JSValue, _ nextProps: JSValue ) { guard let view = instance.toObject() as? UIView else { return } ... } nextPropsに新しいpropsが入っているので これを見てViewを更新する

Slide 59

Slide 59 text

状態変更が起こった時 // commitUpdate内 switch type { case "button": guard let button = view as? UIButton else { return } if let title = nextProps.objectForKeyedSubscript("title"), !title.isUndefined { button.configuration?.title = title.toString() } return button ... }

Slide 60

Slide 60 text

状態変更が起こった時 // commitUpdate内 switch type { case "button": guard let button = view as? UIButton else { return } if let title = nextProps.objectForKeyedSubscript("title"), !title.isUndefined { button.configuration?.title = title } return button ... } propsからtitleの値を取り出して UIButtonにセット

Slide 61

Slide 61 text

状態変更が起こった時 const MyComponent = () => { const [count, setCount] = useState(0) return ( setCount(count + 1)} /> ) }

Slide 62

Slide 62 text

View構造に対応する 0 親子構造になっているViewでも、appendInitialChild, appendChildを実装すればUIKitのViewとして描画可能 hello world hello world

Slide 63

Slide 63 text

View構造に対応する @ appendInitialChildは初回のUI描画前に行う処Y @ appendChildはMutation Modeで途中でView構造が変わった時に 呼ばれる。どちらも同様に実装すればOK func appendInitialChild(_ parent: JSValue, _ child: JSValue) { guard let parentView = parent.toObject() as? UIView else { return } guard let childView = child.toObject() as? UIView else { return } parentView.addSubview(childView) }

Slide 64

Slide 64 text

UI構造が変わった時 I 他にもView構造の操作をするMutation Methodを一通り実装して い I insertBefore, removeChild, hideInstance, unhideInstanceなど

Slide 65

Slide 65 text

À const MyComponent = () => { const [count, setCount] = useState(0) const views = [...Array(count)].map((_, index) => { return {index + 1} }) return ( {views} setCount(count + 1)} /> setCount(count - 1)} /> ) }

Slide 66

Slide 66 text

補足: SwiftUIは使えないの? t SwiftUIは宣言的なUIフレームワークなので、外部の入力を受け 取って動的にViewを変更することは苦T t 特定のViewの参照を取得したり、特定のViewにaddSubviewなど するようにできてない

Slide 67

Slide 67 text

レイアウトする F Viewの幅・高さ、margin、Viewを並べる方向など、いろいろなレ イアウト情報を設定したくなってくる

Slide 68

Slide 68 text

現代的なUIレイアウトを構築する要件 † どんな画面サイズにも対応できる必要があ‘ † Viewの位置やサイズを直接入力することは避けるべき(UIView のframeなどˆ † UIKitでは通常、AutoLayoutを使ってレスポンシブなUIを作 る。AutoLayoutは親と子で制約を貼る必要があるため、React のような書き方とは相性が悪い

Slide 69

Slide 69 text

Flexboxを使ってレイアウトする – Flexboxはさまざまなプラットフォームに実装されているレイアウ トエンジV – CSSで見たことある人も多いはB – 今回、React Nativeにも用いられているYogaというFlexboxの実 装で、レイアウトを組めるようにす` – サンプルではSwift Packageから簡単に利用できる SwiftYogaKit を使っています

Slide 70

Slide 70 text

Yogaを使ってレイアウトする let uiView = UIView() uiView.backgroundColor = .red uiView.yoga.width(200).height(200) let child1 = UIView() child1.backgroundColor = .green child1.yoga .width(100) .height(40) .margin(20) uiView.addSubview(child1) let child2 = UIView() child2.backgroundColor = .yellow child2.yoga .alignSelf(.flexEnd) .width(100) .height(100) uiView.addSubview(child2) 100x40

Slide 71

Slide 71 text

Yogaを使ってレイアウトする let uiView = UIView() uiView.backgroundColor = .red uiView.yoga.width(200).height(200) let child1 = UIView() child1.backgroundColor = .green child1.yoga .width(100) .height(40) .margin(20) uiView.addSubview(child1) let child2 = UIView() child2.backgroundColor = .yellow child2.yoga .alignSelf(.flexEnd) .width(100) .height(100) uiView.addSubview(child2) 100x40 20 20

Slide 72

Slide 72 text

Yogaを使ってレイアウトする let uiView = UIView() uiView.backgroundColor = .red uiView.yoga.width(200).height(200) let child1 = UIView() child1.backgroundColor = .green child1.yoga .width(100) .height(40) .margin(20) uiView.addSubview(child1) let child2 = UIView() child2.backgroundColor = .yellow child2.yoga .alignSelf(.flexEnd) .width(100) .height(100) uiView.addSubview(child2) 100x100 20

Slide 73

Slide 73 text

ここまでのを使って実装していくと...できた!

Slide 74

Slide 74 text

パフォーマンスについて h 触ってる感じ、パフォーマンスは問題ないように思える...4 h 大量のViewを配置して、スライダーなどで連続的に状態を更新 → Time Profilerで見てみると、CPUをめっちゃ食っていC h JSを呼んでいるから、だけではない ※1秒間スライダーを動かし続けたときのCPU時間を計測 623ms/1000ms こんな感じのView × 50個

Slide 75

Slide 75 text

原因1: 関係ないViewが更新されている c commitUpdateは実際にViewの更新をすべきでない時にも呼ばれ てしま5 c そのため、UIViewの更新処理に重いものがあったりすると、ボト ルネックとなる // commitUpdate内 guard let button = view as? UIButton else { return } if let title = nextProps.objectForKeyedSubscript("title") { // 関係ない変更でも毎回呼ばれてしまっている! button.configuration?.title = title.toString() }

Slide 76

Slide 76 text

原因2: Yogaのレイアウトを走らせている x レイアウトをし直す処理は遅いので、頻繁に呼ばれるのを防2 x yoga.applyLayout()や、yoga.markDirty()な0 x 例: テキストを更新しない場合でもyoga.markDirty()を呼んでし まっている // commitUpdate内 guard let label = view as? UILabel else { return } if let children = nextProps.objectForKeyedSubscript("children") { label.text = children.toString() label.yoga.markDirty() // 関係ない変更でも毎回呼ばれてしまっている! }

Slide 77

Slide 77 text

propsが更新されたときのみ更新する ƒ 更新すべきプロパティを見てViewを更新してあげf ƒ prepareUpdateはcommitUpdateの前に呼ばれf ƒ このメソッドではoldPropsとnewPropsを比較して、更新があった かpropsのkeyの配列を返A ƒ shallowDiff などで判定してあげる prepareUpdate(instance, type, oldProps, newProps, rootContainer, hostContext) { return shallowDiff(oldProps, newProps) }

Slide 78

Slide 78 text

propsが更新されたときのみ更新する 8 commitUpdateのupdatePayloadにprepareUpdateで計算した、 更新されたpropsのkeysが入ってくる func commitUpdate( _ instance: JSValue, _ updatePayload: JSValue, _ type: String, _ prevProps: JSValue, _ nextProps: JSValue ) { ... }

Slide 79

Slide 79 text

propsが更新されたときのみ更新する E updatePayloadに対象のkeyが入っていなければ、関係ない変更な のでViewを更新しないようにする func updateTitle( _ button: UIButton, _ nextProps: JSValue, _ updatePayload: [String] ) { guard updatePayload.contains("title") else { return } let title = nextProps.objectForKeyedSubscript("title") button.configuration?.title = title.toString() }

Slide 80

Slide 80 text

propsが更新されたときのみ更新する f 623ms→427msくらいまで減らすことができE f そもそも重いComponentなので、これ以上減らすには Componentをmemo化するなどが効果的(React側のテクニック)

Slide 81

Slide 81 text

原因3: 重いReactコードを書いている ƒ コンポーネントのpropsに即時関数を渡してしまうと、毎回 functionのオブジェクトが生成されて、更新されてしま5 ƒ useCallbackを使って、関係ない更新のときは同じfunctionを返す ようにする // ↓useCallbackで、callbackをメモ化してあげる必要がある const handleSliderValueChange = useCallback( (value) => { setProgress(value) }, [progress] ) { setProgress(value) }} />

Slide 82

Slide 82 text

原因3: 重いReactコードを書いている G また、callback内で重い処理をするとレンダリングが遅い原因とな る const handleSliderValueChange = useCallback( (value) => { // とても重い処理 }, [progress] )

Slide 83

Slide 83 text

原因3: 重いReactコードを書いている S React 18から追加された、startTransitionなどを使って重い処理 の優先度を下げると効果的 const handleSliderValueChange = useCallback( (value) => { startTransition(() => { // とても重い処理 }) }, [progress] )

Slide 84

Slide 84 text

Concurrent Rendering ‚ React 18からConcurrent Renderingが利用可能になっB ‚ レンダリングプロセスを非同期的に行えて、より滑らかなユー ザーインタラクションが提供可能にな& ‚ このサンプルでもReact 17 → React 18にするだけでかなりパ フォーマンスの改善がされた

Slide 85

Slide 85 text

CurrentEventPriority q HostConfigにはイベントの優先度を制御するメソッドがあA q getCurrentEventPriorityで以下のいずれかを返E q DiscreteEventPriority // 離散イベンf q ContinuousEventPriority // 連続イベンf q DefaultEventPriority

Slide 86

Slide 86 text

DiscreteEventPriority(離散イベント) ˆ タップなど、ユーザーが引き起こしたイベントの場合に返‡ ˆ 例: touchUpInsideやvalueChange‚ ˆ バックグラウンド処理を中断し、すぐに処理す% ˆ すばやくUIに反映することができる

Slide 87

Slide 87 text

ContinuousEventPriority(連続イベント) e マウスオーバーなど、ユーザーが引き起こしたイベントの場合に 返d e 離散イベントと違うのは、連続イベントの個々のイベントは離散 イベントよりは価値が低4 e バックグラウンド処理は中断するが、時間を掛けてバッチ処理で きることを伝えられる

Slide 88

Slide 88 text

DefaultEventPriority H 離散・連続イベントどちらでもない場合に返( H バックグラウンドで処理して良いと見なされて、離散・連続イベ ントの処理が優先される

Slide 89

Slide 89 text

CurrentEventPriorityは実装すべきか v CurrentEventPriorityの違いを体感できるサンプルを実装できな かっ1 v CurrentEventPriorityを実装するより、重い処理の優先度を下げ る(startTransitionなどを使う)ことがパフォーマンス向上に繋がっ た

Slide 90

Slide 90 text

React Nativeの実装と比較する

Slide 91

Slide 91 text

React Nativeの実装と比較する X 先ほど作ったレンダラーとは違って、React Nativeのレンダラー にはさまざまな工夫がされてい3 X React NativeのHostConfigはReactのリポジトリ内にあ3 X React Native側じゃないんだ...となって結構探した

Slide 92

Slide 92 text

Fabric Renderer y Fabricは、React Native 0.68から利用可能になった、新しいレン ダラe y FabricはC++で実装されており、iOS・Androidで高速にレンダリ ングできるような仕組みになっていE y FabricのHostConfigはReactFabricHostConfig.jsに実装されてい る。JSIというJavaScriptのインターフェースを通してC++とやり 取りする

Slide 93

Slide 93 text

Fabric Rendererの効果 t Fabric上で直接C++でYogaのレイアウト計算を行うので、 AndroidのJNIで呼び出すオーバーヘッドを減らすことができq t 実際のViewのInstanceではなくShadowViewNodeを使っていq t react-reconcilerのPersistence Modeが利用できq t パフォーマンスにどれだけ影響あるかわからない。Immutable に書けるので、処理がシンプルになって結果的にパフォーマン スが上がるとか...(?)

Slide 94

Slide 94 text

View Flattening ) Fabric RendererではViewの描画を高速にするために、レイアウト 構造のためのみに使用しているViewを描画しないようにしている Hello Hello Hello

Slide 95

Slide 95 text

Hermes € HermesはReact Nativeのために作られたJavaScriptCoreの代替 となる、JavaScriptエンジ4 € 0.70からデフォルトで利用可能になA € releaseビルドではJavaScriptのパースやコンパイルが事前に行わ れるので、実行時はバイトコードを直接実行できて高速

Slide 96

Slide 96 text

Hermes vs JavaScriptCore c iOS・Androidともに起動時間と使用メモリが削減され3 c iOSはJavaScriptCoreを使わない分、バンドルサイズが若干上が る https://reactnative.dev/blog/2022/07/08/hermes-as-the-default

Slide 97

Slide 97 text

Hermes vs JavaScriptCore https://reactnative.dev/blog/2022/07/08/hermes-as-the-default

Slide 98

Slide 98 text

Hermes vs JavaScriptCore https://reactnative.dev/blog/2022/07/08/hermes-as-the-default

Slide 99

Slide 99 text

作ったものとReact Nativeの比較 ˜ ランタイムのパフォーマンス的には、正直そこまで変わらない※e ˜ 起動時間(JSContextの初期化)も、Webpackのバンドルを production用にしてしまえば3ms程$ ˜ React NativeはJNIを使うAndroidに向けてパフォーマンスチュー ニングをしているため、Androidでも実装して比較しないと分から ない ※1: スライダーを10個配置して同じuseStateの値を見るサンプルを作成し、スライダーを1秒間動かした時のCPU時間を比較 オレオレReact Nativeが161msに対し、Expo Goアプリで実装したものは292msだった。ちなみに、SwiftUIは159ms程度

Slide 100

Slide 100 text

作ったものとReact Nativeの比較 g 自作はエコシステム的なところでつらみが多F g FlatListなどの、多くのネイティブアプリで使うコンポーネン トがなF g ホットリロードがない

Slide 101

Slide 101 text

おまけ: ホットリロード ` watchmanとInjectionIIIを使うと簡易的なホットリロードが作成 可v ` watchmanでJavaScriptファイルの変更を検知し、Swiftのコード に書き出す → InjectionIIIでiOSアプリのホットリロードを行‚ ` InjectionIIIはSwiftUIのホットリロードに使えて、便 ` https://github.com/johnno1962/InjectionIII

Slide 102

Slide 102 text

まとめ 9 react-reconcilerを使ってレンダラーを作ると、好きな環境で Reactを動かすことができH 9 React Nativeの実装ではさまざまな工夫がされており、求めてい るパフォーマンスに応じてReact Nativeのプラクティスを採用す ると良Q 9 規模が小さければ素朴実装でもパフォーマンスの問題はなさそう

Slide 103

Slide 103 text

参考資料 5 React コードベースの概' 5 https://ja.reactjs.org/docs/codebase-overview.htmÇ 5 React Native Architecture Overvie 5 https://reactnative.dev/architecture/overview

Slide 104

Slide 104 text

参考資料 ) JavaScriptCoreで遊ぼA ) https://booth.pm/ja/items/188581 ) What is Lanes in React source code? - React Source Code Walkthrough 2E ) https://jser.dev/react/2022/03/26/lanes-in-react.html

Slide 105

Slide 105 text

付録: その他のreconciler S Vue 3.0にもreconcilerのような仕組み(createRenderer)があり、 VueでもiOSのUIを組んだりできるかも(試してない)

Slide 106

Slide 106 text

We are hiring! e ピクシブ株式会社では、iOSアプリをネイティブで開発していま すQ e (React Nativeは普段扱っていませんが、react-reconcilerの可能 性を感じて発表させていただきました)