快應(yīng)用 list教程

2020-08-10 11:18 更新

了解如何正確使用list,優(yōu)化列表渲染性能,靈活實(shí)現(xiàn)需求

通過(guò)本節(jié),你將學(xué)會(huì):

適用場(chǎng)景


簡(jiǎn)單場(chǎng)景

開發(fā)者在頁(yè)面中實(shí)現(xiàn)長(zhǎng)列表或者屏幕滾動(dòng)等效果時(shí),習(xí)慣使用div組件做循環(huán)遍歷

示例如下:

假設(shè)開發(fā)者要這樣的效果:一個(gè)結(jié)構(gòu)簡(jiǎn)單的商品列表

使用 div 組件的代碼如下:

<template>
  <!-- div實(shí)現(xiàn) -->
  <div class="tutorial-page">
    <!-- 商品列表 -->
    <block for="productList">
      <div class="content-item" onclick="route($item.url)">
        <image class="img" src="{{$item.img}}"></image>
        <div class="text-wrap">
          <div class="top-line">
            <text class="text-name">{{$item.name}}</text>
            <text class="text-price">{{$item.price}}</text>
          </div>
          <text class="bottom-line">{{$item.brief}}</text>
        </div>
      </div>
    </block>

    <!-- 加載更多,監(jiān)聽通用事件appear,出現(xiàn)時(shí)加載更多數(shù)據(jù) -->
    <div class="load-more" onappear="loadMoreData">
      <progress type="circular"></progress>
      <text>加載更多</text>
    </div>
  </div>
</template>

然而,當(dāng) DOM 結(jié)構(gòu)復(fù)雜時(shí),滾動(dòng)頁(yè)面會(huì)出現(xiàn)卡頓現(xiàn)象,因?yàn)?Native 無(wú)法復(fù)用div組件實(shí)現(xiàn)的列表元素

為了得到流暢的列表滾動(dòng)體驗(yàn),推薦開發(fā)者使用list組件替代div組件實(shí)現(xiàn)長(zhǎng)列表布局,因?yàn)?Native 會(huì)復(fù)用相同type屬性list-item

使用 list 組件的代碼如下:

<template>
  <!-- 列表實(shí)現(xiàn) -->
  <list class="tutorial-page" onscrollbottom="loadMoreData">
    <!-- 商品列表 -->
    <block for="productList">
      <list-item type="product" class="content-item" onclick="route($item.url)">
        <image class="img" src="{{$item.img}}"></image>
        <div class="text-wrap">
          <div class="top-line">
            <text class="text-name">{{$item.name}}</text>
            <text class="text-price">{{$item.price}}</text>
          </div>
          <text class="bottom-line">{{$item.brief}}</text>
        </div>
      </list-item>
    </block>

    <!-- 加載更多,type屬性自定義命名為loadMore -->
    <list-item type="loadMore" class="load-more">
      <progress type="circular"></progress>
      <text>加載更多</text>
    </list-item>
  </list>
</template>

要實(shí)現(xiàn) DOM 片段的復(fù)用,要求相同type屬性的 DOM 結(jié)構(gòu)完全相同;所以設(shè)置相同type屬性list-item是優(yōu)化列表滾動(dòng)性能的關(guān)鍵

注意:

  • list-item內(nèi)不能再嵌套list
  • list-itemtype屬性為必填屬性
  • list-item內(nèi)部需謹(jǐn)慎使用if指令for指令,因?yàn)橄嗤?code>type屬性的list-item的 DOM 結(jié)構(gòu)必須完全相同,而使用if指令for指令會(huì)造成 DOM 結(jié)構(gòu)差異

提示:

若遇到類似xxx cannot be cast to xxx at ...list的錯(cuò)誤,請(qǐng)檢查list-item組件是否存在如下情形:

  • 未設(shè)置type屬性。解決方案:設(shè)置type屬性
  • 內(nèi)部使用了if指令。解決方案:使用show指令代替if指令,或設(shè)置不同的type屬性
  • 設(shè)置為相同的type屬性,但 DOM 結(jié)構(gòu)不一致。解決方案:設(shè)置不同的type屬性

復(fù)雜場(chǎng)景

實(shí)現(xiàn)簡(jiǎn)單的商品列表,了解list組件的基本用法和優(yōu)化性能的關(guān)鍵后,接下來(lái)通過(guò)實(shí)現(xiàn)多種列表元素類型的復(fù)雜列表,進(jìn)一步了解list組件

示例如下:

假設(shè)開發(fā)者要實(shí)現(xiàn)這樣的效果:一個(gè)商品列表頁(yè),圖片位于左邊和圖片位于右邊的商品交錯(cuò)顯示

列表中的列表元素可以分為三類,設(shè)置三種不同type屬性list-item。分別為:

  • 圖片在左,文字在右的list-itemtype屬性自定義命名為productLeft
  • 圖片在右,文字在左的list-itemtype屬性自定義命名為productRight
  • 加載更多的list-item,type屬性自定義命名為loadMore

示例代碼如下:

<template>
  <!-- list中可以劃分為三種類型的DOM結(jié)構(gòu),對(duì)應(yīng)三種type屬性的list-item -->
  <list class="tutorial-page" onscrollbottom="loadMoreData">
    <block for="{{productList}}">
      <!-- 圖片在左,文字在右的list-item,type屬性自定義命名為productLeft -->
      <list-item type="productLeft" class="content-item" if="{{$idx%2 === 0}}" onclick="route($item.url)">
        <image class="img" src="{{$item.img}}"></image>
        <div class="text-wrap">
          <div class="top-line">
            <text class="text-name">{{$item.name}}</text>
            <text class="text-price">{{$item.price}}</text>
          </div>
          <text class="bottom-line">{{$item.brief}}</text>
        </div>
      </list-item>

      <!-- 圖片在右,文字在左的list-item,type屬性自定義命名為productRight -->
      <list-item type="productRight" class="content-item" if="{{$idx%2 === 1}}" onclick="route($item.url)">
        <div class="text-wrap">
          <div class="top-line">
            <text class="text-name">{{$item.name}}</text>
            <text class="text-price">{{$item.price}}</text>
          </div>
          <text class="bottom-line">{{$item.brief}}</text>
        </div>
        <image class="img" src="{{$item.img}}"></image>
      </list-item>
    </block>

    <!-- 加載更多的list-item,type屬性自定義命名為loadMore -->
    <list-item type="loadMore" class="load-more">
      <progress type="circular"></progress>
      <text>加載更多</text>
    </list-item>
  </list>
</template>

性能優(yōu)化


當(dāng) DOM 結(jié)構(gòu)復(fù)雜時(shí),為了得到流暢的列表滾動(dòng)體驗(yàn),list組件的性能優(yōu)化必不可缺

list組件的性能優(yōu)化分為精簡(jiǎn)DOM層級(jí)、復(fù)用list-item、細(xì)粒度劃分list-item關(guān)閉scrollpage四個(gè)方面

其中,精簡(jiǎn)DOM層級(jí)復(fù)用list-item是使用list組件必須遵循的優(yōu)化原則,細(xì)粒度劃分list-item關(guān)閉scrollpage適用于部分場(chǎng)景,詳見下文

精簡(jiǎn)DOM層級(jí)

精簡(jiǎn)DOM層級(jí),即減少DOM樹的級(jí)數(shù)和分支上的DOM節(jié)點(diǎn)數(shù)。層級(jí)越少、數(shù)量越少,布局和繪制就會(huì)越快

因此,開發(fā)者需要盡量剔除list中無(wú)意義的包裹類標(biāo)簽和層級(jí)

復(fù)用list-item

復(fù)用list-item,即列表中相同的DOM結(jié)構(gòu)設(shè)置為同一type屬性list-item,這是優(yōu)化列表滾動(dòng)體驗(yàn)的關(guān)鍵

