微信小程序开发提高效率

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

相关文章
相关标签/搜索