视图容器。相当于是传统网页中的div,可以用来存放任何的其他子元素。相关的属性请参考
有时候我们的一些视图在手机指定的宽度和高度不够存放。那么可以放在scroll-view
中。
给scroll-view
添加scroll-x="{{true}}"
属性。
给scroll-view
添加white-space:nowrap;
样式。
给scroll-view
中的子元素设置为display:inline-block;
。
示例代码如下:
index.wxml代码:
<scroll-view class='scroll-x-view' scroll-x="{{true}}">
<view class='scroll-view-item bg_red'>view>
<view class='scroll-view-item bg_blue'>view>
<view class='scroll-view-item bg_green'>view>
<view class='scroll-view-item bg_yellow'>view>
scroll-view>
index.wxss代码:
.scroll-x-view{
width: 100%;
height: 200px;
background: #43234f;
white-space: nowrap;
}
.scroll-x-view .scroll-view-item{
width: 100px;
height: 100px;
display:inline-block;
}
.bg_red{
background: red;
}
.bg_blue{
background: blue;
}
.bg_green{
background: green;
}
.bg_yellow{
background: yellow;
}
给scroll-view
添加scroll-y="true"
属性。
给scroll-view
设置高度。
wxml和wxss示例代码如下:
<scroll-view class='scroll-y-view' scroll-y="{{true}}">
<view class='scroll-view-item bg_red'>view>
<view class='scroll-view-item bg_blue'>view>
<view class='scroll-view-item bg_green'>view>
<view class='scroll-view-item bg_yellow'>view>
scroll-view>
.scroll-y-view{
width: 100%;
height: 200px;
background: gray;
}
.scroll-y-view .scroll-view-item{
width: 100%;
height: 100px;
}
.bg_red{
background: red;
}
.bg_blue{
background: blue;
}
.bg_green{
background: green;
}
.bg_yellow{
background: yellow;
}
鼠标滚动到距离顶部或者左边、鼠标滚动到底部或者右边多少距离的时候会执行这个事件。默认的间隔是50像素。示例代码如下:
<scroll-view class='scroll-x-view' scroll-y bindscrolltoupper="scrollToUpper">
<view style="height:1000px;">
view>
scroll-view>
Page({
scrollToUpper: function(event){
console.log(event);
}
});
只要scroll-view
发生了滚动,就会执行这个事件。
<scroll-view class='scroll-x-view' scroll-y bindscrolltoupper="scrollEvent">
<view style="height:1000px;">
view>
scroll-view>
Page({
scrollEvent: function(event){
console.log(event);
}
});
在app里面,轮播图(banner)是非常常见的,因此小程序专门针对这个出了一个组件就是swiper。
swiper
就是一个包裹轮播图的容器,里面的子元素必须是swiper-item
组件。swiper
可以自动的轮播子元素,并且天生就带有指示点,还可以使用手指左右滑动。下面用代码的方式来演示一下:
<swiper class='swiper' style='width:{{width*2}}rpx;height:{{height*2}}rpx;'
autoplay indicator-dots indicator-color='blue' indicator-active-color='red'>
<swiper-item>
<image src='https://static-image.xfz.cn/1539770831_872.jpg'>image>
swiper-item>
<swiper-item>
<image src='https://static-image.xfz.cn/1541147489_121.jpeg'>image>
swiper-item>
swiper>
.swiper image{
width: 100%;
height: 100%;
}
Page({
data:{},
onLoad: function (options) {
var systemInfo = wx.getSystemInfoSync();
var windowWidth = systemInfo.windowWidth;
var width = windowWidth;
var height = width/4;
// 根据图片的宽高比例设置swiper的宽高
this.setData({
width: width,
height: height
});
},
});
属性名 | 类型 | 默认值 | 说明 | 最低版本 |
---|---|---|---|---|
indicator-dots | Boolean | false | 是否显示面板指示点 | |
indicator-color | Color | rgba(0, 0, 0, .3) | 指示点颜色 | 1.1.0 |
indicator-active-color | Color | #000000 | 当前选中的指示点颜色 | 1.1.0 |
autoplay | Boolean | false | 是否自动切换 | |
current | Number | 0 | 当前所在滑块的 index | |
current-item-id | String | “” | 当前所在滑块的 item-id ,不能与 current 被同时指定 | 1.9.0 |
interval | Number | 5000 | 自动切换时间间隔 | |
duration | Number | 500 | 滑动动画时长 | |
circular | Boolean | false | 是否采用衔接滑动 | |
vertical | Boolean | false | 滑动方向是否为纵向 | |
previous-margin | String | “0px” | 前边距,可用于露出前一项的一小部分,接受 px 和 rpx 值 | 1.9.0 |
next-margin | String | “0px” | 后边距,可用于露出后一项的一小部分,接受 px 和 rpx 值 | 1.9.0 |
display-multiple-items | Number | 1 | 同时显示的滑块数量 | 1.9.0 |
skip-hidden-item-layout | Boolean | false | 是否跳过未显示的滑块布局,设为 true 可优化复杂情况下的滑动性能,但会丢失隐藏状态滑块的布局信息 | 1.9.0 |
bindchange | EventHandle | current 改变时会触发 change 事件,event.detail = {current: current, source: source} | ||
bindanimationfinish | EventHandle | 动画结束时会触发 animationfinish 事件,event.detail 同上 | 1.9.0 |
正常情况下,一个组件设置了后,如果不通过js
或者css
动画,那么是很难实现移动的。如果我们有些组件设置完成后想要能够移动。那么我们就可以借助movable-view
组件来实现。
movable-view
组件,正如他的名字一样,是可以移动的容器,但是这个容器必须要放在movable-area
中才能移动。因此实际上是这两个组件配合使用才能实现移动的效果的。看以下示例代码:
<movable-area class="area" scale-area>
<movable-view class='view' direction="all" inertia out-of-bounds damping="10" friction="0" scale>知了黄勇movable-view>
movable-area>
.area{
width: 200px;
height: 200px;
background: blue;
}
.area .view{
width: 100px;
height: 100px;
background: red;
}
属性名 | 类型 | 默认值 | 说明 | 最低版本 |
---|---|---|---|---|
direction | String | none | movable-view的移动方向,属性值有all、vertical、horizontal、none | |
inertia | Boolean | false | movable-view是否带有惯性 | |
out-of-bounds | Boolean | false | 超过可移动区域后,movable-view是否还可以移动 | |
x | Number/String | 定义x轴方向的偏移,如果x的值不在可移动范围内,会自动移动到可移动范围;改变x的值会触发动画 | ||
y | Number/String | 定义y轴方向的偏移,如果y的值不在可移动范围内,会自动移动到可移动范围;改变y的值会触发动画 | ||
damping | Number | 20 | 阻尼系数,用于控制x或y改变时的动画和过界回弹的动画,值越大移动越快 | |
friction | Number | 2 | 摩擦系数,用于控制惯性滑动的动画,值越大摩擦力越大,滑动越快停止;必须大于0,否则会被设置成默认值 | |
disabled | Boolean | false | 是否禁用 | 1.9.90 |
scale | Boolean | false | 是否支持双指缩放,默认缩放手势生效区域是在movable-view内 | 1.9.90 |
scale-min | Number | 0.5 | 定义缩放倍数最小值 | 1.9.90 |
scale-max | Number | 10 | 定义缩放倍数最大值 | 1.9.90 |
scale-value | Number | 1 | 定义缩放倍数,取值范围为 0.5 - 10 | 1.9.90 |
animation | Boolean | true | 是否使用动画 | 2.1.0 |
bindchange | EventHandle | 拖动过程中触发的事件,event.detail = {x: x, y: y, source: source},其中source表示产生移动的原因,值可为touch(拖动)、touch-out-of-bounds(超出移动范围)、out-of-bounds(超出移动范围后的回弹)、friction(惯性)和空字符串(setData) | 1.9.90 | |
bindscale | EventHandle | 缩放过程中触发的事件,event.detail = {x: x, y: y, scale: scale},其中x和y字段在2.1.0之后开始支持返回 | 1.9.90 |
更多详情请查看小程序官方文档
官方文档
除了以上官方文档中描述的,还需要注意:
官网
官网
主要的资料请参考
这里补充几点:
在输入框被键盘挡住的时候,指定光标与键盘的距离,单位px(2.4.0起支持rpx)。取 input 距离底部的距离和 cursor-spacing 指定的距离的最小值作为光标与键盘的距离。如果没有指定这个值,并且输入框被挡到了,那么距离键盘的距离为0。
官网
components
文件夹,用来存放所有自定义的组件。mybox
这个组件,那么可以创建一个mybox
的一个文件夹。右键->新建Component
创建组件。这样创建的目的是在json文件中添加"component": true
,将其申明为一个组件。<view class="outter">
<view class="inner">自定义组件view>
view>
.outter{
width:200px;
height:200px;
background:burlywood;
}
.inner{
width:100px;
height:200px;
background:gray;
}
{
"usingComponents": {
"mybox": "/components/mybox/mybox"
}
}
<mybox>mybox>
在组件的js文件中,在properties
中添加属性,添加属性的时候,需要指定两个值,一个是type
,代表的是这个属性的类型,一个是value
,代表的是这个属性的默认值。示例代码如下:
Component({
properties: {
showInner: {
type: Boolean,
value: false
}
}
})
<view class="outter">
<view wx:if="{{showInner}}" class="inner">自定义组件view>
view>
<mybox showInner="true">mybox>
data
的形式,data
中的数据可以渲染到组件的代码中,但是使用data
不能作为属性来使用。在使用小程序内置的组件的时候,比如view
,我们还可以在view
中添加其他的组件。这个功能可以通过slot节点来实现。示例代码如下:
<view class="outter">
<view wx:if="" class="inner">知了课堂view>
<slot>slot>
view>
以后在使用这个组件的时候,还可以添加自己的节点。示例代码如下:
<mybox showInner="true">
<view>这是index中添加的view>
mybox>
以上是添加一个slot
,如果想要添加多个slot
,那么需要在js
文件中添加一个multipleSlots
属性,示例代码如下:
Component({
options: {
multipleSlots: true
},
...
然后在wxml
文件中,需要给每一个slot
都指定name
。示例代码如下:
<view class="outter">
<slot name="before">slot>
<view wx:if="" class="inner">自定义组件添加slotview>
<slot name="after">slot>
view>
最后在使用这个组件的时候,也需要指定放在那个slot
中。示例代码如下:
<mybox showInner="true">
<view slot="before">这是before的组件view>
<view slot="after">这是after的组件view>
mybox>
组件对应 wxss
文件的样式,只对组件wxml
内的节点生效。编写组件样式时,需要注意以下几点:
id
选择器(#a
)、属性选择器([a])和标签名选择器,请改用class选择器。app.wxss
中的样式、组件所在页面的的样式对自定义组件无效。#a { } /* 在组件中不能使用 */
[a] { } /* 在组件中不能使用 */
button { } /* 在组件中不能使用 */
.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */
// my-component组件代码
<view>
组件内的代码
view>
然后在使用的时候,直接在上面绑定就可以了。
<my-component bindtap="tapEvent">my-component>
triggerEvent
方法来触发自定义的事件。示例代码如下:// my-component组件代码
<view class="outter">
<view class="inner" bindtap="onInnerTapEvent">view>
view>
然后在组件的js
文件中,使用以下代码进行捕获和传递给父组件。示例代码如下:
Component({
method: {
onInnerTapEvent: function(event){
console.log("组件内监听到了innerTapEvent事件");
var detail = {} // detail对象,提供给事件监听函数
var option = {} // 触发事件的选项
this.triggerEvent("customevent",detail,option);
}
}
})
然后在使用组件的地方,需要给自定义事件绑定监听方法。示例代码如下:
<my-component bindcustomevent="onCustomEventEvent">
my-component>
其中的detail
是在触发innerEvent
事件的时候,传递给外面接收这个事件的对象一些额外的信息,option
是一些选项。这些选项可以如下:
选项名 | 类型 | 是否必填 | 默认值 | 描述 |
---|---|---|---|---|
bubbles | Boolean | 否 | false | 事件是否冒泡 |
composed | Boolean | 否 | false | 事件是否可以穿越组件边界,为false时,事件将只能在引用组件的节点树上触发,不进入其他任何组件内部 |
capturePhase | Boolean | 否 | false | 事件是否拥有捕获阶段 |
组件的生命周期,指的是组件自身的一些函数,这些函数在特殊的时间点或遇到一些特殊的框架事件时被自动触发。
其中,最重要的生命周期是created/attached/detached
,包含一个组件实例生命流程的最主要时间点。(注意:在2.2.3
基础库之前,生命周期函数写在Component
中就可以,在2.2.3
后应该写在lifetimes
中。)
created
生命周期被触发。此时,组件数据this.data
就是在Component
构造器中定义的数据data
。此时还不能调用setData
。通常情况下,这个生命周期只应该用于给组件this
添加一些自定义属性字段。attached
生命周期被触发。此时,this.data
已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。detached
生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则 detached
会被触发。还有一些特殊的生命周期,它们并非与组件有很强的关联,但有时组件需要获知,以便组件内部处理。这样的生命周期称为“组件所在页面的生命周期”,在 pageLifetimes 定义段中定义。其中可用的生命周期包括:
生命周期 | 参数 | 描述 | 最低版本 |
---|---|---|---|
show | 无 | 组件所在的页面被展示时执行 | 2.2.3 |
hide | 无 | 组件所在的页面被隐藏时执行 | 2.2.3 |
resize | Object | Size | 组件所在的页面尺寸变化时执行 |
小程序中的数据,并不是写死的。而是从服务器动态加载的。这时候就需要借助网络请求的api
来实现。api
全称application programming interface
,是小程序提供给开发者的接口。api的调用都是在js文件中完成的。网络请求的api相关的介绍请参考
聚合数据网址
要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。
因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 Jscript 和 ActionScript)。日常场合,这两个词是可以互换的。
ECMAScript 2015(简称 ES2015)这个词,也是经常可以看到的。它与 ES6 是什么关系呢?
2011 年,ECMAScript 5.1 版发布后,就开始制定 6.0 版了。因此,ES6 这个词的原意,就是指 JavaScript 语言的下一个版本。
但是,因为这个版本引入的语法功能太多,而且制定过程当中,还有很多组织和个人不断提交新功能。事情很快就变得清楚了,不可能在一个版本里面包括所有将要引入的功能。常规的做法是先发布 6.0 版,过一段时间再发 6.1 版,然后是 6.2 版、6.3 版等等。
但是,标准的制定者不想这样做。他们想让标准的升级成为常规流程:任何人在任何时候,都可以向标准委员会提交新语法的提案,然后标准委员会每个月开一次会,评估这些提案是否可以接受,需要哪些改进。如果经过多次会议以后,一个提案足够成熟了,就可以正式进入标准了。这就是说,标准的版本升级成为了一个不断滚动的流程,每个月都会有变动。
标准委员会最终决定,标准在每年的 6 月份正式发布一次,作为当年的正式版本。接下来的时间,就在这个版本的基础上做改动,直到下一年的 6 月份,草案就自然变成了新一年的版本。这样一来,就不需要以前的版本号了,只要用年份标记就可以了。
ES6 的第一个版本,就这样在 2015 年 6 月发布了,正式名称就是《ECMAScript 2015 标准》(简称 ES2015)。2016 年 6 月,小幅修订的《ECMAScript 2016 标准》(简称 ES2016)如期发布,这个版本可以看作是 ES6.1 版,因为两者的差异非常小(只新增了数组实例的includes方法和指数运算符),基本上是同一个标准。根据计划,2017 年 6 月发布 ES2017 标准。
因此,ES6 既是一个历史名词,也是一个泛指,含义是 5.1 版以后的 JavaScript 的下一代标准,涵盖了 ES2015、ES2016、ES2017 等等,而 ES2015 则是正式名称,特指该年发布的正式版本的语言标准。本书中提到 ES6 的地方,一般是指 ES2015 标准,但有时也是泛指“下一代 JavaScript 语言”。
以上内容引用自《ECMAScript 6 入门》
ES6
是ECMAScript 6
的简写,ECMAScript
是JavaScript
的标准,ECMAScript 6
泛指“下一代JavaScript语言”,具体每年发布的都有自己的专属名称,比如2015年发布的则为ESMAScript 2015
.目前说的ES6
语法,一般值得都是ESMAScript 2015
版。
在微信开发者工具中->详情->ES6转ES5,勾选上就可以了!
因为本课程不是专业的前端课程,只是为了微信小程序的学习做一些铺垫,所以只介绍到一些常用的语法。
let
跟var
一样,也是用来定义变量,但是他比var更加的安全,体现在以下两点:
console.log(a);
var a = 10;
按道理变量a是在打印的后面定义的,但是以上的代码并不会出错,而是会打印undefined,因为var会把变量a提升到代码最开始的地方进行定义。但是如果使用let就不会出现这种问题了。
console.log(b);
let b = 10;
此时再去打印的时候,就直接会抛出一个错误ReferenceError: b is not defined.
,这才是正确的方式。
注意:小程序中不能真正解析ES6语法,他只是借助了第三方工具将ES6语法转成ES5语法运行的,在底层也是用var来代替let的,所以依然会发生变量提升。
for(var i=0;i<=3;i++){
console.log(i);
}
console.log(i);
按道理来说i
在for
循环结束后,应该就不能够再使用了,但是我们下面再打印i的时候,会发现打印的结果是3而不是抛出异常,这在一些情况下也会产生莫名其妙的错误。但是let
就不会出现这种情况了。示例代码如下:
for(let i=0; i<=3; i++){
console.log(i);
}
console.log(i);
此时再打印i的时候,就会抛出错误ReferenceError: b is not defined.
。
const
是用来定义常量的,常量是一旦定义好了以后,就不能够再修改了。比如以下代码:
const PI = 3.14;
PI = 3 // 会抛出异常
const
只是用来限制指向的内存地址不能改变,但是如果这个内存地址上的数据改变了,是可以的。或者说得直白一点,就是指向的如果是一个可变的容器,容器中的数据改变了,那么是允许的。比如以下代码:
const mylist = [1,2,3];
mylist.push(4);
console.log(mylist);
以上代码后面会打印一个[1,2,3,4]
的数组。
在ES 2015之前,函数是不能指定默认参数的,在ES 2015后,就可以使用默认参数了。比如以下代码:
function fetch(url,method="get"){
// 发送网络请求案例
console.log(url);
console.log(method);
}
那么以后我们在使用fetch函数的时候,可以不传method参数,他会默认使用get:
fetch("http://www.baidu.com/"); //method == get
fetch("http://www.baidu.com/","post") // method == post
定义默认参数的时候,默认参数必须要在非默认参数的后面。
如果有多个默认值,我们只想提供其中的一个默认值,那么这时候需要与解构赋值默认值结合使用,示例代码如下:
function person(name,age=18,gender='男'){
console.log(name,age,gender);
}
比如我只想提供gender这个默认参数,age这个参数不提供,那么必须与解构赋值默认值结合使用实现:
function person(name,{age=18,gender='女'}={}){
console.log(name,age,gender);
}
person("知了",{gender:"男"});
有时候,函数作为一个参数变量传进去的时候,为了简化他的写法,我们可以使用箭头函数来实现。比如以下:
wx.request({
url: "http://www.baidu.com/",
success: function(res){
// 做一些事情
}
});
以上代码可以使用箭头函数进行简化:
wx.request({
url: 'http://www.baidu.com/',
success: res => {
//做一些事情
}
});
可以看到使用箭头函数更加的简洁。
箭头函数的语法是:
(参数1,参数2) => {代码块}
如果只有一行代码,那么可以不用花括号:
(a,b) => a+b // 返回a+b的结果
// 如果只有一个参数,可以不使用圆括号
a => a+1
在云开发中,提供的API有大量的Promise方式调用。在这里就简单的讲解一下Promise的实现原理。看以下代码:
const p = new Promise(function(resolve,reject){
// 如果执行以下代码,那么会执行下面的then函数
setTimeout(() => {
resolve("success");
},1000);
// 如果执行以下代码,那么会执行下面的catch函数
setTimeout(() => {
reject("fail")
},1000);
// 如果以上两个代码都执行,那么只会调用下面的then方法,因为resolve的调用在前面。
});
p.then((res) => {
console.log(res);
}).catch((error) => {
console.log(error);
});
以后在云开发中,如果出现then和catch,就知道分别对应的是成功的回调以及失败的回调。
在ES5
中,如果想要实现一个类,那么需要通过以下代码来实现:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
var p = new Point(1, 2);
这种写法虽然可以实现,但是不够清晰,让有其他编程语言的小伙伴看了后会有一脸问号。那么ES6
就提供了一个新的语法class来实现。示例代码如下:
class Person{
// 构造函数
constructor(name,age){
this.name = name;
this.age = age;
}
sayHello(){
console.log("hello world");
}
}
let p = new Person("zhiliao",18);
p.sayHello();
以上便是定义一个Person
类的非常简单的方式。
在定义方法的时候,可以使用static
关键字定义静态方法,静态方法是只属于类的,不属于对象的。示例代码如下:
class Utils{
constructor(){}
static timeFormat(time){
// 时间格式化的代码
}
}
// 直接调用
Utils.timeFormat("2019/1/1")
以上定义了一个timeFormat
的方法,并且在这个方法前面加了一个static
关键字,所以他就是一个静态方法了,那么以后在使用这个方法的时候直接通过类名就可以调用了。
在传统的JS中,是没有办法在多个js文件中互相导入的,这对于大型项目来说很不方便。因此ES6提供了一个模块的功能。要学会使用模块,只要掌握两个关键字export
以及import
即可。
默认在一个js文件中写好的代码或者变量,是不能够给其他的文件使用的,如果想要被外部使用,那么需要使用export
关键字。示例代码如下:
// utils.js
var name = "zhiliao";
function dateFormat(date){
// 格式化代码
}
class Person{
constructor(name,age){
this.name = name;
this.age = age;
}
}
export {name,dateFormat,Person}
以上文件进行export
了,那么以后其他文件想要使用的,则需要从这个文件中把需要的进行import
。示例代码如下:
// from 后面是一个相对路径
import {name,dateFormat,Person} from "utils.js";
按照正常的流程来说,如果我们想要是想一个能进行网络数据通信的微信小程序。比如:
那么我们不仅需要开发微信小程序,还需要开发一个服务器端程序来保存数据、保存文件、逻辑处理等。并且服务器端还需要专业的运维人员来运维,以防遭受攻击,因此需要的人力和资金成本非常大。而云开发技术就是专门为我们解决服务器端需求的,使用云开发,我们不用关心服务器端运维,数据库的管理,文件的管理等。只需要调用云开发给我们提供的API即可进行服务器端的操作,因此大大的提高了小程序开发的效率。
目前提供三大基础能力支持:
注册微信小程序
在微信官网,点击立即注册。示例图如下:
然后选择小程序:
接下来就是填写相关的信息,比如邮箱,密码等:
然后再根据自己的情况,选择到底是申请个人的还是企业的:
想使用云开发服务,在创建项目的时候就需要选择云开发的模板,并且因为云开发不能使用测试ID,因此在创建项目之前,要先在微信的后台创建好小程序,然后在创建项目的时候填入APPID。
创建完项目后,还需要在项目中开通云开发服务。跟着以下步骤就可以开通云开发服务了。
云开发能力从基础库2.2.3
开始支持,现在2.2.3
或以上的基础库没有完全覆盖所有用户(目前约90%),如需使上传的代码能够覆盖全量用户,请做以下特殊处理:
在app.json / game.json
中增加字段"cloud": true
指定后云能力可以在所有基础库中使用,并且如果云能力有更新,并不会随着基础库升级而自动升级,需在后续版本发布后重新上传。如2.2.4
发布后,需重新上传才能将云能力更新至2.2.4
版本的云能力。
在创建完云开发的项目后,默认会生成一个miniprogram
项目,这个项目是一些演示性的代码,不符合我们的需求。因此我们重新创建一个新的项目,项目名字可以随便取,比如叫做databasedemo
,然后在项目中需要创建好项目所需要的文件,比如app.json
、app.wxss
以及pages
文件夹等。创建完项目后,还需要在project.config.json
文件中配置"miniprogramRoot":"databasedemo/"
。
如果我们想要使用小程序的云开发功能,必须在小程序初始化的时候就调用wx.cloud.init
方法来进行初始化,这个方法可以接受两个参数,两个参数的作用如下:
字段 | 数据类型 | 必填 | 默认值 | 说明 |
---|---|---|---|---|
env | string \ object | 否 | default | 默认环境配置,传入字符串形式的环境 ID 可以指定所有服务的默认环境,传入对象可以分别指定各个服务的默认环境 |
traceUser | boolean | 否 | false | 是否在将用户访问记录到用户管理中,在控制台中可见 |
在app.js
文件中的App.onLaunch
方法中调用。示例代码如下:
App({
onLaunch: function () {
wx.cloud.init({
env: "环境ID",
traceUser: true,
})
}
})
在云开发的数据库中,使用的是NoSQL类型的数据库。关系型数据库中的表,对应的是NoSQL中的一个集合。所以在所数据操作之前,应该先创建一个集合。创建完集合后,也不需要跟关系型数据库一样,先定义好这个集合中的字段,而是直接插入数据,并且插入数据的时候,每条数据的字段无需保持一致!
在做数据的操作(增删改查)之前,先要获取数据库的对象,可以通过wx.cloud.database函数来获取到,这个函数默认会使用wx.cloud.init方法中提供的环境下的数据库,如果你想要使用其他环境的数据库,可以给wx.cloud.database方法传递一个env参数,比如:wx.cloud.database({“env”:“数据库环境ID”})。
在获取到了数据库的对象之后,比如db = wx.cloud.database(),然后就可以进行一些操作了。
操作数据的时候首先要通过db.collection来获取集合对象,比如const article = db.collection(“article”),然后就可以通过article来进行操作了。
在获取到集合的对象后,我们可以调用他的add方法来插入数据。示例代码如下:
const db = wx.cloud.database();
db.collection("article").add({
data: {
title: "learn cloud database",
pub_date: new Date("2019-1-1"),
author: "知了",
content: "very good!"
}
}).then(res => {
console.log(res);
});
获取所有数据(考虑到性能,小程序一次性最多只能获取20条数据):
db.collection("article").get().then(res => {
console.log(res);
});
如果你知道某条数据的id,可以根据id获取某条数据:通过id获取数据需要通过doc函数来实现:
db.collection("article").doc("article id").get().then(res => {
console.log(res)
});
根据条件获取数据:根据条件获取数据,可以通过where函数来实现。示例代码如下:
db.collection("article").where({
title: "知了"
}).get().then(res => {
console.log(res);
});
删除一条数据:输出一条数据,需要知道这条数据的id。示例代码如下:
db.collection("article").doc("article id").remove().then(res => {
console.log(res);
});
删除多条数据(只能在服务端实现,需要用到云函数):
db.collection("article").where({
title: "知了"
}).remove().then(res => {
console.log(res);
});
局部数据:局部更新是一次性只更新一条数据中的某几个字段的值,用的是update方法。示例代码如下:
db.collection("article").doc("article id").update({
data: {
title: "知了课堂"
}
}).then(res => {
console.log(res);
});
整体更新:整体更新是一次性把所有数据都更新,用的是set方法。示例代码如下:
db.collection("article").doc("article id").set({
data: {
title: "知了课堂",
author: "abc",
content: "1111",
author: "ddd"
}
});
一次更新多个数据:需要在服务器端,使用云函数来实现。
更新的方式有很多种,比如给数组添加元素,删除元素等。这些方法可以通过db.command来获取到,以下进行讲解:
在做查询的时候,大部分情况都需要做一些条件查询。在云开发提供的API中,我们可以通过db.command
来实现。以下将讲解这些指令的操作:
查询筛选条件,表示字段等于某个值。eq
指令接受一个字面量 (literal
),可以是number,boolean,string, object,array, Date
。
比如想要获取作者是北京日报的所有文章:
// 第一种方式,直接通过指定的数据查询:
db.collection("article").where({
author: "北京日报"
});
// 第二种方式,通过eq指令查询:
const _ = db.command;
db.collection("article").where({
author: _.eq("北京日报")
});
command.eq
与指定数据还是有区别的,比如以下两个作用是不一样的:
// 这种写法表示匹配 stat.publishYear == 2018 且 stat.language == 'zh-CN'
db.collection('articles').where({
author: {
name: "知了课堂",
age: 18
}
})
// 这种写法表示 stat 对象等于 { publishYear: 2018, language: 'zh-CN' }
const _ = db.command
db.collection('articles').where({
stat: _.eq({
name: "知了课堂",
age: 18
})
})
表示字段不等于某个值,和db.command.eq
相反。
表示小于某个值。比如查找小于2019/1/1 00:00:00
发布的文章:
const _ = db.command;
db.collection("article").where({
pub_date: _.lt(new Date("2019/1/1 00:00:00"))
});
表示小于或者等于某个值。与command.lt
类似。
表示大于某个值。与command.lt
类似。
表示大于或者等于某个值。与command.lte
类似。
查询筛选条件,表示字段的值需在给定的数组内。比如查找北京日报和今日头条两个作者发表的文章:
const _ = db.command;
db.collection("article").where({
author: _.in(['北京日报','今日头条'])
});
查询筛选条件,表示字段的值需不在给定的数组内。与command.int
类似。
查询指令,用于表示逻辑 “与” 的关系,表示需同时满足多个查询筛选条件。比如获取发表在2019/1/1 00:00:00
和2019/1/1 23:59:59
之间的所有文章:
const _ = db.command;
// 普通调用:
db.collection("article").where({
pub_date: _.and(_.gte(new Date("2019/1/1 00:00:00")),_.lte(new Date("2019/1/1 23:59:59")))
});
// 链式调用:
db.collection("article").where({
pub_date: _.gte(new Date("2019/1/1 00:00:00")).and(_.lte(new Date("2019/1/1 23:59:59")))
});
查询指令,用于表示逻辑 “或” 的关系,表示需同时满足多个查询筛选条件。或指令有两种用法,一是可以进行字段值的 “或” 操作,二是也可以进行跨字段的 “或” 操作。
一个字段的或操作(比如获取时间在2019/1/1 00:00:00
前或者2019/1/1 23:59:59
后发表的文章):
const _ = db.command;
db.collection("article").where({
pub_date: _.or(_.lt("2019/1/1 00:00:00"),_gt(new Date("2019/1/1 23:59:59")))
});
跨字段或操作(比如想要获取时间在2019/1/1
前,或者是标题中包含知了课堂的文章):
const _ = db.command;
db.collection("article").where(
_.or([{
pub_date: _.lt(new Date("2019/1/1 00:00:00"))
},{
title: /知了课堂/
}])
);
更新指令。用于设定字段等于指定值。比如以下是一个文章的数据:
{
"title": "abc",
"content": "111",
"author": {
"name": "知了课堂",
"age": 18
}
}
如果我想将这个文章中的author
变成{"name":"知了课堂"}
,那么通过传统的方式是无法实现的:
db.collection("article").doc("id").update({
data: {
author: {
"name": "知了课堂"
}
}
})
上面这种方式,只是将author.name
设置为知了课堂,并没有将author
的值设置为{name:"知了课堂"}
,也就是说,age:18
这个值依然在author
中存在。这时候就需要通过set
来实现了。示例代码如下:
const _ = db.command;
db.collection("article").doc("id").update({
data: {
author: _.set({
"name": "知了课堂"
})
}
})
更新指令。用于表示删除某个字段。比如我们想将author
这个字段删除,那么就可以调用这个方法。示例代码如下:
const _ = db.command;
db.collection("article").doc("id").update({
data: {
author: _.remove()
}
})
更新指令。用于指示字段自增某个值,这是个原子操作,使用这个操作指令而不是先读数据、再加、再写回的好处是:
原子性:多个用户同时写,对数据库来说都是将字段加一,不会有后来者覆写前者的情况。
减少一次网络请求:不需先读再写。
比如article
这个集合中的一条数据有一个read_count
字段,想给这个字段加1,那么可以使用以下方式实现:
const _ = db.command
db.collection('article').doc('id').update({
data: {
read_count: _.inc(1)
}
})
自乘指令,跟command.inc
一样。
更新指令,对一个值为数组的字段,往数组尾部添加一个或多个值。或字段原为空,则创建该字段并设数组为传入值。
比如要给article集合中的某条数据添加标签,那么可以使用以下方式来实现:
const _ = db.command
db.collection('article').doc('id').update({
data: {
tags: _.push(['新闻'])
}
})
更新指令,对一个值为数组的字段,将数组尾部元素删除。
更新指令,对一个值为数组的字段,将数组头部元素删除。
更新指令,对一个值为数组的字段,往数组头部添加一个或多个值。或字段原为空,则创建该字段并设数组为传入值。
统计集合记录数或统计查询语句对应的结果记录数,注意这与集合权限设置有关,一个用户仅能统计其有读权限的记录数。比如查找所有由“北京日报”发布的文章的个数:
db.collectioin("article").where({
author: "北京日报"
}).count().then(res => {
console.log(res);
});
方法接受一个必填字符串参数fieldName
用于定义需要排序的字段,一个字符串参数order
定义排序顺序。order
只能取 asc
或desc
。
如果需要对嵌套字段排序,需要用 “点表示法” 连接嵌套字段,比如author.age
表示字段author
里的嵌套字段age
。
同时也支持按多个字段排序,多次调用orderBy即可,多字段排序时的顺序会按照orderBy调用顺序先后对多个字段排序。
比如我想通过阅读量从大到小以及作者的年龄从小到大进行排序。那么可以使用以下代码来实现:
db.collection("article")
.orderBy("read_count","desc")
.orderBy("author.age","asc")
.get()
.then(res => {
console.log(res);
})
指定查询结果集数量上限。比如一次性获取10篇文章,那么可以通过以下代码来实现:
const db = wx.cloud.database()
db.collection(‘article’).limit(10)
.get()
.then(res => {
console.log(res);
})
指定查询返回结果时从指定序列后的结果开始返回,常用于分页。比如跳过前面10篇文章,从第11篇开始获取。代码如下:
const db = wx.cloud.database()
db.collection('todos').skip(10)
.get()
.then(console.log)
.catch(console.error)
指定返回结果中记录需返回的字段。比如只想获取article
文章中的title
字段。那么可以使用以下代码来实现:
const db = wx.cloud.database()
db.collection('todos').field({
title: true
})
.get()
.then(console.log)
.catch(console.error)
从基础库2.3.2
开始(wx-server-sdk
从0.0.23
开始),数据库支持正则表达式查询,开发者可以在查询语句中使用 JavaScript
原生正则对象或使用db.RegExp方法来构造正则对象然后进行字符串匹配。在查询条件中对一个字段进行正则匹配即要求该字段的值可以被给定的正则表达式匹配,注意正则表达式不可用于db.command内(如db.command.in)。
请参考
因为云函数在服务器上实际上是运行在node.js
环境中的,并且云函数是专门用来处理一些逻辑的,所以难免要用到一些第三方库,而云函数在编写的过程中,是需要先在本地写好,然后再进行提交到云服务器上。因此我们本地也需要安装好一套node.js
环境。
nvm(Node Version Manager)
是一个用来管理node
版本的工具。我们之所以需要使用node
,是因为我们需要使用node
中的npm(Node Package Manager)
,使用npm
的目的是为了能够方便的管理一些前端开发的包!nvm
的安装非常简单,步骤如下:
我的电脑->属性->高级系统设置->环境变量->系统环境变量->Path
下新建一个,把nvm所处的路径填入进去即可!cmd
,然后输入nvm
,如果没有提示没有找不到这个命令。说明已经安装成功!Mac
或者Linux
安装nvm
请看这里。也要记得配置环境变量。nvm常用命令:
安装完nvm
后,我们就可以通过nvm
来安装node
了。根据小程序中,云函数使用的node.js
版本,来选择你要安装的版本。因为目前我的云函数用到的node.js
版本是8.9.0
,因此这里我们安装8.9.0
版本的的node.js
就可以。安装命令如下:
nvm install 6.4.0
如果你的网络够快,那以上命令在稍等片刻之后会安装成功。如果你的网速很慢,那以上命令可能会发生超时。因为node
的服务器地址是https://nodejs.org/dist/
,这个域名的服务器是在国外。因此会比较慢。因此我们可以设置一下nvm的源。
nvm node_mirror https://npm.taobao.org/mirrors/node/
nvm npm_mirror https://npm.taobao.org/mirrors/npm/
npm(Node Package Manager)
在安装node
的时候就会自动的安装了。当时前提条件是你需要设置当前的node的版本:nvm use 8.9.0
。然后就可以使用npm了.
关于npm常用命令以及用法,请看下文。
安装包分为全局安装和本地安装。全局安装是安装在当前node
环境中,在可以在cmd
中当作命令使用。而本地安装是安装在当前项目中,只有当前这个项目能使用,并且可以通过require
引用。安装的方式只有-g
参数的区别:
npm install express # 本地安装
npm install express -g # 全局安装
将安装包放在./node_modules
下(运行 npm 命令时所在的目录),如果没有node_modules
目录,会在当前执行npm
命令的目录下生成node_modules
目录。
可以通过require()
来引入本地安装的包。
将安装包放在/usr/local
下或者你node
的安装目录。
可以直接在命令行里使用。
npm uninstall [package]
npm update [package]
npm search [package]
npm install -g cnpm --registry=https://registry.npm.taobao.org
那么以后就可以使用cnpm来安装包了!
在cloudfunctions
文件夹上,右键->创建云函数,填入云函数的名称,然后点击确定即可创建好。然后就可以在相应的index.js
文件中写代码了。示例代码如下:
// 云函数入口文件
const cloud = require('wx-server-sdk')
cloud.init()
// 云函数入口函数
exports.main = async (event, context) => {
const x = event.x;
const y = event.y;
return {
result: x + y
}
}
在本地创建完云函数后,还只是在本地,所以还需要上传到服务器和部署。上传和部署非常简单,我们只需要在相应的函数的文件夹上,右键->上传并部署:云端安装依赖即可。
在云函数中,操作数据库,包括文件的操作,可以通过wx-server-sdk
来实现。但是这个sdk与小程序端的API有以下两点不同:
Promise
风格调用。update
和remove
操作。在我们的小程序,有时候需要让用户提交一些文字信息。但是对于用户提交的文字信息是否安全(反国家言论等),我们不得而知,因此需要对用户上传的数据进行一些检测。小程序中已经提供了这样的API,我们直接调用就可以了。
在检测过程中,我们需要先获取AppID
和AppSecret
,这两个参数可以在微信公众平台(小程序后台)->开发->开发设置中获取到。示例图如下:
向这个https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
发送GET
请求,把AppID
和AppSecret
都替换为之前获取的。返回回来的结果如下:
属性 | 类型 | 说明 |
---|---|---|
access_token | string | 获取到的凭证 |
expires_in | number | 凭证有效时间,单位:秒。目前是7200秒之内的值。 |
errcode | number | 错误码 |
errmsg | string | 错误信息 |
在以上拿到access_token
后,再向https://api.weixin.qq.com/wxa/msg_sec_check?access_token=ACCESS_TOKEN
发送POST
请求,并且把ACCESS_TOKEN
替换为之前获取到的,在发送这个请求的时候还需要携带content
参数,这个参数就是需要检验的文本内容。
对于用户上传上来的图片,我们需要验证是否涉黄,如果有涉黄,就需要隐藏掉或者删掉,以免小程序或者公司被有关部门谈话。
流程:
进入快速开始文档https://cloud.tencent.com/product/pornidentification/getting-started
。
进入到智能鉴黄SDK文档中https://cloud.tencent.com/document/product/864/18708。代码如下:
// 云函数入口文件
const { ImageClient } = require("image-node-sdk");
let AppId = '1253230774'; // 腾讯云 AppId
let SecretId = 'AKID5D7sUBg47wlaGoIIL0YoqGWaULsJP8Tf'; // 腾讯云 SecretId
let SecretKey = 'ALOLD7Ufjnhc5Pbxe6zg8MLPE2bk4A2f'; // 腾讯云 SecretKey
// 云函数入口函数
exports.main = async (event, context) => {
const imageUrl = event.imageUrl;
let imgClient = new ImageClient({ AppId, SecretId, SecretKey });
try{
return await imgClient.imgPornDetect({
data: {
url_list: [imageUrl]
}
})
}catch(e){
console.log(e)
}
}
https://cloud.tencent.com/document/product/864/17609
。在云开发中,登录的概念被弱化了,取而代之的是授权功能,比如你想在小程序中发布一条微博,那么小程序必须要获取到你的相关的信息,比如微信昵称,头像,城市等。这时候就需要你授权给这个小程序。
在之前的小程序版本中,只要调用wx.getUserInfo,微信就会自动弹出一个授权的界面,然后就可以获取用户的信息了。但是随着改版,这个接口只能是在已经被授权的情况下才能调用成功。官方文档如下:https://developers.weixin.qq.com/community/develop/doc/0000a26e1aca6012e896a517556c01。
新的授权方式要分两步走,第一步是进行用户的授权,第二步才是去调用wx.getUserInfo获取用户的信息。这两步的走法分别是:
<button type="primary" open-type="getUserInfo" bindgetuserinfo="onGetUserInfoTap">
授权button>
当用户点击这个按钮的时候,就会弹出一个授权对话框,当用户点击了授权后,就会执行onGetUserInfoTap的方法,然后把用户相关的信息返回回来。
loadUserInfo: function(){
const that = this;
wx.getSetting({
success: res => {
const isUserInfo = res.authSetting['scope.userInfo'];
if(isUserInfo){
wx.getUserInfo({
success: res => {
const userInfo = res.userInfo;
that.globalData.userInfo = userInfo;
}
})
}
}
})
}
获取用户信息的授权比较独特,有自己的一套授权方式。但是其他的数据授权,则又是另外一种方式。以下讲下基本的授权流程。
如果需要授权某方面的数据,比如获取用户的地理位置,那么需要先在app.json中配置好permission字段。拿地理位置为例,示例代码如下:
{
...
"permission": {
"scope.userLocation": {
"desc": "你的位置将用于微博的展示"
}
}
...
}
其中的desc将用于在获取用户授权的时候的提示性话语。
如果用户没有授权过某方面的数据,那么需要先调用wx.authorize来进行授权。示例代码如下:
wx.authorize({
scope: "scope.userLocation",
success: res => {
// 做些授权过后的事情
}
})
在授权完成后,就可以获取相关数据了。比如选择位置。示例代码如下:
wx.chooseLocation({
success: res => {
delete res.errMsg;
that.setData({
location: res
})
}
})