Taro 預(yù)渲染(PreRender)

2021-09-23 21:10 更新

Prerender 是由 Taro CLI 提供的在小程序端提高頁(yè)面初始化渲染速度的一種技術(shù),它的實(shí)現(xiàn)原理和服務(wù)端渲染(Server-side Rendering)一樣:將頁(yè)面初始化的狀態(tài)直接渲染為無(wú)狀態(tài)(dataless)的 wxml,在框架和業(yè)務(wù)邏輯運(yùn)行之前執(zhí)行渲染流程。經(jīng)過(guò) Prerender 的頁(yè)面初始渲染速度通常會(huì)和原生小程序一致甚至更快。

為什么需要 Prerender?

Taro Next 在一個(gè)頁(yè)面加載時(shí)需要經(jīng)歷以下步驟:

  1. 框架(React/Nerv/Vue)把頁(yè)面渲染到虛擬 DOM 中
  2. Taro 運(yùn)行時(shí)把頁(yè)面的虛擬 DOM 序列化為可渲染數(shù)據(jù),并使用 setData() 驅(qū)動(dòng)頁(yè)面渲染
  3. 小程序本身渲染序列化數(shù)據(jù)

和原生小程序或編譯型小程序框架相比,步驟 1 和 步驟 2 是多余的。如果頁(yè)面的業(yè)務(wù)邏輯代碼沒(méi)有性能問(wèn)題的話,大多數(shù)性能瓶頸出在步驟 2 的 setData() 上:由于初始化渲染是頁(yè)面的整棵虛擬 DOM 樹,數(shù)據(jù)量比較大,因此 setData() 需要傳遞一個(gè)比較大的數(shù)據(jù),導(dǎo)致初始化頁(yè)面時(shí)會(huì)一段白屏的時(shí)間。這樣的情況通常發(fā)生在頁(yè)面初始化渲染的 wxml 節(jié)點(diǎn)數(shù)比較大或用戶機(jī)器性能較低時(shí)發(fā)生。

使用 Prerender

使用 Prerender 非常簡(jiǎn)單,你可以找到項(xiàng)目根目錄下的 config 文件夾,根據(jù)你的項(xiàng)目情況更改 index.js/dev.js/prod.js 三者中的任意一個(gè)項(xiàng)目配置,在編譯時(shí) Taro CLI 會(huì)根據(jù)你的配置自動(dòng)啟動(dòng) prerender:

  1. // /project/config/prod.js
  2. const config = {
  3. ...
  4. mini: {
  5. prerender: {
  6. match: 'pages/shop/**', // 所有以 `pages/shop/` 開頭的頁(yè)面都參與 prerender
  7. include: ['pages/any/way/index'], // `pages/any/way/index` 也會(huì)參與 prerender
  8. exclude: ['pages/shop/index/index'] // `pages/shop/index/index` 不用參與 prerender
  9. }
  10. }
  11. };
  12. module.exports = config

完整 Prerender 配置可參看下表:

參數(shù) 類型 默認(rèn)值 必填 說(shuō)明
match string string[] glob 字符串或 glob 字符串?dāng)?shù)組,能匹配到本參數(shù)的頁(yè)面會(huì)加入 prerender
include Array Array [] ?頁(yè)面路徑與數(shù)組中字符串完全一致的會(huì)加入 prerender
exclude string[] [] ?頁(yè)面路徑與數(shù)組中字符串完全一致的不會(huì)加入 prerender
mock Record 在 prerender 環(huán)境中運(yùn)行的全局變量,鍵名為變量名,鍵值為變量值
console boolean false 在 prerender 過(guò)程中 console 打印語(yǔ)句是否執(zhí)行
transformData Function 自定義虛擬 DOM 樹處理函數(shù),函數(shù)返回值會(huì)作為 transformXML 的參數(shù)
transformXML Function 自定義 XML 處理函數(shù),函數(shù)返回值是 Taro 運(yùn)行時(shí)初始化結(jié)束前要渲染的 wxml

在表中有用到的類型:

  1. // PageConfig 是開發(fā)者在 prerender.includes 配置的頁(yè)面參數(shù)
  2. interface PageConfig {
  3. path: string // 頁(yè)面路徑
  4. params: Record<string, unknown> // 頁(yè)面的路由參數(shù),對(duì)應(yīng) `getCurrentInstance().router.params`
  5. }
  6. // DOM 樹數(shù)據(jù),Taro 通過(guò)遍歷它動(dòng)態(tài)渲染數(shù)據(jù)
  7. interface MiniData {
  8. ["cn" /* ChildNodes */]: MiniData[]
  9. ["nn" /* NodeName */]: string
  10. ["cl" /* Class */]: string
  11. ["st" /* Style */]: string
  12. ["v" /* NodeValue */]: string
  13. uid: string
  14. [prop: string]: unknown
  15. }
  16. type transformData = (data: MiniData, config: PageConfig) => MiniData
  17. type transformXML = (
  18. data: MiniData,
  19. config: PageConfig,
  20. xml: string // 內(nèi)置 xml 轉(zhuǎn)換函數(shù)已經(jīng)處理好了的 xml 字符串
  21. ) => string

Prerender 的所有配置選項(xiàng)都是選填的,就多數(shù)情況而言只需要關(guān)注 match、include、exclude 三個(gè)選項(xiàng),matchinclude 至少填寫一個(gè)才能匹配到預(yù)渲染頁(yè)面,三者可以共存,當(dāng)匹配沖突時(shí)優(yōu)先級(jí)為 match < include < exclude。

和所有技術(shù)一樣,Prerender 并不是銀彈,使用 Prerender 之后將會(huì)有以下的 trade-offs 或限制:

  • 頁(yè)面打包的體積會(huì)增加。Prerender 本質(zhì)是一種以空間換時(shí)間的技術(shù),體積增加的多寡取決于預(yù)渲染 wxml 的數(shù)量。
  • 在 Taro 運(yùn)行時(shí)把真實(shí) DOM 和事件掛載之前(這個(gè)過(guò)程在服務(wù)端渲染被稱之為 hydrate),預(yù)渲染的頁(yè)面不會(huì)相應(yīng)任何操作。
  • Prerender 不會(huì)執(zhí)行例如 componentDidMount()(React)/ready()(Vue) 這樣的生命周期,這點(diǎn)和服務(wù)端渲染一致。如果有處理數(shù)據(jù)的需求,可以把生命周期提前到 static getDerivedStateFromProps()(React) 或 created()(Vue)。

進(jìn)階說(shuō)明和使用

PRERENDER 全局變量

在預(yù)渲染容器有一個(gè)名為 PRERENDER 的全局變量,它的值為 true。你可以通過(guò)判斷這個(gè)變量是否存在,給預(yù)渲染時(shí)期單獨(dú)編寫業(yè)務(wù)邏輯:

  1. if (typeof PRERENDER !== 'undefined') { // 以下代碼只會(huì)在預(yù)渲染中執(zhí)行
  2. // do something
  3. }

disablePrerender

對(duì)于任意一個(gè)原生組件,如果不需要它在 Prerender 時(shí)中顯示,可以把組件的 disablePrerender 屬性設(shè)置為 true,這個(gè)組件和它的子孫都不會(huì)被渲染為 wxml 字符串。

  1. /* id 為 test 的組件和它的子孫在預(yù)渲染時(shí)都不會(huì)顯示 */
  2. <View id="test" disablePrerender>
  3. ...children
  4. View>

自定義渲染

當(dāng)默認(rèn)預(yù)渲染的結(jié)果不滿足你的預(yù)期時(shí),Taro 提供了兩個(gè)配置項(xiàng)自定義預(yù)渲染內(nèi)容。

Prerender 配置中的 transformData() 對(duì)需要進(jìn)行渲染的虛擬 DOM 進(jìn)行操作:

  1. const config = {
  2. ...
  3. mini: {
  4. prerender: {
  5. match: 'pages/**',
  6. tranformData (data, { path }) {
  7. if (path === 'pages/video/index') {
  8. // 如果是頁(yè)面是 'page/video/index' 頁(yè)面只預(yù)渲染一個(gè) video 組件
  9. // 關(guān)于 data 的數(shù)據(jù)結(jié)構(gòu)可以參看上文的數(shù)據(jù)類型簽名
  10. data.nn = 'video'
  11. data.cn = []
  12. data.src = 'https://v.qq.com/iframe/player.html?vid=y08180lrvth&tiny=0&auto=0'
  13. return data
  14. }
  15. return data
  16. }
  17. }
  18. }
  19. }

Prerender 配置中的 transformXML() 可以自定義預(yù)渲染輸出的 wxml:

  1. const config = {
  2. ...
  3. mini: {
  4. prerender: {
  5. match: 'pages/**',
  6. tranformXML (data, { path }, xml) {
  7. if (path === 'pages/video/index') {
  8. // 如果是頁(yè)面是 'page/video/index' 頁(yè)面只預(yù)渲染一個(gè) video 組件
  9. return ``
  10. }
  11. return xml
  12. }
  13. }
  14. }
  15. }

減少預(yù)渲染的 wxml 數(shù)量

一般而言,用戶只需要看到首屏頁(yè)面,但實(shí)際上頁(yè)面初次渲染的我們構(gòu)建的業(yè)務(wù)邏輯有可能會(huì)把頁(yè)面的所有內(nèi)容都渲染,而 Taro 初始渲染慢的原因在于首次傳遞的數(shù)據(jù)量過(guò)大,因此可以調(diào)整我們的業(yè)務(wù)邏輯達(dá)到只渲染首屏的目的:

  1. class SomePage extends Component {
  2. state = {
  3. mounted: false
  4. }
  5. componentDidMount () {
  6. // 等待組件載入,先渲染了首屏我們?cè)黉秩酒渌鼉?nèi)容,降低首次渲染的數(shù)據(jù)量
  7. // 當(dāng) mounted 為 true 時(shí),CompA, B, C 的 DOM 樹才會(huì)作為 data 參與小程序渲染
  8. // 注意我們需要在 `componentDidMount()` 這個(gè)周期做這件事(對(duì)應(yīng) Vue 的 `ready()`),更早的生命周期 `setState()` 會(huì)與首次渲染的數(shù)據(jù)一起合并更新
  9. // 使用 nextTick 確保本次 setState 不會(huì)和首次渲染合并更新
  10. Taro.nextTick(() => {
  11. this.setState({
  12. mounted: true
  13. })
  14. })
  15. }
  16. render () {
  17. return <View>
  18. <FirstScreen /> /* 假設(shè)我們知道這個(gè)組件會(huì)把用戶的屏幕全部占據(jù) */
  19. {this.state.mounted && <React.Fragment> /* CompA, B, C 一開始并不會(huì)在首屏中顯示 */
  20. <CompA />
  21. <CompB />
  22. <CompC />
  23. React.Fragment>}
  24. View>
  25. }
  26. }

這樣的優(yōu)化除了加快首屏渲染以及 hydrate 的速度,還可以降低 Prerender 的所增加的 wxml 體積。當(dāng)你的優(yōu)化做得足夠徹底時(shí),你會(huì)發(fā)現(xiàn)多數(shù)情況下并不需要 Prerender。


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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)