加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 资源网站 > 资源 > 正文

小程序技能进阶回忆录 - 在缺少组件化的日子里

发布时间:2020-12-14 19:07:54 所属栏目:资源 来源:网络整理
导读:战争,信念,意志和情感,这些散发着光芒和硝烟的词汇,象一枚枚炮弹轰入我们现在的生活。历史的记忆不会被抹灭。 当我们在各自项目里幸福的拷贝着官方代码 demo,在? componnets ?文件夹里使用? Component 方法书写一个个组件时,不要忘记,在 2018 年上半

战争,信念,意志和情感,这些散发着光芒和硝烟的词汇,象一枚枚炮弹轰入我们现在的生活。历史的记忆不会被抹灭。

当我们在各自项目里幸福的拷贝着官方代码 demo,在?componnets?文件夹里使用?Component方法书写一个个组件时,不要忘记,在 2018 年上半年以前,小程序是没有提供组件化方案的。

当时,主要有两种解决方法,一种是 WePY 拷贝法,另一种则是 template 法。

WePY 拷贝法

比如有个最简单的按钮组件:

<!-- components/button.wpy -->
<template>
  <view class="button">
    <button @tap="onTap">点这里</button>
  </view>
</template>

<!-- pages/index.wpy -->
<"container">
    <wpy-button /> // button 组件1
    <wpy-button2 /> // button 组件2
  </template>
复制代码

经过编译后结果如下:

<"container">
  <button bindtap="$wpyButton$onTap">点这里</view>
  <"$wpyButton2$onTap">点这里</view>
复制代码

为了方便变量隔离,所以引入到页面中的组件得单独命名:

import wepy from 'wepy'
import Button from '@/components/button'
export default class Index extends wepy.page {
  components = {
    'wpy-button': Button,'wpy-button2': Button
  }
  ...
}
复制代码

如果多个页面引入同一组件会拷贝多份,有一些不便的地方,但也很好的解决了当时组件化缺失的问题。

template 法

有心的同学可能记得当初我们发了这篇文章:?微信小程序组件化解决方案wx-component?,当时主要讲了如何使用,这次讲讲技术的细节。

主要利用小程序当时提供的?template?模板方法,使用方式如下:

<!-- pages/login/index.wxml -->
<import src='../../components/login/index.wxml'/>
<"login-box">
  <template is="login" data="{{...}}"></template>
</view>
复制代码

由于知道这只是临时的解决方法,最终还会迁移到微信官方组件化方案。了解到微信团队正在开发,就死皮赖脸找了微信研发同学要下技术方案,以便后期迁移成本做到最低。最后微信同学不耐烦的扔给我们如下代码,并特别嘱咐?不要泄露出去?-_-:

Component({
  // 组件名
  name: '',// 为其他组件指定别名
  using: {},136);">// 类似mixins,组件间代码复用
  behaviors: [],136);">// 组件私有数据
  data: {
  },136);">// 外部传入的组件属性
  propties: {
  },136);">// 当组件被加载
  attached () {
  },136);">// 当组件被卸载
  detached () {
  },136);">// 组件私有方法
  methods: {
  }
})
复制代码

一目了然,依照此文档实现一个简单的组件化方案也有了思路。

如何引入组件

由于没有办法在小程序全局注入?Component?方法,可以将组件代码以模块方式导出,在页面的?Page?方法里引入:

// components/login/index.wxml
<form bindsubmit="onLoginSubmit">
    ...
    <button type="primary" formType="submit">{{btnText}}</form>
</template>
复制代码
// components/login/index.js
module.exports = {
  name: 'login',data: {
    btnText: ''
  }
  ....
}
复制代码
,onLoginCallback() { ... }
    }
  }
})
复制代码
在?Page?的传参里多了?components?属性,传入了组件名?login?,以及组件对应的属性值和方法。为了使这些新增传参生效,那势必需要对?Page?进行改造。

改造 Page

如何用一行代码毁掉你的小程序,在小程序根目录的?app.js?里加入这段代码即可:

Page = funtion() {}
复制代码

这样核心的?Page?的方法就被覆盖掉了,所以利用这个“特性”,可以改造?Page?方法:

// app.js
Page = require('./utils/wx').page
复制代码

这就完成了独一无二的自定义的小程序?Page?的方法。

Component 怎么写

精简了核心的代码如下:

function noop() {}

