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

青灯夜游
풀어 주다: 2021-12-29 10:21:43
앞으로
2874명이 탐색했습니다.

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

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

이 기사에서는 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<를 사용했습니다. /코드> 소개. </strong></p><p></p>조심하세요<p><strong><span style="font-size: 18px;"></span>하지만! 실제 개발시 미니프로그램의 js파일은 절대경로</strong> 도입을 지원하지 않습니다! 즉, 페이지에 공개 메소드를 도입해야 하는 경우 동일한 페이지에 여러 메소드를 도입할 때 <code>../../../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으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