2019,幫助你更好的開發小程式

RainBow發表於2019-03-21

前言

原生開發小程式有了兩個專案,在原生開發小程式經驗技巧方面有一些自己的總結,此篇文章做原創分享!

本文適合老手檢視,新手請參閱官方文件,同步至github

1.釋出訂閱處理複雜邏輯

支援先訂閱後釋出,以及先發布後訂閱
  • 方法原始碼
var Event = (function() {
  var clientList = {},
    pub,
    sub,
    remove;

  var cached = {};

  sub = function(key, fn) {
    if (!clientList[key]) {
      clientList[key] = [];
    }
    // 使用快取執行的訂閱不用多次呼叫執行
    cached[key + "time"] == undefined ? clientList[key].push(fn) : "";
    if (cached[key] instanceof Array && cached[key].length > 0) {
      //說明有快取的 可以執行
      fn.apply(null, cached[key]);
      cached[key + "time"] = 1;
    }
  };
  pub = function() {
    var key = Array.prototype.shift.call(arguments),
      fns = clientList[key];
    if (!fns || fns.length === 0) {
      //初始預設快取
      cached[key] = Array.prototype.slice.call(arguments, 0);
      return false;
    }

    for (var i = 0, fn; (fn = fns[i++]); ) {
      // 再次釋出更新快取中的 data 引數
      cached[key + "time"] != undefined
        ? (cached[key] = Array.prototype.slice.call(arguments, 0))
        : "";
      fn.apply(this, arguments);
    }
  };
  remove = function(key, fn) {
    var fns = clientList[key];
    // 快取訂閱一併刪除
    var cachedFn = cached[key];
    if (!fns && !cachedFn) {
      return false;
    }
    if (!fn) {
      fns && (fns.length = 0);
      cachedFn && (cachedFn.length = 0);
    } else {
      if (cachedFn) {
        for (var m = cachedFn.length - 1; m >= 0; m--) {
          var _fn_temp = cachedFn[m];
          if (_fn_temp === fn) {
            cachedFn.splice(m, 1);
          }
        }
      }
      for (var n = fns.length - 1; n >= 0; n--) {
        var _fn = fns[n];
        if (_fn === fn) {
          fns.splice(n, 1);
        }
      }
    }
  };
  return {
    pub: pub,
    sub: sub,
    remove: remove
  };
})();
  • 全域性掛載使用
// app.js
App({
  onLaunch: function(e) {
    // 註冊 storage,這是第二條
    wx.Storage = Storage;
    // 註冊釋出訂閱模式
    wx.yue = Event;
  }
});
  • 使用例項
// 新增收貨地址頁面訂閱
 onLoad: function (options) {
        wx.yue.sub("addAddress", function (data) {
            y.setData({
                addAddress: data
            })
        })
 }
/**
 * 生命週期函式--監聽頁面隱藏
 */
 onHide: function () {
    // 取消多餘的事件訂閱
    wx.Storage.removeItem("addAddress");
},
 onUnload: function () {
    // 取消多餘的事件訂閱
    wx.yue.remove("addAddress");
}
// 傳遞地址頁面獲取好資料傳遞
wx.yue.pub("addAddress", data.info);
// 補充跳轉返回
注意:使用完成資料後要注意解除安裝,在頁面被關閉時操作

2.Storage

storage 管理封裝,用法和上面的一致,掛載在全域性物件上呼叫,使用介紹就不列了
const Storage = {
  //  第一個 key 引數可以省略,直接傳遞 obj 物件,支援 callback
  setItem: function (key, obj, callback) {
    const getType = function (a) {
      var typeArray = Object.prototype.toString.call(a).split(" ");
      return typeArray[1].slice(0, -1);
    }
    var firstParamType = getType(arguments[0]);
    if (firstParamType === 'Object') {
      var keyArrayLength = Object.keys(arguments[0]).length
      var index = 0;
      for (var keyName in arguments[0]) {
        index++;
        wx.setStorage({
          key: keyName,
          data: arguments[0][keyName],
          success: index == keyArrayLength ? arguments[1] : function () { }
        })
      }
    }
    if (firstParamType === 'String') {
      wx.setStorage({
        key: key,
        data: obj,
        success: callback || function () { }
      })
    }

  },
  getItem: function (key) {
    return wx.getStorageSync(key);
  },
  removeItem: function (key) {
    wx.removeStorage({
      key: key
    })
  }
}

