React Hooks 在列表页中的实践

栏目: 服务器 · 发布时间: 5年前

内容简介:在本文中,我想向大家介绍如果你还不知道在日常的开发中,我们常常会碰到一些列表页的开发需求。

在本文中,我想向大家介绍 React Hooks 在列表页中的实践,主要是通过 useReduceruseEffect 来实现的。以及表达一下我对 React Hooks 的理解与思考。

如果你还不知道 React 的这个新特性 React Hooks ,那么点击Hooks 简介 ; 如果你想看直接查看最后的实现效果,请点击 仓库

使用场景

在日常的开发中,我们常常会碰到一些列表页的开发需求。

一个列表页的最基本的功能就是列表的展示,而列表的展示就需要很多 state 去管理,比如:列表数据、数据总数、当前页、展示条数等等。而这些 state 绝大部分是所有列表页的都通用的即有共同的逻辑。

在以往的方案中还没有特别好的方案,可以共用这些逻辑代码。高阶组件 HOC 可以,但会引入组件层级过深的问题。(如读者有兴趣,可自行去了解高阶组件的用途,本文不深入讨论)。

好消息是, React Hooks 就能帮助我们完成这个心愿。

做什么

我们要是现实一个自定义 Hook useTable ,简单说明一下功能

  1. 接收一个 url ,向外暴露 { onSearch, bind: { loading, dataSource, pagination: { current, pageSize, total }, onChange } } 。 这个是基于 antd 开发的,所以 bind 内的东西是绑定在 antdTable 组件上的。
  2. 在页面初始化时,自动请求数据
  3. 在页面卸载时,取消异步请求的后续操作
  4. onChangeonSearch 时自动请求数据
  5. 在 loading 中,不会触发新的异步请求

好了话不多说,直接上代码。

// useTable.js
import { useReducer, useEffect } from 'react';
import axios from 'axios';

// action type
const DATA_CHANGE = 'DATA_CHANGE';
const STATE_CHANGE = 'STATE_CHANGE';

const DEFAULT_STATE = {
  loading: false,
  current: 1,
  pageSize: 10,
  total: 0,
  order: false,
  field: '',
  dataSource: [],
  params: {},
}

// 用作 useReducer 中的 reducer
const reducer = (state, action) => {
  const { type, data: { dataSource, ...nextState } } = action;
  switch (type) {
    case STATE_CHANGE:
      return {...state, ...nextState};
    case DATA_CHANGE:
      return {...state, dataSource, ...nextState};
    default:
      return state;
  }
}

export default (url, initState = {}) => {
  /**
   * useReducer 的概念和 redux 很像
   * 会返回一个 dispatch 函数,调用的时候传给它一个 action 
   * 相应的会有一个 reducer 函数,用于数据处理
   */
  const [{
    loading, // 加载态
    current, // 当前页
    pageSize, // 一页多少条
    total, // 总共多少条
    order, // 排序方向
    field, // 排序字段
    dataSource, // 数据
    params, // 额外搜索项
  }, dispatch] = useReducer(reducer, {
    ...DEFAULT_STATE,
    ...initState,
  });
    
  // 获取数据的 hooks
  useEffect(() => {
    let cancel = false;
    dispatch({ type: STATE_CHANGE, data: { loading: true } });

    axios.post(
      url,
      { current, pageSize, order, field, ...params },
    ).then(({ data, status }) => {
      if (status === 200) return data;
    }).then(({ data = [], total }) => {
      !cancel && dispatch({ type: DATA_CHANGE, data: { dataSource: data, total }});
    }).finally(() => dispatch({ type: STATE_CHANGE, data: { loading: false } }));
    
    // 返回值时页面卸载之后调用的函数
    return () => cancel = true;
  }, [url, current, pageSize, order, field, params]); // 当这几个状态改变时自动调用函数

  // 搜索事件
  function onSearch(nextParams) {
    // 点击搜索按钮 跳到第一页
    !loading && dispatch({ type: STATE_CHANGE, data: { params: nextParams, current: 1 } });
  }

  // 变更事件
  function onChange({ current, pageSize }, filters, { order = false, field = ''}) {
    !loading && dispatch({ type: STATE_CHANGE, data: { current, pageSize, order, field }});
  }

  return {
    onSearch,
    bind: {
      loading,
      dataSource,
      pagination: { current, pageSize, total },
      onChange,
    }
  };
}

// UseHooksTable.js
import React, { Fragment } from 'react';
import { Table } from 'antd';
import SearchForm from './SearchForm';
import useTable from './useTable';

