<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Taro 助力京喜拼拼項目性能體驗優(yōu)化

          共 7553字,需瀏覽 16分鐘

           ·

          2021-02-23 10:51



          背景

          2020 年是社區(qū)團(tuán)購風(fēng)起云涌的一年,互聯(lián)網(wǎng)大廠紛紛抓緊一分一秒跑步進(jìn)場?!熬┫财雌础?微信搜京喜拼拼)是京東旗下的社區(qū)團(tuán)購平臺,依托京東供應(yīng)鏈體系,精選低價好貨,為社區(qū)用戶提供次日達(dá)等優(yōu)質(zhì)服務(wù)。

          京喜拼拼團(tuán)隊技術(shù)選型使用 Taro 以便于實現(xiàn)多端需求,因此 Taro 團(tuán)隊有幸參與到 “京喜拼拼” 小程序的性能體驗優(yōu)化工作。

          全面體驗 - 梳理 Taro 寫法最佳實踐

          我們?nèi)骟w驗后和熟悉業(yè)務(wù)代碼后梳理出一系列 Taro3 寫法的最佳實踐:

          1. 性能相關(guān)

          對小程序的性能影響較大的有兩個因素,分別是 setData數(shù)據(jù)量和單位時間 setData 函數(shù)的調(diào)用次數(shù)

          當(dāng)遇到性能問題時,在項目中打印 setData 的數(shù)據(jù)將非常有利于幫助定位問題。開發(fā)者可以通過進(jìn)入 Taro 項目的 dist/taro.js 文件,搜索定位 .setData 的調(diào)用位置,然后對數(shù)據(jù)進(jìn)行打印。

          在 Taro 中,會對 setDatabatch 捆綁更新操作,因此更多時候只需要考慮 setData 的數(shù)據(jù)量大小問題。

          以下是我們梳理的開發(fā)者需要注意的寫法問題,有一些問題需要開發(fā)者手動調(diào)整,一些問題 Taro 可以幫助自動化規(guī)避:

          1.1. 刪除樓層節(jié)點需要謹(jǐn)慎處理

          假設(shè)有一種這樣一種結(jié)構(gòu):



          <Slider />

          <Goods />

          {isShowModal && <Modal />}
          </View>

          Taro3 目前對節(jié)點的刪除處理是有缺陷的。當(dāng) isShowModaltrue 變?yōu)?false 時,模態(tài)彈窗會從消失。此時 Modal 組件的兄弟節(jié)點都會被更新,setData 的數(shù)據(jù)是 Slider + Goods 組件的 DOM 節(jié)點信息。

          一般情況下,影響不會太大,開發(fā)者無須由此產(chǎn)生心智負(fù)擔(dān)。但倘若待刪除節(jié)點的兄弟節(jié)點的 DOM 結(jié)構(gòu)非常復(fù)雜,如一個個樓層組件,刪除操作的副作用會導(dǎo)致 setData 數(shù)據(jù)量較大,從而影響性能。

          解決辦法:

          目前我們可以這樣優(yōu)化,隔離刪除操作:



          <Slider />

          <Goods />

          <View>
          {isShowModal && <Modal />}
          View>

          </View>

          我們正在對刪除節(jié)點的算法進(jìn)行優(yōu)化,完全規(guī)避這種不必要的 setData,于 v3.1 推出。

          1.2. 基礎(chǔ)組件的屬性盡量保持引用

          假設(shè)基礎(chǔ)組件(如 View、Input 等)的屬性值為非基本類型時,盡量保持對象的引用。

          假設(shè)有以下寫法:

          <Map
          latitude={22.53332}
          longitude={113.93041}
          markers={[{
          latitude: 22.53332,
          longitude: 113.93041
          }]}
          />

          每次渲染時,React 會對基礎(chǔ)組件的屬性做淺對比,這時發(fā)現(xiàn) markers 的引用不同,就會去更新組件屬性。最后導(dǎo)致 setData 次數(shù)增多、setData 數(shù)據(jù)量增大。

          解決辦法:

          可以通過 state、閉包等手段保持對象的引用:

          <Map
          latitude={22.53332}
          longitude={113.93041}
          markers={this.state.markers}
          />

          1.3. 小程序基礎(chǔ)組件盡量不要掛載額外屬性

          基礎(chǔ)組件(如 View、Input 等)如若設(shè)置了非標(biāo)準(zhǔn)的屬性,目前這些額外屬性會被一并進(jìn)行 setData,而實際上小程序并不會理會這些屬性,所以 setData 的這部分?jǐn)?shù)據(jù)是冗余的。

          例如 Text 組件的標(biāo)準(zhǔn)屬性有 selectableuser-select、spacedecode 四個,如果我們?yōu)樗O(shè)置一個額外屬性 something,那么這個額外的屬性也是會被 setData。

          'extra' />

          Taro v3.1 將會自動過濾這些額外屬性,屆時這個限制將不再存在。

          2. 體驗相關(guān)

          2.1. 滾動穿透

          在小程序開發(fā)中,滑動蒙層、彈窗等覆蓋式元素時,滑動事件會冒泡到頁面,使頁面元素也跟著滑動,往往我們的解決辦法是設(shè)置 catchTouchMove 從而阻止冒泡。

          由于 Taro3 事件機制[1]的限制,小程序事件都以 bind 的形式進(jìn)行綁定。所以和 Taro1、Taro2 不同,調(diào)用 e.stopPropagation() 并不能阻止?jié)L動穿透。

          解決辦法:
          1. 使用樣式解決(推薦)

          給需要禁用滾動的組件寫一個樣式,類似于:

          {
          overflow:hidden;
          height: 100vh;
          }
          1. catchMove

          對于 Map 等極個別組件,使用樣式固定寬高也無法阻止?jié)L動,因為這些組件本身就具有滾動的能力。所以第一種辦法處理不了冒泡到 Map 組件上的滾動事件。

          這時候可以為 View 組件增加 catchMove 屬性:

          // 這個 View 組件會綁定 catchtouchmove 事件而不是 bindtouchmove

          2.2. 跳轉(zhuǎn)預(yù)加載

          在小程序中,從調(diào)用 Taro.navigateTo 等跳轉(zhuǎn)類 API,到新頁面觸發(fā) onLoad 會有一定延時。因此類如網(wǎng)絡(luò)請求等操作可以提前到調(diào)用跳轉(zhuǎn) API 之前。

          熟悉 Taro 的同學(xué)可能會想起 Taro1、Taro2 中的 componentWillPreload 鉤子。但 Taro3 不再提供這個鉤子,開發(fā)者可以使用 Taro.preload() 方法實現(xiàn)跳轉(zhuǎn)預(yù)加載:

          // pages/index.js
          Taro.preload(fetchSomething())
          Taro.navigateTo({ url: '/pages/detail' })
          // pages/detail.js
          console.log(getCurrentInstance().preloadData)

          2.3. 建議把 Taro.getCurrentInstance() 的結(jié)果保存下來

          開發(fā)中我們常常會調(diào)用 Taro.getCurrentInstance() 獲取小程序的 app、page 對象、路由參數(shù)等數(shù)據(jù)。但頻繁調(diào)用它可能會導(dǎo)致問題。因此推薦把 Taro.getCurrentInstance() 的結(jié)果在組件中保存起來,之后直接使用:

          class Index extends React.Component {
          inst = Taro.getCurrentInstance()

          componentDidMount () {
          console.log(this.inst)
          }
          }

          難啃的骨頭 - 購物車頁

          我們在低端機上受到了性能的困擾,尤其是在購物車頁面卡頓最為明顯。通過分析頁面結(jié)構(gòu)和反思 Taro 底層實現(xiàn),我們主要采取了兩項優(yōu)化措施,提升了低端機型滾動的流暢度,同時將點擊延時從 1.5s 降到 300ms。

          1. 長列表優(yōu)化

          在 Taro3 中,我們新增了虛擬列表這樣一個特殊的組件,幫助很多社區(qū)的開發(fā)者對超長列表進(jìn)行優(yōu)化,相信很多同學(xué)對虛擬列表的實現(xiàn)原理、包括下圖都已經(jīng)是很熟悉了,但購物車頁卻給我們提出了新的需求。

          虛擬列表

          1.1 不限制高度

          虛擬列表根據(jù) itemSize 來計算每個節(jié)點的位置,如果節(jié)點的寬高不確定,在每個節(jié)點至少加載完成一次之前,我們很難去判斷列表的真實尺寸。這也是為什么在虛擬列表的早期版本中我們并沒有支持這樣的特性,而是選擇固定了每個節(jié)點的高度,避免讓開發(fā)者使用虛擬列表時增加心智負(fù)擔(dān)。

          不過這個需求也并非不能完成,簡單地調(diào)整虛擬列表實現(xiàn)和使用的邏輯,我們就可以輕松實現(xiàn)這個特性。

          import VirtualList from `@tarojs/components/virtual-list`

          function buildData (offset = 0) {
          return Array(100).fill(0).map((_, i) => i + offset);
          }

          - const Row = React.memo(({ index, style, data }) => {
          + const Row = React.memo(({ id, index, style, data }) => {
          return (
          -
          +
          Row {index}

          );
          })

          export default class Index extends Component {
          state = {
          data: buildData(0),
          }

          render() {
          const { data } = this.state
          const dataLen = data.length
          return (
          height={500} // 列表的高度
          width='100%' // 列表的寬度
          itemData={data} // 渲染列表的數(shù)據(jù)
          itemCount={dataLen} // 渲染列表的長度
          itemSize={100} // 列表單項的高度
          + unlimitedSize={true} // 解開列表節(jié)點大小限制
          >
          {Row} // 列表單項組件,這里只能傳入一個組件

          );
          }
          }

          可以看到,我們在新增了 id 傳入來幫助獲取每個節(jié)點在首次加載之后讀取它的真實大小,得益于 Taro 跨平臺的優(yōu)勢,這是重構(gòu)虛擬列表組件中最簡單的一步,有了這個基礎(chǔ),我們就可以將節(jié)點的實際大小和它們的位置信息關(guān)聯(lián)到一起,讓列表自己調(diào)整每個節(jié)點的位置,并呈現(xiàn)給用戶。

          而對于開發(fā)者,如果想要使用這個模式,只需要傳入 unlimitedSize 就可以讓虛擬列表解開高度限制。當(dāng)然這并不意味著在使用虛擬列表時可以不需要傳入節(jié)點大小, itemSize 在這個模式下將作為初始值輔助列表中每個節(jié)點位置信息的計算。

          如果itemSize和實際大小差別過大,在超長列表中會有較明顯的問題,大家需要小心使用哦~

          1.2 列表底部

          列表的底部區(qū)域可以幫助我們便捷地完成信息的展示,比如上拉加載等,對于虛擬列表也是如此。

          return (
          height={500} // 列表的高度
          width='100%' // 列表的寬度
          itemData={data} // 渲染列表的數(shù)據(jù)
          itemCount={dataLen} // 渲染列表的長度
          itemSize={100} // 列表單項的高度
          + renderBottom={我就是底線}
          >
          {Row} // 列表單項組件,這里只能傳入一個組件

          );

          當(dāng)然也有同學(xué)會注意到,在 虛擬列表 文檔中是通過 scrollOffset > ((dataLen - 5) * itemSize + 100) 這樣的方法來判斷是否觸底,這是因為我們并沒有在 VirtualList 中返回滾動的詳細(xì)信息,這次我們也返回相關(guān)的數(shù)據(jù),幫助大家更好地使用虛擬列表。

          interface VirtualListEvent {
          /** 滾動方向,可能值為 forward 往前, backward 往后。*/
          scrollDirection: 'forward' | 'backward'
          /** 滾動距離 */
          scrollOffset: number
          /** 當(dāng)滾動是由 scrollTo() 或 scrollToItem() 調(diào)用時返回 true,否則返回 false */
          scrollUpdateWasRequested: boolean
          /** 當(dāng)前只有 React 支持 */
          + detail?: {
          + scrollLeft: number
          + scrollTop: number
          + scrollHeight: number
          + scrollWidth: number
          + clientWidth: number
          + clientHeight: number
          + }
          }

          1.3 性能優(yōu)化

          在虛擬列表中,無論是使用那種布局方式,都會造成頁面的回流,所以不論選擇哪一種對于瀏覽器內(nèi)核渲染頁面而言并沒有很大的區(qū)別。但是如果使用 relative,對于列表來說,需要調(diào)整的節(jié)點樣式要少得多。所以我們在新的虛擬列表中也支持了這樣的定位模式,供開發(fā)者自由選擇。對于低端機型來說,在我們完成整體的渲染性能優(yōu)化之前,relative 模式已經(jīng)能夠讓虛擬列表在低端機型上擁有不錯的體驗。

          2. 渲染性能優(yōu)化

          Taro3 使用小程序的 template 進(jìn)行渲染,一般情況下并不會使用原生自定義組件。這會導(dǎo)致一個問題,所有的 setData 更新都是由頁面對象調(diào)用,如果我們的頁面結(jié)構(gòu)比較復(fù)雜,更新的性能就會下降。

          層級過深時 setData 的數(shù)據(jù)結(jié)構(gòu):

          page.setData({
          "root.cn.[0].cn.[0].cn.[0].cn.[0].markers": []
          })

          針對這個問題,主要的思路是借用小程序的原生自定義組件,以達(dá)到局部更新的效果,從而提升更新性能。

          期望的 setData 數(shù)據(jù)結(jié)構(gòu):

          component.setData({
          "cn.[0].cn.[0].markers": []
          })

          開發(fā)者有兩種辦法可以實現(xiàn)這個優(yōu)化:

          2.1 全局配置項 baseLevel

          對于不支持模板遞歸的小程序(微信、QQ、京東小程序),在 DOM 層級達(dá)到一定數(shù)量后,Taro 會使用原生自定義組件協(xié)助遞歸。

          簡單理解就是 DOM 結(jié)構(gòu)超過 N 層后,會使用原生自定義組件進(jìn)行渲染。N 默認(rèn)是 16 層,可以通過修改配置項 baseLevel[2] 修改 N。

          baseLevel 設(shè)置為 8 甚至 4 層,能非常有效地提升更新時的性能。但是設(shè)置是全局性的,會帶來若干問題:

          1. flex 布局在跨原生自定義組件時會失效,這是影響最大的一個問題。
          2. SelectorQuery.select 方法的跨自定義組件的后代選擇器[3]寫法需要增加 >>>.the-ancestor >>> .the-descendant

          2.2 CustomWrapper 組件

          為了解決全局配置不靈活的問題,我們增加了一個基礎(chǔ)組件 CustomWrapper。它的作用是創(chuàng)建一個原生自定義組件,對后代節(jié)點的 setData 將由此自定義組件進(jìn)行調(diào)用,達(dá)到局部更新的效果。

          開發(fā)者可以使用它去包裹遇到更新性能問題的模塊,提升更新時的性能。因為 CustomWrapper 組件需要手動使用,開發(fā)者能夠清楚“這層使用了自定義組件,需要避免自定義組件的兩個問題”。

          例子

          <GoodsList>
          <Item />
          <Item />
          // ...
          GoodsList>

          </CustomWrapper>

          十全十美 - 體驗評分平均 95+

          把開發(fā)者工具的體驗評分給拉滿,這里我們遇到了一個問題,開發(fā)者工具會識別所有綁定了點擊事件的組件,如果組件的面積過小則提示點擊區(qū)域過小,會影響“體驗項”的評分。但是 Taro3 默認(rèn)會為組件綁定上所有屬性和事件[4]。這樣會“誤傷”一些組件,它們雖然面積很小,實際上并沒有點擊功能,但因為 Taro3 默認(rèn)綁定的事件,被開發(fā)者工具認(rèn)為點擊區(qū)域過小,從而拉低體驗評分。

          Text 組件的模板,默認(rèn)綁定了所有屬性和事件:

          <template name="tmpl_0_text">
          <text
          selectable="{{...}}"
          space="{{...}}"
          decode="{{...}}"
          user-select="{{...}}"
          style="{{...}}"
          class="{{...}}"
          id="{{...}}"
          bindtap="..."
          >

          ...
          text>
          template>

          因此我們?yōu)?View、TextImage 組件各設(shè)立了一個 static 模板,當(dāng)檢測到組件沒有綁定事件時,則使用 static 模板,避免被“誤傷”。

          另一方面,這一舉動也能減少小程序 DOM 綁定的事件,對性能稍有提升,而且減少了屬性讓開發(fā)者工具的 xml 面板在調(diào)試時更加清晰。但這一方案也存在瑕疵,會導(dǎo)致編譯后的 base.wxml 體積略微增大,和性能權(quán)衡來看,這仍然是值得的。

          Text 組件的 static 模板,沒有綁定事件:

          <template name="tmpl_0_static-text">
          <text
          selectable="{{...}}"
          space="{{...}}"
          decode="{{...}}"
          user-select="{{...}}"
          style="{{...}}"
          class="{{...}}"
          id="{{...}}"
          >

          ...
          text>
          template>
          優(yōu)化后的購物車頁體驗評分

          另一個戰(zhàn)場 - 多端適配&原生混合

          適配京東小程序

          適配京東小程序的過程比較順利,需要改動的地方不多。

          在此過程中 Taro3 最主要的升級是增強了對 HTML 文本的解析能力,增加了對

          <del id="afajh"><form id="afajh"></form></del>
              1. <th id="afajh"><progress id="afajh"></progress></th>
                <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一区二区三区水蜜桃 | 操逼好爽视频 | 囯产精品久久久久久久久久免费 | 五月色综合 | 激情五月天影院 |