![](https://p.upyun.lithub.cc/p4.ssl.qhimgs4.com/t01b6fd1f549ff588bc.png)
## 需求描述
由于我所在的业务是资讯内容类业务,因而在业务中会经常碰到如下场景:有一个内容列表,列表中需要按照一定的规则插入广告。除了获取广告数据,广告展现和点击后需要有打点上报逻辑。正常来说我们会这么写:
```js
import React from 'react';
export default class extends React.Component {
state = {newsData: [], adData: []};
constructor() { this.getNewsData(); }
getNewsData() {
const newsData = [...];
this.setState({newsData});
this.getAdData(newsData.length / 2); //根据新闻数和插入规则换算广告请求数
}
getAdData() {
const adData = [...];
this.setState({adData});
}
render() {
const {newsData, adData} = this.state;
const comps = [];
for(let i = 0; i < newsData.length; i++) {
// 根据插入规则判断当前新闻卡片后是否要插入广告
comps.push();
if(i % 2) { comps.push(); }
}
return (
{comps}
);
}
}
class AdCard extends React.Component {
componentDidMount() {
observe(this.dom, () => {});
}
onClick = () => {};
onMouseUp = () => {};
onMouseDown = () => {};
getDOM = dom => this.dom = dom;
render() {
return {this.props.title}
}
}
```
逻辑非常的简单,`getNewsData()` 拿到资讯列表数据之后计算需要请求的广告数调用 `getAdData()` 请求广告数据,最后根据插入规则将资讯和内容渲染到列表中。广告使用自定义组件渲染,使用 [Intersection Observe][1] API 实现广告曝光打点,监听 DOM 对应的点击时间实现广告点击打点。
![](https://p.upyun.lithub.cc/p0.ssl.qhimgs4.com/dr/1920_1116_100/t012f2d497ae092e051.png)
如果说只有一个组件是这样的还好说,但是从上图可以看出,我们有大量的内容+广告混排场景。整体的逻辑和刚才说的都是一样的,唯一的区别是不同的列表对应不一样的显现形式。在这种情况下如何设计一个既能将通用逻辑提取,又能满足各个模块的自定义需求的通用模块就成了我们必须考虑的事情了。
## React 组件设计模式
在具体讨论方案之前,我们先简单的了解一下常见的 React 组件设计模式。基本上分为以下几种方案:
- [Context 模式][2]
- [组合组件][3]
- 继承模式
- 容器组件和展示组件
- Render Props
- Hoc 高阶组件
其中 Context 模式多用来在多层嵌套组件中进行跨组件的数据传递,针对我们当前组件层级不多的情况用处不是非常大,这里就不多表。我们来看看剩下的几个模式各自有什么优缺点,最终来评估下是否能应用到我们的场景中。
### 组合组件
组合组件是通过模块化组件构建应用的模式,它是 React 模块化开发的基础。除去普通的按照正常的业务功能进行模块拆分,还有就是将配置和逻辑进行解耦的组合组件方式。例如下面的组合方式就是利用类似 Vue 的 slot 方式将配置通过子组件的形式与 `` 组件进行组合,是的组件配置更优雅。
```jsx
Modal Title
Modal Content
```
又如下面的下拉选择组件,通过将 `` 和 `