Я только начал играть с хуками 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.info(res)
});
});
return (
<div>
// display content here
</div>
)
}
Отвечает ли это на ваш вопрос? каков правильный способ вызова API в React JS?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Вы можете создать собственный перехватчик под названием useFetch, который будет реализовывать перехватчик useEffect.
Передача пустого массива в качестве второго аргумента ловушке useEffect вызовет запрос на componentDidMount.
Вот демонстрация в песочнице кода.
См. Код ниже.
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>
);
}
Что, если я хочу повторно использовать тот же ответ в другом компоненте? каждый вызов useFetch будет обновлять данные, даже если URL такой же. Невозможно использовать один и тот же useState для двух компонентов.
@WalidAmmar Я думаю, вы могли бы переместить useFetch в родительский компонент и передать данные оттуда
Почему бы вместо этого не использовать Context?
@PaulFitzgerald, откуда взялась ваша собственность data? Вы должны получить ReferenceError: data is not defined.
@peterflanagan, а как насчет ошибки CORS, как вы этого избежали?
Работает нормально ... Вот и все:
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 за то, что предоставил его мое внимание в комментариях).
Вы не должны передавать асинхронную функцию для useEffect, поскольку такая функция возвращает обещание вместо функции очистки. В таком случае Newer React выведет на консоль предупреждение: codeandbox.io/s/1qn0pjx0oj
@mgol Кажется, что в остальных ответах все еще есть такая же проблема :)
@SakoBu Спасибо за обновление. Я удалил свой голос против и проголосовал за ваше сообщение. Очистки нет, но здесь она может и не понадобиться.
Проголосовали против. Этот ответ может быть связан с некоторым объяснением того, что происходит в коде, поэтому он более полезен для новичков, которые нашли этот вопрос.
Пока что отличные ответы, но я добавлю специальный крючок, когда вы хотите инициировать запрос, потому что вы тоже можете это сделать.
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 на обработчик onSubmit или onClick. newTodo будет иметь ваши данные, статус загрузки и ошибки. Код песочницы Прямо здесь.
То, как вы вызываете запрос ajax внутри useEffect, неверно. Согласно документу, функция useEffect должна быть чистой, так как она также выполняет очистку автоматически. Итак, правильный способ: useEffect(() => { fetchData() }), где fetchData можно записать как: async function fetchData() { ...}.
Привет, @AshishRawat, какие документы вы имеете в виду? Можно ссылку, пожалуйста?
Вот кое-что, что, я думаю, сработает:
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 () => ...) 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 перед закрытием.
Традиционно вы должны написать вызов Ajax в жизненном цикле componentDidMount компонентов класса и использовать 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/[email protected]/umd/react.development.js"></script>
<script src = "https://unpkg.com/[email protected]/umd/react-dom.development.js"></script>
<div id = "app"></div>В приведенных выше ответах я обнаружил много неправильного использования useEffect.
В useEffect не следует передавать асинхронную функцию.
Посмотрим подпись useEffect:
useEffect(didUpdate, inputs);
Вы можете делать побочные эффекты в функции didUpdate и возвращать функцию удаления. Функция удаления очень важна, вы можете использовать эту функцию для отмены запроса, сброса таймера и т. д.
Любая асинхронная функция вернет обещание, но не функцию, поэтому функция удаления на самом деле не действует.
Так что передайте асинхронную функцию, которая может справиться с вашими побочными эффектами, но это антипаттерн Hooks API.
Я бы рекомендовал вам использовать реагировать-запрос-ловушка, поскольку он охватывает множество вариантов использования (несколько запросов одновременно, отменяемые запросы при размонтировании и состояния управляемого запроса). Он написан на машинописном тексте, поэтому вы можете воспользоваться этим, если в вашем проекте также используется машинописный текст, а если нет, в зависимости от вашей 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}
>
)
}
Отказ от ответственности автора: мы использовали эту реализацию в производственной среде. Есть множество ловушек для работы с обещаниями, но есть и крайние случаи, которые не рассматриваются или не выполняется достаточное количество тестов. response-request-hook прошел боевые испытания еще до официального релиза. Его главная цель - быть хорошо протестированным и безопасным в использовании, поскольку мы имеем дело с одним из наиболее важных аспектов наших приложений.
Отличная библиотека Матеуса! Один вопрос: как вы имитируете аксиомы для тестирования приложения с помощью response-request-hook? Использовать с ним axios-mock-adapter мне не удалось.
использовать-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>
)}
</>
)
}