3.filter 計算屬性

小程式也有計算屬性,你知道嗎?
// 檔名稱為 :filter.wxs
// 不支援es6,Date,Number
function filterOrderTitleName(status) {
  switch (status) {
    case "1":
      return "待支付";
    case "2":
      return "待配送";
    case "3":
      return "配送中";
    case "4":
      return "已完成";
  }
}
function filterPrice(str) {
  // 四捨五入 格式化數字
  // toFix(8440.55,1) => 8440.6
  var times = Math.pow(10, 2);
  var roundNum = Math.round(str * times) / times;
  return roundNum.toFixed(2);
}

module.exports = {
  filterOrderTitleName: filterOrderTitleName,
  filterPrice: filterPrice
};
  • 使用例項,過濾處理打折後的金額小數位數
// 當前檔名:shoppingCart.wxml
// wxs 檔案頂部匯入
<wxs src="../../filter/filter.wxs" module="filter"></wxs>
 <view class='offerPrice nowrap'>¥{{filter.filterPrice(item.plus*100*item.price/1000)}}
    <image class='youhuiBox' src="../../assets/youhuiBox.png">
        <view class='youhuiText'>會員{{item.dazhe}}折</view>
    </image>
 </view>

4.flex Style

分享我常使用的自定義的一套 flex 樣式,快速實現佈局
/* -------------------------------------------------------------flex------------------------------------------------------- */

