在 React Hooks 中如何請求資料?

舞動乾坤發表於2019-03-26

通過這個教程,我想告訴你在 React 中如何使用 state 和 effect 這兩種 hooks 去請求資料。我們將使用眾所周知的 Hacker News API 來獲取一些熱門文章。你將定義屬於你自己的資料請求的 Hooks ,並且可以在你所有的應用中複用,也可以釋出到 npm 。

如果你不瞭解 React 的這些新特性,可以檢視我的另一篇文章 introduction to React Hooks。如果你想直接檢視文章的示例,可以直接 checkout 這個 Github 倉庫

注意:在 React 未來的版本中,Hooks 將不會用了獲取資料,取而代之的是一種叫做 Suspense 的東西。儘管如此,下面的方法依然是瞭解 state 和 effect 兩種 Hooks 的好方法。

使用 React Hooks 進行資料請求

如果你沒有過在 React 中進行資料請求的經驗,可以閱讀我的文章:How to fetch data in React。文章講解了如何使用 Class components 獲取資料,如何使用可重用的 Render Props Components 和 Higher Order Components ,以及如何進行錯誤處理和 loading 狀態。在本文中,我想用 Function components 和 React Hooks 來重現這一切。

import React, { useState } from 'react';

function App() { const [data, setData] = useState({ hits: [] });

return ( <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> ); }

複製程式碼

export default App; 複製程式碼複製程式碼

App 元件將展示一個列表,列表資訊來自 Hacker News articles 。狀態和狀態更新函式將通過被稱為 useState 的狀態鉤子來生成,它負責管理通過請求得到的 App 元件的本地狀態。初始狀態是一個空陣列,目前沒有任何地方給它設定新的狀態。

我們將使用 axios 來獲取資料,當然也可以使用你熟悉的請求庫,或者瀏覽器自帶的 fetch API。如果你還沒有安裝過 axios ,可以通過 npm install axios 進行安裝。

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() { const [data, setData] = useState({ hits: [] });

useEffect(async () => { const result = await axios( 'hn.algolia.com/api/v1/sear…', );

<span class="hljs-built_in">set</span>Data(result.data);
複製程式碼

});

return ( <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> ); }

複製程式碼

export default App; 複製程式碼複製程式碼

我們在 useEffect 這個 effect hook 中,通過 axios 從 API 中獲取資料,並使用 state hook 的更新函式,將資料存入到本地 state 中。並且使用 async/await 來解析promise。

然而,當你執行上面的程式碼的時候,你會陷入到該死的死迴圈中。effect hook 在元件 mount 和 update 的時候都會執行。因為我們每次獲取資料後,都會更新 state,所以元件會更新,並再次執行 effect,這會一次又一次的請求資料。很明顯我們需要避免這樣的bug產生,我們只想在元件 mount 的時候請求資料。你可以在 effect hook 提供的第二個引數中,傳入一個空陣列,這樣做可以避免元件更新的時候執行 effect hook ,但是元件在 mount 依然會執行它。

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() { const [data, setData] = useState({ hits: [] });

useEffect(async () => { const result = await axios( 'hn.algolia.com/api/v1/sear…', );

<span class="hljs-built_in">set</span>Data(result.data);
複製程式碼

}, []);

return ( <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> ); }

複製程式碼

export default App; 複製程式碼複製程式碼

第二個引數是用來定義 hook 所以依賴的變數的。如果其中一個變數發生變化,hook 將自動執行。如果第二個引數是一個空陣列,那麼 hook 將不會在元件更新是執行,因為它沒有監控任何的變數。

還有一個需要特別注意的點,在程式碼中,我們使用了 async/await 來獲取第三方 API 提供的資料。根據文件,每一個 async 函式都將返回一個隱式的 promise:

"The async function declaration defines an asynchronous function, which returns an AsyncFunction object. An asynchronous function is a function which operates asynchronously via the event loop, using an implicit Promise to return its result. "

“async 函式定義了一個非同步函式,它返回的是一個非同步函式物件,非同步函式是一個通過事件迴圈進行操作的函式,使用隱式的 Promise 返回最終的結果。”

然而,effect hook 應該是什麼也不返回的,或者返回一個 clean up 函式的。這就是為什麼你會在控制檯看到一個錯誤資訊。

index.js:1452 Warning: useEffect function must return a cleanup function or nothing. 
Promises and useEffect(async () => …) are not supported, but you can call an async function inside an effect.
複製程式碼複製程式碼

