『 文章首發於GitHub Blog 』javascript
在深刻react 技術棧一書中,提到了基於Decorator的HOC。而不是直接經過父組件來逐層傳遞props,由於當業務邏輯愈來愈複雜的時候,props的傳遞和維護也將變得困難且冗餘。前端
書裏對基於Decorator的HOC沒有給出完整的實現,在這裏實現並記錄一下實現的思路。java
整個實現的代碼放到了個人Github上,是用來獲取豆瓣的電影列表的,npm start
便可開箱。react
書裏描述的總體思路,先將整個組件,按照view邏輯抽象爲互不重疊的最小的原子組件,使組件間組合更自由。在這裏最小的組件就是SearchInput
SelectInput
List
。原子組件必定是純粹的、木偶式的組件,若是他們自身帶有複雜的交互/業務邏輯,那麼在組合起來之後可想須要修改多少個原子組件,也就失去了相對配置式的優點。git
這是對原書代碼稍加修改的SearchInput原子組件,由於沒加Icon,因此改了一下(逃),總體思路不變。原子組件沒什麼可說的,木偶組件就是接收props來實現功能,是對view層邏輯的抽象。github
須要一提的是displayName
,是用來肯定組件的『身份』的,會被包裹它的組合組件用到,後面會提到組合組件。npm
export default class SearchInput extends PureComponent {
static displayName = 'SearchInput'
render() {
const { onSearch, placeholder } = this.props
return (
<div> <p>SearchSelect</p> <div> <Input type="text" placeholder={placeholder} onChange={onSearch} /> </div> </div> ) } } 複製代碼
先放代碼app
const searchDecorator = WrappedComponent => {
class SearchDecorator extends Component {
constructor(props) {
super(props)
this.handleSearch = this.handleSearch.bind(this)
this.state = {
keyword: ''
}
}
handleSearch(e) {
this.setState({
keyword: e.target.value
})
this.props.onSearch(e)
}
render() {
const { keyword } = this.state
return (
<WrappedComponent {...this.props} data={this.props.data} keyword={keyword} onSearch={this.handleSearch} /> ) } } 複製代碼
Decorator的做用就是將業務/交互邏輯抽象出來進行了處理,view的邏輯仍是交由原子組件來實現,能夠看到最後的render
渲染的仍是wrappedComponent
,只不過是在通過Decorator以後多了幾個props,這些props的中有鉤子函數,有要傳遞給原子組件的參數。async
這樣,視圖邏輯就由view層抽象,交互/業務邏輯由Decorator來抽象。函數
先上代碼。
export default class Selector extends Component {
render() {
return (
<div> { this.props.children.map((item) => { // SelectInput if (item.type.displayName === 'SelectInput') { ... } // SearchInput if (item.type.displayName === 'SearchInput') { return React.cloneElement(item, { key: 'searchInput', onSearch: this.props.onSearch, placeholder: this.props.searchPlaceholder } ) } // List if (item.type.displayName === 'List') { ... }) } </div>
)
}
}
複製代碼
組合組件的children爲根據不一樣業務須要包裹起來的原子組件,組合組件的邏輯處理功能來自於Decorator,各類Decorator的鉤子函數或者參數做爲props傳遞給了Selector,Selector再用它們去完成原子組件之間的交互。組合組件經過以前提到的displayName
爲不一樣的原子組件分配props並根據業務須要進行組件間邏輯交互的調整。
一個 Decorator 只作最簡單的邏輯,只是給組件增長一個原子的智能特性。業務組件經過組織和拼接 Decorator 來實現功能,而不是改變 Decorator 自己的邏輯。
當咱們業務邏輯變得複雜的時候,不要去增長Decorator的複雜度,而是去拼接多個Decorator再經過組合組件去處理具體的業務邏輯,這樣能保證Decorator的可複用性。
const FinalSelector = compose(asyncSelectDecorator, selectedItemDecorator, searchDecorator)(Selector)
class SearchSelect extends Component {
render() {
return (
<FinalSelector {...this.props}> <SelectInput /> <SearchInput /> <List /> </FinalSelector>
)
}
}
class App extends Component {
render() {
return (
<SearchSelect searchPlaceholder={'請搜索電影'} onSearch={(e) => { console.log(`自定義onSearch: ${e.target.value}`) }} onClick={(text) => { console.log(`自定義onClick: ${text}`) }} url="/v2/movie/in_theaters" /> ) } } 複製代碼
經過compose
賦予組合組件不一樣的邏輯處理功能,而後根據業務須要讓compose
後的組合組件包含原子組件,最後給從最外層傳遞參數就完成了。
在實際的場景中也不能濫用HOC,基於Decorator的HOC通常是用來處理偏數據邏輯的部分,而DOM相關的東西就直接簡單粗暴的用父組件就行了。
對比 HOC 範式 **compose(render)(state) **與父組件(Parent Component)的範式 render(render(state)),若是徹底利用 HOC 來實現 React 的 implement,將操做與 view 分離,也何嘗不可,但卻不優雅。HOC 本質上是統一功能抽象,強調邏輯與 UI 分離。但在實際開發中,前端沒法逃離 DOM ,而邏輯與 DOM 的相關性主要呈現 3 種關聯形式:
- 與 DOM 相關,建議使用父組件,相似於原生 HTML 編寫
- 與 DOM 不相關,如校驗、權限、請求發送、數據轉換這類,經過數據變化間接控制 DOM,能夠使用 HOC 抽象
- 交叉的部分,DOM 相關,但能夠作到徹底內聚,即這些 DOM 不會和外部有關聯,都可