小程序模板網(wǎng)

干貨!小程序開發(fā),你應(yīng)該知道的那些事兒:項目工程化與突破最大并發(fā)數(shù)5

發(fā)布時間:2017-12-08 17:14 所屬欄目:小程序開發(fā)教程

一直負(fù)責(zé)公司的一個小程序項目開發(fā),到目前為止,一期版本也算完成的差不多了,覺得也是時候從技術(shù)的角度對項目作一個小結(jié)了,記錄踩的一些坑和一些自己覺得的最佳實踐吧!關(guān)于項目工 ...

 
 
 

一直負(fù)責(zé)公司的一個小程序項目開發(fā),到目前為止,一期版本也算完成的差不多了,覺得也是時候從技術(shù)的角度對項目作一個小結(jié)了,記錄踩的一些坑和一些自己覺得的最佳實踐吧!

關(guān)于項目工程化

小程序運行時,會把所有的源代碼下載到本地。之后小程序每次運行就像App一樣,幾乎(除cgi數(shù)據(jù),網(wǎng)絡(luò)圖片)全是本地文件IO,而沒有網(wǎng)絡(luò)下載,這也是小程序快的主要原因之一。另外,小程序自帶了ES6編譯轉(zhuǎn)換,css3樣式補全,所以我們基本不需要做任何工程化的事情,因為我們根本不需要合并,打包。JS代碼規(guī)范,是我們所做的唯一與工程化相關(guān)的事了。以下是我們的eslint配置:

//.eslintrc.js
module.exports = {
  "env": {
    "browser": true,
    "node": true,
    "commonjs": true,
    "es6": true
  },
  "globals": {
    "App": true,
    "wx": true,
    "Page": true,
    "getApp": true,
    "getCurrentPages": true
  },
  "extends": "eslint:recommended",
  "parserOptions": {
    "sourceType": "module",
  },
  "rules": {
    // enable additional rules
    // 強制使用一致的縮進
    // "indent": ["error", 2],
    // 要求加上分號
    "semi": ["error", "always"],
    // override default options for rules from base configurations
    // 禁止在條件語句中出現(xiàn)賦值操作符
    "no-cond-assign": ["error", "always"],
    // disable rules from base configurations
    "no-console": "off",
    "no-debugger": 0,
  }
}

關(guān)于小程序開發(fā)IDE

在小程序官方提供的IDE中,編輯與調(diào)試在兩個Tab中,切換起來實在麻煩;另外小程序開發(fā)工具對Emmet (Zen Coding)不支持...;再加上習(xí)慣了自己的開發(fā)工具,要一下切到小程序開發(fā)工具上,真是不適應(yīng);所以我在開發(fā)時,小程序開發(fā)工具僅用于效果預(yù)覽與調(diào)試,而真正的代碼編輯還是使用了自己習(xí)慣的IDE,配合雙顯示器,開發(fā)體驗與開發(fā)H5基本一致。

如果不使用小程序開發(fā)工具做代碼編輯器,要讓.wxml、.wxss支持語法高亮,只需要將.wxml文件設(shè)置為html文件類型,而.wxss文件設(shè)置為css文件類型。由于不同編輯器設(shè)置文件類型的方法不一樣,google一下就知道了。

開發(fā)時,如何體驗小程序

先用管理員賬號上傳小程序,然后在管理平臺上指定此版本為體驗版,使用擁有體驗權(quán)限的微信號掃碼就可以體驗了。這里有注意:

  • 上傳代碼只有管理員權(quán)限才可以
  • 小程序開發(fā)者默認(rèn)沒有體驗權(quán)限,必須單獨申請

Api提示

把小程序api定義wx.d.ts放到項目目錄中,在編碼時,就會有很酷的代碼提示

在小程序中使用promise與突破wx.request最大并發(fā)數(shù)5的限制

從小程官方文檔:工具->細(xì)節(jié)點中,我們可以知道,Promise在ios9中不支持,那么我們使用promise時就需要polyfill。 關(guān)于wx.request最大并發(fā)數(shù)為5的限制問題在官網(wǎng)有提及(地址),但我測試時,沒有發(fā)現(xiàn)有這個限制,為了保險起見,我們還是做了相應(yīng)處理。

/**
 * utils/app.js
 * 1. 增加promise支持
 * 2. 突破wx.request最大并發(fā)數(shù)是5的限制
 */

