微信小程序
一:项目开始
- 申请小程序账号
- AppID:
- 服务器域名:小程序发请求必须先配置请求的服务器域名
- 安装微信开发者工具
- 使用微信开发者工具初始化项目
注意
服务器域名:
- 域名只支持 https (request、uploadFile、downloadFile) 和 wss (connectSocket) 协议;
- 一个月内仅能修改5次
- 小程序必须使用 HTTPS 请求。小程序内会对服务器域名使用的HTTPS证书进行校验,如果校验失败,则请求不能成功发起(跳过域名校验)
二:项目构成
- app.js :
全局逻辑
- app.json:
全局配置
,包括小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等 - app.wxss:
全局css
- project.config.json:
微信开发者工具配置
- page:
页面
- logs
- index
- json 后缀的 JSON 配置文件:
页面的单独配置
- wxml 后缀的 WXML 模板文件:
页面结构
- wxss 后缀的 WXSS 样式文件:
页面样式
(仅对当前页面生效) - js 后缀的 JS 脚本逻辑文件:
页面逻辑
(可通过getApp()获取全局应用实例)
- json 后缀的 JSON 配置文件:
注意:为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名
三:生命周期
前台、后台定义: 当用户点击左上角
关闭
,或者按了设备Home 键离开微信
,小程序并没有直接销毁,而是进入了后台;当再次进入微信或再次打开小程序,又会从后台进入前台。
注意:只有当小程序进入后台一定时间,或者系统资源占用过高,才会被真正的销毁
属性 | 描述 |
---|---|
onLoad(option) | 监听页面加载: componentWillMount |
onReady | 监听页面初次渲染完成 -- 结构渲染:相当于componentDidMount |
onShow | 监听页面显示 -- 前后台切换 |
onHide | 监听页面隐藏 -- 前后台切换 |
onUnload | 监听页面卸载 -- 跳转页面 |
- Page.prototype.setData:页面重新渲染
-
Page.prototype.route:页面路由
四:视图层
4.1 wxml
用于描述页面的结构
data: {
str: 'str',
num1: 1,
num2: 2,
length: 5,
arr: [
{name: 'arr1', id: 1},
{name: 'arr2', id: 2},
],
obj: {
a: 'obj1',
b: 'obj2'
}
}
4.1.1 数据绑定
<!-- 数据绑定 -->
<view> {{str}} </view>
<view> {{obj.a}} </view>
<!-- 运算 -->
<view>{{num1 + num2}}</view>
<!-- 属性内调用需要包裹在引号内 -->
<view id="{{str}}">属性</view>
<!-- 三元运算 -->
<view id="{{str === '123' ? 1 : 2}}">三元运算</view>
4.1.2 列表渲染
<!-- 默认index、item -->
<view wx:for="{{arr}}">
{{index}}: {{item.name}}
</view>
<!-- 修改index、item -->
<view wx:for="{{arr}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.name}}
</view>
<!-- 渲染多个节点 -->
<block wx:for="{{[1, 2, 3]}}">
<view> {{index}} </view>
<view> {{item}} </view>
</block>
<!-- 注意wx:key来指定列表中项目的唯一的标识符,以保证效率和状态 -->
<view wx:for="{{arr}}" wx:key="id">
{{index}}: {{item.name}}
</view>
<!-- or -->
<view wx:for="{{arr}}" wx:key="*this">
{{index}}: {{item.name}}
</view>
4.1.3 条件渲染
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
<!-- 多个节点 -->
<block wx:if="{{true}}">
<view> view1 </view>
<view> view2 </view>
</block>
注意:
<block/>
并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性
wx:if
vs hidden
-
hidden
:始终会渲染 -
wx:if
:可能会渲染
如果有频繁的切换,选择hidden
,否则wx:if
4.1.4 模板
<template name="msgItem">
<view>
<text> {{index}}: {{msg}} </text>
<text> Time: {{time}} </text>
</view>
</template>
<template is="msgItem" data="{{index: 1, msg: 'aaa', time: '2017-1-1'}}"/>
4.1.5 引用
WXML 提供两种文件引用方式import和include
- import:引用部份
- include:引用全部
import
<!-- item.wxml -->
<template name="item">
<text>{{text}}</text>
</template>
<!-- index.wxml -->
<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>
include
<!-- index.wxml -->
<include src="header.wxml"/>
<view> body </view>
<include src="footer.wxml"/>
<!-- header.wxml -->
<view> header </view>
<!-- footer.wxml -->
<view> footer </view>
4.2 事件
事件类型
- 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递
- 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递
事件绑定
- bind事件绑定不会阻止冒泡事件向上冒泡
- catch事件绑定可以阻止冒泡事件向上冒泡
- capture-bind事件绑定会触发捕获事件向下捕获
- capture-catch 中断捕获阶段和取消冒泡阶段
<view id="outer" bind:touchstart="handleTap1" capture-bind:touchstart="handleTap2">
outer view
<view id="inner" bind:touchstart="handleTap3" capture-bind:touchstart="handleTap4">
inner view
</view>
</view>
dataset
事件的参数
<view bindtap="handleClick" data-alphaBeta="2">click me!</view>
Page({
handleClick: function(e){
console.log(111, e)
}
})
4.3 wxs
WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构
<wxs module="m1">
var msg = "hello world";
var fun = function(num1, num2){
var res = num1 + num2
return res.toFixed(2)
}
module.exports.message = msg;
module.exports.fun = fun;
</wxs>
<view> {{m1.message}} </view>
<view>{{m1.fun(num1, num2)}}</view>
4.4 wxss
WXSS 具有 CSS 大部分特性
全局wxss和page的wxss会自动引用,不需要手动导入
4.4.1 尺寸单位
- rpx(responsive pixel): 可以根据屏幕宽度进行自适应
4.4.2 样式导入
/** common.wxss **/
.small-p {
padding:5px;
}
/** app.wxss **/
import "common.wxss";
.middle-p {
padding:15px;
}
4.4.3 内联样式
<view style="color: red;" />
五:组件
降低耦合,利于维护代码
5.1 创建组件
page
// page json
{
"component": true,
"usingComponents": {
"comtext": "path/to/the/custom/component"
}
}
引用组件的路径写 相对路径
<!-- page wxml -->
<view>
<!-- 以下是对一个自定义组件的引用 -->
<comtext inner-text="Some text"></comtext>
</view>
引用组件的属性名采用连写
component
<!-- component wxml -->
<view class="inner">
{{innerText}}
</view>
<slot></slot>
<slot>
标签用于显示子节点
/* component wxss */
.inner {
color: red;
}
注意:在组件wxss中不应使用ID选择器、属性选择器和标签名选择器(尝试后可以用标签名选择器,但建议用class选择器)
// component js
Component({
properties: {
// 这里定义了innerText属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: 'default value',
}
},
data: {
// 这里是一些组件内部数据
someData: {}
},
ready: function(){
console.log(this, 'this')
},
methods: {
// 这里是一个自定义方法
customMethod: function(){}
}
})
properties
的属性名采用驼峰
5.2 事件
page与component之间的事件
page
<comtext inner-text="Some text" bind:parentEvent="parentEvent">
<view bindtap="childEvnet">我是component的子节点</view>
</comtext>
Page({
parentEvent: function(e){
console.log('parentEvent', e.detail)
}
})
component
<view id="inner">
<text bind:tap="childEvent">{{innerText}}</text>
</view>
<slot></slot>
Component({
properties: {
// 这里定义了innerText属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: 'default value',
}
},
data: {
// 这里是一些组件内部数据
someData: {}
},
methods: {
// 这里是一个自定义方法
childEvent: function (e) {
console.log('childEvent')
var myEventDetail = {
data: 1
} // detail对象,提供给事件监听函数
var myEventOption = {} // 触发事件的选项
this.triggerEvent('parentEvent', myEventDetail, myEventOption)
}
}
})
5.3 behaviors
抽离共用的组件js代码
behaviors 是用于 组件间 代码 共享 的特性,类似于一些编程语言中的
mixins
或traits
behavior
// my-behavior.js
module.exports = Behavior({
behaviors: [],
properties: {
myBehaviorProperty: {
type: String
}
},
data: {
myBehaviorData: {}
},
attached: function(){},
methods: {
myBehaviorMethod: function(){}
}
})
component
// my-component.js
var myBehavior = require('my-behavior')
Component({
behaviors: [myBehavior],
properties: {
myProperty: {
type: String
}
},
data: {
myData: {}
},
attached: function(){},
methods: {
myMethod: function(){}
}
})
5.4 组件间关系
注意:必须在 两个组件 定义中都加入relations定义,否则不会生效
page
<!-- page -->
<custom-ul>
<custom-li> item 1 </custom-li>
<custom-li> item 2 </custom-li>
</custom-ul>
custom-ul
<!-- custom-ul wxml -->
<view>
<slot></slot>
</view>
// custom-ul js
Component({
relations: {
'../custom-li/custom-li': {
type: 'child', // 关联的目标节点应为子节点
linked: function (target) {
console.log(target, 'ul-linked')
},
linkChanged: function (target) {
console.log(target, 'ul-linkChanged')
},
unlinked: function (target) {
console.log(target, 'ul-unlinked')
}
}
},
methods: {
_getAllLi: function () {
// 使用getRelationNodes可以获得nodes数组,包含所有已关联的custom-li,且是有序的
var nodes = this.getRelationNodes('../custom-li/custom-li')
console.log(nodes, 'nodes-before')
nodes[0].setData({
name: 2
}, () => {
console.log(nodes, 'nodes-after')
})
}
},
ready: function () {
this._getAllLi()
}
})
custom-li
<!-- custom-li wxml -->
<text>{{name}}</text>
// custom-li js
Component({
data: {
name: 1
},
relations: {
'../custom-ul/custom-ul': {
type: 'parent', // 关联的目标节点应为父节点
linked: function (target) {
console.log(target, 'li-linked')
},
linkChanged: function (target) {
console.log(target, 'li-linked')
},
unlinked: function (target) {
console.log(target, 'li-linked')
}
}
}
})
六:请求
这是一个demo
const url = "https://cnodejs.org/api/v1"
const fetch = function (method = 'get', uri, data = {}, success = () => {}){
wx.request({
method,
url: `${url}/${uri}`,
data,
header: {
'content-type': 'application/json'
},
success
})
}
module.exports = {
url,
fetch
}
<!--pages/demo/demo.wxml-->
<view wx:for="{{category}}" wx:key="id" bindtap="handleTap" class="{{item.del ? 'del' : ''}}" id="{{item.id}}">
{{index}} - {{item.title}}
</view>
.del{
text-decoration: line-through;
}
const { fetch } = require('../../config/api.js')
Page({
data: {
category: []
},
onLoad: function (options) {
this.getList()
},
getList: function(){
fetch('get', '/topics', {}, res => {
const val = res.data.map(v => {
let obj = v
obj.del = false
return obj
})
this.setData({
category: val
})
})
},
handleTap: function(e){
const id = e.currentTarget.id
const index = this.data.category.findIndex(v => v.id == id)
let category = this.data.category
category[index].del = !category[index].del
this.setData({
category
})
}
})