Skip to content

Instantly share code, notes, and snippets.

@calloc134
Last active February 8, 2025 09:21
Show Gist options
  • Save calloc134/57779602bd64372731a8894fa2d213c9 to your computer and use it in GitHub Desktop.
Save calloc134/57779602bd64372731a8894fa2d213c9 to your computer and use it in GitHub Desktop.

以下に日本語訳を示します。

react-reconciler

これはカスタムReactレンダラーを作成するための実験的なパッケージです。

このAPIはReact、React Native、React DOMほど安定しておらず、一般的なバージョニングスキームに従っていません。

自己責任で使用してください。

使用法

const Reconciler = require('react-reconciler');

const HostConfig = {
  // いくつかのメソッドをここに実装する必要があります。
  // 詳細と例については、以下を参照してください。
};

const MyRenderer = Reconciler(HostConfig);

const RendererPublicAPI = {
  render(element, container, callback) {
    // MyRenderer.updateContainer()を呼び出して、ルートの変更をスケジュールします。
    // 実際の例については、ReactDOM、React Native、またはReact ARTを参照してください。
  }
};

module.exports = RendererPublicAPI;

実践的な例

"host config" は、あなたが提供する必要があるオブジェクトであり、"host" 環境 (例えば、DOM、canvas、console、またはあなたのレンダリングターゲット) で何かを起こす方法を記述します。これは次のようになります。

const HostConfig = {
  createInstance(type, props) {
    // 例: DOM レンダラーは DOM ノードを返します
  },
  // ...
  supportsMutation: true, // ノードのミューテーションによって動作します
  appendChild(parent, child) {
    // 例: DOM レンダラーはここで .appendChild() を呼び出します
  },
  // ...
};

非常にシンプルなカスタムレンダラーの作成に関する入門については、以下の一連の記事を確認してください。

サポートされているメソッドの完全なリストはこちらにあります。それらのシグネチャについては、以下の具体的な例を参照することをお勧めします。

React リポジトリには、いくつかのレンダラーが含まれています。それぞれに独自のホスト構成があります。

React リポジトリの例は、サードパーティのレンダラーとは少し異なる方法で宣言されています。具体的には、上記の HostConfig オブジェクトは明示的に宣言されず、代わりにコード内の モジュール です。ただし、そのエクスポートは、コードで宣言する必要がある HostConfig オブジェクトのプロパティに直接対応しています。

これらのリンクが壊れている場合は、問題を報告してください。修正します。API はまだ進化しているため、意図的に最新バージョンにリンクしています。さらに質問がある場合は、問題を報告してください。できる限りお手伝いします。

(不完全な!) リファレンス

現時点では、ホスト構成はリリース間で非常に頻繁に変更されるため、すべてのAPIの詳細を文書化することを約束できません。以下のドキュメントは、APIの保証ではなく、最善を尽くすという精神で提供されています。あまり頻繁に変更されない部分に焦点を当てています。これは、React自体の急速な開発の必要性と、カスタムレンダラーコミュニティにとってのこのパッケージの有用性との間のバランスをとる妥協案です。最新の安定版の動作と一致しない部分や古い部分に気付いた場合は、問題を報告するかプルリクエストを送信してください。ただし、応答には時間がかかる場合があります。

モード

reconciler には、ミューテーションモードとパーシステントモードの 2 つのモードがあります。どちらかを指定する必要があります。

ターゲットプラットフォームが DOM に似ており、appendChildremoveChild などのようなメソッドを持っている場合は、ミューテーションモードを使用する必要があります。これは、React DOM、React ART、および従来の React Native レンダラーで使用されるのと同じモードです。

const HostConfig = {
  // ...
  supportsMutation: true,
  // ...
}

ターゲットプラットフォームがイミュータブルなツリーを持っている場合は、代わりにパーシステントモードを使用する必要があります。そのモードでは、既存のノードは変更されず、代わりにすべての変更が親ツリーを複製し、ルートで親ツリー全体を置き換えます。"Fabric" というコードネームの新しい React Native レンダラーで使用されるモードです。

const HostConfig = {
  // ...
  supportsPersistence: true,
  // ...
}

