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

react-reconcilerでオレオレReact Nativeを作ろう!

ああうえ
September 12, 2022

react-reconcilerでオレオレReact Nativeを作ろう!

iOSDC 2022
2022/09/12 13:00〜 Track D レギュラートーク(40分)

ああうえ

September 12, 2022
Tweet

More Decks by ああうえ

Other Decks in Programming

Transcript

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

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

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

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

  5. 前置き y React Nativeを自作する意味は、多くの場合ありません8 y こんな時に役立つ資料でw y Reactをソフトウェアの一部に組み込みたいと y ReactとReact

    Nativeがどう繋がっているのか知りたいと y このスライドで紹介するReactとReact Nativeの情報は2022年8 月時点のものです
  6. Reactをソフトウェアに組み込みたい例 とは

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

  8. 例) Raycast コード・実行例 export default function Command() { function handleSubmit(values:

    Values) { showToast(...) } return <Form ...> <Form.Description ... /> <Form.DropDown> <Form.DropDownItem ... /> <Form.DropDownItem ... /> </Form.DropDown> <Form.TextField .../> </Form> }
  9. 例) Figma/FigJam U Figma: ブラウザで使えるデザインツー0 U FigJam: ホワイトボードツー0 U Widgetと呼ばれるプラグインをReactで書くことができる

  10. 例) Figma/FigJam コード・実行例 function Widget() { const [count, setCount] =

    useSyncedState('count', 0) return <AutoLayout ...> <SVG src={minusIcon} onClick={() => { setCount(count - 1) }} /> <Text>{count}</Text> <SVG src={plusIcon} onClick={() => { setCount(count + 1) }} /> </AutoLayout> } widget.register(Widget)
  11. こんな機能ってどう作るんだろう...?

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

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

  14. プラグインとApple Review Guideline  残念ながら、プラグイン機能を持つアプリの配信はApple Review Guidelineで禁止されていB  iOS/macOSのApp Store向けには配信することができなV

     詳しくは2.5.2, 3.2.2あたりを参照
  15. 何に使えるのか V 野良macOSアプY V React Nativeが対応していないプラットフォームでReactを書きた いと8 V クロスプラットフォームで一部のUIをReactで書きたいと8 V

    React Nativeを一部導入する際はCocoaPodsが必要だったり、 Objective-CやC++に触れる必要があったりする
  16. オレオレReact Nativeを作る 基礎知識

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

    => { return <div> <p>Hello</p> <p>World</p> </div> }
  18. React Nativeとは? G React Nativeは、Reactをモバイル開発で使えるようにしたライブ ラc G iOS・Androidに対してクロスプラットフォーム開発ができA G ホットリロードで高速に開発

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

  20. react-reconcilerとは — Webフロントで利用するReact DOMと、モバイルアプリで利用す るReact Nativeでは、UIの描画方法は違えど、共通していること があu — JSXを利用してUI記述することg —

    状態変更によって描画の更新が起こるというこg — react-reconcilerはReactの共通ロジックの定義と、差分検出処理 を行うパッケージ
  21. reconciler(リコンサイラ) $ 和訳: 調停者、平和をもたらす0 $ 日本語だと全然ピンとこないので、和訳せずにreconcilerと言いま す

  22. react-reconcilerが行う差分検出処理とは ' 状態更新があってUIを描画し直すときのことを考えてみましょう <div> <p>Hello</p> </div> <div> <p color='red'>Hello</p> <p>World</p>

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

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

    reconcilerが使われていs x UIツリー全てを素朴にtraverseしていく
  25. react-reconcilerの利用例 G ブラウザのCanvasで2Dや3D描画をする際に、Reactを使って書け るようにreact-reconcilerを使っているライブラリがあ9 G react-pixV G react-three-fiber

  26. react-pixi const App = () => ( <Stage ...> <Container

    ...> <RotatingPenguin /> </Container> </Stage> )
  27. react-three-fiber export default function App() { return ( <Canvas> <ambientLight

    ... /> <spotLight ... /> <pointLight ... /> <Box ... /> </Canvas> ) }
  28. react-reconcilerをiOSで動かしたい ) JavaScriptが動けばどんな環境でもReactを動かすことができ' ) → オレオレReact Nativeを作ってみよう!

  29. 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上でレンダリング可能
  30. 参考: iOSでJavaScriptを動かす G 「JavaScriptCoreで遊ぼう」という本をめちゃくちゃ参考にさせ ていただきました$ G https://booth.pm/ja/items/1885814

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

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

    let context = JSContext()! context.setObject(text, forKeyedSubscript: "text" as NSString) print(context.evaluateScript("text")) // Hello
  33. JavaScriptコードを動かす(3) 型情報の公開 w 型情報を公開して、JavaScript側で利用することもできP w JSExportにconformした型であれば、JSContextにsetObjectし て公開したときも、プロパティやメソッドにもアクセスできる

  34. 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" } }
  35. 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
  36. JavaScriptコードを動かせた 5 JS←→Swiftでやり取りする準備が整った

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

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

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

  40. 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) ...
  41. 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) ... どのようにレンダリングするか書く
  42. react-reconcilerの使い方 Swiftから呼ぶrender関数を定義 global.render = () => { if (renderContainer ==

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

    null) { renderContainer = MyRenderer.createContainer( "root", ConcurrentRoot ); } MyRenderer.updateContainer( <RootComponent />, renderContainer ) } レンダリングしたい rootコンポーネントを渡す
  44. 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つがあり、どちらかを選 ぶ必要がある
  45. Mutation Methods p supportsMutationをtrueにすると呼ばれa p View構造が変わったときに呼ばれるメソッドs p 子Viewの追加・削除・可視状態などを制御できまg p React

    DOM, 旧React Nativeのレンダラーはこのモードを利 p 今回のサンプルではMutation Modeで実装していきます
  46. Persistence Methods I supportsPersistenceをtrueにすると呼ばれ… I 既存のノードを変更していくのではなく、全ノードを複製して新 しいViewツリーを作るためのメソッド群が呼ばれるようにな… I (おそらく) Immutableに書けるので、処理がシンプルになる)

    I React Nativeの新しいレンダラーで利用されてい… I (おそらくShadowNodeを実装しないとパフォーマンスを発揮できないので、今回は実装しない)
  47. Hydration Methods U SSR(Server Side Rendering)用のメソッドなので、UIKit上だけで のレンダリングであれば実装は不要

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

    const HostConfig = { createInstance(type, props) { ... } ... }
  49. 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)
  50. HostConfigのメソッド例 Swiftのメソッドを呼び出す I UIViewのインスタンスを返す準備ができた // HostConfig.js const HostConfig = {

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

    {fontSize: 10, children: “hello”}のような値が入ってくる
  52. 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 ... } }
  53. Viewの生成 const MyComponent = () => { return <text>Hello</text> });

  54. 状態変更が起こった時 5 初回のViewの描画はできた。次は状態変更が起こった場合のこと を考えていきます const MyComponent = () => {

    const [count, setCount] = useState(0) return ( <button title={`Increment: ${count}`} onClick={() => setCount(count + 1)} /> ) }
  55. 状態変更が起こった時 E propsが更新されたときにはcommitUpdateが呼ばれま8 E ここに、Viewのプロパティや見た目などを更新していくコードを 書いていきま8 E ※JS側からの呼び出し方は変わらないので省略してSwiftコードの み紹介します

  56. 状態変更が起こった時 func commitUpdate( _ instance: JSValue, _ updatePayload: JSValue, _

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

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

    type: String, _ prevProps: JSValue, _ nextProps: JSValue ) { guard let view = instance.toObject() as? UIView else { return } ... } nextPropsに新しいpropsが入っているので これを見てViewを更新する
  59. 状態変更が起こった時 // 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 ... }
  60. 状態変更が起こった時 // 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にセット
  61. 状態変更が起こった時 const MyComponent = () => { const [count, setCount]

    = useState(0) return ( <button title={`Increment: ${count}`} onClick={() => setCount(count + 1)} /> ) }
  62. View構造に対応する 0 親子構造になっているViewでも、appendInitialChild, appendChildを実装すればUIKitのViewとして描画可能 <stack> <text>hello</text> <text>world</text> </stack> hello world

  63. 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) }
  64. UI構造が変わった時 I 他にもView構造の操作をするMutation Methodを一通り実装して い I insertBefore, removeChild, hideInstance, unhideInstanceなど

  65. À const MyComponent = () => { const [count, setCount]

    = useState(0) const views = [...Array(count)].map((_, index) => { return <text>{index + 1}</text> }) return ( <vstack> {views} <button title={'Add'} onClick={() => setCount(count + 1)} /> <button title={'Remove'} onClick={() => setCount(count - 1)} /> </vstack> ) }
  66. 補足: SwiftUIは使えないの? t SwiftUIは宣言的なUIフレームワークなので、外部の入力を受け 取って動的にViewを変更することは苦T t 特定のViewの参照を取得したり、特定のViewにaddSubviewなど するようにできてない

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

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

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

    – サンプルではSwift Packageから簡単に利用できる SwiftYogaKit を使っています
  70. 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
  71. 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
  72. 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
  73. ここまでのを使って実装していくと...できた!

  74. パフォーマンスについて h 触ってる感じ、パフォーマンスは問題ないように思える...4 h 大量のViewを配置して、スライダーなどで連続的に状態を更新 → Time Profilerで見てみると、CPUをめっちゃ食っていC h JSを呼んでいるから、だけではない

    ※1秒間スライダーを動かし続けたときのCPU時間を計測 623ms/1000ms こんな感じのView × 50個
  75. 原因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() }
  76. 原因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() // 関係ない変更でも毎回呼ばれてしまっている! }
  77. propsが更新されたときのみ更新する ƒ 更新すべきプロパティを見てViewを更新してあげf ƒ prepareUpdateはcommitUpdateの前に呼ばれf ƒ このメソッドではoldPropsとnewPropsを比較して、更新があった かpropsのkeyの配列を返A ƒ shallowDiff

    などで判定してあげる prepareUpdate(instance, type, oldProps, newProps, rootContainer, hostContext) { return shallowDiff(oldProps, newProps) }
  78. propsが更新されたときのみ更新する 8 commitUpdateのupdatePayloadにprepareUpdateで計算した、 更新されたpropsのkeysが入ってくる func commitUpdate( _ instance: JSValue, _

    updatePayload: JSValue, _ type: String, _ prevProps: JSValue, _ nextProps: JSValue ) { ... }
  79. 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() }
  80. propsが更新されたときのみ更新する f 623ms→427msくらいまで減らすことができE f そもそも重いComponentなので、これ以上減らすには Componentをmemo化するなどが効果的(React側のテクニック)

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

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

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

    (value) => { startTransition(() => { // とても重い処理 }) }, [progress] ) <slider value={progress} onChange={handleSliderValueChange} />
  84. Concurrent Rendering ‚ React 18からConcurrent Renderingが利用可能になっB ‚ レンダリングプロセスを非同期的に行えて、より滑らかなユー ザーインタラクションが提供可能にな& ‚

    このサンプルでもReact 17 → React 18にするだけでかなりパ フォーマンスの改善がされた
  85. CurrentEventPriority q HostConfigにはイベントの優先度を制御するメソッドがあA q getCurrentEventPriorityで以下のいずれかを返E q DiscreteEventPriority // 離散イベンf q

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

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

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

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

  90. React Nativeの実装と比較する

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

    React Native側じゃないんだ...となって結構探した
  92. Fabric Renderer y Fabricは、React Native 0.68から利用可能になった、新しいレン ダラe y FabricはC++で実装されており、iOS・Androidで高速にレンダリ ングできるような仕組みになっていE

    y FabricのHostConfigはReactFabricHostConfig.jsに実装されてい る。JSIというJavaScriptのインターフェースを通してC++とやり 取りする
  93. Fabric Rendererの効果 t Fabric上で直接C++でYogaのレイアウト計算を行うので、 AndroidのJNIで呼び出すオーバーヘッドを減らすことができq t 実際のViewのInstanceではなくShadowViewNodeを使っていq t react-reconcilerのPersistence Modeが利用できq

    t パフォーマンスにどれだけ影響あるかわからない。Immutable に書けるので、処理がシンプルになって結果的にパフォーマン スが上がるとか...(?)
  94. View Flattening ) Fabric RendererではViewの描画を高速にするために、レイアウト 構造のためのみに使用しているViewを描画しないようにしている <vstack background={'#f00'}> <vstack background={'#0f0'}

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

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

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

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

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

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

    ` InjectionIIIはSwiftUIのホットリロードに使えて、便 ` https://github.com/johnno1962/InjectionIII
  102. まとめ 9 react-reconcilerを使ってレンダラーを作ると、好きな環境で Reactを動かすことができH 9 React Nativeの実装ではさまざまな工夫がされており、求めてい るパフォーマンスに応じてReact Nativeのプラクティスを採用す ると良Q

    9 規模が小さければ素朴実装でもパフォーマンスの問題はなさそう
  103. 参考資料 5 React コードベースの概' 5 https://ja.reactjs.org/docs/codebase-overview.htmÇ 5 React Native Architecture

    Overvie 5 https://reactnative.dev/architecture/overview
  104. 参考資料 ) 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
  105. 付録: その他のreconciler S Vue 3.0にもreconcilerのような仕組み(createRenderer)があり、 VueでもiOSのUIを組んだりできるかも(試してない)

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