- 每天一个小程序知识点-自定义组件
原标题 学习自定义组件系列,难度指数★★★★★ 自定义组件算是小程序开发里面难度最高的一个知识点,在阅读本文之前,请看看下面红字提示 本文面向对象为具有一定小程序开发基础的同学,其实对我来讲,学习自定义组件,也是反复折腾了好几天,每一次学习都有不同的感悟和收获,特别是近期有个二开项目中,到处到是自定义组件,才硬着头皮啃下这块骨头 今天又开始学习自定义组件 https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/ 希望通过本次学习,可以回答下面几个问题,并能在小程序项目中初步掌握自定义组件的创建和使用 1)自定义组件的属性、内部数据、自定义方法; 2)自定义组件的生命周期; 3)自定义组件与调用页面如何通信; 4)自定义组件的属性和内部数据有何区别; 5)如何理解自定义组件中的纯数据字段; 由于我对自定义组件的生命周期和组件间通信与事件这块比较陌生,这两块是我今天重点学习的 关于生命周期的几点,我单独拎出来 1)定义生命周期方法 生命周期方法可以直接定义在 Component 构造器的第一级参数中。 自小程序基础库版本 2.2.3 起,组件的的生命周期也可以在 lifetimes 字段内进行声明(这是推荐的方式,其优先级最高)。 https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/lifetimes.html 2)组件间通信 组件间的基本通信方式有以下几种。 WXML 数据绑定:用于父组件向子组件的指定属性设置数据,仅能设置 JSON 兼容数据(自基础库版本 2.0.9 开始,还可以在数据中包含函数)。具体在 组件模板和样式 章节中介绍。 事件:用于子组件向父组件传递数据,可以传递任意数据。 如果以上两种方式不足以满足需要,父组件还可以通过 this.selectComponent 方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。 学习这一点需要重点消化下面的关于事件的知识点 组件间通信与事件 https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/events.html 事件 https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html 组件事件处理函数 https://developers.weixin.qq.com/miniprogram/dev/reference/api/Page.html 下面这句话要重点理解下 自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件 1 本文参考以下文章 1)自定义组件公测说明及入门介绍? - 微信开放社区 https://developers.weixin.qq.com/community/develop/doc/00028abbd342f0c014868e37f57809 2)自定义组件? - 微信开放社区 https://developers.weixin.qq.com/community/develop/doc/0006e8c236cd10d499b6217a351c09 3)https://developers.weixin.qq.com/ebook?action=get_post_info&volumn=1&lang=zh_CN&book=miniprogram&docid=000846df9a03909b0086a50025180a 4)微信小程序:自定义组件的数据传递 https://segmentfault.com/a/1190000014474289 5)小程序自定义组件,父组件如何向子组件传递参数,以及如何传递一个对象,导入代码片段一试便知? - 微信开放社区 https://developers.weixin.qq.com/community/develop/article/doc/00086eb5294e0894b63a8898e56013 6)小程序-实现自定义组件以及自定义组件间的通信 https://juejin.cn/post/6889218680975491085 第四篇文章我担心由于不可抗拒原因而不能访问,我转发到社区了,具体见下文 微信小程序:自定义组件的数据传递? - 微信开放社区 https://developers.weixin.qq.com/community/develop/article/doc/0000c0cfa587b81dd05b04f205b813 奉上官方的代码片段可以辅助我们更好的理解组件和页面之间的同学 https://developers.weixin.qq.com/s/X314zqmp70mx 通过下面四张截图基本理解的比较透彻了 截图一 [图片] 截图二 [图片] 截图三 [图片] 截图四 [图片] 本文总结 自定义组件的事件这里太绕了 祝各位学有所得
2020-12-08 - 微信小程序:自定义组件的数据传递
本文转载思否社区 微信小程序:自定义组件的数据传递 https://segmentfault.com/a/1190000014474289 ~~ 由于这篇文章质量非常高,且在我另一篇社区文章的参考列表里面。 学习自定义组件系列? - 微信开放社区 https://developers.weixin.qq.com/community/develop/article/doc/000ca0f395cc58cbda5bb20425b413 一、前言如果小程序中有可复用的UI且具有一定的功能性,就可以使用自定义组件将其封装起来。(如果仅仅只需要复用UI可使用template) 下面介绍父子组件的数据传递方法,以及一个简单的组件和一个复杂的组件示例。 【由于刚开始写这篇文章的时候我还算是一个小程序的新手,自己看着官方文档研究并整理归纳的,有很多不足以及错误的地方。在经过一年的沉淀以后(虽然这一年我主要在写vue而不是小程序),我决定重新整理这篇浏览量比较大的文章,以免新手因我的文章走了弯路。】 二、父子组件传递数据的方法1.父组件向子组件传递数据parent.wxml parent.js data: { name: 'Peggy', age: 25 } child.js properties: { name: { type: String, value: '小明' }, age: Number } 父组件向子组件传值以属性的形式,子组件以properties接收,并可指定数据类型type以及默认值value。 在wxml里可直接以{{name}}的形式使用,而在js中以this.properties.name获取。 2.子组件向父组件传值child.js methods: { changeName() { this.triggerEvent('changeName', { name: '李四' }) } } parent.wxml parent.js changeName(event) { console.log(event.detail) // { name: '李四' } } 子组件向父组件传递数据使用this.triggerEvent方法,这个方法接受3个参数: this.triggerEvent('myevent', myEventDetail, myEventOption); myevent为方法名, myEventDetail是传到组件外的数据, myEventOption为是否冒泡的选项,有三个参数可以设置: bubbles 默认false 事件是否冒泡 composed 默认false 事件是否可以穿越组件边界 capturePhase 默认false 事件是否拥有捕获阶段 在父组件监听事件bindchangeName="changeName",在changeName方法里有一个event参数,可以从event.detail里拿到组件内部传出来的值。 三、简单的组件(计数器)[图片] 1. 组件功能介绍这个组件常见于外卖软件中,用于记录想要购买的商品的数量。初始化的时候只有一个加号,点击加号以后出现数字和减号,并最后将数字传到组件外供外部使用。 2. 创建组件首先在根目录创建components文件夹(推荐),然后创建num-controller文件夹(我取的组件名字),在这个文件夹上点击右键新建一个component,名字为index。 [图片] /components/num-controller/index.wxml {{num}} 这段代码就是加减两个按钮和一个数字。 /components/num-controller/index.json { "component": true, "usingComponents": {} } 这个文件在创建component的时候会自动写入这段代码。 /components/num-controller/index.js Component({ /** * 组件的属性列表 */ properties: { nameId: { type: String }, num: { type: Number, value: 0 }, int: { type: Number, value: 1 }, min: { type: Number, value: 0 } }, /** * 组件的初始数据 */ data: { }, /** * 组件的方法列表 */ methods: { numChange() { this.triggerEvent('numChange', { num: this.properties.num, nameId: this.properties.nameId }) }, add() { this.setData({ num: this.properties.num + this.properties.int }) this.numChange() }, sub() { this.setData({ num: this.properties.num - this.properties.int }) this.numChange() } } }) 在组件内部我定义了4个属性,properties是父组件传给子组件的属性。 nameId用来标识子组件的唯一性,如果在父组件内有多个计数器,子组件想把改变的数据传给父组件时可以用到; num代表计数器中的数字,默认为0; int代表加减一次改变多少,默认为1; min代表计数器的最小值,等于这个值时减号会消失,默认为0。 同时在子组件内定义了两个方法: add点击加号触发,首先改变子组件内部的数字,同时触发numChange方法将改变的数字传到组件外部; sub点击减号触发,首先改变子组件内部的数字,同时触发numChange方法将改变的数字传到组件外部。 3. 引入组件假如我要在index.wxml里引入组件: index.json { "usingComponents": { "num-controller": "/components/num-controller/num-controller" } } 想在页面中使用组件必须在json文件里注册组件。 index.js Page({ data: { num1: 0, num2: 10, num3: 100 }, numChange(event) { const {num, nameId} = event.detail this.setData({ [nameId]: num }) } }) data里的num1, num2, num3是从组件外传入的num,在numChange方法里用event.detail可以拿到组件内部通过this.triggerEvent传出来的数据,然后根据业务需求做逻辑修改。 四、复杂的组件(筛选面板)[图片] 这是一个二级菜单,点击左边(一级)会改变右边(二级)的展示。 1. 创建组件并引入组件内部: /components/filter-panel/index.wxml ... ... /components/filter-panel/index.json { "component": true, "usingComponents": {} } 组件外部: 假如我要在index.wxml里引入组件: index.json { "usingComponents": { "filter-panel": "/components/filter-panel/index" } } 这样就成功引入组件啦~(说真的组件化做好了非常舒服,后期会省很多力气) 2.组件与外部的数据传递(1) 固定数据渲染组件外部: index.wxml index.js data: { list: [ ['附近', '地铁站'], [['不限', '1km', '2km', '3km'], ['江汉路', '积玉桥', '洪山广场', '楚河汉街', '光谷广场']] ], active: [0, 0] }, 组件内部: /components/filter-panel/index.js Component({ /** * 组件的属性列表 */ properties: { list: Array, active: Array }, /** * 组件的方法列表 */ methods: { ... } }) 如果想从组件外向组件内传递数据,直接在外部引用时以属性的方式传入。 这里我传入了2个属性: list是二级选择面板渲染的数据。 active是用户选择的选项数据。 到这里组件已经可以正常展示了,但是点击显示选中项还未实现。 (2) 可变数据渲染控制组件active项的是外部的数据active: [0, 0],通过组件以属性的形式传到了内部。 /components/filter-panel/index.wxml {{item}} {{item}} /components/filter-panel/index.js Component({ /** * 组件的属性列表 */ properties: { list: Array, active: Array }, /** * 组件的方法列表 */ methods: { changeLevel() { this.triggerEvent('changeLevel', { level1: this.properties.active[0], level2: this.properties.active[1] }) }, changeLevel1(event) { const index = event.target.dataset.index this.setData({ active: [index, 0] }) this.changeLevel() }, changeLevel2(event) { const level2 = 'active[1]' const index = event.target.dataset.index this.setData({ [level2]: index }) this.changeLevel() } } }) (3) 组件内数据传到外部在这个组件内我定义了changeLevel这个方法,每次点击一级菜单或二级菜单的时候我就用过this.triggerEvent方法把active的值传到组件外部以供使用。 /components/filter-panel/index.js methods: { changeLevel() { this.triggerEvent('changeLevel', { level1: this.properties.active[0], level2: this.properties.active[1] }) }, changeLevel1(event) { const index = event.target.dataset.index this.setData({ active: [index, 0] }) this.changeLevel() }, changeLevel2(event) { const level2 = 'active[1]' const index = event.target.dataset.index this.setData({ [level2]: index }) this.changeLevel() } } 五、总结这个项目里倒是没用用到组件间的数据传递,所以只是组件和外部的传递,还算是比较简单,但是一定要思考清楚数据的变化状态。
2020-12-08 - 微信小程序页面之间正向传值和逆向传值的方法
微信小程序页面之间正向传值和逆向传值 正向传值 一 直接使用URL传值 [代码] wx.navigateTo({ url: `/pages/contacts-edit/contacts-edit?name=zhangsan&idx=1`, }) [代码] 但是如果一个对象结构比较复杂, 数据量比较大, 即使转换成JSON也有可能会被莫名其妙的截取. 所以使用URL传值的时候, 需要先编码 我是这样做的 [代码]// A页面触发事件, 跳转到B页面 _onClickCell: function (e) { let contacts = { name: '张三', phone: '13800001111', safePhone: '138****1111', idCard: '230524202113324455', safeIdCard: '230524********4455', typeStr: '成人', gender: '0', genderStr: '保密' } // 先对数据进行JSON let jsonStr = JSON.stringify(contacts) // 对数据进行URI编码, 如果不进行这一步操作, 数据有可能会被截断, 少量数据没有问题, 如果是一个大的对象, 就容易被截断获取不到完整的数据 let data = encodeURIComponent(jsonStr) wx.navigateTo({ url: `/pages/contacts-edit/contacts-edit?contacts=${data}&idx=${idx}`, }) }, // B页面再onLoad方法中接收参数 onLoad: function (options) { let idx = (!!options.idx) ? Number(options.idx) : -1 let contacts = {} if (!!options.contacts) { let jsonStr = decodeURIComponent(options.contacts) contacts = JSON.parse(jsonStr) } this.setData({ contacts, idx }) }, [代码] 二 使用eventChannel来传递 [代码]//A页面准备跳转到B页面 _onClickCell: function (e) { let address = { id: 457, name: '小艾-3', countryCode: '86', phone: '13892292222', reginoCode: '871', city: '市辖区', area: '海淀区', street: '东北旺路8号院中关村软件园8号楼华夏科技大厦', address: '中国北京市市辖区海淀区东北旺路8号院中关村软件园8号楼华夏科技大厦' }, wx.navigateTo({ url: '/pages/address-edit/address-edit', success: res => { // 这里给要打开的页面传递数据. 第一个参数:方法key, 第二个参数:需要传递的数据 res.eventChannel.emit('setAddressEditData', address) } }) } //B页面在onLoad方法中接收参数 onLoad: function (options) { // 接收上个页面传递来的数据 let eventChannel = this.getOpenerEventChannel() // setAddressEditData和上个页面设置的相同即可 eventChannel.on('setAddressEditData', (address) => { this.setData({ address: address || {}, }) }) }, [代码] 逆向传值 一 使用全局对象, 获取全部页面来逆向传值 [代码] _onClickComplete: function () { // 获取当前全部的页面栈 let arr = getCurrentPages() // 获取到要逆向传值的上一个页面 let lastPage = (arr.length >= 2) ? arr[arr.length - 2] : undefined // 判断拿到的上一个页面是不是我们要的页面 if (!!lastPage && lastPage.route == 'pages/contacts-list/contacts-list') { /* 这里我们就拿到了上一个页面的页面对象, 这里其实我们就可以使用lastPage做很多事情了, 例如直接操作lastPage.data, 修改上一个页面的数据 或者调用这个页面内的方法, 我上一个页面预留了一个更新方法, 所以这里就直接用上一个页面调用数据刷新的方法, 我这里给赋值, 就可以携带数据回上一个页面了 */ lastPage.updateContactList(this.data.contacts, this.data.idx) // 返回上一个页面 wx.navigateBack() } }, [代码] 二 使用eventChannel来逆向传值 B->A [代码]// B页面 _onClickComplete: function (e) { let eventChannel = this.getOpenerEventChannel() // updateAddressListData 这个方法需要上一个页面的支持, 上一个页面在navigateTo方法中的events数据中定义这个方法来接收数据 eventChannel.emit('updateAddressListData', this.data.address, this.data.idx) wx.navigateBack() }, // A页面需要的支持 _onClickCell: function (e) { wx.navigateTo({ url: '/pages/address-edit/address-edit', events: { // 这里用来接收后面页面传递回来的数据 updateAddressListData: (address, index) => { // 这里处理数据即可 } } }) } [代码] 代码片段
2020-07-21 - 小程序选择图片上传base64格式给后端
小程序选择图片上传base64格式给后端。 1 、在wxml,创建节点 [代码]<canvas canvas-id='myCanvas' style='width:{{imgWidth}}px;height:{{imgHeight}}px;'></canvas>[代码] [代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]"weui-cell weui-cell_access"[代码] [代码]hover-class[代码][代码]=[代码][代码]"weui-cell_active"[代码] [代码]bindtap[代码][代码]=[代码][代码]"chooseImage"[代码] [代码]data-id[代码][代码]=[代码][代码]"myCanvas"[代码][代码]>[代码] [代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]"weui-cell__bd"[代码][代码]>选择图片</[代码][代码]view[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]view[代码] [代码]class[代码][代码]=[代码][代码]"weui-cell__ft weui-cell__ft_in-access"[代码][代码]>[代码][代码] [代码][代码]<[代码][代码]image[代码] [代码]src[代码][代码]=[代码][代码]'{{canvasImg}}'[代码] [代码]style[代码][代码]=[代码][代码]'width:50px;height:50px;'[代码][代码]></[代码][代码]image[代码][代码]>[代码][代码] [代码][代码]</[代码][代码]view[代码][代码]>[代码][代码]</[代码][代码]view[代码][代码]>[代码] 2、在js,编写逻辑 [代码]chooseImage:[代码][代码]function[代码][代码](){[代码][代码] [代码][代码]var[代码] [代码]self = [代码][代码]this[代码][代码];[代码][代码] [代码][代码]var[代码] [代码]canvasId = e.currentTarget.dataset.id;[代码][代码] [代码][代码]console.log([代码][代码]"canvasId==>"[代码][代码], canvasId)[代码][代码] [代码][代码]var[代码] [代码]newDate = [代码][代码]new[代码] [代码]Date();[代码][代码] [代码][代码]var[代码] [代码]year = newDate.getFullYear();[代码][代码] [代码][代码]var[代码] [代码]month = newDate.getMonth() + 1;[代码][代码] [代码][代码]var[代码] [代码]date = newDate.getDate();[代码][代码] [代码][代码]var[代码] [代码]str = year + [代码][代码]"-"[代码] [代码]+ month + [代码][代码]"-"[代码] [代码]+ date;[代码] [代码] [代码][代码]wx.chooseImage({[代码][代码] [代码][代码]count: 1,[代码][代码] [代码][代码]success: [代码][代码]function[代码][代码](res) {[代码][代码] [代码][代码]console.log([代码][代码]"wx.chooseImage res=>"[代码][代码], res);[代码][代码] [代码][代码]var[代码] [代码]imgUrls = res.tempFilePaths;[代码][代码] [代码][代码]wx.getImageInfo({[代码][代码] [代码][代码]src: imgUrls[0],[代码][代码] [代码][代码]success: [代码][代码]function[代码][代码](res) {[代码][代码] [代码][代码]console.log([代码][代码]'wx.getImageInfo res=>'[代码][代码], res);[代码] [代码] [代码][代码]let imgWidth = res.width;[代码][代码] [代码][代码]let imgHeight = res.height;[代码][代码] [代码][代码]let imgName = str + [代码][代码]"-"[代码] [代码]+ res.path.slice(-20);[代码] [代码] [代码][代码]if[代码] [代码](imgWidth >= 2000) {[代码][代码] [代码][代码]imgWidth = imgWidth / 2;[代码][代码] [代码][代码]imgHeight = imgHeight / 2;[代码][代码] [代码][代码]}[代码] [代码] [代码][代码]self.setData({[代码][代码] [代码][代码]imgWidth: imgWidth,[代码][代码] [代码][代码]imgHeight: imgHeight,[代码][代码] [代码][代码]});[代码] [代码] [代码][代码]setTimeout(() => {[代码][代码] [代码][代码]self.createCanvas(imgUrls, imgWidth, imgHeight, canvasId, imgName);[代码][代码] [代码][代码]}, 100);[代码] [代码] [代码][代码]}[代码][代码] [代码][代码]})[代码][代码] [代码][代码]}[代码][代码] [代码][代码]});[代码][代码]},[代码][代码]createCanvas: [代码][代码]function[代码][代码](imgUrls, imgWidth, imgHeight, canvasId, imgName) {[代码][代码] [代码] [代码] [代码][代码]var[代码] [代码]self = [代码][代码]this[代码][代码];[代码] [代码] [代码][代码]var[代码] [代码]ctx = wx.createCanvasContext(canvasId);[代码][代码] [代码][代码]ctx.drawImage(imgUrls[0], 0, 0, imgWidth, imgHeight);[代码] [代码] [代码][代码]wx.showLoading({[代码][代码] [代码][代码]title: [代码][代码]'上传中'[代码][代码],[代码][代码] [代码][代码]})[代码] [代码] [代码][代码]ctx.draw([代码][代码]false[代码][代码], () => {[代码][代码] [代码][代码]wx.hideLoading();[代码][代码] [代码][代码]wx.canvasGetImageData({[代码][代码] [代码][代码]canvasId: canvasId,[代码][代码] [代码][代码]x: 0,[代码][代码] [代码][代码]y: 0,[代码][代码] [代码][代码]width: imgWidth,[代码][代码] [代码][代码]height: imgHeight,[代码][代码] [代码][代码]success: res => {[代码][代码] [代码][代码]// wx.hideLoading();[代码][代码] [代码][代码]// 3. png编码[代码][代码] [代码][代码]let pngData = upng.encode([res.data.buffer], res.width, res.height)[代码][代码] [代码][代码]// 4. base64编码[代码][代码] [代码][代码]let base64 = wx.arrayBufferToBase64(pngData)[代码][代码] [代码][代码]let base64Data = [代码][代码]'data:image/jpeg;base64,'[代码] [代码]+ base64;[代码][代码] [代码][代码]console.log([代码][代码]'base64Data=>'[代码][代码], base64Data);[代码][代码] [代码] [代码] [代码][代码]self.addImageFile(base64Data, canvasId, imgName);[代码][代码] [代码][代码]},[代码][代码] [代码][代码]fail(err) {[代码][代码] [代码][代码]wx.hideLoading();[代码][代码] [代码][代码]console.log(err)[代码][代码] [代码][代码]},[代码][代码] [代码][代码]})[代码][代码] [代码][代码]});[代码][代码] [代码][代码]},[代码][代码] [代码][代码]addImageFile: [代码][代码]function[代码][代码](base64Data, canvasId, imgName) {[代码][代码] [代码][代码]var[代码] [代码]self = [代码][代码]this[代码][代码];[代码] [代码] [代码][代码]let url = [代码][代码]"api/uploadImg.do"[代码][代码];[代码][代码] [代码][代码]let ticket = wx.getStorageSync([代码][代码]'ticket'[代码][代码]) || [代码][代码]""[代码][代码];[代码] [代码] [代码][代码]let data = {[代码][代码] [代码][代码]"ticket"[代码][代码]: ticket,[代码][代码] [代码][代码]"base64Data"[代码][代码]: encodeURIComponent(base64Data),[代码][代码] [代码][代码]"imgName"[代码][代码]: imgName[代码][代码] [代码][代码]}[代码] [代码] [代码][代码]wx.request({[代码][代码] [代码][代码]url: url, [代码][代码]//仅为示例,并非真实的接口地址[代码][代码] [代码][代码]data: data,[代码][代码] [代码][代码]header: {[代码][代码] [代码][代码]'content-type'[代码][代码]: [代码][代码]'application/json'[代码] [代码]// 默认值[代码][代码] [代码][代码]},[代码][代码] [代码][代码]method: [代码][代码]"POST"[代码][代码],[代码][代码] [代码][代码]success: [代码][代码]function[代码][代码](res) {[代码][代码] [代码][代码]let data = res.data;[代码][代码] [代码][代码]wx.hideLoading();[代码][代码] [代码][代码]},[代码][代码] [代码][代码]fail: [代码][代码]function[代码][代码](err) {[代码][代码] [代码][代码]wx.hideLoading();[代码] [代码] [代码][代码]}[代码][代码] [代码][代码]});[代码] [代码] [代码][代码]},[代码] 3.page 里面要引入 const upng = require("./UPNG.js"); 在upng.js里面要引入pako.min.js 具体的可github搜索查找。 [图片] 4、根据小程序前端转base64给后端的过程中,小程序会出现奔溃,延时长的情况,因为转base64比较耗时间,实际开发中还是建议让后端去转,使用api接口 wx.uploadFile(OBJECT) 但有需要的也可以了解下该方法
2018-09-11 - 微信小程序可以主动推送消息给用户吗?
我们是招投标的网站,会在我们的网站上发布新的招投标的项目,我们想做一个小程序方便招投标的用户及时知道项目的进展,我想用微信小程序实现不同用户绑定不同的项目,当关注的项目有了新的动态时,可以主动给关注此项目的用户推送消息,提示项目有新的动态,小程序可以实现吗?
2018-04-14