如何使用React Hooks获取数据

334 阅读4分钟

以下为译文:

教程中涉及useState, useEffect, useReducer的使用

  • 基础的 Hook

    • useState
    • useEffect
    • useContext
  • 额外的 Hook

    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue

原文地址(www.robinwieruch.de/react-hooks…

在本教程中,我想向您展示如何使用Hooks中获取数据。我们将使用广为人知的Hacker News API从科技世界获取热门文章。您还将实现用于数据获取的Custom Hook,该Hook可在应用程序中的任何位置复用或作为独立节点程序包发布在npm上。

如果您对这个新的React功能一无所知,请查看这里对React Hooks的介绍(www.robinwieruch.de/react-hooks)。如果你想要查看文章中涉及的代码,可以从这个git仓库中获取(github.com/the-road-to…)

如果您只想准备好使用React Hook获取数据,可以执行npm install use-data-api并查看文档。 (如果使用它,别忘了给它加星)


下面的组件用于展示Hacker News的文章列表,使用useState这个钩子来更新组件的state,初始的hits是空数组,这里使用axios这个库来获取数据。

import React, { useState, useEffect } from 'react';
import axios from 'axios';
function App() {
  const [data, setData] = useState({ hits: [] });
  useEffect(async () => {
    const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );
    setData(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的第二个参数可用于定义Hook所监听的所有变量(在此数组中分配)。 如果变量之一更改,则hook再次运行。 如果带有变量的数组为空,则在更新组件时挂钩根本不会运行,因为它不必监听任何变量。 并且上面的代码会在控制台中报warning:

Warning: An effect function must not return anything besides a function, which is used for clean-up.

It looks like you wrote useEffect(async () => ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately:

useEffect(() => {
  async function fetchData() {
    // You can await here
    const response = await MyAPI.getData(someId);
    // ...
  }
  fetchData();
}, [someId]); // Or [] if effect doesn't need props or state

根据文档,每个带有async注释的函数都返回一个promise:“ async函数的声明定义了一个异步函数,该函数返回一个AsyncFunction对象。异步函数是通过事件循环异步操作的函数,使用隐式Promise返回结果。”。warning中给出了具体的解决办法,也就是将userEffect第一个参数改写为一个不带有任何返回方法

修改后的代码如下:

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(
        'https://hn.algolia.com/api/v1/search?query=redux',
      );
      setData(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;

那么在使用useEffect时又如何通过事件来触发数据请求呢?

在前面的例子中,组件挂载时就会获取数据。但是如果我们要执行输入检索该怎么办呢?前面的例子中'redux'时默认查询(?query=redux),那么如果要查询'react'相关内容的话,该怎么做。下一个例子中会新增一个input元素,搜索input的输入内容。

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

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

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

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

上面的代码中缺失一个重要的部分,导致在input中输入内容时,并没有触发数据请求。原因是useEffect的第二个参数是一个空数组,并没有监听任何变量,所以只会在组件挂载时请求数据,如果要支持input查询获取数据,需要按照下面的写法。

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

即在useEffect的第二个参数中增加对query变量的监听。现在可以做到在input中输入数据即可触发数据请求,但是现在的情况是每次输入都会触发请求,那么如何添加一个search按钮,手动触发请求。


带有loading状态的react钩子

为上面的例子代码会请求增加一个loading状态

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

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

  useEffect(() => {
    setIsLoading(true);
    const fetchData = async () => {
      const result = await axios(
        `https://hn.algolia.com/api/v1/search?query=${search}`,
      );
      setData(result.data);
      setIsLoading(false);
    };
    fetchData();
  }, [search]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button type="button" onClick={() => setSearch(query)}>
        Search
      </button>
      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {_.map(data.hits, item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}
export default EffectHooks;
使用Form表单获取数据

前面的例子中只有输入框和搜索按钮,如果要加入更多的form表单元素,并使用回车键触发搜索,来通过下面的代码一步步完成。

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

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

  useEffect(() => {
    setIsLoading(true);
    const fetchData = async () => {
      const result = await axios(
        `https://hn.algolia.com/api/v1/search?query=${search}`,
      );
      setData(result.data);
      setIsLoading(false);
    };
    fetchData();
  }, [search]);

  return (
    <Fragment>
      <form
        onSubmit={event => {
          setSearch(query);
          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>
      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {_.map(data.hits, item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}
export default EffectHooks;

上面的代码实现了表单搜索,回车键触发搜索。


对获取的数据进行自定义处理

在组件之外新增一个处理数据请求的方法,将请求相关的state处理移到这个方法中进行处理,并确保该方法返回组件所必需的所有变量。

并且对钩子方法的初始状态进行设置

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

const useDataApi = (initialData, initialSearch) => {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [search, setSearch] = useState('');

  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);
      try {
        const result = await axios(
          `https://hn.algolia.com/api/v1/search?query=${search}`,
        );
        setData(result.data);
      } catch (error) {
        setIsError(true);
      }
      setIsLoading(false);
    };
    fetchData();
  }, [search]);
  return [{ data, query, isLoading, isError }, setSearch];
};

function EffectHooks() {
  const [query, setQuery] = useState('redux');
  const [{ data, isLoading, isError }, setSearch] = useDataApi(
    { hits: [] },
    query,
  );

  return (
    <Fragment>
      <form
        onSubmit={event => {
          setSearch(query);
          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>
      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {_.map(data.hits, item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}
export default EffectHooks;

用于数据请求的reducer钩子

到目前为止,我们已经使用各种状态挂钩来管理数据的获取状态,加载和错误状态。那么如何用reducer钩子对与数据获取相关的状态管理进行统一管理。

reducer钩子返回一个状态对象和一个更改状态对象的函数。这个函数被成为dispatch函数,处理一个action,拥有type属性和optional payload,通过下面的代码来分析该钩子如何工作

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

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();
  }
};

const useDataApi = (initialData, initialSearch) => {
  const [search, setSearch] = useState(initialSearch);

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

  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });
      try {
        const result = await axios(
          `https://hn.algolia.com/api/v1/search?query=${search}`,
        );
        dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
      } catch (error) {
        dispatch({ type: 'FETCH_FAILURE' });
      }
    };
    fetchData();
  }, [search]);
  return [state, setSearch];
};

function EffectHooks() {
  const [query, setQuery] = useState('redux');
  const [{ data, isLoading, isError }, setSearch] = useDataApi(
    { hits: [] },
    query,
  );

  return (
    <Fragment>
      <form
        onSubmit={event => {
          setSearch(query);
          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>
      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {_.map(data.hits, item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}
export default EffectHooks;

欢迎扫码关注作者公众号