>  기사  >  위챗 애플릿  >  작은 프로그램에서 모듈화를 우아하게 구현하는 방법에 대한 간략한 분석은 무엇입니까?

작은 프로그램에서 모듈화를 우아하게 구현하는 방법에 대한 간략한 분석은 무엇입니까?

青灯夜游
青灯夜游앞으로
2021-12-29 10:21:432847검색

미니 프로그램에서 우아하게 모듈화하는 방법은 무엇입니까? 이 글은 여러분에게 작은 프로그램을 우아하게 모듈화하는 방법을 가르쳐 줄 것입니다. 여러분에게 도움이 되기를 바랍니다.

작은 프로그램에서 모듈화를 우아하게 구현하는 방법에 대한 간략한 분석은 무엇입니까?

이 기사에서는 WeChat 미니 프로그램에서 모듈식 처리를 우아하게 구현하는 방법에 대해 설명합니다. 최근 개발 경험의 요약을 통해 WeChat 애플릿 개발의 효율성을 높이고 정신적 부담을 줄일 수 있는 몇 가지 방법을 탐색해 보겠습니다.

ES6과 commonJS의 선택

우선 위챗 애플릿에서는 ES6이든 commonJS이든 모듈식 구문을 지원합니다. 개인적으로 저는 개발을 위해 ES6 모듈식 구문을 사용하는 데 익숙합니다. ES6 或者是 commonJS 模块化语法都是支持的,在传统的web项目中我个人是习惯统一使用 ES6 模块化语法进行开发的。

在最初我也是将小程序中所有的通用方法抽离成单独的文件,并使用exportexport default 导出,使用 import 引入。

注意点

但是!在实际开发中,小程序的js文件是不支持绝对路径引入的!这意味着如果你需要在你的页面中引入一个公用方法,你必须使用 ../../../xxx/xxx.js 的方式,当你同一个页面引入多个模块时,这种写法绝对会极大的打击你的开发热情。

解决方式

那我们该如何解决这么长的引入路径呢,在web项目中,我们常常会使用路径别名的方式,例如 webpackvite 中的 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 进行抽离复用,微信小程序模板文档 ,模板相较于组件抽离的仅仅是部分的页面,不包含功能部分的抽离。

以下是我抽离的一个模板,这是一个文章的列表项,它并没有什么单独的功能,但是代码很长并且却在很多页面中复用到,于是我将它进行了一个抽离。把样式都通过行内样式的方式写上,这样在哪里引入都是一样的样式。

<!-- 文章列表项 -->
<import src=&#39;./avatar&#39; />
<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">
                <!-- 文章标题 -->
                <view class="text-xl  ">
                        <text class="cu-tag margin-right-sm bg-color radius">{{topic}}</text>
                        <text class="text-bold">{{title}}</text>
                </view>
                <!-- 文章内容 -->
                <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>

                        <!-- 浏览量-点赞数 -->
                        <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>

                <!-- 发布时间 -->
                <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>

在页面中使用的时候需要提前引入,由于可以引入多个模板,因此使用时需要使用 is属性 声明使用的是哪一个template,数据的话可以通过data처음에는 애플릿에 있는 공통 메서드도 모두 별도의 파일로 추출하고 export 또는 export default를 사용하여 내보내고 import 소개.

조심하세요

하지만! 실제 개발시 미니프로그램의 js파일은 절대경로 도입을 지원하지 않습니다! 즉, 페이지에 공개 메소드를 도입해야 하는 경우 동일한 페이지에 여러 메소드를 도입할 때 ../../../xxx/xxx.js를 사용해야 합니다. 모듈을 작성할 때 이런 방식으로 작성하면 개발에 대한 열정이 크게 약화될 것입니다.

🎜🎜🎜Solution🎜🎜🎜🎜그러면 이렇게 긴 가져오기 경로를 어떻게 해결합니까? 웹 프로젝트에서는 resolve in 🎜webpack🎜 또는 🎜vite🎜 .alias와 같은 경로 별칭을 자주 사용합니다. 가져온 경로를 단축합니다. 🎜
<!-- 某个页面 -->
<import src=&#39;../../template/post-item&#39; />

<template data="{{...item}}" is="post-item" />
🎜하지만 기본 WeChat 미니 프로그램에서는 🎜gulp🎜 또는 🎜webpack🎜과 같은 일부 프런트 엔드 엔지니어링 도구를 사용하여 미니 프로그램을 일부 수정할 수 있지만 오픈 소스 프로젝트로서 시작 프로세스가 추가 구성이 너무 많이 필요하지 않습니다. 이를 구현하려면 기본 구문을 사용하는 것이 가장 좋습니다. 🎜🎜결국 모듈을 소개하기 위해 app.js에 require 메소드를 추가하기로 결정했습니다. 이런 식으로 페이지에 모듈을 소개할 때 해당 인스턴스만 사용하면 됩니다. 이 방법으로 app.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,
	})
}
🎜Usage method🎜
// 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)
    }
    // ...其他业务代码
    }
})
🎜물론 이는 별로 편리하지 않습니다. 우선, 코드 프롬프트가 완벽하지 않습니다🎜, 위의 방법을 사용하세요. 그렇다면 매개변수나 일부 반환 값에 대한 프롬프트가 제자리에 있지 않을 수 있지만 영향은 크지 않습니다. 앞으로 다른 더 나은 구현 방법을 찾으면 이를 분석하는 기사를 작성하겠습니다. 둘째, 🎜commonJS🎜의 모듈식 구문을 전역적으로 균일하게 사용해야 하지만 이는 큰 문제가 되지 않습니다. 🎜

