微信小程序:开发总结

架构分析

  • 软件应用架构包括:

    数据层、业务逻辑层、服务处、控制层、展示层、用户,小程序属于展示层,通常还需要其他层次提供支持

  • 主体文件:

    app.js,app.json,app.wxss,前两者是必须存在再根目录下,app.wxss可以不要

  • 页面文件:

    一般位于pages目录下,同一个目录下的四个文件必须同名a.wxml,a.js,a.wxss,a.json,前两者是必须存在的,后两者可以不要

  • 其他文件:其他静态资源可以放在目录下的任何位置
  • 页面的配置文件 只能配置全局app.json文件中的window配置项中内容
  • 小程序属于表现层,但又可以细分为逻辑层和视图层
  • 注册

    提供App方法来注册程序(只能注册一次),page方法来注册页面

  • 框架并非运行在浏览器中,一些document,window等无法使用
  • 编译

    所有的代码都被打包为一份javascript,在小程序启动时运行,直到销毁。类似ServerWorker,所以逻辑层又称为AppServer

  • block

    并不是一个元素组件,仅仅是一个包装元素,不会在页面做任何渲染,只接受控制属性。嵌套组件的渲染必须用block或repeat包装,否则可能会出现问题

  • 模板
    模板拥有自己的作用域,只能使用data传入的数据
  • 引入文件的三种方式:

    1. import方式引入的是模板文件,如<import src="a.wxml"/>,不会嵌套引入;
    2. include方式是将除模板之外的代码全部引入到当前位置,如<include src="h.wxml"/>
    3. @import方式可引入外部wxss文件,如@import a.wxss。注意:必须放在最前面,放在代码后不会生效
  • target与currentTarget对象区别:

    多数情况下两者的返回值时一样的,但是当组件嵌套时,并且内外层组件都定义了事件处理函数,那么在触发内存组件时,这两个对象的返回值就不太一样了

  • 尺寸单位

    建议使用小程序扩展的两种rpxrem

  • 数据绑定

    被绑定的数据必须在data属性里先做定义,否则可能不会正常渲染


线程架构

  • 每个小程序分为2个线程,viewAppServer。其中view线程负责页面的解析和渲染(包括wxmlwxss),AppServer线程负责运行jsAppServer线程运行在jsCore中(安卓下运行在X5中,开发者工具中运行在nwjs中)。由于js并不运行在web-view里,所以就无法操作BOMDOM,这就是小程序没有window全局对象的原因

生命周期

app生命周期——始终优先于page声明周期

  • onLaunch:小程序初始化完成就调用,只被触发一次
  • onShow:小程序启动、从后台进入前台显示,被触发
  • onHide:小程序从前台进入后台,被触发
  • onError:小程序出错、api调用失败、被触发
  • -onPageNotFound:打开的页面不存在,被触发
  • 其他:开发者可以添加任意函数或数据到Object对象中,用this访问

注意:

  • App() 必须在 app.js 中注册,且不能注册多个
  • 不要在定义于 App() 内的函数中调用 getApp() ,使用 this 就可以拿到 app 实例
  • 不要在 onLaunch 的时候调用 getCurrentPages(),此时 page 还没有生成
  • 通过 getApp() 获取实例之后,不要私自调用生命周期函数

page生命周期

  • onLoad: 页面在加载时,只被触发一次
  • onReady: 页面初次渲染完成,只被触发一次
  • onShow: 页面显示时,被触发
  • onHide: 页面隐藏时,被触发(触发左上角的退出箭头时,仅触发app.onHide
  • onUnload: 生命周期函数--监听页面卸载,只被触发一次
  • onPullDownRefresh: 页面相关事件处理函数--监听用户下拉动作
  • onReachBottom: 页面上拉触底事件的处理函数
  • onShareAppMessage: 用户点击右上角转发
  • onPageScroll: 页面滚动触发事件的处理函数
  • onTabItemTap: 当前是 tab 页时,点击 tab 时触发
  • 其他 Any 开发者可以添加任意的函数或数据到 object 参数中,在页面的函数中用 this 可以访问

注意:

  • 执行顺序:onLoad => onShow => onReady

**一个完整的小程序执行的生命周期如下:

app.onLaunch => app.onShow => page1.onLoad => page1.onShow => page1.onReady
(打开程序,第一个页面page1加载完成)
=> page1.onHide => page2.onLoad => page2.onShow => page2.onReady
(从第一个页面新打开page2)
=> page2.onUnload => page1.onShow => ... => app.onUnload
(关闭page2,返回page1...退出小程序)

视图层注意事项

  • 请使用官方提供的wxss选择器,其他选择器可能也没问题,但是不推荐。
例如`.cls1 .cls2`级联样式,微信团队说会破坏组件结构,级联最终会被取消,未来会推出新的方案。
  • 所有的组件和属性都需要使用小写

踩坑指南

网络相关问题

  • 无法抓到请求

    charles或fiddler抓取不到开发者工具的请求,打开代理127.0.0.1:8888

  • 提示非法域名

    一般情况都是出现了不在合法域名中的域名,打开开发者工具中的“不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书”

  • Get和Post请求的返回数据不支持二进制流

    小程序支持通过post获取小程序码(有点像菊花)。尝试通过wx.request向微信服务器获取小程序码的图片,结果发现返回的结果无法显示。开始怀疑代码有问题,调试之后,发现微信服务器返回的结果正确,而小程序会自动把二进制结果转码。更郁闷的是,这种转码丢失了文件内容,并且转换是不可逆的。

    于是,我们改方案,把服务器当中转站,让小程序使用wx.downloadFile从服务器下载图片。在收到小程序下载图片的请求之后,服务器直接和微信服务器获取小程序码的图片,然后以附件的形式返回给小程序。

    参考:小程序开发,那些我们跳过的坑

  • 断网或者与正常网络互相切换时问题

    如果需要根据网络状态判断是否发送异步请求,可以通过官方文档提供的onNetworkStatusChange和``两个方法来处理

    wx.getNetworkType({

    success(res) {
      if(res.networkType === 'none') {
        globalData.netNotConnected = true;
      }
    }

    })

    wx.onNetworkStatusChange(res => {

    if (res.isConnected) {
      globalData.netNotConnected = false;
    } else {
      globalData.netNotConnected = true;
      tip.alert("网络已断开,请连接后重试");
    }

    });

组件相关

  • 自定义字体

    无法使用本地的字体文件,必须使用网络地址

  • 背景图片问题

    background-image背景图片不支持本地图片,只能用网络url或者base64;本地图片要用image标签才行

  • button样式

    • 去掉默认样式

      小程序的button是用伪元素实现的,去掉其默认样式需要用button::after{ border: none; }

    • 设置背景图片

      • 方法1:

        <button 
          open-type="share" 
          class="btn" 
          style="background-image: url(11.png);" 
          plain="true"
        >
        </button>
        
        .btn{
          width: 30rpx;
          height: 30rpx;
          padding: 0 20rpx;
          position: absolute;
          right: 32rpx;
          top: 0;
          bottom: 0;
          margin: auto;
          background-size: 30rpx 30rpx;
          background-repeat: no-repeat;
          border: none;
        }
参考:https://www.jianshu.com/p/b1d8a62da876 

  - 方法2:
  ```
 .btn {
    position: relative;
    &::after {
      display: inline-block;
      content: "";
      position: absolute;
      top: 0;
      right: 20rpx;
      left: 0;
      background: url(11.png) no-repeat 0 0;
      background-size: 715rpx 120rpx;
      background-size: 100% 100%;
      z-index: -1;
    }
 } 
  ```
  • input长度

    value值的默认最大长度是140个字符(不包括空格),如果长度长度超过最大长度,需要设置maxlength=-1即可。

    H5中inputvalue默认没有长度限制

  • text组件的/n问题

    view组件不识别/ntext可以。保存的文本中有换行,如果读取的时候将数据放入view中换行没有效果,放入text中就好了。原则上text标签与其中的内容不能有换行或空格,否则会有大片空白

    wx.showModal方法的换行问题:在文本中添加\r\n,但需要在真机上才能生效,开发者工具不行

  • 多个空格的问题

    小程序中通过多个&nbsp是无法正常显示多个空格的,解决方法:用中文全角空格部  门 (shift+space)

  • textarea问题

    • 设置了auto-height="true"不设置maxlength=“-1”,输入长度会遭限制;
    • 页面中只能有一个 <textarea/>或<input/>设置auto-focus属性;
    • textarea苹果和安卓的显示高度不一致,不给textarea设置宽度,则默认是300px;
    • 设置auto-height时,style.height不生效,并且iosandroid高度显示不一致
    • button的边框是用:after方式实现的,用户如果在button上定义边框会出现两条线,需用:after的方式去覆盖默认值。
  • scroll-view

    • tip: 请勿在 scroll-view 中使用 textarea、map、canvas、video 组件
    • tip: scroll-into-view 的优先级高于 scroll-top
    • tip: 在滚动 scroll-view 时会阻止页面回弹,所以在 scroll-view 中滚动,是无法触发 onPullDownRefresh
    • tip: 若要使用下拉刷新,请使用页面的滚动,而不是 scroll-view ,这样也能通过点击顶部状态栏回到页面顶部
    • 注意:使用竖向滚动时,需要给<scroll-view>一个固定高度height(如100vh),设置百分数无效
    • 清除黑色滚动条(竖向滚动时)

      ::-webkit-scrollbar { 
        width: 0; 
        height: 0; 
        color: transparent; 
      }

      参考:小程序填坑之路

  • cover-view

    • cover-view的内容只有放置在原生组件里才能显示
    • cover-view的内容不能超过原生组件,否则会被强制截取
    • 微信文档中说只支持最外围的cover-view的fixed,经测试内围absolute也可使用,但有时候会看到用absolute的标签不见了,这时候你就要查看包括这个不见的标签的父元素的高度了,估计是absolute的标签超高了被父元素截取了
    • cover-image不支持absolute
由于小程序里面video标签的层级是最高的无法覆盖。所以cvoer-view应运而生。它就是用于盖在video标签上面,进行对video标签的周遭加以装饰的利器。 

例如在cover-view上面使用相对定位,当video标签大小发生变化的时候,cover-view上面的元素就乱七八糟。 又譬如圆角的不起效等等。 具体的问题大家可以在开发者社区看看。[cover-view定位问题](https://developers.weixin.qq.com/search?action=list&t=search/index&search_type=1&key=cover-view&token=&lang=zh_CN) 

避坑方法:尽量在cover-view上不使用定位,其他的bug只能等官方优化,大家谨慎使用。
  • 关于live-pusher,live-player

    <live-player />’渲染失败,错误原因: insertLivePlayer:fail:access denied

    原因:微信的权限里的相机和麦克风没有权限导致的,到手机 设置-应用-微信-权限里设置

  • 键盘与输入框距离

    input,textarea 可以通过标签的cursor-spacing属性设置键盘与输入框的距离

  • 循环渲染

    有嵌套关系的组件需要被循环渲染时,一定 要用blockrepeat,否则会出现一些奇怪问题,如可能取不到item对象的属性值等

  • dataset属性问题
    属性名必须全部小写,不支持驼峰方式。如data-id必须小写。data-author-namee.currentTarget.dataset中会自动转换authorName
  • 视频标签黑边

    objectFit去除小程序视频标签的黑边(没试过)

  • swiper问题

    1. 初次使用swiper的的时候可能遇到当图片自动轮播到最后的时候,跳转到第一页的效果不友好,此时需要添加 circular=“true” 无缝衔接
**问题2:** 轮播图显示异常问题:A页面有`swiper`图片轮播,跳转到B页面编辑并删除某张轮播图片,再返回A页面后,轮播图显示空白,添加新图片时没问题;初步猜测:返回A页面后,`swiper`的`current`属性的当前值状态值指向不存在导致。但在A页面`onShow`时重置`current`值也不行,暂没找到原因 

1. 省市县镇级联选择时,当有多个`swiper-item`时,点击第二个及以后的省获取市时显示空白

解决方法:在点击第二个`swiper-item`里的省获取市之前先把`swiper`组件重置,如设置渲染条件先不让他渲染,获取到市数据后再设置条件让`swiper`组件渲染出来

1. `swiper`滑动过一次后,无法再动态的设置`current`的值解决方案


给`swiper`添加`change`监听事件,当滑动`swiper`时通过`e.detail.current`记录`current`,下次再返回时设置`current`为上次保存的值。

参考:[swiper bug, swiper滑动过一次后,无法动态改变current值](https://openclub.alipay.com/read.php?tid=2919&fid=51)
  • canvas问题

    canvas层级最高,其他元素无论z-index设置多大,都不能盖在canvas上,解决方法:让canvas定位后设置比较大的偏移值,或设置hidden

    绘图时的图片必须是已经下载好的图片,不能使用网络图片或base64,可以用微信提供的getImageInfodownloadFile,示例:

    async createPoster() {
        const imgPromise1 = return new Promise((resolve, reject) => {
          wx.getImageInfo({ 
            src, 
            success(res) {
              resolve(res);
            },
            fail(error) {
              reject(error);
            }
          })
        });
    
        Promise.all([imgPromise1, imgPromise2]).then(res => {
          const [ res1, res2 ] = res;
          ...
          const ctx = wx.createCanvasContext('poster-canvas-id');
          ctx.drawImage(res1.path, x, y, ...);
        })
      }
  • 海报(二维码)

    手机扫描分享的海报二维码,只能跳转到小程序线上版本,可以通过抓包获取跳转时的启动参数,放到开发者工具中模拟线下环境跳转

    如:通过scene值来实现:手机扫码生成海报二维码,抓包这个请求https://activity.12345.com/wxa/town/load?scene=hvORN4dgYy,记下这个scene值,在开发者工具中配置路径'pages/index/index'  参数scene=hvORN4dgYy即可
    
    原理:getAppCode方法中,会把页面加载所需要的参数作为scene值来获取二维码;当扫描二维码时,这个二维码中含有scene值,先进入XX首页时会通过上面那个抓包请求获取这个scene,然后从scene中解析并调整到pagetype,从而先通过跳转首页后再跳转到指定的页面
  • 其他 - Canvas和Image对图片的各种不支持

    • 扫码签到中用到了二维码

      在开始的版本中,我们准备在Canvas上直接绘制二维码,接着使用wx.canvasToTempImage函数保存为image文件,然后通过Image组件加载。

      经过调试,一切顺利。运行的时候呢,发现有时候在绘制完图片之后,调用wx.canvasToTempImage函数失败。这种情况在调试无法重现,运行的时候偶尔出现,不稳定。

      仔细检查了代码,没问题啊。Google之后,有网友提出了解决方案,在drawImage完成之后,最好等3秒钟再调用wx.canvasToTempImage,以保证保存成功。

    • Canvas和Image都不支持Base64图片,Image无法保存

      最初的方案中,我们自己生成二维码,后来为了兼容微信的“扫一扫”功能,我们决定改用小程序码。

      开始,我们把Image的src设置为Base64格式,从服务器上通过request获取图片的Base64编码。小程序开发工具和iPhone上面测试都没有问题,唯独Android手机上无法正常显示图片。哦,原来在Android上,Canvas和Image都不支持Base64图片。可是...小程序开发文档中并没有这方面的说明啊。

      怎么解决Android手机上的这个问题呢?如果把Image改成URL形式呢,小程序无法保存图片,以致即使是相同的图片,每次都要从服务器获取,这又加重了服务器的负担。这样吧,使用wx.downloadFile把文件下载到本地,然后再处理

      参考:Canvas和Image对图片的各种不支持

  • 凌晨定时清理storage

    可以把日期(年/月/日)存储到storage;在页面onLoad时,判断当前日期和storage存储的日期(转时间戳)是否相等,否则就视为第二天

事件 & api 相关

  • 无法通过event事件对象修修改dataset属性值???

    试过好像不行

  • 无法通过event事件对象修修改class类属性值???

    试过好像不行

  • tabBar问题

    tabBar里面的图标 只能是本地图片,不能是网络图片,否则无法显示。至少2个,最多5个

  • 路由跳转问题

    • 不要尝试修改页面栈,会导致路由以及页面状态错误。
    • 不要在 App.onLaunch 的时候调用 getCurrentPages(),此时 page 还没有生成。
-----------
- navigateTo, redirectTo 只能打开非 tabBar 页面。
- switchTab 只能打开 tabBar 页面。
- reLaunch 可以打开任意页面。
- 页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。
- 调用页面路由带的参数可以在目标页面的onLoad中获取。
-----------
- **注意:最多跳转5个页面**

- 在路由跳转的时候,模拟器偶尔会系统报错。频繁跳转出现概率比较高,但仅仅在模拟器出现。可以无视

  **经过测试,因为页面跳转是有动画时效的,在动画进行中当前页面还能操作。如果双击当前跳转按钮机会进行两次跳转。第二次点击的时候小程序内置的路由栈是已经定位到了新的页面了,而这时候在按照原来的路由栈去定位当前页面,因此会报错。所以页面跳转按钮需要进行短时间多次点击的限制**

详见:[小程序路由](https://developers.weixin.qq.com/miniprogram/dev/framework/app-service/route.html)
  • 上下拉页面刷新问题

    bindscrolltoupper 还是 bindscrolltoupper做上拉下拉刷新都需要注意这两个事件是会多次调用的。需要一个标识符来拦截多次调用

  • 缓存问题

    • 缓存最大支持 10M,可以写入多种类型数据number、boolean、array、string、object 等
    • 调用同步方法写缓存时,经常会报错(如下图提示),官方建议尽量使用异步方式写入缓存,编码同步方式;可以添加 try catch finally 处理

      微信小程序:开发总结

**注意:** 

  你在开发小程序过程中,是否遇到,自己已经删除了体验版小程序,但是缓存依然存在?

  那是因为,同一个小程序的开发版、体验版、线上版的缓存是共用的,你需要同时删除这三个版本的小程序,缓存才会被删除。

  参考:[小程序缓存踩过的坑](https://blog.csdn.net/weixin_42133469/article/details/81875125)
  • 涂层 & 弹框 问题

    设置当前涂层和弹框的 @touchmove.stop="func" 即可,不要在func内添加preventDefault 或 cancelBabel(不支持)

  • 网络状态变化问题 onNetworkStatusChange

    网络由正常状态变为断开状态时,发布帖子会报错;这时网络又恢复正常,点击发布按钮却没反应,也没触发请求。

    解决方案:在注册小程序的onLaunch中通过wx.onNetworkStatusChange监听网络变化,同时把该状态存储到globalData全局变量中;当发帖时,首先判断网络状态,如果是断网的情况,就给出toast提示并禁止发布;如果是网络正常,就正常发布。这样即可解决断网又联网时导致不能发布的问题,示例:

// app.wpy
```
onLaunch() {
  wx.onNetworkStatusChange(res => {
    if (res.isConnected) {
      globalData.netNotConnected = false;
    } else {
      globalData.netNotConnected = true;
      tip.alert("网络已断开,请连接后重试");
    }
  });
}
```
// formSubmit.wpy  表单提交
```
if(globalData.netNotConnected) {
  tip.alert("网络已断开,请连接后重试");
  return;
}
...
```

wepy相关

  • this.$apply方法

    wepy中的this.$apply()方法一般用于组件从头传值的.sync中,其他状态更新的地方没必要添加该方法

  • 组件问题

    wepy的组件是静态组件,是以ID作为唯一标识,每一个ID都对应一个组件实例,当页面引入两个相同ID的组件时,这两个组件共用同一个实例与数据,当其中一个组件数据变化时,另外一个也会一起变化。如果需要避免这个问题,则需要分配多个组件ID和实例。

    components = {
        //为两个相同组件的不同实例分配不同的组件ID,从而避免数据同步变化的问题
        ErrorComponent: ErrorComponent,
        ExceptionComponent: ErrorComponent
    };
  • 组件传值问题

    • 只能传递字符串类型,可以传object,array等,但需要先序列号;注意:如果传递是Object,内部不能再有嵌套的复合数据类型了,负责子组件无法正确渲染(手机),array类型的可以
    • 不要再在子组件中修改props中属性的数据类型,否则当该属性需要频繁获取时,页面会出现抖动,体验极差(如省市县镇的级联选择时,当前显示的areaData都是通过props中的areaData获取时,不能在子组件中修改其数据类型)

      ...
      props = {
        areaData: {
          type: String,
          default: ""
        }
      }
      computed = {
        this.areaData = JSON.parse(areaData);   // 不可以,页面抖动
        this.$apply();  
      }
      ...

其他

  • 1.模拟器和真机的差异

    在模拟器上表现正常的,在真机未必正常,问题也很多。譬如

    - 动画的使用
    - cover-view上面使用定位
    - wepy 组件传值
    - canvas定位、绘图 
    - 背景图片路径
原因:造成这些错乱主要是pc端和移动端不同的内核导致的。 

避坑方式:开发过程中,要时不时地用真机也看一下效果。
  • 无效的appID

    重启开发者工具;

    描述:凡是公众平台服务端的修改,都需要重启开发者工具才能生效,包括https域名设置,开发者添加,appid使用,等等

  • **开发者工具提示‘无法创建新项目’

    文件夹不是空文件夹

  • 开发

    以iphone6的rpx来进行小程序的布局最为方便,iphone6的宽度是750rpx

  • 搜索

    小程序不支持模糊匹配,只能精确搜索

  • 数据

    单词设置的数据大小不能超过1024KB,否则会崩溃

  • 校验

    var wxReg=new RegExp("^[a-zA-Z]([-_a-zA-Z0-9]{5,19})+$") //微信号正则验证
    var qqReg=new RegExp("[1-9][0-9]{4,}")  //QQ号正则验证
    var phReg=new /^1[345678]\d{9}$/  //手机号正则验证
    var nameReg=new RegExp("^[\u4e00-\u9fa5]{2,4}$")  //2-4位中文姓名正则验证
  • 引入iconfont问题
    H5方式:

    @font-face {font-family: "iconfont";
      src: url('iconfont.eot?t=1485242349767'); /* IE9*/
      src: url('iconfont.eot?t=1485242349767#iefix') format('embedded-opentype'), /* IE6-IE8 */
      url('iconfont.woff?t=1485242349767') format('woff'), /* chrome, firefox */
      url('iconfont.ttf?t=1485242349767') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
      url('iconfont.svg?t=1485242349767#iconfont') format('svg'); /* iOS 4.1- */
    }
    
    .iconfont {
      font-family:"iconfont" !important;
      font-size:16px;
      font-style:normal;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }

    小程序使用这种方式无法引入字体,解决方案如下:

    - 进入iconfont选择自己需要的icon,并且下载到本地,找到后缀名为.ttf的文件
    - 打开https://transfonter.org/,将字体文件转化成base64的格式
    - 转化无完成后将生成的stylesheet.css拷贝到微信小程序项目中,通过@import方式引入,在需要引入的地方
      ```
      #icons:before{
        font-family: "iconfont";
        /* color: red; */
        font-size: 40rpx;
        content: "\e60b"
      }
      ```
      参考:[微信小程序踩坑日志](https://blog.csdn.net/marko_zheng/article/details/79130076)

分享,二维码,小程序码

  • 分享相关

    • 小程序中的分享只能分享到聊天或群里,不能分享到朋友圈。
    • 扫描所分享的二维码图片,只能跳转到正式版,无法跳转到线下版本
  • 扫码相关

    小程序支持扫二维码进入,但不支持长按识别

授权 & 登录

授权和登录是两回事。登录是用户无感知的,获取到code 和后端通信获得openid来定位用户。而授权才能获取用户头像和名字等信息

开发板 & 体验版 & 正式版

小程序第一次提交审核的时间比较后面的长,第一次审核时间一般1-2天

  • 开发版本只限定管理员扫码预览,不支持其他开发者或体验者预览
  • 体验版不支持其他开发者预览,仅支持被授权的体验者预览

其他:

相关推荐