私は新しいreact useReducer APIでいくつかのデータを取得しようとしていますが、非同期で取得する必要がある段階で立ち往生しています。方法がわからない:/
Switch文にデータフェッチを配置する方法、またはそれを行う方法ではありませんか?
import React from 'react'
const ProfileContext = React.createContext()
const initialState = {
data: false
}
let reducer = async (state, action) => {
switch (action.type) {
case 'unload':
return initialState
case 'reload':
return { data: reloadProfile() } //how to do it???
}
}
const reloadProfile = async () => {
try {
let profileData = await fetch('/profile')
profileData = await profileData.json()
return profileData
} catch (error) {
console.log(error)
}
}
function ProfileContextProvider(props) {
let [profile, profileR] = React.useReducer(reducer, initialState)
return (
<ProfileContext.Provider value={{ profile, profileR }}>
{props.children}
</ProfileContext.Provider>
)
}
export { ProfileContext, ProfileContextProvider }
私はこのようにしようとしていましたが、非同期で動作していません;(
let reducer = async (state, action) => {
switch (action.type) {
case 'unload':
return initialState
case 'reload': {
return await { data: 2 }
}
}
}
これは、useReducer
の例が触れていない興味深いケースです。減速機は非同期にロードするのに適した場所ではないと思います。 Reduxの考え方からは、通常はサンク、オブザーバブル(例:redux-observable)、またはcomponentDidMount
などのライフサイクルイベントのいずれかでデータをロードします。新しいuseReducer
を使用すると、componentDidMount
を使用してuseEffect
アプローチを使用できます。効果は次のようになります。
function ProfileContextProvider(props) {
let [profile, profileR] = React.useReducer(reducer, initialState);
useEffect(() => {
reloadProfile().then((profileData) => {
profileR({
type: "profileReady",
payload: profileData
});
});
}, []); // The empty array causes this effect to only run on mount
return (
<ProfileContext.Provider value={{ profile, profileR }}>
{props.children}
</ProfileContext.Provider>
);
}
また、ここでの作業例: https://codesandbox.io/s/r4ml2x864m 。
reloadProfile
関数にプロップまたはステートを渡す必要がある場合は、2番目の引数をuseEffect
(この例では空の配列)に調整して、必要な場合にのみ実行されるようにすることができます。前の値をチェックするか、何らかのキャッシュを実装して、不要なときにフェッチしないようにする必要があります。
子コンポーネントからリロードできるようにする場合、いくつかの方法があります。最初のオプションは、ディスパッチをトリガーするコールバックを子コンポーネントに渡すことです。これは、コンテキストプロバイダーまたはコンポーネントプロパティを通じて実行できます。既にコンテキストプロバイダーを使用しているため、そのメソッドの例を次に示します。
function ProfileContextProvider(props) {
let [profile, profileR] = React.useReducer(reducer, initialState);
const onReloadNeeded = useCallback(async () => {
const profileData = await reloadProfile();
profileR({
type: "profileReady",
payload: profileData
});
}, []); // The empty array causes this callback to only be created once per component instance
useEffect(() => {
onReloadNeeded();
}, []); // The empty array causes this effect to only run on mount
return (
<ProfileContext.Provider value={{ onReloadNeeded, profile }}>
{props.children}
</ProfileContext.Provider>
);
}
really明示的なコールバックの代わりにディスパッチ関数を使用したい場合は、特別な関数を処理する高次関数でディスパッチをラップすることで行うことができますReduxの世界ではミドルウェアによって処理されていたアクション。以下にその例を示します。 profileR
をコンテキストプロバイダーに直接渡すのではなく、ミドルウェアのように動作するカスタムプロバイダーを渡し、レデューサーが気にしない特別なアクションをインターセプトすることに注意してください。
function ProfileContextProvider(props) {
let [profile, profileR] = React.useReducer(reducer, initialState);
const customDispatch= useCallback(async (action) => {
switch (action.type) {
case "reload": {
const profileData = await reloadProfile();
profileR({
type: "profileReady",
payload: profileData
});
break;
}
default:
// Not a special case, dispatch the action
profileR(action);
}
}, []); // The empty array causes this callback to only be created once per component instance
return (
<ProfileContext.Provider value={{ profile, profileR: customDispatch }}>
{props.children}
</ProfileContext.Provider>
);
}
私は問題と可能な解決策について非常に詳細な説明を書きました。ダンアブラモフはソリューション3を提案しました。
注:Gistの例では、ファイル操作の例を示していますが、データフェッチについても同じアプローチを実装できます。
https://Gist.github.com/astoilkov/013c513e33fe95fa8846348038d8fe42
非同期アクションの問題を解決するために、ディスパッチメソッドをレイヤーでラップしました。
初期状態です。 loading
キーは、アプリケーションの現在の読み込み状態を記録します。アプリケーションがサーバーからデータを取得しているときに読み込みページを表示したい場合に便利です。
{
value: 0,
loading: false
}
4種類のアクションがあります。
function reducer(state, action) {
switch (action.type) {
case "click_async":
case "click_sync":
return { ...state, value: action.payload };
case "loading_start":
return { ...state, loading: true };
case "loading_end":
return { ...state, loading: false };
default:
throw new Error();
}
}
function isPromise(obj) {
return (
!!obj &&
(typeof obj === "object" || typeof obj === "function") &&
typeof obj.then === "function"
);
}
function wrapperDispatch(dispatch) {
return function(action) {
if (isPromise(action.payload)) {
dispatch({ type: "loading_start" });
action.payload.then(v => {
dispatch({ type: action.type, payload: v });
dispatch({ type: "loading_end" });
});
} else {
dispatch(action);
}
};
}
非同期メソッドがあるとします
async function asyncFetch(p) {
return new Promise(resolve => {
setTimeout(() => {
resolve(p);
}, 1000);
});
}
wrapperDispatch(dispatch)({
type: "click_async",
payload: asyncFetch(new Date().getTime())
});
完全なサンプルコードは次のとおりです。
非同期useReducerを使用したtodolistの例非同期関数をリデューサーに入れないでください。