歡迎您光臨深圳塔燈網(wǎng)絡(luò)科技有限公司!
          電話圖標(biāo) 余先生:13699882642

          網(wǎng)站百科

          為您解碼網(wǎng)站建設(shè)的點(diǎn)點(diǎn)滴滴

          小程序技能進(jìn)階回憶錄 - 在缺少組件化的日子里

          發(fā)表日期:2019-11 文章編輯:小燈 瀏覽次數(shù):5222

          戰(zhàn)爭,信念,意志和情感,這些散發(fā)著光芒和硝煙的詞匯,象一枚枚炮彈轟入我們現(xiàn)在的生活。歷史的記憶不會被抹滅。

          當(dāng)我們在各自項(xiàng)目里幸福的拷貝著官方代碼 demo,在 componnets 文件夾里使用 Component 方法書寫一個個組件時,不要忘記,在 2018 年上半年以前,小程序是沒有提供組件化方案的。

          當(dāng)時,主要有兩種解決方法,一種是 WePY 拷貝法,另一種則是摩拜 template 法。

          WePY 拷貝法

          比如有個最簡單的按鈕組件:

          <!-- components/button.wpy -->
          <template>
            <view class="button">
              <button @tap="onTap">點(diǎn)這里</button>
            </view>
          </template>
          
          <!-- pages/index.wpy -->
          <template>
            <view class="container">
              <wpy-button /> // button 組件1
              <wpy-button2 /> // button 組件2
            </view>
          </template>

          經(jīng)過編譯后結(jié)果如下:

          <view class="container">
            <view class="button">
              <button bindtap="$wpyButton$onTap">點(diǎn)這里</button>
            </view>
            <view class="button">
              <button bindtap="$wpyButton2$onTap">點(diǎn)這里</button>
            </view>
          </view>

          為了方便變量隔離,所以引入到頁面中的組件得單獨(dú)命名:

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

          有一些不便的地方,但也很好的解決了組件化缺失的問題。

          摩拜 template 法

          有心的同學(xué)可能記得當(dāng)初我們發(fā)了這篇文章:微信小程序組件化解決方案wx-component,當(dāng)時主要講了如何使用,這次講講技術(shù)的細(xì)節(jié)。

          主要利用小程序當(dāng)時提供的 template 模板方法,使用方式如下:

          <!-- pages/template/login.wxml -->
          <template name="login">
            <view class="login">這是登錄組件</view>
          </template>
          <!-- pages/login/index.wxml -->
          <import src='../../components/login/index.wxml'/>
          <view class="login-box">
            <template is="login" data="{{...}}"></template>
          </view>

          由于知道這只是臨時的解決方法,最終還會遷移到微信官方組件化方案。了解到微信團(tuán)隊(duì)正在開發(fā),就死皮賴臉找了微信研發(fā)同學(xué)要下技術(shù)方案,以便后期遷移成本做到最低。最后微信同學(xué)不耐煩的扔給我們?nèi)缦麓a,并特別囑咐不要泄露出去

          Component({
            // 組件名
            name: '',
            // 為其他組件指定別名
            using: {},
            // 類似mixins,組件間代碼復(fù)用
            behaviors: [],
            // 組件私有數(shù)據(jù)
            data: {
            },
            // 外部傳入的組件屬性
            propties: {
            },
            // 當(dāng)組件被加載
            attached () {
            },
            // 當(dāng)組件被卸載
            detached () {
            },
            // 組件私有方法
            methods: {
            }
          })

          一目了然,依照此文檔實(shí)現(xiàn)一個簡單的組件化方案也有了思路。

          如何引入組件

          由于沒有辦法在小程序全局注入 Component 方法,可以將組件代碼以模塊方式導(dǎo)出,在頁面的 Page 方法里引入:

          // components/login/index.wxml
          <template name="login">
            <form bindsubmit="onLoginSubmit">
              ...
              <button type="primary" formType="submit">{{btnText}}</button>
            </form>
          </template>
          // components/login/index.js
          module.exports = {
            name: 'login',
            data: {
              btnText: ''
            }
            ....
          }
          // pages/index/index.js
          Page({
            data: {
              ...
            },
            components: {
              login: {
                btnText: '開始',
                onLoginCallback() { ... }
              }
            }
          })
          <!-- pages/index/index.wxml -->
          <import src='../../components/login/index.wxml'/>
          <view class="login-box">
            <template is="login" data="{{...login}}"></template>
          </view>

          Page 的傳參里多了 components 屬性,傳入了組件名login,以及組件對應(yīng)的屬性值和方法。為了使這些新增傳參生效,那勢必需要對 Page 進(jìn)行改造。

          改造 Page

          如何用一行代碼毀掉你的小程序,在小程序根目錄的 app.js 里加入這段代碼即可:

          Page = funtion() {}

          這樣核心的 Page 的方法就被覆蓋掉了,所以利用這個“特性”,可以改造 Page 方法:

          // utils/wx.js
          var page = function() {
            // 改造代碼
            ...
          }
          module.exports = {
            page
          }
          // app.js
          Page = require('./utils/wx').page

          這就完成了獨(dú)一無二的自定義的小程序 Page 的方法。

          Component 怎么寫

          精簡了核心的代碼如下:

          function noop() {}
          
          class 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
              this.data = mergeData
              // 更新頁面的 data
              parent.setData(newData)
            }
            setName (name) {
              this.name = name
            }
            setParent (parent) {
              this.parent = parent
            }
          }

          主要完成了三件事:

          • 配置了組件的生命周期事件 attacheddetached
          • 綁定了組件的事件,使得 templatebindtap 等代碼生效
          • 實(shí)現(xiàn)了組件的 setData 功能

          有個細(xì)節(jié),為了讓大家容易理解(不泄露微信的方法名),分享到外部的文章用 onLoadonUnload 代替了 attacheddetached,但內(nèi)部早就開始用微信命名的這兩個屬性名,才有了代碼中的兼容寫法。

          自定的 Page 怎么寫

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

          // 緩存下微信的 Page
          const originalPage = Page
          // 組件生命周期
          const LIFETIME_EVENT = [
            'onLoad',
            'onUnload'
          ]
          class 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
                  for (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]
                  })
                }
                
                // 合并所有依賴組件的生命周期函數(shù)
                LIFETIME_EVENT.forEach((prop) => {
                  this.config[prop] = () => {
                    for (let item in this.components) {
                      this.childrenEvents[item][prop].apply(this.component, arguments)
                    }
                    this.origin[prop] && this.origin[prop].apply(this, arguments)
                  }
                })
          
                // 把新生成的 config 傳給原始的微信的 Page 方法
                originalPage(this.config)
              } else {
                // 沒有依賴組件,直接透傳給微信的 Page 方法
                originalPage(origin)
              }
          
            }
          }

          可能有點(diǎn)亂,其實(shí)就是不斷 merge datamethod的過程。最終所有組件自定的數(shù)據(jù)和方法都被掛在到了 Page 的傳參里。

          最后,導(dǎo)出自定義的 page

          // utils/wx.js
          const page = function (config) {
            return new MyPage(config)
          }
          
          module.exports = {
            page
          }

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

          // app.js
          Page = require('./utils/wx').page

          不完善的地方

          雖然滿足業(yè)務(wù)了,但也是有些問題的,例如上面 MyPage 方法里的這段:

          for (let fnName in component.methods) {
            this.config[fnName] = component.methods[fnName].bind(component)
          }

          可以看出,直接把組件內(nèi)部定義的方法,掛在到 config 中去了,這就要求頁面的方法和組件的方法不能重名,這是為了方便 template 可以直接綁定組件定義的事件,只能通過把組件事件轉(zhuǎn)移到頁面的事件方法里。

          也有很多不完善的地方,但通過內(nèi)部約束代碼規(guī)范也基本可以解決。

          結(jié)語

          這種近乎 Hack 的方式支撐了摩拜單車小程序業(yè)務(wù)大半年的時間,期間產(chǎn)出了大大小小十多個組件。而由于組件內(nèi)部基本是按照微信官方組件化 api 書寫,等待官方推出組件化方案后,全部遷移過去的成本也大大減小。


          本頁內(nèi)容由塔燈網(wǎng)絡(luò)科技有限公司通過網(wǎng)絡(luò)收集編輯所得,所有資料僅供用戶學(xué)習(xí)參考,本站不擁有所有權(quán),如您認(rèn)為本網(wǎng)頁中由涉嫌抄襲的內(nèi)容,請及時與我們聯(lián)系,并提供相關(guān)證據(jù),工作人員會在5工作日內(nèi)聯(lián)系您,一經(jīng)查實(shí),本站立刻刪除侵權(quán)內(nèi)容。本文鏈接:http://www.cjxv.cn/25243.html
          相關(guān)小程序