【问题标题】:React Hooks - Making an Ajax requestReact Hooks - 发出 Ajax 请求
【发布时间】:2019-04-03 04:26:49
【问题描述】:

我刚刚开始使用 React 钩子,想知道 AJAX 请求应该是什么样子?

我尝试了很多次,但无法让它发挥作用,而且也不知道实现它的最佳方法。以下是我最近的尝试:

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

【问题讨论】:

标签: javascript reactjs react-hooks


【解决方案1】:

您可以创建一个名为 useFetch 的自定义挂钩,该挂钩将实现 useEffect 挂钩。

通过将空数组作为第二个参数传递给useEffect 钩子将触发componentDidMount 上的请求。

Here is a demo in code sandbox.

参见下面的代码。

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

【讨论】:

  • 如果我想在另一个组件中重用相同的响应怎么办?即使 URL 相同,每次调用 useFetch 都会重新获取数据。无法在 2 个组件之间共享相同的 useState
  • @WalidAmmar 我想您可以将useFetch 移动到父组件并从那里向下传递数据
  • 为什么不改用Context
  • @PaulFitzgerald,您的data 属性来自哪里?你应该得到ReferenceError: data is not defined
  • @peterflanagan,CORS 错误怎么样,你是如何避免的?
【解决方案2】:

工作得很好...给你:

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

现场演示:

编辑

根据版本更改更新(感谢@mgol 将它带到 我在 cmets 中的注意力)。

【讨论】:

  • 您不应该将异步函数传递给 useEffect,因为这样的函数返回的是 promise 而不是清理函数。在这种情况下,较新的 React 将向控制台打印警告:codesandbox.io/s/1qn0pjx0oj
  • @mgol 似乎其余的答案仍然有完全相同的问题:)
  • @SakoBu 感谢您的更新。我删除了我的反对票并赞成你的帖子。没有清理,但这里可能不需要。
  • 投反对票。这个答案可以对代码中发生的事情做一些解释,所以对发现这个问题的初学者更有帮助。
  • 2021 年 12 月:此 CodeSandbox 链接有一个 lint 错误:“React Hook useEffect 缺少依赖项:'fetchUser'。包括它或删除依赖项数组。(react-hooks/exhaustive-deps) " 如果您按照消息请求进行修复……那么 API 调用会触发每个渲染。那么在 useEffect 代码中移动 fetchUser 声明的真正解决方法是什么?
【解决方案3】:

到目前为止,答案很好,但我会添加一个自定义钩子,用于当您想要触发请求时,因为您也可以这样做。

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

如果您愿意,您可以使用此钩子为特定的 API 方法创建一个函数,但请注意,这种抽象并不是严格要求的,而且可能非常危险(带有钩子的松散函数不是一个好主意如果它在 React 组件函数的上下文之外使用)。

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

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

最后,在你的函数组件中

const [newTodo, postNewTodo] = postTodoEndpoint();

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

然后只需将createTodo 指向onSubmitonClick 处理程序。 newTodo 将包含您的数据、加载和错误状态。沙盒代码right here.

【讨论】:

  • 您在useEffect 中调用ajax 请求的方式是错误的。根据文档,useEffect 函数必须是纯函数,因为它也会自动进行清理。所以,正确的做法是:useEffect(() =&gt; { fetchData() }) 其中 fetchData 可以写成:async function fetchData() { ...}
  • 嗨@AshishRawat 您指的是哪个文档?能给个链接吗?
【解决方案4】:

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>
      )}
    </>
  )
}

或者,如果你不想自己管理状态,你可以这样做

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>
      )}
    </>
  )
}

现场演示

【讨论】:

    【解决方案5】:

    我建议您使用react-request-hook,因为它涵盖了很多用例(同时多个请求、卸载和托管请求状态下的可取消请求)。它是用 typescript 编写的,因此如果您的项目也使用 typescript,则可以利用这一点,如果不使用,则取决于您的 IDE,您可能会看到类型提示,并且该库还提供了一些帮助程序以允许您安全地键入您期望作为请求结果的有效负载。

    它已经过很好的测试(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

    作者免责声明:我们一直在生产中使用此实现。有很多钩子可以处理 Promise,但也有一些边缘情况没有被覆盖或没有实现足够的测试。 react-request-hook 甚至在正式发布之前就已经过实战测试。它的主要目标是经过良好测试并安全使用,因为我们正在处理我们的应用程序最关键的方面之一。

    【讨论】:

    • 优秀的图书馆马修斯!一个问题:您如何模拟 axios 以使用 react-request-hook 测试应用程序?我还没有设法使用 axios-mock-adapter。
    【解决方案6】:

    传统上,您会在类组件的componentDidMount 生命周期中编写Ajax 调用,并在请求返回时使用setState 显示返回的数据。

    使用钩子,您可以使用useEffect 并传入一个空数组作为第二个参数,以使回调在组件挂载时运行一次。

    这是一个从 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>

    【讨论】:

      【解决方案7】:

      我在上面的答案中发现useEffect 的许多错误用法。

      不应将异步函数传递给 useEffect

      我们来看看useEffect的签名:

      useEffect(didUpdate, inputs);
      

      你可以在didUpdate函数中做副作用,并返回一个dispose函数。 dispose 函数非常重要,你可以使用该函数取消请求、清除计时器等。

      任何异步函数都会返回一个promise,而不是一个函数,所以dispose函数实际上没有任何作用。

      所以传入一个异步函数绝对可以处理你的副作用,但它是 Hooks API 的反模式。

      【讨论】:

        【解决方案8】:

        以下是我认为可行的方法:

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

        有几个重要的位:

        • 传递给useEffect 的函数充当componentDidMount,这意味着它可能会被执行多次。这就是为什么我们要添加一个空数组作为第二个参数,这意味着“这个效果没有依赖关系,所以只运行一次”。
        • 即使数据尚未出现,您的App 组件仍会呈现某些内容。因此,您必须处理未加载数据但渲染组件的情况。顺便说一句,这没有任何变化。我们现在也在这样做。

        【讨论】:

        • 这种方法不合适。请参阅您将收到的以下警告:index.js:1 Warning: An effect function must not return anything besides a function, which is used for clean-up. It looks like you wrote useEffect(async () =&gt; ...) or returned a Promise. Instead, write the async function inside your effect and call it immediately: useEffect(() =&gt; { async function fetchData() { // You can await here const response = await MyAPI.getData(someId); // ... } fetchData(); }, [someId]); // Or [] if effect doesn't need props or state
        • 刚刚更新了sn-p。问题是闭包前面的async
        猜你喜欢
        • 2020-09-11
        • 1970-01-01
        • 1970-01-01
        • 2019-02-25
        • 2015-10-26
        • 2019-09-02
        • 2015-07-08
        • 2018-04-12
        • 2021-07-02
        相关资源
        最近更新 更多