摘要:不多廢話,先說結(jié)論小程序組件寫法這里就不再介紹。在官方文檔中,我們可以看到使用構(gòu)造器構(gòu)造頁面事實上,小程序的頁面也可以視為自定義組件。經(jīng)過一番測試,得出結(jié)果為為了簡便。畢竟官方標(biāo)準(zhǔn),不用擔(dān)心其他一系列后續(xù)問題。
在開發(fā)小程序的時候,我們總是期望用以往的技術(shù)規(guī)范和語法特點來書寫當(dāng)前的小程序,所以才會有各色的小程序框架,例如 mpvue、taro 等這些編譯型框架。當(dāng)然這些框架本身對于新開發(fā)的項目是有所幫助。而對于老項目,我們又想要利用 vue 的語法特性進行維護,又該如何呢?
在此我研究了一下youzan的 vant-weapp。而發(fā)現(xiàn)該項目中的組件是如此編寫的。
import { VantComponent } from "../common/component"; VantComponent({ mixins: [], props: { name: String, size: String }, // 可以使用 watch 來監(jiān)控 props 變化 // 其實就是把properties中的observer提取出來 watch: { name(newVal) { ... }, // 可以直接使用字符串 代替函數(shù)調(diào)用 size: "changeSize" }, // 使用計算屬性 來 獲取數(shù)據(jù),可以在 wxml直接使用 computed: { bigSize() { return this.data.size + 100 } }, data: { size: 0 }, methods: { onClick() { this.$emit("click"); }, changeSize(size) { // 使用set this.set(size) } }, // 對應(yīng)小程序組件 created 周期 beforeCreate() {}, // 對應(yīng)小程序組件 attached 周期 created() {}, // 對應(yīng)小程序組件 ready 周期 mounted() {}, // 對應(yīng)小程序組件 detached 周期 destroyed: {} });
居然發(fā)現(xiàn)該組件寫法整體上類似于 Vue 語法。而本身卻沒有任何編譯??磥韱栴}是出在了導(dǎo)入的 VantComponet 這個方法上。下面我們開始詳細(xì)介紹一下如何利用 VantComponet 來對老項目進行維護。
TLDR (不多廢話,先說結(jié)論)小程序組件寫法這里就不再介紹。這里我們給出利用 VantComponent 寫 Page 的代碼風(fēng)格。
import { VantComponent } from "../common/component"; VantComponent({ mixins: [], props: { a: String, b: Number }, // 在頁面這里 watch 基本上是沒有作用了,因為只做了props 變化的watch,page不會出現(xiàn) props 變化 // 后面會詳細(xì)說明為何 watch: {}, // 計算屬性仍舊可用 computed: { d() { return c++ } }, methods: { onLoad() {} }, created() {}, // 其他組件生命周期 })
這里你可能感到疑惑,VantComponet 不是對組件 Component 生效的嗎?怎么會對頁面 Page 生效呢。事實上,我們是可以使用組件來構(gòu)造小程序頁面的。
在官方文檔中,我們可以看到 使用 Component 構(gòu)造器構(gòu)造頁面
事實上,小程序的頁面也可以視為自定義組件。因而,頁面也可以使用 Component 構(gòu)造器構(gòu)造,擁有與普通組件一樣的定義段與實例方法。代碼編寫如下:
Component({ // 可以使用組件的 behaviors 機制,雖然 React 覺得 mixins 并不是一個很好的方案 // 但是在某種程度該方案的確可以復(fù)用相同的邏輯代碼 behaviors: [myBehavior], // 對應(yīng)于page的options,與此本身是有類型的,而從options 取得數(shù)據(jù)均為 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 // 頁面參數(shù) paramA 的值 123 this.data.paramB // 頁面參數(shù) paramB 的值 ’xyz’ } } })
那么組件的生命周期和頁面的生命周期又是怎么對應(yīng)的呢。經(jīng)過一番測試,得出結(jié)果為: (為了簡便。只會列出 重要的的生命周期)
// 組件實例被創(chuàng)建 到 組件實例進入頁面節(jié)點樹 component created -> component attched -> // 頁面頁面加載 到 組件在視圖層布局完成 page onLoad -> component ready -> // 頁面卸載 到 組件實例被從頁面節(jié)點樹移除 page OnUnload -> component detached
當(dāng)然 我們重點不是在 onload 和 onunload 中間的狀態(tài),因為中間狀態(tài)的時候,我們可以在頁面中使用頁面生命周期來操作更好。
某些時候我們的一些初始化代碼不應(yīng)該放在 onload 里面,我們可以考慮放在 component create 進行操作,甚至可以利用 behaviors 來復(fù)用初始化代碼。
某種方面來說,如果不需要 Vue 風(fēng)格,我們在老項目中直接利用 Component 代替 Page 也不失為一個不錯的維護方案。畢竟官方標(biāo)準(zhǔn),不用擔(dān)心其他一系列后續(xù)問題。
此時,我們對 VantComponent 開始進行解析
// 賦值,根據(jù) map 的 key 和 value 來進行操作 function mapKeys(source: object, target: object, map: object) { Object.keys(map).forEach(key => { if (source[key]) { // 目標(biāo)對象 的 map[key] 對應(yīng) 源數(shù)據(jù)對象的 key target[map[key]] = source[key]; } }); } // ts代碼,也就是 泛型 function VantComponent( vantOptions: VantComponentOptions< Data, Props, Watch, Methods, Computed, CombinedComponentInstance > = {} ): void { const options: any = {}; // 用function 來拷貝 新的數(shù)據(jù),也就是我們可以用的 Vue 風(fēng)格 mapKeys(vantOptions, options, { data: "data", props: "properties", mixins: "behaviors", methods: "methods", beforeCreate: "created", created: "attached", mounted: "ready", relations: "relations", destroyed: "detached", classes: "externalClasses" }); // 對組件間關(guān)系進行編輯,但是page不需要,可以刪除 const { relation } = vantOptions; if (relation) { options.relations = Object.assign(options.relations || {}, { [`../${relation.name}/index`]: relation }); } // 對組件默認(rèn)添加 externalClasses,但是page不需要,可以刪除 // add default externalClasses options.externalClasses = options.externalClasses || []; options.externalClasses.push("custom-class"); // 對組件默認(rèn)添加 basic,封裝了 $emit 和小程序節(jié)點查詢方法,可以刪除 // add default behaviors options.behaviors = options.behaviors || []; options.behaviors.push(basic); // map field to form-field behavior // 默認(rèn)添加 內(nèi)置 behavior wx://form-field // 它使得這個自定義組件有類似于表單控件的行為。 // 可以研究下文給出的 內(nèi)置behaviors if (vantOptions.field) { options.behaviors.push("wx://form-field"); } // add default options // 添加組件默認(rèn)配置,多slot options.options = { multipleSlots: true,// 在組件定義時的選項中啟用多slot支持 // 如果這個 Component 構(gòu)造器用于構(gòu)造頁面 ,則默認(rèn)值為 shared // 組件的apply-shared,可以研究下文給出的 組件樣式隔離 addGlobalClass: true }; // 監(jiān)控 vantOptions observe(vantOptions, options); // 把當(dāng)前重新配置的options 放入Component Component(options); }
內(nèi)置behaviors
組件樣式隔離
剛剛我們談到 basic behaviors,代碼如下所示
export const basic = Behavior({ methods: { // 調(diào)用 $emit組件 實際上是使用了 triggerEvent $emit() { this.triggerEvent.apply(this, arguments); }, // 封裝 程序節(jié)點查詢 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中的數(shù)據(jù)進行 監(jiān)視 if (key in props) { let prop = props[key]; if (prop === null || !("type" in prop)) { prop = { type: prop }; } // prop的observer被watch賦值,也就是小程序組件本身的功能。 prop.observer = watch[key]; // 把當(dāng)前的key 放入prop props[key] = prop; } }); // 經(jīng)過此方法 // 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) { // 監(jiān)視props,如果props發(fā)生改變,計算屬性本身也要變 observeProps(options.properties); } } }observeProps
現(xiàn)在剩下的也就是 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]; } // 調(diào)用之前保存的 observer observer.apply(this, arguments); } // 在發(fā)生改變的時候調(diào)用一次 set 來重置計算屬性 this.set(); }; // 把修改的props 賦值回去 props[key] = prop; }); }behavior
最終 behavior,也就算 computed 實現(xiàn)機制
// 異步調(diào)用 setData function 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 = () => { // 需要更新的數(shù)據(jù) const needUpdate = {}; keys.forEach(key => { const value = computed[key].call(this); // 緩存數(shù)據(jù)不等當(dāng)前計算數(shù)值 if (cache[key] !== value) { cache[key] = needUpdate[key] = value; } }); // 返回需要的更新的 computed return needUpdate; }; }, attached() { // 在 attached 周期 調(diào)用一次,算出當(dāng)前的computed數(shù)值 this.set(); }, methods: { // set data and set computed data // set可以使用callback 和 then set(data: object, callback: Function) { const stack = []; // set時候放入數(shù)據(jù) if (data) { stack.push(setAsync(this, data)); } if (this.calcComputed) { // 有計算屬性,同樣也放入 stack中,但是每次set都會調(diào)用一次,props改變也會調(diào)用 stack.push(setAsync(this, this.calcComputed())); } return Promise.all(stack).then(res => { // 所有 data以及計算屬性都完成后調(diào)用callback if (callback && typeof callback === "function") { callback.call(this); } return res; }); } } });寫在后面
js 是一門靈活的語言(手動滑稽)
本身 小程序 Component 在 小程序 Page 之后,就要比Page 更加成熟好用,有時候新的方案往往藏在文檔之中,每次多看幾遍文檔絕不是沒有意義的。
小程序版本 版本2.6.1 Component 目前已經(jīng)實現(xiàn)了 observers,可以監(jiān)聽 props data 數(shù)據(jù)監(jiān)聽器,目前 VantComponent沒有實現(xiàn),當(dāng)然本身而言,Page 不需要對 prop 進行監(jiān)聽,因為進入頁面壓根不會變,而data變化本身就無需監(jiān)聽,直接調(diào)用函數(shù)即可,所以對page而言,observers 可有可無。
該方案也只是對 js 代碼上有vue的風(fēng)格,并沒在 template 以及 style 做其他文章。
該方案性能一定是有所缺失的,因為computed是每次set都會進行計算,而并非根據(jù)set 的 data 來進行操作,在刪減之后我認(rèn)為本身是可以接受。如果本身對于vue的語法特性需求不高,可以直接利用 Component 來編寫 Page,選擇不同的解決方案實質(zhì)上是需要權(quán)衡各種利弊。如果本身是有其他要求或者新的項目,仍舊推薦使用新技術(shù),如果本身是已有項目并且需要維護的,同時又想擁有 Vue 特性。可以使用該方案,因為代碼本身較少,而且本身也可以基于自身需求修改。
同時,vant-weapp是一個非常不錯的項目,推薦各位可以去查看以及star。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/103913.html
摘要:好的項目代碼結(jié)構(gòu)會大大提升項目的維護性和可擴展性。多說無益,我這里直接給大家一個示意圖,大家可以按照我給的這個項目結(jié)構(gòu)組織項目。你連文件目錄都設(shè)計不好,我拿什么相信你能設(shè)計出來可擴展的程序 很多人都會用項目腳手架,也會跑hello world,然后再寫寫簡單的todolist。但是再往下深入就難了。比如很多教程和老師都會說,大家要多問一個為什么。其實我想說多問你妹啊。我都不知道問為什么...
摘要:最近,一個問題總是時不時的冒出我的腦海前端性能優(yōu)化時候還有必要回顧前端性能優(yōu)化然后我找到了雅虎軍規(guī)的條盡量減少請求個數(shù)須權(quán)衡使用內(nèi)容分發(fā)網(wǎng)絡(luò)為文件頭指定或,使內(nèi)容具有緩存性。那就是時候停下來,問一問是否還有必要這樣做。 之前,何同學(xué)的視頻在網(wǎng)上活了一陣子。引發(fā)了我們思考:5G將會給我們帶來什么。同時也回顧了4G乃至3G時代已經(jīng)給我們帶來了哪些新的變革。最近,一個問題總是時不時的冒出我的...
摘要:原則具體包括自動化獨立性可重復(fù)簡單的解釋一下三個原則單元測試應(yīng)該是全自動執(zhí)行的。為了保證單元測試穩(wěn)定可靠且便于維護,需要保證其獨立性。原則編寫單元測試用例時為了保證被測模塊的交付質(zhì)量需要符合原則。與設(shè)計文檔相結(jié)合來編寫單元測試。 本文首發(fā)于泊浮目的專欄:https://segmentfault.com/blog... 背景 最近項目在測試階段陸陸續(xù)續(xù)的測出了一些bug.這個情況剛出現(xiàn)...
閱讀 2134·2019-08-29 16:53
閱讀 2708·2019-08-29 16:07
閱讀 2052·2019-08-29 13:13
閱讀 3274·2019-08-26 13:57
閱讀 1340·2019-08-26 13:31
閱讀 2444·2019-08-26 13:22
閱讀 1231·2019-08-26 11:43
閱讀 2094·2019-08-23 17:14