在開發極速飛艇源碼詳情咨詢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。