Skip to content

Latest commit

 

History

History
570 lines (570 loc) · 21.9 KB

微信小程序--逻辑层与界面层.md

File metadata and controls

570 lines (570 loc) · 21.9 KB

一.逻辑层与界面层分离

小程序开发框架将我们需要完成的编码,划分成了两种类型的编码:逻辑编码(由JavaScript完成,业务数据供给界面事件处理),界面编码(页面结构WXML,页面样式WXSS,展示逻辑层的数据).

二.逻辑层的JavaScript

小程序开发中使用到的JavaScript同常规网页开发所使用的JavaScript有所差异,在小程序的开发之中,有很多的.js文件,他们都属于页面的逻辑层,我们知道程序一旦启动就会去执行app.js文件中的代码,我们可以使用console.log()方法来验证. 小程序不是运行在浏览器中,所以没有BOM和DOM对象,但是在小程序只用存在一些特有的全局成员:

 1 //app.js
 2 
 3 console.log("==============================");
 4 //自定义逻辑代码
 5 //1.小程序不是运行在浏览器中,所以没有BOM和DOM对象
 6 console.log(window); //在小程序中无法获取到BOM中定义的成员
 7 console.log(document); //无法获取到DOM中定义的成员
 8 //2.小程序的JS有一些额外的成员(全局)
 9 //App 方法:用于定义应用程序实例对象
10 //Page方法:用于定义页面对象
11 //getApp方法:用于获取全局应用程序对象,每个小程序都由唯一的应用程序实例
12 //getCurrentPages:用于获取当前页面的调用栈
13 //wx对象: 用来提供核心api
14 console.log(wx);
15 //3.小程序的JS是支持CommonJS规范(用于约定这些文件与文件之间是如何组织,相互之间协同的)的
16 //通过module 和 require来传递通用方法
17 const foo = require("./utils/foo.js");    
18 foo.say("world");
19 console.log("==============================");
20 var obj = {
21   onLaunch: function() {
22     // 展示本地存储能力
23     var logs = wx.getStorageSync('logs') || []
24     logs.unshift(Date.now())
25     wx.setStorageSync('logs', logs)
26 
27     // 登录
28     wx.login({
29       success: res => {
30         // 发送 res.code 到后台换取 openId, sessionKey, unionId
31       }
32     })
33     // 获取用户信息
34     wx.getSetting({
35       success: res => {
36         if (res.authSetting['scope.userInfo']) {
37           // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
38           wx.getUserInfo({
39             success: res => {
40               // 可以将 res 发送给后台解码出 unionId
41               this.globalData.userInfo = res.userInfo
42 
43               // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
44               // 所以此处加入 callback 以防止这种情况
45               if (this.userInfoReadyCallback) {
46                 this.userInfoReadyCallback(res)
47               }
48             }
49           })
50         }
51       }
52     })
53   },
54   globalData: {
55     userInfo: null
56   },
57   //自定义成员方法
58   foo: function() {
59       console.log("自定义成员方法");
60   }
61 };
62 //调用了一个APP方法(全局方法)
63 App(obj);
64 //调用App方法的作用是用来创建应用程序实例对象
65 //定义应用程序的生命周期事件
1 function say(msg){
2   console.log("hello " + msg);
3 }
4 
5 // 导出say()方法
6 module.exports = {
7   say: say
8 }
9 //在标准的COmmonJS规范中还支持exports.say 来导出,但是在小程序当中是不支持的,只支持module.exports
 1 //index.js
 2 //获取应用实例
 3 //完成页面的逻辑,包括功能的实现
 4 const app = getApp()
 5 
 6 Page({
 7   data: {
 8     motto: 'Hello World',
 9     userInfo: {},
10     hasUserInfo: false,
11     canIUse: wx.canIUse('button.open-type.getUserInfo')
12   },
13   //事件处理函数
14   bindViewTap: function() {
15     wx.navigateTo({
16       url: '../logs/logs'
17     })
18   },
19   onLoad: function () {
20     if (app.globalData.userInfo) {
21       this.setData({
22         userInfo: app.globalData.userInfo,
23         hasUserInfo: true
24       })
25     } else if (this.data.canIUse){
26       // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
27       // 所以此处加入 callback 以防止这种情况
28       app.userInfoReadyCallback = res => {
29         this.setData({
30           userInfo: res.userInfo,
31           hasUserInfo: true
32         })
33       }
34     } else {
35       // 在没有 open-type=getUserInfo 版本的兼容处理
36       wx.getUserInfo({
37         success: res => {
38           app.globalData.userInfo = res.userInfo
39           this.setData({
40             userInfo: res.userInfo,
41             hasUserInfo: true
42           })
43         }
44       })
45     }
46   },
47   getUserInfo: function(e) {
48     console.log(e)
49     app.globalData.userInfo = e.detail.userInfo
50     this.setData({
51       userInfo: e.detail.userInfo,
52       hasUserInfo: true
53     })
54   },
55   onShow: function () {
56     //在每次该界面唤醒时,获取当前页面的调用栈:
57     console.log(getCurrentPages());
58     console.log("当前调用栈中的页面数量: " + getCurrentPages());
59   }
60   
61 })
 1 // pages/demo/demo.js
 2 
 3 //获取当前小程序对象实例
 4 var app = getApp();
 5 Page({
 6 
 7   /**
 8    * 页面的初始数据
 9    */
10   data: {
11 
12   },
13 
14   /**
15    * 生命周期函数--监听页面加载
16    */
17   onLoad: function (options) {
18     app.foo();
19     
20   },
21 
22   /**
23    * 生命周期函数--监听页面初次渲染完成
24    */
25   onReady: function () {
26 
27   },
28 
29   /**
30    * 生命周期函数--监听页面显示
31    */
32   onShow: function () {
33     //在每次该界面唤醒时,获取当前页面的调用栈:
34     console.log(getCurrentPages());
35     console.log("当前调用栈中的页面数量: " + getCurrentPages());
36     console.log(getCurrentPages()[getCurrentPages().length - 1] == this);
37   },
38 
39   /**
40    * 生命周期函数--监听页面隐藏
41    */
42   onHide: function () {
43 
44   },
45 
46   /**
47    * 生命周期函数--监听页面卸载
48    */
49   onUnload: function () {
50 
51   },
52 
53   /**
54    * 页面相关事件处理函数--监听用户下拉动作
55    */
56   onPullDownRefresh: function () {
57 
58   },
59 
60   /**
61    * 页面上拉触底事件的处理函数
62    */
63   onReachBottom: function () {
64 
65   },
66 
67   /**
68    * 用户点击右上角分享
69    */
70   onShareAppMessage: function () {
71 
72   }
73 })

