小程序实际开发

515 阅读9分钟

创建小程序初始页面

weui 库

我们这边直接github中找到weui的样式例子库可复制相应的组件源码

weui的样式源码 下载后往app.wxss中引入@import 'static/css/weui.wxss'; 直接可以用了

端口配置异步请求

  1. params 需要添加字符串参数 qs,执行安装命令 npm init -y , npm i qs -S 取qs导入util中。

    在这当中由于小程序开启了ES6转码导致页面使用async函数无法使用,故需要导入npm install regenerator-runtime --save devruntime.js到相应调用的页面中,具体参考微信小程序regeneratorRuntime is not defined解决方案

  2. util.js中

        const qs = require('../static/js/qs.js');
        let baseURL = "http://192.168.2.120:5000/api/"
        let request = function(url,options) {
          let myUrl = `${baseURL}${url}`;
          //处理method,data,params => 查询字符串
          //请求头
          //合并对象data
        
          if(options.data) {
            tempOptions.data = data;
          }
          //如果params,添加查询字符串参数
          if (options.params) {
            let urlQueryString = qs.stringify(options.params,{
              addQueryPrefix: true,
              allowDots:true,
            })
            myUrl += addQueryString;
          }
          return new Promise((resolve,reject) => {
            wx.showLoading({
              title: '玩命加载中...',
            })
            wx.request({
              method: 'get',
              url: myUrl,
              success:resolve,
              fail: reject,
              complete: wx.hideLoading,
              ...options
            })
          })
        }
        
        module.exports = {
          formatTime: formatTime,
          request: request
        }
    

    app.js中导入工具对象

        const { formaTime, request } = require('utils/util.js');
        globalData: { //向外提供属性
            userInfo: null,
            request,
            formaTime
          }
    

    编写好域名,直接页面调用,index.js

     // 获取app的值 getApp()
      const {request} = getApp().globalData;
      const regeneratorRuntime = require('../../static/js/runtime.js');
    
      onLoad: async function () {
          let result = await request('Profile/test',{
            method: 'get',
            // params: {}
          });
          console.log(result);
      },
    

    常见的功能开发

    链接跳转

    <navigator url="{{'/pages/list/list?id='+item.id}}" wx:for="{{swipe}}" wx:key="{{item.id}}">{{item.name}}</navigator>
    
    //链接跳转后获取
      onLoad: function (options) {
        let {id} = options;
        console.log(id);
      },
    

    获取整个view的数据

        bindtap="doSearchByQuery"
    

    for循环多层嵌套

        <!-- 内外层都是item 会优先内层 -->
        <view class="item" wx:for="{{list}}" wx:key="{{index}}" wx:for-item="Q">
           {{Q.title}}
           <view wx:for="{{Q.item_list}}" wx:key="{{index}}" data-index="{{index}}" data-item="{{item}}">
              {{item.classfiy}} 
           </view>
        </view>
    

    需要取值的话可以设置data-*系列去取到相应点击的值

    页面滚动监听

    onPageScroll(obj) {}
    

    tab实现切换展示不一样的数据分页

    需求: 切换不同的栏目,加载出不同的数据,而且还能分页

    解决: 定义出tab三个栏目不同的数据,一方面由于每个栏目都能分页,故定义出三组,每一组再拼接进请求到的数据。

    const types = ['pop', 'new', 'sell']
    //data
    goods: {
      pop: {page: 0, list: []},
      new: {page: 0, list: []},
      sell: {page: 0, list: []}
    },
    //数据请求(传入相应的数据)
    // 2.3.将数据设置到data中的goods中
    const page = this.data.goods[type].page + 1;
      const typeKey = `goods.${type}.list`;
      const pageKey = `goods.${type}.page`;
      this.setData({
        [typeKey]: oldList,
        [pageKey]: page
      })
    

    其二我们定义的tab切换为子组件,需要每次切换传值到给页面中,页面可以根据标识显示相应的数据(w-goods也是将图片文字封装成子组件)

    <w-goods goods="{{goods[currentType].list}}"/>
    

    需求: 固定住tab栏目(滑到相应的位置固定住)

    解决: 如果普通的滑到某个位置让tab栏目固定住,就会出现闪跳得情况,所以我们可以复制出两份,让他到一定位置的显示出已经固定住的一份,隐藏另一个

    <w-tab-control titles="{{titles}}"
               bind:tabclick="handleTabClick"
               id="tab-control"
               class="{{isTabFixed ? 'fixed': ''}}"/>
    <w-tab-control titles="{{titles}}" wx:if="{{isTabFixed}}"/>
    

    其二,我们需要获取到相应滚动到的高度,我们一开始加载完页面获取到高度会出现页面图片没加载完的情况,导致获取到的高度是少了图片的高度情况,那么我们可以监听图片的,等图片加载完再回调执行我们获取到高度

    //子组件
    <image src="{{item.image}}" bind:load="handleImageLoad"/>
    handleImageLoad() {
      if (!this.data.isLoad) {
        this.triggerEvent('imageload')
        this.data.isLoad = true
      }
    }
    //页面: 监听图片加载完回调有bindload
       handleImageLoad() {
        wx.createSelectorQuery().select('#tab-control').boundingClientRect(rect => {
          this.data.tabScrollTop = rect.top
        }).exec()
      },
      
      
    
    

    页面按钮跳转

    通过某些按钮或者函数调用而导致底部的按钮切换需要配置switchTab来实现的

       <navigator url="/pages/add/add" open-type="switchTab" class="item">添加</navigator>
    

    多项目管理方式