const url = 'https://www.easy-mock.com/mock/5cf8ead34758621a19eef994/getData';
function UseHooksTable () {
  // 使用自定义 hook
  const { onSearch, bind } = useTable(url);
  const columns = [
    { title: '编号', dataIndex: 'id' },
    { title: '姓名', dataIndex: 'name' },
    { title: '年龄', dataIndex: 'age' },
    { title: '邮箱', dataIndex: 'email' },
    { title: '主页', dataIndex: 'url' },
    { title: '城市', dataIndex: 'city' },
  ];
  return (
    <Fragment>
      <SearchForm onSearch={onSearch}/>
      <Table
        rowKey={'id'}
        columns={columns}
        {...bind}
      />
    </Fragment>
  );
}

export default UseHooksTable;

复制代码

在代码中的注释简单解释了一下代码,应该也没有什么难点。

解决了什么问题

到此为止,我们应该思考 React Hooks 可以给我们带来些什么。 为此,我额外的写了一个使用 class 方式实现的列表页,下面上代码

import React, { Component, Fragment } from 'react';
import { Table } from 'antd';
import axios from 'axios';
import SearchForm from './SearchForm';

const url = 'https://www.easy-mock.com/mock/5cf8ead34758621a19eef994/getData';
class UseClassTable extends Component {
  state = {
    loading: false,
    current: 1,
    pageSize: 10,
    total: 0,
    order: 0,
    field: '',
    params: {
      name: '',
    },
    dataSource: [],
  }
  cancel = false;
  columns = [
    { title: '编号', dataIndex: 'id', sorter: true },
    { title: '姓名', dataIndex: 'name', sorter: true },
    { title: '年龄', dataIndex: 'age', sorter: true },
    { title: '邮箱', dataIndex: 'email', sorter: true },
    { title: '主页', dataIndex: 'url', sorter: true },
    { title: '城市', dataIndex: 'city', sorter: true },
  ];
  componentDidMount() {
    this.getData();
  }
  componentWillUnmount() {
    this.cancel = true;
  }
  // 搜索事件
  handleSearch = (nextParams) => {
    // 点击搜索按钮 跳到第一页
    !this.state.loading && this.setState({ params: nextParams, current: 1 }, this.getData);
  }
  // 变更事件
  handleTableChange = ({ current, pageSize }, filters, { order = false, field = ''}) => {
    !this.state.loading && this.setState({ current, pageSize, order, field }, this.getData);
  }
  getData() {
    const { current, pageSize, order, field, params } = this.state;
    this.setState({ loading: true }, () => {
      axios.post(
        url,
        { current, pageSize, order, field, ...params },
      ).then(({ data, status }) => {
        if (status === 200) return data;
      }).then(({ data = [], total }) => {
        !this.cancel && this.setState({ dataSource: data, total });
      }).finally(() => this.setState({ loading: false }));
    });
  }
  render() {
    const {
      loading, // 加载态
      current, // 当前页
      pageSize, // 一页多少条
      total, // 总共多少条
      dataSource, // 数据
    } = this.state;
    return (
      <Fragment>
        <SearchForm onSearch={this.handleSearch}/>
        <Table
          rowKey={'id'}
          loading={loading}
          columns={this.columns}
          pagination={{ current, pageSize, total }}
          dataSource={dataSource}
          onChange={this.handleTableChange}
        />
      </Fragment>
    )
  }
}

export default UseClassTable;
复制代码

我们可以看到使用 Hooks 的方式,我们可以把共有的逻辑封装到 Hooks 中,在所有有共有逻辑的页面都使用这样的 hook ,代码行数可以从原先的80行减少到30行,代码变得简单易懂,使用起来也很简单,提高的代码的复用性。

这应该就是 Hooks 的魅力。

结语

我们可以想象到,以后的社区会提供给我们有趣的 Hooks

在我们自己的开发中,也可以使用 Hooks 来封装我们的共有逻辑,效率可以大大提高。

而且最重要的是, Hooks 可以让我们的 代码变得美观

嗯,这很重要,哈哈哈哈哈哈。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

程序员2010精华本

程序员2010精华本

程序员杂志社 / 电子工业 / 2011-1 / 49.00元

《程序员(2010精华本)》主要内容:《程序员》创刊10年来,每年末编辑部精心打造的“合订本”已经形成一个品牌,得到广大读者的认可和喜爱。今年,《程序员》杂志内容再次进行了优化整合,除了每期推出的一个大型专题策划,各版块也纷纷以专题、策划的形式,将每月的重点进行了整合,让内容非常具有凝聚力,如专题篇、人物篇、实践篇等。另外杂志的版式、色彩方面也有了很大的飞跃,给读者带来耳目一新的阅读体验。一起来看看 《程序员2010精华本》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具