麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學院 > 開發設計 > 正文

微信小程序開發如何使用npm包--labrador使用

2019-11-09 16:12:17
字體:
來源:轉載
供稿:網友

相信做過微信小程序的都知道,官方給出的微信web開發工具上根本就無法加載node_modules包,即使可以加載,node_modules動輒幾十M的大小,小程序的代碼限制在1M以內,微信小程序的三個不足:

1無法調用npm包

2無法使用babel轉嗎

3無法重用組件(像react那樣重用組件功能)

接下來給大家介紹一個相對完整的微信開發解決方案:

Labrador:(目前最新版本為:0.6.12)

github地址:https://github.com/maichong/labrador

特點:

1,使用Labrador框架可以使微信開發者工具支持加載海量NPM包

2,支持ES6/7標準代碼,使用async/await能夠有效避免回調地獄

3,組件重用,對微信小程序框架進行了二次封裝,實現了組件重用和嵌套

4,自動化測試,非常容易編寫單元測試腳本,不經任何額外配置即可自動化測試

5,使用Editor Config及ESLint標準化代碼風格,方便團隊協作

當然了也有缺點,你看完會發現缺點

首先系統全局安裝nodejs和Labrador命令行工具。

npm install -g labrador-cli

查看當前labrador版本

labrador -V

新建一個目錄,初始化項目

labrador create mylabrador        # 初始化labrador項目 mylabrador是你的項目名字

用Egret Wing3(這個IDE更適合開發微信小程序),打開labradordemo這個項目,

開啟代碼自動轉換功能

labrador watch

然后用微信開發著工具打開labradordemo項目下面的dist文件

這個里面不需要做任何的編碼工作,在下面的src目錄作修改,會自動同步到微信開發者工具上面

在src/pages/index新增一個index.json文件,主要內容為設置頁面的title

{  "navigationBarTitleText": "主頁",  "enablePullDownRefresh": false}

然后保存,會同步到微信開發者工具

labrador 庫對全局的 wx 變量進行了封裝,將所有wx 對象中的異步方法進行了PRomise支持, 除了同步的方法,這些方法往往以on*、create*、stop*、pause*、close* 開頭或以 *Sync 結尾。在如下代碼中使用 labrador 庫。

import wx, { Component, PropTypes }from'labrador';

wx.wx;          // 原始的全局 wx 對象

wx.app;         // 和全局的 getApp() 函數效果一樣,代碼風格不建議粗暴地訪問全局對象和方法

wx.currentPages // 對全局函數 getCurrentPages() 優雅的封裝

Component;      // Labrador 自定義組件基類

PropTypes;      // Labrador 數據類型校驗器集合

wx.login;       // 封裝后的微信登錄接口

wx.getStorage;  // 封裝后的讀取緩存接口

//... 更多請參見 https://mp.weixin.QQ.com/debug/wxadoc/dev/api/

我們建議不要再使用 wx.getStorageSync() 等同步阻塞方法,而在async 函數中使用await wx.getStorage() 異步非阻塞方法提高性能,除非遇到特殊情況。

app.js文件

import request from 'al-request';import { setStore } from 'labrador-redux';import { sleep } from './utils/utils';import store from './redux';if (__DEV__) {  console.log('當前為開發環境');}// 向labrador-redux注冊storesetStore(store);export default class {  async onLaunch() {    try {      await sleep(100);      await request('api/start');    } catch (error) {      console.error(error);    }    this.timer();  }  async timer() {    while (true) {      console.log('hello');      await sleep(10000);    }  }}

代碼中全部使用ES6/7標準語法。代碼不必聲明 use strict ,因為在編譯時,所有代碼都會強制使用嚴格模式。

代碼中并未調用全局的 App() 方法,而是使用export 語法默認導出了一個類,在編譯后,Labrador會自動增加App() 方法調用,所有請勿手動調用 App() 方法。這樣做是因為代碼風格不建議粗暴地訪問全局對象和方法。

