### [浅析小程序中如何优雅地进行模块化处理?](https://www.prosaas.cn/article/16325)
**Published:** 2021-12-29T02:21:43
**Author:** 5201314
**Excerpt:** 小程序中如何优雅地进行模块化处理?本篇文章就来手把手教大家如何优雅的在小程序中进行模块化,希望对大家有所帮助!…
小程序中如何优雅地进行模块化处理?本篇文章就来手把手教大家如何优雅的在小程序中进行模块化,希望对大家有所帮助!

这一篇文章就讲讲在微信小程序中如何优雅地进行模块化处理。通过最近的一些开发经验进行浓缩总结,探索一些可以提升微信小程序开发效率和降低心智负担的方法。
## ES6和commonJS的选择
首先在微信小程序中不论是 `ES6` 或者是 `commonJS` 模块化语法都是支持的,在传统的web项目中我个人是习惯统一使用 `ES6` 模块化语法进行开发的。
在最初我也是将小程序中所有的通用方法抽离成单独的文件,并使用`export` 或 `export default` 导出,使用 `import` 引入。
**注意点**
但是!在实际开发中,小程序的js文件是**不支持绝对路径引入**的!这意味着如果你需要在你的页面中引入一个公用方法,你必须使用 `../../../xxx/xxx.js` 的方式,当你同一个页面引入多个模块时,这种写法绝对会极大的打击你的开发热情。
**解决方式**
那我们该如何解决这么长的引入路径呢,在web项目中,我们常常会使用路径别名的方式,例如 **webpack**或**vite** 中的 `resolve.alias` 来缩短引入的路径。
alias: {"@src":path.resolve("src"),
但是在原生微信小程序中,虽然可以通过 **gulp** 或者 **webpack** 等一些前端工程化的工具对小程序进行一些改造,但是作为一个开源项目我希望它的启动过程不需要太多额外配置。最好是能够使用原生的语法去实现。
最终我选择了在 app.js中新增一个`require`方法用于引入模块,这样在页面内引入模块时,我们只需要使用app的实例来进行模块引入,这样可以实现使用与`app.js`文件的相对路径来引入文件.
// app.js
App({
require(path){
return path
}
})
使用方式
// 使用基于app.js的相对路径来引入文件,这样就避免了写很多"../"
const app = getApp()
const upload = app.require("lib/upload")
当然这样做也不是特别方便,首先是**代码提示的不健全**,使用以上方式的话可能对于参数或者一些返回值的提示不到位,但是影响不大。如果之后我摸索出了其他比较好的实现方式再写一篇文章解析。其次是必须使用全局统一使用**commonJS** 的模块化语法啦,不过这一点的话问题不大。
## **单页面模块化**
小程序中并没有提供特殊的模块化方式,比较常用的就是将一些方法抽离为单独的js文件,然后再引入。想要避免一个页面文件代码太长的话最好的方式是组件化,但是在小程序中,认为写组件真的是一件很不爽的事情。
小程序组件拥有自己的生命周期,而且引入时必须在**页面json中提前定义**,由于组件是挂在在**shadow root节点**上,如果想要和页面共享样式例如colorUI的全局样式还需要写入**单独的配置项styleIsolation**。整体开发体验相比vue而言比较割裂。
基于以上的一些个人看法,我在写小程序时比较少使用组件,如果是需要抽离wxml或者是js我通常使用以下的方法。
**wxml模块化**
在小程序中我通常使用 `模板template` 进行抽离复用,微信小程序模板文档 ,模板相较于组件抽离的仅仅是部分的页面,不包含功能部分的抽离。
以下是我抽离的一个模板,这是一个文章的列表项,它并**没有什么单独的功能**,但是**代码很长**并且却**在很多页面中复用**到,于是我将它进行了一个抽离。把样式都通过**行内样式**的方式写上,这样在哪里引入都是一样的样式。
{{topic}}
{{title}}
{{content}}
{{viewNum||0}}
{{favorNum||0}}
{{user.nickName}}
{{createTime}}
在页面中使用的时候需要提前引入,由于可以引入多个模板,因此使用时需要使用 `is属性` 声明使用的是哪一个template,数据的话可以通过`data`属性传入,这里的示例是我将**遍历的item**解构后再赋值进去。
当然使用template进行模块化进行抽离的模板代码可不能包涵太多的功能逻辑,具体的使用还是需要根据业务来噢。
**js模块化**
在小程序中最基本的js模块化就是直接抽离js文件,例如一些全局通用的方法,下面展示一个全局上传方法的封装
// lib/upload.js
// 上传方法
module.exports = async function upload(path) {
return await wx.cloud.uploadFile({
cloudPath: new Date().getTime() + path.substring(path.lastIndexOf(".")),
filePath: path,
})
}
// pages/form/form.js
const app = getApp()
const upload = app.require("lib/upload")
Page({
async submit() {
wx.showLoading({
mask: true,
title: "发布中"
})
const imgList = \[\]
for (let img of this.data.form.imgList) {
const uploadRes = await upload(img)
imgList.push(uploadRes.fileID)
}
// ...其他业务代码
}
})
当然以上的办法对于通用方法来说很方便,但是对于与 **页面操作的逻辑耦合性** 很高的一些业务代码,这样子抽离并不方便。
在vue2中我们可以使用**mixin**的方法模块化代码,在vue3中我们可以使用**hook**的方式模块化代码,但是在小程序中并没有以上两者的支持,最初我想仿照 **vue3的hook** 方式进行页面js封装改造,但最终实现的效果不理想,于是选择了实现一个模仿vue2 **mixin** 的方法来实现模块化。
具体代码其他博主有实现过,因此我就直接拿来使用了,具体代码如下。如果不了解vue中mixin的使用方法的可以自行去官网看文档,这里不做过多介绍。
// mixin.js
// 保存原生的 Page 函数
const originPage = Page
// 定义小程序内置的属性/方法
const prop = \['data', 'properties', 'options'\]
const methods = \['onLoad', 'onReady', 'onShow', 'onHide', 'onUnload', 'onPullDownRefresh', 'onReachBottom', 'onShareAppMessage', 'onPageScroll', 'onTabItemTap'\]
Page = (options) => {
if (Array.isArray(options.mixins)) {
const mixins = options.mixins
delete options.mixins
mixins.forEach((mixin) => {
for (let \[key, value\] of Object.entries(mixin)) {
if (prop.includes(key)) {
// 混入属性
options\[key\] = {
...value,
...options\[key\]
}
} else if (methods.includes(key)) {
// 混入原生方法
const originFunc = options\[key\]
options\[key\] = function (...args) {
value.call(this, ...args)
return originFunc && originFunc.call(this, ...args)
}
} else {
// 混入普通方法
options = {
...mixin,
...options
}
}
}
})
}
originPage(options)
}
实现的原理是改造小程序中的Page()函数,小程序的每一个页面都是通过调用`Page({option})`方法来实现的,在`option`参数中传入页面相关的data和声明周期函数及其他方法。
我们通过在Page方法的参数`option`中增加一个`mixin`属性,这个属性可以传入一个数组,数组即是每一个要混入的模块,每一个模块的结构其实与参数`option`是一样的,我们只需要将所有混入的模块与页面自身的option进行一个参数和方法的合并就能实现一个`mixin`的功能。
使用的方法是现在app.js中引入mixin.js
// app.js
require("./mixins.js")
App({
// ...其他代码
})
然后我们写一个常规页面的js,业务代码大家不用看,主要关注Page的属性中多了一个mixins选项,而`mixins`数组中有一个`topic`模块。
// pages/form/form.js
const app = getApp()
const upload = app.require("lib/upload")
const to = app.require("lib/awaitTo")
const db = wx.cloud.database()
Page({
mixins: \[require("./mixins/topic")\],
data: {
user: wx.getStorageSync('user'),
form: {
title: "",
topic: "",
content: "",
imgList: \[\]
}
},
chooseImg() {
wx.chooseImage({
count: 9 - this.data.form.imgList.length,
sizeType: \['original'\], //可以指定是原图还是压缩图,默认二者都有
sourceType: \['album', 'camera'\], //从相册选择
success: (res) => {
res.tempFilePaths = res.tempFilePaths
if (this.data.form.imgList.length != 0) {
this.setData({ "form.imgList": this.data.form.imgList.concat(res.tempFilePaths) })
} else {
this.setData({ "form.imgList": res.tempFilePaths })
}
}
});
},
async delImg(e) {
const index = e.currentTarget.dataset.index
const temp = this.data.form.imgList
temp.splice(index, 1)
this.setData({ "form.imgList": temp })
}
})
由于 `topic` 内都是关联性较强的属性与方法,因此就可以抽离出来,这样页面的js就会更加精简啦,如果有更多的代码就根据自己对于功能的判断进行抽离,然后放在页面对于**mixin目录**中即可!
// // pages/form/mixin/topic.js
const db = wx.cloud.database()
module.exports = {
data:{
topic:{
flag:false,
list:\[\]
},
},
onLoad(options) {
this.getTopic()
},
async getTopic(){
const res = await db.collection("topic").get()
this.setData({"topic.list":res.data})
},
clearTopic(){
this.setData({"form.topic":""})
},
toggleTopic(e){
console.log(e.currentTarget.dataset)
const flag = e.currentTarget.dataset.flag
this.setData({"topic.flag":flag})
},
}
**注意点**
但是使用`mixin`也有着与vue中同样的问题就是变量及方法的来源不好追溯,变量是在那个位置定义的比较难以定位,这时就更加依赖开发者的开发规范以及命名方式了,再不济也可以每一个方法写一个独有的注释嘛~
【相关学习推荐:小程序开发教程】
以上就是浅析小程序中如何优雅地进行模块化处理?的详细内容,更多请关注知企PROSAAS其它相关文章!
**Categories:** 后端开发
---