欢迎光临,了解各类小程序、网站开发,就上众彬建站! 退出 注册 登录

我是如何写超大型小程序代码的?

发布:2022-03-16 08:45浏览: 来源: 作者:

你 和一头骆驼准备穿过沙漠,前面是一眼望不到头的沙海,你的目的是要穿过 沙漠到达对面的绿洲。 现在你写的每一行代码就是往骆驼上负重。 当然,有些 负重是 必须 的,比如水和食物。

可能会由于负重太多,骆驼严重拖累行程,甚至可能永远走不出沙漠。

可能水和食物不够,都被饿死在中途。

本文目录:

  • 有必要重申的交互原理

  • 请慎重设置 data

  • 请三思组件间如何通信

  • 友好的使用 sass

  • 不可或缺的 template & @import

  • 理解多页应用的单页应用

  • 第三方库

  • 请善待主包

小程序有包大小限制,单个主包或者子包不能超过 2M,总包不能高于 12M。

有大小限制,那么对于超大型小程序,就要悠着点了,否则超包那就是家常便饭了,当然小程序这个远不及浏览器的内存和性能也提醒着我们要悠着点。

现在,请开始给骆驼负重!!!

有必要重申的交互原理

小程序的 Page 给我们提供了一个 data 对象。这个 data 对象的数据更新,则直接影响 wxml 的内容。有 vue 或者 react 开发经验的同学可能再熟悉不过了。当然小程序我们需要使用 setData 来更新数据。

Page({
  data: {
    text: "This is page data."
  },
  onLoad: function(options) {
    // doSomething
  }
});

这是官网上的描述:

小程序的视图层目前使用 WebView 作为渲染载体,而逻辑层是由独立的 JavascriptCore 作为运行环境。在架构上,WebView 和 JavascriptCore 都是独立的模块,并不具备数据直接共享的通道。当前,视图层和逻辑层的数据传输,实际上通过两边提供的 evaluateJavascript 所实现。即用户传输的数据,需要将其转换为字符串形式传递,同时把转换后的数据内容拼接成一份 JS 脚本,再通过执行 JS 脚本的形式传递到两边独立环境。

简而言之,js 逻辑层的 data 的数据是需要转换成字符串,再使用 JS 脚本传输到 wxml 的视图层。文档也明确了传输不实时。实际上如果数据量大还很慢。

请慎重设置 data

data 对象是类似 vue 的 data,是 reactivity 的。vue 的 reactivity 的数据,是都需要 Object.defineProperty(或者 Proxy)的。

比如看到有代码,在 data 里面放大量的数据,而这些数据根本上在 wxml 里面用不上,仅仅是为了存储一个对象,在其他文件模块(同 this)的时候,能直接用。还看到在 data 里面设置 isLoading 对象,这个仅仅是为了在点击的时候做一个锁,等接口调用完成,再把 isLoading 改为 false。

以上情况,在小程序代码中比比皆是。这些都有一个通病,忽略了性能,随心使用 data,导致一个页面,在 data 下设置的变量可能占满一屏的高度。

那我们如何做呢?

原则:只在 data 对象设置在 wxml 需要使用的变量。

那么其他变量我们怎么做呢?有两种方案:

1、设置在实例作用域外,脱离 Page 或者 Component 对象。

let name = "is file scope data";
Page({});

2、设置在实例作用域内,但是脱离 data 对象,不需要 reactivity。

a) 如果是 Page ,可以直接新增一个 customData,

Page({
    data:{
        name:"reactivity data"
    },
    customData: {
        name: 'not reactivity data'
    }.
});

b) 如果是 Component,可以使用 pureData。

