【问题标题】:Generic type in useReducer for a returned parameteruseReducer 中返回参数的泛型类型
【发布时间】:2019-08-19 03:44:02
【问题描述】:

我正在编写一个自定义挂钩来从 API 获取一些数据。如果可能的话,我希望返回的数据是类型安全的。这可以用泛型来完成吗?

type Action = { type: 'PENDING' } | { type: 'SUCCESS'; payload: any } | { type: 'FAIL' };

interface State {
  isLoading: boolean;
  isError: boolean;
  data: any;
}

const dataFetchReducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'PENDING':
      return {
        ...state,
        isLoading: true,
      };
    case 'SUCCESS': {
      return {
        ...state,
        isLoading: false,
        isError: false,
        data: action.payload,
      };
    }
    case 'FAIL':
      return {
        ...state,
        isLoading: false,
        isError: true,
      };
    default:
      throw new Error('Action not supported');
  }
};

const baseUrl = 'http://localhost:4000';

function useDataFetchFromAPI(initUrl: string, initData: any) {
  const [url, setUrl] = useState(`${baseUrl}${initUrl}`);
  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initData,
  });

  // effect only runs if url changes
  // see deps array (2nd argument)
  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: 'PENDING' });
      try {
        const result = await axios(url);
        dispatch({ type: 'SUCCESS', payload: result.data });
      } catch (error) {
        dispatch({ type: 'FAIL' });
      }
    };
    fetchData();
  }, [url]);
  const executeFetch = (url: string) => {
    setUrl(url);
  };
  return { ...state, executeFetch };
}

用法为useDataFetchFromAPI('url', [])

我认为您可以执行 useDataFetchFromAPI < SomeType > () 之类的操作并通过它,但我不确定实施。

【问题讨论】:

    标签: reactjs typescript generics react-hooks


    【解决方案1】:
    import axios, { AxiosPromise } from 'axios';
    import { FC, useEffect, useReducer, useState } from 'react';
    
    type Action<T> = { type: 'PENDING' } | { type: 'SUCCESS'; payload: T } | { type: 'FAIL' };
    
    interface State<T> {
        isLoading: boolean;
        isError: boolean;
        data: T;
    }
    
    const createDataFetchReducer = <T>() => (state: State<T>, action: Action<T>): State<T> => {
        switch (action.type) {
            case 'PENDING':
                return {
                    ...state,
                    isLoading: true,
                };
            case 'SUCCESS': {
                return {
                    ...state,
                    isLoading: false,
                    isError: false,
                    data: action.payload,
                };
            }
            case 'FAIL':
                return {
                    ...state,
                    isLoading: false,
                    isError: true,
                };
            default:
                throw new Error('Action not supported');
        }
    };
    
    const baseUrl = 'http://localhost:4000';
    
    function useDataFetchFromAPI<T>(initUrl: string, initData: T) {
        const [url, setUrl] = useState(`${baseUrl}${initUrl}`);
        const dataFetchReducer = createDataFetchReducer<T>();
        const [state, dispatch] = useReducer(dataFetchReducer, {
            isLoading: false,
            isError: false,
            data: initData,
        });
    
        // effect only runs if url changes
        // see deps array (2nd argument)
        useEffect(() => {
            const fetchData = async () => {
                dispatch({ type: 'PENDING' });
                try {
                    const axiosPromise: AxiosPromise<T> = axios(url);
                    const result = await axiosPromise;
                    dispatch({ type: 'SUCCESS', payload: result.data });
                } catch (error) {
                    dispatch({ type: 'FAIL' });
                }
            };
            fetchData();
        }, [url]);
        const executeFetch = (url: string) => {
            setUrl(url);
        };
        return { ...state, executeFetch };
    }
    
    const MyComponent: FC<{}> = props => {
        type Response = { foo: number; }
        const x = useDataFetchFromAPI<Response>('/foo', {
            foo: 1
        });
        x.data.foo;
    
        return null;
    };
    

    【讨论】:

    • 这是正确的答案。如果其他人出现在这里并且这对您不起作用,请确保将 initialState 内联,not 作为单独的变量编写。 useReducer 的第二个参数应该直接写入,就像这个答案所示。如果你早先把它写成一个变量,然后把变量放在那里,你可能会得到一个错误。
    • 这似乎不再有效...const createDataFetchReducer = &lt;T&gt;() =&gt; (state: 将无法编译。
    • 如果在 tsx 文件中,您需要这样做:const createDataFetchReducer = &lt;T,&gt;() =&gt; (state:
    • @FernandoRojo,您能否解释一下为什么将第二个参数作为预定义变量传递不起作用?我已经明确提到了这个变量的类型,它仍然给我一个错误
    • @Sri 我有一段时间没看这个了,所以我不完全记得了,但我认为这是一个 TypeScript 推理问题。如果您将其内联传递,似乎它只会推断类型。如果您需要在上面声明它,也许您可​​以尝试传递 initialState as State 作为解决方法。不过,我不能保证这会奏效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-16
    • 1970-01-01
    • 2016-11-13
    • 2020-10-26
    • 1970-01-01
    • 1970-01-01
    • 2021-05-01
    相关资源
    最近更新 更多