重构示例 使用HOC编写可维护的React Component
main.js
1 | import React from "react"; |
如果你匆匆的瞄了一眼,应该会感觉上面的代码很乱是不是?
上面的代码有几个特点。
1.视图层和逻辑层混合在一起
由于react提供class的写法。所有视图层的代码很理所当然的容易跟逻辑层的代码混写在一起。
这一点的好处是如果代码写得好紧凑的话,并不会显得难看。
2.多组件逻辑代码穿插
造成无关逻辑代码穿插在一个文件中的原因是。这是容器组件,承载着这个页面逻辑。这也是为什么我选这个例子的原因。
3.代码细节,渲染细节暴露所以层次不分明
其它缺点不属于本次文章所谈范围。
再让我们看看怎么解决
你应该知道stateless组件,其实就是一个纯函数,输入什么就输出什么。输出的东西没有状态,所有的依赖都从props上注入。由于纯函数把逻辑都丢给上层去实现,所以写起来特别的简单。让我们试试怎么实现一个纯函数组件,然后慢慢的把纯函数变成能承载我们业务的组件。
假设我们1
2
3
4
5
6
7
8
9
10
11import React from "react";
function FollowStatistics({hasErp,showModal,goDetail,goDetail}) {
return (<div>
此处隐藏具体逻辑反正你们也不关心
</div>)
}
function LfdjFollow({hasErp,showModal,goDetail,goDetail}) {
return (<div>
此处隐藏具体逻辑反正你们也不关心
</div>)
}
接下来让我们实现这个逻辑1
2
3
4
5
6//已隐藏无关代码
class main{
renderFollow() {
return bindLfdj ? <LfdjFollow /> : <FollowStatistics/>
}
}
我们可以这样做1
2
3
4
5
6//再写一个纯组件 而不是用类函数来实现
//这方便于后续的扩展 虽然现在感觉效果不大
//但这是方向 因为大类是没法去优化的
function CombineFollowStatistics() {
return bindLfdj ? <LfdjFollow /> : <FollowStatistics/>
}
我们还可以这样做
使用recompose1
2
3
4//这段代码应该配合上下文去理解的。这里的意思是
//props上的bindLfdj属性如果为true的话就渲染lfdjFollow否则渲染FollowStatistics
//这是一个HOC,属于recompose库的方法
branch(property("bindLfdj"), renderComponent(lfdjFollow))(FollowStatistics)
接下来我们给这个组件注入数据,最后的代码如下1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30import FollowStatistics from "../components/followStatistics"
import lfdjFollow from "../components/lfdjFollow"
import {
setDisplayName,
compose,
mapProps,
branch,
renderComponent
} from "recompose"
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { partial, property, get } from "lodash"
import { method } from "../action"
function mapStateToProps(state) {
const followStatistics = get(state, "case_statistics.main.followStatistics", {});
const bindLfdj = get(state, "case_statistics.main.bindLfdj");
return {
statistics: followStatistics,
bindLfdj
}
}
const mapActionToProps = partial(bindActionCreators, {
goDetail: method.goFollowDetailForMain
})
const emhance = compose(
setDisplayName("combineFollowAndLfdj"),
connect(mapStateToProps, mapActionToProps),
branch(property("bindLfdj"), renderComponent(lfdjFollow))
)
export default emhance(FollowStatistics)
我们使用connect注入了数据
{ followStatistics,bindLfdj }
和方法
{ goDetail1 }
为了调试我们还会为组件设置一个名字
setDisplayName(“combineFollowAndLfdj”)
我们就可以在主页面中直接了。最后重构的代码如下:
main.js
1 | import React from "react"; |
逻辑不见了,页面结构清晰了。那些逻辑都各回各家各找各妈去了。这也符合编程里只做一件事的说法(虽然一件事不好定义是什么但是这件事尽量不要定义得很大,在这里这件事就是构建页面结构)
再看看main.js自己的逻辑代码到哪里去了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61import { connect } from 'react-redux';
import { withRouter } from "react-router"
import { withProps, compose, lifecycle } from "recompose"
import main from "./containers/main"
import { actionsCreator } from "./../../../../base/config/action"
import { method } from "./action"
import { bindActionCreators } from "redux"
import { createSelector } from "reselect"
import { property, flowRight } from "lodash"
import { withLocalStorageWhenUnload } from "./../../../../base/systen/withSystem"
const mapStateToProps = createSelector(
[
property("case_statistics.main.date"),
property("case_statistics.main.date_type")
],
(date, date_type) => ({ date, date_type })
)
function mapDispatchToProps(dispatch) {
return bindActionCreators({
...method,
...actionsCreator
}, dispatch)
}
const enhance = compose(
withLocalStorageWhenUnload("manager/case_statistice", property("case_statistics")),
connect(mapStateToProps, mapDispatchToProps),
withRouter,
withProps(({ location: { query: { user_type, date_type } } }) => {
const isReferer = !!user_type && !!date_type
return {
isReferer
}
}),
lifecycle({
componentDidMount() {
const { props: {
getCurrentProjIsErp,
getTeamMenu,
getCurrentUserRole,
getVisitBarChart,
getFollowStatistics,
getDealStatistics,
getGroupMenu,
getOverdueStatistics,
date_type,
date
} } = this
getCurrentProjIsErp()
const UserRolePromise = getCurrentUserRole()
UserRolePromise.then(flowRight(getGroupMenu, property("roleInfo.value")))
UserRolePromise.then(() => {
getFollowStatistics({ date_type, date })
getVisitBarChart({ date_type, date })
getDealStatistics({ date_type, date })
getOverdueStatistics({ date_type, date })
})
getTeamMenu()
}
})
)
export default enhance(main)
再另一个文件中通过
withLocalStorageWhenUnload(“manager/case_statistice”, property(“case_statistics”))
connect(mapStateToProps, mapDispatchToProps)
withRouter
withProps
lifecycle
多个HOC实现了数据的注入逻辑的实现,其中withLocalStorageWhenUnload是自己实现的HOC,作用在于页面卸载后将某些数据存到localstorage中。这种装饰形的写法可以很方便逻辑的拆卸。
最后我感觉本文写得有点烂。很多想表达的东西都没有表达出来。代码也很冗长。毕竟这是我的第三遍文章吧。