如何寫好一個vue元件,老夫的一年經驗全在這了
如果該元件有一個引數的時候,筆者習慣使用v-model
將該引數傳入元件,減少記憶成本(畢竟 vue 官方的語法糖,不用白不用)
元件具有自身狀態,當沒有相關 porps 傳入時,使用自身狀態完成渲染和互動邏輯;當該元件被呼叫時,如果有相關 props 傳入,那麼將會交出控制權,由父元件控制其行為
<my-component v-model="text" /> 複製程式碼
很多值需要傳入
比如當一個元件有諸多配置項,且當沒有傳入配置項取用元件內部預設項的時候,我們原先的父元件寫法:
<child-component :prop1="var1" :prop2="var2" :prop="var3" ... /> 複製程式碼
並且要在子元件裡面判斷每一個傳入的值是否為空,不為空替代預設的配置項
這種情況,不妨使用一個物件將配置收集到一起
<child-component v-model="text" :setting="{color:'bule'}" /> // 子元件內部讀取配置,通過擴充套件運算子替換掉預設配置 const setting ={ ...defaultSetting, ...this.setting } 複製程式碼
computed 屬性
vue 的 computed 屬性預設是隻讀的,你可以提供一個setter
。它可以優化我寫元件的邏輯,適用於父元件處理的值和子元件處理的值是同一個的情況
<template> <el-select v-model="email"> <el-option v-for="item in adminUserOptions" :key="item.email" :label="item.email" :value="item.email" /> </el-select> </template> 複製程式碼
export default { props: { value: {} }, computed: { email: { get() { return this.value }, set(val) { this.$emit('input', val) this.$emit('change', val) } } } } 複製程式碼
靈活的 prop
我們常看到一些優秀的元件庫,傳入的值既可以是一個 String/Number,也可以是一個函式。
比如ElementUI
的Table
元件,當你想要顯示樹形資料的時候,必須傳入row-key
。看它的介紹就知道是有多靈活:
row-key
的作用:行資料的Key
,用來優化Table
的渲染;在使用reserve-selection
功能與顯示樹形資料時,該屬性是必填的。型別為 String 時,支援多層訪問:user.info.id
,但不支援 user.info[0].id,此種情況請使用Function
處理 rowKey 生成 RowIdentity 的函式原始碼:
//https://github.com/ElemeFE/element/blob/dev/packages/table/src/util.js export const getRowIdentity = (row, rowKey) => { if (!row) throw new Error('row is required when get row identity') // 行資料的key if (typeof rowKey === 'string') { if (rowKey.indexOf('.') < 0) { return row[rowKey] } // 支援多層訪問:user.info.id let key = rowKey.split('.') let current = row for (let i = 0; i < key.length; i++) { current = current[key[i]] } return current // 通過函式自定義 // 我處理過父和子id可能相同的情況,只好通過Function自定義 // 不可以通過時間或者隨機字串生成ID } else if (typeof rowKey === 'function') { return rowKey.call(null, row) } } 複製程式碼
元件的設計者很難考慮完全,不妨設計靈活的 prop,由開發者自行定義
事件
emit/on
讀者肯定知道 emit/on 如何使用,我就簡單說一下 vue 的v-model
和sync
的語法糖,我們可以利用這些語法糖,幫助我們寫出簡潔的程式碼(父元件可以少寫監聽子元件的事件,比如你不用寫@input)
v-model
看一下下面的程式碼示例,就能懂這句話了。v-model 會忽略所有表單元素的 value、checked、selected 特性的初始值而總是將 Vue 例項的資料作為資料來源。你應該通過 JavaScript 在元件的 data 選項中宣告初始值
<input v-model="searchText" /> <input v-bind:value="searchText" v-on:input="searchText = $event.target.value" /> // 當把v-model用在元件上 <custom-input v-bind:value="searchText" v-on:input="searchText = $event" ></custom-input> 複製程式碼
為了讓它正常工作,這個元件內的
必須:將其 value 特性繫結到一個名叫 value 的 prop 上在其 input 事件被觸發時,將新的值通過自定義的 input 事件丟擲,即this.$emit('input',changedValue)
自定義 v-model
為啥要自定義元件的 v-model 呢,因為資料不符合要求唄。你的輸入值不可能總是 value ,你的事件不可能總是 input,具體詳見文件
sync(雙向繫結語法糖)
vue 真的是方便了開發者很多,站在開發者的角度考慮,可以很大的提升開發效率
以 update:myPropName 的模式觸發事件取代雙向繫結this.$emit('update:title', newTitle)
,具體詳見文件
Function 通過 prop 傳入
本來想放在 prop 部分的,但是個人覺得其實它和 emit/on 更有關係一點
有讀者可能會問,為什麼不能把子元件裡面的事件 emit 出來,通過父元件處理?然後傳入一個控制子元件的 prop 屬性。
我想說的是,可以,但是這樣真的很麻煩,子元件內部的狀態卻要依賴父元件傳值。
該元件內部的狀態,我們需要把它暴露出來嘛?我覺得不需要,元件內部的狀態就讓它處於元件內部
但是可以通過傳入 function(你可以理解為一個鉤子),參與元件狀態變更的行為。比如很好用的拖拽庫,Vue.Draggable 控制元素是否被拖動的行為。
Vue.Draggable
可以傳入一個 move 方法,我們看一下它如何處理的。
onDragMove(evt, originalEvent) { const onMove = this.move; // 如果沒有傳入move,那麼返回true,可以移動 if (!onMove || !this.realList) { return true; } const relatedContext = this.getRelatedContextFromMoveEvent(evt); const draggedContext = this.context; const futureIndex = this.computeFutureIndex(relatedContext, evt); Object.assign(draggedContext, { futureIndex }); const sendEvt = Object.assign({}, evt, { relatedContext, draggedContext }); // 元件行為由傳入的move函式控制 return onMove(sendEvt, originalEvent); } 複製程式碼
這樣做的好處,就是元件內部自由一套執行邏輯,但是我可以通過傳入 function 來干預。我沒有直接修改元件內部狀態,而是通過函式(你可以稱它為鉤子)去觸發,方便除錯元件,使得元件行為具有可預測性
父元件直接操作子元件
很少有這樣的騷操作,但是由於資料和操作的複雜性,當資料結構複雜,巢狀過深的情況下,父元件很難對於子元件的資料的精細控制
因此,如果不得已而為之,請在文件裡,把子元件可以呼叫的方法暴露出來,供使用者使用。使用這種元件比較麻煩,得去看文件,沒有文件的只好去看原始碼
ElementUI
的
tree
元件
提供了很多方法,用於父元件去操作子元件。
eg:this.$refs.tree.setCheckedKeys([]);
插槽
能用預設插槽就不要使用具名插槽(我真的不想使用你這個元件的時候還去翻看你的插槽叫什麼名字)
之前我司一個網頁模板 三個插槽,header,body,footer,我用的是真的難受,每次都記不得,看似三個單詞都挺熟悉的,但是其實 head,content,foot 這些單詞也都行啊,誰知道用啥(可能我老了吧,元件如果不是必要儘量不要讓人有記憶成本)。
元件命名
這裡推薦遵循vue 官方指南,值得一看
我們構建元件的時候通常會將其入口命名為 index.vue ,引入的時候,直接引入該元件的資料夾即可。
但是這樣做會有一個問題,當你編輯多個元件的時候,所有的元件入口都叫做index.vue
,容易糊塗
vscode 顯然意識到了這個問題,所以當檔名相同的檔案被開啟時,它會在檔名旁邊顯示資料夾名
如何解決呢,我們可以把 index.js 當作一個單純的入口,不承擔任何邏輯。僅僅負責引入component-name-container
以及export default component-name-container
my-app └── src └── components └── component-name ├── component-name.css ├── component-name-container.vue └── index.js 複製程式碼