使用React Hooks封装一套异步拉取的TableList

Background

React在16.7-alpha版本中新增了React的一些新特性,如Hook。

在这里主要想分享一下关于Hook使用

About Hook

  • useState() 让你定义一个state变量
  • useEffect() 让你定义一个副作用
  • useContext() 让你读取某些Context

PS: 了解上面三个 api 就可以对Hook进行基本的使用了。 类似于官方的声明可以直接去官网查看。下面直接进入实战模式。

Practice

 这里我们先说明我们要做的是什么, 我们在这里最终目的是要做一个由Hook制作的一个异步Table组件,Table使用的是 **Ant-design **里封装好的组件, 而cli用 **create-react-app **官方自带吧。因为侧重 hook 所以我们不会涉及到redux之类的,所以也不必要徒增不必要的代码量。 then let's do it。

Mock Data

第一步,首先我们要构造后台的数据,或者自己找一个可以进行mock的数据就可以。这个尽可能模仿后台,我这里使用的就用 ant-design-pro 提供的分页。https://preview.pro.ant.design/api/rule?currentPage=1&pageSize=10 可以在postman搜索即可 ,浏览器打开不正常。估计是做了额外的处理。但是有个问题就是跨域问题解决不了,我这边基于easy-mock又做了一个 接口, 理论上两个行为是一致的。

主要后台和前端配合下 前端传 当前页码 (currentPage) 和 展示数量 (pageSize) 即可。 后台返回需要有 总页数。 因为我们要做如下这个情况就必须得要有总页数。

Like this. ↓

image

anyway. 数据有了就可以下一步了。

Font-end

构建目录

  • create-react-app 构建出基本的react项目 这个可以看官方文档

  • 对package.json进行修改,把 react , react-dom 两个修改成 v16.8.0-alpha.1, 然后重新安装

  • 这里还需要把 antd 加上 毕竟我们也要用到他们的Table组件

  • 还有一个请求这里直接用原生的fetch就可以了

贴一下目前的package.json

代码块

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "antd": "^3.13.0",
    "react": "v16.8.0-alpha.1",
    "react-dom": "v16.8.0-alpha.1",
    "react-scripts": "2.1.3"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}

接下来我们删除目前不需要用到的页面,并且把 server-worker 去掉, 最后简化到如下即可。 代码可在github上预览。

接下来第一次尝试

异步获取数据并且渲染到页面

首先这里有一个关键的问题 , **异步 **在一开始,我们就得明确hook中使用带有副作用的都得使用 useEffect() 进行定义, 异步也是副作用的一种。因此,我们获取数据的时候应该是使用 useEffect() 代码走一个

yap,我们可以得到我们想要的数据了!

image

解释一下这里, useEffect() 这个非常强大, 哈哈哈。 不愧是在React发布会上Ryan特别点了一下,真好用。哈哈

这里主要是容纳了 componentDidMount 和 componentDidUpdate return 的时候就是 组件 umount的时机, 因此 subscribe在useEffect中,但return的时候就应该把他们给 unsubscribe。

Ok. just fine.

下一步。

我们已经知道如何得到数据 , 那么我们把它塞到我们的Table组件中。试一下

失败

image

这里错误的原因很简单,因为数据在更新之后并没有处罚需要 re-render 的条件,因此我们加上 useState()

image

数据请求成功。并且渲染成功! but,这里有一个问题。为什么我们会有这么多条请求?如果不报错甚至会出现死循环? 因为 调用的位置 出现问题了! 想想我们以前写的react, 只有一次的触发时机。 那就是 componentWillMount 或者 componentDidMount 那么 ,这个位置在哪里呢?

useEffect() 就因此而来。

image

没有任何问题! 但是,这里我多做了一步,就是 给useEffect添加了第二个参数

useEffect() 的第二个参数就是专门做数据对比,它的入参是一个变量。只有在变量指向的数据进行修改后,进行对比发现不一致时,才会重新进行渲染。

好的,现在才是正题啊!

封装异步Table组件

定义入参方法

展示的columns, 请求后台api, 传后台参数, 后台返回的数组的字键名称

function asyncTableList(columns, queryAction, params, cgiKey) {

// do something

}

整套代码如下

代码块


