准备
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
领取专属 10元无门槛券
私享最新 技术干货