微信小程序開發提高效率

http://www.ifanr.com/minapp/790017javascript

微信小程序的 API 實現須要兼顧方方面面,因此仍然使用 callback 寫法。java

衆所周知,Callback-Hell(回調地獄)是傳統 JS 語法上的歷史問題。但畢竟稱手的工具是開發效率的源泉,所以筆者對當前版本的微信小程序 API 作了簡單的封裝——weapp。node

同時,微信小程序框架自己專一於交互和 UI 的實現,並未提供內置的狀態管理。若是衆多的異步操做都直接在 App 或 Page 中一一實現,相信開發起來會很困難,並且不易於測試。webpack

所以,我又所以針對微信小程序實現了一個基於 Redux 方案的狀態管理模塊,用以方便的在小程序中實現應用狀態管理 redux-weapp。git

特別地,微信小程序構建(編譯)時不支持從 App scope 以外 require 文件,npm 在此就很差用了。es6

因此,咱們須要實時 build 依賴到應用本地,在微信小程序中引用本地的 modules。github

對於這種構建場景,我認爲 webpack 算是最方便的方案。web

在開始以前,你須要準備

  • 從官方文檔,瞭解微信小程序是什麼;
  • 瞭解 Redux 應用狀態管理方案,同時它也是 Flux 架構的具體實現;
  • 瞭解 JavaScript 打包工具 webpack;
  • 瞭解 ES6/7 代碼轉譯(transcompile)工具 Babel。原理是藉助語法分析工具,將代碼解析成抽象語法樹後「重寫」成最終的代碼;
  • 相似 Jest、Mocha 等 JavaScript 測試工具,能夠根據須要選擇。

安裝工具和依賴模塊

下載微信小程序開發者工具

開發者工具是用 NW.js 模擬的環境,在微信中,則是 JavascriptCore 環境。typescript

不過不用擔憂, 只是兩個不一樣的 VM,本質是同樣的。npm

NW.js 可能存在一些小 bug,寫代碼的時候注意一下就好。

用 npm 命令開始一個微信小程序項目

mkdir myapp cd myapp npm init

開始安裝必要的依賴模塊

因爲除了小程序運行時須要的模塊,還有構建所須要的模塊。

看起來會比較多,不過不用擔憂,大多數都是聲明性的,不須要你直接調用。

爲了方便經驗少些的同窗理解,我將這些依賴分步安裝。

首先是代碼轉譯工具 Babel:

npm install --save-dev babel-cli babel-core babel-loader babel-plugin-add-module-exports babel-polyfill babel-preset-es2015 babel-preset-stage-0

有了上面這些模塊,就能夠在構建時,將 ES6/7 的代碼轉譯爲 ES5 的代碼了(其實解釋器都只認 ES5)。

接下來,咱們安裝打包工具 webpack:

npm install webpack --save-dev

咱們只須要對代碼進行打包,不須要 dev server 和 hot module replace 功能。

所以,咱們只須要安裝 webpack module 自己便可,無需安裝其餘擴展和插件。

接下來,咱們來安裝 Redux:

npm install redux redux-thunk --save-dev

須要注意的是,因爲在實際應用中,咱們常常會須要異步調用 API 服務器的接口,所以咱們還須要 redux-thunk 這個模塊,來處理異步行爲。

而後安裝開發小程序的輔助模塊:

npm install xixilive/weapp xixilive/redux-weapp --save-dev

其中,weapp 模塊是對微信小程序 API 的 wrapper,提供了更易於使用的 API,redux-weapp 是基於 Redux 對微信小程序進行狀態管理。

創建項目目錄結構

myapp
 |- es6                # 源代碼 |- myapp.js # 在app.js文件中require此文件 |- lib # 存放編譯以後的js文件 |- pages # 小程序頁面定義 |- projects |- projects.js |- projects.json |- projects.wxml |- projects.wxss ... |- app.js # 小程序入口文件 |- app.json |- app.wxss |- webpack.config.js # webpack配置文件 

編寫構建腳本

首先得寫 webpack.config.js, 這個是必須的。

因爲這個構建是爲了本地化微信小程序的依賴,所以咱們只處理 JS 文件。若須要打包其餘資源,請讀者自行研究。

並且,值得注意的是,微信小程序包有 1 MB 的上限。

// webpack.config.js var path = require('path'), webpack = require('webpack') var jsLoader = { test: /\.js$/, // 你也能夠用.es6作文件擴展名, 而後在這裏定義相應的pattern loader: 'babel', query: { // 代碼轉譯預設, 並不包含ES新特性的polyfill, polyfill須要在具體代碼中顯示require presets: ["es2015", "stage-0"] }, // 指定轉譯es6目錄下的代碼 include: path.join(__dirname, 'es6'), // 指定不轉譯node_modules下的代碼 exclude: path.join(__dirname, 'node_modules') } module.exports = { // sourcemap 選項, 建議開發時包含sourcemap, production版本時去掉(節能減排) devtool: null, // 指定es6目錄爲context目錄, 這樣在下面的entry, output部分就能夠少些幾個`../`了 context: path.join(__dirname, 'es6'), // 定義要打包的文件 // 好比: `{entry: {out: ['./x', './y','./z']}}` 的意思是: 將x,y,z等這些文件打包成一個文件,取名爲: out // 具體請參看webpack文檔 entry: { myapp: './myapp' }, output: { // 將打包後的文件輸出到lib目錄 path: path.join(__dirname, 'lib'), // 將打包後的文件命名爲 myapp, `[name]`能夠理解爲模板變量 filename: '[name].js', // module規範爲 `umd`, 兼容commonjs和amd, 具體請參看webpack文檔 libraryTarget: 'umd' }, module: { loaders: [jsLoader] }, resolve: { extensions: ['', '.js'], // 將es6目錄指定爲加載目錄, 這樣在require/import時就會自動在這個目錄下resolve文件(能夠省去很多../) modulesDirectories: ['es6', 'node_modules'] }, plugins: [ new webpack.NoErrorsPlugin(), // 一般會須要區分dev和production, 建議定義這個變量 // 編譯後會在global中定義`process.env`這個Object new webpack.DefinePlugin({ 'process.env': { 'NODE_ENV': JSON.stringify('development') } }) ] }