🎜단일 페이지 모듈화🎜🎜🎜미니 프로그램은 특별한 모듈화 방법을 제공하지 않습니다. 가장 일반적인 방법은 일부 메서드를 별도의 js 파일로 추출한 다음 소개하는 것입니다. 코드가 너무 긴 페이지 파일을 피하려면 가장 좋은 방법은 이를 컴포넌트화하는 것입니다. 그러나 작은 프로그램에서는 컴포넌트를 작성하는 것이 정말 불편합니다. 🎜🎜애플릿 구성 요소에는 자체 수명 주기가 있으며 소개할 때 🎜페이지 json🎜에서 미리 정의해야 합니다. 구성 요소가 🎜섀도 루트 노드🎜에 걸려 있기 때문에 페이지와 스타일을 공유하려면 다음과 같이 하세요. colorUI의 전역 스타일로 별도의 구성 항목 styleIsolation🎜을 작성해야 합니다. 전반적인 개발 경험은 vue에 비해 상대적으로 단편화되어 있습니다. 🎜🎜위의 개인적인 의견에 따르면, 저는 작은 프로그램을 작성할 때 컴포넌트를 거의 사용하지 않습니다. wxml이나 js를 추출해야 하는 경우에는 다음과 같은 방법을 주로 사용합니다. 🎜🎜🎜🎜wxml 모듈화🎜🎜🎜🎜작은 프로그램에서는 추상화와 재사용을 위해 보통 템플릿을 사용합니다. WeChat Mini 프로그램 템플릿 문서는 구성 요소에 비해 템플릿은 페이지의 일부만 추출하며 그렇지 않습니다. 기능적인 부분의 추출을 포함합니다. 🎜🎜다음은 제가 추출한 템플릿입니다. 기사의 목록 항목입니다. 독립적인 기능은 없지만, 코드가 매우 길고 여러 페이지에서 재사용되므로 분리를 수행합니다. 모든 스타일은 🎜인라인 스타일🎜을 사용하여 작성하여 어디서든 동일한 스타일이 도입되도록 합니다. 🎜
// mixin.js
// 保存原生的 Page 函数
const originPage = Page
// 定义小程序内置的属性/方法
const prop = [&#39;data&#39;, &#39;properties&#39;, &#39;options&#39;]
const methods = [&#39;onLoad&#39;, &#39;onReady&#39;, &#39;onShow&#39;, &#39;onHide&#39;, &#39;onUnload&#39;, &#39;onPullDownRefresh&#39;, &#39;onReachBottom&#39;, &#39;onShareAppMessage&#39;, &#39;onPageScroll&#39;, &#39;onTabItemTap&#39;]

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)
}
🎜는 페이지에서 사용할 때 미리 소개해야 합니다. 여러 템플릿을 소개할 수 있으므로 is 속성을 사용하여 어떤 템플릿을 사용할지 선언해야 합니다. code>data 속성이 전달됩니다. 여기의 예는 탐색된 항목🎜을 분해한 다음 여기에 할당하는 것입니다. 🎜
// app.js
require("./mixins.js")
App({
// ...其他代码
})
🎜물론, 모듈화 및 추출을 위해 템플릿을 사용하는 템플릿 코드에는 너무 많은 기능적 논리가 포함될 수 없습니다. 구체적인 용도는 여전히 비즈니스에 기반해야 합니다. 🎜🎜🎜🎜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 = [&#39;data&#39;, &#39;properties&#39;, &#39;options&#39;]
const methods = [&#39;onLoad&#39;, &#39;onReady&#39;, &#39;onShow&#39;, &#39;onHide&#39;, &#39;onUnload&#39;, &#39;onPullDownRefresh&#39;, &#39;onReachBottom&#39;, &#39;onShareAppMessage&#39;, &#39;onPageScroll&#39;, &#39;onTabItemTap&#39;]

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(&#39;user&#39;),
		form: {
			title: "",
			topic: "",
			content: "",
			imgList: []
		}
	},
	chooseImg() {
		wx.chooseImage({
			count: 9 - this.data.form.imgList.length,
			sizeType: [&#39;original&#39;], //可以指定是原图还是压缩图,默认二者都有
			sourceType: [&#39;album&#39;, &#39;camera&#39;], //从相册选择
			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中同样的问题就是变量及方法的来源不好追溯,变量是在那个位置定义的比较难以定位,这时就更加依赖开发者的开发规范以及命名方式了,再不济也可以每一个方法写一个独有的注释嘛~

【相关学习推荐:小程序开发教程

위 내용은 작은 프로그램에서 모듈화를 우아하게 구현하는 방법에 대한 간략한 분석은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 juejin.cn에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제