這意味著我們不能直接在 useEffect 函式使用async。讓我們來實現一個解決方案,能夠在 effect hook 中使用 async 函式。

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() { const [data, setData] = useState({ hits: [] });

useEffect(() => { const fetchData = async () => { const result = await axios( 'hn.algolia.com/api/v1/sear…', );

  <span class="hljs-built_in">set</span>Data(result.data);
};

fetchData();
複製程式碼

}, []);

return ( <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> ); }

複製程式碼

export default App; 複製程式碼複製程式碼

這就是一個使用 React Hooks 進行資料請求的小案例。但是,如果你對錯誤處理、loading 態、如何觸發表單資料獲取以及如何複用出具處理 hook 感興趣,那我們接著往下看。

如何手動或者自動觸發一個 hook?

現在我們已經能夠在元件 mount 之後獲取到資料,但是,如何使用輸入框動態告訴 API 選擇一個感興趣的話題呢?可以看到之前的程式碼,我們預設將 "Redux" 作為查詢引數('hn.algolia.com/api/v1/sear…'),但是我們怎麼查詢關於 React 相關的話題呢?讓我們實現一個 input 輸入框,可以獲得除了 “Redux” 之外的其他的話題。現在,讓我們為輸入框引入一個新的 state。

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

function App() { const [data, setData] = useState({ hits: [] }); const [query, setQuery] = useState('redux');

useEffect(() => { const fetchData = async () => { const result = await axios( 'hn.algolia.com/api/v1/sear…', );

  <span class="hljs-built_in">set</span>Data(result.data);
};

fetchData();
複製程式碼

}, []);

return ( <Fragment> <input type="text" value={query} onChange={event => setQuery(event.target.value)} /> <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> </Fragment> ); }

複製程式碼

export default App; 複製程式碼複製程式碼

現在,請求資料和查詢引數兩個 state 相互獨立,但是我們需要像一個辦法希望他們耦合起來,只獲取輸入框輸入的引數指定的話題文章。通過以下修改,元件應該在 mount 之後按照查詢獲取相應文章。

...

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${query}`,
      );

      setData(result.data);
    };

    fetchData();
  }, []);

  return (
    ...
  );
}

export default App;
複製程式碼複製程式碼

實際上,我們還缺少部分程式碼。你會發現當你在輸入框輸入內容後,並沒有獲取到新的資料。這是因為 useEffect 的第二個引數只是一個空陣列,此時的 effect 不依賴於任何的變數,所以這隻會在 mount 只會觸發一次。但是,現在我們需要依賴查詢條件,一旦查詢傳送改變,資料請求就應該再次觸發。

...

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${query}`,
      );

      setData(result.data);
    };

    fetchData();
  }, [query]);

  return (
    ...
  );
}

export default App;
複製程式碼複製程式碼

