在前端业务开发中,组件化已经成为咱们的共识。沉淀和复用组件,是提升开发效率的利器。但在组件复用的过程当中,咱们每每会遇到这样的问题,组件类似,却在结构或交互上有些许差异,须要对组件进行改造方可知足需求。这个问题以前在 React实践 - Component Generator 就有所说起。javascript
之初,咱们提出了组件配置式。在业务统一的状况下,仅仅修改组件用于配置的props就能够知足业务需求。但随着业务发生变化致使组件形态发生变化时,咱们就必须不断增长配置去应对变化,便会出现配置泛滥,而在扩展过程当中又必须保证组件向下兼容,只增不减,使组件可维护性的下降。前端
最近的项目开发中,@JasonHuang 提出了组件组合式开发思想,有效地解决了配置式所存在的一些问题。下面我将详细阐述其思想与具体实现。java
对于组件的 view 层,咱们指望组件是没有冗余的,组件与组件间 view 重叠的部分应当被抽离出来,造成颗粒度更细的组件,使组件组合产生更多的可能。编程
这种 view 细化的组合式思想早已在咱们团队可视化库 Recharts 中有所体现。Recharts 避免了复杂的图表配置,而将图表进行有效拆分,经过声明式的标签进行组合,从而使图表更具扩展性。json
一样,咱们在组件上也但愿秉承这种思想,先来看一下在现有业务比较典型的三个公共组件:app
这三个组件不管在 UI 仍是逻辑上均存在必定共性。在配置式中,咱们会将这三个组件经过一个组件的配置变换来实现,但无疑会提升单个组件内部逻辑的复杂性。echarts
再作一次分离!它们可由 SelectInput、SearchInput 与 List 三个颗粒度更细的组件来组合。而对于颗粒度最细的组件,咱们但愿它是纯粹的,木偶式的组件。函数式编程
例如 SelectInput 组件,组件状态彻底依赖传入的 props,包括 selectedItem (显示用户所选项)、isActive (当前下拉状态)、onClickHeader (反馈下拉状态)以及placeholder (下拉框提示)。咱们来看一下它的简要实现:函数
class SelectInput extends Component { static displayName = 'SelectInput'; render() { const { selectedItem, isActive, onClickHeader, placeholder } = this.props; const { text } = selectedItem || {}; return ( <div onClick={onClickHeader}> <Input type="text" disabled value={text} placeholder={placeholder} /> <Icon className={isActive} name="angle-down" /> </div> ); } }
当组件被再次分离后,咱们能够根据业务中的组件形态对其进行任意组合,造成统一层,摆脱在原有组件上扩展的模式,有效提升组件的灵活性。组件化
那么有了 view 细化再重组的公共组件后,是否是就能够愉快地开发了?
是的,但组件层面的抽象不该该只停留在 view 层面,组件中的相同交互逻辑和业务逻辑也应该进行抽象。
ReactEurope 2016 小记 - 上 中提到复用高阶函数的思想,编写 Higher-Order Components (高阶组件)来为基础组件增长新的功能。
Higher-Order Components = Decorators + Components。在咱们的组件中,也正是贯穿着这样函数式的思想,来完成组件逻辑上的抽象,例如:
// 完成SearchInput与List的交互 const SearchDecorator = Wrapper => { class WrapperComponent extends Component { handleSearch(keyword) { this.setState({ data: this.props.data, keyword, }); this.props.onSearch(keyword); } render() { const { data, keyword } = this.state; return ( <Wrapper {...this.props} data={data} keyword={keyword} onSearch={this.handleSearch.bind(this)} /> ); } } return WrapperComponent; }; // 完成List数据请求 const AsyncSelectDecorator = Wrapper => { class WrapperComponent extends Component { componentDidMount() { const { url } = this.props; fetch(url) .then(response => response.json()) .then(data => { this.setState({ data, }); }); } render() { const { data } = this.state; return ( <Wrapper {...this.props} data={data} /> ); } } return WrapperComponent; }
拥有 Decorator 以后,咱们就能赋予组件能力了,例如合成 Search 组件:
@SearchDecorator class Search extends Component { render() { return ( <Selector {...this.props} > <SearchInput /> <List /> </Selector> ); } }
那么当咱们将逻辑抽象成为多个 Decorator 时,又该如何去组合呢?你是否还记得 React Mixin 的前世此生 中提到的方法?没错,就是compose!这里建议读者 review 这篇文章,顺便回顾一下Mixin与高阶组件的不一样点。
// SelectedItemDecorator为List与SelectInput的交互,读者能够自行尝试实现 const FinalSelector = compose(AsyncSelectDecorator, SearchDecorator, SelectedItemDecorator)(Selector); class SearchSelect extends Component { render() { return ( <FinalSelector {...this.props} > <SelectInput /> <SearchInput /> <List /> </FinalSelector> ); } }
在配置式组件内部,组件与组件间以及组件与业务间是紧密关联的,而对于开发人员而言须要完成的仅仅是配置的工做。而组合式意图打破这种关联,寻求单元化,经过颗粒度更细的基础组件与抽象组件共有交互与业务逻辑的 Decorator,使组件更灵活,更易扩展,也使开发者可以完成对于基础组件的自由支配。
虽然组合式确实能解决配置式所存在的一些问题,但多层 Decorator 带来的多层包裹,会对组件理解和调试形成必定困难,也"不能"使用外部公有的方法。同时组合式所基于的函数式编程的思想可否被整个团队所接受,也是咱们须要考量的问题。