数据预取
收藏我的收藏
基础库 2.18.0 开始支持本功能。
数据预取能够在小程序冷启动时提前发起请求,并缓存请求内容,在真实请求时使用缓存数据,可减少网络请求耗时。在文章详情页、商品详情页等高频访问页面配置数据预取,可提升页面加载速度。
以西瓜视频小程序为例,在接入数据预取后,线上 FMP 从 1173ms 优化到 818ms,整体优化 300+ms,速度提升幅度为 30%(为了更好的对比效果,下图为 0.5 倍速的启动对比)。
功能简介
通常来说,小程序获取网络数据是在首页 page 加载后使用
tt.request
来实现的。在运行时,小程序开发者可提前获取用户可能触发的下一步流程数据。小程序数据预取的主要功能是提前按照小程序配置规则发起网络请求并缓存,在小程序启动后直接使用缓存数据,以避免因等待网络数据返回导致的 loading 或页面显示不全,具体流程如下:使用方式
配置预取规则
数据预取规则需要在
app.json
中配置,示例如下:{ "prefetchRules": { // 需要配置预取请求的页面 "pages/index/index": { // 请求的url "https://developer.bytedance.com/${pageid}": { // 请求的方法 "method": "POST", // 请求的header "header": { "token": "${token}" }, // POST请求的body数据 "data": { "pageSize": "${pageSize}", "pageNum": 1 }, // 返回结果的类型 "responseType": "text", // 模糊匹配规则,只要包含以下key,并且key对应的值相等就算命中 "hitPrefetchExtraRules": { // 只要请求query中以下key的value相等,则匹配成功 "requiredQueryKeys": ["a", "b", "c"], // 只要请求header中以下key的value相等,则匹配成功 "requiredHeaderKeys": ["a", "token"], // 只要请求data中以下key的value相等,则匹配成功 "requiredDataKeys": ["a.b", "c"] } } } } }
配置变量
为了配置的请求可以动态化,我们引入了配置变量。配置变量的格式为
${variable}
,其中variable
是变量名。配置变量赋值的过程就是字符串替换,用变量的值替换 prefetchRules
配置字符串中的 ${variable}
。配置变量的数据源为小程序页面路由的
query
参数及开发者存储在storage
中的数据缓存,其中页面路由的query
参数优先级高于storage
中的数据缓存。关于query
和storage
两种数据源的优缺点,如下表所示: | 优点 | 缺点 |
query | 统一 | 无法针对单机,无法使用基础变量。 |
storage | 可以使用持久化的数据,可以使用运行时的数据, | 首次打开,变量不存在,无法使用。 |
请求地址
构成请求最基础的就是
url
,只要有完整 url
我们就可以发起请求了。目前我们支持 path
,query
部分的变量替换,host
目前不支持。当预取配置如下时,假设页面路由为
pages/index/index?pageid=1000
,开发者的 storage
中存储了pageSize
变量,值为10
,那么最终的预取请求将为 https://developer.bytedance.com/1000?pagesize=10&pagenum=1
。{ "prefetchRules": { "pages/index/index": { "https://developer.bytedance.com/${pageid}?pagesize=${pageSize}&pagenum=1": {} } } }
请求参数
请求地址的值为请求参数,它包含
method
,header
,data
和 responseType
,对应着 tt.request API 的各项参数,这些参数也支持配置变量。method
,header
,data
和 responseType
的默认值具体规则见下表:参数名 | 数据类型 | 必填 | 默认值 | 说明 |
header | object | 否 | {'content-type': 'application/json'} | 请求 header |
method | string | 否 | GET | 请求方法,支持 GET ,POST。 |
data | string | object | 否 | null | 请求数据,命中需完全一致(GET请求不支持,会改为空字符串)。 |
responseType | string | 否 | text | 响应数据类型,可选值:text。 |
举个例子,假设请求参数的配置如下:
{ "https://developer.bytedance.com/${pageid}": { "method": "POST", "header": { "token": "${token}" }, "data": { "pageSize": "${pageSize}", "pageNum": 1 } } }
若页面路由为
pages/index/index?pageid=1000
,开发者的 storage
中存储了pageSize
变量,值为10
,同时也存储了token
变量,值为test_token
,那么最终的预取请求的参数将如下所示:{ "https://developer.bytedance.com/1000": { "method": "POST", "header": { "token": "test_token" }, "data": { "pageSize": "10", "pageNum": 1 } } }
匹配规则
在判断缓存是否能复用时,系统支持精确匹配与模糊匹配两种策略。未配置
hitPrefetchExtraRules
时默认匹配规则为精确匹配,精确匹配会比较每一个配置,包括 host
,path
,query
,method
,header
等。如果请求中的冗余参数比较多,我们建议使用模糊匹配规则。下面我们来看示例配置中的最后一块,模糊匹配规则 hitPrefetchExtraRules
,可以进一步提升缓存的命中率。{ "https://developer.bytedance.com/${pageid}?pagesize=${pageSize}&pagenum=1": { "header": { "token": "${token}" }, "hitPrefetchExtraRules": { "requiredQueryKeys": ["pagesize", "pagenum"], "requiredHeaderKeys": ["token"], "requiredDataKeys": ["common.id", "common.info.pid"] } } }
requiredQueryKeys
、requiredHeaderKeys
、requiredDataKeys
规则描述:- 1.该字段的值为一个字符串数组。
- 2.该字段不存在时,命中规则为严格匹配,只有在 tt.request 请求的配置和预取请求的配置完全相同时才会命中。
- 3.当该字段存在时,在对比配置的 query、header、data 时,只会比较 requiredKeys 数组中的键值对是否相等,其他值不做比较。
示例如下:
query 参数匹配
// 预取请求 "https://developer.bytedance.com/1000?pagesize=10&pagenum=1" // 命中规则 { "hitPrefetchExtraRules" :{"requiredQueryKeys": ["pagesize", "pagenum"]} } // tt.request 请求 // 命中 "https://developer.bytedance.com/1000?pagesize=10&pagenum=1&other=1&other2=2" // 不命中,pagenum值不相等 "https://developer.bytedance.com/1000?pagesize=10&pagenum=2&other=1&other2=2" // 不命中,缺少pagesize "https://developer.bytedance.com/1000?pagenum=1&other=1&other2=2"
header 参数匹配
// 预取请求 "https://developer.bytedance.com/1000?pagesize=10&pagenum=1":{ header:{ token: "test_token", other: 1 } } // 命中规则 { "requiredHeaderKeys": ["token"] } // tt.request 请求 // 不命中,query 严格匹配,多了 other "https://developer.bytedance.com/1000?pagesize=10&pagenum=1&other=1" // 命中,other 为不影响判断的 key "https://developer.bytedance.com/1000?pagesize=10&pagenum=1":{ header:{ token: "test_token", other: 5 } } // 不命中,token的值不一致 "https://developer.bytedance.com/1000?pagesize=10&pagenum=1":{ header:{ token: "test", other: 1 } }
data 参数匹配
鉴于 data 字段的复杂性,在服务端使用中,常常会使用嵌套的 json,此时单层 KV 匹配的范围会偏大。在 requiredDataKeys 支持指定多层 key。
例:以下匹配规则表示,只需满足 data 字段中的 id 和 pid 两项的值相等,即可通过 data 字段的匹配。
// app.json 中配置的预取规则 { "https://developer.bytedance.com/${pageid}?pagesize=${pageSize}&pagenum=1": { "header": { "token": "${token}" }, "data": { "common": { "id":"${id}", "info":{ "pid":"${pid}", "purl":"${purl}" } } }, "hitPrefetchExtraRules": { "requiredQueryKeys": ["pagesize", "pagenum"], "requiredHeaderKeys": ["token"], "requiredDataKeys": ["common.id", "common.info.pid"] } } } // 端上解析出的预取请求 { ... "data": { "common": { "id":"aj123hgh32", "info":{ "pid":"k3k4h473ga", "purl":"https://www.aaa.com/a.png" } } } ... } // 如果 tt.request 为以下信息,则命中,purl 等其他 KV 不影响判断 { ... "data": { "common": { "id":"aj123hgh32", "info":{ "pid":"k3k4h473ga", "purl":"https://www.aaa.com/b.png", "kkk":"ppp" }, "aaa":"bbb" } } ... } // 如果 tt.request 为以下信息,则未命中,id不匹配 { ... "data": { "common": { "id":"123", "info":{ "pid":"k3k4h473ga" } } } ... } // 如果 tt.request 为以下信息,则未命中,pid不匹配 { ... "data": { "common": { "id":"aj123hgh32", "info":{ "pid":"123" } } } ... } //未命中,不支持数组形式 { ... "data":{ "common":[ { "id":"aj123hgh32", "info":{ "pid":"123" } } ] } ... } //未命中,根据"common",要匹配common对象所有字段,purl 不同 { ... "data":{ "common": { "id":"aj123hgh32", "info":{ "pid":"k3k4h473ga", "purl":"https://www.aaa.com/2/a.png" } } } ... }
使用预取缓存
tt.request 新增 usePrefetchCache 参数并在返回数据中添加 isPrefetch 字段区分数据来源,示例如下:
// app.json中配置的预取规则 { "prefetchRules": { "pages/index/index": { "https://developer1.bytedance.com?testid=${id}&testdata=${sData}": {}, "https://developer1.bytedance.com/${sid}?testid=${id}&testdata=${sData}": { "method": "POST", "header": { "testCookie": "${sCookie}", "token": "xxxs1823730" }, "data": { "mData": "${mData}" }, "responseType": "", "hitPrefetchExtraRules": { "requiredQueryKeys": ["testid", "testdata"], "requiredHeaderKeys": ["testCookie", "token"] } } } } }
//storage const testCookie = tt.getStorageSync("sCookie"); const mData = tt.getStorageSync("mData"); //request const token = "xxxs1823730"; const { sid, testid } = option.query; const url = `https://developer1.bytedance.com/${sid}?testid=${testid}&testdata=${sData}`; const header = { testCookie, token }; const data = { mData }; tt.request({ url, header, data, method: "POST", dataType: "json", responseType: "text", // 如果需要使用预取结果缓存,必须添加 usePrefetchCache: true usePrefetchCache: true, success: (res) => { console.log("返回数据是否来自预取:", res.isPrefetch); console.log("请求数据:", res.data); }, });
预取的返回值
为降低数据预取的接入难度,减少开发者定位未命中问题的成本,目前将数据预取的返回结果统一放于 tt.request API 的返回值中。
tt.request API 的返回值中与数据预取相关的返回参数:
属性名 | 类型 | 说明 | 最低支持版本 |
isPrefetch | boolean | 是否使用预取缓存 | 2.18.0 |
prefetchDetail | number |
| 2.18.0 |
prefetchInfo | string | object | 数据预取的结果的详细信息。 | 2.80.0 |
prefetchInfo 是对于预取失败原因的详细说明(prefetchInfo 仅在配置了数据预取且预取失败时存在),具体说明如下: