【问题标题】:custom hooks for data fetch用于数据获取的自定义钩子
【发布时间】:2020-07-09 15:14:03
【问题描述】:

我已经制作了这个自定义钩子来使用 fetcher 获取数据并返回带有数据的状态以显示在 antd 表中(喜欢它们)。 我不确定这是使用钩子的正确方法,我已经在我有疑问的代码中添加了 cmets。

import { useEffect, useReducer } from 'react';
import { useLocation, useHistory } from 'react-router-dom';
import queryString from 'query-string'

const initialState = { data: [], loading: true };

const fetchData = (fetcher, params, dispatch) => {

    //in this case i need to change my state to loading, the reducer will do the trick, but i fill this kinda triky, should i call the fatcher after the dispatch end using an effect?

    dispatch({ type: 'FETCHING' })
    fetcher({ ...params, page: params.page > 0 ? params.page - 1 : params.page }, response => dispatch({ type: 'DATA_RECEIVED', payload: response.data }));
    return () => {
        console.log('unmounting useGridDataFetch')
    };
}

const handlePageChange = (page, history, location) => {
    const qparams = queryString.parse(location.search)
    const qs = Object.keys(qparams).map(key => key != 'page' ? `&${key}=${qparams[key]}` : '')
    history.push(`${history.location.pathname}?page=${page}${qs.reduce((acc,val)=> acc + val , '')}`)
}


const useGridDataFetch = (fetcher, initialParams) => {
    const location = useLocation()
    const history = useHistory()
    const params = { ...initialParams, ...queryString.parse(location.search) }

    // to use history and location inside my reducer to change the querystring on page change, i had to put the reducer definition inside the body of the hook, is there a better way?

    const reducer = (state, action) => {
        switch (action.type) {
            case 'FETCHING':
                return { loading: true };
            case 'DATA_RECEIVED':
                const pagination = {
                    pageSize: action.payload.pageable.pageSize,
                    current: action.payload.pageable.pageNumber + 1,
                    total: action.payload.totalElements,
                    onChange: page => handlePageChange(page, history, location)
                }
                return { dataSource: action.payload.content, loading: false, pagination };
            default:
                throw new Error();
        }
    }

    const [state, dispatch] = useReducer(reducer, initialState);
    useEffect(() => fetchData(fetcher, params, dispatch), [location.search])

    return [state, (_params) => fetchData(fetcher, { ...params, ..._params }, dispatch)]
}


export default useGridDataFetch

一般来说,您可以给我的每一个改进我的代码的提示都会很感激。

谢谢。

【问题讨论】:

    标签: javascript reactjs react-router react-hooks antd


    【解决方案1】:

    我会尽力帮助你

    首先你必须从钩子中提取reducer。 reducer 函数应该独立于钩子, 因为useReducer(reducer, initialState); 只使用这两个参数reducerinitialState 一次,并且不会在每次下次调用时更新它。因此你不能在 reducer 中使用handlePageChange 方法。

    第二点是你的 reducer 必须返回具有相同接口的数据,如{ data: Array, isLoading: Boolean }。在您的情况下,reducer 有 3 种不同的响应:init 之后 - { data: Array, loading: Boolean }FETCHING 之后 - { loading: Boolean }DATA_RECEIVED 之后 - { dataSource: Object, loading: Boolean, pagination: Object }

    我建议你不要使用 useReducer。我将建议我实现您的代码

    const useGridDataFetch = (fetcher, initialParams) => {
      const { data, isLoading } = useGetData(fetcher, initialParams);
      const { dataSource, pagination } = useGetPreparedData(data);
      
      return {
        isLoading,
        dataSource,
        pagination,
      }
    }
    
    const useGetData = (fetcher, initialParams) => {
      const [isLoading, setIsLoading] = useState(false);
      const [data, setData] = useState({});
    
      const location = useLocation();
    
      useEffect(() => {
        const params = {
          ...initialParams,
          ...queryString.parse(location.search)
        }
        setIsLoading(true);
        fetcher({
          ...params,
          page: params.page > 0 ? params.page - 1 : params.page
        }, response => {
          setData(response.data);
          setIsLoading(false);
        });
      }, [location.search]);
    
      return {
        isLoading,
        data
      }
    }
    
    const useGetPreparedData = (data) => {
      const location = useLocation();
      const history = useHistory();
    
      const handlePageChange = useCallback((page) => {
        const qparams = queryString.parse(location.search);
        const qs = Object.keys(qparams).map(key => key != 'page' ? `&${key}=${qparams[key]}` : '')
        history.push(`${location.pathname}?page=${page}${qs.reduce((acc,val)=> acc + val , '')}`)
      }, [location.pathname, location.search, history]);
    
      return useMemo(() => {
        if (!data.content) {
          return {
            dataSource: [],
            pagination: {}
          }
        }
    
        const pagination = {
          pageSize: data.pageable.pageSize,
          current: data.pageable.pageNumber + 1,
          total: data.totalElements,
          onChange: page => handlePageChange(page)
        }
    
        return {
          dataSource: data.content,
          pagination
        };
      }, [data, handlePageChange])
    }
    

    我可能对这段代码有一些错误,但我认为主要思想很简单

    【讨论】:

    • 嗯,首先感谢您的回复。我已经在您的代码中修复了一些问题,但效果很好,只是一些问题:为什么我应该避免使用 useReducer?使用多个 useState 会更好吗?正如您在代码中看到的,现在有一系列状态设置器,所以我必须首先设置加载状态 => 获取数据 => 设置数据 => 设置加载。这将导致额外的渲染导致 setIsLoading(false)。谢谢。
    • @claud.io 谢谢,我已经批准了你的修复。我建议您在这种情况下避免使用 useReducer ,因为它会为我在答案开头所描述的一些错误创造更多位置。我建议编写更简单的代码,当有人在你更新你的代码时,不要给你犯错误的机会。
    • 重新渲染的计数将是相同的,因为当您调用第一次调度时,它是第一次重新渲染,第二次调度是第二次重新渲染。在使用 useState 的情况下,首先在 setLoading(true) 上重新渲染调用,并在获取数据后将 setLoading 和 setData 这两个方法合并为单个重新渲染。
    猜你喜欢
    • 2022-01-07
    • 2022-01-01
    • 2020-06-20
    • 1970-01-01
    • 2021-08-21
    • 2022-10-08
    • 2022-01-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多