Labrador的自定義組件,是基于微信小程序框架的組件之上,進一步自定義組合,擁有邏輯處理、布局和樣式。

項目中通用自定義組件存放在src/compontents 目錄,一個組件一般由三個文件組成,*.js 、*.xml 和 *.less 分別對應微信小程序框架的js 、wxml 和wxss 文件。在Labardor項目源碼中,我們特意采用了xml 和 less 后綴以示區別。如果組件包含單元測試,那么在組件目錄下會存在一個*.test.js 的測試腳本文件。

0.6 版本后,支持*.sass 和*.sCSS 格式樣式文件。

自定義組件示例

下面是一個簡單的自定義組件代碼實例:

邏輯 src/compontents/todo/todo.js

import { Component, PropTypes } from 'labrador-immutable';const { string, bool, func } = PropTypes;class Todo extends Component {  static propTypes = {    id: string,    title: string,    createdAt: string,    finished: bool,    finishedAt: string,    onRemove: func,    onRestore: func,    onFinish: func  };  constructor(props) {    super(props);    this.state = {      icon: props.finished ? 'success_circle' : 'circle',      className: props.finished ? 'todo-finished' : ''    };  }  onUpdate(props) {    this.setState({      icon: props.finished ? 'success_circle' : 'circle',      className: props.finished ? 'todo-finished' : ''    });  }  handleRemove() {    this.props.onRemove(this.props.id);  }  handleFinish() {    if (this.props.finished) {      this.props.onRestore(this.props.id);    } else {      this.props.onFinish(this.props.id);    }  }}export default Todo;

自定義組件的邏輯代碼和微信框架中的page很相似,最大的區別是在js邏輯代碼中,沒有調用全局的 Page() 函數聲明頁面,而是用 export 語法導出了一個默認的類,這個類必須繼承于Component 組件基類。

相對于微信框架中的page,Labrador自定義組件擴展了 propTypes 、defaultProps 、onUpdate()、setState() 、children() 等方法和屬性,children()方法返回當前組件中的子組件集合,此選項將在下文中敘述。

Labrador的目標是構建一個可以重用、嵌套的自定義組件方案,在現實情況中,當多個組件互相嵌套組合,就一定會遇到父子組件件的數據和消息傳遞。因為所有的組件都實現了setState 方法,所以我們可以使用this._children.foobar.setState(data) 或this.parent.setState(data) 這樣的代碼調用來解決父子組件間的數據傳遞問題,但是,如果項目中出現大量這樣的代碼,那么數據流將變得非常混亂。

我們借鑒了 React.js 的思想,為組件增加了 props 機制。子組件通過 this.props 得到父組件給自己傳達的參數數據。父組件怎樣將數據傳遞給子組件,我們下文中敘述。

onUpdate 生命周期函數是當組件的 props 發生變化后被調用,類似React.js中的 componentWillReceiveProps 所以我們可以在此函數體內監測props 的變化。

組件定義時的 propTypes 靜態屬性是對當前組件的props參數數據類型的定義。defaultProps 選項代表的是當前組件默認的各項參數值。propTypes 、defaultProps 選項都可以省略,但是強烈建議定義 propTypes,因為這樣可以使得代碼更清晰易懂,另外還可以通過Labrador自動檢測props值類型,以減少BUG。為優化性能,只有在開發環境下才會自動檢測props值類型。

編譯時默認是開發環境,當編譯時候采用 -m 參數才會是生產模式,在代碼中任何地方都可以使用魔術變量__DEV__ 來判斷是否是開發環境。

組件向模板傳值需要調用 setState 方法,換言之,組件模板能夠讀取到當前組件的所有內部狀態數據。

0.6版本后,Component 基類中撤銷了setData 方法,新增了setState 方法,這樣做并不是僅僅為了像React.js,而是在老版本中,我們將所有組件樹的內部狀態數據和props全存放在page.data中,在組件更新時產生了大量的setData 遞歸調用,為了優化性能,必須將組件樹的狀態和page.data 進行了分離。

