02 小程序入门实战
技术交流QQ群:1027579432,欢迎你的加入!
欢迎关注我的微信公众号:CurryCoder的程序人生
1.创建项目和目录文件结构
小程序包含一个描述整体程序的app和多个描述各自页面的page。
一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:
一个小程序页面由四个文件组成,如下图所示:
注意:为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名。
由于一个小程序可能有多个页面,所以在根目录下创建pages文件夹。假设小程序仅有一个详情页面about,因此为了便于管理在pages文件夹下创建about文件夹。里面新建4个与小程序页面相关的文件:about.js、about.json、about.wxml、about.wxss。同时,在根目录下创建小程序全局相关文件app.js、app.json、app.wxss。
注意:app.json和about.json配置文件不能什么也不写,否则会报错。因此,需要写入如下内容:
// app.json文件内容,路径必须写相对路径 { "pages": ["pages/about/about"] // 配置小程序需要访问的所有页面路径 } // about.json文件内容 { navigationBarTitleText": "关于" }
接着,在app.js文件中,最简单的方法是注册一个空的页面对象。添加如下内容:
Page({})
2.view、text、image组件(元素)
所有的wxml组件元素都是既有开始标记又有结束标记。都是双标签!
view组件元素相当于HTML中的div元素。
<view class="container"> <image src="../../images/curry.jpg" class="about-banner"></image> <text>电影周周看</text> <text>我每周推荐一部好电影</text> <text>我的微博:weibo.com/CurryCoder</text> </view>
3.快速实现基本布局
传统方式:相关的wxss属性赋值非常分散。
/* 传统方式 */ .container { background-color: #eee; height: 100vh; text-align: center; } text { display: block; } image, text { margin-bottom: 60px; }
弹性布局方式:相关的wxss属性赋值比较统一,方式灵活。
/* flex弹性布局方式 */ .container { background-color: #eee; height: 100vh; display: flex; /* 修改主轴 */ flex-direction: column; justify-content: space-around; align-items: center; }
4.响应式长度单位rpx
- 问题的引入:如何让元素大小适配不同宽度屏幕?使用px这种绝对长度单位无法始终保持图片宽度和高度为屏幕的一半。
- 小程序规定所有设备的屏幕宽高均为750rpx。
.about-banner { width: 375rpx; height: 375rpx; }
- iPhone6的屏幕尺寸为375px*667px,而小程序规定所有设备的屏幕的宽高均为750rpx。因此,两者宽度的关系刚好是1:2关系(即px:rpx=1:2),便于换算。
/* 例如,在视觉设计稿中规定margin-left属性设置为100px,则在css中可以写成200rpx */ .about-banner { width: 375rpx; height: 375rpx; margin-left: 200rpx; }
5.新增每周推荐页面weekly
- 与详情页about类似,在pages目录下创建weekly文件夹。其中,创建weekly.js、weekly.wxss、weekly.wxml、weekly.json这四个文件。
- 每一个用户可能访问到的页面,都需要在全局配置中来登记它的访问路径。在全局配置文件app.json中写入如下内容。注意:要将weekly页面的路径写在最前面,这样小程序会最先加载它!
{ "pages": ["pages/weekly/weekly", "pages/about/about"] }
- 在weekly.js文件中,写入最简单的注册空的页面对象。
Page({})
- 在视图文件weekly.wxml中写入如下代码:
<view> <text>本周推荐</text> <image src="../../images/curry.jpg"></image> <text>库里</text> <text>点评:最牛逼的三分投手,最萌的库日天。</text> </view>
- 在布局文件weekly.wxss中写入如下内容:
<view class="container"> <text>本周推荐</text> <image src="../../images/curry.jpg"></image> <text>库里</text> <text>点评:最牛逼的三分投手,最萌的库日天。</text> </view>
- 我们发现页面布局在weekly页面并没有生效,所以,可以将weekly和about页面***同的样式放入全局样式文件app.wxss中。如下所示:
/* flex弹性布局方式 */ .container { background-color: #eee; height: 100vh; display: flex; /* 修改主轴方向 */ flex-direction: column; justify-content: space-around; align-items: center; }
- 在weekly.json文件中,写入如下内容,设置页面的标题。
{ "navigationBarTitleText": "人物介绍" }
6.使用navigator组件实现从about页面跳转到weekly页面
- 通过将about页面的路径作为第一个路径页登记,这样就恢复成了小程序的初始页面。
{ "pages": [ "pages/about/about", "pages/weekly/weekly" ] }
- 修改about.wxml文件中的内容,增加navigator组件(行内元素)。text组件中只能包含文本信息,为了使显示的信息在同一行中显示,需要用view组件包含。
<view class="container"> <image src="../../images/curry.jpg" class="about-banner"></image> <text style="font-weight: blod; font-size: 40rpx">NBA人物周周看</text> <view> <navigator url="/pages/weekly/weekly" style="display: inline;" open-type="redirect" hover-class="nav-hover" class="nav-default">每周推荐</navigator> <text>一个NBA球员</text> </view> <text>我的微博:weibo.com/CurryCoder</text> </view>
- navigator组件中有一个重要的属性open-type。默认值是navigate,当设置为redirect时,表示一般页面发生跳转后,无法点击页面左上角的返回按钮进行返回。
- navigator组件中有一个重要的点击态属性hover-class。当鼠标点击时,会出现鼠标点击后的效果。在about.wxss文件中加入样式。
.nav-hover { color: red; }
- 使用hover-class时的注意点:当鼠标点击导航链接前,就已经给导航链接设置初始样式为蓝色。当鼠标点击时,会发现导航链接并没有变为红色。这是因为点击导航链接时,会将nav-hover赋值为class属性。而在css中,权重值相同时,会发生覆盖现象(就近原则)。因此,在css中需要将.nav-hover的样式写在.nav-default之后即可解决上面的问题。
.nav-default { color: blue; }
7.配置tabBar[标签栏]——对若干一级页面的入口链接
- 很多时候,对于about和weekly这样的一阶页,我们希望实现的是在它们之间的快速的任意切换。
- 在全局配置文件app.json中配置tabBar属性,如下所示:
{ "pages": [ "pages/about/about", "pages/weekly/weekly" ], "tabBar": { "list": [ { "text": "每周推荐", "pagePath": "pages/weekly/weekly", "iconPath": "images/icons/weekly.png", "selectedIconPath": "images/icons/weekly-selected.png" }, { "text": "关于", "pagePath": "pages/about/about", "iconPath": "images/icons/index.png", "selectedIconPath": "images/icons/index-selected.png" } ], "color": "#000", "selectedColor": "#00f" } }
- 新的问题:点击导航链接时,页面无法实现跳转。原因是:全局配置文件app.json中的list参数已经加载了pagePath,但是tabBar还增加了标签栏。因此,我们在进行鼠标点击时,不能仅仅考虑页面的跳转,还要考虑tabBar标签栏的跳转。因此,需要在about.wxml文件中,修改navigator组件中的open-type为switchTab。
<view class="container"> <image src="../../images/curry.jpg" class="about-banner"></image> <text style="font-weight: blod; font-size: 40rpx">NBA人物周周看</text> <view> <navigator url="/pages/weekly/weekly" style="display: inline;" open-type="switchTab" hover-class="nav-hover" class="nav-default">每周推荐</navigator> <text>一个NBA球员</text> </view> <text>我的微博:weibo.com/CurryCoder</text> </view>
8.配置全局的顶部导航栏样式
- 原先的about页面顶部导航栏默认是黑色背景,黑字。可以在about.json文件中修改为黑字,白色背景。
{ "navigationBarTitleText": "关于", "navigationBarBackgroundColor": "#fff", "navigationBarTextStyle": "black" }
- 同时,为了使weekly页面的顶部导航栏样式与about页面顶部导航栏样式一样,因此,可以复制上面的代码到weekly.json文件中。
{ "navigationBarTitleText": "人物介绍", "navigationBarBackgroundColor": "#fff", "navigationBarTextStyle": "black" }
- 但是,以上代码直接复制的方法,必然会带来代码升级时的问题。因此,必须在全局配置文件app.json文件中写入window属性进行设置。
{ "pages": [ "pages/about/about", "pages/weekly/weekly" ], "tabBar": { "list": [ { "text": "每周推荐", "pagePath": "pages/weekly/weekly", "iconPath": "images/icons/weekly.png", "selectedIconPath": "images/icons/weekly-selected.png" }, { "text": "关于", "pagePath": "pages/about/about", "iconPath": "images/icons/index.png", "selectedIconPath": "images/icons/index-selected.png" } ], "color": "#000", "selectedColor": "#00f" }, "window": { "navigationBarBackgroundColor": "#fff", "navigationBarTextStyle": "black", "navigationBarTitleText": "NBA人物周周看" # 某个页面默认的顶部导航栏标题,如果某个页面没有单独设置此属性,就用全局配置文件中的 } }
9.数据绑定
9.1 基于DOM API手动更新视图的传统方法
- 在之前讲到的about.wxml和weekly.wxml视图文件中,相关的数据都是进行直接硬编码。直接硬编码对一些数据不变频繁发生变化的页面(如about页面)是合适的。但是,对于像weekly页面中大量的数据是频繁变化的需要是不合适的。
- 直接硬编码weekly页面中的数据,在weekly.wxml文件是无法提前获知的。需要在小程序运行的过程中,动态的从服务器端去获取,然后再渲染输出到这个weekly.wxml视图中进行显示。
- 传统方式解决上述直接硬编码问题:一般来说,会在页面加载的过程中,通过一个Ajax调用来请求server返回本周推荐球员的详细信息。假设客户端收到了服务端返回的一个javascript对象,如下所示:
var thisWeekMoive = { name: "Stephen Curry", comment: "最牛逼的三分投手,最萌的库日天", imagePath: "/images/fd.jpg" }
- 接下来,通过如下的赋值语句,将javascript对象中的name、comment、imagePath作为id为t001的text元素的内容进行设置。
document.getElementById("t001").innerHTML = thisWeekMovie.name;
- 传统方法的缺点:
- 传统方法需要我们在收到数据后,或者说每次这个数据被更新的时候,我们都需要执行上面的一段代码来对这个视图进行更新。
- 其次,上面这样一段代码它的维护是比较困难的。更新视图中数据的代码与页面代码紧耦合,不利于后期页面的重新布局。
9.2 数据绑定(data binding)
- 我们应该有一种机制能够让视图中的每一个部分与对应的数据做一个映射,即数据绑定。
- 在小程序框架中,每个页面所需要的数据,都是集中在这个页面所注册的页面对象PageObject中所定义的。因此,我们打开weekly这个页面所注册的页面对象所在的文件weekly.js。写入如下内容:
Page({ data: { thisWeekMoive: { name: "Stephen Curry", comment: "最牛逼的三分投手,最萌的库日天", imagePath: "/images/fd.jpg" }, count: 123 } })
- 接下来要把count的值渲染输出到weekly.wxml视图上显示,我们通过一个双大括号进行基本的数据绑定。
<view class="container"> <text>本周推荐</text> <image src="../../images/fd.jpg"></image> <text>库里</text> <text>点评:最牛逼的三分投手,最萌的库日天。</text> <text>{{count}}</text> // 数据绑定 </view>
- 我们还可以对内部状态变量进行一些运算或者说对它们进行一些组合来进行输出显示。在weekly.js中再定义一个内部状态数据score。如下所示:
Page({ data: { thisWeekMovie: { name: "Stephen Curry", comment: "最牛逼的三分投手,最萌的库日天", imagePath: "/images/fd.jpg" }, count: 123, score: 98 } })
- 接下来要把count+score的值渲染输出到weekly.wxml视图上显示,说明内部状态变量可以进行一些运算或组合后再进行输出。如下所示:
<view class="container"> <text>本周推荐</text> <image src="../../images/fd.jpg"></image> <text>库里</text> <text>点评:最牛逼的三分投手,最萌的库日天。</text> <text>{{count}}</text> <text>{{count + score}}</text> // 内部状态变量进行一些运算 </view>
- 可以利用条件表达式对score内部状态变量进行绑定,判断出一个球员是否为优秀、良好、中等、及格、差,并将其渲染在视图中。
<view class="container"> <text>本周推荐</text> <image src="../../images/fd.jpg"></image> <text>库里</text> <text>点评:最牛逼的三分投手,最萌的库日天。</text> <text>{{count}}</text> <text>{{count + score}}</text> <text>{{(score >= 90)? "优秀": "其他"}}</text> </view>
- 接下来,对原始weekly.wxml视图中的硬编码进行修改,利用thisWeekMovie内部状态变量进行绑定。如下所示:
<view class="container"> <text>本周推荐</text> <image src="{{thisWeekMovie.imagePath}}"></image> <text>{{thisWeekMovie.name}}</text> <text>点评:{{thisWeekMovie.comment}}</text> <text>{{count}}</text> <text>{{count + score}}</text> <text>{{(score >= 90)? "优秀": "其他"}}</text> </view>
- 最后,可以在调试器窗口中的AppData中查看与修改每个页面所包含的内部状态变量值与状态。
10.小程序运行环境与基本架构
- 通过开发者工具调试器的AppData这个type,我们可以实时的调试每个页面的所有内部状态变量的取值。
- 运行环境(宿主环境):每个小程序都是运行在它所在的微信客户端上的,通过微信客户端给它提供运行环境,小程序可以直接获取微信客户端原生的体验和能力。
- 视图层(渲染层)和逻辑层
- 各自的描述语言:wxml视图文件和wxss样式文件都是对渲染层的描述;脚本文件js都是对页面的逻辑层的描述。
- 各自的运行进程:逻辑层的javascript代码是统一运行在JsCore进程中;渲染层的每一个页面都是在一个独立的Webview进程中进行渲染。例如:about页和weekly页,都内置了一个webviewId的内部状态变量,来记录它们各自是在几号Webview进程当中进行渲染的。
- 基于数据绑定和事件机制进行通讯
11.条件渲染
- wx:if:条件渲染——条件成立时组件才渲染生成。
- 我们现在想要做的一个改动就是希望在视图中,对于那些博主真正强烈推荐的NBA球员,我们要显示一个强烈推荐的红色标识。
Page({ data: { thisWeekMovie: { name: "Stephen Curry", comment: "最牛逼的三分投手,最萌的库日天。", imagePath: "/images/fd.jpg", isHighlyRecommended: true // 强烈推荐 }, count: 123, score: 98 } })
- 首先,把强烈推荐这个元素给定义出来。强烈推荐这个text元素,它的渲染生成的条件是:绑定到了thisWeekMovie.isHighlyRecommended属性上。当博主有对这个NBA球员标记为强烈推荐时,返回的这个属性的取值为true。那么这时候渲染生成条件成立,这个元素会被渲染生成;反正,则为false。那么这时候渲染生成条件不成立,这个元素不会被渲染生成。
<view class="container"> <text>本周推荐</text> <image src="{{thisWeekMovie.imagePath}}"></image> <text>{{thisWeekMovie.name}}</text> <text>点评:{{thisWeekMovie.comment}}</text> <text>{{count}}</text> <text>{{count + score}}</text> <text>{{(score >= 90)? "优秀": "其他"}}</text> <text wx:if="{{thisWeekMovie.isHighlyRecommended}}" style="font-size: 16px; color: red;">强烈推荐</text> </view>
- wx:if VS hidden属性(传统方法)
- 使用hidden属性时,这个元素始终要先被渲染生成,hidden属性只是控制了它的可见性而已。
- 对于元素可见性需要频繁切换的场景,不建议使用条件渲染。
<view class="container"> <text>本周推荐</text> <image src="{{thisWeekMovie.imagePath}}"></image> <text>{{thisWeekMovie.name}}</text> <text>点评:{{thisWeekMovie.comment}}</text> <text>{{count}}</text> <text>{{count + score}}</text> <text>{{(score >= 90)? "优秀": "其他"}}</text> <text hidden="{{!thisWeekMovie.isHighlyRecommended}}" style="font-size: 16px; color: red;">强烈推荐</text> </view>
12.列表渲染
wx:for 列表渲染——重复的渲染生成组件。利用列表渲染可以一次性在weekly页面中推荐多个NBA球员。首先修改weekly.js文件,增加多个球员的对象信息。
Page({ data: { weeklyMovieList: [{ name: "Stephen Curry", comment: "最牛逼的三分投手,最萌的库日天。", imagePath: "/images/curry1.jpg", isHighlyRecommended: true }, { name: "Harden", comment: "最牛逼的碰瓷手,最长的大胡子。", imagePath: "/images/harden1.jpg", isHighlyRecommended: true }, { name: "Durant", comment: "最牛逼的得分手,最强的死神。", imagePath: "/images/durant1.png", isHighlyRecommended: true }], count: 123, score: 98 } })
接着,对weekly.wxml视图文件重新编写,注意其中的wx:for列表渲染语法。当中,item和index属性是列表渲染wx:for自带的属性。
<view class="movie" wx:for="{{weeklyMovieList}}"> <image class="movie-image" src="{{ item.imagePath}}"></image> <view class="movie-details"> <text>第{{index+1}}周:{{item.name}}</text> <text>点评:{{item.comment}}</text> <text wx:if="{{item.isHighlyRecommended}}" style="font-size: 16px; color: red">强烈推荐</text> </view> </view>
最后,使用弹性盒子模型对页面视图进行样式布局。如下所示:
.movie { display: flex; } .movie-details { display: flex; flex-direction: column; width: 550rpx; } .movie-image { width: 200rpx; height: 200rpx; }
13.使用swipter组件
使用swipter组件——从列表展示变为幻灯片轮播展示。swipter元素用来表示一个滑动容器,常用于幻灯片轮播或轮播图效果。在设置幻灯片的宽高时,一般要在swipter元素上统一设置。
<view class=""> <swiper> <swiper-item class="movie" wx:for="{{weeklyMovieList}}" wx:key="key"> <image class="movie-image" src="{{ item.imagePath}}"></image> <view class="movie-details"> <text>第{{index+1}}周:{{item.name}}</text> <text>点评:{{item.comment}}</text> <text wx:if="{{item.isHighlyRecommended}}" style="font-size: 16px; color: red">强烈推荐</text> </view> </swiper-item> </swiper> </view>
幻灯片轮播的样式优化
<view class=""> <swiper class="movie-swiper" indicator-dots="{{true}}" previous-margin="50rpx" next-margin="50rpx"> <swiper-item class="movie" wx:for="{{weeklyMovieList}}" wx:key="key"> <view class="container movie-card"> <image class="movie-image" src="{{ item.imagePath}}"></image> <text>第{{index+1}}周:{{item.name}}</text> <text>点评:{{item.comment}}</text> <text wx:if="{{item.isHighlyRecommended}}" style="font-size: 16px; color: red">强烈推荐</text> </view> </swiper-item> </swiper> </view>
以下是weekly.wxss文件内容:
.movie { display: flex; } .movie-swiper { height: 90vh; } .movie-card { height: 100%; width: 100%; background: #eee; margin: 0 20rpx; } .movie-image { width: 200rpx; height: 200rpx; }
14.页面的生命周期函数
需求1:在swiper中如何默认切换到最后一页幻灯片?
<view class=""> <swiper class="movie-swiper" indicator-dots="{{true}}" previous-margin="50rpx" next-margin="50rpx" current="{{weeklyMovieList.length - 1}}"> <swiper-item class="movie" wx:for="{{weeklyMovieList}}" wx:key="key"> <view class="container movie-card"> <image class="movie-image" src="{{ item.imagePath}}"></image> <text>第{{index+1}}周:{{item.name}}</text> <text>点评:{{item.comment}}</text> <text wx:if="{{item.isHighlyRecommended}}" style="font-size: 16px; color: red">强烈推荐</text> <text bindtap="f0" wx:if="{{index < (weeklyMovieList.length - 1)}}" class="return-button">返回本周</text> </view> </swiper-item> </swiper> </view>
上面在weekly.ml文件中直接添加swiper元素的current属性,可以实现默认切换到最后一页幻灯片。但是,当实现需求2时无法给返回本周按钮绑定内部状态数据currentIndex。根本原因是:由于在小程序中逻辑层与渲染层是独立的,小程序并没有提供类似于DOM这样的API,让逻辑层的javascript对渲染层进行直接更新。因此,首先需要在渲染层建立对内部状态数据currentIndex的绑定。然后在逻辑层的javascript中通过对渲染层所绑定的内部状态数据更新,间接的对渲染层数据进行更新。所以,先要在weekly.js文件中定义内部状态数据currentIndex。
Page({ data: { weeklyMovieList: [{ name: "Stephen Curry", comment: "最牛逼的三分投手,最萌的库日天。", imagePath: "/images/curry1.jpg", isHighlyRecommended: true }, { name: "Harden", comment: "卡戴珊的最强对手,最稳的罚球与欧洲步。", imagePath: "/images/harden1.jpg", isHighlyRecommended: true }, { name: "Durant", comment: "最牛逼的得分手,最强的死神来啦。", imagePath: "/images/durant.jpg", isHighlyRecommended: true }], count: 123, score: 98, currentIndex: 0 }, onLoad: function(options) { this.setData({ currentIndex: this.data.weeklyMovieList.length - 1 }) } })
接着,在weekly.wxml文件中重新给current绑定一个内部状态数据currentIndex,如下所示:
<view class=""> <swiper class="movie-swiper" indicator-dots="{{true}}" previous-margin="50rpx" next-margin="50rpx" current="{{currentIndex}}"> <swiper-item class="movie" wx:for="{{weeklyMovieList}}" wx:key="key"> <view class="container movie-card"> <image class="movie-image" src="{{ item.imagePath}}"></image> <text>第{{index+1}}周:{{item.name}}</text> <text>点评:{{item.comment}}</text> <text wx:if="{{item.isHighlyRecommended}}" style="font-size: 16px; color: red">强烈推荐</text> <text bindtap="f0" wx:if="{{index < (currentIndex)}}" class="return-button">返回本周</text> </view> </swiper-item> </swiper> </view>
需求2:在非本周幻灯片页面添加“返回本周”按钮
.movie { display: flex; } .movie-swiper { height: 90vh; } .movie-card { height: 100%; width: 100%; background: #eee; margin: 0 20rpx; position: relative; } .movie-image { width: 200rpx; height: 200rpx; } .return-button { position: absolute; right: 0; top: 40px; font-size: 26rpx; font-style: italic; border: 1px solid blue; border-right: 0; border-radius: 10%; }
页面的生命周期及页面生命周期函数:onLoad(options)、onShow(options)、onReady(options)、onHide(options)、onUnload(options)
- 小程序注册完成后,加载页面,触发onLoad方法,一个页面只会调用一次;
- 页面载入后触发onShow方法,显示页面,每次打开页面都会调用一次;
- 首次显示页面,会触发onReady方法,渲染页面和样式,一个页面只会调用一次;
- 当小程序后台运行或跳转到其他页面时,触发onHide方法;
- 当小程序从后台进入前台运行或重新载入页面时,触发onShow方法;
- 当使用wx.readirectTo(OBJECT)或关闭当前页返回上一页wx.navigateBack(),触发onUnload
小程序的生命周期app.js
- 用户首次打开小程序,会触发onLauch(全局只触发一次);
- 小程序初始化完成后,触发onShow方法,监听小程序显示;
- 小程序从前台进入后台,触发onHide方法;
- 小程序从后台进入前台显示,触发onShow方法;
- 小程序后台运行一定时间,或系统资源占用过高,会被销毁;