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

准备

TL;DR;

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

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

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

安装工具和依赖模块

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

nwjs可能存在一些小bug, 写代码的时候注意一下就好.

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

用npm命令开始一个微信小程序项目

mkdir myapp &&cdmyappnpm 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

其中, 模块是对微信小程序API的wrapper, 提供了更易于使用的API, 是基于Redux对微信小程序进行状态管理.

建立项目目录结构如下

编写构建脚本

//webpack.config.js

varpath=require('path'),

webpack=require('webpack')

varjsLoader={ test:/\.js$/,//你也可以用.es6做文件扩展名, 然后在这里定义相应的patternloader:'babel', query:{//代码转译预设, 并不包含ES新特性的polyfill, polyfill需要在具体代码中显示requirepresets:["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'),//定义要打包的文件//比如: `}` 的意思是: 将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:[

newwebpack.NoErrorsPlugin(),//通常会需要区分dev和production, 建议定义这个变量//编译后会在global中定义`process.env`这个Objectnewwebpack.DefinePlugin({

'process.env':{

'NODE_ENV':JSON.stringify('development') } }) ]}

定义npm命令

笔者比较喜欢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/"]}

这里就是构建的命令了, 成败在此一举 :)

//package.json

"scripts":{

...,

//带上watch选项, 实时编译修改, 由于小程序开发工具也监视应用文件的修改, 所以es6目录下的js文件修改, 将导致小程序开发工具自动重新加载"build":"webpack --watch --progress --colors --config webpack.config.js"

},

写应用代码

总算进入正题了(工欲善其事,...), 借助上述的 weapp 和 redux-weapp, 希望你会感到很舒服~~.

在这个范例中, 我们目标是去查询 github/octokit 的开源项目, 并显示在小程序中.

myapp模块

定义store:

这里只是简单的范例, 实际中会有比较复杂的store shape, 需要引入更多的middleware来处理动作和状态的变化.

///es6/store.js

import{createStore,applyMiddleware,bindActionCreators}from'redux'

importthunkfrom'redux-thunk'

importreducersfrom'./reducers'

exportdefaultfunction(initState={}){

returncreateStore( reducers, initState,

applyMiddleware(thunk) )}

定义reducers:

Reducer就是处理因Store dispatch actions时发生的状态变化的function, 参数总是为(state, action)

///es6/reducers.js

import{combineReducers}from'redux'

//处理projects逻辑

constprojects=(state=[],action)=>{

switch(action.type) {

case'PROJECTS_LOADED':

returnstate.concat[action.payload]

//other cases}

returnstate}

//将多个reducer合并起来//这里就可以看出store的结构了, 是不是很 predictable ?

exportdefaultcombineReducers({ projects})

定义actions:

Action通常是个Plain Object, 总是被Store dispatch, 描述了"发生了什么, 结果是什么"的逻辑

///es6/actions.js

import{weapp}from'weapp'

//更好的方法是定义一个api module, 来处理网络请求

consthttp=weapp.Http('https://api.github.com')

//这是一个异步action, redux-thunk会处理返回值为Function的action(可以编入绕口令大全了~~)

exportconstloadProjects=(org)=>{

return(dispatch)=>{

http.get(`/orgs/${org}/repos`).then(response=>{

//让store去广播'PROJECTS_LOADED'这件事情发生了dispatch({ type:'PROJECTS_LOADED', payload:response }) }) }}

myapp模块入口:

///es6/myapp.js

import{bindActionCreators}from'redux'

import{weapp}from'weapp'

importconnectfrom'redux-weapp'

importstorefrom'./store'

importactionsfrom'./actions'

export{weapp,connect,bindActionCreators,store,actions}

小程序模块

入口文件: 和

///app.js

App({

//方便起见, 这里不做任何life-cycle处理

})

{

"pages": [

"pages/projects/projects"],

"window": {

"navigationBarTitleText":"Orchid"},

"networkTimeout": {

"request":10000,

"downloadFile":10000},

"debug":true

}

页面逻辑:

如上定义, 小程序的启动页面是

///pages/projects/projects.js

//引入编译过的modules

import{

weapp,connect,bindActionCreators,store,actions

}from'../../lib/app'

//标准Page定义Object

constconfig={ data:{ projects:[]//for init-render},

onReady(){//哪里来的 loadProjects? 往下看this.loadProjects('octokit') },

onStateChange(nextState){

this.setData() }}

//connect store with page

constpage=connect.Page( store,//required

//这个页面只关注projects变化(state)=>(),//将Action定义与Store.dispatch binding在一起, 这样就是一个可以发起对github API的请求了

(dispatch)=>{

return{ loadProjects:bindActionCreators(actions.loadProjects, dispatch) } })(config)

//启动被connect过的页面

Page(page)

页面UI:

{}

参考

https://github.com/nwjs/nw.js/issues

http://redux.js.org

https://webpack.github.io

https://facebook.github.io/jest/docs

https://babeljs.io

https://github.com/xixilive/weapp

https://github.com/xixilive/redux-weapp

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180510G23J8R00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券