import { Promise, } from "./promise";
import Helper from "./helper";

// 突破 request 的最大并發(fā)數(shù)是 5的限制
// refer https://mp.weixin.qq.com/debug/wxadoc/dev/api/network-request.html#wxrequestobject
let RequestMQ = {
  map: {},
  mq: [],
  running: [],
  MAX_REQUEST: 5,
  push(param) {
    param.t = +new Date();
    while ((this.mq.indexOf(param.t) > -1 || this.running.indexOf(param.t) > -1)) {
      param.t += Math.random() * 10 >> 0;
    }
    this.mq.push(param.t);
    this.map[param.t] = param;
  },
  next() {
    let me = this;

    if (this.mq.length === 0)
      return;

    if (this.running.length < this.MAX_REQUEST - 1) {
      let newone = this.mq.shift();
      let obj = this.map[newone];
      let oldComplete = obj.complete;
      obj.complete = (...args) => {
        me.running.splice(me.running.indexOf(obj.t), 1);
        delete me.map[obj.t];
        oldComplete && oldComplete.apply(obj, args);
        me.next();
      };
      this.running.push(obj.t);
      return wx.request_bak(obj);
    }
  },
  request(obj) {
    let me = this;

    obj = obj || {};
    obj = (typeof(obj) === "string") ? { url: obj, } : obj;


    this.push(obj);

    return this.next();
  },
};

function hackRequest() {
  wx["request_bak"] = wx["request"];
  Object.defineProperty(wx, "request", {
    get() {
      return (obj) => {
        obj = obj || {};
        obj = (typeof(obj) === "string") ? { url: obj, } : obj;
        return new Promise((resolve, reject) => {
          obj.success = resolve;
          obj.fail = (res) => {
            if (res && res.errMsg) {
              reject(new Error(res.errMsg));
            } else {
              reject(res);
            }
          };
          RequestMQ.request(obj);
        });
      };
    },
  });
}

// 增加promsie支持
function addPromise() {
  let noPromiseMethods = {
    stopRecord: true,
    pauseVoice: true,
    stopVoice: true,
    pauseBackgroundAudio: true,
    stopBackgroundAudio: true,
    showNavigationBarLoading: true,
    hideNavigationBarLoading: true,
    createAnimation: true,
    createContext: true,
    createCanvasContext: true,
    hideKeyboard: true,
    stopPullDownRefresh: true,
  };
  Object.keys(wx).forEach((key) => {
    if (!noPromiseMethods[key] && key.substr(0, 2) !== "on" && key !== "request" && !(/\w+Sync$/.test(key))) {
      wx[key + "_bak"] = wx[key];
      Object.defineProperty(wx, key, {
        get() {
          return (obj) => {
            obj = obj || {};
            //obj = (typeof(obj) === 'string') ? {url: obj} : obj;
            return new Promise((resolve, reject) => {
              obj.success = resolve;
              obj.fail = (res) => {
                if (res && res.errMsg) {
                  reject(new Error(res.errMsg));
                } else {
                  reject(res);
                }
              };
              wx[key + "_bak"](obj);
            });
          };
        },
      });
    }
  });
}

export default function createApp(config) {
  addPromise();
  hackRequest();

  let helper = Helper.$extend({}, Helper, {
    Promise,
  });
  return Helper.$extend({}, config, {
    helper,
  });
}
// app.js啟動小程序
import createApp from "./utils/app";