細(xì)粒度劃分list-item

細(xì)粒度劃分list-item,即列表中相同的DOM結(jié)構(gòu)劃分為盡可能小的列表元素(即list-item

示例如下:

假設(shè)開發(fā)者要實(shí)現(xiàn)這樣的效果:商品按類別分類,展示多種類別

從業(yè)務(wù)角度,可按類別劃分為不同type屬性list-item

然而,當(dāng)list-item復(fù)雜時(shí),會(huì)出現(xiàn)卡頓現(xiàn)象。推薦拋開業(yè)務(wù)邏輯,劃分為盡可能小的列表元素

示例代碼如下:

<template>
  <list class="tutorial-page" onscrollbottom="loadMoreData">
    <!-- 細(xì)粒度劃分list-item -->
    <block for="productList">
      <!-- title -->
      <list-item type="title" if="$item.title" class="title {{$idx>0?'margin-top':''}}">
        <text>{{$item.title}}</text>
      </list-item>
      <!-- banner -->
      <list-item type="banner" if="$item.bannerImg" class="banner">
        <image src="{{$item.bannerImg}}"></image>
      </list-item>
      <!-- productMini -->
      <list-item type="productMini" if="$item.productMini" class="product-mini-wrap">
        <div for="value in $item.productMini" class="product-mini">
          <image src="{{value.img}}" class="product-mini-img"></image>
          <text>{{value.name}}</text>
          <text class="product-mini-brief">{{value.brief}}</text>
          <text class="product-mini-price">{{value.price}}</text>
        </div>
      </list-item>
      <!-- textHint -->
      <list-item type="textHint" if="$item.textHint" class="text-hint">
        <text>{{$item.textHint}} ></text>
      </list-item>
    </block>
    <!-- list底部的加載更多 -->
    <list-item type="loadMore" class="load-more">
      <progress type="circular"></progress>
      <text>加載更多</text>
    </list-item>
  </list>
</template>

關(guān)閉scrollpage

list組件支持屬性scrollpage,默認(rèn)關(guān)閉,標(biāo)志是否將頂部頁(yè)面中非list的元素隨list一起滾動(dòng)。開啟scrollpage會(huì)降低list渲染性能

因此,在開發(fā)者開啟scrollpage前,推薦先嘗試將頂部頁(yè)面中非list的元素,作為一種或多種type屬性list-item,移入list中,從而達(dá)到關(guān)閉scrollpage提高渲染性能的目的

示例如下:

假設(shè)開發(fā)者要實(shí)現(xiàn)這樣的效果:頂部banner,banner下方為常見列表,需要整屏滾動(dòng)

開發(fā)者一般會(huì)將頁(yè)面劃分為banner和list兩部分,然后開啟listscrollpage屬性,實(shí)現(xiàn)整屏滾動(dòng)

然而,開啟scrollpage會(huì)降低list渲染性能,推薦將頂部banner作為一種特殊type屬性list-item,移入list中,關(guān)閉scrollpage

示例代碼如下:

<template>
  <!-- 列表實(shí)現(xiàn),監(jiān)聽列表的scrollbottom事件,列表滾動(dòng)到底部時(shí)加載更多數(shù)據(jù) -->
  <list class="tutorial-page" onscrollbottom="loadMoreData">
    <list-item type="banner" class="banner">
      <image src="../../Common/img/demo_large.png"></image>
    </list-item>

    <!-- 商品列表 -->
    <block for="productList">
      <list-item type="product" class="content-item" onclick="route($item.url)">
        <image class="img" src="{{$item.img}}"></image>
        <div class="text-wrap">
          <div class="top-line">
            <text class="text-name">{{$item.name}}</text>
            <text class="text-price">{{$item.price}}</text>
          </div>
          <text class="bottom-line">{{$item.brief}}</text>
        </div>
      </list-item>
    </block>

    <!-- list-item實(shí)現(xiàn)的加載更多,type屬性自定義命名為loadMore -->
    <list-item type="loadMore" class="load-more">
      <progress type="circular"></progress>
      <text>加載更多</text>
    </list-item>
  </list>
</template>

list-item懶加載

懶加載,簡(jiǎn)稱lazyload,本質(zhì)上是按需加載

在傳統(tǒng)的頁(yè)面中,常用lazyload優(yōu)化網(wǎng)頁(yè)的性能:

  • 實(shí)現(xiàn):不加載全部頁(yè)面資源,當(dāng)資源即將呈現(xiàn)在瀏覽器可視區(qū)域時(shí),再加載資源
  • 優(yōu)點(diǎn):加快渲染的同時(shí)避免流量浪費(fèi)

在框架中,開發(fā)者也可使用lazyload概念優(yōu)化列表的渲染:

  • 實(shí)現(xiàn):提前fetch請(qǐng)求足夠的列表數(shù)據(jù)保存在內(nèi)存變量memList中,當(dāng)list滾動(dòng)到底部時(shí),從memList中提取部分?jǐn)?shù)據(jù)來(lái)渲染list-item。當(dāng)memList中數(shù)據(jù)不足時(shí),提前fetch請(qǐng)求數(shù)據(jù),填充memList
  • 優(yōu)點(diǎn):每次網(wǎng)絡(luò)請(qǐng)求與頁(yè)面渲染的數(shù)據(jù)量不一致,減少首屏渲染占用的JS執(zhí)行時(shí)間,減少渲染后續(xù)list-item的等待時(shí)間

示例如下:

假設(shè)開發(fā)者要實(shí)現(xiàn)這樣的效果:一個(gè)商品列表,每次渲染10個(gè)商品

  • 渲染首屏?xí)r,請(qǐng)求數(shù)據(jù)保存在內(nèi)存變量memList中,從memList中提取部分?jǐn)?shù)據(jù)渲染列表
  • 加載更多時(shí),首先檢查memList中是否有足夠數(shù)據(jù),有則直接從memList中提取部分?jǐn)?shù)據(jù)渲染,而不是才開始網(wǎng)絡(luò)請(qǐng)求,減少時(shí)間消耗。當(dāng)memList中數(shù)據(jù)不足時(shí),提前請(qǐng)求數(shù)據(jù)

示例代碼如下:

<template>
  <!-- 列表實(shí)現(xiàn),監(jiān)聽列表的scrollbottom事件,列表滾動(dòng)到底部時(shí)加載更多數(shù)據(jù) -->
  <list class="tutorial-page" onscrollbottom="renderMoreListItem">
    <!-- 商品列表 -->
    <block for="productList">
      <list-item type="product" class="content-item">
        <image class="img" src="{{$item.img}}"></image>
        <div class="text-wrap">
          <div class="top-line">
            <text class="text-name">{{$item.name}}</text>
            <text class="text-price">{{$item.price}}</text>
          </div>
          <text class="bottom-line">{{$item.brief}}</text>
        </div>
      </list-item>
    </block>

    <list-item type="loadStatus" class="load-status">
      <progress type="circular" show="{{hasMoreData}}"></progress>
      <text show="{{hasMoreData}}">加載更多</text>
      <text show="{{!hasMoreData}}">沒有更多了~</text>
    </list-item>
  </list>
</template>

<script>
  import {dataComponentListLazyload} from '../../Common/js/data'

  // 模擬fetch請(qǐng)求數(shù)據(jù)
  function callFetch (callback) {
    setTimeout(function () {
      callback(dataComponentListLazyload)
    }, 500)
  }

  // 內(nèi)存中存儲(chǔ)的列表數(shù)據(jù)
  let memList = []

  export default {
    data: {
      productList: [],
      hasMoreData: true,
      // 每次渲染的商品數(shù)
      size: 10,
      // 是否正在fetch請(qǐng)求數(shù)據(jù)
      isLoadingData: false
    },
    onInit () {
      this.$page.setTitleBar({ text: 'list-item懶加載' })
      // 獲取數(shù)據(jù)并渲染列表
      this.loadAndRender()
    },
    /**
     * 請(qǐng)求并渲染
     */
    loadAndRender (doRender = true) {
      this.isLoadingData = true
      // 重新請(qǐng)求數(shù)據(jù)并根據(jù)模式判斷是否需要渲染列表
      callFetch(function (resList) {
        this.isLoadingData = false
        if (!resList) {
          console.error(`數(shù)據(jù)請(qǐng)求錯(cuò)誤`)
        }
        else if (!resList.length) {
          this.hasMoreData = false
        }
        else {
          memList = memList.concat(resList)
          if (doRender) {
            this._renderList()
          }
        }
      }.bind(this))
    },
    _renderList () {
      // 渲染列表
      if (memList.length > 0) {
        const list = memList.splice(0, this.size)
        this.productList = this.productList.concat(list)
      }
      if (memList.length <= this.size) {
        // 提前請(qǐng)求新的數(shù)據(jù)
        this.loadAndRender(false)
      }
    },
    /**
     * 滑動(dòng)到底部時(shí)加載更多
     */
    renderMoreListItem () {
      if (!this.isLoadingData) {
        this._renderList()
      }
    }
  }
</script>

注意:避免在ViewModeldata屬性中定義memList。因?yàn)樵?code>ViewModel的data屬性中定義變量會(huì)觸發(fā)set/get數(shù)據(jù)驅(qū)動(dòng)定義,而memList作為暫時(shí)保存數(shù)據(jù)的變量,不需監(jiān)聽數(shù)據(jù)變化

效果展示:吸頂


本部分非必讀,旨在為有以下需求之一的開發(fā)者提供參考:

  • 需要判斷頁(yè)面滾動(dòng)位置
  • 需要了解appear事件disappear事件

傳統(tǒng)頁(yè)面的實(shí)現(xiàn)思路

吸頂是傳統(tǒng)web頁(yè)面中的一種比較老的交互方式:

  • 吸頂元素的初始位置一般靠近頁(yè)面頂部,但與頂部有一定的距離
  • 當(dāng)手指向上滑動(dòng)超過(guò)吸頂元素的初始位置時(shí),把吸頂元素固定在頂部
  • 當(dāng)手指向下滑動(dòng)到達(dá)吸頂元素的初始位置時(shí),取消吸頂元素在頂部的固定

吸頂在傳統(tǒng)web頁(yè)面中的實(shí)現(xiàn)思路是監(jiān)聽scroll事件,當(dāng)頁(yè)面滾動(dòng)到一定位置時(shí),做一些事情來(lái)改變吸頂元素在窗口中的位置

框架的實(shí)現(xiàn)思路

然而,與傳統(tǒng)web頁(yè)面不同,在框架中,scroll事件僅適用于list組件,且獲取的值是滾動(dòng)的相對(duì)坐標(biāo)值,在使用時(shí),需要通過(guò)累加來(lái)獲取當(dāng)前滾動(dòng)位置的絕對(duì)坐標(biāo)

此外,scroll事件在列表滾動(dòng)時(shí)會(huì)被高頻觸發(fā),存在潛在性能問題

因此,在框架中,推薦開發(fā)者使用appear事件disappear事件來(lái)實(shí)現(xiàn)吸頂效果,appear事件在組件出現(xiàn)時(shí)觸發(fā),disappear事件在組件消失時(shí)觸發(fā)

appear事件disappear事件是組件的通用事件,文檔中標(biāo)有支持通用事件的組件都支持這兩個(gè)事件,包括div組件、list-item組件

靈活使用appear事件disappear事件,能實(shí)現(xiàn)大部分需要判斷滾動(dòng)位置的需求

框架的具體實(shí)現(xiàn)與代碼

接下來(lái),對(duì)應(yīng)在list組件中實(shí)現(xiàn)吸頂效果的示例代碼,具體分析實(shí)現(xiàn)思路

首先,了解頂部元素吸頂元素

  • 列表中的頂部元素type屬性toplist-item
  • 列表中的吸頂元素type屬性ceilinglist-item

然后,分析吸頂效果實(shí)現(xiàn)方案:

  • 使用stack組件做為整個(gè)頁(yè)面的容器,stack組件的特性為:每個(gè)直接子組件按照先后順序依次堆疊,覆蓋前一個(gè)子組件
  • stack組件中增加一個(gè)排在最后的子組件,作為mask遮擋之前的子組件,顯示效果為一直固定在頂部,這個(gè)mask吸頂元素渲染效果完全一致
  • 當(dāng)吸頂元素需要吸頂時(shí),顯示對(duì)應(yīng)的mask,實(shí)現(xiàn)吸頂?shù)男Ч?;?dāng)吸頂元素不需要吸頂時(shí),隱藏對(duì)應(yīng)的mask

最后,判斷吸頂條件:

  • 當(dāng)頁(yè)面向下滾動(dòng)到頂部元素消失在視野時(shí),吸頂元素需要固定在頂部,因此,監(jiān)聽頂部元素disappear事件,顯示mask
  • 當(dāng)頁(yè)面向上滾動(dòng)到頂部元素出現(xiàn)在視野時(shí),吸頂元素需要取消固定,因此,監(jiān)聽頂部元素appear事件,隱藏mask

示例代碼如下:

<template>
  <!-- 利用stack組件,使"列表中的吸頂元素對(duì)應(yīng)的Mask"覆蓋列表 -->
  <stack class="tutorial-page">
    <list class="list">
      <!-- 通過(guò)監(jiān)聽"列表中的頂部元素"的元素的appear和disappear事件,控制"列表中的吸頂元素對(duì)應(yīng)的Mask"的顯示 -->
      <list-item type="top" ondisappear="showMask" onappear="hideMask">
        <div class="height-300 bg-blue">
          <text>列表中的頂部元素</text>
        </div>
      </list-item>
      <!-- 列表中的吸頂元素 -->
      <list-item type="ceiling">
        <div class="height-300 bg-red">
          <text>列表中的吸頂元素</text>
        </div>
      </list-item>
      <!-- 普通列表元素 -->
      <list-item for="list" type="common" class="list-item">
        <text class="text">{{$item}}</text>
      </list-item>
    </list>

    <!-- 列表中的吸頂元素對(duì)應(yīng)的Mask -->
    <div show="{{maskShow}}">
      <div class="height-300 bg-red">
        <text>列表中的吸頂元素</text>
      </div>
    </div>
  </stack>
</template>

<style lang="less">
  .tutorial-page {
    flex-direction: column;
    .list {
      width: 750px;
      flex-grow: 1;
      .list-item {
        height: 150px;
        border-bottom-width: 1px;
        border-bottom-color: #0faeff;
        .text {
          flex: 1;
          text-align: center;
        }
      }
    }
    .height-300 {
      height: 300px;
    }
    .bg-red {
      flex-grow: 1;
      justify-content: center;
      background-color: #f76160;
    }
    .bg-blue {
      flex-grow: 1;
      justify-content: center;
      background-color: #0faeff;
    }
  }
</style>

<script>
  export default {
    data: {
      maskShow: false,
      appearCount: 0,
      list: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N']
    },
    onInit(){
      this.$page.setTitleBar({ text: '效果展示:吸頂' })
    },
    showMask () {
      this.maskShow = true
    },
    hideMask () {
      // 加載頁(yè)面時(shí),所有元素的appear事件都會(huì)被觸發(fā)一次。因此,需要過(guò)濾第一次的appear事件
      if (this.appearCount) {
        this.maskShow = false
      } else {
        ++this.appearCount
      }
    }
  }
</script>

總結(jié)


了解list組件的特點(diǎn),可以更好的提升頁(yè)面性能,避免后期開發(fā)過(guò)程中引起的性能問題


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)