三.界面层--数据绑定

在程序的开发过程中,界面层提供一种模板机制,可以借助一种特殊的语法来完成数据的绑定,将动态的数据绑定到模板当中,最近将嵌入过后的数据显示到界面中,小程序中使用的是Mustache 语法(双大括号)语法:

1 <!--pages/demo/demo.wxml-->
 2 <!-- 框架最大的特点就是让我们开发者必须按照特定的方法来编写代码 -->
 3 <!-- 数据绑定
 4   1.数据在哪 (在当前页面对象Page的data属性中)
 5   2.绑定到哪里 (想绑定到哪就在哪里用Mustache 语法进行输出)
 6   在小程序中规定所有的数据绑定对在相应js文件的Page对象中的data属性中 -->
 7 <view class="container">
 8   <text>{{ message }}</text>
 9   <text>{{ person.name }} : {{ person.age }}</text>
10 </view>
 1 // pages/demo/demo.js
 2 
 3 Page({
 4   //为页面提供数据,界面和逻辑之间的桥梁
 5   data:{
 6       message:"hello world",
 7       person:{
 8         name: "张三",
 9         age:25
10       }
11   }
12 })

--数据绑定语法补充,除了在页面标签的内部我们会使用Mustache语法进行输出,在标签的内部,我们也可以使用Mustache语法:

 1 <!--pages/demo/demo.wxml-->
 2 <!-- 框架最大的特点就是让我们开发者必须按照特定的方法来编写代码 -->
 3 <!-- 数据绑定
 4   1.数据在哪 (在当前页面对象Page的data属性中)
 5   2.绑定到哪里 (想绑定到哪就在哪里用Mustache 语法进行输出)
 6   在小程序中规定所有的数据绑定对在相应js文件的Page对象中的data属性中 -->
 7 <view class="container">
 8   <text>{{ message }}</text>
 9   <text>{{ person.name }} : {{ person.age }}</text>
