前言:本系列是针对于React在界面开发痛点的一些解决方案,只是React应用中偏向展示的一环
构建一个业务与UI分离的react应用
本篇是基于HOC方案并未使用Hooks
业务逻辑与UI
在编写一个react组件前,我们一定要弄清两件事。
- 什么是UI?
- 什么是业务逻辑?
UI:组件的具体展示元素,通俗点就是组件的长相。接受到合理的数据就可以展示出一个合格的组件。
业务逻辑:获取数据、发送请求等等有比较明确的独特业务的逻辑。
为什么要业务逻辑与UI分离?
在编写react组件的时候,经常会出现业务逻辑相似,UI基本相同的组件,可能只是获取的数据方式有一些不同。
for example: 掘金的感兴趣小册列表为例子
- 最外层的红框为这个小册的组件Booklet
- 左侧的是小册的封面
- 右侧上方的是小册的名称
- 购买人数
- 小册的链接
以上这些都是Booklet组件的UI元素
在Component中:
Booklet组件只需要知道以下数据就可以正常工作:
- 小册的封面(imageUrl)
- 小册的名称(name)
- 购买人数(buyerNums)
- 链接(link)
UI组件并不需要知道:
- 如何获取数据,(如何根据小册id如何获取到的)
- 数据何时回来,(请求结束填充数据)
- 请求的具体处理,(请求开始、结束、容错、什么时候开始loading等等)
以上这些应该在哪里处理?
在Container中
- 根据小册ID获取数据。
- 判断请求的当前状态。(loading、error、success、cancel)。
- 制作好数据传递给Component
想要分离UI与业务逻辑时遇到的常见痛点
- 生命周期中包含请求代码,不好抽离。
- 内部state导致组件后续扩展能力差。
如何解决以上问题。
recompose可以合理解决以上问题。
在传统的写法中:
path: components/card/index.jsx
class Card extends React.PureComponent {
state = {
name: undefined,
link: undefined,
buyerNum: undefined,
logoUrl: undefined,
}
componentDidMount() {
getCardById(this.props.id).then(card => {
const {name ,link, buyerNum, logoUrl} = card;
this.setState({
name,
link,
buyerNum,
logoUrl,
})
});
}
render() {
const {name ,link, buyerNum, logoUrl} = this.state;
return (
<div className="card">
<div className="logo">
<img src={logoUrl} alt={name} />
</div>
<div className="name">{name}</div>
<div className="buyer-num">
{buyerNum}人已购买
</div>
<div className="booklet-link">
<a href={link}>试读</a>
</div>
</div>
)
}
}
复制代码
上面的代码就是标准的业务与UI不分离
难以维护的影响
后续需求仍然使用这个卡片组件的UI,但是变成了卡片列表包含多个卡片,使用卡片id获取卡片数据的业务逻辑就很不合理。
如何解决?
UI与业务分离
UI层
path: components/card/index.jsx
const Card = ({name, logoUrl, buyerNum, link}) => {
return (
<div className="card">
<div className="logo">
<img src={logoUrl} alt={name} />
</div>
<div className="name">{name}</div>
<div className="buyer-num">
{buyerNum}人已购买
</div>
<div className="booklet-link">
<a href={link}>试读</a>
</div>
</div>
)
}
复制代码
业务逻辑层
path: containers/Card/index.js
import {lifecycle, compose, withState, withProps} from 'recompose';
import Card from '../../components/Card';
// recompose提供的创建state的hoc
const withCardState = withState(
'card', 'handleUpdateCard', {}
);
// recompose提供的再次处理props的hoc
const withCardProps = withProps(
({card}) => ({
...card,
})
)
// recompose提供的生命周期钩子的hoc
const withLifecycle = lifecycle({
componentDidMount() {
getCardById().then(card => {
this.props.handleUpdateCard(card);
})
}
});
// compose 从右边往左边执行嵌套HOC的逻辑 相当于lodash的flowRight,顺序很关键。
export default compose(
withCardState,
withCardProps,
withLifecycle,
)(Card);
复制代码
新的代码种Card的UI组件不再被任何业务逻辑干扰。Card的container包含了本次根据id获取卡片的业务逻辑。如果后续需要卡片列表。只需要一个CardList的container去获取数据,渲染Card的UI组件。
UI与业务分离的思路已经讲完了。
下一期会讲一讲reselect这个简洁的第三方库如何减少react组件无意义的render。
最后打个广告(我们一起维护的学习公众号)
公众号主要面向的是初级/应届生。内容包含我们从应届生转换为职场开发所踩过的坑,以及我们每周的学习计划和学习总结。 内容会涉及计算机网络、算法等基础;也会涉及前端,后台,Android等内容~