性能优化实践

开发者手动优化

开发者可以借助 Taro 提供的各种工具对程序性能进行优化。

预加载

微信小程序支付宝小程序字节跳动小程序QQ轻应用中,从调用 Taro.navigateToTaro.redirectToTaro.switchTab 后,到页面触发 componentWillMount 会有一定延时。因此一些网络请求可以提前到发起跳转前一刻去请求。

Taro 提供了 componentWillPreload 钩子,它接收页面跳转的参数作为参数。可以把需要预加载的内容通过 return 返回,然后在页面触发 componentWillMount 后即可通过 this.$preloadData 获取到预加载的内容。

注意:调用跳转方法时需要使用绝对路径,相对路径不会触发此钩子。

  1. class Index extends Component {
  2. componentWillMount () {
  3. console.log('isFetching: ', this.isFetching)
  4. this.$preloadData
  5. .then(res => {
  6. console.log('res: ', res)
  7. this.isFetching = false
  8. })
  9. }
  10. componentWillPreload (params) {
  11. return this.fetchData(params.url)
  12. }
  13. fetchData () {
  14. this.isFetching = true
  15. ...
  16. }
  17. }

在小程序中,可以使用 this.$preload 函数进行页面跳转传参

用法:this.$preload(key: String | Object, [ value: Any ])

之所以命名为 $preload,因为它也有一点预加载数据的意味。

如果觉得每次页面跳转传参时,需要先把参数 stringify 后加到 url 的查询字符串中很繁琐,可以利用 this.$preload 进行传参。

另外如果传入的是下一个页面的数据请求 promise,也有上一点提到的“预加载”功能,也能够绕过 componentWillMount 延时。不同点主要在于代码管理,开发者可酌情使用。

例子:

  1. // 传入单个参数
  2. // A 页面
  3. // 调用跳转方法前使用 this.$preload
  4. this.$preload('key', 'val')
  5. Taro.navigateTo({ url: '/pages/B/B' })
  6. // B 页面
  7. // 可以于 this.$router.preload 中访问到 this.$preload 传入的参数
  8. componentWillMount () {
  9. console.log('preload: ', this.$router.preload.key)
  10. }
  1. // 传入多个参数
  2. // A 页面
  3. this.$preload({
  4. x: 1,
  5. y: 2
  6. })
  7. Taro.navigateTo({ url: '/pages/B/B' })
  8. // B 页面
  9. componentWillMount () {
  10. console.log('preload: ', this.$router.preload)
  11. }

shouldComponentUpdate

当你清楚在某些情况下组件不需要被重新渲染时,可以通过在 shouldComponentUpdate 钩子里返回 false 来跳过本次渲染流程。

  1. shouldComponentUpdate (nextProps, nextState) {
  2. if (this.props.color !== nextProps.color) {
  3. return true
  4. }
  5. if (this.state.count !== nextState.count) {
  6. return true
  7. }
  8. return false
  9. }

Taro.PureComponent

在大多数情况下,开发者可以让组件继承于 Taro.PureComponent 类,而无需手动实现 shouldComponentUpdateTaro.PureComponent 里实现了 shouldComponentUpdate,它会把新旧 props 和新旧 state 分别做一次浅对比,以避免不必要的渲染。

Taro.memo

自 v1.3.0 可用

Taro.memo 是一个高阶组件,它和 PureComponent 非常相似。但它适用于函数式组件,而非 Class 组件。

如果你的函数组件在给定相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 Taro.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,Taro 将跳过渲染组件的操作并直接复用最近一次渲染的结果。

默认情况下其只会对复杂对象做浅层对比(和 PureComponent 行为一致),如果你想要控制对比过程,那么请将自定义的比较函数通过第二个参数传入来实现。

  1. function MyComponent(props) {
  2. /* 使用 props 渲染 */
  3. }
  4. function areEqual(prevProps, nextProps) {
  5. /*
  6. 如果把 nextProps 传入 render 方法的返回结果与
  7. 将 prevProps 传入 render 方法的返回结果一致则返回 true,
  8. 否则返回 false
  9. */
  10. }
  11. export default Taro.memo(MyComponent, areEqual);

注意与 class 组件中 shouldComponentUpdate() 方法不同的是,如果 props 相等,areEqual 会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate 方法的返回值相反。

Taro 框架层面优化

Taro 框架做了一些性能优化方面的工作,这部分毋需开发者手动处理,开发者可稍做了解。

小程序数据 diff

在真正调用小程序的 setData 方法之前,Taro 会把页面或组件的 state 和当前页面或组件的 data 做一次 diff,只对必要更新的数据做 setData,开发者无需手动优化。

diff 逻辑:
  • 全等 => 跳过
  • 新增字段 => 使用新值
  • 类型不同 => 使用新值
  • 类型相同、基础数据类型 => 使用新值
  • 其中一方为数组,另一方不是 => 使用新值
  • 都为数组、新数组比旧数组短 => 使用新值
  • 都为数组、新数组长度大于等于旧数组的长度 => 逐项 diff、按路径更新
  • 其中一方为 null,另一方不是 => 使用新值
  • 都为对象,新对象缺少旧对象某些属性 => 使用新值
  • 都为对象,新对象拥有旧对象所有的属性 => 逐项 diff、按路径更新例子:
  1. // 新值
  2. const state = {
  3. a: 1,
  4. b: 22,
  5. d: 4,
  6. list: [1],
  7. arr: [1, 'a', true, null, 66],
  8. obj: {
  9. x: 5
  10. },
  11. foo: {
  12. x: 8,
  13. y: 10,
  14. z: 0
  15. }
  16. }
  17. // 旧值
  18. const data = {
  19. a: 1,
  20. b: 2,
  21. c: 3,
  22. list: [1, 2, 3],
  23. arr: [1, 2, 3],
  24. obj: {
  25. x: 10,
  26. y: 8
  27. },
  28. foo: {
  29. x: 'xxx',
  30. y: 10
  31. }
  32. }
  33. diff(data, state)
  34. /**
  35. * diff 结果
  36. {
  37. b: 22,
  38. d: 4,
  39. list: [ 1 ],
  40. 'arr[1]': 'a',
  41. 'arr[2]': true,
  42. 'arr[3]': null,
  43. 'arr[4]': 66,
  44. obj: { x: 5 },
  45. 'foo.x': 8,
  46. 'foo.z': 0
  47. }
  48. */