React Native 中的指標事件
今天,我們將分享 React Native 的實驗性跨平台指標 API。我們將介紹動機、運作方式及其對 React Native 使用者的好處。其中包含如何啟用的說明,我們很期待聽到您的回饋!
自從我們分享 我們的多平台願景以來,已經過了一年多,內容是關於在行動裝置以外的平台建置的優勢,以及它如何為所有平台設定更高的標準。在這段期間,我們增加了對 VR、桌上型電腦和網路版 React Native 的投資。由於這些平台上的硬體和互動存在差異,因此引發了 React Native 應如何全面處理輸入的問題。
超越觸控
桌上型電腦和 VR 歷來依賴滑鼠和鍵盤輸入,而行動裝置主要為觸控。隨著觸控螢幕筆記型電腦的出現,以及在行動裝置上支援透過鍵盤和觸控筆進行互動的需求日益增長,這種情況已有所改變。所有這些都是 React Native 觸控事件系統無法處理的。
因此,非樹狀平台的用戶會分支 React Native 和/或建立自訂原生組件和模組,以支援懸停偵測或滑鼠左鍵點擊等重要功能。這種分歧導致屬性冗餘,事件處理常式用於類似目的,但適用於不同平台。這增加了框架的複雜性,並使平台之間的程式碼共用變得繁瑣。基於這些原因,團隊有動機提供跨平台指標 API。
React Native 旨在提供強大且具表現力的 API,以便為多個平台建置應用程式,同時保持平台特有的體驗。設計這樣的 API 具有挑戰性,但幸運的是,在指標領域有先前的技術,React Native 可以加以利用。
放眼網路
網路是一個平台,在擴展到多個平台方面面臨類似的挑戰,同時也考慮到面向未來的設計。全球資訊網協會 (W3C) 的任務是制定標準和提案,以建置可在不同平台和瀏覽器之間互通的網路。
與我們的需求最相關的是,W3C 定義了指標這種抽象形式的輸入的行為。《指標事件》規範建立在滑鼠事件的基礎上,旨在為跨裝置指標輸入提供一組事件和介面,同時在必要時仍允許裝置特定的處理。
遵循指標事件規範為 React Native 使用者帶來許多好處。除了解決先前所述的問題外,它還提高了歷來不必考慮多輸入類型互動的平台的功能。想想將藍牙滑鼠連接到您的 Android 手機,或 Apple Pencil 支援 iPad M2 上的懸停功能。
符合規範也為網路和 React Native 之間的知識共享提供了機會。對圍繞指標事件的網路預期的教育可以雙重服務於 React Native 開發人員。但是,我們也認識到 React Native 的需求與網路不同,而我們對規範的方法是盡力而為,並有充分記錄的偏差,以便明確預期。在將某些網路標準與 減少 API 碎片 在無障礙功能和效能 API 方面,有相關的工作。
移植網路平台測試
雖然指標事件規範提供了 API 的介面和行為描述,但我們發現它不夠具體,無法讓我們放心地進行變更,並將規範作為驗證的依據。但是,網路瀏覽器使用另一種機制來確保合規性和互通性 — 網路平台測試!
網路平台測試旨在針對瀏覽器的命令式 DOM API 進行測試 — React Native 不支援它,因為它使用自己的檢視基元。這表示我們無法與瀏覽器共用程式碼測試,而是為 React Native 提供類似的測試 API,使其更容易移植這些網路平台測試。
我們實作了一個新的手動測試框架,我們現在使用它來透過 RNTester 驗證我們的實作。這些測試暫時命名為 RNTester 平台測試,並且仍然相當基本。我們的實作提供了一個 API,用於將測試案例建構為組件本身,這些組件會被渲染,並且結果僅透過 UI 報告。
當我們進一步完善指標事件實作的完整性時,這些測試將繼續有所幫助。這些測試也將擴展到測試 Android 和 iOS 以外平台上的指標事件實作。隨著我們的套件中測試數量的增加,我們將尋求自動化執行這些測試,以便我們能夠更好地捕捉實作中的回歸。
運作方式
我們的指標事件實作很大程度上建立在現有的基礎架構之上,用於調度觸控事件。在 Android 和 iOS 上,我們利用相關的 MotionEvent 和 UITouch 事件。事件調度的通用流程如下圖所示。
以 Android 為例,利用平台事件的通用方法是
- 迭代
MotionEvent
的所有指標,並執行深度優先搜尋,以確定每個指標的目標 React 檢視及其祖先路徑。 - 將
MotionEvent
的類別對應到相關的指標事件。MotionEvent
和PointerEvent
之間存在一對多關係。在其關係的圖示中,虛線表示如果指標裝置不支援懸停時觸發的事件。
- 使用來自
MotionEvent
的平台詳細資訊和先前互動的快取狀態來建置PointerEvent
介面。(例如,button
屬性) - 將指標事件從 Android 調度到 React Native 的 核心事件佇列,並利用 JSI 呼叫
dispatchEvent
方法,該方法位於react-native-renderer
中,它會迭代 React 樹以進行事件的冒泡和捕獲階段。
實作進度
在我們目前實作指標事件規範的進度方面,我們專注於最常見事件的穩固基準實作,這些事件處理諸如按下、懸停和移動之類的事情。
事件
已實作 | 工作中 | 尚待實作 |
---|---|---|
onPointerOver | onPointerCancel | onClick |
onPointerEnter | onContextMenu | |
onPointerDown | onGotPointerCapture | |
onPointerMove | onLostPointerCapture | |
onPointerUp | onPointerRawUpdate | |
onPointerOut | ||
onPointerLeave |
onPointerCancel 已連接到原生平台的「取消」事件,但這不一定對應於網路平台預期它們觸發的時間。
事件屬性
對於上述每個事件,我們也實作了 PointerEvent 物件中預期的大多數屬性 — 儘管在 React Native 中,這些屬性是透過 event.nativeEvent
屬性公開的。您可以在 事件物件的 Flowtype 介面定義中找到所有已實作屬性的列舉。一個值得注意的例外是 relatedTarget
屬性,因為以這種特定方式公開原生檢視參考並非易事。
未來工作和探索
除了上述事件外,還有一些與指標事件相關的其他 API。未來,我們計劃將這些 API 作為此工作的一部分來實作。這些 API 包括
- 指標捕獲 API
- 包括元素參考上公開的命令式 API,包括
setPointerCapture()
、releasePointerCapture()
和hasPointerCapture()
。
- 包括元素參考上公開的命令式 API,包括
touch-action
樣式屬性- 網路使用此 CSS 屬性以宣告方式協商瀏覽器和網站自身事件處理程式碼之間的手勢。在 React Native 中,這可用於協商 View 的指標事件處理程式和父 ScrollView 之間的事件處理。
click
、contextmenu
、auxclick
click
是互動的抽象定義,可以透過無障礙功能範例或其他特有的平台互動觸發。
原生指標事件實作的另一個好處是,它將允許我們重新檢視和改進目前僅限於觸控事件且由 Responder、Pressability 和 PanResponder API 在 JavaScript 中處理的各種形式的手勢處理。
此外,我們正在繼續探索為 React Native 主機組件(即 add
/removeEventListener
)包含 EventTarget
介面的實作,我們認為這將為處理指標互動提供更多使用者領域的抽象。
試用看看
我們的指標事件實作仍處於實驗階段,但我們有興趣從社群獲得對我們所分享內容的回饋。如果您有興趣試用此 API,您需要啟用幾個功能標誌
啟用功能標誌
指標事件僅針對 全新架構 (Fabric) 實作,並且僅適用於 React Native 0.71+,在撰寫本文時,這是一個候選版本。
在您的入口 JavaScript 檔案(預設 React Native 應用程式範本中的 index.js)中,您需要為指標事件啟用 shouldEmitW3CPointerEvents
標誌,並為在 Pressability
中使用指標事件啟用 shouldPressibilityUseW3CPointerEventsForHover
。
import ReactNativeFeatureFlags from 'react-native/Libraries/ReactNative/ReactNativeFeatureFlags';
// enable the JS-side of the w3c PointerEvent implementation
ReactNativeFeatureFlags.shouldEmitW3CPointerEvents = () => true;
// enable hover events in Pressibility to be backed by the PointerEvent implementation
ReactNativeFeatureFlags.shouldPressibilityUseW3CPointerEventsForHover =
() => true;
iOS 特定
為了確保指標事件從原生 iOS 渲染器發送,您需要在原生應用程式的初始化程式碼(通常為 AppDelegate.mm
)中翻轉原生功能標誌。
#import <React/RCTConstants.h>
// ...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
RCTSetDispatchW3CPointerEvents(YES);
// ...
}
請注意,為了確保指標事件實作可以區分 iOS 上的滑鼠和觸控指標,您需要將 UIApplicationSupportsIndirectInputEvents
新增到您的 Xcode 專案的 info.plist
中。
Android 特定
與 iOS 類似,Android 也有一個功能標誌,您需要在應用程式的初始化中啟用它 — 通常是您的根 React 活動或介面的 onCreate
。
import com.facebook.react.config.ReactFeatureFlags;
//... somewhere in initialization
@Override
public void onCreate() {
ReactFeatureFlags.dispatchPointerEvents = true;
}
JavaScript
function onPointerOver(event) {
console.log(
'Over blue box offset: ',
event.nativeEvent.offsetX,
event.nativeEvent.offsetY,
);
}
// ... in some component
<View
onPointerOver={onPointerOver}
style={{height: 100, width: 100, backgroundColor: 'blue'}}
/>;
歡迎回饋
如今,指標事件已由我們的 VR 平台使用,並為 Oculus Store 提供支援,但我們也在尋求社群對我們的方法以及我們目前實作的回饋。我們很高興與您分享我們的進一步進展,如果您對這項工作有任何疑問或想法,請加入我們在 指標事件專門討論 中的討論。