文章目录:
-微信小程序公测啦!!!
-哦。
唉我这么激动周围的人毫无反应,真的是无趣啊。不管了,今天下午终于没课了,就放着要期末的积分变换不管来学这个吧。
本质上来说,我们还是构造了一个webapp,但是微信弃用了安卓卡卡的原生webview,用x5的内核来解析应用的Js(支持ES6哦��),也不知道性能会如何。大概分为下面这么几块来研究一下这个框架:
- 开发环境搭建
- 应用架构
- 逻辑层
- 视图层
一、开发环境搭建
开发环境搭建特别简单,直接下载开发IDE就行了,直接贴地址。。开发IDE下载。
二、应用架构解析
我们从官方给出的Hello World开始讲起吧,首先让我们看看目录结构:
- pages
- index
- index.js
- index.wxml
- index.wxss
- logs
- logs.js
- logs.json
- logs.wxml
- logs.wxss
- index
- utils
- util.js
- app.js
- app.json
- app.wxss
首先可以看到有好几种没见过的后缀,类比过去的话wxml
就是html
,wxss
就是css
,按照那样理解就行了(当然是精简修改破解版的)。然后还有一些json,这些是作为配置文件使用的,一会单独讲。项目整体的架构相当清晰简洁(毕竟是helloworld。。),一个应用入口文件app.js
,然后多个页面index
, logs
,一个公用的工具函数存放处utils
。
我们从单个页面讲起。比如说Index页面,小程序要求页面文件名和目录名(页面名称)必须一致,所以也不需要在js里做import(这样挺方便的)。看看js文件和模板文件
//index.js
//获取应用实例
var app = getApp()
Page({
data: {
motto: 'Hello World',
userInfo: {}
},
//事件处理函数
bindViewTap: function() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad: function () {
console.log('onLoad')
var that = this
//调用应用实例的方法获取全局数据
app.getUserInfo(function(userInfo){
//更新数据
that.setData({
userInfo:userInfo
})
})
}
})
<!--index.wxml-->
<view class="container">
<view bindtap="bindViewTap" class="userinfo">
<image class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</view>
<view class="usermotto">
<text class="user-motto">{{motto}}</text>
</view>
</view>
其中Page是框架提供的全局方法,然后和一般的MVVM框架一样,采用了数据绑定,然后也有事件绑定、生命周期这些东西(而且生命周期和应用需求很一致,比如说应用后台,这点很赞),可以看到也有路由跳转(index貌似是默认打开的),视图更新。
下面看看json文件是干什么的,比如说logs.json
{
"navigationBarTitleText": "查看启动日志"
}
因为程序是运行在微信之上的,所以我们给的这些配置就是改一下标题栏颜色啊文字啊这些东西。然后还有一个复杂一些的app.json,这个就看文档配置就好了,要注册page之类的。架构大概就这个样子。
三、逻辑层
微信小程序的逻辑层内容相当的简洁,官方文档的TAB就这几个东西
- 注册程序
- 注册页面
- 模块化
- API
从应用本身来说呢,我们处理逻辑大概就这几种情境吧:
- 页面内部的逻辑处理
- 路由跳转
- 跨页面的通信
1. 注册程序
框架给我们提供了什么呢?首先是 注册程序,也就是全局的App方法(传入一个对象),这个作为应用的主入口,可以监听一些应用生命周期的事件,然后配置一些全局变量,按理说应该还要能配置路由关系。看看我们的App.js
//app.js
App({
onLaunch: function () {
//调用API从本地缓存中获取数据
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
},
getUserInfo:function(cb){
var that = this
if(this.globalData.userInfo){
typeof cb == "function" && cb(this.globalData.userInfo)
}else{
//调用登录接口
wx.login({
success: function () {
wx.getUserInfo({
success: function (res) {
that.globalData.userInfo = res.userInfo
typeof cb == "function" && cb(that.globalData.userInfo)
}
})
}
})
}
},
globalData:{
userInfo:null
}
})
这里的onLaunch是一个生命周期的事件钩子,然后getUserInfo相当于一个全局的方法(使用getApp()获取实例以后就可以调用,在index.js里就有),globalData作为app的属性自然就担任了全局变量这一角色啦。这里还用到了本地存储这一API,这也是一个体验好的应用必不可少的部分。
2. 注册页面
然后是 注册页面。和全局的App方法对应也有一个Page方法用于页面的注册,页面也会有一些生命周期的事件,除此之外还有一个比较特殊的事件:页面下拉刷新 onPullDownRefresh
。其他的就是自定义事件处理函数的注册了,见DEMO
<view bindtap="viewTap"> click me </view>
Page({
viewTap: function() {
console.log('view tap')
}
})
除了bind+event作为属性名还可以用catch+event,区别在于后者可以阻止继续冒泡。然后事件绑定函数只有一个参数event,但是event包含了该节点的信息所以很方便就能完成各种逻辑定制。
3. 模块化
首先要明确的是,目前版本的小程序还不支持node_modules的导入,所以要用的话只能复制源码过去了。和webpack的模块化类似,小程序各个page的作用域是独立的,如果要对外暴露接口的话(比如写公共组件),要使用模块导出
// common.js
function sayHello(name) {
console.log(`Hello ${name} !`)
}
function sayGoodbye(name) {
console.log(`Goodbye ${name} !`)
}
module.exports.sayHello = sayHello
// module.exports = { sayHello: sayHello }
结合的ES6的import食用口感更佳。
4. API
小程序的API都通过wx这一个全局对象来访问,和运行在浏览器的javascript类似,wx也提供了如HTTP请求,本地存储,绘图这一类的API,除此之外还有一些更为本地化的API,比如拨打电话、重力感应、交互反馈、支付这一类API,非常的方便,具体使用时请查阅小程序API
四、视图层
与React Native类似,微信小程序也自制了一套标签(不过要精简的多),称为WXML(WeiXin Markup language),也同样是基于组件的。与之配合的样式表称为WXSS(WeiXin Style Sheet)。
WXML作为模板语言,少不了数据绑定、条件渲染、列表渲染这些东西,和Vue.js很像,下面简单过一遍。
1. 数据绑定
数据绑定采用Mustache语法,也就是双大括号。值的注意的是大括号里支持一些ES6的表达式,比如说展开语法和简化的对象属性写法
<template is="objectCombine" data="{{...obj1, ...obj2, e}}"></template>
Page({
data: {
obj1: {
a: 1,
b: 2
},
obj2: {
c: 3,
d: 4
},
e: 5
}
})
得到的结果就是{a: 1, b: 2, c: 3, d: 4, e: 5}
。
2. 条件渲染和列表渲染
条件渲染和列表渲染很简单,就像vue.js里两个指令v-if
和v-for
,小程序里用的是wx:if
和wx:for
,用两个简单的DEMO过一下
<!-- 条件渲染 -->
<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
<!-- 列表渲染 -->
<view wx:for="{{array}}">
{{index}}: {{item.message}}
</view>
注意一下列表渲染里面的关键字index和item,对应的是数组的下标和元素,如果想自定义关键字的话就这样
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
{{idx}}: {{itemName.message}}
</view>
还要提到一个block标签,在学RN的时候就觉得为了将几个部分组合起来要套一个View在外面很坑,这里就诞生了一个block标签实现这个需求
<block wx:if="{{true}}">
<view> view1 </view>
<view> view2 </view>
</block>
提到列表渲染就要提到 key 这个东西,因为框架不知道列表元素变了以后是怎么变的,所以对于动态列表需要给列表元素一个独一无二的key,使用wx:key
指令提供。指令值可以是一个字符串表示属性名(循环中的item的Property),也可以是关键字*this
(如果item不是一个对象的话)。
<switch wx:for="{{objectArray}}" wx:key="unique" style="display: block;"> {{item.id}} </switch>
<button bindtap="switch"> Switch </button>
<button bindtap="addToFront"> Add to the front </button>
<switch wx:for="{{numberArray}}" wx:key="*this" style="display: block;"> {{item}} </switch>
<button bindtap="addNumberToFront"> Add to the front </button>
Page({
data: {
objectArray: [
{id: 5, unique: 'unique_5'},
{id: 4, unique: 'unique_4'},
{id: 3, unique: 'unique_3'},
{id: 2, unique: 'unique_2'},
{id: 1, unique: 'unique_1'},
{id: 0, unique: 'unique_0'},
],
numberArray: [1, 2, 3, 4]
},
switch: function(e) {
const length = this.data.objectArray.length
for (let i = 0; i < length; ++i) {
const x = Math.floor(Math.random() * length)
const y = Math.floor(Math.random() * length)
const temp = this.data.objectArray[x]
this.data.objectArray[x] = this.data.objectArray[y]
this.data.objectArray[y] = temp
}
this.setData({
objectArray: this.data.objectArray
})
},
addToFront: function(e) {
const length = this.data.objectArray.length
this.data.objectArray = [{id: length, unique: 'unique_' + length}].concat(this.data.objectArray)
this.setData({
objectArray: this.data.objectArray
})
},
addNumberToFront: function(e){
this.data.numberArray = [ this.data.numberArray.length + 1 ].concat(this.data.numberArray)
this.setData({
numberArray: this.data.numberArray
})
}
})
3. 模板
还是和RN一样,因为View这些标签语义化的限制及组件化的要求,我们很多时候我们会自己定义一个模板来复用,这个直接看DEMO就懂了
<template name="msgItem">
<view>
<text> {{index}}: {{msg}} </text>
<text> Time: {{time}} </text>
</view>
</template>
<template is="msgItem" data="{{...item}}"/>
注意模板作用域是独立的,只能使用data属性传入的数据。
4. 事件
前面简单提了一下事件的两种绑定方式,现在简单列一下wx支持的一些冒泡事件名称:
- touchstart 手指触摸动作开始
- touchmove 手指触摸后移动
- touchcancel 手指触摸动作被打断,如来电提醒,弹窗
- touchend 手指触摸动作结束
- tap 手指触摸后马上离开
- longtap 手指触摸后,超过350ms再离开
其余的事件都是不冒泡的,那些就因组件而异了,比如说<form />
的submit等。然后之前也提过,事件参数就只有一个event,所以要传值的时候data-属性就显得很重要了,因为事件对象可以获取到一个dataset
对象,对应都是就是各个自定义的data-属性。
5. 视图的引用
就像ejs里会有include一样,模板语言肯定是会有引用嵌套的功能的,wx里提供了两种方法,分别是import
和include
,见DMEO
<!-- item.wxml -->
<template name="item">
<text>{{text}}</text>
</template>
<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>
然后是include
<!-- index.wxml -->
<include src="header.wxml"/>
<view> body </view>
<!-- header.wxml -->
<view> header </view>
6. WXSS
WXSS继承了css的大部分特性,同时针对移动端做了一些拓展优化:
- 尺寸单位
- 样式导入
尺寸单位是最赞的特性了,WXSS新增了rpx
这个单位及重制了rem
这个单位。其中使用rpx时,默认按照屏幕宽度750px进行样式编写即可,页面会根据实际页面大小进行缩放。而rem
则默认屏幕为20rem宽,同样会自动缩放。
样式导入和css中的写法一样(原理肯定不一样),使用@import ()
来进行导入即可。
初次之外,内联样式支持动态更新(数据绑定),选择器目前的支持范围如下:
class .intro 选择所有拥有 class=”intro” 的组件
id #firstname 选择拥有 id=”firstname” 的组件
element view 选择所有 view 组件
element, element view checkbox 选择所有文档的 view 组件和所有的 checkbox 组件
::after view::after 在 view 组件后边插入内容
::before view::before 在 view 组件前边插入内容
值得注意的是,在wx中,选择器的优先级概念有差异,不同的page下面的wxss是独立的,只作用于相应页面不会相互覆盖,只有app下面的wxss才是通用的。
7. 组件
首先还是要吐槽一下,真的和RN很像。。。wx提供了一系列组件,具体的还是查询官方文档吧~贴地址微信小程序组件
嗯,真的就这么多,感觉这个框架设计的挺好用的。。。