
你 和一頭駱駝準備穿過沙漠,前面是一眼望不到頭的沙海,你的目的是要穿過 沙漠到達對面的綠洲。 現在你寫的每一行代碼就是往駱駝上負重。 當然,有些 負重是 必須 的,比如水和食物。
可能會由于負重太多,駱駝嚴重拖累行程,甚至可能永遠走不出沙漠。
可能水和食物不夠,都被餓死在中途。
本文目錄:
-
有必要重申的交互原理
-
請慎重設置 data
-
請三思組件間如何通信
-
友好的使用 sass
-
不可或缺的 template & @import
-
理解多頁應用的單頁應用
-
第三方庫
-
請善待主包
小程序有包大小限制,單個主包或者子包不能超過 2M,總包不能高于 12M。
有大小限制,那么對于超大型小程序,就要悠著點了,否則超包那就是家常便飯了,當然小程序這個遠不及瀏覽器的內存和性能也提醒著我們要悠著點。
現在,請開始給駱駝負重?。?!
有必要重申的交互原理
小程序的 Page 給我們提供了一個 data 對象。這個 data 對象的數據更新,則直接影響 wxml 的內容。有 vue 或者 react 開發經驗的同學可能再熟悉不過了。當然小程序我們需要使用 setData 來更新數據。
Page({
data: {
text: "This is page data."
},
onLoad: function(options) {
// doSomething
}
});
這是官網上的描述:
小程序的視圖層目前使用 WebView 作為渲染載體,而邏輯層是由獨立的 JavascriptCore 作為運行環境。在架構上,WebView 和 JavascriptCore 都是獨立的模塊,并不具備數據直接共享的通道。當前,視圖層和邏輯層的數據傳輸,實際上通過兩邊提供的 evaluateJavascript 所實現。即用戶傳輸的數據,需要將其轉換為字符串形式傳遞,同時把轉換后的數據內容拼接成一份 JS 腳本,再通過執行 JS 腳本的形式傳遞到兩邊獨立環境。
簡而言之,js 邏輯層的 data 的數據是需要轉換成字符串,再使用 JS 腳本傳輸到 wxml 的視圖層。文檔也明確了傳輸不實時。實際上如果數據量大還很慢。
請慎重設置 data
data 對象是類似 vue 的 data,是 reactivity 的。vue 的 reactivity 的數據,是都需要 Object.defineProperty(或者 Proxy)的。
比如看到有代碼,在 data 里面放大量的數據,而這些數據根本上在 wxml 里面用不上,僅僅是為了存儲一個對象,在其他文件模塊(同 this)的時候,能直接用。還看到在 data 里面設置 isLoading 對象,這個僅僅是為了在點擊的時候做一個鎖,等接口調用完成,再把 isLoading 改為 false。
以上情況,在小程序代碼中比比皆是。這些都有一個通病,忽略了性能,隨心使用 data,導致一個頁面,在 data 下設置的變量可能占滿一屏的高度。
那我們如何做呢?
原則:只在 data 對象設置在 wxml 需要使用的變量。
那么其他變量我們怎么做呢?有兩種方案:
1、設置在實例作用域外,脫離 Page 或者 Component 對象。
let name = "is file scope data";
Page({});
2、設置在實例作用域內,但是脫離 data 對象,不需要 reactivity。
a) 如果是 Page ,可以直接新增一個 customData,
Page({
data:{
name:"reactivity data"
},
customData: {
name: 'not reactivity data'
}.
});
b) 如果是 Component,可以使用 pureData。
Component({
options: {
pureDataPattern: /^_/ // 指定所有 _ 開頭的數據字段為純數據字段
},
data: {
name: "is reactivity",
_name: "is pureData",
},
經過實驗證明,對于邏輯復雜,數據量大的頁面,很好的優化你的 data,對性能提升比較明顯。
請三思組件間如何通信
組件間通信方式:
-
子組件向父組件:triggerEvent
-
父組件向子組件:props
-
父組件向子組件:selectComponent
-
訂閱發布模式:emit、on
當前前三者,都是小程序直接提供的方式,第四種我們需要寫一個公共的 emitter 組件。下面我們來分析下這種情況。
1.triggerEvent
子組件向父組件通信,這種在開發中非常常見,子組件響應了操作,如果需要同步給父組件,直接用 this.triggerEvent,然后在父組件中定義 bind 就行。
2. props
父組件向子組件傳參,這種也非常常見,如果你需要傳參給子組件,需要做如下兩步:
1、父組件需要在 reactivity 的 data 中設置數據;
2、子組件需要在 props 屬性中設置接收該數據對象。
發現沒有,如果你需要傳一個參數,那么你需要在父子組件當中都設置為 reactivity 的 data 對象。顯然,對于大量的傳輸數據對象(比如一個大型數據列表),不適合直接這樣傳參。因為如果這樣做的話,顯然增大了父子組件成本。
3. selectComponent
父組件直接調用子組件實例對象,然后直接執行子組件的方法,比如:
子組件:
Component({
methods: {
updateStatus() {}
}
});
父組件:
this.selectComponent("#childComponent").updateStatus();
selectComponent,完美的解決了 props 傳參的問題,微信給你另一條道路,也說明了 props 的問題。但是 props 傳參方式更符合開發習慣和數據流思維。
個人建議,大數據傳輸,直接用 selectComponent,反之用 props。
4. $emit
發布訂閱模式,大伙都很熟悉了。注冊 on("key", () => {}),發布 emit("key", data)。這種方式的優點很明顯,完全突破組件直接的關系。任何地方都可以監聽,都可以發布。
缺點則是無狀態的,和組件實例無關,且是全局的。
如果頁面打開了兩個 Page,比如 商祥 -> 店鋪 -> 商祥
,這個時候,如果商詳接受到一個消息,兩個商祥 Page 都會收到。
發布訂閱模式需要注意四點:
-
key 是全局的,好好命名。
-
接收消息的區分是不是該實例的。
-
銷毀注冊消息通常是全部的。A 監聽 key,B 監聽 key,如果執行 remove(key),則 key 都會清除。
-
如果不支持粘性事件則需要關注發布訂閱時機。
(當然你可以選擇不把 EventBus 放到全局)
友好的使用 sass
樣式文件,我們一般使用預處理,比如 sass。我們看如下例子。
<view class="recommend">
<view class="recommend_header">
<view class="recommend_header_author">
<view class="recommend_header_author_img"></view>
<view class="recommend_header_author_info">
<view class="recommend_header_author_info_name"></view>
<view class="recommend_header_author_info_location"></view>
</view>
</view>
<view class="recommend_header_follow"></view>
</view>
<view class="recommend_footer">
</view>
</view>
.recommend { &_header { &_author { &_img { } &_info { &_name { } } } } }
上述結構和樣式,看不出啥,我們經常在 H5 頁面的時候都是如此做的,按照頁面模塊和層級定義結構,使用 sass 逐層寫樣式,這可以說是標準寫法,語義和結構清晰,
但是在超大型小程序中,我要 say No!
我們知道,sass 是 css 預處理器,最終是需要轉換成 css 被瀏覽器和微信小程序識別。
我們看下轉換后的樣式文件
.recommend {
}
.recommend .recommend_header {
}
.recommend .recommend_header .recommend_header_author {
}
.recommend .recommend_header .recommend_header_author {
}
.recommend
.recommend_header
.recommend_header_author
.recommend_header_author_info {
}
.recommend
.recommend_header
.recommend_header_author
.recommend_header_author_info
.recommend_header_author_info_name {
}
看了之后有沒有覺得你哪里不對?重復的 class 太多了,看到這么多重復的,JS 開發人員第一反應:提煉抽取才是正道。想想我們大幾十行研發人員開發的小程序,功能如此之多,體積如此之珍貴,豈能如此浪費。
回歸到 sass,我們使用樣式層級作用域的目的是啥呢?我認為無非是絕對標識該樣式,類似 js 的作用域,不被其他模塊的樣式影響,然后能夠清晰的定義樣式。
試想,如果張三在頁面 header 模塊的用戶昵稱,定義成 .name,那么李四在 content 模塊的用戶昵稱也可能會被定義成 .name,然而這兩個樣式完全不一樣,但是樣式就會相互影響,李四把 .name 樣式寫好了,然而 header 模塊的 .name 樣式又不對了,這顯然不是我們想要的,所以推薦把樣式的名稱按層級定義,不會被影響。
理由很充分,sass 寫起來也很舒服,但是在實際中極力不推薦嚴格按這種層級定義。
那么在大型小程序中,推薦: 最多三級,建議兩級。
對應上述 wxml ,在結構不變的情況下,樣式修正為如下:
<view class="recommend">
<view class="recommend_header">
<view class="recommend_author">
<view class="recommend_img"></view>
<view class="recommend_info">
<view class="recommend_name"></view>
<view class="recommend_location"></view>
</view>
</view>
<view class="recommend_follow"></view>
</view>
<view class="recommend_footer">
</view>
</view>
有人會說了,這個 header 模塊用戶昵稱可能是 .recommend_name,但是 footer 模塊也有一個用戶昵稱怎么辦?
通常一個模塊是一個人維護,就算多個人修改,那么樣式也只在當前模塊內影響,風險完全可控。那么 footer 模塊的昵稱可以定義成 .recommend_footer_name,增加到第三層,甚至于我推薦直接是 .recommend_footer-name,這樣在解析成 css 的時候,仍然是兩層。
都 21 世紀了,總想著再不斷的出現愛因斯坦、牛頓等已經不太現實了。我們要 “微優化”!
總結:在可讀性仍然很強大的情況下,保證模塊直接樣式不沖突,建議控制在兩層 sass,最多不超過三層。減少文件大小。
在模塊多的頁面中,這種帶來 wxml 和 css 體積的縮減其實是很可觀的。
template & @import
根據頁面劃分組件,大伙都會做。然而實際上在模塊中會存在很多共同的結構,但是有時候我們因為邏輯較少等原因沒必要抽離成一個組件,這個時候 template 就派上大用場了。
單個文件中的共同結構
<template name="liveItem"> </template>
<template is="liveItem" data="{{list:preLiveList}}" />
<template is="liveItem" data="{{list:liveList}}" />
多個模塊中的共同結構
<import src="./template.wxml" />
<template is="goodsItem" data="{{item}}" />
<template is="goodsItem" data="{{item}}" />
當然,wxml 抽離成模塊引入了,那么 css 自然也需要抽離成公共的文件。
@import "../../common/common.wxss";
按照經驗,一個呈現稍微復雜一點的頁面,如果沒有存在 template 和 公共引入 css 文件,大概率重復代碼不少。當然,如果你很樂意把組件拆的足夠細的話(component 渲染性能慢的問題指南),那么也是可以規避這個問題,但是實際上可能有些代碼量反而還增多了, 合理 template 才是正確姿勢。
理解多頁應用的單頁應用
小程序是一個多頁面應用,各個模塊獨立開發,整個系統是多個 Pages 組成的,但是和我們常規的 H5 不同,訪問多個頁面之后,這些頁面是共存的。
訪問 A 頁面 -> 訪問 B 頁面,此時 A 頁面是仍然在運行,只不過是 hide 的。
既然多個頁面都存在,但是當前 active 的只有一個頁面,因此我們仍然要做一些單頁面應用需要做的事,比如:
1、停止更新數據 直播的點贊動畫,如果當前頁面不是 onShow 的,就不要去更新動畫了,節省點 CPU 吧!
2、及時回收定時器 在 A 頁面開啟了定時器,顯示倒計時,但是到 B 頁面了,A 頁面的倒計時就沒有意義了,應該及時回收和清除。當然如果你需要在頁面返回到 A 頁面的時候,仍然能看到倒計時,那么請在 onShow 的時候喚醒倒計時,onHide 的時候清除倒計時。只管利用,不善后的可不是好學生哦!
3、及時清除不必要的資源。當前訪問 A 頁面,然后返回,再次訪問 A 頁面,那么頁面的非實例對象數據是不會清除的,我們需要手動清除對象,釋放資源,防止重復注冊等操作。
第三方庫
第三方庫用起來爽呀,一個看起來不簡單的三下五除二就搞定了,能不爽嗎?
比如我們使用 vue 開發頁面,有輪播圖,引用第三方 swiper,我們需要做時間處理,引用 moment。
本人開發這么多頁面和系統,這些組件很少引用,我的做法是明確我的需求,如果有類似的,然后不會寫的,那么閱讀下源碼,自己寫下就行。因為我知道,開發的組件都是支持最全面的,實際上你根本不需要這么多,比如格式化時間,你的需求可能就是實際十幾行代碼的事情,為啥要引入一個第三方組件呢?同理一個 swiper,別人支持的各種各種需求,你的可能只是需要普通的滑動切換就行了,為啥要引入強大的第三方組件呢?
當然如果第三方組件可以支持良好的 tree-shaking 就另當別論。
在 H5 時代,我一般不輕易引入第三方庫,對于他人代碼我也是持保留意見。
但是在大型小程序時代,我是堅決反對用啥直接引用啥。畢竟 【體積 & 內存 & 性能】三座大山擺在眼前。
請善待主包
如果直接打開子包鏈接,那么是需要下載 主包 + 當前子包,然后才能訪問頁面。那么主包的下載對頁面首屏的打開,就顯得至關重要了。
按照我們 H5 開發的邏輯,公共代碼都需要抽離成獨立的模塊,方便共用。但是小程序場景有點不一樣。
A 子包 require goodsModule
B 子包 require goodsModule
這個時候,將 goodsModule 抽離出來,放在主包(因為子包直接相互引用的問題)??雌饋硪磺许樒渥匀?。
然而,如果你遵循上面的方案,那么你會發現,在超大型的小程序中,會有大量的 Modules 被放到主包,導致主包嚴重不足。那如何解決這個問題?我們可以使用 NPM 功能。
思想仍然會是模塊單獨下載,訪問了 A 頁面,下載 A 子包,然后再訪問 B 頁面,下載 B 子包。盡管 A 和 B 子包有重復下載的代碼。
那什么情況放在主包呢?
1、主包 Pages 已經有引用了
2、至少有三個及以上的子包引用同樣的模塊
善待主包,兼顧總包!