布局 src/compontents/todo/todo.xml

<view class="list-item flex-row items-center todo {{state.className}}">  <icon class="todo-icon" type="{{state.icon}}" size="20" color="#999" catchtap="handleFinish"/>  <view class="block flex todo-title {{state.className}}-title"> {{props.title}} </view>  <view class="btn btn-small {{props.finished?'btn-gray-hollow':'btn-danger'}}" catchtap="handleRemove">刪除</view></view>

XML布局文件和微信WXML文件語法完全一致,只是擴充了兩個自定義標簽 <component/> 和<list/>,下文中詳細敘述。

使用 {{}} 綁定變量時,以props.* 或state.* 開頭,即XML模板文件能夠訪問組件對象的props 和state。

樣式 src/compontents/todo/todo.less

@import 'al-ui';.todo {  background: #fff;  font-size: @font-size-medium;}.todo-icon {  margin-right: 10px;}.todo-finished {  background: @color-page;}.todo-finished-title {  .gray;  text-decoration: line-through;}

雖然我們采用了LESS文件,但是由于微信小程序框架的限制,不能使用LESS的層級選擇及嵌套語法。但是我們可以使用LESS的變量、mixin、函數等功能方便開發。

頁面

我們要求所有的頁面必須存放在 pages 目錄中,每個頁面的子目錄中的文件格式和自定義組件一致,只是可以多出一個*.json 配置文件。

頁面示例

下面是默認首頁的示例代碼:

邏輯 src/pages/index/index.js

import wx, { Component, PropTypes } from 'labrador-immutable';import { bindActionCreators } from 'redux';import { connect } from 'labrador-redux';import Todo from '../../components/todo/todo';import * as todoActions from '../../redux/todos';import { sleep } from '../../utils/utils';const { array, func } = PropTypes;class Index extends Component {  static propTypes = {    todos: array,    removeTodo: func,    restoreTodo: func,    createTodo: func,    finishTodo: func  };  state = {    titleInput: '',    finished: 0  };  children() {    let todos = this.props.todos || [];    let unfinished = [];    let finished = [];    if (todos.length) {      unfinished = todos.filter((todo) => !todo.finished);      finished = todos.asMutable()        .filter((todo) => todo.finished)        .sort((a, b) => (a.finishedAt < b.finishedAt ? 1 : -1))        .slice(0, 3);    }    return {      list: unfinished.map((todo) => ({        component: Todo,        key: todo.id,        props: {          ...todo,          onRemove: this.handleRemove,          onRestore: this.handleRestore,          onFinish: this.handleFinish        }      })),      finished: finished.map((todo) => ({        component: Todo,        key: todo.id,        props: {          ...todo,          onRemove: this.handleRemove,          onRestore: this.handleRestore,          onFinish: this.handleFinish        }      }))    };  }  onUpdate(props) {    let nextState = {      finished: 0    };    props.todos.forEach((todo) => {      if (todo.finished) {        nextState.finished += 1;      }    });    this.setState(nextState);  }  async onPullDownRefresh() {    await sleep(1000);    wx.showToast({ title: '刷新成功' });    wx.stopPullDownRefresh();  }  handleCreate() {    let title = this.state.titleInput;    if (!title) {      wx.showToast({ title: '請輸入任務' });      return;    }    this.props.createTodo({ title });    this.setState({ titleInput: '' });  }  handleInput(e) {    this.setState({ titleInput: e.detail.value });  }  handleRemove = (id) => {    this.props.removeTodo(id);  };  handleFinish = (id) => {    this.props.finishTodo(id);  };  handleRestore = (id) => {    this.props.restoreTodo(id);  };  handleShowFinished() {    wx.navigateTo({ url: 'finished' });  }  handleShowUI() {    wx.navigateTo({ url: '/pages/ui/index' });  }}export default connect(  ({ todos }) => ({ todos }),  (dispatch) => bindActionCreators({    createTodo: todoActions.create,    removeTodo: todoActions.remove,    finishTodo: todoActions.finish,    restoreTodo: todoActions.restore,  }, dispatch))(Index);

頁面代碼的格式和自定義組件的格式一模一樣,我們的思想是 頁面也是組件

js邏輯代碼中同樣使用 export default 語句導出了一個默認類,也不能手動調用Page() 方法,因為在編譯后,pages 目錄下的所有js文件全部會自動調用Page() 方法聲明頁面。

我們看到組件類中,有一個對象方法 children() ,這個方法返回了該組件依賴、包含的其他自定義組件,在上面的代碼中頁面包含了三個自定義組件list 、title 和counter ,這個三個自定義組件的key 分別為list 、motto 和counter。

children() 返回的每個組件的定義都包含兩個屬性,component 屬性定義了組件類,props 屬性定義了父組件向子組件傳入的props 屬性對象。

頁面也是組件,所有的組件都擁有一樣的生命周期函數onLoad, onReady, onShow, onHide, onUnload,onUpdate 以及setState函數。

componets 和 pages 兩個目錄的區別在于,componets 中存放的組件能夠被智能加載、重用,pages 目錄中的組件在編譯時自動加上 Page() 調用,所以,pages 目錄中的組件不能被其他組件調用,否則將出現多次調用Page()的錯誤。如果某個組件需要重用,請存放在componets 目錄或打包成NPM包。

注意 雖然頁面也是組件,雖然頁面的代碼格式和組件一模一樣,但是運行時,getCurrentPages() 得到的頁面對象page 并非pages目錄中聲明的頁面對象,page.root 才是pages目錄中聲明的頁面對象,才是組件樹的最頂端。這里我們用了組合 模式而非繼承模式。

注意 所有組件的生命周期函數支持 async ,但默認是普通函數,如果函數體內沒有異步操作,我們建議采用普通函數,因為async 函數會有一定的性能開銷,并且無法保證執行順序。當聲明周期函數內需要異步操作,并且【不關心】各個生命周期函數的執行順序時,可以采用async 函數。

布局 src/pages/index/index.xml

<view class="todo-list has-toolbar">  <view class="list">    <list key="list" name="todo"/>  </view>  <block wx:if="{{state.finished}}">    <view class="group-header">已完成</view>    <view class="list">      <list key="finished" name="todo"/>    </view>    <view wx:if="{{state.finished>3}}" class="padding-h-xxlarge padding-top-large">      <view class="btn btn-gray-hollow block" catchtap="handleShowFinished">查看全部已完成</view>    </view>  </block>  <view wx:if="{{props.todos.length}}" class="gray padding text-right">總數 {{props.todos.length}} 已完成    {{state.finished}}  </view>  <view wx:else class="msg">    <icon class="msg-icon" type="info" size="80" color="#ccc"/>    <view class="msg-title">當前沒有任務</view>    <view class="msg-desc">請在下方輸入框中填入新任務然后點擊新增</view>  </view>  <view class="toolbar toolbar-bottom padding-h-xsmall">    <view class="form-group flex">      <input class="form-control" type="text" placeholder="請輸入新的任務" value="{{state.titleInput}}"             bindinput="handleInput"/>    </view>    <view class="btn btn-success btn-toolbar" catchtap="handleCreate">新增</view>  </view>  <view class="footer">    <view>Powered by Labrador</view>    <text class="link" catchtap="handleShowUI">AL UI</text>  </view></view>

XML布局代碼中,使用了Labrador提供的 <component/> 標簽,此標簽的作用是導入一個自定義子組件的布局文件,標簽有兩個屬性,分別為key (必選)和name (可選,默認為key的值)。key 與js邏輯代碼中的組件key 對應,name 是組件的目錄名。key 用來綁定組件JS邏輯對象的children 中對應的數據, name 用于在src/componets 和node_modules 目錄中尋找子組件模板。

樣式 src/pages/index/index.less

@import 'al-ui';@import 'todo';.todo-list {  }

LESS樣式文件中,我們使用了 @import 語句加載所有子組件樣式,這里的@import 'list' 語句按照LESS的語法,會首先尋找當前目錄src/pages/index/ 中的 list.less 文件,如果找不到就會按照Labrador的規則智能地嘗試尋找src/componets 和node_modules 目錄中的組件樣式。

接下來,我們定義了 .motto-title-text 樣式,這樣做是因為motto key 代表的title組件的模板中(src/compontents/title/title.xml)有一個view 屬于title-text 類,編譯時,Labrador將自動為其增加一個前綴motto- ,所以編譯后這個view所屬的類為title-text motto-title-text (可以查看dist/pages/index/index.xml)。那么我們就可以在父組件的樣式代碼中使用.motto-title-text 來重新定義子組件的樣式。

Labrador支持多層組件嵌套,在上述的實例中,index 包含子組件list 和title,list 包含子組件title,所以在最終顯示時,index 頁面上回顯示兩個title 組件。

自定義組件列表

邏輯 src/components/list/list.js

import wx, { Component } from 'labrador';

import Title from'../title/title';

import Item from'../item/item';

import { sleep } from'../../utils/util';

export defaultclassListextendsComponent {

  constructor(props){

    super(props);

    this.state= {

      items: [

        { id:1, title:'Labrador' },

        { id:2, title:'Alaska' }

      ]

    };

  }

  children (){

    return {

      title:{

        component: Title,

        props: { text:'The List Title' }

      },

      listItems:this.state.items.map((item)=> {

        return {

          component: Item,

          key: item.id,

          props: {

            item: item,

            title: item.title,

            isNew: item.isNew,

            onChange: (title)=> {this.handleChange(item, title) }

          }

        };

      })

    };

  }

  asynconLoad() {

    awaitsleep(1000);

    this.setState({

      items: [{ id:3, title:'Collie', isNew:true }].concat(this.data.items)

    });

  }

  handleChange(item, title) {

    let items=this.state.items.map((i)=> {

      if(item.id== i.id){

        returnObject.assign({},i,{ title });

      }

      return i;

    });

    this.setState({ items });

  }

}

在上邊代碼中的 children() 返回的listItems 子組件定義時,是一個組件數組。數組的每一項都是一個子組件的定義,并且需要指定每一項的key 屬性,key 屬性將用于模板渲染性能優化,建議將唯一且不易變化的值設置為子組件的key,比如上邊例子中的id。

模板 src/components/list/list.xml

<viewclass="list">

  <componentkey="title"name="title"/>

  <listkey="listItems"name="item"/>

</view>

在XML模板中,調用 <list/> 標簽即可自動渲染子組件列表。和<component/> 標簽類似,<list/> 同樣也有兩個屬性,key 和name。Labrador編譯后,會自動將 <list/> 標簽編譯成wx:for 循環。

自動化測試

我們規定項目中所有后綴為 *.test.js 的文件為測試腳本文件。每一個測試腳本文件對應一個待測試的JS模塊文件。例如src/utils/util.js 和src/utils/utils.test.js 。這樣,項目中所有模塊和其測試文件就全部存放在一起,方便查找和模塊劃分。這樣規劃主要是受到了GO語言的啟發,也符合微信小程序一貫的目錄結構風格。

在編譯時,加上 -t 參數即可自動調用測試腳本完成項目測試,如果不加-t 參數,則所有測試腳本不會被編譯到dist 目錄,所以不必擔心項目會肥胖。

普通JS模塊測試

測試腳本中使用 export 語句導出多個名稱以test* 開頭的函數,這些函數在運行后會被逐個調用完成測試。如果test測試函數在運行時拋出異常,則視為測試失敗,例如代碼:

// src/util.js

// 普通項目模塊文件中的代碼片段,導出了一個通用的add函數

export functionadd(a, b) {

  return a+ b;

}

// src/util.test.js

// 測試腳本文件代碼片段

import assert from'assert';

//測試 util.add() 函數

export functiontestAdd(exports) {

  assert(exports.add(1,1)===2);

}

代碼中 testAdd 即為一個test測試函數,專門用來測試add() 函數,在test函數執行時,會將目標模塊作為參數傳進來,即會將util.js 中的exports 傳進來。

自定義組件測試

自定義組件的測試腳本中可以導出兩類測試函數。第三類和普通測試腳本一樣,也為 test* 函數,但是參數不是exports 而是運行中的、實例化后的組件對象。那么我們就可以在test函數中調用組件的方法或則訪問組件的props 和state 屬性,來測試行為。另外,普通模塊測試腳本是啟動后就開始逐個運行test* 函數,而組件測試腳本是當組件onReady 以后才會開始測試。

自定義組件的第二類測試函數是以 on* 開頭,和組件的生命周期函數名稱一模一樣,這一類測試函數不是等到組件onReady 以后開始運行,而是當組件生命周期函數運行時被觸發。函數接收兩個參數,第一個為組件的對象引用,第二個為run 函數。比如某個組件有一個onLoad 測試函數,那么當組件將要運行onLoad 生命周期函數時,先觸發onLoad 測試函數,在測試函數內部調用run() 函數,繼續執行組件的生命周期函數,run() 函數返回的數據就是生命周期函數返回的數據,如果返回的是Promise,則代表生命周期函數是一個異步函數,測試函數也可以寫為async 異步函數,等待生命周期函數結束。這樣我們就可以獲取run()前后兩個狀態數據,最后對比,來測試生命周期函數的運行是否正確。

第三類測試函數與生命周期測試函數類似,是以 handle* 開頭,用以測試事件處理函數是否正確,是在對應事件發生時運行測試。例如:

// src/components/counter/counter.test.js

export functionhandleTap(c, run) {

  let num= c.data.num;

  run();

  let step= c.data.num- num;

  if (step!==1) {

    thrownewError('

  }

}

生命周期測試函數和事件測試函數只會執行一次,自動化測試的結果將會輸出到Console控制臺。

項目配置文件

labrador create 命令在初始化項目時,會在項目根目錄中創建一個.labrador 項目配置文件,如果你的項目是使用 labrador-cli 0.3 版本創建的,可以手動增加此文件。

配置文件為JSON5格式,默認配置為:

{

  "define":{

    "API_ROOT":"http://localhost:5000/"

  },

  "npmMap":{

    "lodash-es":"lodash"

  },

  "uglify":{

    "mangle": [],

    "compress": {

      "warnings":false

    }

  },

  "classNames": {

    "text-red":true

  },

  "env":{

    "development": {},

    "production": {

      "define":{

        "API_ROOT":"https://your.online.domain/"

      }

    }

  }

}


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 久久96国产精品久久秘臀 | 亚洲综合中文 | 免费毛片免费看 | 天堂成人一区二区三区 | 黄污免费网站 | 国产在线精品91 | 久久成人精品视频 | 少妇一级淫片免费放正片 | 日韩视频一区在线 | 91久久九色 | 欧美精品一区二区久久 | 99国产精品白浆在线观看免费 | 好吊一区二区三区 | 九九热九九爱 | pornoⅹxxxxhd麻豆 | 久久精品视频日本 | 中文字幕精品一区久久久久 | 毛片在线免费观看网址 | 久久成人福利 | 欧美一级毛片美99毛片 | 久久影院午夜 | 国产精品伦视频看免费三 | 国产99久久久国产精品下药 | 国产一国产一级毛片视频 | 爽成人777777婷婷 | 在线成人一区 | 国产精品影视 | 国产精品麻豆一区二区三区 | 97干色| 午夜视频久久久 | av视在线| 哪里可以看免费的av | 欧美国产综合视频 | 国产成人在线一区二区 | 国产成人综合在线 | 一级免费在线视频 | 海外中文字幕在线观看 | 爱爱视频天天干 | 婷婷久久综合九色综合色多多蜜臀 | 欧美 videos粗暴 | 亚洲午夜精选 |