开启项目
新项目
采用WePY开源框架开发,WePY项目的创建与使用步骤如下
1、在全局安装WePY命令行工具,执行命令:
npm install wepy-cli -g
2、在开发目录中,生成Demo开发项目文件夹:
wepy new myDemo
3、移动到项目目录下,开始编译
wepy build --watch
开源项目
下载一个开源的采用WePY框架开发的小程序项目,“友福图书馆”,对其进行研究学习。下载完后,打开并启动项目:
1、移动到项目的根目录
cd library
2、安装npm,demo项目中可能运用到相关的库
npm install
3、运行
npm run dev
下面以“友福图书馆”项目为例,进行小程序开发的研究
项目架构
把demo下载下来,编译后,结构目录变成如下所示:
其中,dist目录是编译后生成的,其中包含的是demo对应的小程序原生文件。在利用WePY框架开发小程序时,在src目录下进行代码的编辑。
项目结构整理
app.wpy是小程序的入口文件。
另外,将自定义的组件、配置文件、项目中用到的图片图标、小程序的各个页面、组件样式等等,进行分门别类,保存在各自的文件夹,使得项目结构清晰,且易于管理。
小程序整体架构
先预览一下这个小程序的外观呈现,如下图:
从功能上说,分为三大块:首页、借阅页面、个人页面。
具体代码实现是,在小程序入口文件app.wpy的APP示例中,在config对象中声明tabBar。
<script>
import wepy from 'wepy'
import 'wepy-async-function'
export default class extends wepy.app {
config = {
tabBar: { //
color: '#AEADAD',
selectedColor: '#049BFF',
backgroundColor: '#fff',
borderStyle: 'black',
list: [{
pagePath: 'pages/index',
selectedIconPath: './images/tabbars/icon-mark-active@2x.png',
iconPath: './images/tabbars/icon-mark@2x.png',
text: '首页'
}, {
pagePath: 'pages/borrow',
selectedIconPath: './images/tabbars/icon-shelf-active@2x.png',
iconPath: './images/tabbars/icon-shelf@2x.png',
text: '借阅'
}, {
pagePath: 'pages/user',
selectedIconPath: './images/tabbars/icon-smile-active@2x.png',
iconPath: './images/tabbars/icon-smile@2x.png',
text: '我的'
}]
},
}
}
除此之外,应该事先编辑好TabBar中包含的各个页面。这样,就实现了图中的效果。
另外,还可以在config中声明:整个项目各个页面的文件路径、整体风格样式、以及一些全局配置和全局方法等等。如下:
pages: [
'pages/index',
'pages/main/search',
'pages/main/list',
... ... // 整个项目各个页面的文件路径
'pages/user/collect'
],
window: {
navigationBarTitleText: '友福图书馆',
navigationBarTextStyle: 'white',
navigationBarBackgroundColor: '#049BFF',
backgroundColor: '#eaeaea',
backgroundTextStyle: 'light',
enablePullDownRefresh: true // 是否下拉刷新
},
在APP实例中,除了config对象外,还可以设置一些项目中要用到的工具方法。注意:app.wpy中没有</template>部分。
以上是从整体的角度来看的,下面逐渐更加细节地来研究小程序的开发。这里重点看了首页的设计、自定义组件的设计与使用,以及网络工具封装使用,这3大不同类别的模块的设计。
首页
首页的文件内容总体分为3大块:<template>部分、<script>部分、</style>部分
其中,
- 在<template>部分进行静态页面的设计
<template>
<view class="page-index">
<SearchBar :placeholder="searchText"></SearchBar>
<BookList :list.sync="list" title="图书推荐"
:loading.sync="loading" :noMore.sync="noMoreList"></BookList>
</view>
</template>
1、组件的引入与声明
在<template>标签当中堆积、嵌套了许多组件。
其中自定义组件需要在<script>部分import进来。
如:import SearchBar from '../components/searchbar'
并要在页面示例中的component对象中进行声明,如:
components = {
SearchBar, //命名与import的一致
}
2、组件的属性设置
在首页中,组件的属性值来自页面示例中data对象声明的属性。
<SearchBar :placeholder="searchText"></SearchBar>
... ...
data = {
searchText: '搜索图书',
}
另外,进行了父组件与子组件的属性的数据绑定,用到的语法是:props
和.sync
,这样实现了属性的动态设置。
3、“推荐图书”列表组件
<BookList :list.sync="list" title="图书推荐"
:loading.sync="loading" :noMore.sync="noMoreList"></BookList>
在首页中,主体是一个类似listView的组件。但是,小程序中并没有封装好这么一个组件给我们直接使用。这就涉及到了自定义组件了,后面再详细来看。
这里要说的是,在首页的页面示例中,给这个组件提供数据,这部分是在<script>部分处理的。
- <script>部分进行变量声明、逻辑交互及事件处理等
1、给组件提供数据
接着上面的思路,如何给自定义组件BookList设置数据?
首先在页面示例的data对象中,声明相关数据变量,因为涉及到网络请求数据,并分页加载,这里添加了以下变量。
data = {
noMoreList: false,
loading: false,
list: [], // 装载请求回来的数据
page: 1 // 分页加载页数
}
同时,添加请求数据的方法updateBookList(page) { }
、下拉刷新的方法onPullDownRefresh() { }
、加载更多触发方法onReachBottom() { }
,方法的具体内容看需求来决定,这里不多说。什么时候开始网络请求呢?
onReady() {
// 页面准备加载的时候,触发
}
2、事件处理
这里,没有看到一些类似于按钮点击的数据处理,因为这在自定义组件中完成了。这是一种MVVM的设计模式。这也是采用WePY框架开发获得的一个好处。
- </style>部分引入样式
在首页文件中,这部分代码不多。
<style lang="less">
.page-index{
// some style
}
</style>
因为这里lang="less"标志是表示,引入了全局的公共样式page-index,其中包含了首页需要用到的样式,这在别的文件中已经编辑好了。所以,在首页直接引用即可。
到此,首页的研究大概完毕。下面看小程序中自定义组件的设计。
自定义组件
这里,以自定义的搜索栏组件为例,进行研究。
自定义组件的文件内容也分成3部分:<template>部分、<script>部分、</style>部分,与页面文件相似。
1、与页面的不同点
自定义组件示例继承wepy.component基类,而页面示例继承自wepy.page基类。另外,自定义组件中一般没有设置onLoad等表示组件周期的方法。
2、逻辑交互与事件处理
关于自定义组件,上面已经说了有关父子组件传值的问题了,这里主要看组件中的逻辑交互与事件处理。
如:搜索按钮
// 在template部分
<view class="weui-search-bar__cancel-btn" hidden="{{!inputShowed}}" @tap="search">
搜索
</view>
这里用系统组件view表示搜索按钮,并在其中绑定了一个点击事件。语法是@tap="search"
。其中,search方法在部分的method对象中声明
methods = {
search () {
const params = {
keyword: this.inputVal || this.placeholder
}
wx.navigateTo({
url: `/pages/main/list?params=${JSON.stringify(params)}`
})
},
}
可以看出,其中的逻辑功能处理包括传值、页面跳转。
3、页面之间的传值,页面跳转
执行微信小程序系统提供的方法wx.navigateTo,参数url表示下一个页面的路径,并且可以带上传递的值
wx.navigateTo({
url: `/pages/main/list?params=${JSON.stringify(params)}`
})
下一个页面怎么接收传过来的值呢?在下一个页面的页面示例中,方法是:
onLoad(query) {
let params = query && query.params
try {
params = JSON.parse(params)
} catch (e) {
params = {}
}
this.params = params // 保存在data对象中声明的属性params中
}
网络工具
1、在.js文件中封装网络请求工具,继承的基类是wepy.mixin。
import wepy from 'wepy'
import { service } from '../config.js'
export default class httpMixin extends wepy.mixin {
$get(
{url = '', headers = {}, data = {} },
{success = () => {}, fail = () => {}, complete = () => {} }
) {
const methods = 'GET'
this.$ajax(
{url, headers, methods, data},
{success, fail, complete }
)
}
$post(
{url = '', headers = {}, data = {} },
{success = () => {}, fail = () => {}, complete = () => {} }
) {
const methods = 'POST'
this.$ajax(
{url, headers, methods, data},
{success, fail, complete }
)
}
}
$ajax(
{url = '', headers = {}, methods = 'GET', data = {} },
{success = () => {}, error = () => {}, fail = () => {}, complete = () => {} }
) {
// 增强体验:加载中
wx.showNavigationBarLoading()
// 构造请求体
const request = {
url: url + '?XDEBUG_SESSION_START=1',
method: ['GET', 'POST','PUT', 'DELETE'].indexOf(methods) > -1 ? methods : 'GET',
header: Object.assign({
'Authorization': 'Bearer ' + wx.getStorageSync('token'),
'X-Requested-With': 'XMLHttpRequest'
}, headers),
data: Object.assign({
// set something global
}, data)
}
// 控制台调试日志
console.table(request)
// 发起请求
wepy.request(Object.assign(request, {
success: ({ statusCode, data }) => {
// 控制台调试日志
console.log('[SUCCESS]', statusCode, typeof data === 'object' ? data : data.toString().substring(0, 100))
// 状态码正常 & 确认有数据
if (0 === +data.code && data.data) {
// 成功回调
return setTimeout(() => {
this.isFunction(success) && success({statusCode, ...data})
this.$apply()
})
} else if (data.code == 2) {
// 删除过时token
wx.removeStorageSync('token', null)
// 重新登录
wepy.login({
success: (res) => {
console.log('wepy.login.success:', res)
// 根据业务接口处理:业务登陆:异步
this.$post({ url: service.login, data: {code: res.code} }, {
success: ({code, data}) => {
if(data.token){
wx.setStorageSync('token', data.token)
}
var route = '/' + getCurrentPages()[0].__route__;
if(route == '/pages/user/register'){
return
}
if (!data.token ){
// wx.reLaunch({url: '/pages/user/register'})
wx.navigateTo({url: '/pages/user/register'})
}else{
wx.reLaunch({url: route})
}
}
})
},
fail: (res) => {
console.log('wepy.login.fail:', res)
}
})
} else {
// 失败回调:其他情况
return setTimeout(() => {
/* if(this.isFunction(fail)) {
fail({statusCode, ...data})
this.$apply()
}else{ */
wx.showModal({
title: '操作错误',
content: data.message,
showCancel: false
})
// }
})
}
},
fail: ({ statusCode, data }) => {
// 控制台调试日志
console.log('[ERROR]', statusCode, data)
// 失败回调
return setTimeout(() => {
this.isFunction(error) && error({statusCode, ...data})
this.$apply()
})
},
complete: (res) => {
// 控制台调试日志
//console.log('[COMPLETE]', res)
// 隐藏加载提示
wx.hideNavigationBarLoading()
// 停止下拉状态
wx.stopPullDownRefresh()
// 完成回调
return (() => {
this.isFunction(complete) && complete(res)
this.$apply()
})()
}
}))
}
2、网络工具的使用
先引入工具方法所在的文件
import http from '../mixins/http'
在mixins对象做声明
mixins = [http]
调用工具方法,如,请求图书数据
updateBookList(page) {
this.loading = true
// 请求列表
this.$get({
url: 'http://www......',
data: {
// 默认从1开始为第一页
page: page
}
}, {
success: ({code, data}) => {
// 成功回调 逻辑处理
data = data.data
... ...
},
fail: ({code, data}) => {
// 失败回调
},
complete: () => {
this.loading = false
}
})
}
总结
了解了代码的组织,找到各个主要逻辑/功能模块与代码文件之间的对应关系,通过代码分析走通几个关键的、有代表性的执行流程,挑选感兴趣的“枝干”代码来阅读(网络工具代码)。总体来说,对于微信小程序的开发有了进一步的认识。