react实现一个分页、搜索高阶组件

5,768 阅读8分钟

一、前言

先感谢掘金这个平台让我可以看到别人的思想,别人的智慧。说真的,在这个平台上还是看到了许多有营养的文章。目前的我是产不出像大佬那样富有营养的文章,那就记录一点我在工作中的事情,如有说错的地方或是有不合理的地方,希望各位大佬指出。

项目的背景是做一个总管理后台,我的那个项目直接用的就是蚂蚁金服的ant-design-pro,不得不夸赞这个真的是太好用了。然后做这种管理后台总是会遇到很多表格,列表之类的展示,一般的表格和列表都会有两个最基本的诉求,那就是分页和搜索。然后我的代码中就充斥了很多这样的逻辑。 在 componentDidMount 的时候发送请求加载数据,在页码改变的时候加载数据,在客户搜索的时候加载数据......

总之就是我写了很多功能重复的代码,不行,我是一个善于思(作)考(死)的人,我要想一个办法去解决这个问题!然后我感觉react的高阶组件可以应用于此场景。

二、思考这个高阶组件要解决什么问题

本文就不在此说高阶组件到底是什么,本文主要探讨的目标在于如何利用高阶组件去解决问题,如果有对高阶组件不甚了解的同学可以先看看 react官方文档

接下来的探讨就基本我本次开发的项目中,先讲一下本次项目开发采用的技术栈。本次是开发一个后台管理系统,项目使用了 antddva。 其实其他的都不重要了,最重要的是这个 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 原本刚好是一个路由组件,当你在定义路由的页面去引用这个组件的时候,它已经被高阶组件包裹了,就是说对原本的这个 WarpComponentprops 传递的值现在都传递到了我们所编写的高阶组件上去了,所以,在这里,我们将原本属于WarpComponentprops “还” 给它。

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.dispathdva 提供给我的方法,我只需要根据 dva 定义的去使用去就可以了。 如果想直接使用 redux 的话按照 redux 那一套就可以了,使用 redux 的话这里就是 发起一个 action, 那么很简单,传进来的那个 type 只要改成 reduxaction 就可以了。

然后还有一个 resetData 方法,很明显的如果要 reset 的话,需要重置页码还有搜索参数:

resetData = ()=> {
    this.setState({
        page: 1,
        formValues: {}
    }, this.getData)
}

然后是获取数据,一般都是在 componentDidMount 生命周期里去获取。所以在加上代码:

componentDidMount() {
   this.getData()
}

4、如何使用

组件的代码我们是已经编写完了。组件很简单,使用起来也很简单。只需要在页码改变的时候,或者是搜索参数改变的时候去调用这个高阶组件所提供的 handlePageChange 方法和 searchData 方法就可以了。这里我提供一个使用范例,范例中有完整的高阶组件的代码,也有完整的使用的代码。 在这个项目中使用了 antd dva ,很真实的使用环境,希望可以提供帮助。在项目数据mock服务中我只处理了页码,没有去管搜索参数,有疑问可以打开 network看看搜索参数是否正常传递。

如果有疑问可以在掘金下回复我,也可以在我贴出的git项目的 issue中提出问题,我会尽力解答。

四、总结

文章写完啦,深深的感觉自己的陈述能力不足。言归正传,这就是一次高阶组件的使用,其实很多设计都是用到了高阶组件,就比如说 reduxconnect , 用过 antd 的朋友应该也知道在表单验证的时候有 Form.creategetFieldDecorator 。这些都是很经典的使用,平时我也喜欢用高阶组件去做一些权限处理啊什么的。 虽然说现在 react 已经推出了 hooks ,可能 class 类型的组件被淘汰掉只是时间的问题。试着去了解了一下 hooks 觉得我get不到其中的思想。等后续我再试着了解 hooks 看看能不能带来一个 hooks 的实现版本。

水平有限,可能说的有一些错的地方我却不自知, 希望大家指正。

最后:

使用范例地址:github.com/Chechengyi/…

我的 github地址:github.com/Chechengyi

码字不易,欢迎朋友们来一波 star 或者 赞。