阅读 2255

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

一、前言

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

项目的背景是做一个总管理后台,我的那个项目直接用的就是蚂蚁金服的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 或者 赞。