溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

小程序中怎么進(jìn)行模塊化處理

發(fā)布時(shí)間:2021-12-29 10:42:58 來源:億速云 閱讀:185 作者:小新 欄目:移動(dòng)開發(fā)

這篇文章主要介紹了小程序中怎么進(jìn)行模塊化處理,具有一定借鑒價(jià)值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。

ES6和commonJS的選擇

首先在微信小程序中不論是 ES6 或者是 commonJS 模塊化語法都是支持的,在傳統(tǒng)的web項(xiàng)目中我個(gè)人是習(xí)慣統(tǒng)一使用 ES6 模塊化語法進(jìn)行開發(fā)的。

在最初我也是將小程序中所有的通用方法抽離成單獨(dú)的文件,并使用exportexport default 導(dǎo)出,使用 import 引入。

注意點(diǎn)

但是!在實(shí)際開發(fā)中,小程序的js文件是不支持絕對(duì)路徑引入的!這意味著如果你需要在你的頁面中引入一個(gè)公用方法,你必須使用 ../../../xxx/xxx.js 的方式,當(dāng)你同一個(gè)頁面引入多個(gè)模塊時(shí),這種寫法絕對(duì)會(huì)極大的打擊你的開發(fā)熱情。

解決方式

那我們?cè)撊绾谓鉀Q這么長的引入路徑呢,在web項(xiàng)目中,我們常常會(huì)使用路徑別名的方式,例如 webpackvite 中的 resolve.alias 來縮短引入的路徑。

alias: {"@src":path.resolve("src"),

但是在原生微信小程序中,雖然可以通過 gulp 或者 webpack 等一些前端工程化的工具對(duì)小程序進(jìn)行一些改造,但是作為一個(gè)開源項(xiàng)目我希望它的啟動(dòng)過程不需要太多額外配置。最好是能夠使用原生的語法去實(shí)現(xiàn)。

最終我選擇了在 app.js中新增一個(gè)require方法用于引入模塊,這樣在頁面內(nèi)引入模塊時(shí),我們只需要使用app的實(shí)例來進(jìn)行模塊引入,這樣可以實(shí)現(xiàn)使用與app.js文件的相對(duì)路徑來引入文件.

// app.js
App({
    require(path){
        return path
    }
})

使用方式

// 使用基于app.js的相對(duì)路徑來引入文件,這樣就避免了寫很多"../"
const app = getApp()
const upload = app.require("lib/upload")

當(dāng)然這樣做也不是特別方便,首先是代碼提示的不健全,使用以上方式的話可能對(duì)于參數(shù)或者一些返回值的提示不到位,但是影響不大。如果之后我摸索出了其他比較好的實(shí)現(xiàn)方式再寫一篇文章解析。其次是必須使用全局統(tǒng)一使用commonJS 的模塊化語法啦,不過這一點(diǎn)的話問題不大。

單頁面模塊化

小程序中并沒有提供特殊的模塊化方式,比較常用的就是將一些方法抽離為單獨(dú)的js文件,然后再引入。想要避免一個(gè)頁面文件代碼太長的話最好的方式是組件化,但是在小程序中,認(rèn)為寫組件真的是一件很不爽的事情。

小程序組件擁有自己的生命周期,而且引入時(shí)必須在頁面json中提前定義,由于組件是掛在在shadow root節(jié)點(diǎn)上,如果想要和頁面共享樣式例如colorUI的全局樣式還需要寫入單獨(dú)的配置項(xiàng)styleIsolation。整體開發(fā)體驗(yàn)相比vue而言比較割裂。

基于以上的一些個(gè)人看法,我在寫小程序時(shí)比較少使用組件,如果是需要抽離wxml或者是js我通常使用以下的方法。

wxml模塊化

在小程序中我通常使用 模板template 進(jìn)行抽離復(fù)用,微信小程序模板文檔 ,模板相較于組件抽離的僅僅是部分的頁面,不包含功能部分的抽離。

以下是我抽離的一個(gè)模板,這是一個(gè)文章的列表項(xiàng),它并沒有什么單獨(dú)的功能,但是代碼很長并且卻在很多頁面中復(fù)用到,于是我將它進(jìn)行了一個(gè)抽離。把樣式都通過行內(nèi)樣式的方式寫上,這樣在哪里引入都是一樣的樣式。

<!-- 文章列表項(xiàng) -->
<import src='./avatar' />
<template name="post-item">
<view class="margin padding-sm bg-white radius flex shadow " style="position: relative;height: 350rpx;border-radius: 10rpx;">
        <!-- 背景蒙版 -->
        <view style="position: absolute;top: 0;left: 0;width: 100%;height: 100%;border-radius: 10rpx;">
                <image style="filter:blur(2px) grayscale(80%) opacity(80%)" lazy-load="{{true}}" src="{{imgList[0]}}" mode="aspectFill"></image>
        </view>
        <view style="position: absolute;top: 0;left: 0;width: 100%;height: 100%;background-color: rgba(30, 30, 30, 0.8);border-radius: 10rpx;">
        </view>

        <view style="z-index: 10;width: 100%;" class="text-white">
                <!-- 文章標(biāo)題 -->
                <view class="text-xl  ">
                        <text class="cu-tag margin-right-sm bg-color radius">{{topic}}</text>
                        <text class="text-bold">{{title}}</text>
                </view>
                <!-- 文章內(nèi)容 -->
                <view class="margin-top-xs text-sm text-cut">{{content}}</view>

                <view class="flex align-end justify-between margin-top">
                        <!-- 文章圖片 -->
                        <view class="flex align-center">
                                <view class="margin-xs" style="width: 120rpx;height: 120rpx;" wx:for="{{imgList}}" wx:key="{{index}}" wx:if="{{index < 3}}">
                                        <image class="radius" src="{{item}}" mode="aspectFill"></image>
                                </view>
                        </view>

                        <!-- 瀏覽量-點(diǎn)贊數(shù) -->
                        <view class="bg-color flex align-center text-white text-sm radius" style="padding: 4px 12px;">
                                <view class="cuIcon-attention "></view>
                                <view class="margin-left-xs">{{viewNum||0}}</view>
                                <view class="cuIcon-like margin-left"></view>
                                <view class="margin-left-xs">{{favorNum||0}}</view>
                        </view>
                </view>

                <!-- 發(fā)布時(shí)間 -->
                <view class="margin-top-xs flex align-center text-sm text-gray justify-between padding-lr-xs">
                        <view class="flex align-center">
                                <template is="avatar" data="{{size:45,avatarUrl:user.avatarUrl}}" />
                                <view class="margin-left-xs">{{user.nickName}}</view>
                        </view>

                        <view>{{createTime}}</view>
                </view>
        </view>

</view>
</template>

在頁面中使用的時(shí)候需要提前引入,由于可以引入多個(gè)模板,因此使用時(shí)需要使用 is屬性 聲明使用的是哪一個(gè)template,數(shù)據(jù)的話可以通過data屬性傳入,這里的示例是我將遍歷的item解構(gòu)后再賦值進(jìn)去。

<!-- 某個(gè)頁面 -->
<import src='../../template/post-item' />

<template data="{{...item}}" is="post-item" />

當(dāng)然使用template進(jìn)行模塊化進(jìn)行抽離的模板代碼可不能包涵太多的功能邏輯,具體的使用還是需要根據(jù)業(yè)務(wù)來噢。

js模塊化

在小程序中最基本的js模塊化就是直接抽離js文件,例如一些全局通用的方法,下面展示一個(gè)全局上傳方法的封裝

// 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: "發(fā)布中"
    })
    const imgList = []
    for (let img of this.data.form.imgList) {
            const uploadRes = await upload(img)
            imgList.push(uploadRes.fileID)
    }
    // ...其他業(yè)務(wù)代碼
    }
})

