数据预取收藏我的收藏
收藏
我的收藏基础库 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
数组中的 key
和 key
对应的 value
是否相等,其他值不做比较。示例如下:
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 | 其中 -1 为默认值;1~8 为未匹配原因;9 为没有任何缓存;22,23 为已发起预取,但返回结果失败或返回结果为空;100 为命中缓存;101 为命中请求中缓存,并成功;102 为命中请求中缓存,请求失败,为网络错误。 | 2.18.0 |
prefetchInfo | string | object | 数据预取的结果的详细信息。 | 2.80.0 |
prefetchInfo 是对于预取失败原因的详细说明(prefetchInfo 仅在配置了数据预取且预取失败时存在),具体说明如下:
属性名 | 类型 | 说明 |
mismatchReason | string | 预取失败原因。 |
configurationValue | string | object | 未命中的配置数据,即根据配置生成的预取请求的参数,prefetchDeatil 为 1~8 之间才会有值,值为对应的项的取值。例:prefetchDeatil 为 1,configurationValue 为 GET。 |
actualValue | string | object | 实际请求数据,即用户发起 request 时根据传入参数生成的请求参数,prefetchDeatil 为 1~8 之间才会有值,值为对应的项的取值。例:prefetchDeatil 为 1,actualValue 为 POST |
actualValue 与 configurationValue 的对比是按照固定顺序的,前项数据匹配成功后才会对比后项数据,顺序为:host,path,query,method,data,cookie,header,reponseType。
详细参数对应表:
prefetchDetail等对应关系
prefetchDetail | mismatchReason | configurationValue | actualValue |
-1 | null(不使用预取缓存 usePrefetchCache 参数不传或为false、或当前版本不支持预取) | | |
0 | response is null or expired(无返回值或预取缓存数据过期) | | |
1 | method mismatch | ✅ | ✅ |
2 | header mismatch | ✅ | ✅ |
3 | responseType mismatch | ✅ | ✅ |
4 | data mismatch | ✅ | ✅ |
5 | cookie mismatch | ✅ | ✅ |
6 | host mismatch | ✅ | ✅ |
7 | path mismatch | ✅ | ✅ |
8 | query mismatch | ✅ | ✅ |
9 | no prefetch cache | | |
10 | response is expired(预取结果过期,有效时间5min) | | |
20 | no request(匹配成功,但是没有找到对应的 response) | | |
22 | prefetch net error: <throwbale.localMessage> 已发起预取,结果回来,预取结果失败(依赖网络库的结果) | | |
23 (iOS) | no data(已发起预取,结果回来,数据为空) | | |
100 | null(命中预取,发起预取时成功结果已在本地) | | |
101 | null(已发起预取,结果还没回来,复用结果,结果成功) | | |
102 | null(已发起预取,结果还没回来,复用结果,结果失败,随 request API 错误返回) | | |
Bug & Tip
- •Tip:只会触发当次启动首页(start_page)的数据预取配置,如果 start_page 为空,会触发默认入口页的数据预取配置。预取请求的缓存可以被用于非首页请求,缓存在当次启动生效;
- •Tip:
prefetchRules
配置中的变量在 url 中只有 query 和 path 部分有效,host 部分不支持配置,第一个变量与 host 之间至少要有一个 /
或 ?
间隔。例如:developer.bytedance.com${id}
形式的 url 不支持预取;- •Tip:预请求
url
中参数处理逻辑与 tt.request
接口保持一致,这里需要注意tt.request
会对url
中参数做encodeURIComponent
,已encodeURIComponent
的参数不会重复 encode
;- •Tip:抖音 24.5 以上支持
data
模糊匹配字段 requiredDataKeys
;- •Tip:IDE 暂不支持该能力,请在真机测试。