從極速飛艇源碼 VantComponent 談 小程式維護

来源:https://www.cnblogs.com/q166848365/archive/2019/04/30/10797351.html
-Advertisement-
Play Games

在開發極速飛艇源碼詳情咨詢Q166848365小程式的時候,我們總是期望用以往的技術規範和語法特點來書寫當前的小程式 ...


在開發極速飛艇源碼詳情咨詢Q166848365小程式的時候我們總是期望用以往的技術規範和語法特點來書寫當前的小程式,所以才會有各色的小程式框架,例如 mpvue、taro 等這些編譯型框架。當然這些框架本身對於新開發的項目是有所幫助。而對於老項目,我們又想要利用 vue 的語法特性進行維護,又該如何呢?
在此我研究了一下youzan的 vant-weapp。而發現該項目中的組件是如此編寫的。

import { VantComponent } from '../common/component';

 

VantComponent({

  mixins: [],

  props: {

    name: String,

    size: String

  },

  // 可以使用 watch 來監控 props 變化

  // 其實就是把properties中的observer提取出來

  watch: {

    name(newVal) {

       ...

    },

    // 可以直接使用字元串 代替函數調用

    size: 'changeSize'

  },

  // 使用計算屬性 來 獲取數據,可以在 wxml直接使用

  computed: {

    bigSize() {

      return this.data.size + 100

    }

  },

  data: {

    size: 0

  },

  methods: {

    onClick() {

      this.$emit('click');

    },

    changeSize(size) {

       // 使用set

       this.set(size)

    }

  },

 

  // 對應小程式組件 created 周期

  beforeCreate() {},

 

  // 對應小程式組件 attached 周期

  created() {},

 

  // 對應小程式組件 ready 周期

  mounted() {},

 

  // 對應小程式組件  detached 周期

  destroyed: {}

});

居然發現該組件寫法整體上類似於 Vue 語法。而本身卻沒有任何編譯。看來問題是出在了導入的 VantComponet 這個方法上。下麵我們開始詳細介紹一下如何利用 VantComponet 來對老項目進行維護。

TLDR (不多廢話,先說結論)

小程式組件寫法這裡就不再介紹。這裡我們給出利用 VantComponent 寫 Page 的代碼風格。

import { VantComponent } from '../common/component';

 

VantComponent({

  mixins: [],

  props: {

    a: String,

    b: Number

  },

  // 在頁面這裡 watch 基本上是沒有作用了,因為只做了props 變化的watch,page不會出現 props 變化

  // 後面會詳細說明為何

  watch: {},

  // 計算屬性仍舊可用

  computed: {

    d() {

      return c++

    }

  },

  methods: {

    onLoad() {}

  },

  created() {},

  // 其他組件生命周期

})

這裡你可能感到疑惑,VantComponet 不是對組件 Component 生效的嗎?怎麼會對頁面 Page 生效呢。事實上,我們是可以使用組件來構造小程式頁面的。
在官方文檔中,我們可以看到 使用 Component 構造器構造頁面
事實上,小程式的頁面也可以視為自定義組件。因而,頁面也可以使用 Component 構造器構造,擁有與普通組件一樣的定義段與實例方法。代碼編寫如下:

Component({

    // 可以使用組件的 behaviors 機制,雖然 React 覺得 mixins 並不是一個很好的方案

    // 但是在某種程度該方案的確可以復用相同的邏輯代碼

    behaviors: [myBehavior],

   

    // 對應於page的options,與此本身是有類型的,而從options 取得數據均為 string類型

    // 訪問 頁面 /pages/index/index?paramA=123¶mB=xyz

    // 如果聲明有屬性 paramA 或 paramB ,則它們會被賦值為 123 或 xyz,而不是 string類型

    properties: {

        paramA: Number,

        paramB: String,

    },

    methods: {

        // onLoad 不需要 option

        // 但是頁面級別的生命周期卻只能寫道 methods中來

        onLoad() {

            this.data.paramA // 頁面參數 paramA 的值 123

            this.data.paramB // 頁面參數 paramB 的值 ’xyz’

        }

    }

 

})