/**
 * 异步table
 * 组件使用方法
 * 
 * import renderAsyncTable from './this.file'
 * 
 * const asyncTable = renderAsyncTable(展示的columns, 请求后台api, 传后台参数, 后台返回的数组的字键名称)
 * 
 * function App() {
 *   const [query] = useState()
 *   const columns = [...]
 *   const asyncTable = renderAsyncTable(columns, api, query, listName ) 
 *   return (
 *    
* {asyncTable} *
* ) * } * */ import React, { useEffect, useReducer } from 'react' import Table from 'antd/lib/table'; function useAsyncTable (columns, queryAction, params, listName) { const paginationInitial = { current: 1, pageSize: 10 } const [state, dispatch] = useReducer( (state, action) => { const { payload } = action switch (action.type) { case 'TOGGLE_LOADING': return { ...state, loading: !state.loading } case 'SET_QUERY': return { ...state, query: payload.params, pagination: paginationInitial } case 'SET_PAGINATION': return { ...state, pagination: payload.pagination } case 'SET_DATA_SOURCE': return { ...state, dataSource: payload.dataSource } default: return state } }, { loading: false, query: null, pagination: paginationInitial, dataSource: [] } ) function handleTableChange (event) { if (event) { const { current } = event dispatch({ type: 'SET_PAGINATION', payload: { pagination: { ...state.pagination, current } } }) } } function doQuery () { dispatch({ type: 'TOGGLE_LOADING' }) const { current, pageSize } = state.pagination const pagination = { current, pageSize } queryAction({ ...state.query, ...pagination }) .catch(err => { dispatch({ type: 'TOGGLE_LOADING' }) return {} }) .then((payload) => { if(payload.pagination && payload.list) { const { pagination: { total } } = payload console.log('total', total) dispatch({ type: 'TOGGLE_LOADING' }) if (!state.pagination.total) { dispatch({ type: 'SET_PAGINATION', payload: { pagination: { ...state.pagination, total } } }) } dispatch({ type: 'SET_DATA_SOURCE', payload: { dataSource: payload[listName] } }) } }) } // cDM cDU useEffect( () => { if (params && JSON.stringify(params) !== JSON.stringify(state.query)) { dispatch({ type: 'SET_QUERY', payload: { params } }) } else { doQuery() } }, [params, state.pagination.current, state.query] ) return ( record.key} pagination={state.pagination} dataSource={state.dataSource} loading={state.loading} onChange={handleTableChange} /> ) } export default useAsyncTable

useReducer

这里解释一下:

在React官网的Hook中也有用到useReducer这种方法! 怎么说呢? 用hook的方式写一个redux吧。 用法和redux一样。state只能被dispatch的数据进行修改。 dispatch用type来区分修改state里的哪个数据,然后 我这边用payload 带数据进去。修改 state 内部数据。

每次做diff的之后 只拿 透传过来的 params 和 存在reducer里的 query做对比,如果有修改就进行修改。

没有的话就是调用请求。

这里调用请求可以理解为 :

  1. 只有在 current 页面不一致的时候调用
  2. 只有在query更改过后进行修改

而params在这里只是作为一个触发修改query的操作,并不会直接进行请求。

调用页面

调用相对简单。 我们需要传入的参数只有4个

  • columns的展示
  • 异步拉取的接口
  • 其他query, 这个query可能需要解析一下, 因为不是所有的异步拉取只有 pageSize 和 current 两个参数。 它们还有可能是 cityId 为 10000 或者 name是小明 也有可能是集合 如 城市A区,名字小白。这样,我们异步在使用异步拉取的接口 就可以不单单的延申为 pageSize 和 current这么单调。 它也可以combine参数后得到更好的拓展。
  • listName。 这个主要是为了不同的后台,有可能它是比较泛的名称如 lists 或者 beanList 等等,但也有可能是 cities,topics。 因此也用透传的方式。 更加强壮。

代码块

import React, {useState} from 'react';

import renderAsyncTable from './asyncTable'
import getAjax from './utils/ajax'

function App() {
  const [query] = useState()

  const columns = [
    {
      title: '规则名称',
      dataIndex: 'name',
    },
    {
      title: '描述',
      dataIndex: 'desc',
    },
    {
      title: '服务调用次数',
      dataIndex: 'callNo',
      render: val => `${val} 万`,
      // mark to display a total number
      needTotal: true,
    },
    {
      title: '上次调度时间',
      dataIndex: 'updatedAt',
    },
  ];

  const asyncTable = renderAsyncTable(columns, (query) => getAjax('o2po/data', query), query, 'list' ) 

  return (
    
{asyncTable}
) } export default App;

整套流程就可以了。

调用页面

​github:
https://github.com/panmouren/asyncTableList

欢迎start,如果有什么问题欢迎指正。! 指正比start更重要。 语雀https://www.yuque.com/zibuyu/fed/ognwv0#Background

你可能感兴趣的:(使用React Hooks封装一套异步拉取的TableList)