diff --git a/_sidebar.md b/_sidebar.md index 0258032..04391ce 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -33,7 +33,9 @@ - [第二阶段知识点](usage-frame/typescript二期知识点.md) - [奇怪或有用的代码](usage-frame/typescript-code.md) - 小程序 - - [微信小程序](usage-frame/微信小程序.md) + - 微信小程序 + - [组件](usage-frame/wechat-mini-program/组件.md) + - [基础能力](usage-frame/wechat-mini-program/基础能力.md) - 微前端 - [qiankun](usage-frame/qiankun.md) diff --git "a/usage-frame/wechat-mini-program/\345\237\272\347\241\200\350\203\275\345\212\233.md" "b/usage-frame/wechat-mini-program/\345\237\272\347\241\200\350\203\275\345\212\233.md" new file mode 100644 index 0000000..e138d27 --- /dev/null +++ "b/usage-frame/wechat-mini-program/\345\237\272\347\241\200\350\203\275\345\212\233.md" @@ -0,0 +1,81 @@ +# 基础能力 + +## 存储 + +定义:每个小程序都有自己的本地缓存,可以通过storage进行增删改查 + +隔离策略: +- 同一个微信用户,同一个小程序的storage上限为10MB +- storage以用户为维度,不同用户数据、不同小程序数据无法相互调取 +- 同一小程序使用不同插件,这些不同的插件之间、插件和小程序之间的storage无法相互调取 +- 不同小程序使用同一插件,这个插件的storage不互通 + +清理策略:本地缓存的清理时机和代码包一样,只有在代码包被清理时storage才会被清理 + +用法: +- `wx.setStorage(obj)`:将数据存在指定的key中,会覆盖原有key的内容,若非用户主动删除或因存储原因被系统删除,该数据一直可用。单个key上限1MB(开启加密后上限0.7MB),所有key上限为10MB(开启加密后上限为7.1MB),支持promise形式的回调,obj的参数有: + - key:键名 + - data:键值,支持原生类型、Date、能被JSON.stringify序列化的对象 + - encrypt:是否开启加密存储,只有异步的接口支持开启,开启后,接口回调耗时增加,同时setStorage和getStorage同时需要设置该属性,且加密后数据比原始数据膨大1.4倍 + - success/fail/complete:接口调用成功、失败、结束时的回调 +- `wx.setStorageSync(key, data)`:同步存储,只应用于数据的持久化存储,太多会影响启动耗时 +- `wx.getStorage(obj)`:obj参数比setStorage少了一个data属性,支持promise形式的回调,其中: + - success:接口返回res,使用res.data获取该key的值 +- `wx.getStorageSync(key)`;同步获取缓存 +- `wx.removeStorage(obj)`:移除指定key的缓存,参数比setStorage少了一个data、encrypt属性,支持promise形式的回调 +- `wx.removeStorageSync(key)`:同步移除缓存,支持promise形式的回调 +- `wx.clearStorage(obj)`:清理本地所有缓存,参数obj只有success、fail、complete三个函数属性,支持promise形式的回调 +- `wx.clearStorageSync()`:同步清理所有缓存,无参,支持promise形式的回调 + +```js +// 设置缓存 +wx.setStorage({ + key: 'user-info', + value: data, + // 同时开启 + encrypt: true, + success () { + wx.getStorage({ + key: 'user-info', + // 同时开启 + encrypt: true, + success (res) { + console.log(res.data) + } + }) + } +}) + +// 同步存储 +try { + wx.setStorageSync('user-info', data) +} catch (e) { + console.log(e) +} + +// 获取缓存 +wx.getStorage({ + key: 'user-info', + encrypt: true, + success (res) { + console.log(res.data) + } +}) + +wx.getStorageSync('user-info') + +// 清除key为user-info的缓存 +wx.removeStorage({ + key: 'user-info', + success () {} +}) + +wx.removeStorageSync('user-info') + +// 清空缓存 +wx.clearStorage({ + success () {} +}) + +wx.clearStorageSync() +``` \ No newline at end of file diff --git "a/usage-frame/wechat-mini-program/\347\273\204\344\273\266.md" "b/usage-frame/wechat-mini-program/\347\273\204\344\273\266.md" new file mode 100644 index 0000000..3aa4c69 --- /dev/null +++ "b/usage-frame/wechat-mini-program/\347\273\204\344\273\266.md" @@ -0,0 +1,869 @@ +## 自定义组件 + +> 无特殊说明,下列的组件均指自定义组件 + +### 创建组件 + +自定义组件由json、wxml、wxss、js四个文件构成,和page类似。 + +定义自定义组件,需在其json文件中添加键值`"component": true`,表明其为自定义组件。 + +在js文件中使用`Component()`函数注册组件,其参数为一个对象,和page类似。 + +在其他地方使用组件,需在使用该组件的json文件中添加引用声明字段`"usingComponents": { "component-tag-name": "path" }`,全局组件可在`app.json`中一次引用,后续所有地方都可直接引入该组件。 + +自定义组件和页面所在项目根目录名不能以`"wx-"`开头,否则会报错文档(*存疑,指南-自定义组件-介绍*) + +### 组件模板(wxml) + +自定义组件的标签名,只能包含小写字母、中划线、下划线,所以引用组件(json声明组件、wxml引用组件)时,应当注意。 + +### 组件样式(wxss) + +**构建样式**时,应该使用class选择器: +1. 不应该使用id(#a)、属性([a])、标签选择器,否则无效。 +2. 避免使用后代选择器(.a .b),可能会出现非预期效果,且该选择器只能用于view组件与其子组件之间。 + +**样式的继承**: +1. 继承样式类型(font、color等),会自动从组件外继承 +2. 非继承样式类型,不会自动继承,除非更改**组件样式隔离选项** + +**组件样式隔离**; +1. 默认情况下,组件的样式只受组件自身wxss影响 +2. 当在`app.wxss`或者页面的wxss中使用了标签名以及其他的一些特殊选择器直接指定样式时,会影响到全部组件或者页面内的组件(不推荐) +3. 当指定了样式隔离选项(js或者json中),组件的样式会受到相应的影响: + +```js +// 样式隔离选项 +Component({ + options: { + // addGlobalClass:该选项为true等同于styleIsolation:isolated(优先级更高,会覆盖addGlobalClass) + addGlobalClass: true, + + // isolated:启用样式隔离(默认值),组件的样式只受组件自身wxss影响 + styleIsolation: 'isolated' + // apply-shared(申请分享样式):表示页面样式会影响到组件 + styleIsolation: 'apply-shared' + // shared(分享样式):表示页面样式会影响组件,组件样式会影响页面和其他设置了apply-shared或shared的组件 + styleIsolation: 'shared' + + // 当Component构造器用于构造页面时,shared为默认值,同时还有下列隔离选项 + // page-isolated:表示页面禁用app.wxss的样式,同时页面样式不会影响到组件 + styleIsolation: 'page-isolated' + // page-apply-shared:表示页面禁用app.wxss的样式,同时页面样式不会影响到组件,但设为shared的组件会影响页面 + styleIsolation: 'page-apply-shared' + // page-shared:表示页面禁用app.wxss的样式,同时页面样式会影响到其他设置了apply-shared或shared的组件,也会受其他设为shared的组件样式的影响 + styleIsolation: 'page-shared' + } +}) +``` + +```json +// 也可以在json中设置样式隔离选项 +{ + "styleIsolation": "isolated" +} +``` + +**`:host`选择器**:组件可以使用`:host`选择器指定所在根节点的默认样式,相当于该组件的全局样式 + + + +```wxml + +这是一段文本 +``` + +```wxss +/* 指定默认样式 */ +:host { + color: red; + background-color: yellow; + border: 1px solid green; +} +``` + +```wxml + + +``` + + +```wxml + + +#shadow-root + + 这是一段文本 + +``` + + +**外部样式类**: + +使用场景:组件希望接受外部传过来的样式 + +用法:在Component中`externalClasses`定义一个接受的外部样式的类是数组 + +```js +// 定义接受的样式类数组 +Component({ + externalClasses: ['red-text', 'large-text'] +}) +``` + +```wxml + + + + + +``` + +注意: +1. 同一个节点上使用普通样式类和外部样式类时,没有优先级之分,所以应当避免同时使用具有相同功能的两种样式 + +**其他方法引用页面、父组件的样式**: + +场景:在启用了样式隔离isolated,同时未引入外部样式类的情况下,还想引用父级的类样式 + +使用: +1. 引用页面的样式类:直接在组件内部使用形如`class="~page-red-text"`的方式,就能在该元素上引入页面的page-red-text样式类 +2. 引用父组件的样式:直接在组件内部使用形如`class="^的方式,就能在该元素上引入父组件的parent-red-text"`的方式,就能在该元素上引入父组件的parent-red-text样式类,若想引用祖先组件的样式类,根据层级数来确定`^`的数量,比如祖组件为`^^`,曾祖组件为`^^^` + +注意: +1. 若组件是独立通用的组件,优先使用外部样式类的方式 + +**虚拟化组件节点**: + +知识点: +1. 默认情况下,组件本身那个节点(比如custom-com)是一个普通的节点,使用时可以在其上面设置class、style等,如同view组件一样 + +场景:若自定义组件不希望该节点本身进行设置样式,而是希望内部的第一层节点响应该样式时,可以将该组件节点设置为虚拟节点 + +使用:在Component中的options选项设置`virtualHost: true` + +注意:设置了虚拟节点后,若想使用自定义节点上的style和class,应当将, +1. 在properties中添加style属性,以获取style设置的值 +2. 在externalClasses中添加值为class的元素,让组件wxml内部可以使用class值(*未实现,指南-自定义组件-组件模板和样式*) + +### Component构造器 + +知识点: +1. Component构造器可用于定义组件,调用该构造器可以指定组件的属性、数据、方法 +2. Component还可用于构造页面,用法和定义组件类似,此时: + 1. 需要在json文件中包含`usingComponents`字段 + 2. 组件的属性properties可以用于接收访问页面的参数`page/index?paramA=10`,若properties中声明了paramA,该值会被初始化为10 + 3. 页面的生命周期方法(以on开头的)应该写在methods中 + +```js +// Component构造器 +Component({ + // 公有的行为 + behaviors: [], + // 传入的props或者是页面组件传入的参数 + properties: {}, + // 私有数据 + data: {}, + // 生命周期函数 + lifetimes: { + // 生命周期函数的值可以是函数,或者是定义的方法名,同时这些函数可以写在和lifetimes同级下 + attached () {}, + moved () {}, + detached: 'detachedM' + }, + // 生命周期函数的值可以是函数,或者是定义的方法名,同时这些函数可以写在和lifetimes同级下 + // 此处的生命周期函数会被lifttimes下同名函数覆盖 + attached () {}, + // 组件所在页面的生命周期函数 + pageLifetimes: { + show () {}, + hide () {} + }, + methods: { + detachedM () {}, + // 内部方法建议以_开头 + _showDialog () {} + } +}) +``` + +注意: +1. 使用构造器构造页面的好处是可以使用behaviors提取所有页面公用的代码段(公用的属性、方法、生命周期等) + +### 使用插槽 + +在组件的wxml中使用`slot`标签,承载引用该组件时,插入到该组件内部的内容,和vue类似,其中: +1. 在组件的js文件中,在options字段上添加`multipleSlots: true`,可启用多slot支持,此时slot需要有确定的name属性 +2. 在引用该组件时,该组件内部,在元素中添加`slot="slot-name"`可将该代码块插入到对应的slot上 + + + +```wxml + + + 内容 + + + + 这是slot为first的内容 + + + 这是slot为last的内容 + + + +``` + + +### 组件通信 + +**组件通信的方式**: +- 父组件向子组件传递数据:使用WXML数据绑定的方式,向子组件的指定属性(data-开头的、子组件设置了properties的)设置数据(json格式) +- 子组件向父组件传递数据:使用事件激发的方式(`triggerEvent('event-name', data, eventOptions)`),其中事件选项对象包括(bubbles:是否冒泡,capturePhase:是否有捕获阶段,composed:事件是否可以穿越组件边界,为false时,只能在引用组件的节点链上触发,不会深入嵌套组件的内部根节点) +- 父组件获取子组件实例对象:通过`this.selectComponent(selector)`获取子组件实例对象(this),这样可以直接访问子组件任意数据和方法 + + + + +```wxml + + + + + + + + + + + + + + + + + + +``` + + + +```js +// child-com.js +Compoent({ + methods: { + onTap () { + // 当前触发事件:getDataListenerOnChild + this.triggerEvent('getdata', {data: {}}, {}), + // 当前触发事件冒泡:getDataListenerOnChild、getDataListenerOnParent + this.triggerEvent('getdata', {data:{}}, { bubbles: true }), + // 当前会触发事件冒泡和突破组件边界:getDataListenerOnChild、getDataListenerOnParentRoot、getDataListenerOnParent + this.triggerEvent('getdata', {data: {}}, {bubbles: true, composed: true}) + } + } +}) +``` + + +**注意**: +1. 默认情况下,小程序和插件、插件和插件之间的组件无法通过selectComponent获取组件实例(返回null),若想获取需要自定义返回结果: + 1. 使用内置的behavior:`behaviors: ['wx://component-export']` + 2. Component一级字段使用export:`export () { return { xxx } }` + 3. 此时引用该组件时,通过selectComponent获取到的就是`{ xxx }`的内容 + +### 组件生命周期 + +定义: +- 指的是在一些特殊的时间点或遇到一些特殊的框架事件时被自动触发的函数 +- 在Component中一级字段lifetimes上定义的 +- 同时,在behaviors中也可以定义生命周期,且它不会和其他的behaviors的同名生命周期相互覆盖。但若一个组件多次直接/间接引用同一个behavior,该behavior的生命周期在一个触发时机内只会触发一次 + +常用的生命周期函数: +- created:组件实例被创建好时触发,此时this.data是组件定义时的数据,不能使用setData +- attached(连接的,附属于):组件完全初始化完毕、进入页面节点树后触发,大多数初始化工作可以在这里进行 +- detached(独立的,分离的,拆下的):组件离开页面节点树后,即退出一个页面时,若组件还在页面节点树中,会触发 + +其他生命周期函数: +- ready:组件在视图层布局完成后触发 +- moved:组件实例被移动到节点树另一个位置时触发 +- error:组件方法抛出错误时触发 + +组件所在页面的生命周期,与组件并非很强的关联性,在组件有需要时方便处理,它们在pageLifetimes中定义,有: +- show:组件所在页面被展示时触发 +- hide:组件所在页面被隐藏时触发 +- resize:组件所在页面尺寸变化时触发 + +生命周期的执行顺序: +1. created:创建组件实例 +2. attached:组件实例进入节点树 +3. show:展示页面 +4. ready:组件结构在页面布局完成 + + +### behaviors + +定义: +- behaviors用于组件间代码共享,类似vue中的mixins +- behavior包含属性(properties)、数据(data)、生命周期(lifetimes)、方法(methods) +- 组件引用它时,其内容会被合并/调用 +- 组件可引用多个behavior,behavior可以引用其他behavior + +```js +// behavior实例:my-behavior.js +module.exports = Behavior({ + behaviors: [], + properties: {}, + data: {}, + lifetimes: {}, + methods: {} +}) + +// 组件引用behavior:my-component.js +const myBehavior = require('./my-behavior.js') +Component({ + behaviors: [myBehavior], + properties: {}, + // xxx +}) +``` + +组件引入behaviors时,其结构为: +1. properties:包括组件和behaviors的properties,若有同名property,引用的会覆盖被引用的(即组件的会覆盖behaviors的、behaviors会覆盖它引入的其他behaviors的)、后引入的会覆盖先引入的behaviors(数组先后顺序) +2. methods:同上 +3. data:若是对象类型,会进行对象合并,其他情况同上 +4. lifetimes、observers: + 1. 不同生命周期之间,遵循各自的执行顺序 + 2. 相同生命周期/observer之间,与上面三个(properties、methods、data)的规则相反 + 3. 同一个behavior被引用多次,其生命周期/observe只会执行一次 + +内置的behaviors,会为组件添加一些属性,无特殊说明时组件可以覆盖这些属性来改变它的type/添加observer: +- `'wx://form-field'`:使组件有类似表单控件的行为,form组件可以识别这些子组件,在submit事件中可以获取数据 +- `'wx://form-field-group'`:使form组件可以识别到这个自定义组件内部所有的表单控件,在submit事件中可以获取数据 +- `'wx://form-field-button'`:使form组件可以识别到自定义组件内部的button,若该button设置了`form-type`,他将被form组件接受,效果和直接写在form内部的一样 +- `'wx://component-export'`:使自定义组件支持export字段,该字段函数返回用于指定组件被selectComponent调用时的返回值 + + + +```js +// sub-com.wxml + + +// sub-com.js +Component({ + behaviors: ['wx://form-field'], + methods: { + onChange (e) { + this.setData({ + value: e.detail.value + }) + } + } +}) + +// 引入sub-com:com.wxml +
+ + + // 点击提交时,会触发事件formSubmit + + + +// com.json +{ + "usingComponents": "./sub-com/" +} + +// com.js +Page({ + formSubmit (e) { + // 值为:{ "sub-com-name": "xxx", "sub-com-name-2": "xxx" } + console.log(e.detail.value) + } +}) +``` + +```js +// sub-com.wxml + + + + +// sub-com.js +Component({ + behaviors: ['wx://form-field-group'], + methods: { + onChange (e) { + this.setData({ + value: e.detail.value + }) + } + } +}) + +// 引入sub-com:com.wxml +
+ + // 点击提交时,会触发事件formSubmit + + + +// com.json +{ + "usingComponents": "./sub-com/" +} + +// com.js +Page({ + formSubmit (e) { + // 值为:{ "name": "xxx", "sex": "xxx" } + console.log(e.detail.value) + } +}) +``` + + +```js +// sub-com.wxml +// 点击提交时,会触发事件formSubmit + + + +// sub-com.js +Component({ + behaviors: ['wx://form-field-button'] +}) + +// 引入sub-com:com.wxml +
+ + + + +// com.json +{ + "usingComponents": "./sub-com/" +} + +// com.js +Page({ + formSubmit (e) { + // 值为:{ "name": "xxx" } + console.log(e.detail.value) + } +}) +``` + + +### 组件间关系 + +场景:解决组件间相互通信复杂的问题 + +使用: +- 在具有关联性的各方组件定义时都加入relations一级字段 + +ralation字段属性: +- type:表示关联节点多方之间的关系,有parent、child、ancestor(祖先)、descendant(后代) +- 关联生命周期函数:linked、linkChanged、unlinked +- target:设置了该项,表示各关联方是通过一个公有的behavior关联的,所有拥有这个behavior的组件都会被关联 + + + + +```js +// 引入父子组件 + + + + +// parent-com.js +Component({ + relations: { + // 字段名为路径名 + './child-com': { + // 表示关联的child-com节点为它的子节点 + type: 'child', + // child-com每次插入到该组件内部时执行,target是child-com节点实例对象,该函数在child-com节点的attached后触发 + linked (target) {}, + // child-com每次被移动时执行,target是child-com节点实例对象,该函数在child-com节点的moved后触发 + linkChanged (target) {}, + // child-com每次被移除时执行,target是child-com节点实例对象,该函数在child-com节点的detached后触发 + unlinked (target) {}, + + } + }, + methods: { + _getAllChildCom () { + // 使用 getRelationNodes 获取所有关联的child-com的有序的nodes数组 + console.log(this.getRelationNodes('./child-com')) + } + }, + ready () { + this._getAllChildCom() + } +}) + +// child-com.js +Component({ + relations: { + './parent-com': { + type: 'parent', + // 每次插入到child-com组件内部时执行,target是parent-com节点实例对象,该函数在attached后触发 + linked (target) {}, + // 每次被移动时执行,target是parent-com节点实例对象,该函数在moved后触发 + linkChanged (target) {}, + // 被移除时执行,target是parent-com节点实例对象,该函数在detached后触发 + unlinked (target) {}, + } + } +}) +``` + + +```js +// 引入组件 + + + + + +// child-com1.js、child-com2.js +const commonControl = require('./common-control') +Component({ + behaviors: [commonControl], + relations: { + './parent-com': { + // 表示commonControl关联的目标节点是祖先节点 + type: 'ancestor' + } + } +}) + +// parent-com.js +const commonControl = require('./common-control') +Component({ + behaviors: [commonControl], + relations: { + 'commonControl': { + // 表示parent-com关联的目标节点commonControl是后代节点 + type: 'descendant', + target: commonControl + } + } +}) + +``` + + +### 数据监听器(observers) + +定义:用于监听和响应任何属性和数据字段的变化,功能和vue的watch类似 + +使用: +- 在observers一级字段中定义 +- 监听语法为:`field (newV) {}`、`arr[3] (newV) {}`、`obj.field (newV) {}` +- 可以同时监听多个属性,比如监听属性a和b:`'a, b' (newA, newB) {}`,在属性a或b用setData进行设置时,会触发该监听函数 +- 使用通配符`**`监听全部setData的值设置 + +注意: +- observer监听到的数据是setData涉及到的数据字段,即使他们的值没有变化,监听也会被触发 +- 若在监听器中进行setData当前值,会导致死循环 +- 数据监听器比属性的observer更强大,性能更好 + +```js +Component({ + observers: { + '**' () { + // 此处会监听所有的setData调用 + }, + 'a, b' () { + // 当使用setData设置a、b的值时,会触发当前监听 + }, + 'arr[1]' () { + // 当设置数组arr的第2个元素,触发 + }, + 'obj.name' () { + // 当设置对象obj的name属性时,触发 + } + } +}) +``` + +### 纯数据字段 + +定义: +- 纯数据字段是一些不会用在*界面渲染*(wxml)、*不会传递给其他组件*、*仅在当前组件中使用*的data字段或properties字段,它有助于提升页面更新性能 +- 换句话说,仅在当前组件的js文件中生效,用在其他地方不生效 + +使用: +- 指定纯数据字段应该在options一级字段中指定pureDataPattern为一个正则表达式,比如`pureDataPattern: /^_/`,此时所有以`_`开头的data/property都是纯数据字段 +- 也可在json文件中指定一级字段pureDataPattern,其值为字符串形式,比如`"pureDataPattern": "^_"` + +注意: +- 属性(property)中的纯数据字段的observer不会触发,此时可用监听器监听该属性 + +### 抽象节点 + +场景:当自定义组件中的节点model不是由其本身决定的,而是由调用该组件者决定的,则可以将该节点model声明为抽象节点 + +使用,下面的model-name可以是任意的合法节点标识字段: +1. 首先在组件的json文件中声明节点model的名称,在一级字段componentGenerics对象中添加声明节点名称为true,比如`"model-name": true` +2. 在组件的wxml文件中使用该抽象节点model,比如`` +3. 在调用组件的页面/组件的json文件中,引用需要使用的不确定组件,使用`usingComponents`引入 +4. 在调用组件的页面/组件的wxml文件中,使用该组件,比如`` + +```js +// 第一步:在组件json中声明抽象节点 +// custom-com.json +{ + "componentGenerics": { + "jida-select": true + // 这里可以指定jida-select默认的组件节点,当具体组件未指定时,将创建默认组件实例 + "jida-select": { + "default": "path/xxx" + } + } +} + +// 第二步,在组件wxml中使用抽象节点 +// custom-com.wxml + + +// 第三步:引入需要的组件 +// other-custom-com.json +{ + "usingComponents": { + "custom-com": "./custom-com", + "custom-1": "./custom-1", + "custom-2": "./custom-2" + } +} + +// 第四步:使用组件 +// other-custom-com.wxml +// 这里会将组件custom-com的jida-select节点替换成组件custom-1的内容,其数据也会自动附加到custom-1上 + +// 使用默认组件节点,不需要使用generic属性 + +``` + +注意: +- 节点`generic:xxx="yyy"`中,yyy只能是静态值,也就是引入组件名字符串,不能是数据绑定 + +### 组件扩展 + +场景: +- 为了更好定制组件的功能,可以使用自定义组件扩展机制 +- 相当于在behavior中对组件/behavior的内容进行自定义操作,每当behavior设置的功能对应的数据发生变更时,它会同时进行自定义操作 + +用法: +1. 若想要定制组件功能,需要在组件中引入一个behavior +2. 引入的behavior具有一级字段definitionFilter,形式为:`definitionFilter(deFields, definitionFilterArr) {}` +3. 然后在definitionFilter去定制功能,其中deFields表示引用该behavior的组件/behavior对象,definitionFilterArr表示该behavior引用的其他behavior的definitionFilter函数列表 + +```js +// behavior3.js +module.exports = Behavior({ + definitionFilter (deFields, definitionFilterArr) {} +}) + +// behavior2.js +module.exports = Behavior({ + // 5. 由于behavior2引入了behavior3.js,所以会调用会调用behavior3.js中的definitionFilter函数,详见上面 + behaviors: [require('behavior3.js')], + // 4. 调用该函数,deFields表示组件对象,可以使用deFields.data.xxx,获取/设置组件中data字段xxx的值 + // definitionFilterArr表示behavior2引用的behaviors数组,值为[behavior3.definitionFilter] + definitionFilter (deFields, definitionFilterArr) { + // 若想在此处调用behavior3的definitionFilter去过滤behavior1的内容,可用下面这种方式 + definitionFilterArr[0](deFields) + } +}) + +// behavior1.js +module.exports = Behavior({ + // 3. 由于behavior1引入了behavior2.js,所以会调用会调用behavior2.js中的definitionFilter函数,详见上面 + behaviors: [require('behavior2.js')], + // 2. 调用该函数,deFields表示组件对象,可以使用deFields.data.xxx,获取/设置组件中data字段xxx的值 + // definitionFilterArr表示behavior1引用的behaviors数组,值为[behavior2.definitionFilter] + definitionFilter (deFields, definitionFilterArr) {} +}) + +// component.js +Component({ + // 1. 当进行组件的声明时,由于引入了behavior1 + // 此时会调用behavior1.js中的definitionFilter函数,详见上面 + behaviors: [require('behavior1.js')] +}) +``` + +### 单元测试 + +需要同时兼顾nodejs和dom,选择示例: +1. mocha+jsdom +2. jest + +场景: +- 进行单元测试时,无需用到双线程架构模式,因为进行的是功能测试,而非性能安全测试 +- 测试工具集的wx对象和内置组件都不会实现真正的功能,若需要,可自行覆盖工具集的api接口和内置组件 +- 测试工具集无法全部覆盖自定义组件的特性 + +使用:安装测试工具集`miniprogram-simulate`,其提供了一些方便测试的接口,如: +- 模拟touch事件、自定义事件触发 +- 选取子节点 +- 更新自定义组件数据 +- 触发生命周期 +- xxx,详见[github](https://github.com/wechat-miniprogram/miniprogram-simulate) + +编写测试用例: +1. 安装测试工具集:`npm i --save-dev miniprogram-simulate` +2. 新建一个test文件夹,其子目录和项目根目录的子目录类似,比如要测试`components/index`组件,则在`test/components/index.test.js`中编写对应测试用例 + + + + +```js +// components/index.wxml +{{ prop }} + +// components/index.js +Component({ + properties: { + prop: { + type: String, + value: 'index.properties' + } + } +}) + +// components/index.wxss +.index { + color: green; +} +``` + + +```js +// test/components/index.test.js +// simulate:模仿、假装、冒充、模拟 +const simulate = require('miniprogram-simulate') + +test('components/index', () => { + // 此处的路径必须是绝对路径 + const id = simulate.load('/components/index') + // 渲染成自定义组件树实例 + const comp = simulate.render(id) + // 创建父节点 + const parent = document.createElement('parent-wrapper') + // 将组件附加到父节点上,会触发组件的attached函数 + comp.attach(parent) + // 获取组件内部是一些节点 + const view = comp.querySelector('.index') + // 测试渲染结果 + expect(view.dom.innerHTML).toBe('index.properties') + expect(window.getComputedStyle(view.dom).color).toBe('green') +}) +``` + + + +### 获取更新性能统计信息 + +场景: +- 若想知道setData引发界面更新的开销,可以使用更新性能统计信息接口`setUpdatePerformanceListener`,它将返回每次更新中注意更新步骤发生的时间戳 + +使用:调用时机不能早于attached, + +```js +Component({ + attached () { + this.setUpdatePerformanceListener({ + // 是否返回变更的data字段的信息 + widthDataPaths: true + }, res => { + // res 对象属性: + // dataPaths:数组,此次更新的data字段的信息,在widthDataPaths: true时返回 + // pendingStartTimestamp:更新进入等待队列的时间戳 + // updateStartTimestamp:更新运算执行开始的时间戳 + // updateEndTimestamp:更新运算执行结束的时间戳 + // isMergedUpdate:是否是被合并更新,若是,updateProcessId表示被合并到的更新过程id + // updateProcessId:此次更新过程的id + // parentUpdateProcessId:若该更新是子更新,返回其所属的更新过程Id + console.log(res) + }) + } +}) +``` + +注意: +- setUpdatePerformanceListener只会激活当前组件/页面的统计,若需要知道页面内所有的更新过程,需要所有组件都调用setupdatePerformanceListener +- 统计本身有开销,禁用统计,第二个参数设置为null + +### 使用占位组件 + +场景:在使用分包异步化、用时注入等特性时,自定义组件所引用的其他自定义组件在刚开始渲染时,可能处于不可用状态,此时为了使渲染过程不被阻塞,可以使用一个占位组件替换不可用组件,在该组件可用时再替换回来 + +不可用状态场景: +1. 使用分包异步化时,引入了其他分包的组件,而该分包还未下载 +2. 使用用时注入时,该组件还未注入 + +使用: +1. 在组件的json中一级字段`componentPlaceholder`下配置占位组件,例如:`"comp-a": "view"`,将comp-a组件替换成view组件 +2. 占位组件可以是自定义组件,也可以是内置组件 +3. 若某组件设置为占位组件,则其必须是始终可用的 +4. 若某组件设置为其他组件的占位组件,其不能为其指定它的占位组件 + +占位组件参与的组件渲染过程: +1. 当渲染该组件时,会递归检查usingComponents字段,收集使用到的所有组件的信息 +2. 若使用到的组件不可用,会检查其是否有占位组件 +3. 若没有占位组件,则会中断渲染,抛出错误 +4. 若有占位组件,则会进行标记,并在后续渲染流程中使用占位组件进行渲染 +5. 该不可用组件绘制渲染结束后尝试准备(下载分包/注入代码) +6. 准备完成后,若该不可用组件不存在,抛出错误 +7. 若该组件存在,则会替换掉占位组件进行渲染 + +```js +// com.json +{ + "usingComponents": { + "com-a": "./com-a", + "com-b": "./com-b", + "com-c": "./com-c" + }, + "componentPlaceholder": { + "com-a": "view", + "com-b": "com-c" + } +} + +// com.wxml + + + + +// com-a和com-b不可用时 + + + +``` \ No newline at end of file diff --git "a/usage-frame/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217.md" "b/usage-frame/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217.md" deleted file mode 100644 index a567af1..0000000 --- "a/usage-frame/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217.md" +++ /dev/null @@ -1,241 +0,0 @@ -# 微信小程序 - -## 自定义组件 - -> 无特殊说明,下列的组件均指自定义组件 - -### 创建组件 - -自定义组件由json、wxml、wxss、js四个文件构成,和page类似。 - -定义自定义组件,需在其json文件中添加键值`"component": true`,表明其为自定义组件。 - -在js文件中使用`Component()`函数注册组件,其参数为一个对象,和page类似。 - -在其他地方使用组件,需在使用该组件的json文件中添加引用声明字段`"usingComponents": { "component-tag-name": "path" }`,全局组件可在`app.json`中一次引用,后续所有地方都可直接引入该组件。 - -自定义组件和页面所在项目根目录名不能以`"wx-"`开头,否则会报错文档(*存疑,指南-自定义组件-介绍*) - -### 组件模板(wxml) - -自定义组件的标签名,只能包含小写字母、中划线、下划线,所以引用组件(json声明组件、wxml引用组件)时,应当注意。 - -### 组件样式(wxss) - -**构建样式**时,应该使用class选择器: -1. 不应该使用id(#a)、属性([a])、标签选择器,否则无效。 -2. 避免使用后代选择器(.a .b),可能会出现非预期效果,且该选择器只能用于view组件与其子组件之间。 - -**样式的继承**: -1. 继承样式类型(font、color等),会自动从组件外继承 -2. 非继承样式类型,不会自动继承,除非更改**组件样式隔离选项** - -**组件样式隔离**; -1. 默认情况下,组件的样式只受组件自身wxss影响 -2. 当在`app.wxss`或者页面的wxss中使用了标签名以及其他的一些特殊选择器直接指定样式时,会影响到全部组件或者页面内的组件(不推荐) -3. 当指定了样式隔离选项(js或者json中),组件的样式会受到相应的影响: - -```js -// 样式隔离选项 -Component({ - options: { - // addGlobalClass:该选项为true等同于styleIsolation:isolated(优先级更高,会覆盖addGlobalClass) - addGlobalClass: true, - - // isolated:启用样式隔离(默认值),组件的样式只受组件自身wxss影响 - styleIsolation: 'isolated' - // apply-shared(申请分享样式):表示页面样式会影响到组件 - styleIsolation: 'apply-shared' - // shared(分享样式):表示页面样式会影响组件,组件样式会影响页面和其他设置了apply-shared或shared的组件 - styleIsolation: 'shared' - - // 当Component构造器用于构造页面时,shared为默认值,同时还有下列隔离选项 - // page-isolated:表示页面禁用app.wxss的样式,同时页面样式不会影响到组件 - styleIsolation: 'page-isolated' - // page-apply-shared:表示页面禁用app.wxss的样式,同时页面样式不会影响到组件,但设为shared的组件会影响页面 - styleIsolation: 'page-apply-shared' - // page-shared:表示页面禁用app.wxss的样式,同时页面样式会影响到其他设置了apply-shared或shared的组件,也会受其他设为shared的组件样式的影响 - styleIsolation: 'page-shared' - } -}) -``` - -```json -// 也可以在json中设置样式隔离选项 -{ - "styleIsolation": "isolated" -} -``` - -**`:host`选择器**:组件可以使用`:host`选择器指定所在根节点的默认样式,相当于该组件的全局样式 - - - -```wxml - -这是一段文本 -``` - -```wxss -/* 指定默认样式 */ -:host { - color: red; - background-color: yellow; - border: 1px solid green; -} -``` - -```wxml - - -``` - - -```wxml - - -#shadow-root - - 这是一段文本 - -``` - - -**外部样式类**: - -使用场景:组件希望接受外部传过来的样式 - -用法:在Component中`externalClasses`定义一个接受的外部样式的类是数组 - -```js -// 定义接受的样式类数组 -Component({ - externalClasses: ['red-text', 'large-text'] -}) -``` - -```wxml - - - - - -``` - -注意: -1. 同一个节点上使用普通样式类和外部样式类时,没有优先级之分,所以应当避免同时使用具有相同功能的两种样式 - -**其他方法引用页面、父组件的样式**: - -场景:在启用了样式隔离isolated,同时未引入外部样式类的情况下,还想引用父级的类样式 - -使用: -1. 引用页面的样式类:直接在组件内部使用形如`class="~page-red-text"`的方式,就能在该元素上引入页面的page-red-text样式类 -2. 引用父组件的样式:直接在组件内部使用形如`class="^的方式,就能在该元素上引入父组件的parent-red-text"`的方式,就能在该元素上引入父组件的parent-red-text样式类,若想引用祖先组件的样式类,根据层级数来确定`^`的数量,比如祖组件为`^^`,曾祖组件为`^^^` - -注意: -1. 若组件是独立通用的组件,优先使用外部样式类的方式 - -**虚拟化组件节点**: - -知识点: -1. 默认情况下,组件本身那个节点(比如custom-com)是一个普通的节点,使用时可以在其上面设置class、style等,如同view组件一样 - -场景:若自定义组件不希望该节点本身进行设置样式,而是希望内部的第一层节点响应该样式时,可以将该组件节点设置为虚拟节点 - -使用:在Component中的options选项设置`virtualHost: true` - -注意:设置了虚拟节点后,若想使用自定义节点上的style和class,应当将, -1. 在properties中添加style属性,以获取style设置的值 -2. 在externalClasses中添加值为class的元素,让组件wxml内部可以使用class值(*未实现,指南-自定义组件-组件模板和样式*) - -### Component构造器 - -知识点: -1. Component构造器可用于定义组件,调用该构造器可以指定组件的属性、数据、方法 -2. Component还可用于构造页面,用法和定义组件类似,此时: - 1. 需要在json文件中包含`usingComponents`字段 - 2. 组件的属性properties可以用于接收访问页面的参数`page/index?paramA=10`,若properties中声明了paramA,该值会被初始化为10 - 3. 页面的生命周期方法(以on开头的)应该写在methods中 - -```js -// Component构造器 -Component({ - // 公有的行为 - behaviors: [], - // 传入的props或者是页面组件传入的参数 - properties: {}, - // 私有数据 - data: {}, - // 生命周期函数 - lifetimes: { - // 生命周期函数的值可以是函数,或者是定义的方法名,同时这些函数可以写在和lifetimes同级下 - attached () {}, - moved () {}, - detached: 'detachedM' - }, - // 生命周期函数的值可以是函数,或者是定义的方法名,同时这些函数可以写在和lifetimes同级下 - // 此处的生命周期函数会被lifttimes下同名函数覆盖 - attached () {}, - // 组件所在页面的生命周期函数 - pageLifetimes: { - show () {}, - hide () {} - }, - methods: { - detachedM () {}, - // 内部方法建议以_开头 - _showDialog () {} - } -}) -``` - -注意: -1. 使用构造器构造页面的好处是可以使用behaviors提取所有页面公用的代码段(公用的属性、方法、生命周期等) - -### 使用插槽 - -在组件的wxml中使用`slot`标签,承载引用该组件时,插入到该组件内部的内容,和vue类似,其中: -1. 在组件的js文件中,在options字段上添加`multipleSlots: true`,可启用多slot支持,此时slot需要有确定的name属性 -2. 在引用该组件时,该组件内部,在元素中添加`slot="slot-name"`可将该代码块插入到对应的slot上 - - - -```wxml - - - 内容 - - - - 这是slot为first的内容 - - - 这是slot为last的内容 - - - -``` - \ No newline at end of file