淺談組件增強
11月的上海,褪去了一絲溫暖,夾帶著絲絲寒意。獨自一人走在街上,望著路邊的男男女女,不禁讓我想起了那個前任的她。因為有她,我才學會了做飯,學會照顧人,學會怎么謙讓,不過在生命的進程中,她不過是一個過客罷了,她的出現和離開,對我都沒有本質的改變。一陣寒風吹過,我再次抬頭,是啊,剛才的故事蠻好的,是時候來知乎分享剛編的故事了,顯然,程序員并不會有前任。
細細想一想,其實生活中存在很多起修飾作用的東西,萬物本通,在編程界,也有許多的起修飾作用的東東,而且基于這個概念還發展出了許許多多的名稱。
裝飾函數
來首先看第一個裝飾函數,函數可以接受參數并且返回值。如果接受的值和返回的一樣呢?如果我們有一個構造函數:
function Person(name) {
this.name = name
}
Person.prototype.greet = function() {
console.log('hello:' + this.name)
}
現在由于 Person 函數是被封裝的,不可以改變其源代碼,而又想在先增加一個輸出時間的方法,再調用原輸出。就可以簡單采用如下方式:
const cloudic = new Person('cloudic')
cloudic.greet() // => hello:cloudic
// 函數直接包裝
function sayDate(f, ...args) {
console.log('show date')
typeOf f === 'funciton' && f(...args)
}
cloudic.dateGreet = sayDate(cloudic.greet)
cloudic.dateGreet() // => show date hello:cloudic
// 采用原型繼承包裝
function DateDecorator(man) {
const newMan = Object.create(man)
newMan.greet = function() {
console.log('show date')
man.greet()
}
return newMan
}
const newCloudic = DateDecorator(cloudic)
newCloudic.greet() // => show date hello:cloudic
這樣子類的更改并不會影響其他子類,當然第一種看起來更加簡單點,如果希望所做的改變能同步影響到子類,可以覆蓋原型對象上同名的方法,當然也可以用 decorator 語法糖。
為什么想動態增加個功能這樣繁瑣呢,JS 是動態語言,在運行的時候,可以方便的修改對象的屬性,但是由于函數存在作用域的限制,想要動態修改函數內部的變量很難實現。所以才繞著彎子,采用包裝的方式來實現。換個角度想下,如果一個對象或者函數內部提供了一個方法,該方法可以動態的修改函數內部的變量,那我們只需要調用該方法即可,這樣的模式暫且叫中間件模式吧,比如 redux 的中間件。
高階組件
說回組件層面,如果把上面說的應用到組件上,那么就是傳入一個組件返回另外一個組件。在 React 中,我們比較常用的叫 HOC 模式,常見的實現方式如下:
function HOC(WrappedComponent) {
return class Wrap extends React.Component {
render() {
return <WrappedComponent {...this.props}/>
}
}
}
在使用的時候,只需要傳入需要包裝的組件,調用函數后會返回一個包裝過的組件。這樣的好處是通過避免直接調用原組件,采用類似代理的方式來間接調用,從而可以方便后期替換原組件,可以做適配器,同時相比于 mixin 的實現方式,高階組件更容易被調試,由于 mixin 是混入模式,導致在組件之間共享的代碼很隱晦,如果方法比較多的話,還可能會覆蓋現有的組件,所以 React 官方廢棄了 mixin 的模式,推崇高階組件來代替組件之間共享代碼。
抽象組件
在 Vue 中,如果想對一個組件或者 DOM 進行相關功能的增強,我們可以用下面幾種方式:
其一我們可以想到是采用指令的形式:
Vue.directive('name', {
inserted: function (el) {
// do something...
}
})
指令可以方便的增加功能和復用,但是指令不能使用回調函數,不能傳 props,沒有事件,導致使用起來的靈活度不高。指令更多的是做 DOM 層面的工作,封裝一些方法來修飾元素或操作元素屬性。
其二我們可以用函數式組件:
Vue.component('name', {
functional: true,
render: function (createElement, context) {
// ...do something
},
// Props 可選
props: {
// ...
}
})
函數式組件不會被實例化,也就是沒有 data 和 this 上下文,也不能使用事件回調。可以簡單的認為就是一個函數而已。不過函數式組件的優點也在于此,并不會有額外的性能開銷,從而可以提高程序的性能。
其三就是采用抽象組件:
Vue.component('name', {
abstract: true,
render: function (createElement) {
return this.$slots.default[0]
}
})
抽象組件在 Vue 官網并沒有文檔介紹,因為這是一個內部組件定義的功能,不是很穩定,隨時可能會更改,并且不會通知到你。Vue 內置的組件比如 keep-alive, transition, component, slot 都是抽象組件,抽象組件沒有自己的 DOM 元素,只是簡單增加功能然后返回子元素。和純函數組件相比,它有自己的生命周期,會被實例化,內部有this,是一個真正的組件,所以可以用 emit 發放事件。
總結
在組件增強的各個方法中,每個都有優劣,至于到底選擇那個,還是需要使用者根據業務場景來決定。
來自:https://zhuanlan.zhihu.com/p/30818429