React Hooks 在列表頁中的實踐

在本文中,我想向你們介紹 React Hooks 在列表頁中的實踐,主要是經過 useReduceruseEffect 來實現的。以及表達一下我對 React Hooks 的理解與思考。javascript

若是你還不知道 React 的這個新特性 React Hooks,那麼點擊 Hooks 簡介 ; 若是你想看直接查看最後的實現效果,請點擊 倉庫html

使用場景

在平常的開發中,咱們經常會碰到一些列表頁的開發需求。java

一個列表頁的最基本的功能就是列表的展現,而列表的展現就須要不少 state 去管理,好比:列表數據、數據總數、當前頁、展現條數等等。而這些 state 絕大部分是全部列表頁的都通用的即有共同的邏輯。react

在以往的方案中尚未特別好的方案,能夠共用這些邏輯代碼。高階組件 HOC 能夠,但會引入組件層級過深的問題。(如讀者有興趣,可自行去了解高階組件的用途,本文不深刻討論)。ios

好消息是,React Hooks 就能幫助咱們完成這個心願。git

作什麼

咱們要是現實一個自定義 Hook useTable,簡單說明一下功能github

  1. 接收一個 url,向外暴露 { onSearch, bind: { loading, dataSource, pagination: { current, pageSize, total }, onChange } }。 這個是基於 antd 開發的,因此 bind 內的東西是綁定在 antdTable 組件上的。
  2. 在頁面初始化時,自動請求數據
  3. 在頁面卸載時,取消異步請求的後續操做
  4. onChangeonSearch 時自動請求數據
  5. 在 loading 中,不會觸發新的異步請求

好了話很少說,直接上代碼。redux

// 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;

複製代碼

在代碼中的註釋簡單解釋了一下代碼,應該也沒有什麼難點。axios

解決了什麼問題

到此爲止,咱們應該思考 React Hooks 能夠給咱們帶來些什麼。 爲此,我額外的寫了一個使用 class 方式實現的列表頁,下面上代碼antd

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 可讓咱們的代碼變得美觀

嗯,這很重要,哈哈哈哈哈哈。

相關文章
相關標籤/搜索