ค้นหาเว็บไซต์

reactjs - ประเภท TypeScript ทั่วไป + ฮุ๊กของ React


ฉันมี HTTP hook ดังต่อไปนี้:

export const useHttp = <T,>(initUrl: string, initData: T) => {
    const [url, setUrl] = useState(initUrl);
    const [state, dispatch] = useReducer(fetchReducer, {
        isLoading: false,
        error: '',
        data: initData
    });

    useEffect(() => {
        let cancelRequest = false;

        const fetchData = async (cancelRequest: boolean = false) => {
            if (!url) return;

            dispatch({ type: 'API_REQUEST'});
            try {
                const responsePromise: AxiosPromise<T> = axios(url);
                const response = await responsePromise;
                if (cancelRequest) return;
                dispatch({ type: 'API_SUCCESS', payload: response.data });
            } catch (e) {
                console.log("Got error", e);
                dispatch({ type: 'API_ERROR', payload: e.message });
            }
        };
        fetchData(cancelRequest);

        return () => {
            cancelRequest = true;
        }

    }, [url]);

    const executeFetch = (url: string) => {
        setUrl(url);
    };

    return { ...state, executeFetch}
};

รีดิวเซอร์ :

const fetchReducer = <T,>(state: IState<T>, action: TAction<T>): IState<T> => {
    switch (action.type) {
        case 'API_REQUEST':
            return {
                ...state,
                isLoading: true
            };
        case 'API_SUCCESS':
            return {
                ...state,
                data: action.payload,
                isLoading: false,
                error: ''
            };
        case 'API_ERROR':
            console.error(`Triggered: ${API_ERROR}, message: ${action.payload}`);
            return {
                ...state,
                error: action.payload,
                isLoading: false,
            };
        default:
            throw Error('Invalid action');
    }
};

การกระทำ :

export interface IApiSuccess<T> {
    type: types.ApiSuccess,
    payload: T;
}
export type TAction<T> = IApiRequest | IApiSuccess<T> | IApiError;

ใช้แบบนี้:

const { data, error, isLoading, executeFetch } = useHttp<IArticle[]>('news', []);

return (
        <>
            <div className={classes.articleListHeader}>
                <h1>Article List</h1>
                <small className={classes.headerSubtitle}>{data.length} Articles
            </div>
            <ul>
                {data.map(article => <Article article={article}/>)}
            </ul>
        </>
    )

TS ของฉันตะโกนใส่ฉันเพราะฉันกำลังใช้ตัวแปร data: Object เป็นชนิด 'ไม่ทราบ'. TS2571

ฉันได้ระบุประเภทของ useHttp แล้ว ซึ่งคือ IArticle[] มีใครพอจะทราบว่าฉันพลาดอะไรไปบ้างไหม?

อัปเดต: ฉันพยายามเพิ่มประเภทค่าที่ส่งกลับสำหรับรีดิวเซอร์ของฉัน:

interface HttpReducer<T> extends IState<T> {
    executeFetch: (url: string) => void
}

export const useHttp = <T,>(initUrl: string, initData: T): HttpReducer<T> => {

แต่ฉันได้รับ:

Type '{ executeFetch: (url: string) => void; error: string; isLoading: boolean; data: unknown; }' is not assignable to type 'HttpReducer<T>'.

คำตอบ #1

ฉันสามารถทำให้เกิดข้อผิดพลาดของคุณซ้ำได้ คุณคาดหวังว่า hook useReducer จะสามารถอนุมานประเภทสถานะตามประเภทของสถานะแรกเริ่มได้ แต่จริงๆ แล้วมันกำลังอนุมานเป็น IState<unknown>

ประเภทสำหรับ useReducer ถูกกำหนดโดยที่อาร์กิวเมนต์ทั่วไปคือประเภทของ reducer ประเภทของ state ถูกอนุมานจาก reducer ด้วยประเภทยูทิลิตี้ ReducerState มันไม่ได้คาดหวัง reducer ทั่วไปและไม่ทำงานได้ดีกับมัน

ไม่มีความสัมพันธ์ระหว่าง T ของ hook และ T ของ reducer นอกจาก state. fetchReducer เป็นฟังก์ชันทั่วไป ซึ่งหมายความว่ามันสามารถรับ any IState และส่งคืน IState ของชนิดเดียวกันได้ เราสามารถใช้ฟังก์ชันนี้เพื่อประมวลผล IState<T> ของ hook ของเรา แต่เพื่อให้สามารถสรุปชนิดของ state ได้ เราต้องระบุว่าฟังก์ชันของเราจะ รับ และ ส่งคืน IState<T> เท่านั้น

คุณจำเป็นต้องกำหนด generic บน useReducer ของคุณเป็นแบบนี้:

const [state, dispatch] = useReducer<(state: IState<T>, action: TAction<T>) => IState<T>>( ...

บนพื้นผิวแล้วนี่ดูเหมือนกับสิ่งที่กำลังถูกสรุปในขณะนี้ ซึ่งคือ:

const [state, dispatch] = useReducer<<T,>(state: IState<T>, action: TAction<T>) => IState<T>>(...

แต่ความแตกต่างนั้นสำคัญอย่างยิ่ง กระแสไฟอธิบายฟังก์ชันทั่วไป ในขณะที่การแก้ไขอธิบายฟังก์ชันที่รับเพียงประเภทเดียวของ T -- นั่นคือของ hook useHttp มันทำให้เข้าใจผิดเพราะคุณใช้ T สำหรับทั้งสอง บางทีอาจจะง่ายกว่าถ้าเราเปลี่ยนชื่อหนึ่งอัน

เรามีฟังก์ชันทั่วไป:

export const useHttp = <Data,>(initUrl: string, initData: Data) => {
  const [url, setUrl] = useState(initUrl);
  const [state, dispatch] = useReducer<<T,>(state: IState<T>, action: TAction<T>) => IState<T>>(fetchReducer, {

เราต้องการกรณีการใช้งานเฉพาะของฟังก์ชันนั้น:

export const useHttp = <Data,>(initUrl: string, initData: Data) => {
  const [url, setUrl] = useState(initUrl);
  const [state, dispatch] = useReducer<(state: IState<Data>, action: TAction<Data>) => IState<Data>>(fetchReducer, {

เมื่อเราทราบว่าประเภทสถานะของรีดิวเซอร์ของเราคือ IState<Data> เราก็จะทราบว่าประเภทของ data คือ Data

การเรียกใช้ useHttp<IArticle[]>() ตอนนี้จะให้คุณมีตัวแปร data ที่มีชนิดเป็น IArticle[]

ลิงก์ TypeScript Playground