那麼組件的生命周期和頁面的生命周期又是怎麼對應的呢。經過一番測試,得出結果為: (為了簡便。只會列出 重要的的生命周期)

// 組件實例被創建 到 組件實例進入頁面節點樹

component created -> component attched -> // 頁面頁面載入 到  組件在視圖層佈局完成

page onLoad -> component ready -> // 頁面卸載 到 組件實例被從頁面節點樹移除

page OnUnload -> component detached

當然 我們重點不是在 onload 和 onunload 中間的狀態,因為中間狀態的時候,我們可以在頁面中使用頁面生命周期來操作更好。
某些時候我們的一些初始化代碼不應該放在 onload 裡面,我們可以考慮放在 component create 進行操作,甚至可以利用 behaviors 來複用初始化代碼。
某種方面來說,如果不需要 Vue 風格,我們在老項目中直接利用 Component 代替 Page 也不失為一個不錯的維護方案。畢竟官方標準,不用擔心其他一系列後續問題。

VantComponent 源碼解析

VantComponent

此時,我們對 VantComponent 開始進行解析

// 賦值,根據 map 的 key 和 value 來進行操作function mapKeys(source: object, target: object, map: object) {

  Object.keys(map).forEach(key => {

    if (source[key]) {

      // 目標對象 的 map[key] 對應 源數據對象的 key

      target[map[key]] = source[key];

    }

  });

}

// ts代碼,也就是 泛型function VantComponent<Data, Props, Watch, Methods, Computed>(

  vantOptions: VantComponentOptions<

    Data,

    Props,

    Watch,

    Methods,

    Computed,

    CombinedComponentInstance<Data, Props, Watch, Methods, Computed>

  > = {}): void {

  const options: any = {};

  // 用function 來拷貝 新的數據,也就是我們可以用的 Vue 風格

  mapKeys(vantOptions, options, {

    data: 'data',

    props: 'properties',

    mixins: 'behaviors',

    methods: 'methods',

    beforeCreate: 'created',

    created: 'attached',

    mounted: 'ready',

    relations: 'relations',

    destroyed: 'detached',

    classes: 'externalClasses'

  });

 

  // 對組件間關係進行編輯,但是page不需要,可以刪除

  const { relation } = vantOptions;

  if (relation) {

    options.relations = Object.assign(options.relations || {}, {

      [`../${relation.name}/index`]: relation

    });

  }

 

  // 對組件預設添加 externalClasses,但是page不需要,可以刪除

  // add default externalClasses

  options.externalClasses = options.externalClasses || [];

  options.externalClasses.push('custom-class');

 

  // 對組件預設添加 basic,封裝了 $emit 和小程式節點查詢方法,可以刪除

  // add default behaviors

  options.behaviors = options.behaviors || [];

  options.behaviors.push(basic);

 

  // map field to form-field behavior

  // 預設添加 內置 behavior  wx://form-field

  // 它使得這個自定義組件有類似於表單控制項的行為。

  // 可以研究下文給出的 內置behaviors

  if (vantOptions.field) {

    options.behaviors.push('wx://form-field');

  }

 

  // add default options

  // 添加組件預設配置,多slot

  options.options = {

    multipleSlots: true,// 在組件定義時的選項中啟用多slot支持

    // 如果這個 Component 構造器用於構造頁面 ,則預設值為 shared

    // 組件的apply-shared,可以研究下文給出的 組件樣式隔離

    addGlobalClass: true 

  };

 

  // 監控 vantOptions

  observe(vantOptions, options);

 

  // 把當前重新配置的options 放入Component

  Component(options);

}

內置behaviors
組件樣式隔離

basic behaviors

剛剛我們談到 basic behaviors,代碼如下所示

export const basic = Behavior({

  methods: {

    // 調用 $emit組件 實際上是使用了 triggerEvent

    $emit() {

      this.triggerEvent.apply(this, arguments);

    },

 

    // 封裝 程式節點查詢

    getRect(selector: string, all: boolean) {

      return new Promise(resolve => {

        wx.createSelectorQuery()

          .in(this)[all ? 'selectAll' : 'select'](selector)

          .boundingClientRect(rect => {

            if (all && Array.isArray(rect) && rect.length) {

              resolve(rect);

            }

 

            if (!all && rect) {

              resolve(rect);

            }

          })

          .exec();

      });

    }

  }

});

observe

小程式 watch 和 computed的 代碼解析

export function observe(vantOptions, options) {

  // 從傳入的 option中得到 watch computed  

  const { watch, computed } = vantOptions;

 

  // 添加  behavior

  options.behaviors.push(behavior);

 

  /// 如果有 watch 對象

  if (watch) {

    const props = options.properties || {};

    // 例如:

    // props: {

    //   a: String

    // },

    // watch: {

    //   a(val) {

    //     // 每次val變化時候列印

    //     consol.log(val)

    //   }

    }

    Object.keys(watch).forEach(key => {

      

      // watch只會對prop中的數據進行 監視

      if (key in props) {

        let prop = props[key];

        if (prop === null || !('type' in prop)) {

          prop = { type: prop };

        }

        // prop的observer被watch賦值,也就是小程式組件本身的功能。

        prop.observer = watch[key];

        // 把當前的key 放入prop

        props[key] = prop;

      }

    });

    // 經過此方法

    // props: {

    //  a: {

    //    type: String,

    //    observer: (val) {

    //      console.log(val)

    //    }

    //  }

    // }

    options.properties = props;

  }

 

  // 對計算屬性進行封裝

  if (computed) {

    options.methods = options.methods || {};

    options.methods.$options = () => vantOptions;

 

    if (options.properties) {

      

      // 監視props,如果props發生改變,計算屬性本身也要變

      observeProps(options.properties);

    }

  }

}

observeProps

現在剩下的也就是 observeProps 以及 behavior 兩個文件了,這兩個都是為了計算屬性而生成的,這裡我們先解釋 observeProps 代碼

export function observeProps(props) {

  if (!props) {

    return;

  }

 

  Object.keys(props).forEach(key => {

    let prop = props[key];

    if (prop === null || !('type' in prop)) {

      prop = { type: prop };

    }

 

    // 保存之前的 observer,也就是上一個代碼生成的prop

    let { observer } = prop;

    prop.observer = function() {

      if (observer) {

        if (typeof observer === 'string') {

          observer = this[observer];

        }

 

        // 調用之前保存的 observer

        observer.apply(this, arguments);

      }

 

      // 在發生改變的時候調用一次 set 來重置計算屬性

      this.set();

    };

    // 把修改的props 賦值回去

    props[key] = prop;

  });

}

behavior

最終 behavior,也就算 computed 實現機制

// 非同步調用 setDatafunction setAsync(context: Weapp.Component, data: object) {

  return new Promise(resolve => {

    context.setData(data, resolve);

  });

};

export const behavior = Behavior({

  created() {

    if (!this.$options) {

      return;

    }

 

    // 緩存

    const cache = {};

    const { computed } = this.$options();

    const keys = Object.keys(computed);

 

    this.calcComputed = () => {

      // 需要更新的數據

      const needUpdate = {};

      keys.forEach(key => {

        const value = computed[key].call(this);

        // 緩存數據不等當前計算數值

        if (cache[key] !== value) {

          cache[key] = needUpdate[key] = value;

        }

      });

      // 返回需要的更新的 computed

      return needUpdate;

    };

  },

 

  attached() {

    // 在 attached 周期 調用一次,算出當前的computed數值

    this.set();

  },

 

  methods: {

    // set data and set computed data

    // set可以使用callback 和 then

    set(data: object, callback: Function) {

      const stack = [];

      // set時候放入數據

      if (data) {

        stack.push(setAsync(this, data));

      }

 

      if (this.calcComputed) {

        // 有計算屬性,同樣也放入 stack中,但是每次set都會調用一次,props改變也會調用

        stack.push(setAsync(this, this.calcComputed()));

      }

 

      return Promise.all(stack).then(res => {

        // 所有 data以及計算屬性都完成後調用callback

        if (callback && typeof callback === 'function') {

          callback.call(this);

        }

        return res;

      });

    }

  }

});

寫在後面

js 是一門靈活的語言(手動滑稽)