好了,現在一旦你改變輸入框內容,資料就會重新獲取。但是現在又要另外一個問題:每次輸入一個新字元,就會觸發 effect 進行一次新的請求。那麼我們提供一個按鈕來手動觸發資料請求呢?

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [search, setSearch] = useState('redux');

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${search}`,
      );

      setData(result.data);
    };

    fetchData();
  }, [search]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button type="button" onClick={() => setSearch(query)}>
        Search
      </button>
      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}
複製程式碼複製程式碼

此外,search state 的初始狀態也是設定成了與 query state 相同的狀態,因為元件在 mount 的時候會請求一次資料,此時的結果也應該是反應的是輸入框中的搜尋條件。然而, search state 和 query state 具有類似的值,這看起來比較困惑。為什麼不將真實的 URL 設定到 search state 中呢?

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'http://hn.algolia.com/api/v1/search?query=redux',
  );

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(url);

      setData(result.data);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      <ul>
        {data.hits.map(item => (
          <li key={item.objectID}>
            <a href={item.url}>{item.title}</a>
          </li>
        ))}
      </ul>
    </Fragment>
  );
}
複製程式碼複製程式碼

這就是通過 effect hook 獲取資料的案例,你可以決定 effect 取決於哪個 state。在這個案例中,如果 URL 的 state 發生改變,則再次執行該 effect 通過 API 重新獲取主題文章。

Loading 態 與 React Hooks

讓我們在資料的載入過程中引入一個 Loading 狀態。它只是另一個由 state hook 管理的狀態。Loading state 用於在 App 元件中呈現 Loading 狀態。

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

function App() { const [data, setData] = useState({ hits: [] }); const [query, setQuery] = useState('redux'); const [url, setUrl] = useState( 'hn.algolia.com/api/v1/sear…', ); const [isLoading, setIsLoading] = useState(false);

useEffect(() => { const fetchData = async () => { setIsLoading(true);

  const result = await axios(url);

  <span class="hljs-built_in">set</span>Data(result.data);
  <span class="hljs-built_in">set</span>IsLoading(<span class="hljs-literal">false</span>);
};

fetchData();
複製程式碼

}, [url]);

return ( <Fragment> <input type="text" value={query} onChange={event => setQuery(event.target.value)} /> <button type="button" onClick={() => setUrl(http://hn.algolia.com/api/v1/search?query=<span class="hljs-variable">${query}</span>複製程式碼) } > Search </button>

  {isLoading ? (
    &lt;div&gt;Loading ...&lt;/div&gt;
  ) : (
    &lt;ul&gt;
      {data.hits.map(item =&gt; (
        &lt;li key={item.objectID}&gt;
          &lt;a href={item.url}&gt;{item.title}&lt;/a&gt;
        &lt;/li&gt;
      ))}
    &lt;/ul&gt;
  )}
&lt;/Fragment&gt;
複製程式碼

); }

複製程式碼

export default App; 複製程式碼複製程式碼

現在當元件處於 mount 狀態或者 URL state 被修改時,呼叫 effect 獲取資料,Loading 狀態就會變成 true。一旦請求完成,Loading 狀態就會再次被設定為 false。

錯誤處理與 React Hooks

通過 React Hooks 進行資料請求時,如何進行錯誤處理呢? 錯誤只是另一個使用 state hook 初始化的另一種狀態。一旦出現錯誤狀態,App 元件就可以反饋給使用者。當使用 async/await 函式時,通常使用 try/catch 來進行錯誤捕獲,你可以在 effect 中進行下面操作:

...

const [isError, setIsError] = useState(false);

useEffect(() => { const fetchData = async () => { setIsError(false); setIsLoading(true);

try {
  const result = await axios(url);
  <span class="hljs-built_in">set</span>Data(result.data);
} catch (error) {
  <span class="hljs-built_in">set</span>IsError(<span class="hljs-literal">true</span>);
}

<span class="hljs-built_in">set</span>IsLoading(<span class="hljs-literal">false</span>);
複製程式碼

};

fetchData(); }, [url]);

複製程式碼

return ( <Fragment> ... {isError && <div>Something went wrong ...</div>} ... <Fragment> ); 複製程式碼複製程式碼

effect 每次執行都會重置 error state 的狀態,這很有用,因為每次請求失敗後,使用者可能重新嘗試,這樣就能夠重置錯誤。為了觀察程式碼是否生效,你可以填寫一個無用的 URL ,然後檢查錯誤資訊是否會出現。

使用表單進行資料獲取

什麼才是獲取資料的正確形式呢?現在我們只有輸入框和按鈕進行組合,一旦引入更多的 input 元素,你可能想要使用表單來進行包裝。此外表單還能夠觸發鍵盤的 “Enter” 事件。

function App() {
  ...
  const doFetch = (evt) => {
    evt.preventDefault();
    setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`);
  }
  return (
    <Fragment>
      <form
        onSubmit={ doFetch }
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>
  {isError &amp;&amp; &lt;div&gt;Something went wrong ...&lt;/div&gt;}

  ...
&lt;/Fragment&gt;
複製程式碼
複製程式碼

); } 複製程式碼複製程式碼

自定義 hook 獲取資料

我們可以定義一個自定義的 hook,提取出所有與資料請求相關的東西,除了輸入框的 query state,除此之外還有 Loading 狀態、錯誤處理。還要確保返回元件中需要用到的變數。

