Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

帮助你更好的开发小程序 #1

Open
zhou-di opened this issue May 24, 2020 · 0 comments
Open

帮助你更好的开发小程序 #1

zhou-di opened this issue May 24, 2020 · 0 comments

Comments

@zhou-di
Copy link
Owner

zhou-di commented May 24, 2020

前言

原生开发小程序有了两个项目,在原生开发小程序经验技巧方面有一些自己的总结,此篇文章做原创分享!

本文适合老手查看,新手请参阅官方文档,同步至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
    });
  }
};

例子

wx.Storage.setItem(
  {
    class_pid: that.data.class_pid,
    class_id: that.data.class_id,
    is_import: that.data.is_import,
    shop_type: 1
  },
  function() {
    console.log("Storage 已设置完毕!");
  }
);

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(sourceArray, extendObj, filterFn) {
  var getType = function(a) {
    var typeArray = Object.prototype.toString.call(a).split(" ");
    return typeArray[1].slice(0, -1);
  };
  var secondParamType = getType(arguments[1]);

  if (!getType(sourceArray) == "Array") {
    throw new Error("The first argument must be an array type");
  }
  if (secondParamType === "Object") {
    return sourceArray.forEach((v, index, sourceArray) => {
      for (var key in extendObj) {
        v[key] = extendObj[key];
      }
      typeof filterFn === "function" ? filterFn(v, index, sourceArray) : "";
    });
  } else if (secondParamType === "Function") {
    return sourceArray.forEach((v, index, sourceArray) => {
      arguments[1](v, index, sourceArray);
    });
  } else {
    return sourceArray;
  }
}
  • 使用实例
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`
        });
      }
    }
  }
});

更多

  • wxapp-rainbow 安利一下自己写的小程序组件库(偏功能性)

    • uploadImg 上传图片
    • carousel 轮播图
    • authorization 授权登录
    • loading 加载中

8. 自定义 headerBar

后续分享...

参考

生态圈

  • ColorUI 鲜亮的高饱和色彩,专注视觉的小程序组件库
  • taro 多端统一开发框架,支持用 React 的开发方式编写一次代码,生成能运行在微信小程序、H5 、 React Native 等的应用
  • uni-app 使用 Vue.js 开发跨平台应用的前端框架
  • 微信小程序开发资源汇总

来源 yanyue404/blog#47

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant