• 微信小程序(二)--逻辑层与界面层


    一.逻辑层与界面层分离
      小程序开发框架将我们需要完成的编码,划分成了两种类型的编码:逻辑编码(由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="200px;height:200px;background-color:red" bindtap="outerHandler">
    5     <view style="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 }
  • 相关阅读:
    linux内核中如何访问寄存器?
    uboot加载itb文件后提示"ERROR: new format image overwritten"如何处理?
    如何单独编译Linux内核源码中的驱动为可加载模块?
    openwrt如何打开linux内核的CONFIG_DEVMEM选项?
    openwrt的shell下如何访问寄存器的内容?
    linux系统错误码大全
    第 3 章 文本元素
    第 2 章 基本格式
    第 1 章 HTML5 概述
    第 20 章 项目实战--案例和关于[7]
  • 原文地址:https://www.cnblogs.com/skykuqi/p/11735404.html
Copyright © 2020-2023  润新知