欣歆知道
XXINZHIDAO
首页
专栏
日报
题库
工具
登录
微信小程序开发(基础、配置、布局、标签)
2020-04-02
bokequ
博客人生
1399
1200
分类栏目:
小程序
文章标签:
小程序
app.json文件中的属性: ```json // 项目启动页面,以后就以这个为准 "entryPagePath": "路径(例如:pages/index/index)", ``` ## 全局配置-window配置 window字段:用于设置小程序的状态栏、导航条、标题、窗口背景色。 在 app.json 配置文件中配置(和pages同级) ```json "window": { // 标题 "navigationBarTitleText": "尚硅谷", // 状态栏和导航条颜色 "navigationBarBackgroundColor": "#f3514f", // 窗口样式(默认不展示,true开启) "enablePullDownRefresh": true, // 窗口背景色 "backgroundColor": "#efefef", // 窗口下拉刷新点颜色(light: 亮色; dark: 暗色) "backgroundTextStyle": "light" } ``` ## 全局配置-tabbar配置 tabBar字段:定义小程序顶部、底部tab栏,用以实现页面之间的快速切换,可以通过tabBar配置项指定tab栏的表现,以及tab切换时显示的对应页面。 在 app.json 配置文件中配置(和pages同级) ```json tabBar常用的配置项: // 控制tab栏上面的边线是否显示 borderStyle: // 整条tab栏的背景色 backgroundColor: // 当前tabBar被激活时对应的文字颜色 selectedColor: list: selectedIconPath: pagePath: iconPath: text: // 没有被激活时文字的颜色 color: // 设置tab栏位置(“top”, “bottom"),默认bottom,设置为”top"时图标不显示 position: ``` > tab按数组的顺序排序,list配置最少2个、最多5个tab。 配置实例: ```json "tabBar": { "selectedColor": "#f3514f", "color": "#666666", "backgroundColor": "#efefef", "borderStyle": "black", "list": [ { "text": "首页", "pagePath": "pages/index/index", "iconPath": "/assets/error1_icon@2x.png", "selectedIconPath": "/assets/mm_icon@2x.png" }, { "text": "分类", "pagePath": "pages/cate/cate", "iconPath": "/assets/error1_icon@2x.png", "selectedIconPath": "/assets/mm_icon@2x.png" }, { "text": "购物车", "pagePath": "pages/cart/cart", "iconPath": "/assets/error1_icon@2x.png", "selectedIconPath": "/assets/mm_icon@2x.png" }, { "text": "我的", "pagePath": "pages/profile/profile", "iconPath": "/assets/error1_icon@2x.png", "selectedIconPath": "/assets/mm_icon@2x.png" } ] } ``` ## 页面配置 小程序的页面配置,也称局部配置,每一个小程序页面也可以使用自己的.json文件对本页面的窗口表现进行配置。 > 需要注意的是:页面配置文件的属性和全局配置文件中的window属性几乎一致,只不过这里不需要额外指定window字段,因此如果出现相同的配置项,页面中配置项会覆盖全局配置文件中相同的配置项。 ## 项目配置文件和配置sass 在创建项目的时候,每个项目的根目录生成两个config.json文件,用于保存开发者在工具上的个性化配置,例如和编译有关的配置。 当重新安装微信开发者工具或换电脑工作时,只要载入同一个项目的代码包,开发者工具就会自动恢复到当时开发项目时的个性化配置。 > project.config.json:项目配置文件,常用来进行配置公共的配置 > > project.private.config.json:项目私有的配置,常用来配置个人的配置 > > > > 注意事项: > > 1.project.private.config.json写到.gitignore避免版本管理的冲突。 > > 2.与最终编译结果有关的设置必须设置到project.config.json中。 sass配置: > 在project.config.json中的setting下配置: > > "useCompilerPlugins": ["sass"] ## sitemap.json文件 sitemap.json文件:配置小程序及其一名是否允许被微信索引,提高小程序在微信内部被用户搜索到的概率。 微信现已开放小程序内搜索,开发者可以通过sitemap.json配置来设置小程序页面是否允许微信索引。当开发者允许微信索引时,微信会通过爬虫的形式,为小程序的页面内容建立索引。当用户的搜索词条触发该索引时,小程序的页面将可能展示在搜索结果中。 > 注意事项: > > sitemap.json文件不是必须的 > > 1.没有sitemap.json文件,则默认所有页面都能被索引的 > > 2.{"action":"allow","page":"*"}是优先级最低的默认规则,未显示指明"disallow"的都默认被索引。 示例解释: ```json // 表示所有的小程序页面都能被索引得到 { "desc": "关于本文件的更多信息", "rules": [{ "action": "allow", "page": "*" }] } // 表示除index页面不能被索引到,其他页面都能被索引到 { "desc": "关于本文件的更多信息", "rules": [{ "action": "disallow", "page": "pages/index/index" }] } // 表示只有index页面能被索引到,其他页面都不能被索引到 { "desc": "关于本文件的更多信息", "rules": [{ "action": "allow", "page": "pages/index/index" }] } ``` ## 小程序的样式和组件介绍 在开发web网站的时候:页面的结构由HTML进行编写,例如:经常会用到的div、p、span、img、a等标签。 页面的样式由CSS进行编写,例如:经常会采用.class、#id、element等选择器。 > 但在小程序中不能使用HTML标签,也没有DOM和BOM,CSS也仅仅支持部分选择器 > > 小程序提供了WXML进行页面结构编写,同时提供了WXSS进行页面的样式编写 > > > > WXML提供了view、text、image、navigator等标签来构建页面结构,只不过在小程序中将标签称为组件 > > WXSS对CSS扩充和修改,新增了尺寸单位rpx、提供了全局的样式和局部样式,另外需要注意的是WXSS仅支持部分CSS选择器 ## 样式-尺寸单位rpx 随着智能手机的发展,手机设备的宽度也逐步多元化,这就需要开发者在开发的时候,需要适配不同屏幕宽度的手机。为了解决屏幕适配的问题,微信小程序推出了rpx单位。 > rpx:是小程序新增的自适应单位,它可以根据不同设备的屏幕宽度进行自适应缩放。 > > 小程序规定任何型号手机:屏幕宽都为750rpx > 开发建议: > > 1、开发微信小程序时设计师可以用iPhone6作为视觉稿的标准,iPhone6的设计稿一般是750px; > > 2、如果用iPhone6作为视觉稿的标准量取多少px,直接写多少rpx即可,开发起来更方便,也能够适配屏幕的宽度。 > > 设计稿宽度是750px,而iPhone6的手机设备宽度是375px,设计稿想完整展示到手机中,就需要缩小一倍在iPhone6下,px和rpx的换算关系是:1rpx = 0.5px, 750rpx = 375px,刚好能够填充满整个屏幕的宽度。 ## 样式-全局样式和局部样式 在进行网页开发时,我们经常创建global.css、base.css或者reset.css作为全局样式文件进行重置样式或者样式统一,然后在每个页面或组件中写当前页面或组件的局部样式,小程序中也存在全局样式和局部样式。 > 全局样式:指在app.wxss中定义的样式规则,作用于每一个页面,例如:设置字号、背景色、宽高等全局样式。 ## 组件-组件案例演示 小程序常用的组件: 1、view组件 2、swiper和swiper-item组件 3、image组件 4、text组件 5、navigator组件 6、scroll-view组件 7、字体图标 ## 组件案例-轮播图区域绘制 在小程序中,提供了swiper和swiper-item组件实现轮播图: > swiper:滑块视图容器,其中只能放置swiper-item组件 > > swiper-item:只能放置在swiper组件中,宽高自动设置为100%,代表swiper中的每一项 > > autoplay:轮播图自动切换 (默认为true,可设置为false) > > interval:自动播放时长(毫秒) > > indicator-dots:指示点(。。。),属性值为布尔值,默认为true > > indicator-color:指示点颜色 > > indicator-active-color:指示点当前选中的颜色 > > circular:属性值为布尔值,默认为true,作用是轮播到最后一张图时,衔接到第一张图,没有跳转的感觉 ## 组件案例-轮播图图片添加 在小程序中,如果需要渲染图片,需要使用image组件,常用的属性有4个: > 1、src属性:图片资源地址 > > 2、mode:图片裁剪、缩放的模式 > > 3、show-menu-by-longpress:长按图片显示菜单 > > 4、lazy-load:图片懒加载 注意事项: > 1、image默认具有宽度和高度,宽是320px高度是240px; > > 2、image组件不给src属性设置图片地址,也占据宽和高; ## 组件案例-绘制公司信息区域 在小程序中,如果需要渲染文本,需要使用text组件,常用的属性有2个: > 1、user-select:文本是否可选,用于长按选择文本,属性值为布尔值,默认为true > > 2、space:显示连续空格 > > 属性值: > > ensp:以中文字符空格一半大小来展示 > > emsp:以中文字符空格大小来展示 > > nbsp:以字符大小来展示 注意事项: > 1、除了文本节点以外的其他节点都无法长按选中 > > 2、text组件内只支持text嵌套 ## 组件案例-商品导航区域 1、view:视图容器 2、image:图片组件 3、text:文本组件 ## 组件案例-跳转到商品列表 在小程序中,如果需要进行跳转,需要使用navigation组件,常用的属性有2个: 1、url:当前小程序内的跳转链接 2、open-type:跳转方式 - navigate:保留当前页面,跳转到应用内的某个页面,但是不能跳到tabbar页面 - redirect:关闭当前页面,跳转到应用内的某个页面。但是不能跳转到tabbar页面 - switchTab:跳转到tabBar页面,并关闭其他所有非tabBar页面 - reLaunch:关闭所有页面,打开到应用内的某个页面 - navigateBack:关闭当前页面,返回上一页或多级页面 注意事项: > 1、路径后可以带参数。参数与路径之间使用?分隔,参数键与参数值用=相连,不同参数用&分隔 > > 2、open-type="switchTab"时不支持传参 ## 组件案例-推荐商品区域 在小程序中,如果想实现内容滚动,需要使用scroll-view组件 > scroll-view:可滚动视图区域,适用于需要滚动展示内容的场景,用于在小程序中实现类似于网页中的滚动条效果,用户可以通过手指滑动或者点击滚动条来滚动内容。 > > 属性: > > 1. scroll-x:允许横向滚动 > 2. scroll-y:允许纵向滚动 ## 组件案例-字体图标的使用 在项目中使用到的小图标,一般由公司设计师进行设计,设计好以后上传到阿里巴巴矢量图标库,然后方便程序员来进行使用。 引入方式: 在项目根目录下建立iconfont文件夹; 在文件夹中新建iconfont.scss或iconfot.wxss文件; 在文件中将图标代码粘贴进去; ```css @font-face { font-family: "iconfont"; /* Project id 4449514 */ src: url('//at.alicdn.com/t/c/font_4449514_wsisx3xxran.woff2?t=1709256615447') format('woff2'), url('//at.alicdn.com/t/c/font_4449514_wsisx3xxran.woff?t=1709256615447') format('woff'), url('//at.alicdn.com/t/c/font_4449514_wsisx3xxran.ttf?t=1709256615447') format('truetype'); } .iconfont { font-family: "iconfont" !important; font-size: 16px; font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-shouye:before { content: "\e8c6"; } ``` 在app.wxss或app.scss中添加引用; ```css // 引入 @import "./iconfont/iconfont.scss"; ``` 在需要使用的地方用class的方式引入即可; ```html
同城配送
行业龙头
半小时送达
100%好评
``` 引入阿里巴巴矢量图标之后会出现一个错误: > [渲染层网络层错误] Failed to load font http://at.alicdn.com/t/c/font_4449514_wsisx3xxran.woff2?t=1709256615447 > net::ERR_CACHE_MISS > > > > 解决方法: > > 在阿里巴巴矢量图标-我的项目-项目设置-字体格式-勾选Base64-保存-此案件更新代码-重新复制生成的代码粘贴到iconfont.wxss或者iconfont.scss文件中替换原来的即可。 ## 背景图的使用 当编写小程序的样式文件时,我们可以使用background-image属性来设置元素的背景图像。 注意事项: > 小程序的background-image不支持本地路径!需要使用网络图片,或者base64,或者使用
组件 # 微信小程序开发(事件) ## 事件绑定和事件对象 小程序中绑定事件与在网页开发中绑定事件几乎一致,只不过在小程序不能通过on的方式绑定事件,也没有click等事件,小程序绑定事件使用bind方法,click事件也需要使用tap事件来进行代替,绑定事件的方式有两种: > 第一种方式:bind:事件名,bind后面需要跟上冒号,冒号后面跟上事件名,例如:
> > 第二种方式:bind事件名,bind后面直接跟上事件名,例如
事件处理函数需要写到.js文件中,在.js文件中需要调用小程序提供的Page方法来注册小程序的页面,我们可以直接在Page方法中创建事件处理函数。 示例: ```html
绑定事件
绑定事件
``` 事件: ```js // 事件处理函数 Page({ // 事件处理函数需要写到page函数中 handler(event) { console.log("事件触发了~"); console.log(event); }, // 输入框获取值事件 getData(event) { console.log(event.detail.value); } }) ``` input输入框在app.wxss或app.scss中公共处理 ```css input { border: 1px solid #ccc; } ``` ## 事件分类以及阻止事件冒泡 > 事件分为:**冒泡事件**和**非冒泡事件** > > **冒泡事件**:当一个组件的事件被触发后,该事件会向父节点传递 > > **非冒泡事件**:当一个组件的事件被触发后,该事件不会向父节点传递 > > 使用bind绑定的事件,会触发冒泡事件,如果想阻止事件冒泡,可以使用catch来绑定事件。 示例: ```html // 冒泡事件
按钮
// 非冒泡事件
按钮
``` ## 事件传参-data-*自定义数据 事件传参:在触发事件时,将一些数据作为参数传递给事件处理函数的过程,就是事件传参。 在微信小程序中,我们经常会在组件上添加一些自定义数据,然后在事件处理函数中获取这些自定义数据,从而完成业务逻辑的开发。 在组件上通过 data- * 的方式定义需要传递的数据,其中 * 是自定义属性。例如: < view data-id="100" bindtap="handler"/>,然后通过事件对象进行获取自定义数据。 > 注意事项: > > 1. event.target是指事件触发者,event.currentTarget是指事件绑定者; > 2. 使用data-方法传递参数的时候,多个单词由连接符-连接,连接符写法会转换成驼峰写法; > 3. 使用data-方法传递参数的时候,而大写字符会自动转换成小写字符; ## 事件传参-mark自定义数据 小程序进行事件传参的时候,除了使用data- * 属性传递参数外,还可以使用mark标记传递参数。 mark是一种自定义属性,可以在组件上添加,用于来识别具体触发事件的target节点。同时mark还可以用于承载一些自定义数据。 在组件上使用mark:自定义属性的方式将数据传递给事件处理函数,例如:< view mark:id="100" bindtap="handler"/ >然后通过事件对象进行获取自定义数据。 > mark和data- * 很相似,主要区别在于: > > mark包含从触发事件的节点到根节点上所有的mark:属性值 > > currentTarget.dataset或者target.dataset只包含事件绑定者 或者 事件触发者那一个节点的data- * 值 ## 声明和绑定数据 小程序页面中使用的数据均需要在Page()方法的data对象中进行声明定义。 在将数据声明好以后,在WXML使用Mustache语法(双大括号{{}})将变量包起来,从而将数据绑定。 在{{}}内部可以做一些简单的运算,支持如下几种方式: 1. **算数运算** 2. **三元运算** 3. **逻辑判断** 4. **其他........**
注意事项:在{{ }}语法中,只能写表达式,不能写语句,也不能调用js相关的方法。
示例: ```html
{{school}}
{{ obj.name }}
{{ obj.address }}
绑定属性值
{{ number + sum }}
{{ sum - number }}
{{ number * sum }}
{{ sum / number }}
{{ sum > number ? "大于" : "小于" }}
{{ sum === sum }}
``` 数据: ```js data: { id: '1', school: '尚硅谷', obj: { name: '张三', address: '昆明市五华区小康大道' }, checkIsNo: false, number: 2, sum: 10 } ``` ## setData()修改数据 小程序中修改数据不推荐通过赋值的方式进行修改,通过赋值的方式修改数据无法改变页面的数据。而是要调用
setData()
方法进行修改,setData()方法将接收对象作为参数,key是需要修改的数据,value是更新的值。 setData()方法有两个作用: 1. 更新数据 2. 驱动视图更新 示例: ```html
{{ number }}
更新数据
``` 数据: ```js data: { number: 10 } ``` 事件: ```js // 更新数据 updateData(event) { // 获取数据 // this.data.number // 通过赋值的方式直接修改数据 // console.log(this.data.number += 1); this.setData({ // key:是需要更新的数据 // value:是需要更新的值 number: this.data.number + 1 }) } ``` ## setData()-修改对象类型数据 1. 新增 单个 / 多个属性 2. 修改 单个 / 多个属性 3. 删除 单个 / 多个属性 示例: ```html
{{ userInfo.name }}
{{ userInfo.age }}
{{ userInfo.test }}
修改对象类型数据
``` 数据: ```js data: { number: 10, userInfo: { name: 'tom', age: 10, test: 'kaldfjdlasdfjdlasdfjlasfjas' } } ``` 事件: ```js // 更新userInfo updateUserInfo() { // // 新增 单个 / 多个属性 // this.setData({ // // 如果是给对象新增属性,可以将key写成数据路径的方式 a.b.c // 'userInfo.name': 'tom', // 'userInfo.age': 10 // }) // 修改 单个 / 多个属性 // this.setData({ // // 如果需要修改对象属性,可以将key写成数据路径的方式 a.b.c // 'userInfo.name': 'Jerry', // 'userInfo.age': 18 // }) // 目前进行新增和修改都是使用数据路径,如果数据量小还可以 // 如果修改的数据很多,则非常麻烦 // 优化:可以使用 ES6 提供的展开运行符 和 Object.assign() // 使用 ES6 提供的展开运行符 // 通过展开运算符能够将对象中的属性赋值给另一个对象 // 后面的属性会覆盖前面的属性 // const userInfo = { // ...this.data.userInfo, // name: 'jerry', // age: 20 // } // this.setData({ // userInfo // }) // Object.assign() 将多个对象合并为一个对象 // const userInfo = Object.assign(this.data.userInfo,{name:'张三'},{age: 33}) // this.setData({ // userInfo // }) // 删除 单个 / 多个属性 // delete this.data.userInfo.age // this.setData({ // userInfo: this.data.userInfo // }) // 删除多个属性 const {age, test, ...rest} = this.data.userInfo this.setData({ userInfo: rest }) } ``` ## setData()-修改数组类型数据 1. 新增数组元素 ```js // 方式一: this.data.list.push(4); this.setData({ list: this.data.list }) // 方式二: const newList = this.data.list.concat(4); this.setData({ list: newList }) // 方式三: const newList = [...this.data.list, 4]; this.setData({ list: newList }) ``` 2. 修改数组元素 ```js // 更新数组元素 this.setData({ // 方式一: 数字类型 // 'list[1]': 6 // 方式二:对象类型 'list[0].id':'sss1' }) ``` 3. 删除数组元素 ```js // 方式一: this.data.list.splice(1,1); this.setData({ list: this.data.list }) // 方式二: const newList = this.data.list.filter(item => item != 2) this.setData({ list: newList }) ``` ## 简易双向数据绑定 在WXML中,普通属性的绑定是单项的,例如:
如果希望用户输入数据的同时改变data中的数据,可以借助简易双向绑定机制,在对应属性之前添加model:前缀即可:例如
注意事项:简易双向绑定的属性值如下限制: > > 1、只能是一个单一字段的绑定,例如:错误用法:
> > 2、尚不能写data路径,也就是不支持数组和对象,例如:错误用法:
示例: ```html
是否同意 ``` 示例数据: ```js data: { value: 123, isChecked: false } ``` ## 列表渲染-基本使用 列表渲染就是指通过循环遍历一个数组或对象,将其中的每个元素渲染到页面上 在组件上使用wx:for 属性绑定一个数组或对象,即可使用每一项数据重复渲染当前组件 每一项的变量名默认为item,下标变量名默认为index > 在使用wx:for进行遍历的时候,建议加上wx:key属性,wx:key的值以两种形式提供: > > 字符串:代表需要遍历的array中item的某个属性,该属性的值需要是列表中唯一的字符串或数字,且不能动态改变 > > 保留关键字* this代表在for循环中的item本身,当item本身是一个唯一的字符串或者数字时可以使用 > 注意事项: > > 如果不加wx:key,就会报一个warning,如果明确知道该列表是静态,即以后数据不会改变,或者不必关注其顺序,可以选择忽略。 > > 在给wx:key添加属性值的时候,不需要使用双大括号语法,直接使用遍历的array中item的某个属性 示例: ```html
{{ item }}
{{ item.name }}-{{ index }}
{{ item }}
{{ item.name }}
{{ item }}
``` 示例数据: ```js data: { numList: [1,2,3], fruitList: [ {id: 1, name: '苹果'}, {id: 2, name: '柠檬'}, {id: 3, name: '西红柿'} ], obj: { name: 'tom', age: 10 } ``` ## 列表渲染-进阶用法 如果需要对默认的变量名和下标进行修改,可以使用wx:for-item和wx:for-index - 使用wx:for-item可以指定数组当前元素的变量名 - 使用wx:for-index可以指定数组当前下标的变量名 示例: ```html
{{ it }}
序号:{{ i }}
名称:{{ it.name }}
价格:{{ it.price }}
``` 数据: ```js // index.js Page({ data: { list: [1,2,3,4,5,6,7,8,9], obj:[ {id: "1", name: "苹果", price: 12}, {id: "2", name: "香蕉", price: 22}, {id: "3", name: "橙子", price: 98}, {id: "4", name: "西红柿", price: 15} ] } }) ``` block不是一个组件,只是渲染元素,也就是只是包装,可以组织代码结构,支持列表渲染。 block不会在页面中做任何渲染,只接受控制属性。 ## 条件渲染 条件渲染主要用来控制页面结构的展示和隐藏,在微信小程序中实现条件渲染有两种方式: 1. 使用wx:if、wx:elif、wx:else 属性组 2. 使用hidden属性 wx:if和hidden二者的区别: 1. wx:if:当条件为true时将结构展示出来,否则结构不会进行展示,通过
移除/增加节点
的方式来实现; 2. hidden:当条件为true时会将结构隐藏,否则结构会展示出来,通过
display样式属性
来实现; # 小程序生命周期 ## 生命周期-小程序运行机制 小程序启动可以分为两种情况,一种是冷启动,一种是热启动
冷启动:
如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动;
热启动:
如果用户已经打开过某小程序,然后在一定时间内再次打开小程序,此时小程序并未被销毁,只是从后台状态进入前台状态;
前台和后台状态
小程序启动后,界面被展示给用户,此时小程序处于
[前台]
状态 当用户[关闭]小程序时,小程序并没有真正被关闭,而是进入了
[后台]
状态,当用户再次进入微信并打开小程序,小程序又会重新进入[前台]状态
挂起
:小程序进入[后台]状态一段时间后(5秒),微信停止小程序JS线程执行,小程序进入
[挂起]
状态,当开发者使用了后台播放音乐、后台地理位置等能力时,小程序可以在后台持续运行,不会进入到挂起状态
销毁
:如果用户很久没有使用小程序,或者系统资源紧张,小程序会被销毁,即完全终止运行。 当小程序进入后台并被[挂起]后,如果很长时间(目前是30分钟)都未再次进入前台,小程序会被销毁 当小程序占用系统资源过高,可能会被系统销毁或被微信客户端主动回收。 ## 生命周期-小程序更新机制 在访问小程序时,微信会将小程序代码包缓存到本地。 开发者在发布了新的小程序版本以后,微信客户端会检查本地缓存的小程序又没有新版本,并进行小程序代码包的更新。 小程序的更新机制有两种:
启动时同步更新
和
启动时异步更新
启动时同步更新
:微信运行时,
会定期检查最近使用的小程序是否有更新
。如果有更新,下次启动时会同步进行更新,更新到最新版本后再次打开小程序。如果
用户长时间未使用小程序时,会强制同步检查版本更新
。
启动时异步更新
:在启动前没有发现更新,小程序每次
冷启动
时,都会异步检查是否有更新版本。
如果发现有新版本,将会异步下载新版本的代码包,将新版本的小程序在下一次冷启动进行使用,当前访问使用的依然是本地的旧版本代码
在启动时异步更新的情况下,如果开发者希望立刻进行版本更新,可以使用
wx.getUpdateManager
API进行处理。在有新版本时提示用户重启小程序更新新版本。 示例: ```js // app.js App({ // 是小程序的钩子函数,这个钩子函数在冷启动时肯定会执行到 // 当小程序冷启动时,微信会自动向后台请求新版本的信息,如果有新版本,会立即进行下载 onLaunch () { // 用户wxwx.getUpdateManager方法监听下载的状态 const updateManager = wx.getUpdateManager(); // 当下载完成新版本以后,会触发onUpdateReady回调函数 updateManager.onUpdateReady(function () { // 在回调函数中给用户提示 wx.showModal({ title: '更新提示', content: '新版本已经准备好,是否重新启动应用', success(res) { if (res.confirm) { // 强制当前小程序使用新版本并且会重启当前小程序 updateManager.applyUpdate(); } } }) }) } }) ``` ## 生命周期-小程序生命周期介绍 应用生命周期是指应用程序进程
从创建到消亡的整个过程
小程序的生命周期指的是
小程序从启动到销毁的整个过程
一个小程序完整的生命周期由
应用生命周期
、
页面生命周期
和
组件生命周期
三部分来组成
小程序生命周期伴随着一些函数
,这些函数由小程序框架本身提供,被称为
生命周期函数
,生命周期函数会按照顺序依次自动触发调用。 帮助程序员在特定的时机执行特定的操作,辅助程序员完成一些比较复杂的逻辑。 ## 生命周期-应用生命周期 应用生命周期通常是指一个小程序从 启动→运行→销毁的整个过程 应用生命周期伴随着一些函数,我们称为
应用生命周期函数
,应用生命周期函数需要
在app.js文件的App()方法中进行定义,App()方法必须在app.js中进行调用,主要用来注册小程序。
应用生命周期函数由
onLaunch
、
onShow
、
onHide
三个函数组成。 > 启动小程序: > > onLaunch:小程序初始化(只会触发一次)→ 初始化工作 > > onShow:显示工作 > > 有两个触发时机:小程序启动时 或 切前台时 > > onHide:后台工作状态 从小程序生命周期的角度来看,
我们一般讲的[启动]专指冷启动
,热启动一般被称为后台切前台。 ## 生命周期-页面生命周期 页面生命周期就是指小程序页面从 加载 → 运行 → 销毁的整个过程
页面生命周期函数需要在Page() 方法中进行定义
> 访问页面: > > 1. onLoad:监听页面加载函数,一个页面只会调用一次 > > 2. onShow:监听页面展示函数 > > onHide:监听页面隐藏函数 > > 3. onReady:监听初次渲染完成函数,一个页面只会调用一次 > > 4. onUnload:监听页面卸载函数 navigator
(跳转非tabBar页面)
组件跳转页面时: open-type=redirect时,销毁当前页面,跳转到下一个页面; open-type=navigate时,隐藏当前页面,跳转到下一个页面; ## 生命周期-生命周期两个细节补充 1. tabBar页面之间相互切换,页面不会被销毁; 2. 点击左上角,返回上一个页面,会销毁当前页面; # 小程序API ## API介绍 小程序开发框架提供丰富的微信原生API,可以方便的调起微信提供的能力,例如:获取用户信息、微信登录、微信支付等,
小程序提供的API几乎都挂载在wx对象下,
例如:wx.request()、wx.setStorage()等,
wx对象实际上就是小程序的宿主环境微信所提供的全局对象
小程序API分类
: 异步API:特征:通常都接受一个object类型的参数,例如:wx.request({}) 同步API:特征:约定以Sync结尾,例如:wx.setStorageSync() 事件监听API:特征:约定以on开头,例如:wx.onAppHide() > 异步API支持callback(回调函数)& Promise两种调用方式: > > 1. 当接口参数Object对象中不包含success/fail/complete时将默认返回Promise > 2. 部分接口如request,uploadFile本身就有返回值,因此不支持Promise风格的调用方式,它们的promisify需要开发者自行封装。 ## 发起网络请求 发起网络请求获取服务器的数据,需要使用wx.request()接口API
wx.request()请求的域名必须在微信公众平台进行配置
,如果使用wx.request()请求未配置的域名,在控制台会有相应的报错。(request合法域名校验出错) 示例: ```js /** * 获取数据 */ getData () { // 如果需要发起网络请求,需要使用wx.request API wx.request({ // 接口请求地址 url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner', // 请求方式 method: 'GET', // 请求参数 data: {}, // 请求头信息 header: {}, // API 调用成功后,执行的回调 success: (res) => { console.log(res) if (res.data.code == 200) { this.setData({ list: res.data.data }) } }, // API 调用失败,执行的回调 fail: (err) => { console.log(err) }, // 不管成功还是失败都会执行的回调 complete: (res) => { console.log(res) } }) } ``` > 跳过域名的校验的开发: > > 1. 在微信开发者工具中,点击详情按钮,切换到本地详情,将
不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书 勾选上
> 2. 在真机上,需要点击胶囊区域的分析按钮,在弹框中选择
开发调试
,重启小程序后即可 ## 界面交互-loading提示框 小程序提供了一些用于界面交互的API,例如:loading提示框、消息提示框、模态对话框等API > loading提示框常配合网络请求来使用,用于增加用户体验,对应的API有两个: > > 1. wx.showLoading()显示loading提示框 > 2. wx.hideLoading()关闭loading提示框 > > showLoading()和hideLoading()需要配对使用 示例: ```js /** * 获取数据 */ getData () { // 显示loading提示框 wx.showLoading({ // 用来显示提示的内容 // 提示的内容不会换行,如果提示的内容比较多,因为在同一行展示 // 多出来的内容就会被隐藏 title: '数据加载中...', // 是否显示遮罩层,防止触摸穿透, // 默认值为false,如果为true则提示框显示的时候还可以进行其他操作,false则不能进行其他操作 mask: false }) // 如果需要发起网络请求,需要使用wx.request API wx.request({ // 接口请求地址 url: 'https://gmall-prod.atguigu.cn/mall-api/index/findBanner', // 请求方式 method: 'GET', // 请求参数 data: {}, // 请求头信息 header: {}, // API 调用成功后,执行的回调 success: (res) => { if (res.data.code == 200) { this.setData({ list: res.data.data }) } }, // API 调用失败,执行的回调 fail: (err) => { }, // 不管成功还是失败都会执行的回调 complete: (res) => { // 关闭loading提示框 wx.hideLoading() } }) } ``` ## 界面交互-模态对话框-消息提示框 wx.showModal():模态对话框,常用于询问用户是否执行一些操作 例如:询问用户是否退出登录,是否删除该商品等 wx.showToast():消息提示框,根据用户的某些操作来告知操作的结果 例如:退出成功给用户提示,提示删除成功等 示例: ```html
删除案例
``` 事件: ```js // 删除案例 异步 async delHandle () { // showModel 显示模态对话框 const {confirm} = await wx.showModal({ content: '是否删除该案例', title: '提示' }) if (confirm) { wx.showToast({ title: '删除成功', icon: 'success', duration: 2000 }) } else { wx.showToast({ title: '取消删除', icon: 'error', duration: 2000 }) } } ``` ## 本地存储 小程序本地存储是指在小程序中使用API将数据存储在用户的设备上,以便小程序运行时和下次启动时快速地读取这些数据。 > 小程序本地存储API分为: > > 同步API:常用的4个方法 > > - 存储:wx.setStorageSync() > - 获取:wx.getStorageSync() > - 删除:wx.removeStorageSync() > - 清空:wx.clearStorageSync() > > 异步API:常用的4个方法 > > - 存储:wx.setStorage() > - 获取:wx.getStorage() > - 删除:wx.removeStorage() > - 清空:wx.clearStorage()
注意事项:对象类型的数据,可以直接进行存储获取,无需使用JSON.stringify()、JSON.parse()转换
示例: ```html
存储
获取
删除
清空
``` 事件: ```js // 存储 setStorage () { // ------------------- 同步 API ------------------- // 第一个存储的参数:本地存储中指定的key // 第二个存储的参数:需要存储的数据 // wx.setStorageSync('name', 1) // 在小程序中 // 如果存储的是对象类型数据,不需要使用JSON.stringify和JSON.parse进行转换 // 直接进行存储和获取即可 // wx.setStorageSync('obj', {name: 'tom', age: 10}) // ------------------- 异步 API ------------------- wx.setStorage({ key: 'num', data: 1 }) wx.setStorage({ key: 'obj', data: {name: 'zhangsan', age: 12} }) }, // 获取 async getStorage () { // ------------------- 同步 API ------------------- // const num = wx.getStorageSync('name') // const obj = wx.getStorageSync('obj') // console.log(num); // console.log(obj); // ------------------- 异步 API ------------------- const { data } = await wx.getStorage({ key: 'obj' }) console.log(data); }, // 删除 removeStorage () { // ------------------- 同步 API ------------------- // wx.removeStorageSync('name') // ------------------- 异步 API ------------------- wx.removeStorage({ key: 'num' }) }, // 清空 clearStorage () { // ------------------- 同步 API ------------------- // wx.clearStorageSync() // ------------------- 异步 API ------------------- wx.clearStorage() } ``` ## 路由与通信 > 在小程序中实现页面的跳转,有两种方式: > > 1. 声明式导航:navigator组件 > 2. 编程式导航:使用小程序提供的API > > - wx.navigateTo():保留当前页面,跳转到应用内的某个页面,但是不能跳到tabBar页面 > - wx.redirectTo():关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到tabBar页面 > - wx.switchTab():跳转到tabBar页面,路径后不能带参数 > - wx.reLaunch():关闭所有页面,打开到应用内的某个页面 > - wx.navigateBack():关闭当前页面,返回上一页面或多级页面 路径后可以带参数,参数与路径之间使用?分隔,参数键与参数值用=相连,不同参数用&分隔,例如:path?key=value&key2=value2 参数需要在跳转到的页面的
onLoad
钩子函数中通过形参进行接收 示例: ```html
navigateTo
redirectTo
switchTab
reLaunch
navigateBack
``` 事件: ```js // pages/my/my.js Page({ navigateTo () { // 保留当前页面,跳转到应用中其他页面,不能跳转到tabBar页面 wx.navigateTo({ // 没有传递参数写法 // url: '/pages/list/list' // 传递参数写法 url: '/pages/list/list?num=1' }) }, redirectTo () { // 关闭(销毁)当前页面,跳转到应用中其他页面,不能跳转到tabBar页面 wx.redirectTo({ url: '/pages/list/list' }) }, switchTab () { // 跳转到 tabBar 页面,不能跳转到非 tabBar 页面 // 路径后面不能携带参数 wx.switchTab({ url: '/pages/cart/cart' }) }, reLaunch () { // 关闭所有页面,然后跳转到应用中某个页面 wx.reLaunch({ url: '/pages/cart/cart' }) } }) // pages/list/list.js Page({ /** * 生命周期函数--监听页面加载 */ onLoad(options) { // 获取到页面传递过来的参数信息 console.log(options); }, navigateBack () { // 关闭当前页面,返回上一页或多级页面 // 默认只返回上一页 wx.navigateBack({ // 想返回几级就写几,例如:delta: 2 表示返回两级,如果是一级,不写也可以 delta: 1 }) } }) ``` ## 页面处理函数-上拉加载 上拉加载是小程序中常见的一种加载方式,当用户滑动页面到底部时,会自动加载更多的内容,以便用户继续浏览。 > 小程序中实现上拉加载的方式: > > 1. 在app.json或者page.json中配置距离底部距离:onReachBottomDistance;默认50px > 2. 在页面.js中定义onReachBottom事件监听用户上拉加载 示例: ```html
{{ item }}
``` css样式: ```css // page { // height: 1000px; // } view { height: 400rpx; display: flex; align-items: center; justify-content: center; } // 奇数 view:nth-child(odd) { background-color: lightskyblue; } // 偶数 view:nth-child(even) { background-color: lightpink; } ``` 事件及数据: ```js Page({ data: { numList: [1,2,3] }, /** * 页面上拉触底事件的处理函数 */ onReachBottom: function () { // 当用户上拉加载时,需要对数字进行累加,每次累加3个 // 获取目前数组中最后一项 n, n+1, n+2, n+3 wx.showLoading({ title: '数据加载中...' }) // 定时器 setTimeout(() => { // 获取数组中的最后一项 const lastNum = this.data.numList[this.data.numList.length-1] // 定义需要追加的元素 const newArr = [lastNum + 1, lastNum + 2, lastNum + 3] // 更新原始数据 this.setData({ numList: [...this.data.numList, ...newArr] }) wx.hideLoading() }, 1500) } }) ``` page.json配置: ```json { "usingComponents": { "navigation-bar": "/components/navigation-bar/navigation-bar" }, "onReachBottomDistance": 100 } ``` ## 页面处理函数-下拉刷新 下拉刷新是小程序中常见的一种刷新方式,当用户下拉页面时,页面会自动刷新,以便用户获取最新的内容。 > 小程序中实现下拉加载更多的方式: > > 1. 在app.json或者page.json中开启允许下拉,同时可以配置窗口、loading样式等 > 2. 在页面.js中定义onPullDownRefresh事件监听用户下拉刷新 事件: ```js // 监听用户下拉刷新 onPullDownRefresh () { // 用户下拉刷新 将数据重置 this.setData({ numList: [1,2,3] }) } ``` page.json配置 ```json { "usingComponents": { "navigation-bar": "/components/navigation-bar/navigation-bar" }, "onReachBottomDistance": 100, // 开启允许下拉 "enablePullDownRefresh": true, // 背景色 "backgroundColor": "#efefef", "backgroundTextStyle": "light" } ``` ## 增强scroll-view 使用scroll-view实现上拉加载更多和下拉刷新功能 ```js // subPages/xltjDetail/xltjDetail.js var app = getApp(); Page({ /** * 页面的初始数据 */ data: { statusBarHeight: 0, navContextHeight: 0, title: '返回', routeId: '', routeInfo: null, imageUrl: app.globalData.httpServerAddress + 'api/common/getImage?path=', tags: null, images: null, types: null, swiper_img: [],//图片放大预览数组 pageCon: false, meInfo: null, loginStatus: 'no', comments: null, recommends: null, placeholder: "", inputValue: "", focus: false, // 提交评论信息字段 开始 context: '', lx: '0', cid: '', uid: '', rid: '', // 提交评论信息字段 结束 isIos: false, pageHeight: 0, initKeyBoardHeight: false, keyBoardHeight: 0, initTextHeight: false, textHeight: 0, // 输入框弹出, 页面上滑 paddingBottom: 20, beforeScrollTop: 0, sTop: 0, // 容器 textBottom: -1000, elBottom: 0, }, /** * 生命周期函数--监听页面加载 */ onLoad(options) { this.setData({ statusBarHeight: app.globalStyle.statusBarHeight, navContextHeight: app.globalStyle.navContextHeight, routeId: options.xlId, loginStatus: app.globalData.loginStatus, }) this.initPage() this.loadDatas() }, /** * 生命周期函数--监听页面初次渲染完成 */ onReady() { }, /** * 生命周期函数--监听页面显示 */ onShow() { }, /** * 生命周期函数--监听页面隐藏 */ onHide() { }, /** * 生命周期函数--监听页面卸载 */ onUnload() { }, /** * 页面相关事件处理函数--监听用户下拉动作 */ onPullDownRefresh() { }, /** * 页面上拉触底事件的处理函数 */ onReachBottom() { }, /** * 用户点击右上角分享 */ onShareAppMessage() { return { title: "测试小程序朋友圈分享", query: "id=110101&name=heyzqt", imageUrl: "https://example.cn/test.png" } }, loadDatas: function () { this.getRouteDetails() this.getRouteComments() this.getRecommends() }, //预览图片,放大预览 preview(event) { let that = this; let currentUrl = event.currentTarget.dataset.src; wx.previewImage({ current: currentUrl, // 当前显示图片的http链接 urls: that.data.swiper_img // 需要预览的图片http链接列表 }) }, /** * 想去 */ routeXq() { if (this.data.loginStatus == "no") { this.goLoin(); } wx.request({ url: app.globalData.httpServerAddress + '/api/route/fllow', header: { token: app.globalData.token }, data: { rid: this.data.routeId }, success: (res) => { if (res.data.status === "0") { this.getRouteDetails() } } }) }, onShareTimeline: () => { return { title: "测试小程序朋友圈分享", query: "id=110101&name=heyzqt", imageUrl: "https://example.cn/test.png" } }, /** * 分享 */ routeFx() { if (this.data.loginStatus == "no") { this.goLoin(); } console.log("点击了分析"); wx.showShareMenu({ menus: ['shareAppMessage', 'shareTimeline'] //shareAppMessage必须得有 }) }, /** * 底部评论按钮 */ routePl() { if (this.data.loginStatus == "no") { this.goLoin(); } }, ///跳转登录 goLoin: function () { wx.navigateTo({ url: '/pages/login/login', }) }, /** * 获取线路详细信息 */ getRouteDetails() { wx.request({ url: app.globalData.httpServerAddress + 'api/route/details', header: { token: app.globalData.token }, data: { rid: this.data.routeId }, success: (res) => { let swiper_imgs = []; for (let i = 0; i < res.data.data.images.length; i++) { swiper_imgs.push(app.globalData.httpServerAddress + 'api/common/getImage?path=' + res.data.data.images[i].image) } this.setData({ routeInfo: res.data.data, tags: res.data.data.tag, types: res.data.data.type, images: res.data.data.images, meInfo: res.data.data.me, swiper_img: swiper_imgs }) } }) }, /** * 获取线路留言信息 */ getRouteComments() { wx.request({ url: app.globalData.httpServerAddress + 'api/route/comment', data: { rid: this.data.routeId }, success: (res) => { if (res.data.status === '0') { this.setData({ comments: res.data.data }) } } }) }, /** * 商家推荐服务 */ getRecommends() { wx.request({ url: app.globalData.httpServerAddress + 'api/route/recommends', data: { rid: this.data.routeId }, success: (res) => { if (res.data.status === '0') { this.setData({ recommends: res.data.data }) } } }) }, /** * 线路推荐服务商家 == 联系TA * @param {*} event 获取联系TA 的电话号码 */ callPhone(event) { const phone = event.currentTarget.dataset.phone wx.makePhoneCall({ phoneNumber: phone, }) }, bindconfirm() { this.setData({ pageCon: "false" }) wx.pageScrollTo({ scrollTop: this.data.sTop, }) wx.request({ url: app.globalData.httpServerAddress + 'api/route/newComment', header: { token: app.globalData.token }, data: { cid: this.data.cid, uid: this.data.uid, lx: this.data.lx, rid: this.data.rid, context: this.data.context }, success: (res) => { if (res.data.status === "0") { this.getRouteComments() } } }) }, //获取输入框内容 getCommentText(e) { var val = e.detail.value; this.setData({ context: val }) }, /** * 点击起始位置跳转到地图导航 * @param {*} e */ gogogo(e) { const latitude = parseFloat(this.data.routeInfo.lat) const longitude = parseFloat(this.data.routeInfo.lng) wx.openLocation({ latitude, longitude, scale: 18 }) }, async toComment(e) { if (this.data.loginStatus == "no") { this.goLoin(); } this.setData({ pageCon: "true", focus: true, cid: e.currentTarget.dataset.cid, lx: e.currentTarget.dataset.lx, rid: e.currentTarget.dataset.rid, uid: e.currentTarget.dataset.uid }) if (!this.data.initTextHeight) { // 获取输入框高度 // (之前这个方法与获取系统信息一起写在getSystemInfo里, iphone在输入框focus之前获取到的高度比focus后多很多, 我想可能是因为textarea是auto-height吧??) const { height: textHeight } = await this.getDomInfo('njxCommentTextarea'); this.setData({ textHeight, initTextHeight: true, sTop: e.currentTarget.offsetTop }) } // 通过id获取该项元素底边界 const idName = 'id-' + e.currentTarget.dataset.item; const { bottom: elBottom } = await this.getDomInfo(idName); this.setData({ focus: true, elBottom, placeholder: e.currentTarget.dataset.placeholder, }) this.fixTextarea(); }, // 获取键盘高度 getKeyBoardHeight(e) { const { height: keyBoardHeight, } = e.detail; if (!this.data.initKeyBoardHeight && keyBoardHeight !== 0) { // 一台设备只获取一次即可 this.setData({ keyBoardHeight, initKeyBoardHeight: true, }) } this.fixTextarea(); }, // 输入框定位 fixTextarea() { // 还未获取到键盘高度时先不执行此方法 if (this.data.keyBoardHeight < 0) return; // 容器进行计算前定位 = 页面高度 - 元素的底边界 - 输入框高度 let textBottom = this.data.pageHeight - this.data.elBottom - this.data.textHeight; let fixPosition = ({ value, beforeScrollTop }) => { this.setData({ textBottom: this.data.isIos ? value : value - 1, beforeScrollTop }) } if (textBottom > this.data.keyBoardHeight) { fixPosition({ value: this.data.isIos ? this.data.keyBoardHeight : this.data.keyBoardHeight - 1, }); } else { this.setData({ paddingBottom: this.data.textHeight + this.data.keyBoardHeight }) this.createSelectorQuery().select('#scrollNjx').boundingClientRect().selectViewport().scrollOffset().exec(res => { // 当前页面上滑高度 let beforeScrollTop = res[1].scrollTop; // 需要页面上滑高度 = 请见截图思考过程哈哈哈哈哈哈 const scrollTop = beforeScrollTop + this.data.keyBoardHeight - textBottom; wx.pageScrollTo({ scrollTop, success: () => { fixPosition({ value: this.data.keyBoardHeight, beforeScrollTop }); } }) }) } }, onBlur() { // 这个blur方法在iphone7中好像有bug....但就逮到一次, 后面不知道怎么复现....先这样儿 // 先将页面滑动复原 wx.pageScrollTo({ scrollTop: this.data.sTop, }) // 页面底部的内边距复原、输入框定位于页面下、清空输入框内容 this.setData({ paddingBottom: 20, textBottom: -this.data.textHeight, inputValue: "", }) }, // 通过ID获取元素信息 getDomInfo(domName) { return new Promise((resolve) => { this.createSelectorQuery().select('#' + domName).boundingClientRect().exec(function (res) { resolve(res[0]) }) }) }, // 获取textarea容器高度、设备系统 async initPage() { const { system, windowHeight: pageHeight } = wx.getSystemInfoSync(); this.setData({ isIos: system.includes('iOS'), pageHeight, }) } }) ```