React Hooks入門:手寫一個 useAPI

react-hooks 入門

寫在最前面

  • 最近項目 升級了react 16.8+,接入了 hooks,這裏學習一下最基礎的幾個官方 hookshtml

  • 下面是官網文檔的連接,基礎知識掌握不牢靠的朋友能夠再看看,官網的文檔能夠說是很是完整和淺出了。個人文章主要討論具體的幾個 hooks 的具體使用場景。react

  • zh-hans.reactjs.org/docs/hooks-…ios

一、useEffect

  • 官方 demo
mport React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });

  return (
    <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div>
  );
}
複製代碼

useEffect 作了什麼?git

  • 經過使用這個 Hook,你能夠告訴 React 組件須要在渲染後執行某些操做。React 會保存你傳遞的函數(咱們將它稱之爲 「effect」),而且在執行 DOM 更新以後調用它。在這個 effect 中,咱們設置了 document 的 title 屬性,不過咱們也能夠執行數據獲取或調用其餘命令式的 API。

二、useCallback 和 useMemo

把內聯回調函數及依賴項數組做爲參數傳入 useCallback,它將返回該回調函數的 memoized 版本,該回調函數僅在某個依賴項改變時纔會更新。github

  • 爲了節約內存,咱們能夠把接口獲取的數據先使用 useCallback 和 useMemo 作臨時存儲。這種優化有助於避免在每次渲染時都進行高開銷的計算。
  • useCallback(fn, deps) 至關於 useMemo(() => fn, deps)
  • how to use useCallback
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const getDownloadFile = useCallback(async () => {
    setLoading(true);

    try {
        const res = await axios.get(API.CUSTOMER.xxx(), {
            params: { customer_id: 123 }
        });

        setData(res.data as any);
    } catch (error) {
        setError(error);
    }

    setLoading(false);
}, []);
    
// 這裏的 useEffect() 替代了之前的生命週期作的事情
useEffect(() => {
   getDownloadFile();
}, [getDownloadFile]);
複製代碼

三、useContext()

咱們須要先建立一個 context 對象(React.createContext),接收一個 context 對象(React.createContext 的返回值)並返回該 context 的當前值。axios

當組件上層最近的 <MyContext.Provider> 更新時,該 Hook 會觸發重渲染,並使用最新傳遞給 MyContext provider 的 context value 值。api

瞭解了上面解構 hooks 下面咱們來實戰一下

## 這裏咱們來寫一個簡單的 useAPI

import { useState } from 'react';
import axios from 'axios';

// 首先定義一下類型
type UseApiResponse = [
    {
        loading: boolean,

        data: null | object | any[],

        error:  any
    },
    /** * 返回一個 promise 對象 */
    (requestData?: any[] | object) => Promise<any>,
];

type UseApiArgs = {
    /** * HTTP Method. 默認 'GET' */
    method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE' | 'OPTIONS',

    url: string,

    /** * 可選:初始默認值 */
    defaultData?: object | any[],

    /** * 返回數據 */
    bodyData?: object | any[],

};

export const useApi = ({
    method = 'GET',
    url,
    defaultData,
}: UseApiArgs) => {
    const [loading, setLoading] = useState(false);
    const [data, setData] = useState(defaultData | null)
    const [error, setError] = useState(null);
   
    const sendRequest = (requestData?: object | any[]) => {
        const requestConfig = {
            method,
            url,
            data: requestData,
        };

        const axiosConfig = Object.assign({}, requestConfig);

        /** * 返回一個 promise 對象 */
        return new Promise(async (resolve, reject) => {
            setLoading(true);
            try {
                const response = await axios(axiosConfig);
                setData(response.data);
                resolve(data);
            } catch (err) {
                setError(error);
                reject(err);
            } finally {
                setLoading(false);
            }
        });
    };

    const response: UseApiResponse = [
        {
            loading,
            data,
            error
        },
        sendRequest,
    ];
    return response;
};

