小程序的双线程模型
小程序的宿主环境:微信客户端
- 宿主环境为了执行小程序的各种文件:wxml文件、wxss文件、js文件
- 提供了小程序的双线程模型
双线程模型
- WXML模板和WXSS样式运行与渲染层,渲染层使用WebView线程渲染(一个程序有多个页面,会使用多个WebView的线程 )。
- JS脚本(app.js/home.js等)运行于逻辑层,逻辑层使用JsCore运行JS脚本。
- 这两个线程都会经由微信客户端(Native)进行中转交互。
界面渲染整体流程
- 在渲染层,宿主环境会把WXML转化成对应的JS对象;
- 将JS对象再次转成真实的DOM树,交由渲染层线程渲染;
- 数据变化时,逻辑层提供最新的变化数据,JS对象发生变化比较进行diff算法对比;
- 将最新变化的内容反映到真实的DOM树中,更新UI;
启动流程
注册App时,一般会做:
- 判断小程序的进入场景;
- 监听生命周期函数,在生命周期中执行对应的业务逻辑,比如在某个生命周期函数中获取微信用户的信息;
- 因为App()实例只有一个,并且是全局共享的(单例对象),所以可以将一些共享数据放在这里;
常用内置组件
- text
<!-- selectable属性:默认情况下text中的文本长按是不能选中的,要实现可以选中,需要设置成true -->
<text selectable="{{true}}">Hello World</text>
<!-- space属性:决定文本空格的大小 -->
<text space='nbsp'>Hello World</text> // 根据字体设置的空格大小
<text space='ensp'>Hello World</text> // 半个中文字符大小
<text space='emsp'>Hello World</text> // 一个中文字符大小
<!-- decode属性:是否解码文本 -->
<!-- decode可以解析的有: < > & '     -->
<text decode=''>5 > 3</text> // 5 > 3
- button
<!-- size属性:决定按钮的大小,行内块级元素,一行可以有多个;默认是块级元素,独占一行 -->
<button size="mini">按钮</button>
<!-- type属性:primary(主要绿色),default(默认白色),warn(警告红色) -->
<button size="mini" type="primary">按钮</button>
<button size="mini" type="default">按钮</button>
<button size="mini" type="warn">按钮</button>
<!-- disable属性:true为不可用,false为可用;默认值为true -->
<button size="mini" disable>按钮</button>
<!-- loading属性:带有加载的icon,true为显示加载,false为不显示加载,默认为true -->
<button size="mini" loading>按钮</button>
<!-- hover-class属性:添加点击与按住的类名,在wxss中设置点击与按住时按钮的样式 -->
<button size="mini" hover-class="press">按钮</button>
.press {
backgroud: red;
color: white;
}
- view
<!-- hover-class属性:当用户按下组件时,显示的样式 -->
<view class="box" hover-class="press">box</view>
.box {
background: red;
color: white;
font-size: 18px;
}
.press{
background: yellow;
color: black;
font-size: 30px;
}
<!-- hover-start-time属性:当用户按住后多久显示hover-class中设置的样式,单位毫秒,默认50,直接写number无效时,加上{{}},即{{number}} -->
<view class="box" hover-class="press" hover-start-time="{{1000}}">box</view>
<!-- hover-stay-time属性:当用户手指松开后hover-class中设置的样式保留时间,单位毫秒,默认400,直接写number无效时,加上{{}},即{{number}} -->
<view class="box" hover-class="press" hover-start-time="{{1000}}" hover-stay-time"{{1000}}">box</view>
<!-- hover-stop-propagation属性:当有多个view嵌套时,是否阻止当前view的祖先节点显示hover-class中设置的样式,前提是祖先节点也设置了hover-class属性,默认false-->
<view class="box" hover-class="press">box1
<view class="box" hover-class="press" hover-stop-propagation="{{true}}">box2</view>
</view>
- image
<!-- 可以写成单标签,也可以写成双标签 -->
<!-- 默认有自己的大小:320x240 -->
<!-- 行内块级元素 -->
<!-- src属性:本地路径(相对路径/绝对路径)/远程地址 -->
<img src="../../assets/img/home.jpg">
<img src="/assets/img/home.jpg">
<img src="https://res.wx.qq.com/wxdoc/dist/assets/img/0.4cb08bb4.jpg">
<!-- bindload属性:监听图片加载完成 -->
<img src="https://res.wx.qq.com/wxdoc/dist/assets/img/0.4cb08bb4.jpg" bindload="handleWatch">
handleWatch () {
console.log('图片加载完成...')
}
<!-- lazy-load属性:图片懒加载,进入一定范围(上下三屏)时才开始加载,默认为false-->
<img src="https://res.wx.qq.com/wxdoc/dist/assets/img/0.4cb08bb4.jpg" lazy-load>
<!-- show-menu-by-longpress属性:长按图片出现识别小程序码,默认为false -->
<img src="https://res.wx.qq.com/wxdoc/dist/assets/img/0.4cb08bb4.jpg" show-menu-by-longpress>
<!-- mode属性:缩放图片 -->
<!-- scaleToFill:缩放模式,不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素 -->
<!-- aspectFit:缩放模式,保持纵横比缩放图片,使图片的长边能完全显示出来 -->
<!-- aspectFill:缩放模式,保持纵横比缩放图片,只保证图片的短边能完全显示出来 -->
<!-- widthFix:缩放模式,宽度不变,高度自动变化,保持原图宽高比不变 -->
<!-- top/bottom/center/left/right...:裁剪模式,不缩放图片,只显示图片的上面/下面/中间/左边/右边区域 -->
- input
<!-- 可以写成单标签,也可以写成双标签 -->
<!-- 默认背景为透明色 -->
<!-- value属性:默认初始值 -->
<input value="Hello World" />
<!-- type属性:决定键盘类型(文本/数字/身份证/带小数点的数字) -->
<input type="text" /> // 文本键盘
<input type="number" /> // 数字键盘
<input type="idcard" /> // 身份证键盘
<input type="digit" /> // 带小数点的数字键盘
<!-- password属性:暗文 -->
<input password />
<!-- placeholder属性:未输入内容时的占位文字 -->
<input password placeholder="请输入密码"/>
<!-- confir-type属性:设置键盘右下角按钮的文字,仅在type='text'时生效 -->
<input type="text" confir-type="send" /> // 右下角按钮为“发送”
<input type="text" confir-type="search" /> // 右下角按钮为“搜索”
<input type="text" confir-type="next" /> // 右下角按钮为“下一个”
<input type="text" confir-type="go" /> // 右下角按钮为“前往”
<input type="text" confir-type="done" /> // 右下角按钮为“完成”
- scroll-view
HTML
<!-- 横向滚动 -->
<scroll-view class="container" scroll-x bindscroll="handleScroll">
<view wx:for="{{10}}" class="main">{{item}}</view>
</scorll-view>
CSS
.container {
background: red;
white-space: nowrap; // 不换行
}
.main {
width: 100px;
height: 100px;
background: white;
margin: 10px;
display: inline-block;
}
JS
handleScroll (event) {
console.log(event) // 横向滚动时,关注detail中的scrollLeft;纵向滚动时,关注detail中的scrollTop
}
共同属性
属性名 | 类型 | 描述 | 注释 |
id | String | 组件的唯一标识符 | 整个页面唯一 |
class | String | 组件的样式类 | 在对应的WXSS中定义的样式类 |
style | String | 组件的内联样式 | 可以动态设置的内联样式 |
hidden | Boolean | 组件是否显示 | 所有组件默认显示 |
data-* | Any | 自定义属性 | 组件上触发事件时,会发送给相应的事件处理函数 |
bind*/catch* | EventHandler | 组件的事件 |
尺寸单位
- rpx(responsive pixel):可以根据屏幕宽度进行自适应,规定屏幕宽为750rpx;如在iPhone6上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。
设备 | rpx换算px(屏幕宽度/750) | px换算rpx(750/屏幕宽度) |
iPhone5 | 1rpx = 0.42px | 1px = 2.34rpx |
iPhone6 | 1rpx = 0.5px | 1px = 2rpx |
iPhone6 Plus | 1rpx = 0.552px | 1px = 1.81rpx |
关于WXML导入的两种方式:
- import导入:
- 主要是导入template
- 不能进行递归导入(例如,B中导入C,A中导入B,A中使用C时会报错:not found C)
A文件
<import src="/first.wxml" /> // 导入模板
<template is="first"/> // 调用正常导入的
<template is="second"/> // 调用递归导入的
B文件
<import src="/second.wxml"/> // 递归导入模板
<template name="first">
<view>我是第一层</view>
</template>
C文件
<template name="second">
<view>我是第二层</view>
</template>
A中控制台结果
./pages/home/home.wxml
Template `second` not found.
24 |
25 | <template is="first"/>
> 26 | <template is="second"/>
| ^
27 |
28 |
- include导入
- 将公共的wxml中的组件抽取到一个文件中
- 不能导入template/wxs,可以进行递归导入
WXS
为什么要引入wxs?
- 在WXML中是不能直接调用Page/Component中定义的函数的
- 但是某些情况,我们希望可以使用函数来处理WXML中的数据(类似于Vue中的过滤器),这个时候就要使用WXS
WXS使用的限制和特点:
- WXS的运行环境和其他JavaScript代码是隔离的,WXS中不能调用其他JavaScript文件中定义的函数,也不能调用小程序提供的API
- WXS函数不能作为组件的事件回调
- 由于运行环境的差异,在IOS设备上小程序内的WXS会比JavaScript代码快2~20倍。在Android设备上两者运行效率无差异
点击事件对象中currentTarget与target的区别
正常情况下,两者中存放的值是一样的,但是由于事件的冒泡性,当一个组件中嵌套了一个内置组件时,点击内置组件会先后的触发内置组件的点击事件和外层组件的点击事件,这个时候,外层组件的currentTarget是其本身,而target则是内置组件。总结,currentTarget是触发事件的对象,target是产生事件的对象。
<view id="outer" bindtap="handleOutClick">
我是外层的view
<view id="inner" bindtap="handleInnerClick">
我是内层的view
</view>
</view>
handleOutClick (e) {
console.log(e)
},
handleInnerClick (e) {
console.log(e)
}
/*
changedTouches: [{…}]
currentTarget: {id: "inner", offsetLeft: 0, offsetTop: 162, dataset: {…}}
detail: {x: 106, y: 214}
mark: {}
mut: false
target: {id: "inner", offsetLeft: 0, offsetTop: 162, dataset: {…}}
timeStamp: 225847
touches: [{…}]
type: "tap"
*/
/*
changedTouches: [{…}]
currentTarget: {id: "outer", offsetLeft: 0, offsetTop: 141, dataset: {…}}
detail: {x: 106, y: 214}
mark: {}
mut: false
target: {id: "inner", offsetLeft: 0, offsetTop: 162, dataset: {…}}
timeStamp: 225847
touches: [{…}]
type: "tap"
*/
点击事件对象中changedTouches与touches的区别
两者都是一个存放手指触摸屏幕的数组,touches存放的是当前触摸屏幕所有手指的位置等信息,当手指离开屏幕时为空数组,但是changedTouches存放的是触摸前后发生变化的所有手指的位置等信息。例如第一次用食指触摸在屏幕上某一块,在食指未松开的状态下,再用中指触摸屏幕上食指触摸的同一块,此时touches中存放了食指和中指两根手指的位置等信息,而changedTouches只存放了中指的位置等信息。
事件的捕获与冒泡
当界面产生一个事件时,事件分为了捕获阶段和冒泡阶段。
<!-- bind:层次传递 -->
<view class="view1" capture-bind:tap="handleCaptureView1" bindtap="handleBindView1">
<view class="view2" capture-bind:tap="handleCaptureView2" bindtap="handleBindView2">
<view class="view3" capture-bind:tap="handleCaptureView3" bindtap="handleBindView3">
</view>
</view>
</view>
<!-- 点击view3 -->
handleCaptureView1
handleCaptureView2
handleCaptureView3
handleBindView3
handleBindView2
handleBindView1
<!-- 点击view2 -->
handleCaptureView1
handleCaptureView2
handleBindView2
handleBindView1
<!-- 点击view1 -->
handleCaptureView1
handleBindView1
<!-- catch:阻止事件的进一步传递 -->
<view class="view1" capture-bind:tap="handleCaptureView1" bindtap="handleBindView1">
<view class="view2" capture-catch:tap="handleCaptureView2" catchtap="handleBindView2">
<view class="view3" capture-bind:tap="handleCaptureView3" bindtap="handleBindView3">
</view>
</view>
</view>
<!-- 点击view3 -->
handleCaptureView1
handleCaptureView2
<!-- 点击view2 -->
handleCaptureView1
handleCaptureView2
<!-- 点击view1 -->
handleCaptureView1
handleBindView1
handleCaptureView1 () {
console.log('handleCaptureView1')
},
handleCaptureView2 () {
console.log('handleCaptureView2')
},
handleCaptureView3 () {
console.log('handleCaptureView3')
},
handleBindView1 () {
console.log('handleBindView1')
},
handleBindView2 () {
console.log('handleBindView2')
},
handleBindView3 () {
console.log('handleBindView3')
}
自定义组件
- 首先需要在json文件中进行自定义组件声明,(将component字段设为true可将这一组组件设为自定义组件)
- 在wxml中编写属于我们组件自己的模板
- 在wxss中编写属于我们组件自己的相关样式
- 在js文件中,可以定义数据或组件内部的相关逻辑
组件的样式细节
- 组件内的样式对外部样式的影响
- 组件内的class样式,只对组件wxml内的节点生效,对于引用组件的page页面不生效
- 组件内不能使用id选择器、属性选择器、标签选择器
- 外部样式对组件内样式的影响
- 外部使用class的样式,只对外部wxml的class生效,对组件内是不生效的
- 外部使用了id选择器、属性选择器不会对组件内产生影响
- 外部使用了标签选择器,会对组件内产生影响
- 结论
- 组件内的class样式和组件外的class样式,默认是有一个隔离效果的
- 为了防止样式的错乱,官方不推荐使用id、属性、标签选择器
- 如何让class可以相互影响
在自定义组件的js文件中的Component对象中,可以传入一个options属性,options属性中有一个styleIsolation(样式隔离)属性。styleIsolation共用三个取值:
Component({
options: {
styleIsolation: "isolated/apply.shared/shared"
}
})
Δ -isolated:默认取值,启用样式隔离,在自定义组件内外,使用class指定的样式将不会相互影响(互不影响)
Δ -apply-shared:使用自定义组件的页面wxss样式会影响自定义组件,自定义组件wxss样式不会影响页面(单向影响)
Δ -shared:使用自定义组件的页面wxss样式会影响自定义组件,自定义组件wxss样式同样会影响页面(相互影响)
Δ 其他一些相关样式细节,可参考官网
页面与组件之间通信
API接口常用配置
属性 | 类型 | 默认值 | 必填 | 说明 |
url | string | 是 | 请求服务器地址 | |
data | string/object/ArrayBuffer | 否 | 请求参数 | |
header | object | 否 | 设置请求头,header中不能设置Referer content-type默认为application/json | |
method | string | get | 否 | http请求方法 |
dataType | string | json | 否 | 返回的数据格式 |
responseType | string | text | 否 | 响应的数据类型 |
success | function | 否 | 接口调用成功的回调函数 | |
fail | function | 否 | 接口调用失败的回调函数 | |
complete | function | 否 | 接口调用结束的回调函数(无论成功、失败都会执行) |