App(createApp({
  data: {},
  Events: {},
  onLaunch() {
    // console.log(wx.login());
    // Do something initial when launch.
  },
});

小程序在Android機上面拉取不到數(shù)據(jù),而在IOS上可以

遇到這個問題,多半是https版本或證書有問題,找后臺或運維解決。

使用weui-wxss,wept

weui-wxss 是官方提供的一些常用組件,可以根據(jù)情況是否使用。這里主要想說的是,從github下載weui-wxss源碼后,要使用dist作為小程序項目根目錄。在預(yù)覽組件效果時,來回切換項目十分麻煩,這時候我推薦 wept 這個瀏覽器環(huán)境的小程序運行工具來幫幫助我們預(yù)覽。

頁面樣式

頁面樣式,要使用Page這個元素元素器,則不是.page class選擇器。如:

/*所有頁面初始設(shè)置*/
page {
  color: #333;
  height: 100%;
  font-size: 28rpx;
  line-height: 1.5;
  background-color: #f2f2f2;
}

關(guān)于下拉刷新

下拉刷新最好不要在全局開啟,而是在具體的頁面開啟。另外在具體頁面只能配置window下面的屬性,所以不需要再寫window。下面的配置是**此頁面的下拉刷新和設(shè)置頁面標(biāo)題:

{
  "enablePullDownRefresh": true,
  "navigationBarTitleText": "小程序"
}

小程序文件引用路徑

  • js中:只能通過"import RefresherPlugin from '../../plugins/refresher';"這種方式引用,不能省略".."
  • wxss和wxml中:可以使用/root/path的方法引用文件,如
/*引用樣式*/
@import '/components/loading/loading.wxss';

<!--引用wxml-->
<import src="/components/loadmore/loadmore.wxml" />

<!--wxml中引用圖片-->
<image class="icon" src="/images/category.png" mode="aspectFill" />

圖片預(yù)覽(圖片查看器)

小程序提供了wx.previewImage方法來預(yù)覽圖片,所有不需要再實現(xiàn)圖片查看器

wx.previewImage({
  urls: this.data.swiper.imgUrls
});

巧用pages配置方便開發(fā)

app.json中pages選項的第一個頁面即小程序的入口頁面。因此把當(dāng)前開發(fā)頁面配置成第一個頁面,可以方便我們預(yù)覽。

指定頁面path

指定頁面path一定要使用“/”開頭:

wx.navigateTo({
  url: '/pages/goods/search/search'
});
<navigator url="/pages/goods/detail/detail?gid={{goods[0].id}}" hover-class="weui-cell_active">
    <template is="goodsListItem" data="{{goods: goods[0]}}"></template>
</navigator>

在block標(biāo)簽上使用控制指令

block標(biāo)簽在官方文檔中沒有怎么提及,剛開始時甚至都不知道有這個標(biāo)簽。由于標(biāo)簽并不會在頁面中生成具體的節(jié)點,所以我們可以把控制指令寫到這個標(biāo)簽上,從而使用代碼可讀性的維護性更好,如

<!--循環(huán)列表-->
<block wx:for="{{history}}" wx:for-item="item" wx:key="*this">
    <view class="search__history-list-item g-wto" catchtap="clickSearch" data-key="{{item}}">{{item}}</view>
</block>

<!--條件選擇-->
<block wx:if="{{isOrder}}">
  <!--...-->
</block>
<block wx:else >
  <!--...-->
</block>

template vs include

先看使用方法:

<!--template使用-->

<!--/components/nodata/nodata.wxml中定義nodata template-->
<template name="nodata">
  <view class="c-no-data" hidden="{{hidden}}">
    <view class="content">
      <image class="icon" src="{{icon}}" mode="widthFix" />
      <view class="label">{{msg || '沒有數(shù)據(jù)'}}</view>
    </view>
  </view>
</template>

<!--template使用-->
<import src="/components/nodata/nodata.wxml" />
<template is="nodata" data="{{icon:'/images/empty2.png', hidden: empty, msg: '數(shù)據(jù)為空'}}"></template>

<!--include使用-->
<view class="p-search">
  <include src="/components/search/search.wxml" />
</view>
  • template必須先定義,再使用
  • template具有作用域,只能使用data中傳入的數(shù)據(jù)
  • include是把wxml中的內(nèi)容引入到使用include的位置,數(shù)據(jù)直接頁面數(shù)據(jù)
  • 對于復(fù)雜的組件,為了方便數(shù)據(jù)控制,include可能比template更好用

善于使用mixin方式開發(fā)頁面

Page在啟動時,要求傳入一個配置對象。這個配置對象的某些屬性會在頁面具體的生命周期中執(zhí)行,比如onLoad, onShow...等。

// 官方頁面注冊
Page({
  data: {
    text: "This is page data."
  },
  onLoad: function(options) {
    // Do some initialize when page load.
  },
  onReady: function() {
    // Do something when page ready.
  },
  onShow: function() {
    // Do something when page show.
  },
  onHide: function() {
    // Do something when page hide.
  },
  onUnload: function() {
    // Do something when page close.
  },
  onPullDownRefresh: function() {
    // Do something when pull down.
  },
  onReachBottom: function() {
    // Do something when page reach bottom.
  },
  onShareAppMessage: function () {
   // return custom share data when user share.
  },
  // Event handler.
  viewTap: function() {
    this.setData({
      text: 'Set some data for updating view.'
    })
  },
  customData: {
    hi: 'MINA'
  }
});

如果我們抽象一些公共mixin,則頁面的注冊就會像下面的樣子:

import { $extend } from '../../../utils/helper';
import Search from '../../../components/search/search';
import { SEARCH_CACHE_KEY } from '../../../config/index';

Page($extend({
  onLoad() {
    this.init({
      cacheKey: SEARCH_CACHE_KEY,
      cgi: queryOrders,
      isOrder: true
    });
  }
}, Search));

使用小程序全局?jǐn)?shù)據(jù)