export default useApi;
複製代碼

上面知足了基本的調用server api 的需求,可是遠遠是不能知足一些複雜的狀況的,咱們下面來升級一下咱們扥 hooks,增長狀態碼,增長加載狀態,主動觸發 request 的需求等等

升級版

import { createContext, useState, useEffect, useContext } from 'react';
import axios from 'axios';

/** * ApiContext 這裏能夠配置全局的 config. */
export const ApiContext = createContext({});

// 首先定義一下類型
type UseApiResponse = [
    {
        loading: boolean,

        data: null | object | any[],

        error: Error || null,

        /** * The HTTP status number */
        status: number,

        /** * True unless and until the request has been triggered at least once */
        initialLoad: boolean,

        /** * pending 狀態 */
        pendingOrLoading: boolean,

        /** * axios 對象 */
        responseObj: object,
    },
    /** * 返回一個 promise 對象 */
    (requestData?: any[] | object) => Promise<any>,
];

type UseApiArgs = {
    /** * HTTP Method. 默認 'GET' */
    method: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE' | 'OPTIONS',

    url: string,

    /** * 可選:初始默認值 */
    defaultData?: object | any[],

    /** * 返回數據 */
    bodyData?: object | any[],

    /** * 可選 : 若是你想主動調用 request, 設置爲 true */
    autoTrigger?: boolean,
};

export const useApi = ({
    method = 'GET',
    autoTrigger = true,
    url,
    defaultData,
    bodyData,
}: UseApiArgs) => {
    const [initialLoad, setInitialLoad] = useState(true);
    const [loading, setLoading] = useState(false);
    const [data, setData] = useState(defaultData || null)
    const [error, setError] = useState(null);
    const [status, setStatus] = useState();
    const [responseObj, setResponseObj] = useState();

    /** * 你可使用自定義的 api 來替代 Axios config */
    const globalConfig = useContext(ApiContext);

    const sendRequest = (requestData?: object | any[]) => {
        const requestConfig = {
            method,
            url,
            data: requestData,
        };

        const axiosConfig = Object.assign({}, globalConfig, requestConfig);

        /** * 返回一個 promise 對象 */
        return new Promise(async (resolve, reject) => {
            setLoading(true);
            try {
                const response = await axios(axiosConfig);
                setResponseObj(response);
                setData(response.data);
                setStatus(response.status);
                resolve(data);
            } catch (err) {
                setError(error);
                reject(err);
            } finally {
                setLoading(false);
                if (initialLoad) setInitialLoad(false);
            }
        });
    };

    /** * 若是設置了自動觸發這個參數,這裏須要特殊處理一下,檢查一下 initiaload 加載狀態是否完成,而後處理 'POST', 'PATCH', 'PUT' */
    if (autoTrigger) {
        useEffect(() => {
            if (initialLoad) {
                /** * Include body data if method allows */
                if (['POST', 'PATCH', 'PUT'].includes(method)) {
                    sendRequest(bodyData);
                } else sendRequest();
            }
        }, []);
    }

    const response: UseApiResponse = [
        {
            loading,
            data,
            error,
            status,
            initialLoad,
            pendingOrLoading: initialLoad || loading,
            responseObj,
        },
        sendRequest,
    ];
    return response;
};

export default useApi;

複製代碼

怎麼使用?

import React, { useEffect, useRef } from 'react';
import useApi, { ApiContext } from 'use-http-api';

const UserList = () => {
    const [{ loading, data }, getUsers] = useApi({
        url: 'https://reqres.in/api/users',
        defaultData: { data: [] },
    });

    return (
        <div> {loading ? ( 'Loading...' ) : ( <ol> {data.data.map(user => ( <li>{user.email}</li> ))} </ol> )} <button onClick={() => getUsers()}>Update</button> </div>
    );
};
複製代碼

參考

相關文章
相關標籤/搜索