Практика React Hooks — извлечение повторно используемой логики из компонентов

React.js

Посмотрите на спрос:

Несколько страниц в системе должны отображать табличные данные.Некоторые табличные данные необходимо запрашивать путем подкачки, а некоторые нет.

Разделение компонентов:

components:
    TableDisplay.jsx 
    Pagination.jsx
containers:
    TableContainer.jsx

TableDisplay: Компонент отображения табличных данных, параметры следующие:

  • data: тип массива, данные таблицы для отображения
  • загрузка: логический тип, определяет, загружена таблица или нет.

Pagination: компонент, используемый для отображения пейджинга, параметры следующие:

  • total: Тип номера, общий объем данных
  • страница: тип номера, текущий номер страницы
  • pageSize: числовой тип, количество данных, отображаемых на каждой странице.
  • onPageChange: обратный вызов типа Function, изменение страницы
  • onPageSizeChange: тип функции обратного вызова pageSize change

TableContainer

1. Используйте Class для инкапсуляции компонентов бизнес-логики

Во-первых, давайте посмотрим на код, который не использует хуки и использует традиционные компоненты класса для инкапсуляции бизнес-логики:

import React from 'react';
import TableDisplay from 'components/TableDisplay';
import Pagination from 'components/Pagination';
import { getTableData } from 'apis';    //获取表格数据的api

export default class TableContainer extends React.Component {
    state = {
        data: {
            tableData: [],
            total: 0,
        },
        pagination: {
            page: 1,
            pageSize: 10
        },
        loading: false
    }
    
    componentDidMount() {
        //  首次加载时默认查询第一页数据
        this.loadTable();
    }
    
    handlePageChange: (page) => {
        this.setState({
            pagination: {
                ...this.state.pagination, page
            }
        });
        this.loadTable();
    }
    
    handlePageSizeChange: (pageSize) => {
        this.setState({
            pagination: {
                page: 1,    //pageSize改变时,page自动跳到1
                pageSize
            }
        });
        this.loadTable();
    }
    
    loadTable: () => {
        this.setState({
            loading:true
        });
        
        //  调用APi获取数据
        getTableData(this.state.pagination)
            .then(({data}) => {
                //  数据加载成功
                this.setState({
                    data
                });
            })
            .catch(error => {
                console.log(error);
            })
            .finally(() => {
                this.setState({
                    loading: false
                });
            });
    }
    
    render() {
        const {data, pagination, loading} = this.state;
        
        return (
            <div>
                <TableDisplay data={ data.tableData } loading={ loading } />
                <Pagination
                    onPageChange={ this.handlePageChange }
                    onPageSizeChange={ this.handlePageSizeChange }
                    total={ data.total }
                    { ...pagination } />
            </div>
        );
    }
}

Использование компонентов класса затрудняет разделение и повторное использование кода бизнес-логики;

2. Перепишите, используя компонент Function с хуками

import React, { useState, useEffect } from 'react';
import TableDisplay from 'components/TableDisplay';
import Pagination from 'components/Pagination';
import { getTableData } from 'apis';    //获取表格数据的api

export default () => {
    //  表格数据
    const [data, setData] = useState({
        tableData: [],
        total: 0
    });
    //  分页状态
    const [pagination, setPagination] = useState({
        page: 1,
        pageSize: 10
    });
    //  加载状态
    const [loading, setLoading] = useState(false);
    
    useEffect(() => {   //分页状态改变时,加载数据
        setLoading(true);
        getTableData(pagination)
            .then(data => {
                setTableData({
                    total: data.total,
                    data: data.data
                });
            })
            .catch(error => {
                console.log(error);
            })
            .finally(() => {
                setLoading(false);
            });
    }, pagination); 
    
    const handlePageChange = page => setPagination({
        page,
        pageSize: pagination.pageSize
    });
    
    const handlePageSizeChange = pageSize => setPagination({
        page: 1,
        pageSize
    });
    
    return (
        <div>
            <TableDisplay data={ data.tableData } loading={ loading } />
            <Pagination
                onPageChange={ handlePageChange }
                onPageSizeChange={ handlePageSizeChange }
                total={ data.total }
                { ...pagination } />
        </div>
    );
}

На данный момент цель извлечения многократно используемой логики не достигнута.

3. Используйте пользовательские хуки для сегментации и извлечения бизнес-логики

Для выполнения требований, упомянутых в первой строке этой статьи, необходимо разделить загрузку табличных данных и управление разбиением на страницы, чтобы эта часть функции разбиения по страницам была подключаемой.

Итак, вам нужны два нестандартных хука

hooks:
    --useTableDataLoader
    --usePagination

useTableDataLoader.jsкод показывает, как показано ниже:

import { useState, useEffect } from 'react';

export default (api, pagination) => {
    const [data, setData] = useState({
        total: 0,
        tableData: []
    });
    const [loading, setLoading] = useState(false);

    useEffect(() => {
        setLoading(true);
        api(pagination)
            .then(data => {
                setData(data);
            })
            .catch(error => {
                console.log(error)
            })
            .finally(() => {
                setLoading(false);
            });
    }, pagination);
    
    return {
        data, loading
    };
}

HookuseTableDataLoaderТребуются два параметра:

  • api: API для получения данных таблицы
  • pagination: Статус пейджинга, необязательные параметры, нельзя передать, когда API не нуждается в этом, таблица будет загружена только один раз

Возвращает объект:

  • data: запрошенные данные
  • loading: Находится ли он в состоянии загрузки

usePagination.jsкод показывает, как показано ниже:

import { useState } from 'react';

export default () => {
    const [pagination, setPagination] = useState({
        page: 1,
        pageSize: 10
    });

    return {
        pagination,
        setPage(page) {
            setPagination({
                page,
                pageSize: pagination.pageSize
            });
        },
        setPageSize(pageSize) {
            setPagination({
                page: 1,
                pageSize
            });
        }
    };
}

HookuseTableDataLoaderВозвращает объект:

  • pagination: пейджинговая информация
  • setPage: Как изменить текущий номер страницы
  • setPageSize: Модифицировать метод Pagesize

4, используя пользовательскую сборку бизнес-формы, реализованную с помощью элемента управления Hook tab.

TableWithPaginationContainer.jsxкод показывает, как показано ниже:

import React from 'react';
import TableDisplay from 'components/TableDisplay';
import Pagination from 'components/Pagination';
import { getTableData } from 'apis';    //获取表格数据的api
import useTableDataLoader from 'hooks/useTableDataLoader';
import usePagination from 'hooks/usePagination';

export default () => {
    const { pagination, setPage, setPageSize } = usePagination();
    const { data, loading } = useTableDataLoader(getTableData, pagination);
    const { tableData, total } = data;
    return (
        <div>
            <TableDisplay loading={loading} data={tableData}/>
            <Pagination
                onPageChange={ setPage }
                onPageSizeChange={ setPageSize }
                total={total}
                { ...pagination } />
        </div>
    );
}

TableContainer.jsxкод показывает, как показано ниже:

import React from 'react';
import TableDisplay from 'components/TableDisplay';
import { getTableData } from 'apis';    //获取表格数据的api
import useTableDataLoader from 'hooks/useTableDataLoader';

export default () => {
    const { data, loading } = useTableDataLoader(getTableData);
    const { tableData, total } = data;
    return (
        <TableDisplay loading={loading} data={tableData}/>
    );
}