附: tag管理就是新建一个空的分支,每实现一个案例就打个tag,之后再回滚到空的分支初始页面,实现案例二

//上传
git add .
git commit -m '案例1'
git tag 案例1 
//回滚初始
git log //查看分支
git reset --hard 复制其中一段tag的哈希值
git status //初始
//查看目前几个tag
git tag
//上传
git push --tabs
//代码查看
git checkout 案例1

数据处理

循环 wx:for

<view wx:for="{{list}}">{{item.name}}</view>

点击 bindtap=''

vue动态每次改变数据都能更改界面数据,是通过object.defineProperty监听变化

小程序需要this.setData({ counter: 100 })

vue 跟小程序的对比

  1. M:Model ,V:View , VM:ViewMode
  2. 视图层和数据层通过ViewMode在 DataBing和DomListener上实现交互
    MINI框架了解,MVVM架构将我们从命令式编程转移到声明式编程,具体两种声明式编程和命令式编程的理解

命令式编程:操作DOM

声明式编程: vue/React

相关文件说明

project.config.json 是相对于本地设置而生成的文件,目的是在不同的编辑器开发中起到配置的一致性,不用多次配置

sitemap.json 设置为允许给微信小程序爬虫爬取我们开发的任何页面成为搜索引擎的主要依据,方便小程序搜索,提高曝光率

小程序的双线程模型

Dom树

渲染出页面

WXML -> Js对象 -> 真正DOM树 -> WebView界面(渲染线程渲染) -> 数据发生变化 -> 逻辑层提供变化数据 -> JS对象进行diff算法对比 -> 反映到真正DOM树 -> 更新UI

此处中都是虚拟DOM操作,最后只有真正的DOM才起渲染页面的作用,拓展:减少DOM请求

界面渲染过程(diff算法)

这边是如何进行比较前后两个修改的JS对象的呢,又如何应用到原来的DOM上-->Diff算法在此处的重要性

小程序启动

onShow(会执行多次)-场景值进入下程序不同的方式

获取用户信息 : 
第一种: wx.getUserInfo

<button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">第二种</button>

<open-data type="userNickName">第三种</open-data>
<open-data type="userGender">第三种</open-data>

样式权重

条件

wx:if -- wx:elif -- wx:eles

WXS

应用: 数字的转换,时间戳格式化 实践

<wxs module="info">
  var name = '小米'
  var age = '18'
  function sum(num1,num2) {
    return num1+num2;
  }
  module.exports = {
     name: name,
     age: age,
     sum: sum
  }
</wxs>
<view>{{info.name}}</view>
<view>{{info.age}}</view>
<view>{{info.sum(8,4)}}</view>

//一般情况下都是独立文件夹引入的(相对路径,记得module.exports抛出)
<wxs src="../../wxs/info.wxs" module="info" />

冒泡和捕获

bind: 一层层传递

catch: 阻止事件的进一步传递

<view catchtap="hand" capture-catch:tap="handview">
  <view bindtap="hand1" capture-bind:tap="handview1"></view>
</view>

创建自定义组件

目录设置一般可以这样,层次感清晰

引用的话在相应页面的json中

