小程序自定义组件开发规范
一个小程序组件由4个文件组成,分别是wxml
、wxss
、json
、js
,本规范只关注组件的js
,其它自行查看。
在自定义组件的 js
文件中,需要使用 Component()
来注册组件,Component是一个构造器,可用于定义组件,调用Component构造器时可以指定组件的属性、数据、方法等。
Component
的变量可以分为以下2种类型:
-
properties
:组件外部通过组件属性的方式传入内部的数据。- 可用于wxml渲染
-
不能做会修改数据的运算操作,如果必须要修改数据,可以先把数据赋值给组件的
data
,例如:this.data.a = this.properties.a
,再去做运算操作,有以下两种情况:- 如果
this.properties.a
的数据是基本数据类型,则直接赋值 - 如果
this.properties.a
的数据是引用数据类型,则需要深拷贝一个新的数据之后,再赋值
- 如果
-
data
:组件内部声明的数据- 主要用于wxml渲染
- 可以做任何的运算符操作
Component
的函数可以分为以下几种类型:
-
life-cycle-function
:组件生命周期函数 -
event-function
:在组件的methods
下自定义的事件响应函数,与wxml的事件绑定一一对应 -
commen-function
:在组件的methods
下自定义的公共函数,供life-cycle-function
与event-function
调用 -
request-function
:在组件的methods
下自定义的异步请求数据的函数
在实际的代码中,我们利用注释把变量和函数分为以上定义的几种类型。
下面以小程序的语音消息组件为例:
文件路径:components/voice-message
import { isCorrectVal } from '../../utils/index';const app = getApp();Component({ properties: { // work:作业的语音 comment:评论的语音 type: { type: String, value: 'work' }, // 语音的地址 voiceUrl: { type: String, value: '' }, // 音频的长度 voiceLength: { type: Number, value: 0 } }, data: { unsubscribe: function() {}, model: { loading: false, render: false, id: 0, voiceLength: 0, innerAudioContext: null, playing: false, trumpetStatus: [false, false, true], btnLength: '0' } }, /** * life-cycle-function * @description 初始化组件 */ attached: function() { this.data.unsubscribe = app.soundScheduler.subscribe( 'beforePlay', () => { this.data.model.innerAudioContext.stop(); } ); if (!isCorrectVal(this.properties.voiceUrl)) { throw new Error('音频地址错误'); } /* 计算音频按钮长度 */ let base = 40; // 10s内基础长度 let step = 20; // 每10s增加的长度 let stepNum = 0; let length = 40; // 按钮初始长度 if (this.properties.type === 'comment') { base = 30; step = 15; length = 30; } if (this.properties.voiceLength > 10) { stepNum = Math.ceil((this.properties.voiceLength - 10) / 10); } length = base + step * stepNum; this.setData({ 'model.btnLength': length, 'model.voiceLength': this.properties.voiceLength >= 2 ? this.properties.voiceLength : 2 }); this.data.model.innerAudioContext = wx.createInnerAudioContext(); this.data.model.innerAudioContext.obeyMuteSwitch = false; this.data.model.innerAudioContext.src = this.properties.voiceUrl; this.data.model.innerAudioContext.onPlay(() => { this.onPlay(); }); this.data.model.innerAudioContext.onStop(res => { this.onStop(); }); this.data.model.innerAudioContext.onEnded(res => { this.onStop(); }); this.data.model.innerAudioContext.onError(res => { this.onError(res); }); }, methods: { /** * event-function * @description 切换音频播放状态(播放/停止) */ togglePlay: function() { if (this.data.model.loading) return; if (this.data.model.playing) { this.data.model.innerAudioContext.stop(); } else { this.setData( { 'model.loading': true }, () => { app.soundScheduler.dispatch('beforePlay'); app.videoContext.pause(); this.data.model.innerAudioContext.play(); setTimeout(() => { if (this.data.model.loading) { this.setData({ 'model.loading': false }); } }, 3000); } ); } }, /** * common-function * @description 音频开始播放触发时的处理函数 */ onPlay: function() { this.setData( { 'model.loading': false }, () => { this.running(); } ); }, /** * common-function * @description 音频停止播放或者播放结束时的处理函数 */ onStop: function() { this.stop(); }, /** * common-function * @description 音频播放错误时的处理函数 */ onError: function(res) { console.log(res); this.setData( { 'model.loading': false }, () => { this.stop(); } ); }, /** * common-function * @description 启动音频小喇叭动画 */ running: function() { let vm = this; vm.data.model.playing = true; let num = 1; let idx = 1; let timer = null; function animation() { if (!vm.data.model.playing) { clearTimeout(timer); vm.setData({ 'model.trumpetStatus': [false, false, true] }); return; } switch (idx) { case 1: vm.setData({ 'model.trumpetStatus': [true, false, false] }); break; case 2: vm.setData({ 'model.trumpetStatus': [false, true, false] }); break; case 3: vm.setData({ 'model.trumpetStatus': [false, false, true] }); break; } ++idx; if (idx === 4) { idx = 1; } ++num; timer = setTimeout(animation, 600); } timer = setTimeout(animation, 600); }, /** * common-function * @description 停止音频小喇叭动画 */ stop: function() { this.data.model.playing = false; } }, /** * life-cycle-function * @description 卸载组件 */ detached: function() { this.data.model.innerAudioContext.stop(); this.data.unsubscribe(); },});
如果你已经看完了代码,那么对这个组件的 代码执行过程 是否心里已经有数?
这个组件的代码执行过程是这样的:1. 在生命周期钩子函数attached中初始化组件2. 组件挂载并渲染完成,到达可响应用户操作的状态(这个步骤由小程序自动执行,无需写额外的代码)3. 响应用户操作 - 用户点击语音消息,如果语音没在播放,则播放语音 - 用户点击语音消息,如果语音正在播放,则停止播放4. 卸载组件
如果心里还没数,那把除了life-cycle-function
和events-function
的之外的代码都忽略掉,再看看组件生命周期钩子函数和用户交互事件响应函数的代码与注释呢?