Component({
options: {
  pureDataPattern: /^_/ // 指定所有 _ 开头的数据字段为纯数据字段
},
data: {
  name: "is reactivity",
  _name: "is pureData",
},

经过实验证明,对于逻辑复杂,数据量大的页面,很好的优化你的 data,对性能提升比较明显。

请三思组件间如何通信

组件间通信方式:

  1. 子组件向父组件:triggerEvent

  2. 父组件向子组件:props

  3. 父组件向子组件:selectComponent

  4. 订阅发布模式:emit、on

当前前三者,都是小程序直接提供的方式,第四种我们需要写一个公共的 emitter 组件。下面我们来分析下这种情况。

1.triggerEvent

子组件向父组件通信,这种在开发中非常常见,子组件响应了操作,如果需要同步给父组件,直接用 this.triggerEvent,然后在父组件中定义 bind 就行。

2. props

父组件向子组件传参,这种也非常常见,如果你需要传参给子组件,需要做如下两步:

1、父组件需要在 reactivity 的 data 中设置数据;

2、子组件需要在 props 属性中设置接收该数据对象。

发现没有,如果你需要传一个参数,那么你需要在父子组件当中都设置为 reactivity 的 data 对象。显然,对于大量的传输数据对象(比如一个大型数据列表),不适合直接这样传参。因为如果这样做的话,显然增大了父子组件成本。

3. selectComponent

父组件直接调用子组件实例对象,然后直接执行子组件的方法,比如:

子组件:

Component({
  methods: {
    updateStatus() {}
  }
});

父组件:

this.selectComponent("#childComponent").updateStatus();

selectComponent,完美的解决了 props 传参的问题,微信给你另一条道路,也说明了 props 的问题。但是 props 传参方式更符合开发习惯和数据流思维。

个人建议,大数据传输,直接用 selectComponent,反之用 props。

4. $emit

发布订阅模式,大伙都很熟悉了。注册 on("key", () => {}),发布 emit("key", data)。这种方式的优点很明显,完全突破组件直接的关系。任何地方都可以监听,都可以发布。

缺点则是无状态的,和组件实例无关,且是全局的。

如果页面打开了两个 Page,比如 商祥 -> 店铺 -> 商祥 ,这个时候,如果商详接受到一个消息,两个商祥 Page 都会收到。

发布订阅模式需要注意四点:

  1. key 是全局的,好好命名。

  2. 接收消息的区分是不是该实例的。

  3. 销毁注册消息通常是全部的。A 监听 key,B 监听 key,如果执行 remove(key),则 key 都会清除。

  4. 如果不支持粘性事件则需要关注发布订阅时机。

(当然你可以选择不把 EventBus 放到全局)

友好的使用 sass

样式文件,我们一般使用预处理,比如 sass。我们看如下例子。

class="recommend">
    class="recommend_header">
        class="recommend_header_author">
            class="recommend_header_author_img">
            class="recommend_header_author_info">
                class="recommend_header_author_info_name">
                class="recommend_header_author_info_location">
            
        
        class="recommend_header_follow">
    
    class="recommend_footer">
    

.recommend {
  &_header {
    &_author {
      &_img {
      }
      &_info {
        &_name {
        }
      }
    }
  }
}

上述结构和样式,看不出啥,我们经常在 H5 页面的时候都是如此做的,按照页面模块和层级定义结构,使用 sass 逐层写样式,这可以说是标准写法,语义和结构清晰,

但是在超大型小程序中,我要 say No!

我们知道,sass 是 css 预处理器,最终是需要转换成 css 被浏览器和微信小程序识别。

我们看下转换后的样式文件

.recommend {
}
.recommend .recommend_header {
}
.recommend .recommend_header .recommend_header_author {
}
.recommend .recommend_header .recommend_header_author {
}
.recommend
  .recommend_header
  .recommend_header_author
  .recommend_header_author_info {
}
.recommend
  .recommend_header
  .recommend_header_author
  .recommend_header_author_info
  .recommend_header_author_info_name {
}

看了之后有没有觉得你哪里不对?重复的 class 太多了,看到这么多重复的,JS 开发人员第一反应:提炼抽取才是正道。想想我们大几十行研发人员开发的小程序,功能如此之多,体积如此之珍贵,岂能如此浪费。

回归到 sass,我们使用样式层级作用域的目的是啥呢?我认为无非是绝对标识该样式,类似 js 的作用域,不被其他模块的样式影响,然后能够清晰的定义样式。

试想,如果张三在页面 header 模块的用户昵称,定义成 .name,那么李四在 content 模块的用户昵称也可能会被定义成 .name,然而这两个样式完全不一样,但是样式就会相互影响,李四把 .name 样式写好了,然而 header 模块的 .name 样式又不对了,这显然不是我们想要的,所以推荐把样式的名称按层级定义,不会被影响。

理由很充分,sass 写起来也很舒服,但是在实际中极力不推荐严格按这种层级定义。

那么在大型小程序中,推荐: 最多三级,建议两级。

对应上述 wxml ,在结构不变的情况下,样式修正为如下:

class="recommend">
    class="recommend_header">
        class="recommend_author">
            class="recommend_img">
            class="recommend_info">
                class="recommend_name">
                class="recommend_location">
            
        
        class="recommend_follow">
    
    class="recommend_footer">
    

有人会说了,这个 header 模块用户昵称可能是 .recommend_name,但是 footer 模块也有一个用户昵称怎么办?

通常一个模块是一个人维护,就算多个人修改,那么样式也只在当前模块内影响,风险完全可控。那么 footer 模块的昵称可以定义成 .recommend_footer_name,增加到第三层,甚至于我推荐直接是 .recommend_footer-name,这样在解析成 css 的时候,仍然是两层。

都 21 世纪了,总想着再不断的出现爱因斯坦、牛顿等已经不太现实了。我们要 "微优化"!

总结:在可读性仍然很强大的情况下,保证模块直接样式不冲突,建议控制在两层 sass,最多不超过三层。减少文件大小。

在模块多的页面中,这种带来 wxml 和 css 体积的缩减其实是很可观的。

template & @import

根据页面划分组件,大伙都会做。然而实际上在模块中会存在很多共同的结构,但是有时候我们因为逻辑较少等原因没必要抽离成一个组件,这个时候 template 就派上大用场了。

单个文件中的共同结构

<template name="liveItem"> template>
<template is="liveItem" data="{{list:preLiveList}}" />
<template is="liveItem" data="{{list:liveList}}" />

多个模块中的共同结构

<import src="./template.wxml" />