Skip to content

Latest commit

 

History

History
359 lines (324 loc) · 10.6 KB

complier-mode.mdx

File metadata and controls

359 lines (324 loc) · 10.6 KB
title
半编译模式

:::info Taro v3.6.23 开始支持,目前只支持 React,暂不支持 Vue。底层实现原理请参考 RFC 文档。 :::

在节点数量增多到一定量级时,Taro3 的渲染性能会大幅下降,出现白屏时间长、交互延时等问题。经排查发现是目前 Taro 的 <template> 模板语法所造成的,为此我们参考 Taro 1/2 的思路,提供了 CompileMode 渲染模式。CompileMode 适合长列表 Item 这类会被重复渲染多次的组件使用,在长列表场景能提升 30% 以上的首开速度,同时能有效减少节点过多时产生的交互延时问题。CompileMode 可以说是应对复杂页面性能优化的“银弹”。

使用方法

首先在 Taro 编译配置中开启使用半编译模式:

const config = {
  mini: {
    experimental: {
      compileMode: true
    }
  }
  // ...
}

然后只需要给 Taro 基础组件添加 compileMode 属性,该组件及其 children 将会被编译为单独的小程序模板:

function GoodsItem () {
  return (
    <View compileMode>
      ...
    </View>
  )
}

更为详细的用法请看 详细用法

常见问题

1. 编译出的模板文件会增加包体积

半编译模式使用了空间来换时间,编译出模板会令包体积增大。增加的文件大小视 JSX 写法而定,可以在编译后的页面目录下找到对应的模板文件,如 pages/index/index.jsx 编译出的模板位置在 dist/pages/index/index-templates.wxml。因此开发者应权衡后使用。

2. 只能优化部分语法

编译阶段只能识别、优化部分语法,不支持的语法会自动回退到 Taro3 默认的渲染模式,具体支持的语法可以查阅 RFC 文档

有一种常见语法需要注意:编译阶段只能识别 Taro 基础组件,而 React、Vue 组件的渲染会自动回退到旧的渲染模式。如果这些 React、Vue 组件也需要使用半编译模式,需要在组件内部再次添加 compileMode 属性:

function Index () {
  return (
    <View compileMode>
      <Text>Hello</Text> {/* 能被编译阶段识别 */}
      <Foo /> {/*会自动回退到 Taro3 默认的渲染模式*/}
    </View>
  )
}

function Foo () {
  return (
    // 如果希望 Foo 组件也使用半编译模式,需要在 Foo 组件内部再次添加 compileMode 属性
    <View compileMode>
      ...
    </View>
  )
}

详细用法

条件表达式 + 自定义组件

通过状态来控制展示哪一个自定义组件的场景在业务中是很常见的,比如以下场景

export default function Index () {
  const [show, setShow] = useState(true)

  return (
    <View compileMode>
      <Button onClick={()=>setShow(!show)}>toggle show</Button>
      <View>
        {
          show ? <Item/> : null
        }
      </View>
    </View>
  )
}

function Item () {
  return (
    <View compileMode>
      item
    </View>
  )
}

正常来说,上面这段代码是没问题的,但是由于 compileMode 得在编译的时候,给元素加上 compileIf 的属性,所以必须是一个确切的标签,所以以上写法暂不支持。后续计划设法把这个属性直接写入在 template 节点上,以支持以上写法。现阶段,先用以下的降级方法:

export default function Index () {
  const [show, setShow] = useState(true)

  return (
    <View compileMode>
      <Button onClick={()=>setShow(!show)}>toggle show</Button>
      <View>
        <Item show={show}/>
      </View>
    </View>
  )
}

function Item (props) {
  const { show } = props
  return (
    show 
    ?
    <View compileMode>
      item
    </View>
    : null
  )
}

即把组件的展示,放到子组件中去进行判断。

使用 jsx 变量

直接使用 jsx 变量,在半编译的情况下是会,如以下代码:

export default function Index () {

  const item = (<View>item</View>)
  return (
    <View compileMode>
      <View>
        {item}
      </View>
    </View>
  )
}

要改为 render 开头的渲染函数,如下:

export default function Index () {

  const renderItem = () => <View>item</View>
  return (
    <View compileMode>
      <View>
        {renderItem()}
      </View>
    </View>
  )
}

不过这种写法,并不会把 renderItem 的返回值直接打入模版里面,所以这种写法对性能会有一定的消耗。

表单驱动 jsx 元素

这个场景下,其实就是 「使用 jsx 变量」 的一个延伸,如以下代码:

export default function Index () {

  const itemMap = {
    a: <View compileMode>itemA</View>,
    b: <View compileMode>itemB</View>,
    c: <View compileMode>itemC</View>
  }
  return (
    <View compileMode>
      {itemMap.a}
      {itemMap.b}
      {itemMap.c}
    </View>
  )
}

需要改为以下写法:

export default function Index () {
  const itemMap = {
    renderA: ()=> <View compileMode>itemA</View>,
    renderB: ()=> <View compileMode>itemB</View>,
    renderC: ()=> <View compileMode>itemC</View>
  }
  return (
    <View compileMode>
      <View>
      {itemMap.renderA()}
      {itemMap.renderB()}
      {itemMap.renderC()}
      </View>
    </View>
  )
}

最佳实践

总的来说,要最大限度的发挥半编译模式的优势,就是要把尽量把静态节点,尽可能的写到同一个 jsx 里面去。自我检查的最简单的方式就是看看编译后的模版数量是否足够少,每个模版是否包含了足够多节点。 如果一个 template 只是包含了少数节点,那其实无法带来很大的提升。如以下代码:

import { View, Image, Text } from '@tarojs/components'

const dataList = [
  {
      src: "https://media.tiffany.cn/is/image/Tiffany/EcomBrowseM/35189432_1009333_ED.jpg?defaultImage=NoImageAvailableInternal",
      title: "这是标题1",
      subTitle: "这是子标题1",
      tag: ["标签1", "标签2", "标签3"],
      des: "这是描述1",
      subDes:'这是子描述1',
      prices: {
          normal: {
              int: '86',
              float: '88'
          },
          line: 100
      }
  },
  {
      src: "https://media.tiffany.cn/is/image/Tiffany/EcomBrowseM/62866950_989218_ED.jpg?defaultImage=NoImageAvailableInternal",
      title: "这是标题2",
      subTitle: "这是子标题2",
      tag: ["标签1", "标签2", "标签3"],
      des: "这是描述2",
      subDes:'这是子描述2',
      prices: {
          normal: {
              int: '60',
              float: '70'
          },
          line: 100
      }
  },
  {
      src: "https://media.tiffany.cn/is/image/Tiffany/EcomBrowseM/62507586_989743_ED_M.jpg?defaultImage=NoImageAvailableInternal",
      title: "这是标题3",
      subTitle: "这是子标题3",
      tag: ["标签1", "标签2", "标签3"],
      des: "这是描述3",
      subDes:'这是子描述3',
      prices: {
          normal: {
              int: '85',
              float: '10'
          },
          line: 100
      }
  },
  {
      src: "https://media.tiffany.cn/is/image/Tiffany/EcomBrowseM/33263465_997778_ED.jpg?defaultImage=NoImageAvailableInternal",
      title: "这是标题4",
      subTitle: "这是子标题4",
      tag: ["标签1", "标签2", "标签3"],
      des: "这是描述4",
      subDes:'这是子描述4',
      prices: {
          normal: {
              int: '8',
              float: '88'
          },
          line: 100
      }
  },
  {
      src: "https://media.tiffany.cn/is/image/Tiffany/EcomBrowseM/60957401_1023440_ED.jpg?defaultImage=NoImageAvailableInternal",
      title: "这是标题5",
      subTitle: "这是子标题5",
      tag: ["标签1", "标签2", "标签3"],
      des: "这是描述5",
      subDes:'这是子描述5',
      prices: {
          normal: {
              int: '77',
              float: '88'
          },
          line: 100
      }
  }
]


export default function Index () {
  return (
    <View>
      {
        new Array(100).fill(0).map((_, index)=><Item key={index} itemIndex = {index}/>)
      }
    </View>
  )
}

const Item = (props) =>{
  const { itemIndex } = props
  const selectIndex = itemIndex % 5
  const data = dataList[selectIndex]
  return (
      <View compileMode>
          <View className='item-body-wrap' >
              <View className='image-wrap'>
                  <Image src={data.src} mode='aspectFill' className='image-wrap' />
              </View>
              <View className='body-left'>
                  <View className='title-wrap'>
                      <View className='title'>
                          {data.title}
                      </View>
                      <View className='sub-title'>
                          {data.subTitle}
                      </View>
                  </View>

                  <View className='des-wrap'>
                      <View className='des'>
                          {data.des}
                      </View>
                      <View className='sub-des'>
                          {data.subDes}
                      </View>
                  </View>

                  <View className='tag-wrap'>
                  {
                      data.tag.map((e, index)=><View key={index} className='tag'>
                          {e}
                      </View>)
                  }
                  </View>
                  <View className='price-wrap'>
                      <View className='price-normal'>
                          <Text className='price-normal-int'>{data.prices.normal.int}</Text>
                          <Text className='price-normal-float'>.{data.prices.normal.float}</Text>
                      </View>
                      <View className='price-line'>
                          {data.prices.line}
                      </View>

                  </View>
                  <View className='add'>
                      <Image src='https://img12.360buyimg.com/imagetools/jfs/t1/169993/8/27041/5311/61b1b219E03cffee0/778c223bd7677925.png' mode='aspectFill' className='add-image' />
                  </View>
                  <View className='level1'>
                      <View className='level2'>
                          <View className='level3'>
                              <View className='level4'>
                                  <View className='level5'>
                                      搞一个嵌套五层的浮动元素
                                  </View>
                              </View>
                          </View>
                      </View>
                  </View>
              </View>
          </View>
      </View>
  )
}