基础组件

框架为开发者提供了一系列基础组件,开发者可以通过组合这些基础组件进行快速开发。

详细介绍请参考组件文档

自定义组件

从小程序基础库版本 1.6.3 开始,小程序支持简洁的组件化编程。所有自定义组件相关特性都需要基础库版本 1.6.3 或更高。

开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。

创建自定义组件

类似于页面,一个自定义组件由 json wxml wxss js 4个文件组成。要编写一个自定义组件,首先需要在 json 文件中进行自定义组件声明(将 component 字段设为 true 可这一组文件设为自定义组件):

{"component": true
}

同时,还要在 wxml 文件中编写组件模版,在 wxss 文件中加入组件样式,它们的写法与页面的写法类似。具体细节和注意事项参见 组件模版和样式 。

代码示例:

<!-- 这是自定义组件的内部WXML结构 -->
<view class="inner">{{innerText}}
</view>
<slot></slot>
/* 这里的样式只应用于这个自定义组件 */
.inner {color: red;
}

注意:在组件wxss中不应使用ID选择器、属性选择器和标签名选择器。

在自定义组件的 js 文件中,需要使用 Component() 来注册组件,并提供组件的属性定义、内部数据和自定义方法。

组件的属性值和内部数据将被用于组件 wxml 的渲染,其中,属性值是可由组件外部传入的。更多细节参见 Component构造器 。

代码示例:

Component({properties: {// 这里定义了innerText属性,属性值可以在组件使用时指定innerText: {type: String,value: 'default value',}},data: {// 这里是一些组件内部数据someData: {}},methods: {// 这里是一个自定义方法customMethod: function(){}}
})

使用自定义组件

使用已注册的自定义组件前,首先要在页面的 json 文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径:

{"usingComponents": {"component-tag-name": "path/to/the/custom/component"}
}

这样,在页面的 wxml 中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。

代码示例:

在开发者工具中预览效果

<view><!-- 以下是对一个自定义组件的引用 --><component-tag-name inner-text="Some text"></component-tag-name>
</view>

自定义组件的 wxml 节点结构在与数据结合之后,将被插入到引用位置内。

Tips:

  • 对于基础库的1.5.x版本, 1.5.7 也有部分自定义组件支持。
  • 因为WXML节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。
  • 自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用 usingComponents 字段)。
  • 自定义组件和使用自定义组件的页面所在项目根目录名不能以“wx-”为前缀,否则会报错。
  • 旧版本的基础库不支持自定义组件,此时,引用自定义组件的节点会变为默认的空节点。

组件模版和样式

类似于页面,自定义组件拥有自己的 wxml 模版和 wxss 样式。

组件模版

组件模版的写法与页面模板相同。组件模版与组件数据结合后生成的节点树,将被插入到组件的引用位置上。

在组件模板中可以提供一个 <slot> 节点,用于承载组件引用时提供的子节点。

代码示例:

在开发者工具中预览效果

<!-- 组件模板 -->
<view class="wrapper"><view>这里是组件的内部节点</view><slot></slot>
</view>
<!-- 引用组件的页面模版 -->
<view><component-tag-name><!-- 这部分内容将被放置在组件 <slot> 的位置上 --><view>这里是插入到组件slot中的内容</view></component-tag-name>
</view>

注意,在模版中引用到的自定义组件及其对应的节点名需要在 json 文件中显式定义,否则会被当作一个无意义的节点。除此以外,节点名也可以被声明为抽象节点。

组件wxml的slot

在组件的wxml中可以包含 slot 节点,用于承载组件使用者提供的wxml结构。

默认情况下,一个组件的wxml中只能有一个slot。需要使用多slot时,可以在组件js中声明启用。

