简体   繁体   English

React Hooks - 发出 Ajax 请求

[英]React Hooks - Making an Ajax request

I have just began playing around with React hooks and am wondering how an AJAX request should look?我刚刚开始玩 React 钩子,想知道 AJAX 请求应该是什么样子?

I have tried many attempts, but am unable to get it to work, and also don't really know the best way to implement it.我尝试了很多尝试,但无法使其正常工作,并且也不知道实现它的最佳方法。 Below is my latest attempt:以下是我最近的尝试:

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

const App = () => {
    const URL = 'http://api.com';
    const [data, setData] = useState({});

    useEffect(() => {
        const resp = fetch(URL).then(res => {
          console.log(res)
        });
    });

    return (
        <div>
          // display content here
        </div>
    )
}

You could create a custom hook called useFetch that will implement the useEffect hook.您可以创建一个名为useFetch的自定义钩子来实现useEffect钩子。

By passing an empty array as the second argument to the useEffect hook will trigger the request on componentDidMount .通过传递一个空数组作为useEffect钩子的第二个参数将触发对componentDidMount的请求。

Here is a demo in code sandbox .这是代码沙箱中的演示

See code below.请参阅下面的代码。

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

const useFetch = (url) => {
  const [data, setData] = useState(null);

  // empty array as second argument equivalent to componentDidMount
  useEffect(() => {
    async function fetchData() {
      const response = await fetch(url);
      const json = await response.json();
      setData(json);
    }
    fetchData();
  }, [url]);

  return data;
};

const App = () => {
    const URL = 'http://www.example.json';
    const result = useFetch(URL);

    return (
      <div>
        {JSON.stringify(result)}
      </div>
    );
}

Works just fine... Here you go:工作得很好......给你:

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

const useFetch = url => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);

  const fetchUser = async () => {
    const response = await fetch(url);
    const data = await response.json();
    const [user] = data.results;
    setData(user);
    setLoading(false);
  };

  useEffect(() => {
    fetchUser();
  }, []);

  return { data, loading };
};

const App = () => {
  const { data, loading } = useFetch('https://api.randomuser.me/');

  return (
    <div className="App">
      {loading ? (
        <div>Loading...</div>
      ) : (
        <React.Fragment>
          <div className="name">
            {data.name.first} {data.name.last}
          </div>
          <img className="cropper" src={data.picture.large} alt="avatar" />
        </React.Fragment>
      )}
    </div>
  );
};

Live Demo:现场演示:

编辑 x908rkw8yq

Edit编辑

Updated based on version change (thanks @mgol for bringing it to my attention in the comments).根据版本更改更新(感谢@mgol 在评论中引起我的注意)。

Great answers so far, but I'll add a custom hook for when you want to trigger a request, because you can do that too.到目前为止很好的答案,但我会添加一个自定义钩子,用于何时触发请求,因为您也可以这样做。

function useTriggerableEndpoint(fn) {
  const [res, setRes] = useState({ data: null, error: null, loading: null });
  const [req, setReq] = useState();

  useEffect(
    async () => {
      if (!req) return;
      try {
        setRes({ data: null, error: null, loading: true });
        const { data } = await axios(req);
        setRes({ data, error: null, loading: false });
      } catch (error) {
        setRes({ data: null, error, loading: false });
      }
    },
    [req]
  );

  return [res, (...args) => setReq(fn(...args))];
}

You can create a function using this hook for a specific API method like so if you wish, but be aware that this abstraction isn't strictly required and can be quite dangerous (a loose function with a hook is not a good idea in case it is used outside of the context of a React component function).如果你愿意,你可以使用这个钩子为特定的 API 方法创建一个函数,但请注意,这种抽象不是严格必需的,并且可能非常危险(带有钩子的松散函数不是一个好主意,以防万一在 React 组件函数的上下文之外使用)。

const todosApi = "https://jsonplaceholder.typicode.com/todos";

function postTodoEndpoint() {
  return useTriggerableEndpoint(data => ({
    url: todosApi,
    method: "POST",
    data
  }));
}

Finally, from within your function component最后,从您的功能组件中

const [newTodo, postNewTodo] = postTodoEndpoint();

function createTodo(title, body, userId) {
  postNewTodo({
    title,
    body,
    userId
  });
}

And then just point createTodo to an onSubmit or onClick handler.然后只需将createTodo指向onSubmitonClick处理程序。 newTodo will have your data, loading and error statuses. newTodo将拥有您的数据、加载和错误状态。 Sandbox code right here.沙盒代码就在这里。

use-http is a little react useFetch hook used like: https://use-http.com use-http是一个小小的反应 useFetch 钩子,使用如下: https ://use-http.com

import useFetch from 'use-http'

