一、前言
先感谢掘金这个平台让我可以看到别人的思想,别人的智慧。说真的,在这个平台上还是看到了许多有营养的文章。目前的我是产不出像大佬那样富有营养的文章,那就记录一点我在工作中的事情,如有说错的地方或是有不合理的地方,希望各位大佬指出。
项目的背景是做一个总管理后台,我的那个项目直接用的就是蚂蚁金服的ant-design-pro,不得不夸赞这个真的是太好用了。然后做这种管理后台总是会遇到很多表格,列表之类的展示,一般的表格和列表都会有两个最基本的诉求,那就是分页和搜索。然后我的代码中就充斥了很多这样的逻辑。 在 componentDidMount
的时候发送请求加载数据,在页码改变的时候加载数据,在客户搜索的时候加载数据......
总之就是我写了很多功能重复的代码,不行,我是一个善于思(作)考(死)的人,我要想一个办法去解决这个问题!然后我感觉react的高阶组件可以应用于此场景。
二、思考这个高阶组件要解决什么问题
本文就不在此说高阶组件到底是什么,本文主要探讨的目标在于如何利用高阶组件去解决问题,如果有对高阶组件不甚了解的同学可以先看看 react官方文档 。
接下来的探讨就基本我本次开发的项目中,先讲一下本次项目开发采用的技术栈。本次是开发一个后台管理系统,项目使用了 antd
、dva
。 其实其他的都不重要了,最重要的是这个 dva
,使用过的同学应该都知道,它提供了一个 model
的概念。也就是说我们所获取的数据是要用这个 model
去管理的。我这个高阶组件要实现的最基本的功能有一下两点:
-
翻页获取数据
-
能够根据表单搜索数据
三、开始编写
1、搭好架子
废话就不多说了,开始编写这个高阶组件,先把这个组件写出来再说!新建一个table.js
文件,这个文件有以下内容:
import React from 'react'
export default (WarpComponent)=> {
return class extends React.Component {
state = {
formValues: {} // 表单搜素字段
page: 1, // 分页页码
num: 10
}
render(){
return (
<WarpComponent {...this.props} />
)
}
}
}
现在写出来的这个高阶组件已经可以直接使用了,使用的时候只需要:
import TableHoc from 'xxx'
@TabelHoc
export default class XXX {...}
// 相当于 TableHoc(XXX)
可以看见其实高阶组件就是一个函数,接收了组件然后返回了组件。可以看到在渲染被包裹的那个组件的时候把 props
全部解构给了被包裹的这个组件,这并不是多此一举。想象一下如果这个WarpComponent
原本刚好是一个路由组件,当你在定义路由的页面去引用这个组件的时候,它已经被高阶组件包裹了,就是说对原本的这个 WarpComponent
的 props
传递的值现在都传递到了我们所编写的高阶组件上去了,所以,在这里,我们将原本属于WarpComponent
的 props
“还” 给它。
2、把配置项提出来
在上面的编写中我把分页的页码和条数都设定死了,其实这是很不友好的。我们应该让它变成一个可以配置的项。修改以上的代码为:
import React from 'react'
export default ({type, page, num}) => WrapComponent => {
return class extends React.Component {
state = {
formValues: {},
page: page || 1,
num: num || 10
}
render(){
return (
<WarpComponent {...this.props} />
)
}
}
}
在使用的时候只需要:
import TableHoc from 'xxx'
@TableHoc({
page: 2, // 这里传了多少那高阶组件里初始化的值就是多少
num: 20,
type: 'xxx'
})
export default class XXX {...}
这里说一下这个 type
是什么,其实这个 type
需不需要取决于你自己的项目。比如我这个项目,我用了 dva
那数据我都是用集中管理在 models
的。用过 dva
的朋友都知道,它有一个 命名空间
的概念,就是在 dispatch
的时候有一个 type
需要我们去传嘛。如果你没有使用 dva
或者 redux
。 那就直接在这个高阶组件里去管理、获取数据然后作为 props
传给被包裹的组件就行了,那相应的你也不需要这个 type
参数了。
还有一个问题,就是这个高阶组件它需要去执行 dispatch
操作嘛,那就先需要 connect
,注意,这里你是使用的 dva
还是 redux
其实根本没区别, 本质上这个 connect
也是一个高阶组件。现在我把 connect
写上去:
import TableHoc from 'xxx'
import { connect } from 'dva'
@connect()
@TableHoc({
page: 2, // 这里传了多少那高阶组件里初始化的值就是多少
num: 20,
type: 'xxx'
})
export default class XXX {...}
需要注意的是这个 connect
一定要写到 TableHoc
前面。
3、给予组件翻页,和搜索的功能
现在要说的就是这个组件核心的功能,翻页和搜索,现在 TableHoc
组件里写上两个方法:
searchData = formValues => {
this.setState({
page: 1,
formValues
}, this.getData)
}
handlePageChange = (page, num) => {
this.setState({
page,
num
}, this.getData)
}
先不去管 this.getData
这个方法,这个两个方法明显就是要给被包裹的组件使用的,所以要传给被包裹的组件。还有页码,被包裹的组件在翻页的时候肯定也是需要展示页码的,所以都传下去。
改写 render
方法:
render(){
return (
<WrapComponent
ref={com => this.warpCom = com}
{...this.props}
{...this.state}
searchData = {this.searchData}
handlePageChange = {this.handlePageChange}
resetData = {this.resetData}
/>
)
}
可以注意我给被包裹的组件加上了一个 ref
属性,这样我就可以拿到组件的实例了。为什么要加呢,主要是我考虑到一种情况,就是除了翻页、还有表单搜索这样的一些搜索字段,可能还有一个额外的参数需要用作搜索,所以我把这个决定的权利给到被包裹的组件。接下来完善 this.getData
方法:
getData = ()=> {
let elseSearchPrams = {}
if ( this.warpCom && this.warpCom.getSearchParams ) {
elseSearchPrams = this.warpCom.getSearchParams()
}
const { page, num, formValues } = this.state
this.props.dispatch({
type,
payload: {
page,
num,
...formValues,
...elseSearchPrams
}
})
}
可以看到我试着去获取了一下 warpCom
有没有额外需要传入搜索的参数,在 warpCom
中只需要去定义一个名为 getSearchParams
的方法,然后返回一个包含搜索信息的对象就可以了。这里的 this.props.dispath
是 dva
提供给我的方法,我只需要根据 dva
定义的去使用去就可以了。 如果想直接使用 redux
的话按照 redux
那一套就可以了,使用 redux
的话这里就是 发起一个 action
, 那么很简单,传进来的那个 type
只要改成 redux
的 action
就可以了。
然后还有一个 resetData
方法,很明显的如果要 reset
的话,需要重置页码还有搜索参数:
resetData = ()=> {
this.setState({
page: 1,
formValues: {}
}, this.getData)
}
然后是获取数据,一般都是在 componentDidMount
生命周期里去获取。所以在加上代码:
componentDidMount() {
this.getData()
}
4、如何使用
组件的代码我们是已经编写完了。组件很简单,使用起来也很简单。只需要在页码改变的时候,或者是搜索参数改变的时候去调用这个高阶组件所提供的 handlePageChange
方法和 searchData
方法就可以了。这里我提供一个使用范例,范例中有完整的高阶组件的代码,也有完整的使用的代码。 在这个项目中使用了 antd
dva
,很真实的使用环境,希望可以提供帮助。在项目数据mock服务中我只处理了页码,没有去管搜索参数,有疑问可以打开 network看看搜索参数是否正常传递。
如果有疑问可以在掘金下回复我,也可以在我贴出的git项目的 issue中提出问题,我会尽力解答。
四、总结
文章写完啦,深深的感觉自己的陈述能力不足。言归正传,这就是一次高阶组件的使用,其实很多设计都是用到了高阶组件,就比如说 redux
的 connect
, 用过 antd
的朋友应该也知道在表单验证的时候有 Form.create
和 getFieldDecorator
。这些都是很经典的使用,平时我也喜欢用高阶组件去做一些权限处理啊什么的。 虽然说现在 react
已经推出了 hooks
,可能 class
类型的组件被淘汰掉只是时间的问题。试着去了解了一下 hooks
觉得我get不到其中的思想。等后续我再试着了解 hooks
看看能不能带来一个 hooks
的实现版本。
水平有限,可能说的有一些错的地方我却不自知, 希望大家指正。
最后:
使用范例地址:github.com/Chechengyi/…
我的 github地址:github.com/Chechengyi
码字不易,欢迎朋友们来一波 star 或者 赞。