Component({options: {multipleSlots: true // 在组件定义时的选项中启用多slot支持},properties: { /* ... */ },methods: { /* ... */ }
})

此时,可以在这个组件的wxml中使用多个slot,以不同的 name 来区分。

<!-- 组件模板 -->
<view class="wrapper"><slot name="before"></slot><view>这里是组件的内部细节</view><slot name="after"></slot>
</view>

使用时,用 slot 属性来将节点插入到不同的slot上。

<!-- 引用组件的页面模版 -->
<view><component-tag-name><!-- 这部分内容将被放置在组件 <slot name="before"> 的位置上 --><view slot="before">这里是插入到组件slot name="before"中的内容</view><!-- 这部分内容将被放置在组件 <slot name="after"> 的位置上 --><view slot="after">这里是插入到组件slot name="after"中的内容</view></component-tag-name>
</view>

组件样式

组件对应 wxss 文件的样式,只对组件wxml内的节点生效。编写组件样式时,需要注意以下几点:

  • 组件和引用组件的页面不能使用id选择器(#a)、属性选择器([a])和标签名选择器,请改用class选择器。
  • 组件和引用组件的页面中使用后代选择器(.a .b)在一些极端情况下会有非预期的表现,如遇,请避免使用。
  • 子元素选择器(.a>.b)只能用于 view 组件与其子节点之间,用于其他组件可能导致非预期的情况。
  • 继承样式,如 font 、 color ,会从组件外继承到组件内。
  • 除继承样式外, app.wxss 中的样式、组件所在页面的的样式对自定义组件无效。
#a { } /* 在组件中不能使用 */
[a] { } /* 在组件中不能使用 */
button { } /* 在组件中不能使用 */
.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */

除此以外,组件可以指定它所在节点的默认样式,使用 :host 选择器(需要包含基础库 1.7.2 或更高版本的开发者工具支持)。

代码示例:

在开发者工具中预览效果

/* 组件 custom-component.wxss */
:host {color: yellow;
}
<!-- 页面的 WXML -->
<custom-component>这段文本是黄色的</custom-component>

外部样式类

有时,组件希望接受外部传入的样式类(类似于 view 组件的 hover-class 属性)。此时可以在 Component 中用 externalClasses 定义段定义若干个外部样式类。这个特性从小程序基础库版本 1.9.90 开始支持。

注意:在同一个节点上使用普通样式类和外部样式类时,两个类的优先级是未定义的,因此最好避免这种情况。

代码示例:

/* 组件 custom-component.js */
Component({externalClasses: ['my-class']
})
<!-- 组件 custom-component.wxml -->
<custom-component class="my-class">这段文本的颜色由组件外的 class 决定</custom-component>

这样,组件的使用者可以指定这个样式类对应的 class ,就像使用普通属性一样。

代码示例:

在开发者工具中预览效果

<!-- 页面的 WXML -->
<custom-component my-class="red-text" />
.red-text {color: red;
}

Component构造器

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

定义段 类型 是否必填 描述
properties Object Map 组件的对外属性,是属性名到属性设置的映射表,属性设置中可包含三个字段, type表示属性类型、 value 表示属性初始值、 observer 表示属性值被更改时的响应函数
data Object 组件的内部数据,和 properties 一同用于组件的模版渲染
methods Object 组件的方法,包括事件响应函数和任意的自定义方法,关于事件响应函数的使用,参见 组件事件
behaviors String Array 类似于mixins和traits的组件间代码复用机制,参见 behaviors
created Function 组件生命周期函数,在组件实例进入页面节点树时执行,注意此时不能调用 setData
attached Function 组件生命周期函数,在组件实例进入页面节点树时执行
ready Function 组件生命周期函数,在组件布局完成后执行,此时可以获取节点信息(使用 SelectorQuery )
moved Function 组件生命周期函数,在组件实例被移动到节点树另一个位置时执行
detached Function 组件生命周期函数,在组件实例被从页面节点树移除时执行
relations Object 组件间关系定义,参见 组件间关系
externalClasses String Array 组件接受的外部样式类,参见 外部样式类
options Object Map 一些组件选项,请参见文档其他部分的说明

生成的组件实例可以在组件的方法、生命周期函数和属性 observer 中通过 this 访问。组件包含一些通用属性和方法。

属性名 类型 描述
is String 组件的文件路径
id String 节点id
dataset String 节点dataset
data Object 组件数据,包括内部数据和属性值
方法名 参数 描述
setData Object newData 设置data并执行视图层渲染
hasBehavior Object behavior 检查组件是否具有 behavior (检查时会递归检查被直接或间接引入的所有behavior)
triggerEvent String name, Object detail, Object options 触发事件,参见 组件事件
createSelectorQuery   创建一个 SelectorQuery 对象,选择器选取范围为这个组件实例内
selectComponent String selector 使用选择器选择组件实例节点,返回匹配到的第一个组件实例对象
selectAllComponents String selector 使用选择器选择组件实例节点,返回匹配到的全部组件实例对象组成的数组
getRelationNodes String relationKey 获取所有这个关系对应的所有关联节点,参见 组件间关系

代码示例:

在开发者工具中预览效果

Component({behaviors: [],properties: {myProperty: { // 属性名type: String, // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)value: '', // 属性初始值(可选),如果未指定则会根据类型选择一个observer: function(newVal, oldVal){} // 属性被改变时执行的函数(可选),也可以写成在methods段中定义的方法名字符串, 如:'_propertyChange'},myProperty2: String // 简化的定义方式},data: {}, // 私有数据,可用于模版渲染// 生命周期函数,可以为函数,或一个在methods段中定义的方法名attached: function(){},moved: function(){},detached: function(){},methods: {onMyButtonTap: function(){this.setData({// 更新属性和数据的方法与更新页面数据的方法类似})},_myPrivateMethod: function(){// 内部方法建议以下划线开头this.replaceDataOnPath(['A', 0, 'B'], 'myPrivateData') // 这里将 data.A[0].B 设为 'myPrivateData'this.applyDataUpdates()},_propertyChange: function(newVal, oldVal) {}}})

注意:在 properties 定义段中,属性名采用驼峰写法(propertyName);在 wxml 中,指定属性值时则对应使用连字符写法(component-tag-name property-name="attr value"),应用于数据绑定时采用驼峰写法(attr="{{propertyName}}")。

Tips:

  • Component 构造器构造的组件也可以作为页面使用。
  • 使用 this.data 可以获取内部数据和属性值,但不要直接修改它们,应使用 setData 修改。
  • 生命周期函数无法在组件方法中通过 this 访问到。
  • 属性名应避免以 data 开头,即不要命名成 dataXyz 这样的形式,因为在 WXML 中, data-xyz="" 会被作为节点 dataset 来处理,而不是组件属性。
  • 在一个组件的定义和使用时,组件的属性名和data字段相互间都不能冲突(尽管它们位于不同的定义段中)。

组件事件

事件系统是组件间交互的主要形式。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。关于事件的基本概念和用法,参见 事件。

监听自定义组件事件的方法与监听基础组件事件的方法完全一致:

代码示例:

<!-- 当自定义组件触发“myevent”事件时,调用“onMyEvent”方法 -->
<component-tag-name bindmyevent="onMyEvent" />
<!-- 或者可以写成 -->
<component-tag-name bind:myevent="onMyEvent" />
Page({onMyEvent: function(e){e.detail // 自定义组件触发事件时提供的detail对象}
})

自定义组件触发事件时,需要使用 triggerEvent 方法,指定事件名、detail对象和事件选项:

代码示例:

在开发者工具中预览效果

<!-- 在自定义组件中 -->
<button bindtap="onTap">点击这个按钮将触发“myevent”事件</button>
Component({properties: {}methods: {onTap: function(){var myEventDetail = {} // detail对象,提供给事件监听函数var myEventOption = {} // 触发事件的选项this.triggerEvent('myevent', myEventDetail, myEventOption)}}
})

触发事件的选项包括:

选项名 类型 是否必填 默认值 描述
bubbles Boolean false 事件是否冒泡
composed Boolean false 事件是否可以穿越组件边界,为false时,事件将只能在引用组件的节点树上触发,不进入其他任何组件内部
capturePhase Boolean false 事件是否拥有捕获阶段

关于冒泡和捕获阶段的概念,请阅读 事件 章节中的相关说明。

代码示例:

在开发者工具中预览效果

// 页面 page.wxml
<another-component bindcustomevent="pageEventListener1"><my-component bindcustomevent="pageEventListener2"></my-component>
</another-component>
// 组件 another-component.wxml
<view bindcustomevent="anotherEventListener"><slot />
</view>
// 组件 my-component.wxml
<view bindcustomevent="myEventListener"><slot />
</view>
// 组件 my-component.js
Component({methods: {onTap: function(){this.triggerEvent('customevent', {}) // 只会触发 pageEventListener2this.triggerEvent('customevent', {}, { bubbles: true }) // 会依次触发 pageEventListener2 、 pageEventListener1this.triggerEvent('customevent', {}, { bubbles: true, composed: true }) // 会依次触发 pageEventListener2 、 anotherEventListener 、 pageEventListener1}}
})

behaviors

定义和使用 behaviors

behaviors 是用于组件间代码共享的特性,类似于一些编程语言中的“mixins”或“traits”。

每个 behavior 可以包含一组属性、数据、生命周期函数和方法,组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。每个组件可以引用多个 behavior 。 behavior 也可以引用其他 behavior 。

behavior 需要使用 Behavior() 构造器定义。

代码示例:

// my-behavior.js
module.exports = Behavior({behaviors: [],properties: {myBehaviorProperty: {type: String}},data: {myBehaviorData: {}},attached: function(){},methods: {myBehaviorMethod: function(){}}
})

组件引用时,在 behaviors 定义段中将它们逐个列出即可。

代码示例:

在开发者工具中预览效果

// my-component.js
var myBehavior = require('my-behavior')
Component({behaviors: [myBehavior],properties: {myProperty: {type: String}},data: {myData: {}},attached: function(){},methods: {myMethod: function(){}}
})

在上例中, my-component 组件定义中加入了 my-behavior ,而 my-behavior 中包含有 myBehaviorProperty 属性、 myBehaviorData 数据字段、 myBehaviorMethod 方法和一个 attached 生命周期函数。这将使得 my-component 中最终包含 myBehaviorProperty 、 myProperty 两个属性, myBehaviorData 、 myData 两个数据字段,和 myBehaviorMethod 、 myMethod 两个方法。当组件触发 attached 生命周期时,会依次触发 my-behavior 中的 attached 生命周期函数和 my-component 中的 attached 生命周期函数。

字段的覆盖和组合规则

组件和它引用的 behavior 中可以包含同名的字段,对这些字段的处理方法如下:

  • 如果有同名的属性或方法,组件本身的属性或方法会覆盖 behavior 中的属性或方法,如果引用了多个 behavior ,在定义段中靠后 behavior 中的属性或方法会覆盖靠前的属性或方法;
  • 如果有同名的数据字段,如果数据是对象类型,会进行对象合并,如果是非对象类型则会进行相互覆盖;
  • 生命周期函数不会相互覆盖,而是在对应触发时机被逐个调用。如果同一个 behavior 被一个组件多次引用,它定义的生命周期函数只会被执行一次。

内置 behaviors

自定义组件可以通过引用内置的 behavior 来获得内置组件的一些行为。

代码示例:

在开发者工具中预览效果

Component({behaviors: ['wx://form-field']
})

在上例中, wx://form-field 代表一个内置 behavior ,它使得这个自定义组件有类似于表单控件的行为。

内置 behavior 往往会为组件添加一些属性。在没有特殊说明时,组件可以覆盖这些属性来改变它的 type 或添加 observer 。

wx://form-field

使自定义组件有类似于表单控件的行为。 form 组件可以识别这些自定义组件,并在 submit 事件中返回组件的字段名及其对应字段值。这将为它添加以下两个属性。

属性名 类型 描述 最低版本
name String 在表单中的字段名 1.6.7
value 任意 在表单中的字段值 1.6.7

组件间关系

定义和使用组件间关系

有时需要实现这样的组件:

<custom-ul><custom-li> item 1 </custom-li><custom-li> item 2 </custom-li>
</custom-ul>

这个例子中, custom-ul 和 custom-li 都是自定义组件,它们有相互间的关系,相互间的通信往往比较复杂。此时在组件定义时加入 relations 定义段,可以解决这样的问题。示例:

在开发者工具中预览效果

// path/to/custom-ul.js
Component({relations: {'./custom-li': {type: 'child', // 关联的目标节点应为子节点linked: function(target) {// 每次有custom-li被插入时执行,target是该节点实例对象,触发在该节点attached生命周期之后},linkChanged: function(target) {// 每次有custom-li被移动后执行,target是该节点实例对象,触发在该节点moved生命周期之后},unlinked: function(target) {// 每次有custom-li被移除时执行,target是该节点实例对象,触发在该节点detached生命周期之后}}},methods: {_getAllLi: function(){// 使用getRelationNodes可以获得nodes数组,包含所有已关联的custom-li,且是有序的var nodes = this.getRelationNodes('path/to/custom-li')}},ready: function(){this._getAllLi()}
})
// path/to/custom-li.js
Component({relations: {'./custom-ul': {type: 'parent', // 关联的目标节点应为父节点linked: function(target) {// 每次被插入到custom-ul时执行,target是custom-ul节点实例对象,触发在attached生命周期之后},linkChanged: function(target) {// 每次被移动后执行,target是custom-ul节点实例对象,触发在moved生命周期之后},unlinked: function(target) {// 每次被移除时执行,target是custom-ul节点实例对象,触发在detached生命周期之后}}}
})

注意:必须在两个组件定义中都加入relations定义,否则不会生效。

关联一类组件

在开发者工具中预览效果

有时,需要关联的是一类组件,如:

<custom-form><view>input<custom-input></custom-input></view><custom-submit> submit </custom-submit>
</custom-form>

custom-form 组件想要关联 custom-input 和 custom-submit 两个组件。此时,如果这两个组件都有同一个behavior:

// path/to/custom-form-controls.js
module.exports = Behavior({// ...
})
// path/to/custom-input.js
var customFormControls = require('./custom-form-controls')
Component({behaviors: [customFormControls],relations: {'./custom-form': {type: 'ancestor', // 关联的目标节点应为祖先节点}}
})
// path/to/custom-submit.js
var customFormControls = require('./custom-form-controls')
Component({behaviors: [customFormControls],relations: {'./custom-form': {type: 'ancestor', // 关联的目标节点应为祖先节点}}
})

则在 relations 关系定义中,可使用这个behavior来代替组件路径作为关联的目标节点:

// path/to/custom-form.js
var customFormControls = require('./custom-form-controls')
Component({relations: {'customFormControls': {type: 'descendant', // 关联的目标节点应为子孙节点target: customFormControls}}
})

relations 定义段

relations 定义段包含目标组件路径及其对应选项,可包含的选项见下表。

选项 类型 是否必填 描述
type String 目标组件的相对关系,可选的值为 parent 、 child 、 ancestor 、 descendant
linked Function 关系生命周期函数,当关系被建立在页面节点树中时触发,触发时机在组件attached生命周期之后
linkChanged Function 关系生命周期函数,当关系在页面节点树中发生改变时触发,触发时机在组件moved生命周期之后
unlinked Function 关系生命周期函数,当关系脱离页面节点树时触发,触发时机在组件detached生命周期之后
target String 如果这一项被设置,则它表示关联的目标节点所应具有的behavior,所有拥有这一behavior的组件节点都会被关联

抽象节点

这个特性自小程序基础库版本 1.9.6 开始支持。

在组件中使用抽象节点

有时,自定义组件模版中的一些节点,其对应的自定义组件不是由自定义组件本身确定的,而是自定义组件的调用者确定的。这时可以把这个节点声明为“抽象节点”。

例如,我们现在来实现一个“选框组”(selectable-group)组件,它其中可以放置单选框(custom-radio)或者复选框(custom-checkbox)。这个组件的 wxml 可以这样编写:

<!-- selectable-group.wxml -->
<view wx:for="{{labels}}"><label><selectable disabled="{{false}}"></selectable>{{item}}</label>
</view>

其中,“selectable”不是任何在 json 文件的 usingComponents 字段中声明的组件,而是一个抽象节点。它需要在 componentGenerics 字段中声明:

{"componentGenerics": {"selectable": true}
}

使用包含抽象节点的组件

在使用 selectable-group 组件时,必须指定“selectable”具体是哪个组件:

<selectable-group generic:selectable="custom-radio" />

这样,在生成这个 selectable-group 组件的实例时,“selectable”节点会生成“custom-radio”组件实例。类似地,如果这样使用:

<selectable-group generic:selectable="custom-checkbox" />

“selectable”节点则会生成“custom-checkbox”组件实例。

注意:上述的 custom-radio 和 custom-checkbox 需要包含在这个 wxml 对应 json 文件的 usingComponents 定义段中。

{"usingComponents": {"custom-radio": "path/to/custom/radio","custom-checkbox": "path/to/custom/checkbox"}
}

抽象节点的默认组件

抽象节点可以指定一个默认组件,当具体组件未被指定时,将创建默认组件的实例。默认组件可以在 componentGenerics 字段中指定:

{"componentGenerics": {"selectable": {"default": "path/to/default/component"}}
}

Tips:

  • 节点的 generic 引用 generic:xxx="yyy" 中,值 yyy 只能是静态值,不能包含数据绑定。因而抽象节点特性并不适用于动态决定节点名的场景。

插件

插件的开发和使用自小程序基础库版本 1.9.6 开始支持。

插件是对一组 js 接口或自定义组件的封装,用于提供给第三方小程序调用。插件必须嵌入在其他小程序中才能被用户使用。

插件开发者可以像开发小程序一样编写一个插件并上传代码,在插件发布之后,其他小程序方可调用。小程序平台会托管插件代码,其他小程序调用时,上传的插件代码会随小程序一起下载运行。

相对于普通 js 文件或自定义组件,插件拥有更强的独立性,拥有独立的 API 接口、域名列表等,但同时会受到一些限制,如一些 API 无法调用或功能受限。

对于插件开发者,请阅读开发插件章节;对于插件使用者,请阅读使用插件章节。

开发插件

开发插件前,请阅读了解《小程序插件接入指南》了解开通流程及开放范围,并开通插件功能。如果未开通插件功能,将无法上传插件。

创建插件项目

插件类型的项目可以在开发者工具中直接创建。详情

新建插件类型的项目后,如果创建示例项目,则项目中将包含两个目录:

  • plugin 目录:插件代码目录。
  • miniprogram 目录:放置一个小程序,用于调试插件。
  • 此外,还可以加入一个 doc 目录,用于放置插件开发文档。

miniprogram 目录内容可以当成普通小程序来编写,用于插件调试、预览和审核。下面的内容主要介绍 plugin 的编写方法。

插件目录结构

一个插件可以包含若干个自定义组件,和一组js接口。插件的目录内容如下:

plugin
├── components
│   ├── hello-component.js   // 插件提供的自定义组件(可以有多个)
│   ├── hello-component.json
│   ├── hello-component.wxml
│   └── hello-component.wxss
├── index.js                 // 插件的 js 接口
└── plugin.json              // 插件配置文件

插件配置文件

插件配置文件 plugin.json 主要说明有哪些自定义组件可以供插件外部调用,并标识哪个js文件是插件的js接口文件,如:

代码示例:

{"publicComponents": {"hello-component": "components/hello-component"},"main": "index.js"
}

插件对外接口

插件内的自定义组件与普通的自定义组件相仿。插件可以定义若干个自定义组件,这些自定义组件都可以在插件内相互引用。其中,提供给外部使用的自定义组件,必须在插件配置文件中显式声明。

插件的 js 接口文件 index.js 中可以 export 一些 js 接口,插件的使用者可以使用 requirePlugin 来获得这些接口。

代码示例:

module.exports = {hello: function() {console.log('Hello plugin!')}
}

预览、上传和发布

插件可以像小程序一样预览和上传,但插件没有体验版。

插件会同时有多个线上版本,由使用插件的小程序决定具体使用的版本号。

注意:目前,手机预览插件时将使用一个特殊分配的小程序(即“插件开发助手”)来套用这个插件,这个小程序的 appid 与插件的 appid 不同。服务器端处理插件的网络请求时请留心这个问题。

插件开发文档

除了插件代码本身,小程序开发者可以另外上传一份插件开发文档。这份文档必须放置在插件项目根目录中的 doc 目录下,目录结构如下:

doc
├── README.md   // 插件文档,应为 markdown 格式
└── picture.jpg // 其他资源文件,仅支持图片

其中,引用到的图片资源不能是网络图片,必须放在这个目录下。编辑 README.md 之后,可以使用开发者工具预览插件文档和单独上传插件文档。

上传之后,可以使用帐号和密码登录管理后台,在插件设置页面中找到发布插件的链接指引。

插件请求签名

插件在使用 wx.request 等 API 发送网络请求时,将会额外携带一个签名 HostSign ,用于验证请求来源于小程序插件。这个签名位于请求头中,形如:

X-WECHAT-HOSTSIGN: {"noncestr":"NONCESTR", "timestamp":"TIMESTAMP", "signature":"SIGNATURE"}

其中, NONCESTR 是一个随机字符串, TIMESTAMP 是生成这个随机字符串和 SIGNATURE 的 UNIX 时间戳。它们是用于计算签名 SIGNATRUE 的参数,签名算法为:

SIGNATURE = sha1([APPID, NONCESTR, TIMESTAMP, TOKEN].sort().join(''))

具体来说,这个算法分为几个步骤:

  1. sort 对 APPID NONCESTR TIMESTAMP TOKEN 四个值表示成字符串形式,按照字典序排序(同 JavaScript 数组的 sort 方法);
  2. join 将排好序的四个字符串直接连接在一起;
  3. 对连接结果使用 sha1 算法,其结果即 SIGNATURE 。

插件开发者可以在服务器上使用这个算法校验签名。其中, APPID 是所在小程序的 AppId ; TOKEN 是插件 Token ,可以在小程序插件基本设置中找到。

自基础库版本 2.0.7 开始,在小程序运行期间,若网络状况正常, NONCESTR 和 TIMESTAMP 会每 10 分钟变更一次。如有必要,可以通过判断 TIMESTAMP 来确定当前签名是否依旧有效。

使用插件

申请使用插件

在使用插件前,首先要在小程序管理后台的“设置-第三方服务-插件管理”中添加插件。开发者可登录小程序管理后台,通过 appId 查找插件并添加。插件开发者通过申请后,方可在小程序中使用相应的插件。

引入插件代码包

对于插件的使用者,使用插件前要在 app.json 中声明需要使用的插件,例如:

{"plugins": {"myPlugin": {"version": "1.0.0","provider": "wxxxxxxxxxxxxxxxxx"}}
}

如上例所示, plugins 定义段中可以包含多个插件声明,每个插件声明中都必须指明插件的 appid 和需要使用的版本号。

使用插件的 js 接口

在引入插件代码包之后,就可以在这个小程序中使用插件提供的自定义组件或者 js 接口。

如果需要使用插件的 js 接口,可以使用 requirePlugin 方法:

var myPluginInterface = requirePlugin('myPlugin')myPluginInterface.hello()

使用插件的自定义组件

使用插件提供的自定义组件,和使用普通自定义组件的方式相仿。在 json 文件定义需要引入的自定义组件时,使用 plugin:// 协议即可,例如:

{"usingComponents": {"hello-component": "plugin://myPlugin/hello-component"}
}

出于对插件的保护,插件提供的自定义组件在使用上有一定的限制:

  • 页面中的 this.selectComponent 接口无法获得插件的自定义组件实例对象;
  • wx.createSelectorQuery 等接口的 >>> 选择器无法选入插件内部。

插件调用 API 的限制

插件可以调用的 API 与小程序不同,主要有两个区别:

  • 插件的请求域名列表与小程序相互独立;
  • 一些 API 不允许插件调用。

目前,允许插件调用的 API 及其对应版本要求如下。

API 最低版本
wx.addPhoneContact 1.9.6
wx.arrayBufferToBase64 1.9.6
wx.base64ToArrayBuffer 1.9.6
wx.canvasGetImageData 1.9.6
wx.canvasPutImageData 1.9.6
wx.canvasToTempFilePath 1.9.6
wx.chooseImage 1.9.6
wx.chooseLocation 1.9.6
wx.chooseVideo 1.9.6
wx.closeBLEConnection 1.9.6
wx.closeBluetoothAdapter 1.9.6
wx.connectSocket 1.9.6
wx.createAnimation 1.9.6
wx.createAudioContext 1.9.6
wx.createBLEConnection 1.9.6
wx.createCameraContext 1.9.6
wx.createCanvasContext 1.9.6
wx.createInnerAudioContext 1.9.6
wx.createIntersectionObserver 1.9.6
wx.createLivePlayerContext 1.9.6
wx.createLivePusherContext 1.9.6
wx.createMapContext 1.9.6
wx.createSelectorQuery 1.9.6
wx.createVideoContext 1.9.6
wx.downloadFile 1.9.6
wx.getBLEDeviceCharacteristics 1.9.6
wx.getBLEDeviceServices 1.9.6
wx.getBackgroundAudioManager 1.9.6
wx.getBackgroundAudioPlayerState 1.9.6
wx.getBeacons 1.9.6
wx.getBluetoothAdapterState 1.9.6
wx.getBluetoothDevices 1.9.6
wx.getClipboardData 1.9.6
wx.getConnectedBluetoothDevices 1.9.6
wx.getImageInfo 1.9.6
wx.getLocation 1.9.6
wx.getNetworkType 1.9.6
wx.getRecorderManager 1.9.94
wx.getScreenBrightness 1.9.6
wx.getStorage 1.9.6
wx.getStorageSync 1.9.6
wx.getSystemInfo 1.9.6
wx.getSystemInfoSync 1.9.6
wx.hideLoading 1.9.6
wx.hideToast 1.9.6
wx.makePhoneCall 1.9.6
wx.makeVoIPCall 1.9.6
wx.notifyBLECharacteristicValueChange 1.9.6
wx.notifyBLECharacteristicValueChanged 1.9.6
wx.onAccelerometerChange 1.9.6
wx.onBLECharacteristicValueChange 1.9.6
wx.onBLEConnectionStateChange 1.9.6
wx.onBLEConnectionStateChanged 1.9.6
wx.onBackgroundAudioPause 1.9.6
wx.onBackgroundAudioPlay 1.9.6
wx.onBackgroundAudioStop 1.9.6
wx.onBeaconServiceChange 1.9.6
wx.onBeaconUpdate 1.9.6
wx.onBluetoothAdapterStateChange 1.9.6
wx.onBluetoothDeviceFound 1.9.6
wx.onCompassChange 1.9.6
wx.onNetworkStatusChange 1.9.6
wx.onUserCaptureScreen 1.9.6
wx.openBluetoothAdapter 1.9.6
wx.openLocation 1.9.6
wx.pauseBackgroundAudio 1.9.6
wx.pauseVoice 1.9.6
wx.playBackgroundAudio 1.9.6
wx.playVoice 1.9.6
wx.previewImage 1.9.6
wx.readBLECharacteristicValue 1.9.6
wx.removeStorage 1.9.6
wx.removeStorageSync 1.9.6
wx.reportAnalytics 1.9.6
wx.request 1.9.6
wx.saveImageToPhotosAlbum 1.9.6
wx.saveVideoToPhotosAlbum 1.9.6
wx.scanCode 1.9.6
wx.seekBackgroundAudio 1.9.6
wx.setClipboardData 1.9.6
wx.setKeepScreenOn 1.9.6
wx.setScreenBrightness 1.9.6
wx.setStorage 1.9.6
wx.setStorageSync 1.9.6
wx.showActionSheet 1.9.6
wx.showLoading 1.9.6
wx.showModal 1.9.6
wx.showToast 1.9.6
wx.startAccelerometer 1.9.6
wx.startBeaconDiscovery 1.9.6
wx.startBluetoothDevicesDiscovery 1.9.6
wx.startCompass 1.9.6
wx.startRecord 1.9.6
wx.stopAccelerometer 1.9.6
wx.stopBackgroundAudio 1.9.6
wx.stopBeaconDiscovery 1.9.6
wx.stopBluetoothDevicesDiscovery 1.9.6
wx.stopCompass 1.9.6
wx.stopRecord 1.9.6
wx.stopVoice 1.9.6
wx.uploadFile 1.9.6
wx.vibrateLong 1.9.6
wx.vibrateShort 1.9.6
wx.writeBLECharacteristicValue 1.9.6

分包加载

微信 6.6 客户端,1.7.3 及以上基础库开始支持,请更新至最新客户端版本,开发者工具请使用 1.01.1712150 及以上版本,可点此下载

某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。

在构建小程序分包项目时,构建会输出一个或多个功能的分包,其中每个分包小程序必定含有一个主包,所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本,而分包则是根据开发者的配置进行划分。

在小程序启动时,默认会下载主包并启动主包内页面,如果用户需要打开分包内某个页面,客户端会把对应分包下载下来,下载完成后再进行展示。

目前小程序分包大小有以下限制:

  • 整个小程序所有分包大小不超过 4M
  • 单个分包/主包大小不能超过 2M

对小程序进行分包,可以优化小程序首次启动的下载时间,以及在多团队共同开发时可以更好的解耦协作。

使用方法

假设支持分包的小程序目录结构如下:

├── app.js
├── app.json
├── app.wxss
├── packageA
│   └── pages
│       ├── cat
│       └── dog
├── packageB
│   └── pages
│       ├── apple
│       └── banana
├── pages
│   ├── index
│   └── logs
└── utils

开发者通过在 app.json subPackages 字段声明项目分包结构:

{"pages":["pages/index","pages/logs"],"subPackages": [{"root": "packageA","pages": ["pages/cat","pages/dog"]}, {"root": "packageB","pages": ["pages/apple","pages/banana"]}]
}

打包原则

  • 声明 subPackages 后,将按 subPackages 配置路径进行打包,subPackages 配置路径外的目录将被打包到 app(主包) 中
  • app(主包)也可以有自己的 pages(即最外层的 pages 字段)
  • subPackage 的根目录不能是另外一个 subPackage 内的子目录
  • 首页的 TAB 页面必须在 app(主包)内

引用原则

  • packageA 无法 require packageB JS 文件,但可以 require app、自己 package 内的 JS 文件
  • packageA 无法 import packageB 的 template,但可以 require app、自己 package 内的 template
  • packageA 无法使用 packageB 的资源,但可以使用 app、自己 package 内的资源

低版本兼容

由微信后台编译来处理旧版本客户端的兼容,后台会编译两份代码包,一份是分包后代码,另外一份是整包的兼容代码。 新客户端用分包,老客户端还是用的整包,完整包会把各个 subpackage 里面的路径放到 pages 中。

示例项目

下载 小程序示例分包加载版源码

多线程 Worker

一些异步处理的任务,可以放置于 Worker 中运行,待运行结束后,再把结果返回到小程序主线程。Worker 运行于一个单独的全局上下文与线程中,不能直接调用主线程的方法。 Worker 与主线程之间的数据传输,双方使用 Worker.postMessage() 来发送数据,Worker.onMessage() 来接收数据,传输的数据并不是直接共享,而是被复制的。

步骤

在开发者工具中预览效果

1. 配置 Worker 信息

在 app.json 中可配置 Worker 代码放置的目录,目录下的代码将被打包成一个文件:

配置示例:

{"workers": "workers"
}
2. 添加 Worker 代码文件

根据步骤 1 中的配置,在代码目录下新建以下两个入口文件:

workers/request/index.js
workers/request/utils.js
workers/response/index.js

添加后,目录结构如下:

├── app.js
├── app.json
├── project.config.json
└── workers├── request│   ├── index.js│   └── utils.js└── response└── index.js

3. 编写 Worker 代码

在 workers/request/index.js 编写 Worker 响应代码

var utils = require('./utils')// 在 Worker 线程执行上下文会全局暴露一个 `worker` 对象,直接调用 worker.onMeesage/postMessage 即可
worker.onMessage(function (res) {console.log(res)
})

4. 在主线程中初始化 Worker

在主线程的代码 app.js 中初始化 Worker

var worker = wx.createWorker('workers/request/index.js') // 文件名指定 worker 的入口文件路径,绝对路径

5. 主线程向 Worker 发送消息

worker.postMessage({msg: 'hello worker'
})

worker 对象的其它接口请看 worker接口说明

Tips

  1. Worker 最大并发数量限制为 1 个,创建下一个前请用 Worker.terminate() 结束当前 Worker
  2. Worker 内代码只能 require 指定 Worker 路径内的文件,无法引用其它路径
  3. Worker 的入口文件由 wx.createWorker() 时指定,开发者可动态指定 Worker 入口文件
  4. Worker 内不支持 wx 系列的 API
  5. Workers 之间不支持发送消息

基础库

基础库与客户端之间的关系

小程序的能力需要微信客户端来支撑,每一个基础库都只能在对应的客户端版本上运行,高版本的基础库无法兼容低版本的微信客户端。

关于基础库的兼容方法,可以查看「兼容处理」章节。

基础库更新时机

为了避免新版本的基础库给线上小程序带来未知的影响,微信客户端都是携带 上一个稳定版 的基础库发布的。

在新版本客户端发布后,再通过后台灰度新版本基础库,灰度时长一般为 12 小时,在灰度结束后,用户设备上才会有新版本的基础库。

以微信 6.5.8 为例,客户端在发布时携带的是 1.1.1 基础库(6.5.7 上已全量的稳定版)发布,在 6.5.8 发布后,我们再通过后台灰度 1.2.0 基础库。

基础库版本分布

更新时间:2018 年 6 月 2 日

全部

基础库版本 用户占比
2.0.9 88.48%
1.9.97 6.76%

iOS

基础库版本 用户占比
2.0.9 87.73%
1.9.97 7.05%

Android

基础库版本 用户占比
2.0.9 88.77%
1.9.97 6.65%

兼容

小程序的功能不断的增加,但是旧版本的微信客户端并不支持新功能,所以在使用这些新能力的时候需要做兼容。

文档会在组件,API等页面描述中带上各个功能所支持的版本号。

可以通过 wx.getSystemInfo 或者 wx.getSystemInfoSync 获取到小程序的基础库版本号。

也可以通过 wx.canIUse 详情 来判断是否可以在该基础库版本下直接使用对应的API或者组件

兼容方式 - 版本比较

微信客户端和小程序基础库的版本号风格为 Major.Minor.Patch(主版本号.次版本号.修订号)。 开发者可以根据版本号去做兼容,以下为参考代码:

function compareVersion(v1, v2) {v1 = v1.split('.')v2 = v2.split('.')var len = Math.max(v1.length, v2.length)while (v1.length < len) {v1.push('0')}while (v2.length < len) {v2.push('0')}for (var i = 0; i < len; i++) {var num1 = parseInt(v1[i])var num2 = parseInt(v2[i])if (num1 > num2) {return 1} else if (num1 < num2) {return -1}}return 0
}compareVersion('1.11.0', '1.9.9')
// 1

兼容方式 - 接口

对于新增的 API,可以用以下代码来判断是否支持用户的手机。

if (wx.openBluetoothAdapter) {wx.openBluetoothAdapter()
} else {// 如果希望用户在最新版本的客户端上体验您的小程序,可以这样子提示wx.showModal({title: '提示',content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'})
}

兼容方式 - 参数

对于 API 的参数或者返回值有新增的参数,可以判断用以下代码判断。

wx.showModal({success: function(res) {if (wx.canIUse('showModal.cancel')) {console.log(res.cancel)}}
})

兼容方式 - 组件

对于组件,新增的组件或属性在旧版本上不会被处理,不过也不会报错。如果特殊场景需要对旧版本做一些降级处理,可以这样子做。

Page({data: {canIUse: wx.canIUse('cover-view')}
})
<video controls="{{!canIUse}}"><cover-view wx:if="{{canIUse}}">play</cover-view>
</video>

运行机制

小程序启动会有两种情况,一种是「冷启动」,一种是「热启动」。 假如用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时无需重新启动,只需将后台态的小程序切换到前台,这个过程就是热启动;冷启动指的是用户首次打开或小程序被微信主动销毁后再次打开的情况,此时小程序需要重新加载启动。

更新机制

小程序冷启动时如果发现有新版本,将会异步下载新版本的代码包,并同时用客户端本地的包进行启动,即新版本的小程序需要等下一次冷启动才会应用上。 如果需要马上应用最新版本,可以使用 wx.getUpdateManager API 进行处理。

运行机制

  • 小程序没有重启的概念
  • 当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是5分钟)会被微信主动销毁
  • 当短时间内(5s)连续收到两次以上收到系统内存告警,会进行小程序的销毁

再次打开逻辑

基础库 1.4.0 开始支持,低版本需做兼容处理

用户打开小程序的预期有以下两类场景:

A. 打开首页: 场景值有 1001, 1019, 1022, 1023, 1038, 1056

B. 打开小程序指定的某个页面: 场景值为除 A 以外的其他

当再次打开一个小程序逻辑如下:

上一次的场景 当前打开的场景 效果
A A 保留原来的状态
B A 清空原来的页面栈,打开首页(相当于执行 wx.reLaunch 到首页)
A 或 B B 清空原来的页面栈,打开指定页面(相当于执行 wx.reLaunch 到指定页)

性能

目前,我们提供了两种性能分析工具,和几个性能优化上的建议,开发者可以参考使用。

  1. 分析工具

  2. 优化建议

setData

setData 是小程序开发中使用最频繁的接口,也是最容易引发性能问题的接口。在介绍常见的错误用法前,先简单介绍一下 setData 背后的工作原理。

工作原理

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

而 evaluateJavascript 的执行会受很多方面的影响,数据到达视图层并不是实时的。

常见的 setData 操作错误

1. 频繁的去 setData

在我们分析过的一些案例里,部分小程序会非常频繁(毫秒级)的去setData,其导致了两个后果:

  • Android 下用户在滑动时会感觉到卡顿,操作反馈延迟严重,因为 JS 线程一直在编译执行渲染,未能及时将用户操作事件传递到逻辑层,逻辑层亦无法及时将操作处理结果及时传递到视图层;
  • 渲染有出现延时,由于 WebView 的 JS 线程一直处于忙碌状态,逻辑层到页面层的通信耗时上升,视图层收到的数据消息时距离发出时间已经过去了几百毫秒,渲染的结果并不实时;

2. 每次 setData 都传递大量新数据

setData的底层实现可知,我们的数据传输实际是一次 evaluateJavascript 脚本过程,当数据量过大时会增加脚本的编译执行时间,占用 WebView JS 线程,

3. 后台态页面进行 setData

当页面进入后台态(用户不可见),不应该继续去进行setData,后台态页面的渲染用户是无法感受的,另外后台态页面去setData也会抢占前台页面的执行。

图片资源

目前图片资源的主要性能问题在于大图片和长列表图片上,这两种情况都有可能导致 iOS 客户端内存占用上升,从而触发系统回收小程序页面。

图片对内存的影响

在 iOS 上,小程序的页面是由多个 WKWebView 组成的,在系统内存紧张时,会回收掉一部分 WKWebView。从过去我们分析的案例来看,大图片和长列表图片的使用会引起 WKWebView 的回收。

图片对页面切换的影响

除了内存问题外,大图片也会造成页面切换的卡顿。我们分析过的案例中,有一部分小程序会在页面中引用大图片,在页面后退切换中会出现掉帧卡顿的情况。

当前我们建议开发者尽量减少使用大图片资源。

代码包大小的优化

小程序一开始时代码包限制为 1MB,但我们收到了很多反馈说代码包大小不够用,经过评估后我们放开了这个限制,增加到 2MB 。代码包上限的增加对于开发者来说,能够实现更丰富的功能,但对于用户来说,也增加了下载流量和本地空间的占用。

开发者在实现业务逻辑同时也有必要尽量减少代码包的大小,因为代码包大小直接影响到下载速度,从而影响用户的首次打开体验。除了代码自身的重构优化外,还可以从这两方面着手优化代码大小:

控制代码包内图片资源

小程序代码包经过编译后,会放在微信的 CDN 上供用户下载,CDN 开启了 GZIP 压缩,所以用户下载的是压缩后的 GZIP 包,其大小比代码包原体积会更小。 但我们分析数据发现,不同小程序之间的代码包压缩比差异也挺大的,部分可以达到 30%,而部分只有 80%,而造成这部分差异的一个原因,就是图片资源的使用。GZIP 对基于文本资源的压缩效果最好,在压缩较大文件时往往可高达 70%-80% 的压缩率,而如果对已经压缩的资源(例如大多数的图片格式)则效果甚微。

及时清理没有使用到的代码和资源

在日常开发的时候,我们可能引入了一些新的库文件,而过了一段时间后,由于各种原因又不再使用这个库了,我们常常会只是去掉了代码里的引用,而忘记删掉这类库文件了。目前小程序打包是会将工程下所有文件都打入代码包内,也就是说,这些没有被实际使用到的库文件和资源也会被打入到代码包里,从而影响到整体代码包的大小。

性能 Trace 工具

微信 Andoid 6.5.10 开始,我们提供了 Trace 导出工具,开发者可以在开发者工具 Trace Panel 中使用该功能。

使用方法

  1. PC 上需要先安装 adb 工具,可以参考一些主流教程进行安装,Mac 上可使用 brew 直接安装。
  2. 确定 adb 工具已成功安装后,在开发者工具上打开 Trace Panel,将 Android 手机通过 USB 连接上 PC,点击「Choose Devices」,此时手机上可能弹出连接授权框,请点击「允许」。
  3. 选择设备后,在手机上打开你需要调试的开发版小程序,通过右上角菜单,打开性能监控面板,重启小程序;
  4. 重启后,在小程序上进行操作,完成操作后,通过右上角菜单,导出 Trace 数据;
  5. 此时开发者工具 Trace Panel 上会自动拉取 Trace 文件,选择你要分析的 Trace 文件即可;

可以通过 adb devices 命令确定设备是否已和 PC 建立起连接

性能面板

从微信 6.5.8 开始,我们提供了性能面板让开发者了解小程序的性能。开发者可以在开发版小程序下打开性能面板,打开方法:进入开发版小程序,进入右上角更多按钮,点击「显示性能窗口」。

性能面板指标说明

指标 说明
CPU 小程序进程的 CPU 占用率,仅 Android 下提供
内存 小程序进程的内存占用(Total Pss),仅 Android 下提供
启动耗时 小程序启动总耗时
下载耗时 小程序包下载耗时,首次打开或资源包需更新时会进行下载
页面切换耗时 小程序页面切换的耗时
帧率/FPS  
首次渲染耗时 页面首次渲染的耗时
再次渲染耗时 页面再次渲染的耗时(通常由开发者的 setData 操作触发)
数据缓存 小程序通过 Storage 接口储存的缓存大小

未完待续,下一章节,つづく

微信小程序_文档_05_框架_组件_插件_多线程_兼容_优化相关推荐

  1. 第5节:开发微信小程序之文档详解

    前言 前面4节,我们讲解了为什么要开这一个专栏,以及第一个小程序Hello World,与其说第一个小程序,不如说微信开发者工具为我们创建了一个基础工程项目!然后,又开发了一个真正实现突破0的小程序- ...

  2. 微信小程序学习文档指南

    一.开发前准备 1.开发工具下载. 2.微信小程序设计指南 3.首先要通读一遍官方文档,看看都有哪些东西,都能干什么. 二.目录结构 1.components 小程序自定组件 (组件生命周期,组件间通 ...

  3. 微信小程序常用文档地址(自用常更新)

    微信开放文档: https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 阿里云oss文档 https://he ...

  4. 微信小程序 -- (1)文档说明、配置项

    微信小程序 1. 介绍 微信公众平台由腾讯提供,基于腾讯微信的服务器,为广大企业.组织.个人提供用户管理或咨询服务的平台.微信公众平台提供了3种账号类型: 服务号.订阅号.小程序 服务号: -类似:中 ...

  5. 微信小程序接口文档PHP,微信小程序API 导航

    微信小程序API 导航 一.wx.navigateTo(OBJECT) 保留当前页面,跳转到应用内的某个页面,使用wx.navigateBack可以返回到原页面. OBJECT 参数说明: 示例代码: ...

  6. 微信小程序之文档管理系统(含源码+论文+答辩PPT等)

    项目功能简介: 该项目含有源码.论文等资料.配套开发软件.软件安装教程.项目发布教程等 本系统包含微信小程序做的论文管理系统前台和Java做的后台管理系统: 微信小程序--论文管理系统前台涉及技术:W ...

  7. 微信小程序学习文档总结

    一. 基础用法 div = view span = text (只有当text中的 user-select属性为true时才可选中) button image <image src=" ...

  8. 微信小程序实现pdf、word等格式文件上传的方法,微信小程序word文档

    目前微信只支持从聊天记录里面获取文件 一.前言 目前微信提供了一个接口 wx.chooseMessageFile 它能让用户从聊天记录里面选择一个或者多个文件,然后返回它的一些信息,列入文件的path ...

  9. 微信小程序开发文档及文件上传示例(JAVA)

    微信小程序开发文档及文档上传示例 一.什么是微信小程序 小程序是一种无需下载安装,即可使用的手机应用.只需要扫描二维码,或是搜一搜,就能立即使用. 与APP不同的是,小程序无需下载安装.无需卸载.用完 ...

最新文章

  1. 跟益达学Solr5之Schema.xml详解
  2. Unix/Linux环境C编程入门教程(41) C语言库函数的文件操作详解
  3. ReactOS调试之fDebug
  4. 更改mssqlserver的表的编码格式_Excel格式全揭秘,赶紧收藏起来吧
  5. 万物互联下的碎片化怎么破?UINO优锘推出物联网产业元宇宙“物联森友会”
  6. Windows 搭建 .NET 跨平台环境并运行应用程序
  7. linux shell脚本date命令 按照不同格式输出
  8. 应用悄悄拿走你的隐私做了什么?只需30秒这个AI给你答案
  9. 博为峰Java技术题 ——JavaSE Swing顶层容器中添加菜单栏
  10. 省级、县级行政区shapefile下载
  11. 【爬虫】王者荣耀爬取英雄高清4K图片
  12. DOTA全英雄装备介绍+物品简称[图文]
  13. 根域名服务器性能,根服务器和根域名服务器的区别
  14. 人工智能的八大业界领先的机器学习使用场景
  15. HardSwish和HardSigmoid的关系
  16. RTOS内功修炼记(九)—— 任务入口函数执行完毕之后去哪里?
  17. Maya致命错误解决方法
  18. matlab 不规则 griddata,MATLAB 不规则随机数据点,画三维曲面图形,griddata, meshgrid...
  19. 九龙证券|重大利好!期货公司打新再“解绑”:可直接参与首发网下配售!
  20. 用于特征提取的 LiDAR 数据

热门文章

  1. 菜鸟爬取中关村手机详情页参数及报价
  2. 帝国cms内容模板sql语句方式调用当前TAG标签
  3. Linux命令+shell脚本大全:操作文件系统
  4. 爬取链家北京租房数据并做简单分析
  5. MAX8722 CCFL背光控制器
  6. 中职计算机应用专业教师到电商企业实践报告,中职教师到企业实践总结.doc
  7. 那些年,我们一起写的情诗
  8. Power BI笔记:给排名度量添加矢量图标效果
  9. 计算机键盘感叹号在哪里,键盘上感叹号是哪个键
  10. 专有钉钉下载(windows、IOS、Android)地址