var appInstance = getApp()

// 讀
console.log(appInstance.globalData) // I am global data

// 寫
appInstance.newKey = 'new value';

小程序事件

綁定方式

小程序事件綁定有bind或catch兩種開頭,然后跟上事件的類型,如bindtap, catchtouchstart。區(qū)別是:bind事件綁定不會阻止冒泡事件向上冒泡,catch事件綁定可以阻止冒泡事件向上冒泡。建議使用catch綁定事件。

<view class="search-head">
<view class="search-head__input">
  <icon type="search" size="15" class="icon"></icon>
  <icon type="clear" hidden="{{!showClear}}" size="15" class="clear" catchtap="clearKeyword"></icon>
  <input id="input" class="search-head__input-input" type="text" 
         placeholder="搜索" 
         placeholder-class="search-head__input-ph" value="{{keyword}}" focus="{{true}}" 
         bindinput="keywordInput" 
         bindconfirm="doSearch" />
</view>
<view class="search-head__cancel" catchtap="goHome">取消</view>
</view>

dataset

  • 可以通過dataset在事件處理函數(shù)中傳遞參數(shù)。
  • 一個標(biāo)簽上可以寫多個dataset
<block wx:for="{{filters}}" wx:key="{{filter.name}}" wx:for-item="filter" wx:for-index="idxi">
  <view class="m-detail__size">
    <view class="label m-detail__size-label">{{filter.name}}</view>
    <view class="m-detail__size-wrap">
      <block wx:for="{{filter.value}}" wx:key="*this" wx:for-item="item" wx:for-index="idxj">
        <block wx:if="{{item.enable}}">
          <view class="m-detail__size-item {{item.selected ? 'selected' : ''}}"
                data-target="{{item}}"
                data-i="{{idxi}}"
                data-j="{{idxj}}"
                data-selected="{{item.selected}}"
                data-enable="{{item.enable}}"
                catchtap="doFilter">{{item.value}}</view>
        </block>
      </block>
    </view>
  </view>
</block>
doFilter(e) {
    let target = e.target.dataset.target;
    let selected = e.target.dataset.selected;
    let enable = e.target.dataset.enable;
    let i = ~~(e.target.dataset.i);
    let j = ~~(e.target.dataset.j);
    let value = target.value;

    //... 
}

以下的dataset寫法都會報錯,與常見的mvvm中傳值還是有區(qū)別:

data-j="{{idxj: idxj}}" 
data-j="{{idxj, idxi}}"

尺寸請使用rpx

rpx是小程序提供的一種新的尺寸單位,相比于px,rpx具有更好的兼容性。

小程序中路徑的使用(更新于2017年3月12日)

  • js中:只能通過"import RefresherPlugin from '../../plugins/refresher';"這種方式引用,不能省略".."

  • wxss和wxml中:可以使用/root/path/file.ext的方法引用文件,如

/*引用樣式*/
@import '/components/loading/loading.wxss';

<!--引用wxml-->
<import src="/components/loadmore/loadmore.wxml" />

<!--wxml中引用圖片-->
<image class="icon" src="/images/category.png" mode="aspectFill" />


易優(yōu)小程序(企業(yè)版)+靈活api+前后代碼開源 碼云倉庫:starfork
本文地址:http://22321a.com/wxmini/doc/course/18086.html 復(fù)制鏈接 如需定制請聯(lián)系易優(yōu)客服咨詢:800182392 點擊咨詢
QQ在線咨詢