小程序01
页面目录介绍
|--pages # 所有页面的存放位置
|--index
| |--{}index.js # 每个页面的JS文件
| |--index.json # 每个页面的配置
| |--index.wxml # 每个相当于html文件
| |--idnex.wxss # 每个相当于css文件
|--logs
|--utils
|--util.js # 公共方法存放位置
|--app.js # 全局的app对象文件,启动app的入口
|--app.json # 全局的app配置文件
|--app.wxss # 全局的类似样式文件
|--project.config.json # 多人开发统一的配置文件
|--sitemap.json # 关于本app小程序的对外描述信息
app.json全局配置
# 新增页面 在全局的app.json中的pages数组中注册,注意如果是首页的话,必须将注册的文件夹写在最前面的位置才起作用,其后的其他文件夹就会被覆盖掉,不能作为首页使用;
{
"pages": [
"pages/tests/test",
"pages/index/index",
"pages/logs/logs" # 末尾的文件夹注册不能加逗号,不然报错
],
# 小程序页面的头部配置
"window": {
"backgroundTextStyle": "light", # 修改小程序名称字体背景颜色 只能是dark和light
"navigationBarBackgroundColor": "#fff", # 修改头部的背景颜色
"navigationBarTitleText": "WeChat", # 修改小程序的名称
"navigationBarTextStyle": "black", # 修改头部出现的所有字体的颜色,只能是black和white
"enablePullDownRefresh": true # 首页是否具有下拉刷新的功能
},
# 底部导航栏 list元组中最多不超过5项
"tabBar": {
"color": "#660066", # 没被激活时的颜色
"selectedColor": "#6666FF", # 激活后的颜色
"backgroundColor": "#FFFF99", # 导航栏的背景颜色
"borderStyle": "white", # 导航栏上边框的颜色,只有black和white
"list": [{
"pagePath": "pages/tests/test", # 显示的页面,对应我们在pages中创建的文件夹
"text": "首页", # 创建的导航栏对应页面文字信息
"iconPath": "images/icon1.png", # 文字信息上面的小图标路径 我们需要自己创建一个与pages同级别的文件夹images,内部放我们需要展示的小图标
"selectedIconPath": "images/icon1-active.png" # 点击图标(或说文字),使图标高亮(激活状态),代表跳转到对应的页面
},{
"pagePath": "pages/tests1/test1",
"text": "商品分类",
"iconPath": "images/icon2.png",
"selectedIconPath": "images/icon2-active.png"
}
]
},
"style": "v2",
"sitemapLocation": "sitemap.json"
}
创建的文件夹中.json文件的配置
{
"usingComponents": {},
"navigationBarTitleText": "八折活动" # page中新建的文件夹中.json文件有配置头部标题,就优先使用,没配置就使用全局app.json文件中“window”对象配置的“微信小程序”
}
注意:新增一个页面,直接在app.json中的page数组中添加具体路径到文件名的绝对路径即可。
数据绑定
以我们创建的tests文件夹为例:
1.在 test.js中的“data对象”注册变量
2.在test.wxml中的view标签中渲染数据,将属性或变量写在{{}}中
<view>{{ name }}</view>
test.js
// pages/tests/test.js
Page({
/**
* 页面的初始数据
*/
data: {
name:'sb',
a:1,
b:2,
list:[{name:"egon",like:"sao"},
{name:"echo",like:"xuexi"}]
},
})
test.wxml
<text>pages/test/test.wxml</test>
<view>{{name}}</view>
<view>{{num}}</view>
<view>{{a+b}}</view>
<view>{{name}}</view>
<view>{{a}}+{{b}}</view>
<view>{{name+like}}</view>
<checkbox checked="true"</checkbox> # 页面会出现一个默认选中的单选框
<checkbox checked="true"</checkbox>
<checkbox checked="{{false}}"></checkbox> # 如果页面出现的选框默认没被选中,需要写成变量的形式{{ }}
<view class="name{{b}}">123</view>
<!--for循环 wx:key="index"不写能正常运行,写了可以加快遍历速度-->
<view wx:for="{{list}}" wx:for-index='key' wx:for-item='value'
wx:key="index">
{{key}}:{{value.name}}:{{value.like}}
</view>
<!--if判断-->
<view wx:if="{{b<5}}">
hello!
</view>
双线程模型
小程序的宿主环境
微信客户端微信客户端提供双线程去执行wxml,wxss,js文件。
双线程模型
1.上述的渲染层上面运行wxml文件和wxss文件,渲染层使用是的webview线程进行渲染(一个程序会有多个页面,也就会有多个view线程进行运作)
2.js文件是运行在逻辑层,逻辑层的js是通过jscore进行运行的。
通过双线程界面的渲染过程是怎样的?
wxml与DOM树
其实我们wxml文件与我们html中的DOM树是一样的,这样我们就可以有js来模拟一个虚拟的DOM树:
初始化渲染
如果我们的wxml文件中如果有变量:要与js逻辑层共同渲染页面成为一个真正的DOM树:
界面数据发生变化
1.如果通过setData把hello改成dsb,则js对象的的节点会发生改变.
- 这时会用diff算法对比两个对象的变化,
3 .然后将变化的部分应用到DOM树上
4.从而达到更新页面的目的,这也就是数据驱动的原理
总结
界面渲染的整体流程
1在渲染层将wxml文件与wxss文件转化成js对象也就是虚拟DOM
2 在逻辑成将虚拟的DOM对象配合生成,真实的DOM树,在交给渲染层渲染
3 当数据变化是,逻辑层提供更新数据,js对象发生改变,用diff算法进行比较
4 将更新的内容,反馈到真实的DOM树中,更新页面
小程序的启动流程
在app生命周期中执行了什么?
执行App()函数也就是注册一个App
1.在注册app的时候,可以判断小程序的进入场景,options就是捕获进入场景的参数
options对象:{scene: 1001, path: "pages/tests/test", query: {…}}
2.我们可以在执行通过生命周期函数,做一些数据请求
3.可以在app中设置一个全局对象,让所有页面都能使用
在页面的什么周期中执行了什么?
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
console.log("onload")
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
console.log("onshow")
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
console.log("onReady")
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
console.log("onHide")
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
console.log("onUnload")
},
//监听用户下拉动作,
onPullDownRefresh :function(){
//如果要用到这个,必须在全局的app.json中的window对象加上"enablePullDownRefresh": true
console.log("下拉刷下")
},
//页面上拉触底事件的处理函数
onReachBottom:function(){
console.log("上拉到底部")
},
//页面滚动触发事件的处理函数
onPageScroll: function(e){
console.log("滚轮在动",e)
}
})
1 在生命周期函数中发送网络请求,从服务端获取数据
2 初始化一些数据,在data里面,以方便wxml引用
3 监听wxml的事件,绑定对应的事件函数
4 还有页面滚动,上拉,下拉等
页面的生命周期
在虚拟DOM节点完成以后,wxml文件等待着.js文件传数据,此时触发onReady函数。
事件
常见的事件
类型 | 触发条件 |
---|---|
touchstart | 手指触摸动作开始 |
touchmove | 手指触摸后移动 |
touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 |
touchend | 手指触摸动作结束 |
tap | 手指触摸后马上离开 |
longpress | 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 |
longtap | 手指触摸后,超过350ms再离开(推荐使用longpress事件代替) |
transitionend | 会在 WXSS transition 或 wx.createAnimation 动画结束后触发 |
animationstart | 会在一个 WXSS animation 动画开始时触发 |
animationiteration | 会在一个 WXSS animation 一次迭代结束时触发 |
animationend | 会在一个 WXSS animation 动画完成时触发 |
touchforcechange | 在支持 3D Touch 的 iPhone 设备,重按时会触发 |
有两个注意点:
Touchcancle: 在某些特定场景下才会触发(比如来电打断等)
tap事件和longpress事件通常只会触发其中一个
给事件传参数
在test.wxml文件中写一个button标签,然后给该按钮绑定一个tap事件,函数名叫click。
<!-- 给页面按钮绑定一个tap事件,绑定事件的函数名叫click -->
<button bind:tap='click' data-name="{{b}}" data-age="sb">按钮</button>
在test.js中写click函数
page({
data: {
name:'sb',
a:1,
b:2,
list:[{name:"egon",like:"sao"},
{name:"echo",like:"xuexi"}]
},
click:function(e){
console.log("你点我了!")
console.log(e)
}
})
'''
返回的e中都有哪些参数:
{type: "tap", timeStamp: 1607, target: {…}, currentTarget: {…}, detail: {…}, …}
changedTouches: [{…}]
currentTarget:
dataset: {age: "sb", name: 2}
id: ""
offsetLeft: 0
offsetTop: 214
__proto__: Object
detail: {x: 166, y: 240} 鼠标点击“button按钮的位置”
target:
dataset: {age: "sb", name: 2}
id: ""
offsetLeft: 0
offsetTop: 214
__proto__: Object
timeStamp: 1607 打开页面之后,在多长事件内点击的“button按钮”
touches: [{…}]
type: "tap" 通过什么样的事件类型,触发的当前函数
_requireActive: true
currentTarget.dataset
'''
参数currentTarget和Target的区别
test.wxml
# 嵌套的两个标签
<view class="outter" bind:tap="click1" data-name="1">
外面
<view class="inner" bind:tap='click2' data-name="2">
里面
</view>
</view>
test.js
click1: function (e) {
console.log("你点我了1!")
console.log(e)
},
click2: function (e) {
console.log("你点我了2!")
console.log(e)
}
test.wxss
.outter{
height: 300rpx;
background-color: red;
}
.inner{
height: 100rpx;
background-color: blue;
}
在点击内部的属性为inner的标签时,控制台打印的结果是两个标签都被触发了,先触发inner,再触发outter;内部inner返回的参数currentTarget和target没区别;而外部的就有了区别
inner:
{type: "tap", timeStamp: 231738, target: {…}, currentTarget: {…}, detail: {…}, …}
changedTouches: [{…}]
currentTarget:
dataset: {name: "2"}
id: ""
offsetLeft: 0
offsetTop: 281
__proto__: Object
detail: {x: 96, y: 304}
target:
dataset:
name: "2"
__proto__: Object
id: ""
offsetLeft: 0
offsetTop: 281
__proto__: Object
timeStamp: 231738
touches: [{…}]
type: "tap"
_requireActive: true
__proto__: Object
outter:
{type: "tap", timeStamp: 231738, target: {…}, currentTarget: {…}, detail: {…}, …}
changedTouches: [{…}]
currentTarget:
dataset: {name: "1"}
id: ""
offsetLeft: 0
offsetTop: 260
__proto__: Object
detail:
x: 96
y: 304
__proto__: Object
target:
dataset: {name: "2"}
id: ""
offsetLeft: 0
offsetTop: 281
__proto__:
constructor: ƒ Object()
hasOwnProperty: ƒ hasOwnProperty()
isPrototypeOf: ƒ isPrototypeOf()
nv_constructor: "Object"
nv_toString: ƒ ()
propertyIsEnumerable: ƒ propertyIsEnumerable()
toLocaleString: ƒ toLocaleString()
toString: ƒ toString()
valueOf: ƒ valueOf()
__defineGetter__: ƒ __defineGetter__()
__defineSetter__: ƒ __defineSetter__()
__lookupGetter__: ƒ __lookupGetter__()
__lookupSetter__: ƒ __lookupSetter__()
get __proto__: ƒ __proto__()
set __proto__: ƒ __proto__()
timeStamp: 231738
touches: [{…}]
type: "tap"
_requireActive: true
__proto__: Object
如果要将内部的参数传到外部,就需要使用currentTarget.dataset.name,不能使用Target,这就是这两个参数的区别;
touches和changedTouches的区别
事件捕获和事件冒泡
事件捕获是从外到内的
test.wxml
<view class="outter" capture-bind:tap="click1" >
外面的
<view class="middle" capture-bind:tap='click2' >
中间的
<view class="inner" capture-bind:tap='click3' >
里面的
</view>
</view>
</view>
test.wxss
.outter{
height: 600rpx;
background-color: red;
}
.middle{
height: 400rpx;
background-color: yellow;
}
.inner{
height: 200rpx;
background-color: blue;
}
test.js
page({
click1: function () {
console.log("外面的!")
},
click2: function () {
console.log("中间的!")
},
click3: function () {
console.log("里面的!")
},
})
控制台打印结果:
事件的冒泡是从内到外的
test.wxml
<view class="outter" bind:tap="click1" >
外面的
<view class="middle" bind:tap='click2' >
中间的
<view class="inner" bind:tap='click3' >
里面的
</view>
</view>
</view>
事件的传递和冒泡的顺序是:先从外面向里面传递,然后事件再从内部向外冒泡
阻止事件的传递
将捕获事件capture-bind改成capture-catch:tap="click2"
阻止事件的冒泡
将事件bind:tap 修改成catch:tap
自定义组件
组件的创建
点微信开发工具页面上的“+”、“目录”,创建一个普通的文件夹,右键文件夹选中“Component”,即可创建一个组件。
自定义的组件的文件格式与页面文件一样,都有.js .json .wxml .wxss文件
自定义组件文件内容
.wxml
此文件编写组件模板
<!--components/tes/tes.wxml-->
<text>components/tes/tes.wxml</text>
<!-- 这是自定义组件的内部WXML结构 -->
<view class="inner">
{{innerText}}
<slot></slot>
</view>
.js
放组件中的属性和方法
// components/tes/tes.js
Component({ # 有别于页面的Page
/**
* 组件的属性列表
*/
properties: { # 页面的属性在这里注册
},
/**
* 组件的初始数据
*/
data: {
},
/**
* 组件的方法列表
*/
methods: {
}
})
.json
{
"component": true, # 如果想把这个组件添加到页面,这里就必须设置成true。这里是进行自定义组件声明
"usingComponents": {} # 想要调用哪个组件,就需要将哪一个组件导进来;组件也可以嵌套组件
}
.wxss
在此文件中加入组件样式,注意:在组件wxss中不应使用ID选择器,属性选择器和标签选择器。
/* components/tes/tes.wxss */ # 写组件样式的文件
/* 这里的样式只应用于这个自定义组件 */
.inner {
color: red;
}
如何在页面中使用自定义组件
首先要在页面的.json文件中进行引用声明,还要提供对应的组件名和组件路径
{
// 引用声明
"usingComponents": {
// 要使用的组件的名称 和组件的路径
"tes":"/components/tes/tes"
},
"navigationBarTitleText": "八折活动"
}
之后,在对应页面的.wxml中注册该组件
<tes></tes>
将组件引用到页面**
1.在页面的.json文件中注册组件
{
"usingComponents": {
"tes":"/components/tes/tes"
},
2.将组件.json文件中的component修改成true
{
"component": true,
"usingComponents": {}
}
3.将组件到页面文件的.wxml中,进行渲染。
<tes></tes>
将显示在页面中数字进行动态修改
渲染的页面上添加一个按钮(button按钮),并将要进行修改的数字的初始值也渲染在页面上({{a}})将该按钮绑定一个点击事件,当点击该按钮时,触发事件,通过this.setData将修改的值渲染到页面中。
# .wxml文件
<view>{{a}}</view>
<button bind:tap='click4'>加数按钮</button>
# .js
click4: function (e) {
this.setData({
a:this.data.a+1
}),
console.log("this.setData:",this.setData)
console.log("this.data:", this.data)
},
'''
this.setData: ƒ (e,t){var n=this;return"function"==typeof t&&setTimeout(function(){t.call(n)},0),H(this).setData(e)}
this.data: {name: "sb", a: 2, b: 2, list: Array(2), __webviewId__: 19}
a: 2
b: 2
list: (2) [{…}, {…}]
name: "sb"
__webviewId__: 19
__proto__: Object
'''
页面向自定义组件传递数据
1.将要传到组件中的数据写在要传的组件中,这里name="是的"就是即将传入组件的数据
# pages/tests/test.wxml
<tes name="是的"></tes>
2.在组件的.js文件中的properties属性中注册该属性,
properties: {
name: {
type: String,
value: "你好!" # 这个value是默认值,当页面没有传值过来,又要渲染这个name属性,就渲染这个默认值;如果页面传了name,就渲染传过来的,把这个默认值覆盖掉;
}
},
/**
# 组件的初始数据 也可以通过组件的.wxml文件直接渲染到前端页面上去
*/
data: {
title:"data中的"
},
3.将组件properties中的数据通过变量的形式写在组件的.wxml文件中,进行渲染
<text>{{title}}components/tes/tes.wxml 我添加到页面上了!{{name}}</text>
组件将事件传给页面
还以上述动态的修改页面数字为例:
将渲染在页面上的button按钮写在组件中,在组件中给该按钮绑定一个事件(triggerEvent),当点击事件的时候,组件将事件传递到页面,然后在页面中再进行tap事件绑定函数,在页面的js文件中再去动态修改页面数字。页面的.js文件接收组件传来的参数和事件类型,可以通过e.detail获取参数,通过e.type获取定义的事件。
# components/tes/tes.wxml
<button bind:tap='click4' data-ss="123">组件中的加数按钮</button>
# components/tes/tes.js
Component({
methods: {
click4:function(e){
console.log("我是组件中的e:",e)
console.log("我是组件中的this:",this)
this.triggerEvent("icre",{"INDEX:":"我来自组件,这里用triggerEvent将数据带到页面"},{})
}
}
})
'''
/* 我是组件中的e:
{ type: "tap", timeStamp: 3380, target: { … }, currentTarget: { … }, detail: { … }, … }
changedTouches: [{ … }]
currentTarget:
dataset: { ss: "123" }
id: ""
offsetLeft: 0
offsetTop: 625
__proto__: Object
detail: { x: 193, y: 651 }
target:
dataset: { ss: "123" }
id: ""
offsetLeft: 0
offsetTop: 625
__proto__: Object
timeStamp: 3380
touches: [{ … }]
type: "tap"
_requireActive: true
__proto__: Object
===========================================
我是组件中的this:
r { __wxWebviewId__: 17, __wxExparserNodeId__: "5e379cf3" }
__wxExparserNodeId__: "5e379cf3"
__wxWebviewId__: 17
data: Object
name: "是的" 页面的name数据传到组件
__proto__: Object
dataset: (...)
id: (...)
is: (...)
properties: (...)
__proto__:
click4: ƒ click4(e)
constructor: ƒ r()
data: (...)
dataset: (...)
id: (...)
is: (...)
properties: (...)
__proto__: Object */
'''
# pages/tests/test.wxml
<view>{{a}}</view>
<!-- 页面中捕获(监听)来自组件.js文件中的icre事件,这个事件可以随便取名,但是这里在绑定的时候,必须是组建中传过来的那个事件(icre) -->
<tes name="是的" bind:icre="click66"></tes>
# pages/tests/test.js
click66:function(e){
console.log(e)
this.setData({
a:this.data.a+1
})
}
'''
组件中携带的参数和事件的类型(type)都在页面的.js文件中接收了,可以在组件的.js文件的methods方法中写入参数和事件类型。
这是e接收的数据:type有组件传来的自定义事件,detail接收的是传来的参数
{ type: "icre", timeStamp: 6178, target: { … }, currentTarget: { … }, detail: { … }, … }
changedTouches: undefined
currentTarget: { id: "", dataset: { … } }
detail:
INDEX:: "我来自组件,这里用triggerEvent将数据带到页面"
__proto__: Object
target:
dataset: { }
id: ""
__proto__: Object
timeStamp: 6178
touches: undefined
type: "icre"
_requireActive: undefined
__proto__: Object
'''
一般在.js文件中的this指的都是当前页面,以下是console.log(this)的内容
我是this: r {__wxWebviewId__: 13, __wxExparserNodeId__: "47bdd23b", __route__: "pages/index/index", route: "pages/index/index", bindViewTap: ƒ, …}
bindViewTap: ƒ ()
getUserInfo: ƒ ()
arguments: (...)
caller: (...)
length: 1
name: "bound getUserInfo"
nv_length: (...)
__proto__: ƒ ()
[[TargetFunction]]: ƒ getUserInfo(e)
[[BoundThis]]: r
[[BoundArgs]]: Array(0)
onHide: ƒ ()
onLoad: ƒ ()
onReady: ƒ ()
onRouteEnd: ƒ ()
onShow: ƒ ()
onUnload: ƒ ()
options: {}
route: "pages/index/index"
__route__: "pages/index/index"
__wxExparserNodeId__: "47bdd23b"
__wxWebviewId__: 13
data: (...)
dataset: (...)
id: (...)
is: (...)
properties: (...)
__proto__:
bindViewTap: ƒ bindViewTap()
getUserInfo: ƒ getUserInfo(e)
onLoad: ƒ onLoad()
__freeData__: {}
constructor: ƒ r()
data: (...)
dataset: undefined
id: (...)
is: (...)
properties: (...)
__proto__: Object
详细总结看这里:小程序总结