function Todos() {
  const [todos, setTodos] = useState([])
  const { request, response } = useFetch('https://example.com')

  // componentDidMount
  useEffect(() => { initializeTodos() }, [])

  async function initializeTodos() {
    const initialTodos = await request.get('/todos')
    if (response.ok) setTodos(initialTodos)
  }

  async function addTodo() {
    const newTodo = await request.post('/todos', {
      title: 'no way',
    })
    if (response.ok) setTodos([...todos, newTodo])
  }

  return (
    <>
      <button onClick={addTodo}>Add Todo</button>
      {request.error && 'Error!'}
      {request.loading && 'Loading...'}
      {todos.map(todo => (
        <div key={todo.id}>{todo.title}</div>
      )}
    </>
  )
}

or, if you don't want to manage the state yourself, you can do或者,如果你不想自己管理状态,你可以这样做

function Todos() {
  // the dependency array at the end means `onMount` (GET by default)
  const { loading, error, data } = useFetch('/todos', [])

  return (
    <>
      {error && 'Error!'}
      {loading && 'Loading...'}
      {data && data.map(todo => (
        <div key={todo.id}>{todo.title}</div>
      )}
    </>
  )
}

Live Demo现场演示

编辑基本示例

I'd recommend you to use react-request-hook as it covers a lot of use cases (multiple request at same time, cancelable requests on unmounting and managed request states).我建议您使用react-request-hook,因为它涵盖了很多用例(同时多个请求,卸载和托管请求状态的可取消请求)。 It is written in typescript, so you can take advantage of this if your project uses typescript as well, and if it doesn't, depending on your IDE you might see the type hints, and the library also provides some helpers to allow you to safely type the payload that you expect as result from a request.它是用打字稿编写的,因此如果您的项目也使用打字稿,您可以利用这一点,如果没有,根据您的 IDE,您可能会看到类型提示,并且该库还提供了一些帮助程序以允许您安全地键入您期望作为请求结果的有效负载。

It's well tested (100% code coverage) and you might use it simple as that:它经过了很好的测试(100% 代码覆盖率),您可以像这样简单地使用它:

function UserProfile(props) {
  const [user, getUser] = useResource((id) => {
    url: `/user/${id}`,
    method: 'GET'
  })

  useEffect(() => getUser(props.userId), []);

  if (user.isLoading) return <Spinner />;
  return (
    <User 
      name={user.data.name}
      age={user.data.age}
      email={user.data.email}
    >  
  )
}

image example图像示例

Author disclaimer: We've been using this implementation in production.作者免责声明:我们一直在生产中使用此实现。 There's a bunch of hooks to deal with promises but there are also edge cases not being covered or not enough test implemented.有一堆钩子来处理承诺,但也有一些边缘情况没有被覆盖或没有足够的测试实现。 react-request-hook is battle tested even before its official release. react-request-hook 甚至在正式发布之前就经过了实战测试。 Its main goal is to be well tested and safe to use as we're dealing with one of the most critical aspects of our apps.它的主要目标是在我们处理应用程序最关键的方面之一时经过良好测试和安全使用。

Traditionally, you would write the Ajax call in the componentDidMount lifecycle of class components and use setState to display the returned data when the request has returned.传统上,您会在类组件的componentDidMount生命周期中编写 Ajax 调用,并在请求返回时使用setState来显示返回的数据。

With hooks, you would use useEffect and passing in an empty array as the second argument to make the callback run once on mount of the component.使用钩子,您将使用useEffect并传入一个空数组作为第二个参数,以使回调在组件安装时运行一次。

Here's an example which fetches a random user profile from an API and renders the name.这是一个从 API 获取随机用户配置文件并呈现名称的示例。

 function AjaxExample() { const [user, setUser] = React.useState(null); React.useEffect(() => { fetch('https://randomuser.me/api/') .then(results => results.json()) .then(data => { setUser(data.results[0]); }); }, []); // Pass empty array to only run once on mount. return <div> {user ? user.name.first : 'Loading...'} </div>; } ReactDOM.render(<AjaxExample/>, document.getElementById('app'));
 <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script> <div id="app"></div>

I find many wrong usages of useEffect in the answers above.我在上面的答案中发现了许多useEffect错误用法。

An async function shouldn't be passed into useEffect .不应将异步函数传递给useEffect

Let's see the signature of useEffect :让我们看看useEffect的签名:

useEffect(didUpdate, inputs);

You can do side effects in didUpdate function, and return a dispose function.你可以在didUpdate函数中做副作用,并返回一个 dispose 函数。 The dispose function is very important, you can use that function to cancel a request, clear a timer etc. dispose 函数非常重要,您可以使用该函数取消请求、清除计时器等。

Any async function will return a promise, but not a function, so the dispose function actually takes no effects.任何 async 函数都会返回一个 promise,但不会返回一个函数,因此 dispose 函数实际上不起作用。

So pass in an async function absolutely can handle your side effects, but is an anti-pattern of Hooks API.所以传入一个异步函数绝对可以处理你的副作用,但它是 Hooks API 的反模式。

Here's something which I think will work:这是我认为会起作用的东西:

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

const App = () => {
    const URL = 'http://api.com';
    const [data, setData] = useState({})

    useEffect(function () {
      const getData = async () => {
        const resp = await fetch(URL);
        const data = await resp.json();

        setData(data);
      }
      getData();
    }, []);

    return (
      <div>
        { data.something ? data.something : 'still loading' }
      </div>
    )
}

There are couple of important bits:有几个重要的位:

  • The function that you pass to useEffect acts as a componentDidMount which means that it may be executed many times.您传递给useEffect的函数充当componentDidMount ,这意味着它可能会被执行多次。 That's why we are adding an empty array as a second argument, which means "This effect has no dependencies, so run it only once".这就是我们添加一个空数组作为第二个参数的原因,这意味着“这个效果没有依赖关系,所以只运行一次”。
  • Your App component still renders something even tho the data is not here yet.即使数据不存在,您的App组件仍会呈现某些内容。 So you have to handle the case where the data is not loaded but the component is rendered.因此,您必须处理未加载数据但已呈现组件的情况。 There's no change in that by the way.顺便说一下,这没有任何变化。 We are doing that even now.我们现在也在这样做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM