本次的系列博文的知识点讲解和代码,主要是来自于 七月老师 的书籍《微信小程序开发:入门与实践》,由个人总结并编写,关于更多微信小程序开发中的各项技能,以及常见问题的解决方案,还请大家购买书籍进行学习实践,该系列博文的发布已得到七月老师的授权许可
我们在 WeChat 文章列表页面(二) 中,已经完成了数据的绑定和页面的跳转了,效果图如下所示
模块
我们先把所有文章的数据分离到一个单独的 js 文件中,防止污染我们的业务层,在根目录下新建一个文件夹,命名为 data,在 data 文件夹下新建一个 js 文件,命名为 data.js,并将原来复杂对象的数据绑定修改成简单的字符串
代码语言:javascript复制var postList = [{
date: "Jan 28 2017",
title: "小时候的冰棍儿与雪糕",
postImg: "/images/post/post-4.jpg",
avatar: "/images/avatar/avatar-5.png",
content: "冰棍与雪糕绝对不是同一个东西。3到5毛钱的雪糕犹如现在的哈根达斯,而5分1毛的冰棍儿就像现在的老冰棒。时过境迁,...",
readingNum: 0,
collectionNum: 0,
commentNum: 0
},
{
date: "Jan 9 2017",
title: "从童年呼啸而过的火车",
postImg: "/images/post/post-5.jpg",
avatar: "/images/avatar/avatar-1.png",
content: "小时候,家的后面有一条铁路。听说从南方北上的火车都必须经过这条铁路。火车大多在晚上经过,但也不定时只有在夜深人静的时候,火车的声音才能从远方传来...",
readingNum: 0,
collectionNum: 0,
commentNum: 0
},
{
date: "Jan 29 2017",
title: "记忆里的春节",
postImg: "/images/post/post-1.jpg",
avatar: "/images/avatar/avatar-3.png",
content: "年少时,有几样东西,是春节里必不可少的:烟花、心意、凉茶、压岁钱、饺子。年分大小年,有的地方是腊月二十三过小年,有的地方是腊月二十四...",
readingNum: 0,
collectionNum: 0,
commentNum: 0
},
]
我们提取的数据文件 data.js 可以视作是小程序的一个模块,若是想让其他文件访问这个模块,还需要使用 module.exports 向外部暴露一个接口,在 data.js 文件的最下方添加以下代码:
代码语言:javascript复制module.exports = {
postList:postList
}
定义好模块后,我们还需要在 post.js 中引入 data.js 这个模块
代码语言:javascript复制var dataObj = require("../../data/data.js");
Page({
data: {
},
onLoad:function(){
this.setData({
postList: dataObj.postList
})
}
})
在这里使用 require(path) 引用 js 模块时,要特别注意以下几点:
- 被引用的文件一定要带有扩展名 js,这一点是不同于页面路径的
- path 路径不可以使用绝对路径,否则会报错,应使用相对路径
- 在 JavaScript 文件中声明的变量和函数只在该文件中有效,不同的文件中可以声明相同名字的变量和函数,不会相互影响
最后,记得调整 post.wxml 中 {{}} 的语法,在这里就不再演示了
模板
我们在文章列表里使用了列表渲染,但如果其他页面同样需要显示文章列表该怎么办呢?通常情况下,我们会考虑把一些公共的、经常使用的业务逻辑提取成一个公共函数,当在多个地方需要使用函数时,只需要调用这个函数即可完成相应的业务
而小程序也提供了一个称作模板的技术来支持对 wxml 组件的封装,但这种封装仅仅只是 wxml 的代码片段,并不像 AngularJS 中可以把 HTML、JS 作为一个整体被封装起来
要使用模板,首先需要新建模板文件,在 /pages/post 下新建目录 post-item,作为模板文件目录,并在该目录下新建 2 个文件:post-item-tpl.wxml 和 post-item-tpl.wxss 文件
使用模板是为了简化 post.wxml 中文章的写法,让文章可以成为一个单独的“组件”,供其他多个地方使用,就像是一个简单的 image 组件就可以实现图片的显示功能
接下来,我们把 post.wxml 中 <block>
标签中关于文章的代码剪切到 post-item-tpl.wxml 中,让这段代码成为一个可复用的“组件”
<template name="postItemTpl">
<view class="post-container">
<view class="post-author-date">
<image src="{{item.avatar}}" />
<text>{{item.date}}</text>
</view>
<text class="post-title">{{item.title}}</text>
<image class="post-image" src="{{item.postImg}}" mode="aspectFill" />
<text class="post-content">{{item.content}}</text>
<view class="post-like">
<image src="/images/icon/wx_app_collect.png" />
<text>{{item.collectionNum}}</text>
<image src="/images/icon/wx_app_view.png" />
<text>{{item.readingNum}}</text>
<image src="/images/icon/wx_app_message.png" />
<text>{{item.commentNum}}</text>
</view>
</view>
</template>
template 模板相关的内容必须被包裹在 <template></tenplate>
标签中,使用 name 属性指定 template 模板的模板名,这个模板名将在引用模板时被使用
接下来,我们在 post.wxml 中引用并使用这个 template
代码语言:javascript复制<import src="post-item/post-item-tpl.wxml" />
<view>
<swiper indicator-dots="true" autoplay="true" interval="5000" circular="true">
<swiper-item>
<image src="/images/post/post-1@text.jpg" />
</swiper-item>
<swiper-item>
<image src="/images/post/post-2@text.jpg" />
</swiper-item>
<swiper-item>
<image src="/images/post/post-3@text.jpg" />
</swiper-item>
</swiper>
<block wx:for="{{postList}}" wx:for-index="idx" wx:for-item="item">
<template is="postItemTpl" data="{{item}}" />
</block>
</view>
可以看到,文章列表是可以正常地加载显示,在 post.wxml 的顶部使用 <import src="templatPath" />
来引用模板,对于 templatePath 路径,这里需要注意,去掉 wxml 文章拓展名也是可以的,但官方示例中是带有 .wxml 扩展名的,所以建议开发者戴上模板文件的扩展名
引用模板后,就可以在这个页面中使用这个模板了,在需要模板的位置使用 <template>
标签引入,template 的 is 属性指定要使用哪个模板,通过 template 的 data 属性,可以向 template 传递数据,这里将 wx:for 得到的 item 传入到 template 里,这样就可以在 template 内部使用这个 item 了,这里需要注意的是,向模板里传入数据,同样要使用 {{}} 的数据绑定语法
消除 template 模板对外部变量名的依赖
在上一小节中,我们已经成功地将 wxml 代码做成了模板,同样的,我们也把 post.wxss 中同文章相关的样式作为模板“打包”起来,并在 post.wxss 文件的顶部添加如下代码:
代码语言:javascript复制@import "post-item/post-item-tpl.wxss";
我们之前讲过,列表渲染中 wx:for-item 可以指定数组子元素的变量名,那我们现在尝试将 wx:for-item = "item"
改成 wx:for-item = "item1"
,这将使得 postList 数组子元素的变量名由 item 变成 item1,此时,如果要将数据传入到 postItemTpl 中,则应该设置 data = "{{item1}}"
我们做完以上变更后,却发现文章数据将消失了,而之前代码可以正常运行是因为我们向 template 传入的变量名 data = "{{item}}"
,恰好和 template 里面数据绑定的变量名 item 一样,一旦我们更改 item 为 item1 后,template 就找不到这个 item 了
template 模板并不像函数,没有提供一个定义参数名的地方,没有办法更改从外部传入的 item1 为 item,我们当然可以通过将 postItemTpl 这个 template 内部的 item 更改为 item1 来让代码重新正常运行,但这种由定义方要求调用方遵守变量名命名的做法是不太合理的,要解决这个问题,就必须消除 template 对于外部变量名的依赖,可以使用拓展运算符 ...
展开传入对象变量来消除这个问题
<block wx:for="{{postList}}" wx:for-index="idx" wx:for-item="item">
<template is="postItemTpl" data="{{...item}}" />
</block>
接着去掉 post-item-tpl.wxml 文件中 {{}} 里所有的 item,保存运行,文章列表就可以正常显示了,{{...item}}
可以将 item 这个对象展开,展开之后再传入到 template 里,就可以保证 template 不再依赖 item 这个变量名
include 引用模板
小程序还提供了另一种引入模板的方式:include,include 在使用上同 import 有以下区别:
- import 需要先引入 template,然后再使用 template;但 include 不需要预先引入,直接在需要的地方引入模板即可
- include 模式非常简单,就是简单的代码替换,不存在作用域,也不能像 import 一样使用 data 传递变量
<block wx:for="{{postList}}" wx:for-index="idx" wx:for-item="item">
<include src="post-item/post-item-tpl.wxml" />
</block>
以上代码将 block 标签中的 template 更换成了 <include>
,include 同样使用 src 属性指向模板文件,需要注意的是,include 无法引入包含有 template 标签的代码,所以需要把 post-item-tpl.wxml 里的 template 标签删除,只保留文章本身的 wxml 代码,而文章数据部分,还需要再次在 post-item-tpl.wxml 中的 {{}} 加入 item 这个变量名,开发者可自行尝试
除了在使用上有所不同,include 和 import 还存在其他不同之处:import 有作用域的概念,即只会 import 目标文件中定义的 template,而不会 import 目标文件 import 的 template
如:C import B,B import A,在 C 中可以使用 B 定义的 template,在 B 中可以使用 A 定义的 template,但是 C 不能使用 A 定义的template
所以在这里建议大家,如果模板仅仅是静态 wxml,不涉及数据的传递,可以使用 include,但如果模板涉及数据绑定,还是建议使用 import
缓存
之前我们已经将文章相关数据分离到了 data.js 文件中,并在 post.js 文件里通过 require 来加载 data.js 文件,但现在有一个问题,如果我们要修改数据怎么办?修改后的数据,还想共享给其他页面使用,并长期保存这些数据怎么办?
小程序提供了一个缓存的特性,来支持数据的读取、保存和更新,并且这些数据不会因为应用程序重启或者关闭而消失
根据页面生命周期,将初始化数据装载到缓存的最好时期,应该是在小程序启动时,即执行 onLaunch 生命周期函数时,所以我们在 app.js 中加入以下代码:
代码语言:javascript复制var dataObj = require("data/data.js")
App({
onLaunch:function(){
wx.setStorage({
key: 'postList',
data: dataObj.postList,
success: function(res){
//success
},
fail:function(){
//fail
},
complete:function(){
//complete
}
})
},
})
在上面代码中,首先通过 require 加载 data.js 文件作为初始化数据,在应用程序生命周期函数 onLaunch 里,使用 wx.setStorage 方法将初始化数据存入到小程序的缓存中
缓存使得小程序具备了本地存储数据的能力,它具有以下几个特点:
- 只要用户不主动清除缓存,则缓存一直存在
- 缓存以 key:value 键值对的形式存在,很类似于服务器流行的 memcache 或者 redis 缓存型数据库
- 小程序提供了一系列 API 用来操作缓存,包括:存储、读取、移除、清除全部或获取缓存信息,每种操作同时都具有同步和异步两个方式
- 删除某一个 key 的缓存,请使用 wx.removeStorage 方法;而如果想清除所有的缓存请使用 wx.clearStorage 方法
- 小程序的缓存永久存在,不存在过期时间这个概念,如果想清除缓存,则需要主动调用清除缓存的 API
- 小程序的本地缓存有容量上限,最大不允许超过 10 MB
wx.setStorage(object) 是一个异步方法,参数 object 包含 key,data 和 success、fail、complete 这 3 个通用方法(几乎所有小程序的异步 API 方法中都包含这 3 个方法),key 用来设置缓存的键,而 data 用来设置缓存的值,可以是 JavaScript 对象或者字符串
我们可以通过 Storage 面板来查看具体的变化
图中 postList 就是在代码中设置的 key:'postList'
,后面的 Array 数组就是设置的 data 对象,也就是要初始化的数据,对应的是 data.js 文件的 3 篇文章数据,Storage 面板是查看缓存的重要功能,当开发者遇到与缓存相关的问题时,请一定要到这里来看一看
同步设置缓存
同步方法 wx.setStorageSync 是在异步方法名 wx.setStorage 后加了一个后缀 “Sync”,不仅仅是 setStorage,小程序中几乎所有同步方法的方法名都是在异步方法名后增加了 “Sync”,而同步方法只接收 key 和 data 这 2 个参数,并没有 success、fail、complete 等回调方法
我们这里采取的是同步方法,开发者可以根据自己的业务和环境选取异步方法,但需要注意的是,选取异步方法会大大增加代码风险和调试难度,如果没有必要(通常是处于性能和体验的考虑),建议优先考虑同步方法
代码语言:javascript复制App({
onLaunch:function(){
var storageData = wx.getStorageSync('postList');
if(!storageData){
//如果postList缓存不存在
var dataObj = require("data/data.js")
wx.clearStorageSync();
wx.setStorageSync('postList',dataObj.postList);
}
},
})
我们还在这里增加了一个缓存是否存在的判断,wx.setStorageSync(key)
这个方法可以获取指定key
的缓存内容,如果key
的缓存不存在,则说明数据库还没有初始化,那么此时首先使用wx.clearStorageSync()
清除所有的缓存数据,接着再重新读取并设置初始化数据
以上代码优化了初始化缓存数据库的方案,只有当缓存数据库不存在时,才通过require
加载data.js
文件,并初始化数据库,这样可以避免每次启动应用程序都重复初始化数据库
该章节的内容到这里就全部结束了,源码我已经发到了 GitHub WeChat_04 上了,有需要的同学可自行下载