私は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>
)
}
useFetch
フックを実装するuseEffect
というカスタムフックを作成できます。
useEffect
フックの2番目の引数として空の配列を渡すと、componentDidMount
でリクエストがトリガーされます。
以下のコードを参照してください。
import React, { useState, useEffect } from 'react';
const useFetch = (url) => {
const [data, updateData] = useState(undefined);
// empty array as second argument equivalent to componentDidMount
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const json = await response.json();
updateData(json);
}
fetchData();
}, [url]);
return data;
};
const App = () => {
const URL = 'http://www.example.json';
const result = useFetch(URL);
return (
<div>
{JSON.stringify(result)}
</div>
);
}
これまでのところ素晴らしい回答ですが、リクエストをトリガーするタイミングのためにカスタムフックを追加します。
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
には、データ、ロード、およびエラーのステータスが含まれます。サンドボックスコード ここ
うまく動作します...ここに行きます:
編集:バージョンの変更に基づいて更新されました(コメントで私の注意を喚起してくれた@mgolに感謝...
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>
);
};
ライブデモ:
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}
>
)
}
著者の免責事項:本番環境ではこの実装を使用しています。約束を処理するためのフックがたくさんありますが、カバーされていないEdgeケースや十分なテストが実装されていないケースもあります。 react-request-hookは、公式リリース前でもバトルテスト済みです。その主な目標は、アプリの最も重要な側面の1つを扱っているため、十分にテストされ、安全に使用できるようにすることです。
これがうまくいくと思うものです:
import React, { useState, useEffect } from 'react';
const App = () => {
const URL = 'http://api.com';
const [data, setData] = useState({})
useEffect(async () => {
const resp = await fetch(URL);
const data = await resp.json();
setData(data);
}, []);
return (
<div>
{ data.something ? data.something : 'still loading' }
</div>
)
}
重要な点がいくつかあります。
useEffect
に渡す関数はcomponentDidMount
として機能します。つまり、何度も実行される可能性があります。これが、空の配列を2番目の引数として追加する理由です。つまり、「このエフェクトには依存関係がないため、1回だけ実行する」ということです。App
コンポーネントは、データがまだ存在しない場合でも何かをレンダリングします。そのため、データはロードされず、コンポーネントがレンダリングされるケースを処理する必要があります。ちなみにその点に変更はありません。今でもそうしています。従来、クラスコンポーネントのcomponentDidMount
ライフサイクルでAjax呼び出しを記述し、リクエストが返されたときにsetState
を使用して返されたデータを表示していました。
フックでは、useEffect
を使用し、2番目の引数として空の配列を渡して、コンポーネントのマウント時にコールバックを1回実行します。
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
関数で副作用を発生させ、処分関数を返すことができます。破棄関数は非常に重要です。その関数を使用して、リクエストをキャンセルしたり、タイマーをクリアしたりできます。
非同期関数は、promiseを返しますが、関数は返しません。したがって、dispose関数は実際には効果を持ちません。
そのため、非同期関数を渡すことは絶対に副作用を処理できますが、フックAPIのアンチパターンです。
se-http は、次のように使用される少し反応するuseFetchフックです。 https://use-http.com
function Todos() {
const [todos, setTodos] = useState([])
const request = useFetch('https://example.com')
// on mount, initialize the todos
useEffect(() => {
initializeTodos()
}, [])
async function initializeTodos() {
const initialTodos = await request.get('/todos')
setTodos(initialTodos)
}
async function addTodo() {
const newTodo = await request.post('/todos', {
title: 'no way',
})
setTodos(oldTodos => [...oldTodos, newTodo])
}
return (
<>
<button onClick={ addTodo }>Add Todo</button>
{todos.error && 'Error!'}
{todos.loading && 'Loading...'}
{todos.length > 0 && todos.map(todo => (
<div key={todo.id}>{todo.title}</div>
)}
</>
)
}