const useHackerNewsApi = () => {
  const [data, setData] = useState({ hits: [] });
  const [url, setUrl] = useState(
    'http://hn.algolia.com/api/v1/search?query=redux',
  );
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

useEffect(() => { const fetchData = async () => { setIsError(false); setIsLoading(true);

  try {
    const result = await axios(url);

    <span class="hljs-built_in">set</span>Data(result.data);
  } catch (error) {
    <span class="hljs-built_in">set</span>IsError(<span class="hljs-literal">true</span>);
  }

  <span class="hljs-built_in">set</span>IsLoading(<span class="hljs-literal">false</span>);
};

fetchData();
複製程式碼

}, [url]);

const doFetch = () => { setUrl(http://hn.algolia.com/api/v1/search?query=<span class="hljs-variable">${query}</span>複製程式碼); };

複製程式碼

return { data, isLoading, isError, doFetch }; } 複製程式碼複製程式碼

現在,我們在 App 元件中使用我們的新 hook 。

function App() {
  const [query, setQuery] = useState('redux');
  const { data, isLoading, isError, doFetch } = useHackerNewsApi();

  return (
    <Fragment>
      ...
    </Fragment>
  );
}
複製程式碼複製程式碼

接下來,在外部傳遞 URL 給 DoFetch 方法。

const useHackerNewsApi = () => {
  ...

  useEffect(
    ...
  );

  const doFetch = url => {
    setUrl(url);
  };

  return { data, isLoading, isError, doFetch };
};

function App() {
  const [query, setQuery] = useState('redux');
  const { data, isLoading, isError, doFetch } = useHackerNewsApi();

  return (
    <Fragment>
      <form
        onSubmit={event => {
          doFetch(
            `http://hn.algolia.com/api/v1/search?query=${query}`,
          );

          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      ...
    </Fragment>
  );
}
複製程式碼複製程式碼

初始的 state 也是通用的,可以通過引數簡單的傳遞到自定義的 hook 中:

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

const useDataApi = (initialUrl, initialData) => {
  const [data, setData] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);

      try {
        const result = await axios(url);

        setData(result.data);
      } catch (error) {
        setIsError(true);
      }

      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  const doFetch = url => {
    setUrl(url);
  };

  return { data, isLoading, isError, doFetch };
};

function App() {
  const [query, setQuery] = useState('redux');
  const { data, isLoading, isError, doFetch } = useDataApi(
    'http://hn.algolia.com/api/v1/search?query=redux',
    { hits: [] },
  );

  return (
    <Fragment>
      <form
        onSubmit={event => {
          doFetch(
            `http://hn.algolia.com/api/v1/search?query=${query}`,
          );

          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>

      {isError && <div>Something went wrong ...</div>}

      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}

export default App;
複製程式碼複製程式碼

這就是使用自定義 hook 獲取資料的方法,hook 本身對API一無所知,它從外部獲取引數,只管理必要的 state ,如資料、 Loading 和錯誤相關的 state ,並且執行請求並將資料通過 hook 返回給元件。

用於資料獲取的 Reducer Hook

目前為止,我們已經使用 state hooks 來管理了我們獲取到的資料資料、Loading 狀態、錯誤狀態。然而,所有的狀態都有屬於自己的 state hook,但是他們又都連線在一起,關心的是同樣的事情。如你所見,所有的它們都在資料獲取函式中被使用。它們一個接一個的被呼叫(比如:setIsErrorsetIsLoading),這才是將它們連線在一起的正確用法。讓我們用一個 Reducer Hook 將這三者連線在一起。

Reducer Hook 返回一個 state 物件和一個函式(用來改變 state 物件)。這個函式被稱為分發函式(dispatch function),它分發一個 action,action 具有 type 和 payload 兩個屬性。所有的這些資訊都在 reducer 函式中被接收,根據之前的狀態提取一個新的狀態。讓我們看看在程式碼中是如何工作的:

import React, {
  Fragment,
  useState,
  useEffect,
  useReducer,
} from 'react';
import axios from 'axios';

const dataFetchReducer = (state, action) => { ... };

const useDataApi = (initialUrl, initialData) => { const [url, setUrl] = useState(initialUrl);

const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, isError: false, data: initialData, });

複製程式碼

... }; 複製程式碼複製程式碼

Reducer Hook 以 reducer 函式和一個初始狀態物件作為引數。在我們的案例中,載入的資料、Loading 狀態、錯誤狀態都是作為初始狀態引數,且不會發生改變,但是他們被聚合到一個狀態物件中,由 reducer hook 管理,而不是單個 state hooks。

const dataFetchReducer = (state, action) => {
  ...
};

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });

      try {
        const result = await axios(url);

        dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
      } catch (error) {
        dispatch({ type: 'FETCH_FAILURE' });
      }
    };

    fetchData();
  }, [url]);

  ...
};
複製程式碼複製程式碼

現在,在獲取資料時,可以使用 dispatch 函式向 reducer 函式傳送資訊。使用 dispatch 函式傳送的物件具有一個必填的 type 屬性和一個可選的 payload 屬性。type 屬性告訴 reducer 函式需要轉換的 state 是哪個,還可以從 payload 中提取新的 state。在這裡只有三個狀態轉換:初始化資料過程,通知資料請求成功的結果,以及通知資料請求失敗的結果。

在自定義 hook 的末尾,state 像以前一樣返回,但是因為我們所有的 state 都在一個物件中,而不再是獨立的 state ,所以 state 物件進行解構返回。這樣,呼叫 useDataApi 自定義 hook 的人仍然可以 dataisLoadingisError:

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, isError: false, data: initialData, });

...

const doFetch = url => { setUrl(url); };

複製程式碼

return { ...state, doFetch }; }; 複製程式碼複製程式碼

最後我們還缺少 reducer 函式的實現。它需要處理三個不同的狀態轉換,分被稱為 FEATCH_INITFEATCH_SUCCESSFEATCH_FAILURE。每個狀態轉換都需要返回一個新的狀態。讓我們看看使用 switch case 如何實現這個邏輯:

const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_INIT':
      return { ...state };
    case 'FETCH_SUCCESS':
      return { ...state };
    case 'FETCH_FAILURE':
      return { ...state };
    default:
      throw new Error();
  }
};
複製程式碼複製程式碼

reducer 函式可以通過其引數訪問當前狀態和 dispatch 傳入的 action。到目前為止,在 switch case 語句中,每個狀態轉換隻返回前一個狀態,析構語句用於保持 state 物件不可變(即狀態永遠不會被直接更改)。現在讓我們重寫一些當前 state 返回的屬性,以便在每次轉換時更改 一些 state:

const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_INIT':
      return {
        ...state,
        isLoading: true,
        isError: false
      };
    case 'FETCH_SUCCESS':
      return {
        ...state,
        isLoading: false,
        isError: false,
        data: action.payload,
      };
    case 'FETCH_FAILURE':
      return {
        ...state,
        isLoading: false,
        isError: true,
      };
    default:
      throw new Error();
  }
};
複製程式碼複製程式碼

現在,每個狀態轉換(action.type決定)都返回一個基於先前 state 和可選 payload 的新狀態。例如,在請求成功的情況下,payload 用於設定新 state 物件的 data 屬性。

總之,reducer hook 確保使用自己的邏輯封裝狀態管理的這一部分。通過提供 action type 和可選 payload ,總是會得到可預測的狀態更改。此外,永遠不會遇到無效狀態。例如,以前可能會意外地將 isLoadingisError 設定為true。在這種情況下,UI中應該顯示什麼? 現在,由 reducer 函式定義的每個 state 轉換都指向一個有效的 state 物件。

在 Effect Hook 中中斷資料請求

在React中,即使元件已經解除安裝,元件 state 仍然會被被賦值,這是一個常見的問題。我在之前的文章中寫過這個問題,它描述了如何防止在各種場景中為未掛載元件設定狀態。讓我們看看在自定義 hook 中,請求資料時如何防止設定狀態:

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

const [state, dispatch] = useReducer(dataFetchReducer, { isLoading: false, isError: false, data: initialData, });

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

const fetchData = async () =&gt; {
  dispatch({ <span class="hljs-built_in">type</span>: <span class="hljs-string">'FETCH_INIT'</span> });

  try {
    const result = await axios(url);

    <span class="hljs-keyword">if</span> (!didCancel) {
      dispatch({ <span class="hljs-built_in">type</span>: <span class="hljs-string">'FETCH_SUCCESS'</span>, payload: result.data });
    }
  } catch (error) {
    <span class="hljs-keyword">if</span> (!didCancel) {
      dispatch({ <span class="hljs-built_in">type</span>: <span class="hljs-string">'FETCH_FAILURE'</span> });
    }
  }
};

fetchData();

<span class="hljs-built_in">return</span> () =&gt; {
  didCancel = <span class="hljs-literal">true</span>;
};
複製程式碼

}, [url]);

const doFetch = url => { setUrl(url); };

複製程式碼

return { ...state, doFetch }; }; 複製程式碼複製程式碼

每個Effect Hook都帶有一個clean up函式,它在元件解除安裝時執行。clean up 函式是 hook 返回的一個函式。在該案例中,我們使用 didCancel 變數來讓 fetchData 知道元件的狀態(掛載/解除安裝)。如果元件確實被解除安裝了,則應該將標誌設定為 true,從而防止在最終非同步解析資料獲取之後設定元件狀態。

注意:實際上並沒有中止資料獲取(不過可以通過Axios取消來實現),但是不再為解除安裝的元件執行狀態轉換。由於 Axios 取消在我看來並不是最好的API,所以這個防止設定狀態的布林標誌也可以完成這項工作。

原文連結

【翻譯】在 React Hooks 中如何請求資料?

相關文章