10   <!-- Mustache语法可以用在:
11       1. innerHTML 元素内存
12       2. 元素的属性上
13       3. 不能用在标签名和属性名上面(只能用在vlaue上而无法使用在key上) -->
14   <view class="world {{ viewClassName }}"></view>
15   <!-- 可以直接使用字面量和简单的逻辑运算符 -->
16   <text>{{ 'hello' }}</text>
17   <text>{{ 111 }}</text>
18   <text>{{ 111 + 222 }}</text>
19   <text>{{ 111 < 222 ? 'yes' : 'no' }}</text>
20   <text>{{ true }}</text>
21   <!-- 如我我们默认不添加checked属性,那么默认为false,但是如我们添加了checked属性,则不论我们指定的是什么,都将显示被勾选;
22     在JS中将所有有值的字符串都认为是true ,因此我们想要表达一个true 我们可以使用 Mustache 语法来解析字符串
23    -->
24   <checkbox checked="false">复选框1</checkbox>
25   <checkbox checked="{{ false }}">复选框2</checkbox>
26 </view>

四.列表渲染

在逻辑层中,很多的情况下都会是一个数组类型的数据(列表数据),那么此时在我们的界面层中就需要使用到列表渲染:

1   <!-- 如我我们默认不添加checked属性,那么默认为false,但是如我们添加了checked属性,则不论我们指定的是什么,都将显示被勾选;
 2     在JS中将所有有值的字符串都认为是true ,因此我们想要表达一个true 我们可以使用 Mustache 语法来解析字符串
 3    -->
 4   <checkbox checked="false">复选框1</checkbox>
 5   <checkbox checked="{{ false }}">复选框2</checkbox>
 6   <!-- 列表数组渲染(列表数据绑定) -->
 7   <view>
 8     <!-- <view class="task_1">
 9       <checkbox checked="{{ true }}"></checkbox>
10       <text>HTML</text>
11     </view>
12      <view class="task_2">
13       <checkbox checked="{{ false }}"></checkbox>
14       <text>JS</text>
15     </view>
16      <view class="task_3">
17       <checkbox checked="{{ false }}"></checkbox>
18       <text>CSS</text>
19     </view> -->
20     <!-- 循环遍历输出列表:
21         1.明确页面结构中的循环体(在这里就是 最外层的view),删除多余的内容,只留下一个循环体,在剩下的一个上加上wx:for的属性
22       属性值为我们需要进行遍历的数据源,数据源是一个数组
23         2.在循环体的内部(标签)使用item代表当前被遍历的元素
24         3.如果在我们的Page对象的data中存在一个全局的item属性,此时我们无法在内部获取到全局的item属性,如果想要获取到我们的
25       全局item属性,那么需要使用wx: for-item 属性在自定义循环体的指向 
26         4.如果我们想要在循环体中拿到当前的index,那么默认我们可以直接使用index变量({{ index }})来获取我们的索引值,如果和
27       我们的全局变量中途,那么则可以使用wx:for-index来指定索引别名
28         5.在官方之中.默认是支持循环嵌套的
29     -->
30     <view wx:for="{{ todos }}" wx:for-item="aaa" wx:for-index="bbb">
31       <text>{{ bbb + 1 }}:</text>
32       <checkbox checked="{{ aaa.complete }}"></checkbox>
33       <text>{{ item.name }} {{ aaa.name }}</text>
34     </view>
35     <!-- 打印 9*9 乘法表 -->
36     <view>
37       <view wx:for="{{ [1,2,3,4,5,6,7,8,9] }}" wx:for-item="i">
38         <view wx:for="{{ [1,2,3,4,5,6,7,8,9] }}" wx:for-item="j">
39           <!-- <view wx:if="{{ i <= j }}"> -->
40             <text>{{ i }} * {{ j }} = {{ i * j }}</text>
41           <!-- </view> -->
42         </view>
43       </view>
44     </view>
45   </view>

五.事件处理

1   <!-- 绑定事件
2     1.触碰事件(bindTap):相当于click点击事件,在当前控件下触碰和送开,移除则不会触发-->
3   <button bindtap="buttonTapHandle">点击</button>
1   buttonTapHandle: function(e){
2     console.log("点击按钮打印此条.");
3     // console.dir()将一个对象以树状的形式打印到控制台,便于我们调试复杂对象
4     console.dir(e);
5   }

六.事件冒泡

1   <!-- 事件冒泡 
2     1.我们需要知道手机点击的工作原理是在手机屏幕点击的点,垂直以里发射一条射线,这条射线会先碰到蓝色的块,但是蓝色的块被包裹来红色的块里,所以会同时触发两个事件(先触发内部事件,再触发外部事件).
3     2.在小程序之中阻止冒泡:绑定事件除了使用bindtap外,还能够使用catchtap,catchtap具有阻止冒泡的功能,并且绑定事件-->
4   <view style="width:200px;height:200px;background-color:red" bindtap="outerHandler">
5     <view style="width:100px;height:100px;background-color:blue" catchtap="innerHandler"> </view>
6   </view>
1   innerHandler: function(){
2     console.log("触发内部事件");
3   },
4   outerHandler: function(){
5     console.log("触发外部事件");
6   }