.center {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* 單行水平垂直 */

.oneLineCenter {
  display: flex;
  display: -webkit-flex;
  justify-content: center;
  align-items: center;
}

/* 單行垂直居中,水平向左 */

.oneLineStart {
  display: flex;
  display: -webkit-flex;
  justify-content: flex-start;
  align-items: center;
}

/* 單行垂直居中,水平向右 */

.oneLineEnd {
  display: flex;
  display: -webkit-flex;
  justify-content: flex-end;
  align-items: center;
}

/* 單行垂直居中,水平保持間距 */

.oneLineAround {
  display: flex;
  display: -webkit-flex;
  justify-content: space-around;
  align-items: center;
}

/* 單行垂直居中,兩端對齊 */

.oneLineBetween {
  display: flex;
  display: -webkit-flex;
  justify-content: space-between;
  align-items: center;
}

/* 超過單行設定的最大寬度,允許換行顯示 */

.f-wrap {
  flex-wrap: wrap;
}

/* 多軸線方向,一般配合  wrap 使用 */

/* 寬度不足換行後,垂直方向靠上排列 */

.mulitLineStart {
  display: flex;
  display: -webkit-flex;
  flex-wrap: wrap;
  align-content: flex-start;
}

/* 寬度不足換行後,垂直方向居中排列 */

.mulitLineCenter {
  display: flex;
  display: -webkit-flex;
  flex-wrap: wrap;
  align-content: center;
}

/* 寬度不足換行後,垂直方向靠下排列 */

.mulitLineEnd {
  display: flex;
  display: -webkit-flex;
  flex-wrap: wrap;
  align-content: flex-end;
}

/* 寬度不足換行後,垂直方向上保持間隔排列 */

.mulitLineAround {
  display: flex;
  display: -webkit-flex;
  flex-wrap: wrap;
  align-content: space-around;
}

/* 寬度不足換行後,垂直方向上靠兩側最頂開始間隔排列 */

.mulitLineBetween {
  display: flex;
  display: -webkit-flex;
  flex-wrap: wrap;
  align-content: space-between;
}

/* 縱軸變主軸,垂直靠上,水平居中 */

.columnStart {
  display: flex;
  display: -webkit-flex;
  flex-direction: column;
  justify-content: flex-start;
  align-items: center;
}

/* 縱軸變主軸,垂直靠下,水平居中 */

.columnEnd {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  align-items: center;
}

/* 縱軸變主軸,垂直居中,水平居中 */

.columnCenter {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

/* 縱軸變主軸,垂直間隔排列,水平居中 */

.columnAround {
  display: flex;
  flex-direction: column;
  justify-content: space-around;
  align-items: center;
}

/* 縱軸變主軸,垂直上下兩側按間隔排列,水平居中 */

.columnBetween {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: center;
}
/* 縱軸變主軸,垂直上下兩側按間隔排列,水平靠左 */

.columnBetweenStart {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
}
/* 縱軸變主軸,垂直上下兩側按間隔排列,水平靠右 */

.columnBetweenEnd {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-end;
}

5.async await

使用runtime.js,使小程式支援 async await,拷貝檔案至專案目錄下。

  • 例項用法
const regeneratorRuntime = require("../../utils/runtime.js");
Page({
  shopCartInit() {
    var y = this;
    // 拿到商鋪位置資訊再去渲染購物計算當前的address符合不符合規定
    var showCartList = function() {
      // 顯示全域性的地址資訊
      var globalAddress = wx.Storage.getItem("globalAddress");
      if (globalAddress) {
        y.setData({
          globalAddress: globalAddress,
          addr_id: globalAddress.id
        });
        y.calculateDistance(
          qqmapsdk,
          globalAddress.latitude,
          globalAddress.longitude
        );
      } else {
        y.setData({
          globalAddress: {}
        });
      }
    };
    // await 等待獲取商鋪位置資訊
    async function getShopPosTionMsg() {
      await util.promiseRequest(api.merchant_addr, {}).then(res => {
        var data = res.data.response_data.lists[0];
        y.setData({
          shop_lat: data.latitude, // 商鋪緯度
          shop_lng: data.longitude, // 商鋪經度
          peiSongFanWei: data.scope // 配送範圍
        });
      });
    }

    async function initData() {
      await getShopPosTionMsg();
      await showCartList();
      util.closeLoading();
      y.setData({
        loading: false
      });
    }
    // 開始執行
    initData();
  }
});

6.addKey Api

使用自定義屬性的方法輔助完成業務邏輯,已釋出至 addKey
/**
 * 為陣列新增新的自定義鍵值以及過濾每個子項的方法
 *
 * @param {*} arr
 * @param {*} obj { isShow:false,isStar:false}
 * @param {*} filterFn
 * @returns
 */
function addKey(arr, obj, filterFn) {
  if (!Array.isArray(arr)) {
    throw new Error("第一個引數必須為陣列型別")
  }
  let temp = arr.forEach((v, index, arr) => {
    typeof filterFn === "function" ? filterFn(v, index) : "";
    for (var key in obj) {
      v[key] = obj[key];
    }
  });
  return temp;
}
  • 使用例項
util.addKey(data, { y_isCheck: false }, function(v) {
  v.dazhe = Number(v.plus);
});
this.setData({
  cartList: data
});

7. 元件化複用開發實踐

元件化解構專案元件,提高開發效率,可參照官方介紹起步 !

這裡介紹一個自定義的跑馬燈的輪播圖元件例項

(1) 第一步,檢視目錄結構劃分,主要為 Carousel 元件 在 index 頁面的使用

│   
├───component
│   └───Carousel
│           Carousel.js
│           Carousel.json
│           Carousel.wxml
│           Carousel.wxss
│           
├───filter
│       filter.wxs
│       
├───pages
│   └───index
│           index.js
│           index.json
│           index.wxml
│           index.wxss
│           
└───utils
        api.js
        runtime.js
        util.js

(2) 第二步我們分析看如何使用,設計 元件需要的 props

  • 資料項,必須 bannerList
  • 輪播圖的固定高度 swiperHeight
  • 自定義輪播按鈕小點,寬與高一致,圓形 dotWidthAndHeight
  • 輪播按鈕盒子距離頂部的高度 dotTop

最終在 index.wxml 的實際使用

<view class='Carousel'>
    <Carousel swiperHeight="260rpx" bannerList="{{bannerList}}" dotWidthAndHeight="12rpx" dotTop="-20rpx" ></Carousel>
</view>

(3).業務程式碼編寫

  • Carousel.json

開啟自定義元件模式

{
    "component": true
}
  • Carousel.wxml
<view class='Carousel_wrap'>
<!-- 圖片區 -->
    <swiper current="{{swiperCurrentIndex}}" circular="true" bindchange="swiperChange" indicator-dots="{{indicatorDots}}" autoplay="true" interval="5000" duration="1000" style="height:{{swiperHeight}}">
        <swiper-item wx:for="{{bannerList}}" bindtap="toHref" wx:key="{{index}}" bindtap='toHref' data-type="{{item.type}}" data-id="{{item.goods_id}}" data-content="{{item.content}}" data-link="{{item.link}}">
            <image src="{{item.img_url}}" class="slide-image" />
        </swiper-item>
    </swiper>
    <!-- 關聯按鈕 -->
    <view class="boxCell" style="top:{{dotTop}}">
        <block wx:for="{{bannerList.length > 1 ? bannerList:[]}}" wx:for-index="index" wx:key="{{item.banner}}">
            <view id="{{index}}" class="dot {{index === swiperCurrentIndex ? 'dot_active':''}}" style="width:{{dotWidthAndHeight}},height:{{dotWidthAndHeight}}" bindtap="selectCarouselByIndex" />
        </block>
    </view>
</view>
  • Carousel.js
Component({
    /**
     * 元件的屬性列表 必須
     */
    properties: {
        bannerList: { // 屬性名
            type: Array, // 型別(必填),目前接受的型別包括:String, Number, Boolean, Object, Array, null(表示任意型別)
            value: [], // 屬性初始值(可選),如果未指定則會根據型別選擇一個
            observer: function(newVal, oldVal) {} // 屬性被改變時執行的函式(可選),也可以寫成在methods段中定義的方法名字串, 如:'_propertyChange'
        },
        dotWidthAndHeight: String,
        swiperHeight: String, // swiper 高度
        dotTop: String // 小點距離頂部高度
    },

    /**
     * 元件的初始資料
     */
    data: {
        swiperCurrentIndex: 0,
        indicatorDots: false // 自定義輪播按鈕
    },
    /**
     * 元件的方法列表
     */
    methods: {

        swiperChange: function(e) {
            var source = e.detail.source;
            if (source === "autoplay" || source === "touch") {
                this.setData({
                    swiperCurrentIndex: e.detail.current
                })
            }
        },
        selectCarouselByIndex: function(e) {
            this.setData({
                swiperCurrentIndex: Number(e.currentTarget.id)
            })
        },
        // 輪播圖跳轉至內部頁面
        toHref(e) {
            const data = e.currentTarget.dataset;
            // type = 2,根據 goods_id 展示商品詳情
            // type = 3, 展示富文字的活動詳情頁面
            if (data.type === '2') {
                wx.navigateTo({
                    url: `../sort_detail/sort_detail?id=${data.id}`
                })
            } else if (data.type === '3') {
                wx.yue.pub("renderData", data.content)
                wx.navigateTo({
                    url: `../activity_detail/activity_detail`
                })
            }
        },
    }
})

8. 自定義 headerBar

後續分享...

參考

生態圈

  • ColorUI 鮮亮的高飽和色彩,專注視覺的小程式元件庫
  • taro 多端統一開發框架,支援用 React 的開發方式編寫一次程式碼,生成能執行在微信小程式、H5 、 React Native 等的應用
  • uni-app 使用 Vue.js 開發跨平臺應用的前端框架
  • 微信小程式開發資源彙總

相關文章