Component {
  constructor (config) {
    // 兼容 onLoad onUnload 的写法
    config.onLoad = config.onLoad || config.attached || noop
    config.onUnload = config.onUnload || config.detached || noop
    this.data = config.data || {}
    this.config = config
    this.methods = config.methods || {}
    for (let name in this.methods) {
      // 为了使组件事件绑定生效,直接挂在到 this 下
      this[name] = methods[name]
    }
  }
  setData (data,deepExtend) {
    let name = this.name
    let parent = this.parent
    let mergeData = extend(deepExtend !== false,parent.data[name],data)
    let newData = {}
    newData[name] = mergeData
    data = mergeData
    // 更新页面的 data
    parent.setData(newData)
  }
  setName (name) {
    this.name = name
  }
  setParent (parent) {
    this.parent = parent
  }
}
复制代码

主要完成了三件事:

  • 配置了组件的生命周期事件?attached?和?detached
  • 绑定了组件的事件,使得?template?的?bindtap?等代码生效
  • 实现了组件的?setData?功能

有个细节,为了让大家容易理解,分享到外部用?onLoad?、?onUnload?代替了?attached?、?detached?,但内部早就开始用微信命名的这两个属性名,才有了代码中的兼容写法。

自定的 Page 怎么写

整理了大致的核心代码如下:

,'onUnload'
]
MyPage {
  constructor (origin) {
    this.origin = origin
    this.config = {}
    this.children = {}
    this.childrenEvents = {}

    // 是否需要`components`
    let components = this.components = origin.components

    if (components) {
      this.config.data = {}
      for (let item in components) {
        let props = components[item] || {}
        let component = new Component(require(`../components/${item}/index`))

        this.children[name] = component
        // 合并组件的 data
        extend(component.data,component.props)
        // ...
        // 合并组件的 method
        let fnName in component.methods) {
          this.config[fnName] = component.methods[fnName].bind(component)
        }
        // ...
        let childrenEvents = this.childrenEvents[item] = {}
        LIFETIME_EVENT.forEach((prop) => {
          childrenEvents[item][prop] = component.config[prop]
        })
      }
      
      // 合并所有依赖组件的生命周期函数
      LIFETIME_EVENT.forEach((prop) => {
        this.config[prop] = () => {
          this.components) {
            this.childrenEvents[item][prop].apply(this.component,arguments)
          }
          this.origin[prop] && this.origin[prop].apply(this,0);">arguments)
        }
      })

      // 把新生成的 config 传给原始的微信的 Page 方法
      originalPage(this.config)
    } else {
      // 没有依赖组件,直接透传给微信的 Page 方法
      originalPage(origin)
    }

  }
}
复制代码

可能有点乱,其实就是不断 merge?data?和?method?的过程。最终所有组件自定的数据和方法都被挂在到了?Page?的传参里。

最后,导出自定义的?page?:

app.js?中覆盖掉原有的?Page?方法:

).page
复制代码

不完善的地方

虽然满足业务了,但也是有些问题的,例如上面?MyPage?方法里的这段:

可以看出,直接把组件内部定义的方法,挂在到?config?中去了,这就要求页面的方法和组件的方法不能重名,这是为了方便?template?可以直接绑定组件定义的事件,只能通过把组件事件转移到页面的事件方法里。

也有很多其他不完善的地方,但早期通过内部约束代码规范也基本可以解决。

结语

这种近乎 Hack 的方式支撑了摩拜单车小程序业务大半年的时间,期间产出了大大小小十多个组件。而由于组件内部基本是按照微信官方组件化 api 书写,待官方推出组件化方案后,全部迁移过去的成本也大大减小。

传送门

  • 小程序技能进阶回忆录 - 也许你并不需要小程序框架
  • 小程序技能进阶回忆录 - 在缺少组件化的日子里
  • 小程序技能进阶回忆录 - 如何自主实现数据侦听器和计算器 (待发布)
  • 小程序技能进阶回忆录 - 如何自主实现拦截器(待发布)
  • 小程序技能进阶回忆录 - globalData 的那些事儿(待发布)
  • 小程序技能进阶回忆录 - 什么时候执行 onLoad(待发布)
  • 小程序技能进阶回忆录 - 增强型的 wx.navigateBack(待发布)

广告时间

美团单车事业部(摩拜单车)诚招前端 / 小程序研发工程师,有兴趣可以发简历到 zhangshibing@mobike.com )

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读