モードに応じて、reconciler はホスト構成で異なるメソッドを呼び出します。

どちらが必要かわからない場合は、おそらくミューテーションモードが必要です。

コアメソッド

createInstance(type, props, rootContainer, hostContext, internalHandle)

このメソッドは、新しく作成されたノードを返す必要があります。たとえば、DOM レンダラーはここで document.createElement(type) を呼び出し、props からプロパティを設定します。

rootContainer を使用して、そのツリーに関連付けられているルートコンテナにアクセスできます。たとえば、DOM レンダラーでは、これはルートが属する正しい document 参照を取得するのに役立ちます。

hostContext パラメーターを使用すると、ツリー内の現在の場所に関する情報を追跡できます。詳細については、以下の getChildHostContext を参照してください。

internalHandle データ構造は、不透明であることを意図しています。ルールを曲げて内部フィールドに依存する場合は、バージョン間で大幅に変更される可能性があることに注意してください。そこから読み取ることで追加のメンテナンスリスクを負い、そこに何かを書き込むとすべての保証を放棄することになります。

このメソッドはレンダーフェーズで発生します。返却する前に、作成したばかりのノードを変更できます (そして通常は変更する必要があります) が、他のノードを変更してはなりません。親ツリーにイベントハンドラーを登録してはなりません。これは、インスタンスが作成されたからといって、必ずしもツリーに配置されるとは限らないためです。未使用のままになり、後で GC によって収集される可能性があります。インスタンスが確実にツリー内にあるときに何かを実行する必要がある場合は、代わりに commitMount を参照してください。

createTextInstance(text, rootContainer, hostContext, internalHandle)

createInstance と同じですが、テキストノード用です。レンダラーがテキストノードをサポートしていない場合は、ここで例外をスローできます。

appendInitialChild(parentInstance, child)

このメソッドは parentInstance を変更し、子をその子のリストに追加する必要があります。たとえば、DOM では、これは parentInstance.appendChild(child) 呼び出しに変換されます。

このメソッドはレンダーフェーズで発生します。parentInstancechild は変更できますが、他のノードは変更してはなりません。ツリーがまだ構築中であり、画面上の実際のツリーに接続されていない間に呼び出されます。

finalizeInitialChildren(instance, type, props, rootContainer, hostContext)

このメソッドでは、instance に対していくつかの最終的な変更を実行できます。createInstance とは異なり、finalizeInitialChildren が呼び出されるまでに、すべての初期の子はすでに instance に追加されていますが、インスタンス自体はまだ画面上のツリーに接続されていません。

このメソッドはレンダーフェーズで発生します。instance は変更できますが、他のノードは変更してはなりません。ツリーがまだ構築中であり、画面上の実際のツリーに接続されていない間に呼び出されます。

このメソッドには 2 番目の目的があります。ノードが画面上のツリーに接続されたときに実行する必要がある作業があるかどうかを指定できます。true を返すと、インスタンスは後で commitMount 呼び出しを受け取ります。以下のドキュメントを参照してください。

ここで何もしたくない場合は、false を返す必要があります。

shouldSetTextContent(type, props)

一部のターゲットプラットフォームでは、テキストノードを手動で作成せずに、インスタンスのテキストコンテンツを設定できます。たとえば、DOM では、テキストノードを作成して追加する代わりに、node.textContent を設定できます。

このメソッドから true を返すと、React はこのノードの子がテキストであると想定し、それらのノードを作成しません。代わりに、createInstance 中にそのテキストを入力したことに依存します。これはパフォーマンスの最適化です。たとえば、DOM レンダラーは、type が既知のテキストのみの親 ( 'textarea' など) である場合、または props.children'string' 型である場合にのみ true を返します。true を返す場合は、resetTextContent も実装する必要があります。

ここで何もしたくない場合は、false を返す必要があります。

このメソッドはレンダーフェーズで発生します。そこからツリーを変更しないでください。

getRootHostContext(rootContainer)

このメソッドを使用すると、ツリーのルートから初期ホストコンテキストを返すことができます。ホストコンテキストの説明については、getChildHostContext を参照してください。

ホストコンテキストを使用しない場合は、null を返すことができます。

このメソッドはレンダーフェーズで発生します。そこからツリーを変更しないでください。

getChildHostContext(parentHostContext, type, rootContainer)

ホストコンテキストを使用すると、ツリー内のどこにいるかに関する情報を追跡できるため、createInstance 内で hostContext パラメーターとして使用できます。たとえば、DOM レンダラーはこれを使用して、HTML ツリー内にあるか SVG ツリー内にあるかを追跡します。これは、createInstance の実装がそれらで異なる必要があるためです。

この type のノードが、渡したいコンテキストに影響を与えない場合は、parentHostContext を返すことができます。または、渡したい情報を表す任意のカスタムオブジェクトを返すこともできます。

ここで何もしたくない場合は、parentHostContext を返します。

このメソッドはレンダーフェーズで発生します。そこからツリーを変更しないでください。

getPublicInstance(instance)

ref として公開されるオブジェクトを決定します。おそらく instance 自体を返したいでしょう。しかし、場合によっては、その一部のみを公開する方が理にかなっている場合があります。

ここで何もしたくない場合は、instance を返します。

prepareForCommit(containerInfo)

このメソッドを使用すると、React が画面上のツリーに変更を加える前に、いくつかの情報を保存できます。たとえば、DOM レンダラーは現在のテキスト選択範囲を保存して、後で復元できるようにします。このメソッドは resetAfterCommit と対になっています。

ここで何もしたくない場合でも、そこから null を返す必要があります。

以下に日本語訳を提示します。

resetAfterCommit(containerInfo)

このメソッドは、React がツリーのミューテーションを実行した直後に呼び出されます。prepareForCommit で保存したものを復元するために使用できます (たとえば、テキストの選択範囲など)。

空のままにしておくこともできます。

preparePortalMount(containerInfo)

このメソッドは、ポータルのターゲットとして使用されるコンテナに対して呼び出されます。通常は空のままにしておくことができます。

scheduleTimeout(fn, delay)

これを setTimeout または環境内の同等のものにプロキシできます。

cancelTimeout(id)

これを clearTimeout または環境内の同等のものにプロキシできます。

noTimeout

これは、有効なタイムアウト ID になりえないものに設定する必要があるプロパティ (関数ではない) です。たとえば、-1 に設定できます。

supportsMicrotasks

レンダラーが scheduleMicrotask をサポートしていることを示すには、これを true に設定します。React DOM では、ディスクリートイベント実装の一部としてマイクロタスクを使用します。レンダラーがこれをサポートする必要があるかどうかわからない場合は、おそらくサポートする必要があります。scheduleMicrotask を実装しないオプションが存在するのは、React Native のように、ユーザーイベントをより細かく制御できるプラットフォームが、異なるメカニズムを使用できるようにするためです。

scheduleMicrotask(fn)

任意。これを queueMicrotask または環境内の同等のものにプロキシできます。

isPrimaryRenderer

これは、レンダラーがページ上のメインのレンダラーである場合に true に設定する必要があるプロパティ (関数ではない) です。たとえば、ターミナル用のレンダラーを作成している場合は、これを true に設定するのが理にかなっていますが、レンダラーが React DOM やその他の既存のレンダラーの 上に 使用される場合は、false に設定します。

getCurrentEventPriority

このメソッドを実装するには、特別な react-reconciler/constants エントリポイントで利用可能な定数がいくつか必要になります。

import {
  DiscreteEventPriority,
  ContinuousEventPriority,
  DefaultEventPriority,
} from 'react-reconciler/constants';

const HostConfig = {
  // ...
  getCurrentEventPriority() {
    return DefaultEventPriority;
  },
  // ...
}

const MyRenderer = Reconciler(HostConfig);

返す定数は、現在処理されているイベント (もしあれば) によって異なります。(ブラウザでは、window.event && window.event.type を使用してこれを確認できます)。

  • 離散イベント: アクティブなイベントが ユーザーによって直接引き起こされ (マウスやキーボードイベントなど)、 シーケンス内の各イベントが意図的である (例: click) 場合は、DiscreteEventPriority を返します。これは、バックグラウンド作業を中断する必要があり、時間をまたいでバッチ処理できないことを React に伝えます。

  • 連続イベント: アクティブなイベントが ユーザーによって直接引き起こされる が、 ユーザーがシーケンス内の個々のイベントを区別できない (例: mouseover) 場合は、ContinuousEventPriority を返します。これは、バックグラウンド作業を中断する必要があるが、時間をまたいでバッチ処理できることを React に伝えます。

  • その他のイベント / アクティブなイベントがない場合: その他のすべての場合は、DefaultEventPriority を返します。これは、このイベントがバックグラウンド作業と見なされ、インタラクティブなイベントが優先されることを React に伝えます。

ReactFiberConfigDOM.jsgetCurrentEventPriority() 実装を参照して、実装例を確認できます。

ミューテーションメソッド

React をミューテーションモードで使用している場合 (おそらくそうでしょう)、さらにいくつかのメソッドを実装する必要があります。

appendChild(parentInstance, child)

このメソッドは parentInstance を変更し、子をその子のリストに追加する必要があります。たとえば、DOM では、これは parentInstance.appendChild(child) 呼び出しに変換されます。

このメソッドは現在コミットフェーズで実行されますが、それでも他のノードを変更しないでください。ノードが確実に可視ツリーに接続されているときに追加の作業を行う必要がある場合は、commitMount を参照してください。

appendChildToContainer(container, child)

appendChild と同じですが、ノードがルートコンテナにアタッチされる場合です。これは、ルートへのアタッチの実装が少し異なる場合、またはルートコンテナノードがツリーの残りの部分とは異なるタイプである場合に役立ちます。

insertBefore(parentInstance, child, beforeChild)

このメソッドは parentInstance を変更し、childbeforeChild の前に、その子のリストに配置する必要があります。たとえば、DOM では、これは parentInstance.insertBefore(child, beforeChild) 呼び出しに変換されます。

React はこのメソッドを挿入とノードの並べ替えの両方に使用することに注意してください。DOM と同様に、既存の子を再配置するために insertBefore を呼び出すことができると想定されています。ツリーの他の部分を変更しないでください。

insertInContainerBefore(container, child, beforeChild)

insertBefore と同じですが、ノードがルートコンテナにアタッチされる場合です。これは、ルートへのアタッチの実装が少し異なる場合、またはルートコンテナノードがツリーの残りの部分とは異なるタイプである場合に役立ちます。

以下に日本語訳を示します。

removeChild(parentInstance, child)

このメソッドは parentInstance を変更して、child をその子のリストから削除する必要があります。

React は、削除される最上位のノードに対してのみこれを呼び出します。ガベージコレクションがサブツリー全体を処理することが期待されます。このメソッド内で子ツリーを走査する必要はありません。

removeChildFromContainer(container, child)

removeChild と同じですが、ノードがルートコンテナからデタッチされる場合です。これは、ルートへのアタッチの実装が少し異なる場合、またはルートコンテナノードがツリーの残りの部分とは異なるタイプである場合に役立ちます。

resetTextContent(instance)

以前の props に対して shouldSetTextContent から true を返したが、次の props に対して shouldSetTextContent から false を返した場合、React はこのメソッドを呼び出して、手動で管理していたテキストコンテンツをクリアできるようにします。たとえば、DOM では、node.textContent = '' を設定できます。

shouldSetTextContent から true を返さない場合は、空のままにしておくことができます。

commitTextUpdate(textInstance, prevText, nextText)

このメソッドは textInstance を変更し、そのテキストコンテンツを nextText に更新する必要があります。

ここで、textInstancecreateTextInstance によって作成されたノードです。

commitMount(instance, type, props, internalHandle)

このメソッドは、このインスタンスに対して finalizeInitialChildren から true を返した場合にのみ呼び出されます。

これにより、ノードが初めて画面上のツリーに実際にアタッチされた後に追加の作業を行うことができます。たとえば、DOM レンダラーはこれを使用して、autoFocus 属性を持つノードにフォーカスをトリガーします。

commitMountremoveChild と 1 対 1 で対応していないことに注意してください。removeChild は削除される最上位のノードに対してのみ呼び出されるためです。このため、理想的には commitMountinstance 自体以外のノードを変更すべきではありません。たとえば、上位のノードにイベントを登録する場合、removeChild でツリーを走査してそれらをクリーンアップするのはあなたの責任になりますが、これは理想的ではありません。

internalHandle データ構造は、不透明であることを意図しています。ルールを曲げて内部フィールドに依存する場合は、バージョン間で大幅に変更される可能性があることに注意してください。そこから読み取ることで追加のメンテナンスリスクを負い、そこに何かを書き込むとすべての保証を放棄することになります。

finalizeInitialChildren から true を返さない場合は、空のままにしておくことができます。

commitUpdate(instance, type, prevProps, nextProps, internalHandle)

このメソッドは、instancenextProps に一致するように変更する必要があります。

internalHandle データ構造は、不透明であることを意図しています。ルールを曲げて内部フィールドに依存する場合は、バージョン間で大幅に変更される可能性があることに注意してください。そこから読み取ることで追加のメンテナンスリスクを負い、そこに何かを書き込むとすべての保証を放棄することになります。

hideInstance(instance)

このメソッドは、instance をツリーから削除せずに非表示にする必要があります。たとえば、視覚的なスタイルを適用して非表示にすることができます。Suspense によって、フォールバックが表示されている間、ツリーを非表示にするために使用されます。

hideTextInstance(textInstance)

hideInstance と同じですが、createTextInstance によって作成されたノード用です。

unhideInstance(instance, props)

このメソッドは、instance を表示し、hideInstance が行ったことを元に戻す必要があります。

unhideTextInstance(textInstance, text)

unhideInstance と同じですが、createTextInstance によって作成されたノード用です。

clearContainer(container)

このメソッドは、container ルートノードを変更し、そこからすべての子を削除する必要があります。

以下に日本語訳を示します。

maySuspendCommit(type, props)

このメソッドは、ホストコンポーネントのタイプと props が、更新をコミットする前に完了する必要がある何らかの読み込みプロセスを必要とするかどうかを判断するために、レンダー中に呼び出されます。

preloadInstance(type, props)

このメソッドは、ホストコンポーネントのタイプと props がコミットを中断する可能性がある場合に、レンダー中に呼び出されることがあります。これは、中断されたコミットの期間を短縮する可能性のある作業を開始するために使用できます。

startSuspendingCommit()

このメソッドは、コミットフェーズの直前に呼び出されます。このコミットを中断する可能性のあるホストコンポーネントが評価されてコミットを中断する必要があるかどうかを判断する際に、必要な状態を設定するために使用します。

suspendInstance(type, props)

このメソッドは、コミットを中断する可能性があることを示した各ホストコンポーネントに対して、startSuspendingCommit の後に呼び出されます。

waitForCommitToBeReady()

このメソッドは、すべての suspendInstance 呼び出しが完了した後に呼び出されます。

コミットをすぐに実行できる場合は、null を返します。

コミットを中断する必要がある場合は、(initiateCommit: Function) => Function を返します。このコールバックへの引数は、呼び出されたときにコミットを開始します。戻り値は、Reconciler がコミットを中止するために使用できるキャンセル関数です。

パーシステンスメソッド

ミューテーションモードの代わりにパーシステントモードを使用する場合は、「コアメソッド」は引き続き必要です。ただし、上記のミューテーションメソッドの代わりに、ノードのクローン作成とルートレベルでのノードの置き換えを実行する別のメソッドセットを実装します。それらのリストは、このファイルにリストされている「パーシステンス」セクションにあります。ヘルプが必要な場合は、問題を報告してください。

ハイドレーションメソッド

オプションで、初期レンダリング中にツリーを最初から作成する代わりに、既存のツリーに「アタッチ」するハイドレーションを実装できます。たとえば、DOM レンダラーはこれを使用して HTML マークアップにアタッチします。

ハイドレーションをサポートするには、supportsHydration: true を宣言し、このファイルにリストされている「ハイドレーション」セクションのメソッドを実装する必要があります。ヘルプが必要な場合は、問題を報告してください。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment