Open fi3ework opened 6 years ago
在 深入react 技术栈 一书中,提到了基于 Decorator 的 HOC。而不是直接通过父组件来逐层传递 props,因为当业务逻辑越来越复杂的时候,props 的传递和维护也将变得困难且冗余。
书里对基于 Decorator 的 HOC 没有给出完整的实现,在这里实现并记录一下实现的思路。
整个实现的代码放到了 我的Github 上,是用来获取豆瓣的电影列表的,npm start 即可。
npm start
书里描述的整体思路,先将整个组件,按照 view 抽象为互不重叠的最小的原子组件,使组件间组合更自由。在这里最小的组件就是 SearchInput、SelectInput、List。原子组件一定是纯粹的、木偶式的组件,如果他们自身带有复杂的交互/业务逻辑,那么在组合起来以后可想需要修改多少个原子组件,也就失去了相对配置式的优势。
SearchInput
SelectInput
List
这是对原书代码稍加修改的 SearchInput 原子组件,因为没加 Icon,所以改了一下(逃),整体思路不变。原子组件没什么可说的,木偶组件就是接收 props 来实现功能,是对 view 的抽象。
需要一提的是 displayName,是用来确定组件的『身份』的,会被包裹它的组合组件用到,后面会提到组合组件。
displayName
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> ) } }
先放代码
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 的中有钩子函数,有要传递给原子组件的参数。
render
wrappedComponent
这样,视图逻辑就由原子组件组成,交互/业务逻辑由 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 后的组合组件包含原子组件,最后给从最外层传递参数就完成了。
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 不会和外部有关联,均可
对比 HOC 范式 compose(render)(state) 与父组件(Parent Component)的范式 render(render(state)),如果完全利用 HOC 来实现 React 的 implement,将操作与 view 分离,也未尝不可,但却不优雅。HOC 本质上是统一功能抽象,强调逻辑与 UI 分离。但在实际开发中,前端无法逃离 DOM ,而逻辑与 DOM 的相关性主要呈现 3 种关联形式:
在 深入react 技术栈 一书中,提到了基于 Decorator 的 HOC。而不是直接通过父组件来逐层传递 props,因为当业务逻辑越来越复杂的时候,props 的传递和维护也将变得困难且冗余。
书里对基于 Decorator 的 HOC 没有给出完整的实现,在这里实现并记录一下实现的思路。
整个实现的代码放到了 我的Github 上,是用来获取豆瓣的电影列表的,
npm start
即可。整体思路
书里描述的整体思路,先将整个组件,按照 view 抽象为互不重叠的最小的原子组件,使组件间组合更自由。在这里最小的组件就是
SearchInput
、SelectInput
、List
。原子组件一定是纯粹的、木偶式的组件,如果他们自身带有复杂的交互/业务逻辑,那么在组合起来以后可想需要修改多少个原子组件,也就失去了相对配置式的优势。组件实现
原子组件
这是对原书代码稍加修改的 SearchInput 原子组件,因为没加 Icon,所以改了一下(逃),整体思路不变。原子组件没什么可说的,木偶组件就是接收 props 来实现功能,是对 view 的抽象。
需要一提的是
displayName
,是用来确定组件的『身份』的,会被包裹它的组合组件用到,后面会提到组合组件。Decorator组件
先放代码
Decorator 的作用就是将业务/交互逻辑抽象出来进行了处理,view 还是交由原子组件来实现,可以看到最后的
render
渲染的还是wrappedComponent
,只不过是在经过 Decorator 之后多了几个 props,这些 props 的中有钩子函数,有要传递给原子组件的参数。这样,视图逻辑就由原子组件组成,交互/业务逻辑由 Decorator 来抽象。
组合组件
先上代码。
组合组件的 children 为根据不同业务需要包裹起来的原子组件,组合组件的逻辑处理功能来自于 Decorator,各种 Decorator 的钩子函数或者参数作为 props 传递给了 Selector,Selector 再用它们去完成原子组件之间的交互。组合组件通过之前提到的
displayName
为不同的原子组件分配 props 并根据业务需要进行组件间逻辑交互的调整。当我们业务逻辑变得复杂的时候,不要去增加 Decorator 的复杂度,而是去拼接多个 Decorator 再通过组合组件去处理具体的业务逻辑,这样能保证 Decorator 的可复用性。
业务组件
通过
compose
赋予组合组件不同的逻辑处理功能,然后根据业务需要让compose
后的组合组件包含原子组件,最后给从最外层传递参数就完成了。tips
在实际的场景中也不能滥用 HOC,基于 Decorator 的 HOC 一般是用来处理偏数据逻辑的部分,而 DOM 相关的东西就直接简单粗暴的用父组件就好了。
参考资料