本頁由機器翻譯,可能有錯誤。 閱讀英文原文

API

List

nsh 匯出 List:建立在原生捲動視圖上的虛擬化列表。只有靠近可視區域的列會存在,其儲存格會在捲動時重複使用。哪些列存在、位於何處、欄數、黏性標題和可見性,全都在原生端計算,因此捲動一萬個項目的列表時,除了儲存格被重複使用時會變動的列綁定之外,不會執行任何 JS。

import { List, type ListProps, type ViewToken, type ViewabilityConfig } from "nsh";

指南請參閱列表,元素的屬性請參閱 <List>。

#List

function List<T>(props: ListProps<T>): JSX.Element;
import { List, mount, Text, View } from "nsh";

const contacts = Array.from({ length: 1000 }, (_, i) => ({ id: String(i), name: `Contact ${i}` }));

function App() {
  return (
    <List each={contacts} estimatedItemSize={48} style={{ flex: 1 }}>
      {(item, index) => (
        <View style={{ height: 48, paddingHorizontal: 16, justifyContent: "center", backgroundColor: index() % 2 ? "#f8fafc" : "#fff" }}>
          <Text>{item().name}</Text>
        </View>
      )}
    </List>
  );
}

mount(App);

#列會被回收再利用

children 是渲染單一儲存格的函式。它是每個儲存格執行一次,而不是每個項目執行一次:當儲存格捲出可視範圍時,會被重複使用來顯示另一個項目,此時 item() 與 index()(兩者皆為存取器)會改變。請在 JSX 運算式({item().name})內讀取它們,讓儲存格在被重複使用時能夠更新。

#屬性

ListProps<T> 結合了列表本身的屬性、與 <Scroll> 共用的捲動屬性,以及所有元素都適用的屬性,這些屬性會傳給列表的捲動視圖。

#資料與渲染

屬性型別預設值說明
eachreadonly T[](必填)項目。傳入 signal 的值(each={items()})即可追蹤變更;在可視列上方插入項目時,這些列會保持在原位。
children(item: () => T, index: () => number) => JSX.Element(必填)渲染一列(見上文)。
estimatedItemSizenumber50尚未測量之列沿列表軸向的尺寸,單位為 point。估計值越接近,捲動指示器與 scrollToIndex 就越準確。
numColumnsnumber1每列的項目數(垂直列表):即網格。
horizontalbooleanfalse橫向捲動。
invertedbooleanfalse從末端開始排版,項目 0 排在最後(聊天樣式);一開始會捲動到末端,並在內容變動時保持與末端的距離。
stickyHeaderIndicesnumber[]捲動經過後會黏在起始位置的項目(單欄、非反向)。
headerListSlot渲染於項目之前。
footerListSlot渲染於項目之後。
emptyListSlot當 each 為空時,顯示此內容以取代項目。
separatorListSlot渲染於項目之間(單欄)。
keyExtractor(item: T, index: number) => stringitem.key、item.id,或索引項目在可見性權杖中的索引鍵。
initialScrollIndexnumber在第一個畫面之前捲動到此項目。
contentStyleListContentStyle內容的內距與間距:padding、paddingHorizontal、paddingVertical、paddingTop、paddingRight、paddingBottom、paddingLeft、gap、rowGap、columnGap(數字)。
contentClassstring以 class 指定內容的內距與間距(p-*、gap-*);contentStyle 的優先順序高於它們。
styleStyleProp<ViewStyle>列表本身的樣式(請給它一個尺寸:flex: 1 或高度)。
testIDstringUI 測試用來尋找列表捲動視圖的 id(nsh test 的 id:、XCUITest、UI Automator)。
refListRef | ((node: ListRef) => void)請參閱 ref 方法。

ListSlot 是元件(Header)或元素(<Header />)。

#載入、重新整理與可見性

屬性型別預設值說明
onLoadMore() => void當末端進入 loadMoreThreshold 個可視區域範圍內時呼叫:載入更多。
loadMoreThresholdnumber1onLoadMore 在距離末端多近時觸發(以可視區域長度為單位)。
onLoadMoreAtStart() => void在使用者或捲動指令移動列表後,當起始處進入 loadMoreAtStartThreshold 個可視區域範圍內時呼叫:載入第一個項目之前的內容。
loadMoreAtStartThresholdnumber1onLoadMoreAtStart 在距離起始處多近時觸發(以可視區域長度為單位)。
refreshingboolean顯示下拉重新整理的轉圈指示器。
onRefresh() => void啟用下拉重新整理;載入期間請設定 refreshing。
refreshIndicator{ height: number }應用程式自行繪製下拉重新整理指示器,高度為 height,用來取代轉圈指示器。
pullValueAnimatedValue接收列表被下拉超過頂端的距離,供該指示器使用。
onViewableItemsChange(info: { viewableItems: ViewToken<T>[]; changed: ViewToken<T>[] }) => void變為可見或不再可見的項目。
viewabilityConfigViewabilityConfig項目何時被視為可見。
interface ViewToken<T> {
  item: T;
  key: string;
  index: number;
  isViewable: boolean;
}

interface ViewabilityConfig {
  /** How much of the viewport an item must cover to count as viewable, from 0 to 1. */
  viewAreaCoverageThreshold?: number;
  /** How much of an item must be visible, from 0 to 1. Without either threshold, any visible pixel counts. */
  itemVisibleThreshold?: number;
  /** Milliseconds items must stay viewable before they are reported. */
  minimumViewTime?: number;
}

#捲動

屬性型別預設值說明
scrollbarboolean | ScrollbarThemetrue顯示捲動指示器:使用平台的指示器,或應用程式的內建捲軸;傳入物件則為視圖自身的主題。
scrollablebooleantrue使用者可以捲動。
bouncesbooleantrue允許捲動超出邊緣(過度捲動)。
pagingEnabledbooleanfalse停在可視區域的整數倍位置。
snapToIntervalnumber0停在此長度的整數倍位置(0:關閉)。
snapToOffsetsnumber[]停在這些位移量上。
snapToAlignment"start" | "center" | "end""start"使用 snapToInterval 時,項目的哪一個點會與可視區域對齊。
disableIntervalMomentumbooleanfalse一次快速滑動最多只移動一個停駐點。
decelerationRate"normal" | "fast" | number平台預設快速滑動減速的快慢(0.998 / 0.99,或每毫秒保留的速度)。
contentInsetPartial<Edges>內容周圍的空間。
contentOffset{ x?, y? }起始位置。
keyboardShouldPersistTaps"never" | "always" | "handled" | boolean"never"點一下是否會收起鍵盤("never":點一下只會收起鍵盤)。
keyboardDismissMode"none" | "on-drag" | "interactive""none"拖曳是否會收起鍵盤。
scrollEventThrottlenumberonScroll 事件之間的最小毫秒數(無論如何每個畫面最多一次)。
scrollValueAnimatedValue在 UI 執行緒上以捲動位移量(主軸)驅動的動畫值,用於與捲動連動的效果。

#捲動事件

事件負載時機
onScrollScrollEvent: { offset, contentSize, size, zoomScale }捲動期間。
onScrollDragStart{ offset }開始拖曳。
onScrollDragEnd{ offset }手指抬起。
onMomentumStart{ offset }拖曳之後開始滑行。
onMomentumEnd{ offset }滑行結束(新的拖曳攔下滑行時也會觸發)。
onScrollEnd{ offset }捲動靜止。

列表也接受所有元素都適用的屬性(通用屬性)。

#Ref 方法

列表的 ref 會收到一個 ListRef,除了 ViewRef 的方法之外,還有以下方法:

方法說明
scrollToIndex({ index, animated?, viewPosition?, viewOffset? })將項目 index 捲動到可視範圍內。viewPosition 決定其位置:0 位於可視區域起始處(預設)、0.5 位於中間、1 位於末端;viewOffset 可進一步位移。以已測量(或估計)的列尺寸在原生端解算。
scrollToOffset({ offset, animated? })沿列表軸向捲動到內容位移量。
scrollToEnd({ animated? })捲動到內容末端(反向列表則為起始處)。
scrollTo({ x?, y?, animated? })捲動到某個位移量。
import { List, type ListRef, Text, View } from "nsh";

let list!: ListRef;

<View style={{ flex: 1 }}>
  <Text onPress={() => list.scrollToIndex({ index: 60, viewPosition: 0.5 })}>Jump to 60</Text>
  <List ref={list} each={items} numColumns={3} estimatedItemSize={90} style={{ flex: 1 }}
        contentStyle={{ padding: 12, gap: 8 }}>
    {(item) => <View style={{ height: 90, backgroundColor: "#e0e7ff" }}><Text>{item().title}</Text></View>}
  </List>
</View>

#範例

聊天:最新訊息在底部,向上捲動時載入較舊的訊息。

<List
  each={messages()}
  inverted
  estimatedItemSize={44}
  style={{ flex: 1 }}
  contentStyle={{ padding: 12, gap: 6 }}
  keyboardDismissMode="interactive"
  onLoadMore={loadOlder}
  loadMoreThreshold={0.5}
>
  {(m) => (
    <View style={{ alignSelf: m().mine ? "flex-end" : "flex-start", maxWidth: "75%", padding: 10, borderRadius: 14, backgroundColor: m().mine ? "#6366f1" : "#e2e8f0" }}>
      <Text style={{ color: m().mine ? "#fff" : "#0f172a" }}>{m().text}</Text>
    </View>
  )}
</List>

下拉重新整理:

const refreshing = signal(false);
const refresh = async () => {
  refreshing.set(true);
  items.set(await load());
  refreshing.set(false);
};

<List each={items()} refreshing={refreshing()} onRefresh={refresh} style={{ flex: 1 }}>
  {(item) => <Text style={{ padding: 16 }}>{item().title}</Text>}
</List>

#ListItemsProps

type ListItemsProps<T> = Pick<ListProps<T>,
  "each" | "children" | "keyExtractor" | "stickyHeaderIndices" | "initialScrollIndex"
  | "onViewableItemsChange" | "viewabilityConfig" | "refreshing" | "onRefresh"
  | "onLoadMore" | "loadMoreThreshold"
  | "header" | "footer" | "empty">;

這是 ListProps 中負責資料與列的部分:當你自己的元件要自行排版列表時,就傳入這些屬性,例如以自訂內距與分隔線包裝 List 的設定列表。