七.事件传参

1   <!-- 在网页的开发之中,经常会有多个组件去调用相同方法的情况,此时如果我们需要知道当前触发这个方法的是哪一个组件,通常我们呢可以通过this来获取当前对象,当然也可以通过传递一个参数来完成对于当前对象的判断.
2     但是在小程序之中,我们在指定事件属性的时候,只能去指定事件的名字,而无法像网页那样去指定方法的参数,因此我们在方法中定义的参数e所拿到的就是当前的事件对象,我们可以发现在target属性下,存在一个dataset属性,其记录了data属性信息,例如data-id,data-name这些自定义的属性,都是根据Dataset这个属性来获取的.
3     在小程序中事件处理函数中的this,表示的还是这个Page页面对象,如果我们在定义data属性的时候,使用了多个中横线,那么将自动将该中横线忽略,将后方的变量名转变为驼峰式命名法来展现
4     总结:
5       1.事件的基本使用: bind[xxx] catch[xxx] 
6       2.小程序中事件冒泡处理的机制: 使用catch[xxx]
7       3.如果需要给事件处理函数指定参数,只能通过dataset的这种方式-->
8   <button bindtap="handleWithParm" data-id="123" data-name="hello" data-hello-world="hello world">参数事件</button>
1   handleWithParm(e){
2     console.log(e);     //获取当前的点击元素
3     console.log(e.target.dataset);    //获取元素上所有data-[xxx]属性的集合
4   }

八.单向数据流

1   <!-- 单向数据流
2           在小程序开发之中,数据一定是从逻辑层获取而来的,而后将数据绑定到界面层,界面上则可以通过一些输入将数据同步回来.从而形成一个数据扭转的回环. 
3           1.bindinput事件:用户输入时触发-->
4   <view>
5     <input style="border:1px solid #000;" bindinput="inputHandler"></input>
6     <text>{{ inputMessage }}</text>
7   </view>
 1   inputHandler: function(e) {
 2     //e.detail.value 获取到当前输入的值
 3     console.log(e.detail.value);
 4     //小程序中的数据绑定,本质上是一次绑定,在绑定完成之后,并不会去监视数据的变化.如果想要得到此次的变化,那么就需要使用setData的方法进行改变
 5     //setData是用来改变data数据的,与直接赋值区别在于setData可以通知界面作出变化,而直接赋值的话没有办法实现这一点(早期的JS无法实现)
 6     this.setData({
 7       inputMessage: "您输入的内容: " + e.detail.value
 8     })
 9     console.log(this.data.message);
10   }

九.登录页面案例

 1 <!--pages/login/login.wxml-->
 2 <view class="container">
 3   <view>
 4     <input class="username" placeholder="请输入用户名" style="border:1px solid #000" 
 5     value="{{ username }}" bindinput="usernameChangeHandler"/>
 6     <input class="password" type="password" placeholder="请输入密码" style="border:1px solid #000" 
 7     value="{{ password }}" bindinput="passwordChangeHandler"/>
 8   </view>
 9   <view>
10     <button type="primary" bindtap="buttonLogin">登录</button>
11     <button type="default" bindtap="buttonRegister">注册 </button>
12   </view>
13 </view>
 1 // pages/login/login.js
 2 /* 实现登录界面
 3     1. 设计数据的结构(data属性)
 4     2. 将数据绑定到指定的元素上
 5  */
 6 Page({
 7   data: {
 8     username: "",
 9     password: ""
10   },
11   //用于处理登录按钮点击的事件
12   buttonLogin: function() {
13     console.log("执行登录事件");
14   },
15   //用于处理注册按钮点击的事件
16   buttonRegister: function() {
17     //1. 首先需要知道用户输入了什么
18     console.log(this.data.username + " " + this.data.password);
19     //2. 根据用户输入的值去判断
20     //3. 根据判断的结果做出响应
21     console.log("执行注册事件");
22   },
23   //用户输入用户名
24   usernameChangeHandler: function(e) {
25     this.setData({
26       username: e.detail.value
27     })
28   },
29   //用户输入密码
30   passwordChangeHandler: function(e) {
31     this.setData({
32       password: e.detail.value
33     })
34   }
35 })

十.抽象共同的事件处理函数

如我在我们的代码之中为每一个input标签都增加一个事件绑定函数,那么在我们的代码之中,将会存在很多的重复代码,为了避免这样的的情况,我们可以抽象共同的事件处理函数:

1 <!--pages/login/login.wxml-->
 2 <view class="container">
 3   <view>
 4     <input class="username" placeholder="请输入用户名" style="border:1px solid #000" 
 5     value="{{ username }}" bindinput="inputChangeHandler" data-name="username"/>
 6     <input class="password" type="password" placeholder="请输入密码" style="border:1px solid #000" 
 7     value="{{ password }}" bindinput="inputChangeHandler" data-name="password"/>
 8   </view>
 9   <view>
10     <button type="primary" bindtap="buttonLogin">登录</button>
11     <button type="default" bindtap="buttonRegister">注册 </button>
12   </view>
13 </view>
1 // pages/login/login.js
 2 /* 实现登录界面
 3     1. 设计数据的结构(data属性)
 4     2. 将数据绑定到指定的元素上
 5  */
 6 Page({
 7   data: {
 8     username: "",
 9     password: ""
10   },
11   //用于处理登录按钮点击的事件
12   buttonLogin: function() {
13     console.log(this.data.username + " " + this.data.password);
14     console.log("执行登录事件");
15   },
16   //用于处理注册按钮点击的事件
17   buttonRegister: function() {
18     //1. 首先需要知道用户输入了什么
19     console.log(this.data.username + " " + this.data.password);
20     //2. 根据用户输入的值去判断
21     //3. 根据判断的结果做出响应
22     console.log("执行注册事件");
23   },
24   //用户输入用户名
25   // usernameChangeHandler: function(e) {
26   //   this.setData({
27   //     username: e.detail.value
28   //   })
29   // },
30   //用户输入密码
31   // passwordChangeHandler: function(e) {
32   //   this.setData({
33   //     password: e.detail.value
34   //   }),
35   //   console.log(e);
36   // }
37   //抽象上述两个方法,完成事件
38   inputChangeHandler: function(e) {
39     var key = e.target.dataset.name;
40     var value = e.detail.value;
41     var changed = {};
42     changed[key] = value;
43     this.setData(changed);
44   }
45 })

--除此之外,我们还可以使用表单的方式来进行数据的接收,这样可以免除这一些列复杂的操作:

1     <view>
 2       <input class="username" placeholder="请输入用户名" style="border:1px solid #000" 
 3       value="{{ username }}"  name="username" />
 4       <input class="password" type="password" placeholder="请输入密码" style="border:1px solid #000" 
 5       value="{{ password }}"  name="password" />
 6     </view>
 7     <view>
 8       <button type="primary" form-type="submit">登录</button>
 9       <button type="default" form-type="submit" >注册 </button>
10     </view>
1 //使用表单提交的方式来完成数据接收
2 loginSubmit: function(e){
3   console.log(e.detail.value);
4 }

十一.条件渲染

 1   <!-- 条件渲染 -->
 2   <view>
 3     <button bindtap="showButtonHandler">点击切换面板展示</button>
 4     <text wx:if="{{ !show }}">
 5         面板中显示的内容
 6         面板中显示的内容
 7         面板中显示的内容
 8     </text>
 9     <!-- 如果想要将多个模块同时进行判断,可以使用block标签,block标签没有其他任何的作用,只是作为一个判断属性的载体,
10       在页面渲染过程中并没有实际的意义 -->
11     <block wx:if="{{ !show }}">
12       <text style="display:block;">面板中显示的内容1</text>
13       <text style="display:block;">面板中显示的内容2</text>
14       <text style="display:block;">面板中显示的内容3</text>
15     </block>
16     <!-- hidden只是默认需显示,但是依旧会被渲染,使用if的则不会被渲染,因此wx:if是惰性的,当条件为假的时候,是无渲染的
17       1.hidden:标签还是会工作,只是不显示在界面上
18       2.wx:if: 惰性,只有显示的时候才会进行局部的渲染 -->
19     <view hidden="{{ !show }}">
20       <text style="display:block;">面板中显示的内容4</text>
21       <text style="display:block;">面板中显示的内容5</text>
22       <text style="display:block;">面板中显示的内容6</text>
23     </view>
24   </view>

十二.界面层(WXSS vs CSS)

rpx(responsive pixel): 可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。即将所有设备都使用rpx为750设置为撑满屏幕宽度.

1 .demo {
2   width: 750rpx;
3   height: 200px;
4   background-color: red;
5 }

原文作者:灰色天空_graySky 原文链接:https://www.cnblogs.com/skykuqi/p/11735404.html