//json--此处可以使用绝对路径或是相对
"usingComponents": {
    "my-cpn":"/components/my-cpn/my-cpn" 
}
//wxml
<my-cpn></my-cpn> 
  • 如果自定义子组件相互引用也是支持的,写法雷同(用绝对路径实现更为方便点)
  • 如果多个页面都是需要用到子组件那么我们可以在app.json中直接引用就可以了,就不再需要再各个页面引用
//app.json
"usingComponents": {
      "my-cpn": "/components/my-cpn/my-cpn"
  },
  • 组件内的样式和外部样式是互不影响的,组件内不能使用id选择器,属性选择器,标签选择器
  • 如果想组件内的样式会被外部样式影响,那么我们可以在子组件JS中设置
//my-cpn不设置为隔离
Component({
  options: {
    styleIsolation:'apply-shared'
  },
})
// isolated/apply-shared/shared =>三种状态 隔离/子接受父影响 /互相伤害

命名规范

  1. WXML --- 小写字母,中划线,下划线
  2. 自定义组件和页面所在项目根目录名 不能以 'wx-'为前缀,否则报错

组件和页面通信

properties

子组件

<!--wxml-->
    <view >{{title}}</view>
<!--js-->
properties: {
     title: {
       type: String,
       value: '我是默认值',
       observer:function(newVal,oldVal) { //对新旧值的监听
         console.log(newVal,oldVal)
       }
     }
 },

页面

<!-- 自定义组件 -->
<my-cpn title='我是父子'></my-cpn>  
<my-cpn /> 
<my-cpn title='我也还能是这样'></my-cpn>  

监听properties/data 的数据变化=> observers: {}

externalClasses(有点绕,其实你只要理清楚样式隔离就行了,哪里的样式注定在哪里才生效)

子组件

<!--wxml-->
   <view class="titleclass">{{title}}</view>
<!--js-->
    externalClasses: ['titleclass'],

页面

<!-- wxml -->

  <my-cpn title='我是父子' titleclass="red"></my-cpn>  
  <my-cpn titleclass="green"/> 
  <my-cpn title='我也还能是这样'></my-cpn>   

<!--wxss-->

  .green {color: green}
  .red { color: red}

自定义事件传递数据

子传给父-triggerEvent

子组件

<!--wxml-->
     <button bind:tap="handclick">+1</button>
<!--js-->
     methods: {
       handclick() {
         this.triggerEvent('icrement', { name: '小米' }, {}) //传值需要triggerEvent
       }
     }

页面

<!-- wxml -->
   <view>点击了 {{count}}</view>
   <my-btn bind:icrement="icrement"/>  //写法方式切记如果你写成bindtap="icrement"是获取不到第二个的值
<!--js-->
    icrement(event) {
       console.log(event)
       this.setData({
         count: this.data.count + 1
       })
     },
父改变子,页面直接调用组件修改数据/方法,selectComponent获取到子组件的标签

子组件

<!--wxml-->
     <view>页面来改变我点击次数{{countnum}}</view>
<!--js-->
    data: {
       countnum: 0,
     },
     methods: {
       incrementCounter(num) {
         this.setData({
           countnum: this.data.countnum + num
         })
       }
     }

页面

<!-- wxml -->
   <my-slot id="self_id" class="self_class" />
   <button bind:tap="click_btn">我要改变子页面</button>
<!--js-->
   click_btn() {
       const my_self = this.selectComponent('.self_class')
        console.log(my_self)
        my_self.incrementCounter(20)
     },

slot 插槽

单插槽
 子组件
<!--wxml-->
     <view>我是头部</view>
       <slot />
     <view>我是底部</view>

页面

<!-- wxml -->
   <z-slot>
    <text>我是改变内容</text>
   </z-slot>
多插槽 (multipleSlots: true)
  子组件
<!--wxml-->
       <view>我是头部</view>
       <slot name="slot1"/>
       <view class="xx"><slot name="slot2"/></view>
       <slot name="slot3"/>
       <view>我是底部</view>
<!--js-->
   Component({
     options: {
       multipleSlots: true
     },
   })

页面

<!-- wxml -->
  <z-slot>
     <button slot="slot2" size="mini">我是插入第二个插槽</button>
     <view slot="slot1">我是插入第一个插槽</view>
   </z-slot>

子组件有两套生命周期

网络请求封装

wx.request()直接请求数据会出现 降低网络请求和wx.request的耦合度,使用pomise的方法获取回调。为什么要用Promise?

小程序登录

生成的code 只有五分钟有效期