小程序模块化

原本需要 24 个工作日才能完成的任务,我却在 0.5 个工作日内完成了,是如何实现的呢?趁着放假有空,写篇文章给大家分享一下我们小程序模块化加速项目开发的思路吧。

阅读基础:有小程序项目经验,有查阅官方文档习惯的小伙伴

随着公司小程序项目日益繁多,仅仅靠着官方提供的框架、组件、API,已经远远不能满足项目高效迭代的要求了,于是我们组内萌生了对小程序进行模块化的想法。

实际项目中我们对小程序模块化已经涉及各个模块,我总结一下,从三个方向跟大家分享我们不一样的模块化思路:Page+basePage适配层

Page+

Page()作为页面的入口,我们可以通过对其入参对象的封装实现:生命周期的改造、全局状态管理和新增页面功能。

官方删除了小程序分享回调 complete,一起来尝试将其恢复吧。一般我们的逻辑是这样的:

// pages/index/index.js
Page({
  // 数据初始化
  data: {
    shareFlag: false,        //页面是否处于分享中
    shareComplete: false     //分享回调事件
  },
  // onShow 生命周期
  onShow: function () {
    const { shareFlag, shareComplete } = this.data
    if( shareFlag ){
      this.data.shareFlag = false    //变量不涉及页面渲染,不使用 setData
      shareComplete && shareComplete()
    }
  },
  // 分享事件
  onShareAppMessage: function () {
    let shareInfo = {
      title: '分享测试标题',
      path: '',
      complete: function () {
        console.log('页面分享成功啦~')
      }
    }
    this.data.shareFlag = true
    this.data.shareComplete = typeof (shareInfo.complete) == 'function' ? shareInfo.complete : false
    return shareInfo
  }
})

在单页面内实现分享回调这样操作是可行的,如果多页面、多项目都要实现该功能,重复拷贝代码,则显格外得繁琐。

我们来将这个功能抽离封装一下吧。

// pages/index/index.js
import PagePlus from './pagePlus.js'
PagePlus({
  // 分享事件
  onShareAppMessage: function () {
    return {
      title: '分享测试标题',
      path: '',
      complete: function () {
        console.log('页面分享成功啦~')
      }
    }
  }
})
// pages/index/pagePlus.js
const PagePlus = (pageObj) => {
  const _onShow = pageObj.onShow,
    _onShareAppMessage = pageObj.onShareAppMessage,
    _data = {
      shareStatus: false,   //页面是否处于分享中
      shareComplete: false  //分享回调事件
    }
  Object.assign(_data, pageObj.data)
  delete pageObj.data
  pageObj.onShow = function () {
    typeof _onShow == 'function' && _onShow.apply(this)
    const { shareStatus, shareComplete } = this.data
    if (shareStatus) {
      this.data.shareStatus = false //变量不涉及页面渲染,不使用 setData
      shareComplete && shareComplete()
    }
  }
  pageObj.onShareAppMessage = function () {
    const shareInfo = typeof _onShareAppMessage == 'function' && _onShareAppMessage.apply(this)
    this.data.shareStatus = true
    shareInfo && (this.data.shareComplete = shareInfo.complete)
    return shareInfo
  }
  Page({ data: _data, ...pageObj })
}
export default PagePlus

我们来增加一个新的生命周期回调——onReshow(页面非首次显示回调,常用于详情页操作影响上一页列表数据的场景)。

// pages/index/index.js
import PagePlus from './pagePlus.js'
PagePlus({
  // 监听页面非首次显示
  onReshow: function(){
    console.log('onReshow lifeCallBack')
  },
  onShareAppMessage: function () {
    return {
      title: '分享测试标题',
      path: '',
      complete: function () {
        console.log('页面分享成功啦~')
      }
    }
  }
})
// pages/index/pagePlus.js
class BasePage{
  data = {
    pagePlus: {
      shareStatus: false,   //页面是否处于分享中
      shareComplete: false, //分享回调事件
      firstEnter: true      //第一次进入页面
    }
  }
  methods = {
    onShow: this.onShow,
    onShareAppMessage: this.onShareAppMessage,
    onReshow: this.onReshow
  }
  onShow(){
    const { shareStatus, shareComplete, firstEnter } = this.data.pagePlus
    if (firstEnter) {
      this.data.pagePlus.firstEnter = false
    } else {
      this.onReshow()
    }
    if (shareStatus) {
      this.data.pagePlus.shareStatus = false
      shareComplete && shareComplete()
    }
  }
  onShareAppMessage(shareInfo){
    this.data.pagePlus.shareStatus = true
    shareInfo && (this.data.pagePlus.shareComplete = shareInfo.complete)
  }
}

const PagePlus = (pageObj) => {
  const basePage = new BasePage()
  for (var i in basePage.methods) {
    basePage.methods[i] = (() => {
      const key = i
      const _temFn = basePage.methods[key]
      return function () {
        if (key == 'onShareAppMessage') {
          const shareInfo = typeof pageObj[key] == 'function' && pageObj[key].apply(this, arguments)
          _temFn.apply(this, [shareInfo])
          return shareInfo
        }
        typeof pageObj[key] == 'function' && pageObj[key].apply(this, arguments)
        typeof _temFn == 'function' && _temFn.apply(this, arguments)
      }
    })()
  }
  Object.assign(basePage.data, pageObj.data)
  delete pageObj.data
  Page({ data: basePage.data, ...pageObj, ...basePage.methods })
}

export default PagePlus

自此,我们修改了原生的生命周期回调和增加了新的生命周期回调。当然我们还能为 Page+ 赋予更多的功能,例如:

页面刷新:下拉自动刷新当前页。

定时器自动清除:离开页面时,自动清除页面执行的定时器。

全局状态管理:页面间数据共享,相关数据关联的组件即时渲染更新。

相关的代码实现,大家可以自己思考一下怎么实现;我的实现细节,如果大家感兴趣的话就在下方给我留言吧,你们的回复是我更新的动力哦。

basePage

小程序页面彼此独立,使用 Component 都需要各自引用,为了实现页面公共 Component 的统一管理,这个时候就可以引入 basePage 的概念:以 basePage 作为父组件,其他公共 Component 作为子组件,页面通过 basePage 对公共 Component 进行管理。

实现原理

1、定义一个 Component ,作为 basePage 。

2、每个页面统一引用 basePage ,且规定页面的元素都需要写到 标签内部 。

3、通过 basePage 引用页面公共的 Component ,并进行业务逻辑编辑。

实现细节

实际使用过程中,我发现有两个问题:

1、Page 和 basePage 通信是非常频繁的,需要通过 WXML 数据绑定和 triggerEvent 触发事件,略显麻烦。

2、setTimeout、webSocket 等后台进程,可能触发非当前显示页面的渲染更新,而绝大部分情况,我们只需要当前显示页面的渲染更新。

针对这两种场景的优化,我们可以把当前显示页面的 basePage 实例对象赋值到 global 的某个具体变量;每当 Page 触发 show 生命周期回调的时候,我们就对这个变量赋值的实例对象进行更新,这样我们就可以通过 global 的变量直接操作当前显示页面的 basePage 了。

部分代码示例

{
  "文件路径": "pages/index/index.json",
  "usingComponents": {
    "basePage": "../../components/basePage/index"
  }
}


  
// components/basePage/index.js
Component({
  /**
   * Component 所在页面的生命周期函数
   */
  pageLifetimes: {
    show: function () {
      global.basePage = this
    },
    hide: function () {
      global.basePage = null
    }
  }
})
{
  "文件路径": "components/basePage/index.json",
  "说明": "在此处统一引入页面公共的 Component",
  "component": true,
  "usingComponents": {}
}

适配层

如果你的项目对代码后续维护、迭代和可移植性有较高需求,或者需要多项目并行,这个时候通过适配层去调用各个功能模块就显得尤为重要。适配层方面我做的还是比较粗糙的,如果有建议欢迎指出。

适配层的时机

项目不是 bugfix 级别的迭代,都有适配层设计的必要。

如果是新项目,心底不认为自己是“咸鱼”而是代码的“亲爹”,适配层完全可以作为标配去实现;这就是展现自己对代码全局观的时候了,把自己对代码的理解都用适配层去诠释吧。

如果是旧项目迭代,在项目排期允许的情况下,尽可能理解原代码的基本实现细节;对比新的项目是要束手束脚一些,适配层的设计要在尽可能少改变原有代码的情况下进行;如果排期比紧急,适配层的完整实现可以在几个版本迭代中逐步实现

模块设计必须高内聚低耦合

如果功能模块的设计过于松散、耦合复杂,这就意味着适配层将需要做各种兼容,这和适配层设计的初衷背道而驰,不做也罢。

配置文件

如果你的代码有移植性要求,为这些不同环境准备对应的配置文件吧,配置文件可以通过自制脚手架实现,也可以粗暴地手动替换,在保证尽可能不出错的情况下实现即可。

功能模块的入口
所有整合的功能模块都需要通过适配层进行调用,适配层就是你的“王之财宝”。

规范 && 文档

适配层是从代码的全局考虑,如果是项目是分工完成,项目的开发人员都需要遵守适配层规范进行代码开发;文档我一直都认为都是非常必要的,但还是经常会懈怠,没有进行完整的文档编写,但我基本会在所有项目成员都能理解适配层的情况下,进行简单的口头说明。

因为开心说一些废话

一次需求迭代中,几乎涉及手头上的所有小程序项目;刚好就在需求前的半个月,我们小组完成了对所有项目模块化改造;虽然需求来得很急,我们还是很完美的实现了。毕竟模块化之前,每个项目的改造都是独立的工作量;模块化之后,就只有适配层迭代的工作量了。不过真是辛苦了测试小伙伴,因为对所有项目进行模块化改造,意味着测试小伙伴对所有项目进行回归测试,感谢测试小伙伴,比心!

这篇文章,对 Page+ 的具体实现展示比较详细,感觉对 basePage 和适配层讲的都比较偏概念。毕竟这部分内容都和业务逻辑联系比较紧密,很难抽象深入讲解。刚好还有假期还有一段时间,如果自己还有时间就再写一篇关于最近项目的模块化剖析吧,哈哈。

你可能感兴趣的:(模块化,前端,javascript,小程序)