本身 小程式 Component 在 小程式 Page 之後,就要比Page 更加成熟好用,有時候新的方案往往藏在文檔之中,每次多看幾遍文檔絕不是沒有意義的。

小程式版本 版本2.6.1 Component 目前已經實現了 observers,可以監聽 props data 數據監聽器,目前 VantComponent沒有實現,當然本身而言,Page 不需要對 prop 進行監聽,因為進入頁面壓根不會變,而data變化本身就無需監聽,直接調用函數即可,所以對page而言,observers 可有可無。

該方案也只是對 js 代碼上有vue的風格,並沒在 template 以及 style 做其他文章。

該方案性能一定是有所缺失的,因為computed是每次set都會進行計算,而並非根據set 的 data 來進行操作,在刪減之後我認為本身是可以接受。如果本身對於vue的語法特性需求不高,可以直接利用 Component 來編寫 Page,選擇不同的解決方案實質上是需要權衡各種利弊。如果本身是有其他要求或者新的項目,仍舊推薦使用新技術,如果本身是已有項目並且需要維護的,同時又想擁有 Vue 特性。可以使用該方案,因為代碼本身較少,而且本身也可以基於自身需求修改。

同時,vant-weapp是一個非常不錯的項目,推薦各位可以去查看以及star。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Python多繼承MRO 在Python2.1中,採用了經典類,使用深度優先演算法解析。 Python2.2中,引入了新式類,使用深度優先演算法和廣度優先演算法。 在Python2.3以後的版本中,經典類和新式類共存,使用了DFS演算法和C3演算法。 Python2中的經典類 Python3的新式類 C3演算法 ...
  • 題目 "P2711 小行星" 解析 這道題挺巧妙的,乍一看是空間上的,無從下手,稍微轉換一下就可以了。 看到題目,求消除這些行星的最少次數,就是求最小割,也就是求最大流,考慮怎樣建圖。 考慮當我們消去一個面上的所有點時,我們消去這個面後,這個面就不會再被消了, 也就是只能被消一次 ,比如我們消去與$ ...
  • 思路 既然$x$的數量那麼小,我們就可以先把每個$x$搜索一遍。 枚舉x的時候不需要把$a,b,c$全枚舉一遍,只要枚舉其中的兩個就可以枚舉到當前 ...
  • (劍指offer)輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,如果有多對數字的和等於S,輸出兩個數的乘積最小的。 思路:選定第一個數字,然後遍歷後面的數字求和並與S比較,需要n-1次,不行的話再選定第2,3,,,n個數字,需要n^2次,時間複雜度比較高。更簡單的方法 ...
  • day 20 02 模塊的導入 運行結果: 9. 兩種不同導入的優缺點: (1)import demo demo:裡面的名字都會導入 會占很多記憶體 裡面的命名空間和不是模塊裡面的命名空間是分開的,所以外面如果有和demo裡面相同的變數名,也是不會相互影響 (2)from demo import 變數 ...
  • 超級全局變數有9個特點是全局可用!頁面到處都能訪問到這個變數 $_GET獲得的是瀏覽器地址欄過來的數據,或者表單GET方式提交的數據 $_POST獲得的是表單POST方式提交的數據 $_REQUEST預設情況(具體情況要看php.ini的配置)獲得的是既包含POST過來的值,也包含GET方式獲得的值 ...
  • 併發與並行: 併發:多個任務交替執行 (一個人吃兩個饅頭,兩個交替啃) 並行:同時執行。(n個人吃n個饅頭) 速度快些。 線程與進程: 進程:進入到記憶體中的程式叫進程。 線程: 主線程: 執行主方法(main)的線程 單線程程式:Java程式中只有一個線程,從main方法開始,從上到下依次執行。 J ...
  • 4.30自我總結 一複習 1.查看數據類型 2.關於變數的一些補充 二.數據類型 1.數字類型 a)整型(int) 1.作用 表示年齡,身高,體重等等 2.定義 3.運算 b)浮點型(float) 1,作用 表示稱重,長度精確需要用到小數點時候等等 2.定義: 3.演算法與整數型相同 4.補充演算法 2 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...