當(dāng)然以上的辦法對(duì)于通用方法來說很方便,但是對(duì)于與 頁面操作的邏輯耦合性 很高的一些業(yè)務(wù)代碼,這樣子抽離并不方便。

在vue2中我們可以使用mixin的方法模塊化代碼,在vue3中我們可以使用hook的方式模塊化代碼,但是在小程序中并沒有以上兩者的支持,最初我想仿照 vue3的hook 方式進(jìn)行頁面js封裝改造,但最終實(shí)現(xiàn)的效果不理想,于是選擇了實(shí)現(xiàn)一個(gè)模仿vue2 mixin 的方法來實(shí)現(xiàn)模塊化。

具體代碼其他博主有實(shí)現(xiàn)過,因此我就直接拿來使用了,具體代碼如下。如果不了解vue中mixin的使用方法的可以自行去官網(wǎng)看文檔,這里不做過多介紹。

// mixin.js
// 保存原生的 Page 函數(shù)
const originPage = Page
// 定義小程序內(nèi)置的屬性/方法
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)
}

實(shí)現(xiàn)的原理是改造小程序中的Page()函數(shù),小程序的每一個(gè)頁面都是通過調(diào)用Page({option})方法來實(shí)現(xiàn)的,在option參數(shù)中傳入頁面相關(guān)的data和聲明周期函數(shù)及其他方法。

我們通過在Page方法的參數(shù)option中增加一個(gè)mixin屬性,這個(gè)屬性可以傳入一個(gè)數(shù)組,數(shù)組即是每一個(gè)要混入的模塊,每一個(gè)模塊的結(jié)構(gòu)其實(shí)與參數(shù)option是一樣的,我們只需要將所有混入的模塊與頁面自身的option進(jìn)行一個(gè)參數(shù)和方法的合并就能實(shí)現(xiàn)一個(gè)mixin的功能。

使用的方法是現(xiàn)在app.js中引入mixin.js

// app.js
require("./mixins.js")
App({
// ...其他代碼
})

然后我們寫一個(gè)常規(guī)頁面的js,業(yè)務(wù)代碼大家不用看,主要關(guān)注Page的屬性中多了一個(gè)mixins選項(xiàng),而mixins數(shù)組中有一個(gè)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'], //可以指定是原圖還是壓縮圖,默認(rèn)二者都有
			sourceType: ['album', 'camera'], //從相冊(cè)選擇
			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 內(nèi)都是關(guān)聯(lián)性較強(qiáng)的屬性與方法,因此就可以抽離出來,這樣頁面的js就會(huì)更加精簡啦,如果有更多的代碼就根據(jù)自己對(duì)于功能的判斷進(jìn)行抽離,然后放在頁面對(duì)于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})
	},
}

注意點(diǎn)

但是使用mixin也有著與vue中同樣的問題就是變量及方法的來源不好追溯,變量是在那個(gè)位置定義的比較難以定位,這時(shí)就更加依賴開發(fā)者的開發(fā)規(guī)范以及命名方式了,再不濟(jì)也可以每一個(gè)方法寫一個(gè)獨(dú)有的注釋嘛~

感謝你能夠認(rèn)真閱讀完這篇文章,希望小編分享的“小程序中怎么進(jìn)行模塊化處理”這篇文章對(duì)大家有幫助,同時(shí)也希望大家多多支持億速云,關(guān)注億速云行業(yè)資訊頻道,更多相關(guān)知識(shí)等著你來學(xué)習(xí)!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI