|
| 1 | +--- |
| 2 | +title: 半编译模式 |
| 3 | +--- |
| 4 | + |
| 5 | +:::info |
| 6 | +Taro v3.6.23 开始支持,目前只支持 **React**,暂不支持 Vue。底层实现原理请参考 [RFC 文档](https://github.com/NervJS/taro/discussions/14708)。 |
| 7 | +::: |
| 8 | + |
| 9 | +在节点数量增多到一定量级时,Taro3 的渲染性能会大幅下降,出现白屏时间长、交互延时等问题。经排查发现是目前 Taro 的 `<template>` 模板语法所造成的,为此我们参考 Taro 1/2 的思路,提供了 **CompileMode** 渲染模式。`CompileMode` 适合长列表 Item 这类会被重复渲染多次的组件使用,在长列表场景能提升 **30%** 以上的首开速度,同时能有效减少节点过多时产生的交互延时问题。CompileMode 可以说是应对复杂页面性能优化的“银弹”。 |
| 10 | + |
| 11 | +## 使用方法 |
| 12 | + |
| 13 | +首先在 Taro 编译配置中开启使用半编译模式: |
| 14 | + |
| 15 | +```js title="config/index.js" |
| 16 | +const config = { |
| 17 | + mini: { |
| 18 | + experimental: { |
| 19 | + compileMode: true |
| 20 | + } |
| 21 | + } |
| 22 | + // ... |
| 23 | +} |
| 24 | +``` |
| 25 | + |
| 26 | +然后只需要给 Taro 基础组件添加 `compileMode` 属性,该组件及其 children 将会被编译为单独的小程序模板: |
| 27 | + |
| 28 | +```jsx |
| 29 | +function GoodsItem () { |
| 30 | + return ( |
| 31 | + <View compileMode> |
| 32 | + ... |
| 33 | + </View> |
| 34 | + ) |
| 35 | +} |
| 36 | +``` |
| 37 | + |
| 38 | +更为详细的用法请看 [详细用法](#详细用法) |
| 39 | + |
| 40 | +## 常见问题 |
| 41 | + |
| 42 | +### 1. 编译出的模板文件会增加包体积 |
| 43 | + |
| 44 | +半编译模式使用了空间来换时间,编译出模板会令包体积增大。增加的文件大小视 JSX 写法而定,可以在编译后的页面目录下找到对应的模板文件,如 `pages/index/index.jsx` 编译出的模板位置在 `dist/pages/index/index-templates.wxml`。因此开发者应权衡后使用。 |
| 45 | + |
| 46 | +### 2. 只能优化部分语法 |
| 47 | + |
| 48 | +编译阶段只能识别、优化部分语法,不支持的语法会自动回退到 Taro3 默认的渲染模式,具体支持的语法可以查阅 [RFC 文档](https://github.com/NervJS/taro/discussions/14708)。 |
| 49 | + |
| 50 | +有一种常见语法需要注意:编译阶段只能识别 Taro 基础组件,而 React、Vue 组件的渲染会自动回退到旧的渲染模式。如果这些 React、Vue 组件也需要使用半编译模式,需要在组件内部再次添加 `compileMode` 属性: |
| 51 | + |
| 52 | +```jsx |
| 53 | +function Index () { |
| 54 | + return ( |
| 55 | + <View compileMode> |
| 56 | + <Text>Hello</Text> {/* 能被编译阶段识别 */} |
| 57 | + <Foo /> {/*会自动回退到 Taro3 默认的渲染模式*/} |
| 58 | + </View> |
| 59 | + ) |
| 60 | +} |
| 61 | + |
| 62 | +function Foo () { |
| 63 | + return ( |
| 64 | + // 如果希望 Foo 组件也使用半编译模式,需要在 Foo 组件内部再次添加 compileMode 属性 |
| 65 | + <View compileMode> |
| 66 | + ... |
| 67 | + </View> |
| 68 | + ) |
| 69 | +} |
| 70 | + |
| 71 | +``` |
| 72 | + |
| 73 | +## 详细用法 |
| 74 | +### 条件表达式 + 自定义组件 |
| 75 | +通过状态来控制展示哪一个自定义组件的场景在业务中是很常见的,比如以下场景 |
| 76 | +```jsx |
| 77 | +export default function Index () { |
| 78 | + const [show, setShow] = useState(true) |
| 79 | + |
| 80 | + return ( |
| 81 | + <View compileMode> |
| 82 | + <Button onClick={()=>setShow(!show)}>toggle show</Button> |
| 83 | + <View> |
| 84 | + { |
| 85 | + show ? <Item/> : null |
| 86 | + } |
| 87 | + </View> |
| 88 | + </View> |
| 89 | + ) |
| 90 | +} |
| 91 | + |
| 92 | +function Item () { |
| 93 | + return ( |
| 94 | + <View compileMode> |
| 95 | + item |
| 96 | + </View> |
| 97 | + ) |
| 98 | +} |
| 99 | +``` |
| 100 | +正常来说,上面这段代码是没问题的,但是由于 compileMode 得在编译的时候,给元素加上 compileIf 的属性,所以必须是一个确切的标签,所以以上写法暂不支持。后续计划设法把这个属性直接写入在 template 节点上,以支持以上写法。现阶段,先用以下的降级方法: |
| 101 | +```jsx |
| 102 | +export default function Index () { |
| 103 | + const [show, setShow] = useState(true) |
| 104 | + |
| 105 | + return ( |
| 106 | + <View compileMode> |
| 107 | + <Button onClick={()=>setShow(!show)}>toggle show</Button> |
| 108 | + <View> |
| 109 | + <Item show={show}/> |
| 110 | + </View> |
| 111 | + </View> |
| 112 | + ) |
| 113 | +} |
| 114 | + |
| 115 | +function Item (props) { |
| 116 | + const { show } = props |
| 117 | + return ( |
| 118 | + show |
| 119 | + ? |
| 120 | + <View compileMode> |
| 121 | + item |
| 122 | + </View> |
| 123 | + : null |
| 124 | + ) |
| 125 | +} |
| 126 | +``` |
| 127 | +即把组件的展示,放到子组件中去进行判断。 |
| 128 | + |
| 129 | +### 使用 jsx 变量 |
| 130 | +直接使用 jsx 变量,在半编译的情况下是会,如以下代码: |
| 131 | +```jsx |
| 132 | +export default function Index () { |
| 133 | + |
| 134 | + const item = (<View>item</View>) |
| 135 | + return ( |
| 136 | + <View compileMode> |
| 137 | + <View> |
| 138 | + {item} |
| 139 | + </View> |
| 140 | + </View> |
| 141 | + ) |
| 142 | +} |
| 143 | +``` |
| 144 | +要改为 render 开头的渲染函数,如下: |
| 145 | +```jsx |
| 146 | +export default function Index () { |
| 147 | + |
| 148 | + const renderItem = () => <View>item</View> |
| 149 | + return ( |
| 150 | + <View compileMode> |
| 151 | + <View> |
| 152 | + {renderItem()} |
| 153 | + </View> |
| 154 | + </View> |
| 155 | + ) |
| 156 | +} |
| 157 | +``` |
| 158 | +不过这种写法,并不会把 `renderItem` 的返回值直接打入模版里面,所以这种写法对性能会有一定的消耗。 |
| 159 | + |
| 160 | +### 表单驱动 jsx 元素 |
| 161 | +这个场景下,其实就是 「使用 jsx 变量」 的一个延伸,如以下代码: |
| 162 | +```jsx |
| 163 | +export default function Index () { |
| 164 | + |
| 165 | + const itemMap = { |
| 166 | + a: <View compileMode>itemA</View>, |
| 167 | + b: <View compileMode>itemB</View>, |
| 168 | + c: <View compileMode>itemC</View> |
| 169 | + } |
| 170 | + return ( |
| 171 | + <View compileMode> |
| 172 | + {itemMap.a} |
| 173 | + {itemMap.b} |
| 174 | + {itemMap.c} |
| 175 | + </View> |
| 176 | + ) |
| 177 | +} |
| 178 | +``` |
| 179 | +需要改为以下写法: |
| 180 | +```jsx |
| 181 | +export default function Index () { |
| 182 | + const itemMap = { |
| 183 | + renderA: ()=> <View compileMode>itemA</View>, |
| 184 | + renderB: ()=> <View compileMode>itemB</View>, |
| 185 | + renderC: ()=> <View compileMode>itemC</View> |
| 186 | + } |
| 187 | + return ( |
| 188 | + <View compileMode> |
| 189 | + <View> |
| 190 | + {itemMap.renderA()} |
| 191 | + {itemMap.renderB()} |
| 192 | + {itemMap.renderC()} |
| 193 | + </View> |
| 194 | + </View> |
| 195 | + ) |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +## 最佳实践 |
| 200 | +总的来说,要最大限度的发挥半编译模式的优势,就是要把尽量把静态节点,尽可能的写到同一个 jsx 里面去。自我检查的最简单的方式就是看看编译后的模版数量是否足够少,每个模版是否包含了足够多节点。 |
| 201 | +如果一个 template 只是包含了少数节点,那其实无法带来很大的提升。如以下代码: |
| 202 | +```jsx |
| 203 | +import { View, Image, Text } from '@tarojs/components' |
| 204 | + |
| 205 | +const dataList = [ |
| 206 | + { |
| 207 | + src: "https://media.tiffany.cn/is/image/Tiffany/EcomBrowseM/35189432_1009333_ED.jpg?defaultImage=NoImageAvailableInternal", |
| 208 | + title: "这是标题1", |
| 209 | + subTitle: "这是子标题1", |
| 210 | + tag: ["标签1", "标签2", "标签3"], |
| 211 | + des: "这是描述1", |
| 212 | + subDes:'这是子描述1', |
| 213 | + prices: { |
| 214 | + normal: { |
| 215 | + int: '86', |
| 216 | + float: '88' |
| 217 | + }, |
| 218 | + line: 100 |
| 219 | + } |
| 220 | + }, |
| 221 | + { |
| 222 | + src: "https://media.tiffany.cn/is/image/Tiffany/EcomBrowseM/62866950_989218_ED.jpg?defaultImage=NoImageAvailableInternal", |
| 223 | + title: "这是标题2", |
| 224 | + subTitle: "这是子标题2", |
| 225 | + tag: ["标签1", "标签2", "标签3"], |
| 226 | + des: "这是描述2", |
| 227 | + subDes:'这是子描述2', |
| 228 | + prices: { |
| 229 | + normal: { |
| 230 | + int: '60', |
| 231 | + float: '70' |
| 232 | + }, |
| 233 | + line: 100 |
| 234 | + } |
| 235 | + }, |
| 236 | + { |
| 237 | + src: "https://media.tiffany.cn/is/image/Tiffany/EcomBrowseM/62507586_989743_ED_M.jpg?defaultImage=NoImageAvailableInternal", |
| 238 | + title: "这是标题3", |
| 239 | + subTitle: "这是子标题3", |
| 240 | + tag: ["标签1", "标签2", "标签3"], |
| 241 | + des: "这是描述3", |
| 242 | + subDes:'这是子描述3', |
| 243 | + prices: { |
| 244 | + normal: { |
| 245 | + int: '85', |
| 246 | + float: '10' |
| 247 | + }, |
| 248 | + line: 100 |
| 249 | + } |
| 250 | + }, |
| 251 | + { |
| 252 | + src: "https://media.tiffany.cn/is/image/Tiffany/EcomBrowseM/33263465_997778_ED.jpg?defaultImage=NoImageAvailableInternal", |
| 253 | + title: "这是标题4", |
| 254 | + subTitle: "这是子标题4", |
| 255 | + tag: ["标签1", "标签2", "标签3"], |
| 256 | + des: "这是描述4", |
| 257 | + subDes:'这是子描述4', |
| 258 | + prices: { |
| 259 | + normal: { |
| 260 | + int: '8', |
| 261 | + float: '88' |
| 262 | + }, |
| 263 | + line: 100 |
| 264 | + } |
| 265 | + }, |
| 266 | + { |
| 267 | + src: "https://media.tiffany.cn/is/image/Tiffany/EcomBrowseM/60957401_1023440_ED.jpg?defaultImage=NoImageAvailableInternal", |
| 268 | + title: "这是标题5", |
| 269 | + subTitle: "这是子标题5", |
| 270 | + tag: ["标签1", "标签2", "标签3"], |
| 271 | + des: "这是描述5", |
| 272 | + subDes:'这是子描述5', |
| 273 | + prices: { |
| 274 | + normal: { |
| 275 | + int: '77', |
| 276 | + float: '88' |
| 277 | + }, |
| 278 | + line: 100 |
| 279 | + } |
| 280 | + } |
| 281 | +] |
| 282 | + |
| 283 | + |
| 284 | +export default function Index () { |
| 285 | + return ( |
| 286 | + <View> |
| 287 | + { |
| 288 | + new Array(100).fill(0).map((_, index)=><Item key={index} itemIndex = {index}/>) |
| 289 | + } |
| 290 | + </View> |
| 291 | + ) |
| 292 | +} |
| 293 | + |
| 294 | +const Item = (props) =>{ |
| 295 | + const { itemIndex } = props |
| 296 | + const selectIndex = itemIndex % 5 |
| 297 | + const data = dataList[selectIndex] |
| 298 | + return ( |
| 299 | + <View compileMode> |
| 300 | + <View className='item-body-wrap' > |
| 301 | + <View className='image-wrap'> |
| 302 | + <Image src={data.src} mode='aspectFill' className='image-wrap' /> |
| 303 | + </View> |
| 304 | + <View className='body-left'> |
| 305 | + <View className='title-wrap'> |
| 306 | + <View className='title'> |
| 307 | + {data.title} |
| 308 | + </View> |
| 309 | + <View className='sub-title'> |
| 310 | + {data.subTitle} |
| 311 | + </View> |
| 312 | + </View> |
| 313 | + |
| 314 | + <View className='des-wrap'> |
| 315 | + <View className='des'> |
| 316 | + {data.des} |
| 317 | + </View> |
| 318 | + <View className='sub-des'> |
| 319 | + {data.subDes} |
| 320 | + </View> |
| 321 | + </View> |
| 322 | + |
| 323 | + <View className='tag-wrap'> |
| 324 | + { |
| 325 | + data.tag.map((e, index)=><View key={index} className='tag'> |
| 326 | + {e} |
| 327 | + </View>) |
| 328 | + } |
| 329 | + </View> |
| 330 | + <View className='price-wrap'> |
| 331 | + <View className='price-normal'> |
| 332 | + <Text className='price-normal-int'>{data.prices.normal.int}</Text> |
| 333 | + <Text className='price-normal-float'>.{data.prices.normal.float}</Text> |
| 334 | + </View> |
| 335 | + <View className='price-line'> |
| 336 | + {data.prices.line} |
| 337 | + </View> |
| 338 | + |
| 339 | + </View> |
| 340 | + <View className='add'> |
| 341 | + <Image src='https://img12.360buyimg.com/imagetools/jfs/t1/169993/8/27041/5311/61b1b219E03cffee0/778c223bd7677925.png' mode='aspectFill' className='add-image' /> |
| 342 | + </View> |
| 343 | + <View className='level1'> |
| 344 | + <View className='level2'> |
| 345 | + <View className='level3'> |
| 346 | + <View className='level4'> |
| 347 | + <View className='level5'> |
| 348 | + 搞一个嵌套五层的浮动元素 |
| 349 | + </View> |
| 350 | + </View> |
| 351 | + </View> |
| 352 | + </View> |
| 353 | + </View> |
| 354 | + </View> |
| 355 | + </View> |
| 356 | + </View> |
| 357 | + ) |
| 358 | +} |
| 359 | +``` |
0 commit comments