定義 npm 命令

首先是代碼測試命令 test

因爲我喜歡用 Jest,因此這裏也用 Jest 作範例。

// package.json "scripts": { "pretest": "eslint es6", //推薦進行靜態檢查 "test": "jest", ... }, ..., // jest容許在package.json中定義配置 "jest": { "automock": false, "bail": true, "transform": { ".js": "/node_modules/babel-jest" //用babel轉譯 }, "testPathDirs": [ "/__tests__/" ], "testRegex": ".test.js$", "unmockedModulePathPatterns": [ "/node_modules/" ], "testPathIgnorePatterns": [ "/node_modules/" ] }

接下來,就是激動人心的 build 命令。成敗在此一舉 🙂

// package.json "scripts": { ..., // 帶上watch選項, 實時編譯修改, 因爲小程序開發工具也監視應用文件的修改, 因此es6目錄下的js文件修改, 將致使小程序開發工具自動從新加載 "build": "webpack --watch --progress --colors --config webpack.config.js" },

寫小程序代碼

到這裏,咱們總算進入正題了。

藉助上述的 weapp 和 redux-weapp,但願你在開發小程序的時候,會感到很舒服。

在這個範例中,咱們目標是去查詢 GitHub 和 Octokit 的開源項目,並顯示在小程序中。

myapp 模塊

咱們首先定義 store: /es6/store.js

這裏只是簡單的範例,實際中會有比較複雜的 store shape,須要引入更多的 middleware,來處理動做和狀態的變化。

// /es6/store.js import {createStore, applyMiddleware, bindActionCreators} from 'redux' import thunk from 'redux-thunk' import reducers from './reducers' export default function(initState = {}){ return createStore( reducers, initState, applyMiddleware(thunk) ) }

接下來,咱們繼續定義 reducers:/es6/reducers.js

Reducer 就是處理因 Store dispatch 在執行時,發生的狀態變化的函數,參數老是爲 (state, action)

// /es6/reducers.js import { combineReducers } from 'redux' // 處理projects邏輯 const projects = (state = [], action) => { switch (action.type) { case 'PROJECTS_LOADED': return state.concat[action.payload] //other cases } return state } // 將多個reducer合併起來 // 這裏就能夠看出store的結構了, 是否是很 predictable ? export default combineReducers({ projects })

還有 actions:/es6/actions.js,它一般是個 Plain Object,老是被 Store dispatch,描述了「發生了什麼,結果是什麼」的邏輯。

// /es6/actions.js import {weapp} from 'weapp' // 更好的方法是定義一個api module, 來處理網絡請求 const http = weapp.Http('https://api.github.com') // 這是一個異步action, redux-thunk會處理返回值爲Function的action(能夠編入繞口令大全了~~) export const loadProjects = (org) => { return (dispatch) => { http.get(`/orgs/${org}/repos`).then(response => { // 讓store去廣播'PROJECTS_LOADED'這件事情發生了 dispatch({ type: 'PROJECTS_LOADED', payload: response }) }) } }

最後還有 myapp 模塊的入口:/es6/myapp.js

// /es6/myapp.js import {bindActionCreators} from 'redux' import {weapp} from 'weapp' import connect from 'redux-weapp' import store from './store' import actions from './actions' export { weapp, connect, bindActionCreators, store, actions }

小程序模塊

首先是小程序整體邏輯文件:app.js

// /app.js App({ // 方便起見, 這裏不作任何life-cycle處理 })

以及 app.json

{
  "pages": [ "pages/projects/projects" ], "window": { "navigationBarTitleText": "Orchid" }, "networkTimeout": { "request": 10000, "downloadFile": 10000 }, "debug": true }

還有頁面邏輯 projects.js。在以前,咱們也將小程序的啓動頁面,定義爲 projects 了。

// /pages/projects/projects.js // 引入編譯過的modules import { weapp, connect, bindActionCreators, store, actions } from '../../lib/app' // 標準Page定義Object const config = { data: { projects: [] //for init-render }, onReady(){ // 哪裏來的 loadProjects? 往下看 this.loadProjects('octokit') }, onStateChange(nextState){ this.setData({projects: nextState}) } } // connect store with page const page = connect.Page( store, // required // 這個頁面只關注projects變化 (state) => ({projects: state.projects}), // 將Action定義與Store.dispatch binding在一塊兒, 這樣就是一個能夠發起對github API的請求了 (dispatch) => { return { loadProjects: bindActionCreators(actions.loadProjects, dispatch) } } ) // 啓動被connect過的頁面 Page(page(config))

接下來是頁面 UI:projects.wxml

<scroll-view wx:for="{{projects}}" wx:for-item="project" class="container"> <view>{{project.name}}</view> </scroll-view>

最後的話

範例代碼未實際運行,僅用以表示開發步驟。我會盡快把這個範例實現完整,放到 GitHub 上。

最後,謝謝您耐心閱讀至此!

原文地址:https://gist.github.com/xixilive/5bf1cde16f898faff2e652dbd08cf669weapp 項目地址:https://github.com/xixilive/weapp

相關文章
相關標籤/搜索