<list>

<list> 组件是一个高性能的可滚动容器,通过元素回收和懒加载机制优化性能和内存使用。它支持横向和纵向滚动,可实现单列、网格和瀑布流布局,非常适合无限滚动的信息流等应用场景。

使用指南

单列布局

1. 设置宽高<list> 的宽高代表了其可视区域的大小,因此需要是确定的值,不能由内部内容撑开,只有在可视区域内可见的子节点才会被渲染。

2. 设置滚动方向与布局形式:设置属性 scroll-orientation 指定布局与滚动方向,设置 list-typespan-count 指定布局形式。

3. 配置子节点:使用标签 <list-item> 作为 <list> 直接子节点,并且给 <list-item> 设置 item-keykey,并且需要保持一致。

多列布局

1. 设置布局方式与列数: 设置布局形式 list-typeflow (网格布局) 或者 waterfall (瀑布流布局),并设置 span-count >= 2。

2. 多列布局下实现独占一行的子节点: 为 <list-item/> 设置 full-span 属性,可以使其在布局中独占一行或者一列。

网格布局案例:

瀑布流布局示例

属性

list-typeRequired

list-type: 'single' | 'flow' | 'waterfall'

控制 <list> 组件的布局类型,需要与 span-count 配合使用。

说明
single单列/单行布局
flow多列/多行网格布局。网格布局会充分体现其归整性,左右两列中,相邻位置子节点的 top 是一致的,因此使用场景通常为大小一致的子节点。子节点宽度由 <list> 的宽度和 span-count 决定
waterfall多列/多行瀑布流布局。内容是连续填充的,子节点元件是从上到下填充到最短的列,实现视觉上的连续和动态性,因此使用场景通常为大小不一致的子节点。子节点宽度由 <list> 的宽度和 span-count 决定

你可以通过查看多列布局指南 中的真实案例更清晰地看出 flowwaterfall 的布局差异。

span-countRequired

span-count: number

设置 <list> 组件的布局列数或者行数。

scroll-orientationRequired

// DefaultValue: "vertical"
scroll-orientation?: 'vertical''horizontal'

设置 <list> 组件的滚动方向与布局方向。

item-keyRequired

// DefaultValue: null
item-key: string

<list-item item-key="item"/>

item-key<list-item> 上必须传入的属性。

NOTE

开发者需要为每一个 <list> 子节点设置唯一的 item-key,其会被用来帮助 <list> 识别哪些 <list> 子节点已更改、添加或删除。因此开发者需要保证正确设置 item-key,如设置有误,将会导致错乱、闪动问题。

keyRequired

// DefaultValue: null
key: string

<list-item
  item-key="item-0"
  key="item-0"
/>

使用 key 属性来帮助框架识别哪些元件已更改、添加或删除。

NOTE

在 list 场景下,keyitem-key 保持一致。

enable-scroll

// DefaultValue: true
enable-scroll?: boolean

是否允许 <list> 组件滚动。

enable-nested-scroll

// DefaultValue: true
enable-nested-scroll?: boolean

是否允许 <list> 与其他滚动容器实现嵌套滚动,开启后先滚动内层容器,再滚动外层容器。

list-main-axis-gap

// DefaultValue: null
list-main-axis-gap?: ${number}px | ${number}rpx

<list
  style={{listMainAxisGap:'10px'}}
/>

指定了在主轴方向上,<list> 子节点的间距,需要写在 style 中。

list-cross-axis-gap

// DefaultValue: null
list-cross-axis-gap?: ${number}px | ${number}rpx

<list
  style={{listCrossAxisGap:'10px'}}
/>

指定了在副轴方向上,<list> 子节点的间距,需要写在 style 中。

sticky

// DefaultValue: false
sticky?: boolean

声明在 <list> 组件上,控制 <list> 组件整体是否允许吸顶或者吸底。

sticky-offset

// DefaultValue: 0
sticky-offset?: number

吸顶或者吸底位置距离 <list> 顶部或者底部的偏移,单位为 px

sticky-top

// DefaultValue: false
sticky-top?: boolean

声明在子节点 <list-item> 上,控制该节点是否会吸顶。

sticky-bottom

// DefaultValue: false
sticky-bottom?: boolean

声明在子节点 <list-item> 上,控制该节点是否会吸底。

bounces
iOS only

// DefaultValue: true
bounces?: boolean

开启边缘回弹效果。

initial-scroll-index

// DefaultValue: 0
initial-scroll-index?: number

指定 <list> 在渲染后自动定位到的节点位置,仅首次生效。

need-visible-item-info

// DefaultValue: false
need-visible-item-info?: boolean

控制滚动事件回调参数中是否包含当前正在渲染节点的位置信息,这里滚动事件包含:scrollscrolltoupperscrolltolower

滚动事件回调参数格式:

interface ListScrollInfo {
  // 距上次滚动的横向滚动偏移量,单位 px
  deltaX: number;
  // 距上次滚动的纵向滚动偏移量,单位 px
  deltaY: number;
  // 当前横向滚动偏移量,单位 px
  scrollLeft: number;
  // 当前纵向滚动偏移量,单位 px
  scrollTop: number;
  // 当前内容区域宽度,单位 px
  scrollWidth: number;
  // 当前内容区域高度,单位 px
  scrollHeight: number;
  // `<list>`宽度,单位 px
  listWidth: number;
  // `<list>`高度,单位 px
  listHeight: number;
  // 滚动事件源
  eventSource: ListEventSource;
  // 当前正在渲染节点的位置信息
  attachedCells: [
    {
      id: number; // 节点 id
      itemKey: string; // 节点 item-key
      index: number; // 节点在 list 中的 index
      left: number; // 节点左边界相对于 list 的位置,单位 px
      top: number; // 节点上边界相对于 list 的位置,单位 px
      right: number; // 节点右边界相对于 list 的位置,单位 px
      bottom: number; // 节点下边界相对于 list 的位置,单位 px
    },
  ];
}

upper-threshold-item-count

// DefaultValue: 0
upper-threshold-item-count?: number

<list> 顶部剩余可展示的子节点个数首次小于 upper-threshold-item-count 时,触发一次 scrolltoupper 事件。

lower-threshold-item-count

// DefaultValue: 0
lower-threshold-item-count?: number

<list> 底部剩余可展示的子节点个数首次小于 lower-threshold-item-count 时,触发一次 scrolltolower 事件。

scroll-event-throttle

// DefaultValue: 200
scroll-event-throttle?: number

用于设置 <list> 回调 scroll 事件的时间间隔,单位为毫秒(ms)。其默认 200 ms 回调一次滚动事件。

item-snap

// defaultValue: undefined
'item-snap'?: ListItemSnapAlignment;

interface ListItemSnapAlignment {
  factor: number;
  offset: number;
}

控制 <list> 实现分页滚动的效果。

分页参数

  • factor: 分页定位锚定位置的参数,取值范围 [0, 1]
    • 取值为 0 代表分页滚动的 <list> 子节点和 <list> 顶部对齐
    • 取值为 1 代表分页滚动的 <list> 子节点和 <list> 底部对齐
  • offset: 额外增加偏移参数,在 factor 的基础之上再进一步添加偏移量
NOTE

engineVersion 版本小于 3.2 时,列表滚动速率会存在不一致问题。

need-layout-complete-info

控制 layoutcomplete 事件中是否包含本次 layout 前后的节点排版信息,触发本次排版的 <list> Diff 信息,以及当前 <list> 的滚动状态信息。

export interface LayoutCompleteEvent extends BaseEvent<'layoutcomplete', {}> {
  detail: {
    'layout-id': number;
    // 开启 need-layout-complete-info
    scrollInfo: ListScrollInfo;
    // 开启 need-layout-complete-info
    diffResult?: {
      insertions: number[];
      move_from: number[];
      move_to: number[];
      removals: number[];
      update_from: number[];
      update_to: number[];
    };
    // 开启 need-layout-complete-info
    visibleCellsAfterUpdate?: ListItemInfo[];
    // 开启 need-layout-complete-info
    visibleCellsBeforeUpdate?: ListItemInfo[];
  };
}

