文档章节

微信小程序模块化开发实践

xixilive
 xixilive
发布于 2017/01/05 11:16
字数 2167
阅读 5316
收藏 159

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

准备

  • 了解微信小程序是什么? 微信小程序官方文档

  • 了解应用状态管理方案: Redux, 也是Flux架构的具体实现

  • 了解Javascript打包工具: webpack

  • 了解ES6/7代码转译(transcompile)工具: Babel, 原理大致是借助语法分析工具(Esprima之类的), 将代码解析成抽象语法树, 再"重写"成最终的代码.

  • Javascript测试工具: jest, mocha等等, 请根据需要选择.

TL;DR;

微信小程序目前版本的API实现需要兼顾方方面面, 所以仍然使用callback写法, 众所周知的Callback-Hell是传统js语法上的历史问题, 但毕竟称手的工具是开发效率的源泉. 因此笔者对当前版本的微信小程序API做了简单的封装 weapp.

同时, 微信小程序框架本身专注于交互和UI的实现, 并未提供内置的状态管理, 如果众多的异步操作都直接在App或者Page中一一实现, 相信写起来会是一场噩梦, 而且不易于测试, 笔者又因此针对微信小程序实现了一个基于Redux方案的状态管理模块, 用以方便的在小程序中实现应用状态管理 redux-weapp.

特别地, 微信小程序构建(编译)时不支持从App scope之外require文件, npm在此就不好用了. 所以, 我们需要实时build依赖到应用本地, 在微信小程序中引用本地的modules, 对于这种构建场景, 笔者认为webpack算是最方便的方案. 大家都说COPY到本地是最最最方便的方式~~

安装工具和依赖模块

下载微信小程序开发者工具

开发者工具是用nwjs模拟的环境, 实际在微信中是JavascriptCore环境, 不过不用担心, 只是两个不同的vm, 本质是一样的.

nwjs可能存在一些小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这个模块来处理[异步action](http://redux.js.org/docs/advanced/AsyncActions.html).

安装开发小程序的辅助模块

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文件, 若需要打包其他诸如css, image等资源, 请读者自行研究. 实际上, 微信小程序包有1MB的上限.

// 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": "<rootDir>/node_modules/babel-jest" //用babel转译
  },
  "testPathDirs": [
    "<rootDir>/__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"
},

写应用代码

总算进入正题了(工欲善其事,...), 借助上述的 weappredux-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 actions时发生的状态变化的function, 参数总是为(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

Action通常是个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.jsapp.json
// /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上.

最后, 谢谢您耐心阅读至此!

参考

© 著作权归作者所有

xixilive

xixilive

粉丝 11
博文 1
码字总数 2167
作品 3
徐汇
高级程序员
私信 提问
加载中

评论(6)

无敌PHP
无敌PHP
就是h5和js的前端游戏
戎码生涯
戎码生涯
小程序小程序,准备一堆框架,走java路线了
HideHai
HideHai
范例代码未实际运行.....
OSCharles
OSCharles
范例代码未实际运行.....
颜风
颜风
范例代码未实际运行.....
微信小程序开发框架从入门到放弃

用框架是不可能用框架的,这辈子都不可能用框架。 微信小程序上手成本低,开发成本低,流量红利,推广成本低等等,很多公司的创业项目都会首选小程序来试水,小程序开发太火爆了,苦逼了前端工...

Runner羊
2018/07/24
0
0
掘金开发者大会 ∙ 微信小程序专场

掘金开发者大会 ∙ 微信小程序专场正式启动 一场迟来的聚会 自2017年1月微信小程序正式上线以来,应用数与开发者数持续增长,目前已经有超过100万个微信小程序发布且相关开发者也超过了150万...

掘金
2018/08/07
113
0
掘金开发者大会 ∙ 微信小程序专场 | 一场汇聚了微信小程序日活前十名半数的开发者大会,邀您参加!

掘金开发者大会 ∙ 微信小程序专场 一场迟来的聚会 自2017年1月微信小程序正式上线以来,应用数与开发者数持续增长,目前已经有超过100万个微信小程序发布且相关开发者也超过了150万。同样,...

稀土君
2018/08/06
0
0
掘金开发者大会 ∙ 微信小程序专场

掘金开发者大会 ∙ 微信小程序专场正式启动 一场迟来的聚会 自2017年1月微信小程序正式上线以来,应用数与开发者数持续增长,目前已经有超过100万个微信小程序发布且相关开发者也超过了150万...

掘金
2018/08/07
439
0
掘金开发者大会 ∙ 微信小程序专场正式启动

一场迟来的聚会 自2017年1月微信小程序正式上线以来,应用数与开发者数持续增长,目前已经有超过100万个微信小程序发布且相关开发者也超过了150万。同样,我们也可以通过掘金的数据看到 2017...

掘金官方
2018/08/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周四乱弹 —— 浙江台的电话号码倒是记得挺牢

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @巴拉迪维 :《Out On The Streets》 一直不理解北欧人对重金属和死亡摇滚的热情,听完这首歌好像理解了。#今日歌曲推荐# 《Out On The Stre...

小小编辑
今天
6
0
Leetcode PHP题解--D121 21. Merge Two Sorted Lists

D121 21. Merge Two Sorted Lists 题目链接 21. Merge Two Sorted Lists 题目分析 合并两个有序链表。 思路 逐个遍历两个链表,把小的数字塞入数组里。之后再拼起来。 最终代码 <?php/** *...

skys215
今天
5
0
mars-config 动态配置管理

mars-config 码云地址:https://gitee.com/fashionbrot/mars-config 介绍 spring mvc 、springboot 动态配置系统。http 轮训方式 更新 动态配置 软件架构 软件架构说明 后端使用技术 :sprin...

fashionbrot
今天
14
0
女朋友玩吃鸡手游被开挂老哥骗炮,我见义勇为将骗子绳之以法

大家好,我是乔哥。 晚上10点以后下班后我回到自如出租房里面,开始处理公众号粉丝发来的消息,一条一条处理,突然看到了这么几条消息,吸引了我的眼球: 然后我就和这位女粉丝小红(化名)聊...

gzc426
今天
9
0
两款软件

fadetop保护眼睛软件 Snipaste截图软件

伟大源于勇敢的开始
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部