# 策展 · X (Twitter) 🔥🔥

> 📖 本站完整內容索引（documentation index）：[llms.txt](/llms.txt)

> 作者：Beto (@betomoedano) · 平台：X (Twitter) · 日期：2026-05-25

> 原始來源：https://x.com/betomoedano/status/2058567577366884758

## 中文摘要

# 深入理解 Expo UI 中的 useNativeState

若想獲得最佳閱讀體驗，請前往我的部落格查看。

Expo 發布了 SDK 56，其中在 Expo UI 引入了 `useNativeState` hook。這非常值得從頭深入了解，因為它在那些 React state 一直以來表現遲緩的地方，帶來了實質的效能提升。

簡單來說：`useNativeState` 會為存放在原生記憶體中的值建立一個輕量級的參考（類似指標或 ID），並與 SwiftUI/Compose UI 執行緒共享。如此一來，原生 UI 就能直接讀取與寫入該值，完全不需要經過 JS 執行緒。

## 受控狀態（Controlled State）的問題

以下範例使用一般的 `useState` 從 `TextField` 獲取文字，這就是自從 Dan Abramov 在 X 上發文討論以來，大家一直很討厭的經典「受控 React 狀態」模式。

示範中的應用程式是 Inkigo，這是我在 App Store 上架的 AI 刺青應用。請注意，當我瘋狂輸入自動完成內容時，UI 變得多麼遲鈍且卡頓。

這是因為每次按鍵都會將新值寫入 React state，進而觸發元件重新渲染：

1. 原生 `TextField` 將包含新文字值的事件發送到 JS 執行緒。

2. JS 執行緒使用該值更新 React state。

3. 元件重新渲染，`TextField` 接收到帶有更新值的新 prop。

4. 原生 `TextField` 必須處理這個新 prop 並相應地更新 UI。

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1779671592592-diaHJF9Mt9WEAEemGjpg.jpg)

每次按鍵都要經過這四個步驟。這就是為什麼快速打字時會感覺像在泥沼中行走一樣。

## 引入 useNativeState

讓我們看看改用 `useNativeState` 後的效果如何。

請注意它有多流暢。我盡可能地快速點擊，卻完全無法讓它產生卡頓。我差點為了測試而弄斷手指。

為什麼它能運作得這麼好？

其實這非常聰明。使用 `useNativeState` 時，文字存放在原生記憶體中，並直接與原生 UI 執行緒共享。當使用者輸入時，`TextField` 會自行更新該值。JS 執行緒不需要被喚醒、執行回呼、更新狀態或重新渲染。鍵盤與輸入框之間擁有一個 React 無法參與的私有通道。

![](https://pub-75d4fe1e4e80421b9ecb1245a7ae0d1a.r2.dev/curated/1779671592295-iaHJF9f03XUAA0cEbjpg.jpg)

## 最小範例

這是一個最精簡且具代表性的範例。將你在 React Native 中會用到的任何 `<TextInput>` 替換為來自 `@expo/ui/swift-ui`（或 Android 上的 `@expo/ui/jetpack-compose`）的 `<TextField>`：

```typescript
import { Host, TextField, useNativeState } from "@expo/ui/swift-ui";
import { Button } from "react-native";
 
export default function NameField() {
  // 1. 建立共享狀態，並使用常數作為初始值。
  const name = useNativeState("");
 
  return (
    <Host matchContents>
      <TextField text={name} placeholder="What's your name?" />
      <Button
        title="Submit"
        onPress={() => {
          // 2. 按需讀取 — 同步執行，無需重新渲染。
          console.log("Submitted:", name.value);
        }}
      />
      <Button
        title="Clear"
        onPress={() => {
          // 3. 按需寫入 — 將值推回輸入框。
          name.value = "";
        }}
      />
    </Host>
  );
}
```

有三點需要特別注意：

1. `useNativeState("")`：請傳入一個常數作為初始值。如果你傳入的是會在渲染間變動的值（例如 `useNativeState(someProp)`），該 hook 會在每次變動時銷毀原生物件並建立一個新的。這就是導致第一個示範中出現卡頓的同樣陷阱。

2. `name.value` 是你的讀寫 API。讀取操作在任何執行緒上都是安全的。雖然可以從 JS 進行寫入，但應謹慎使用，例如用於程式化的預填、清除等操作。當使用者輸入時，輸入框本身會在原生端更新該值。

3. 在這個最小範例中不需要 `onChangeText`。只有當 JS 確實需要對變更做出反應（例如啟用按鈕、觸發分析數據、驗證輸入）時，才需要添加回呼。對於高效能路徑，請將回呼標記為 `worklet`，這樣它就會在 UI 執行緒上執行：

```typescript
onTextChange={(value) => {
  "worklet";
  // 在 UI 執行緒上同步執行，無需跳轉至 JS。
}}
```

## 使用場景

`useNativeState` 在任何原生元件頻繁觸發更新，且你不希望每次更新都在 JS 執行緒間來回跳轉的場景中表現出色：

- 長篇文字輸入框：聊天視窗、搜尋輸入框、AI 提示詞（即本文中的案例）。

- 滑桿（Slider）、步進器（Stepper）、旋鈕（Dial）：拖動控制項每秒會觸發多次更新。使用 `useState` 會導致每一幀都重新渲染整個畫面。

- 選擇器（Picker）：顏色、日期、時間。使用者捲動時會發生快速變動，最後才會有一個「確認」的時刻。

- 文字輸入框內的選取範圍：追蹤游標位置，而無需在每次游標移動時重繪螢幕。Inkigo 就是使用 `useNativeState({ start: 0, end: 0 })` 來處理這個需求。

- 包含多個輸入框的表單：每個欄位都有各自的 `useNativeState`，而不是將每次按鍵都傾倒進同一個龐大的 React 狀態樹中。

心法：如果數值的變動速度快到人類眨眼都來不及，且你不需要為了每次變動而在 JS 端渲染任何東西，那麼 `useNativeState` 就是你需要的工具。

有一點需要注意：這是 Expo UI 的 hook。React Native 內建的 `<TextInput>` 並不知道如何綁定到 `useNativeState` 的值。它必須來自 `@expo/ui`。在 Expo UI 之外，最接近的替代方案是 Reanimated 的 `useSharedValue`，它解決了同樣關於「值存放在 JS 執行緒之外」的問題，適用於動畫與手勢。

## 想深入學習 Expo UI 嗎？

我正在為課程製作專屬的 Expo UI 章節，雖然尚未上線，但你開發真實應用程式所需的進階建構模組——如自訂原生視圖、原生模組以及其餘的 Expo Modules API——都已經包含在內了。

🔗 以正確的方式學習 React Native

## 標籤

框架更新, 功能更新, 教學資源, React, SwiftUI, Expo, React
