微信小程序基础扫盲

312 阅读9分钟

本文将会介绍在常用小程序开发中有必要整理和归纳出来的几个知识点,可以理解为这是对小程序官网文档的一个补充或者说注解,因此,不会像官方文档一样事无巨细的说明,只会挑重点阐述。这是笔者在入门小程序时认为最重要的几个概念和总结归纳,希望能对读者有所帮助。

小程序之痛——setData

它不同于正常的组件属性,它的异步更新会影响视图层的渲染和更新。因此,冗余的、不必要的、耗时大的容易阻塞视图层更新,造成页面闪烁等问题。它的不良好使用会同时影响逻辑层的js响应以及视图层的js响应,出现明显延迟。

这或许是双线程的弊端吧,数据传输的耗时与数据量的大小正相关,如果对端线程处于繁忙状态,数据会在消息队列中等待。

注意事项

  1. 只包含渲染相关的数据(即直接在 wxml 中出现的字段)

  2. 尽量不要用data去组件之间数据共享

  3. 对于使用频率降低的一些数据,整合好数据然后再一次性调用

  4. 对于需要频繁更新的页面元素(例如:秒杀倒计时),可以封装为独立的组件,在小组件里调用

  5. created生命周期阶段不能用

  6. 建议以数据路径形式改变数组中的某一项或对象的某个属性,如 this.setData({'array[2].message': 'newVal', 'a.b.c.d': 'newVal'}),而不是每次都更新整个对象或数组

其实只看这些注意事项,很难记得住,而且也只是知其然而不知所以然,因此笔者会在之后的文章用惨痛的教训来说明其中几个注意事项的含金量。

自定义组件的千姿百态

Component 构造器可用于定义组件,调用 Component 构造器时可以指定组件的属性、数据、方法等。

事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用 Component 构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应 json 文件中包含 usingComponents 定义段。

实际测试发现,当设置一个组件是页面时,它还是可以作为一个组件被展示,我只能说呵呵,你怕是万物的起点。

除此之外,笔者也会在之后的文章介绍如何让组件像提线木偶一样任人宰割:论玩弄组件的N种方法(bushi

生命周期

小程序中,生命周期主要分成了三部分:

  1. 应用的生命周期
  2. 页面的生命周期
  3. 组件的生命周期

组件的生命周期:created、attached、ready、moved、detached 将收归到 lifetimes 字段内进行声明,原有声明方式仍旧有效,如同时存在两种声明方式,则 lifetimes 字段内声明方式优先级最高

sequenceDiagram
    participant App
    participant Page
    participant Component

    App->>App: onLaunch
    App->>App: onShow
    App->>Page: onLoad
    Page->>Page: onShow
    Page->>Page: onReady
    Component->>Component: created
    Component->>Component: attached
    Component->>Component: ready

    Note over App,Page: 用户切换到另一个页面
    Page->>Page: onHide
    Page->>Page: onUnload
    App->>Page: 下一个页面onLoad
    Page->>Page: 下一个页面onShow
    Page->>Page: 下一个页面onReady

    Note over App,Page: 返回上一个页面
    Page->>Page: 上一个页面onShow

    Note over App,Page: 用户离开小程序
    App->>App: onHide

    Note over App,Page: 再次进入小程序(未销毁)
    App->>App: onShow
    App->>Page: onLoad
    Page->>Page: onShow
    Page->>Page: onReady

    Note over App,Page: 再次进入小程序(已销毁)
    App->>App: onLaunch
    App->>App: onShow
    App->>Page: onLoad
    Page->>Page: onShow
    Page->>Page: onReady

万年不变——性能优化

  1. 页面分包、分包预下载
  2. 按需注入(全局设置)、用时注入(设置占位组件实现)
  3. 数据预拉取
  4. skyline组件替换webview组件
  5. setData优化
  6. worklet手势、wxs交互动画

由于这里官方文档介绍的比较详细,可以看官方文档说明,这里只做罗列,不做介绍

应有尽有——小程序通信

在微信小程序的开发过程中,通信方式是关键环节之一。我们需要在组件之间、页面之间、甚至全局范围内实现数据的传递与交互。本文只介绍常规的几种方式,利用Store等库和封装的方法暂不讨论。

1. 组件通信:亲子交流🏠

properties:父母的爱

这是父组件向子组件传递数据的方式,就像父母给孩子零花钱一样简单直接。你在 Vue 中用 props,在微信小程序中则是 properties

常见场景:
父组件要给子组件一些数据,比如商品列表,那就用这个吧。

<my-component list="{{list}}"></my-component>
Component({
  properties:{
    list:{
      type: Array,
      value: []
    }
  },
  attached(){
    console.log(this.list)
  }
})

triggerEvent:孩子的呼唤

孩子也有话要对父母说,这时我们用 triggerEvent,就像孩子在喊:“妈妈,我要吃糖!”。

常见场景:
子组件完成了一些事情后,需要告诉父组件,比如按钮点击后的反馈。

Component({
  attached(){
    this.triggerEvent('customEvent', { id: 10 })
  }
})
<my-component bind:customEvent="customEvent"></my-component>
Page({
  customEvent(e){
    console.log(e.detail.id)
  }
})

selectComponent:随身携带的电话📞

有时我们不需要一直“喊”,可以直接用 selectComponent 拿到组件的实例,就像父母有了孩子的电话号码,直接打过去。

常见场景:
父组件需要主动操作子组件,比如清空列表、更新状态等。

Page({
  onLoad(){
    let mycomponent = this.selectComponent('#mycomponent')
    mycomponent.setData({ list: [] })
  }
})

selectOwnerComponent:找到你爸妈!👨‍👩‍👦

在孩子的视角中,有时候需要找到自己的父母是谁。这时候就可以用 selectOwnerComponent,类似 Vue 的 $parent,简直是小程序的“寻亲大会”。

常见场景:
子组件需要知道它是被哪个组件调用的,以便进行更复杂的交互。

Component({
  attached(){
    let parent = this.selectOwnerComponent()
    console.log(parent)
  }
})

2. 全局通信:广播电台📡

globalData:村里有话大家听🗣️

这是最简单粗暴的方式,把数据挂载到 app.js 中,就像在村里的广播中喊话:“大家好,今天有大促销!” 每个页面都可以通过 getApp() 去获取这份数据。

常见场景:
需要在多个页面共享一些全局数据,比如用户信息、购物车内容。

App({
  globalData:{
    list:[]
  }
})
Page({
  onLoad(){
    const app = getApp()
    app.globalData.list.push({ id: 10 })
  }
})

storage:记忆宝藏🧠

storage 就像一个记忆力稍差的老爷爷,只能记住 10M 的东西,但用来存放一些不太重要的数据还是不错的,比如历史搜索记录。

常见场景:
缓存用户数据,比如用户偏好设置、上次浏览的位置等。

wx.setStorageSync('timestamp', Date.now())
wx.getStorageSync('timestamp')
wx.removeStorageSync('timestamp')

eventBus:神奇的传话筒📢

如果全局广播对你来说太吵,没关系!eventBus 可以实现点对点的传话,支持事件的注册、触发、取消,就像一个魔法般的传话筒。

常见场景:
当多个页面或组件之间需要解耦通信时,比如购物车变化时通知不同页面更新数据。

class EventBus{
  constructor(){
    this.task = {}
  }

  on(name, cb){
    if(!this.task[name]){
      this.task[name] = []
    }
    typeof cb === 'function' && this.task[name].push(cb)
  }

  emit(name, ...arg){
    let taskQueen = this.task[name]
    if(taskQueen && taskQueen.length > 0){
      taskQueen.forEach(cb => {
        cb(...arg)
      })
    }
  }

  off(name, cb){
    let taskQueen = this.task[name]
    if(taskQueen && taskQueen.length > 0){
      let index = taskQueen.indexOf(cb)
      index != -1 && taskQueen.splice(index, 1)
    }
  }

  once(name, cb){
    function callback(...arg){
      this.off(name, cb)
      cb(...arg)
    }
    typeof cb === 'function' && this.on(name, callback)
  }
}

export default EventBus

3. 页面通信:时光穿梭机⏳

getCurrentPages:页面栈的穿梭🛤️

getCurrentPages() 是时光穿梭机,可以获取到页面栈中的所有页面,就像翻找一本日记,最底下的那一页是你过去的脚印,最上面的是你现在的位置。

常见场景:
需要从当前页面获取上一个页面的状态,或者在返回上一个页面时更新数据。

Page({
  onLoad(){
    let pages = getCurrentPages()
    let lastPage = pages[pages.length-2]
    lastPage.setData({ list: [] })
  }
})

4. wxs 与 js 的双向通信:像两个邻居的电话聊天📞

在微信小程序中,除了常见的 JS 数据通信,wxs 也加入了这个大家庭。wxs 是微信小程序中的一个小“超人”,可以处理页面渲染性能问题,类似于 JavaScript,但它更轻量,更快,但又不是 JS。wxsjs 之间通信,就像两个住在隔壁的邻居,他们需要特别的“电话线”来联系。

1. wxs 对 js 通信:一声呼唤,callMethod📞

wxs 想要跟 js 说点事儿,比如告诉 JS:“嘿,我这边按钮被按了!”,那它可以直接打电话,用 callMethod() 实现。

常见场景:

当你在 wxs 脚本里执行了某些操作后,需要通知 JS 层来处理一些逻辑,譬如页面上的某个操作导致要更新数据时。

// mywxs.wxs
module.exports = {
  notifyJs: function() {
    this.callMethod('updateDataFromWxs', { value: 42 });
  }
}
// page.js
Page({
  updateDataFromWxs(data) {
    console.log("Received from wxs:", data.value);  // 输出: 42
    // 执行一些逻辑
  }
})

callMethod 是 wxs 和 JS 通信的“直通电话”,它只需要知道 JS 中有这么个方法就能打过去,非常方便。

2. js 对 wxs 通信:静静旁听,observer👂

WXS 想要得知 data 的变化时,就像在隔壁墙上悄悄安了个“监听器”,可以用 observer。每当 data 有什么风吹草动,WXS 都会有所感应。

常见场景:

当 wxml 中的数据变化时,WXS 可以感知到这些变化并作出相应的反应。比如,wxs 控制了某个数据的动画进度,当数据发生变化,WXS 通过 observer 及时响应并更新逻辑。

<wxs module="test" src="./test.wxs"></wxs>
<view change:prop="{{test.propObserver}}" prop="{{propValue}}" bindtouchmove="{{test.touchmove}}" class="movable"></view>
module.exports = {
    touchmove: function(event, instance) {
        console.log('log event', JSON.stringify(event))
    },
    propObserver: function(newValue, oldValue, ownerInstance, instance) {
        console.log('prop observer', newValue, oldValue)
    }
}

在这个例子中,WXS 通过 observers 像个旁听耳朵一样,默默监听着 propValue 的变化。

3. wxs 定时器和 requestAnimationFrame:时间的掌控者⌛

wxs 不仅仅能做简单的计算,它还能掌控时间!这意味着 wxs 也可以用定时器(setTimeoutsetInterval)以及 requestAnimationFrame 来操控动画的时间进度。简直就是个时间魔法师啊!

常见场景:

如果需要在渲染层处理动画而不依赖于 JS,wxs 就可以上场了。它通过 requestAnimationFrame 来保持动画的流畅性,或者通过 setTimeout 来精确控制某个操作的延迟执行。

// mywxs.wxs
var count = 0;
function updateCounter() {
  count++;
  console.log("Counter: " + count);
  requestAnimationFrame(updateCounter);  // 每帧更新一次计数
}

setTimeout(function(){
  console.log("This is a delay in wxs");
}, 1000);

updateCounter();

wxs 就像个“时间魔术师”,让你在视图层轻松处理定时任务和动画渲染。笔者也会在之后的文章介绍一个详细的使用案例。

4. wxs 之间的通信:邻里间的资源共享🛠️

两个 wxs 模块之间也可以互相调用,就像两个邻居互相借工具用。不过,它们之间必须在同一个“街区”(同一个页面上下文)才能彼此联系,通过 require 来引入并使用对方的资源。

常见场景:

当你有一些公共的工具函数,比如数据转换、格式化等等,想要在多个 wxs 文件中复用这些逻辑,就可以使用 require

// tools.wxs
function formatData(data) {
  return "Formatted: " + data;
}
module.exports = {
  formatData: formatData
}
// main.wxs
var tools = require('tools.wxs');

function displayData() {
  var data = tools.formatData("Hello World");
  console.log(data);  // 输出: Formatted: Hello World
}
displayData();

注意:这两个 wxs 模块就像两个在同一个街区里的好邻居,互相借用工具非常方便,但如果他们不住在同一个街区,那就很抱歉,得各干各的。