layout-id

// defaultValue: -1
layout-id?: number

用于标记本次数据源更新的唯一标识,会在 layoutcomplete 事件回调中返回这个 id。

preload-buffer-count

// DefaultValue: 0
preload-buffer-count?: number

该属性可以控制 <list> 提前加载 <list> 外节点的个数。

NOTE
  • preload-buffer-count 的值越大,可以提前加载的屏外节点越多,但是也会增加 <list> 内存占用。

  • preload-buffer-count 的取值建议为占满 <list> 一屏的节点个数。

  • list-type='single'/'flow' 时生效。

scroll-bar-enable
iOS only

// DefaultValue: true
scroll-bar-enable?: boolean

是否显示 <list> 组件滚动条

reuse-identifier

// DefaultValue: null
reuse-identifier: string

<list>
  <list-item
    reuse-identifier="type1"
  >
  <list-item
    reuse-identifier="type2"
  >
</list>

设置 <list-item> 复用 id,<list> 组件在渲染子节点时,会根据 <list-item>reuse-identifier 属性值对 <list-item> 进行复用,只有设置了相同的 reuse-identifier 属性值的 <list-item> 才会被复用。

默认情况下,开发者不提供需要 reuse-identifier,而是由框架在编译阶段确定,比如:当 <list-item> 处于循环内(例如 Array.prototype.map 等)时,由于它们在编译阶段的形态和位置相同,我们会为它们生成相同的 reuse-identifier,我们会认为这一组 <list-item> 可以互相复用。

NOTE

使用场景:结构差异非常大的 <list-item> 在复用时性能比较差,因此建议为它们设置不同的 reuse-identifier,以避免它们之前被相互复用。

full-span

// DefaultValue: false
full-span?: boolean

<list>
  <list-item full-span={true}/>
</list>

属性 full-span 用于标识 <list-item> 独占一行或者一列。

estimated-main-axis-size-px

// DefaultValue: -1
estimated-main-axis-size-px?: number

<list-item
  estimated-main-axis-size-px={100}
/>

用于指定 <list-item> 还未渲染完成时,在主轴方向上的占位大小,单位为 px,如果不设置,默认值为 <list> 在主轴方向上的大小。

NOTE

强烈建议开发者设置与子节点真实大小相近的 estimated-main-axis-size-px

事件

前端可以在元件上绑定相应事件回调来监听元件的运行时行为,使用方法如下。

scroll

bindscroll?: EventHandler<ListScrollEvent>;

interface ListScrollEvent {
  // 距上次滚动的横向滚动偏移量,单位 px
  deltaX: number;
  // 距上次滚动的纵向滚动偏移量,单位 px
  deltaY: number;
  // 当前横向滚动偏移量,单位 px
  scrollLeft: number;
  // 当前纵向滚动偏移量,单位 px
  scrollTop: number;
  // 当前内容区域宽度,单位 px
  scrollWidth: number;
  // 当前内容区域高度,单位 px
  scrollHeight: number;
  // `<list>`宽度,单位 px
  listWidth: number;
  // `<list>`高度,单位 px
  listHeight: number;
  // 滚动事件源
  eventSource: ListEventSource;
  // 当前正在渲染节点的位置信息
  attachedCells: [
    {
      "id": number,        // 节点 id
      "itemKey": string,   // 节点 item-key
      "index": number,     // 节点在 list 中的 index
      "left": number,      // 节点左边界相对于 list 的位置,单位 px
      "top": number,       // 节点上边界相对于 list 的位置,单位 px
      "right": number,     // 节点右边界相对于 list 的位置,单位 px
      "bottom": number,    // 节点下边界相对于 list 的位置,单位 px
    },
  ];
}

enum ListEventSource {
  DIFF = 0,
  LAYOUT = 1,
  SCROLL = 2,
}

<list> 滚动事件。

NOTE

scrolltoupper

bindscrolltoupper?: EventHandler<ListScrollEvent>;

滚动到 <list> 顶部时触发的回调。该回调触发的位置可以由 upper-threshold-item-count 控制。

scrolltolower

bindscrolltolower?: EventHandler<ListScrollEvent>;

滚动到 <list> 底部时触发的回调。该回调触发的位置可以由 lower-threshold-item-count 控制。

scrollstatechange

bindscrollstatechange?: EventHandler<ListScrollStateChangeEvent>;

interface ScrollStateChangeEvent extends CustomEvent {
  detail: {
    // 本次滑动的滑动状态,取值说明
    //   1 - 静止
    //   2 - 拖拽
    //   3 - 惯性滚动
    //   4 - 动画滚动
    state: number;
  };
}

<list> 滚动状态变化时触发的回调。事件参数的字段 detail 里的 state 分别取值: 1234,分别表示 <list> 滚动状态为:静止、拖拽、惯性滚动、动画滚动。

layoutcomplete

bindlayoutcomplete?: EventHandler<ListLayoutFinishEvent>;

interface LayoutCompleteEvent extends BaseEvent<'layoutcomplete', {}> {
  detail: {
    'layout-id': number;
    // 需要开启 need-layout-complete-info
    scrollInfo: ListScrollInfo;
    // 需要开启 need-layout-complete-info
    diffResult?: {
      insertions: number[];
      move_from: number[];
      move_to: number[];
      removals: number[];
      update_from: number[];
      update_to: number[];
    };
    // 需要开启 need-layout-complete-info
    visibleCellsAfterUpdate?: ListItemInfo[];
    // 需要开启 need-layout-complete-info
    visibleCellsBeforeUpdate?: ListItemInfo[];
  };
}

interface ListItemInfo {
  // 子节点高度
  height: number;
  // 子节点宽度
  width: number;
  // 子节点 itemKey
  itemKey: string;
  // 子节点是否处于渲染状态
  isBinding: boolean;
  // 子节点相对于全部滚动区域的 x 坐标位置
  originX: number;
  // 子节点相对于全部滚动区域的 y 坐标位置
  originY: number;
  // 子节点是否存被更新
  updated: boolean;
}

<list> 布局完成后触发回调。

snap

bindsnap?: EventHandler<ListSnapEvent>;

interface ListSnapEvent extends common.BaseEvent<'snap', {}> {
  detail: {
    // 将会分页滚动到的节点索引
    position: number;
    // 当前横向滚动偏移量,单位 px
    currentScrollLeft: number;
    // 当前纵向滚动偏移量,单位 px
    currentScrollTop: number;
    // 分页滚动的目标横向滚动偏移量,单位 px
    targetScrollLeft: number;
    // 分页滚动的目标纵向滚动偏移量,单位 px
    targetScrollTop: number;
  };
};

即将发生分页滚动时的回调。

方法

scrollToPosition

this.createSelectorQuery()
  .select('#id_of_list')
  .invoke({
    method: 'scrollToPosition',
    params: {
      position: 10,
      offset: 100,
      alignTo: 'top',
      smooth: true,
    },
    success: function (res) {},
    fail: function (res) {},
  })
  .exec();

<list> 组件滚动到指定的位置,参数说明:

参数名类型默认值必填简介
positionnumber指定要滚动到的节点的 index, 取值范围为[0, 数据源个数)
offsetnumber在应用 alignTo 对齐后,继续滚动 offset 长度
alignTostringnull滚动后目标节点在视图中的位置.

"bottom": 滑动至该节点在 <list> 中完全可见,且该节点的底部和 <list> 的底部对齐

"top": 滑动至该节点在 <list> 中完全可见,且该节点的顶部和 <list> 的顶部对齐

"middle": 滑动至该节点在 <list> 中完全可见,且该节点在 <list> 中垂直居中
smoothbooleanfalse滑动过程中是否有动画

autoScroll

this.createSelectorQuery()
  .select('#id_of_list')
  .invoke({
    method: 'autoScroll',
    params: {
      rate: string, //  每一秒滚动的间距,支持正负。间距支持单位"px/rpx/ppx" default->null (iOS 取值必须大于 1/screen.scale px)
      start: bool, //  开始/暂停自动滚动 default->false
      autoStop: bool, // 滑到底部是否自动停止 default->true
    },
    success: function (res) {},
    fail: function (res) {},
  })
  .exec();

触发 <list> 自动滚动,参数说明:

参数名类型默认值必填简介
ratestringnull每一秒滚动的间距,支持正负,可设置单位:px/rpx/ppx
startboolfalse开始或者暂停自动滚动,true: 开始自动滚动,false: 暂停自动滚动
autoStopbooltrue滑到底部是否自动停止

getVisibleCells

lynx
  .createSelectorQuery()
  .select('#id_of_list')
  .invoke({
    method: 'getVisibleCells',
    success(res) {
      console.log('succ ');
    },
    fail(res) {
      console.log('err ');
    },
  })
  .exec();

获取当前展示的所有 <list> 子节点的信息。返回信息如下:

attachedCells: [
  {
    id: number, // 节点 id
    itemKey: string, // 节点 item-key
    index: number, // 节点在 list 中的 index
    left: number, // 节点左边界相对于 list 的位置,单位 px
    top: number, // 节点上边界相对于 list 的位置,单位 px
    right: number, // 节点右边界相对于 list 的位置,单位 px
    bottom: number, // 节点下边界相对于 list 的位置,单位 px
  },
];

scrollBy

lynx
  .createSelectorQuery()
  .select('#id_of_list')
  .invoke({
    method: 'scrollBy',
    params: {
      offset: number,
    },
    success(res) {
      console.log('succ ');
    },
    fail(res) {
      console.log('err ');
    },
  })
  .exec();

在现有偏移量的基础上继续滚动 offset 指定的距离,单位 px,返回的信息如下:

{
  "consumedX" : number,  // 水平方向滚动的距离,单位 px
  "consumedY" : number,  // 竖直方向滚动的距离,单位 px
  "unconsumedX" : number,  // 水平方向未滚动的距离,单位 px
  "unconsumedY" : number,  // 竖直方向未滚动的距离,单位 px
}

更多功能

实现节点吸顶或者吸底

<list> 组件中,可以通过设置 <list-item>sticky-topsticky-bottom 属性来实现节点吸顶或者吸底效果。

确保 <list> 组件的 sticky 属性设置为 true,以允许子节点吸顶,并且可以设置 sticky-offset 来确定吸顶或者吸底位置距离 <list> 顶部或者底部的偏移。

<list
  className="list-container"
  sticky={true}
  sticky-offset={50}
  list-type="single"
  span-count={1}
  scroll-orientation="vertical"
>

<list-item> 上设置 sticky-topsticky-bottom 属性,使该节点在滚动时吸顶或者吸底。由于吸顶或者吸底节点一定是独占一行的节点,因此也需要给该节点设置 full-span 属性。

<list-item
  className="sticky-top-item"
  full-span={true}
  sticky-top={true}
  item-key={`list-item-${index}`}
  key={`list-item-${index}`}
/>

实现滚动到底部加载更多数据

<list> 组件中,你可以实现无限滚动加载的功能。这需要两个步骤:

  1. 设置 lower-threshold-item-count 属性来控制触发时机。当列表滚动到距离底部还剩指定数量的 item 时,就会触发加载。

  2. 监听 scrolltolower 事件。当触发条件满足时,该事件会被调用,你可以在回调函数中请求新数据并添加到列表中。

利用item-snap 实现分页滚动

通过设置 factor 来确定分页滚动定位的参数,取值范围 [0, 1]. 以竖直方向为例,0 代表 <list-item><list> 顶部对齐,1 代表 <list-item><list> 底部对齐,还可以通过设置 offset,从而在 factor 的基础上添加滚动偏移量。

竖直方向:

水平方向:

使用z-index

兼容性

<list>

LCD tables only load in the browser

<list-item>

LCD tables only load in the browser

除非另有说明,本项目采用知识共享署名 4.0 国际许可协议进行许可,代码示例采用 Apache License 2.0 许可协议进行许可。