React useReducer async data fetch

我正在尝试获取一些数据与新的反应使用减速器 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 }
}
}
}
91445 次浏览

这是一个 useReducer示例没有涉及的有趣案例。我不认为减速器是异步加载的正确位置。来自于 Redux 思维模式,您通常会将数据加载到其他地方,或者在一个 Thunk 中,或者在一个可观察的(例如。或者仅仅在生命周期事件(如 componentDidMount)中。使用新的 useReducer,我们可以使用使用 useEffectcomponentDidMount方法。你的效果可以是这样的:

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

Also, working example here: https://codesandbox.io/s/r4ml2x864m.

如果需要向 reloadProfile函数传递道具或状态,可以将第二个参数调整为 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直接传递给上下文提供程序,而是传递了一个类似于中间件的自定义提供程序,它拦截了 rereduce 不关心的特殊操作。

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。

Note: The examples in the gist provide examples with file operations but the same approach could be implemented for data fetching.

Https://gist.github.com/astoilkov/013c513e33fe95fa8846348038d8fe42

我用一个层包装了分派方法,以解决异步操作问题。

这是初始状态。loading键记录应用程序当前的加载状态,当应用程序从服务器获取数据时,显示加载页非常方便。

{
value: 0,
loading: false
}

有四种行为。

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

Suppose there is an asynchronous method

async function asyncFetch(p) {
return new Promise(resolve => {
setTimeout(() => {
resolve(p);
}, 1000);
});
}


wrapperDispatch(dispatch)({
type: "click_async",
payload: asyncFetch(new Date().getTime())
});

完整的示例代码如下:

Https://codesandbox.io/s/13qnv8ml7q

这是一个很好的实践 使减速器保持纯净。它将使 useReducer更具可预测性和易于测试性。后续的方法都将异步操作与纯粹的简化器结合起来:

1. 在 dispatch之前获取数据(简单)

asyncDispatch包装原来的 dispatch,让上下文传递这个函数:

const AppContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initState);
const asyncDispatch = () => { // adjust args to your needs
dispatch({ type: "loading" });
fetchData().then(data => {
dispatch({ type: "finished", payload: data });
});
};
  

return (
<AppContext.Provider value=\{\{ state, dispatch: asyncDispatch }}>
{children}
</AppContext.Provider>
);
// Note: memoize the context value, if Provider gets re-rendered more often
};

const reducer = (state, { type, payload }) => {
if (type === "loading") return { status: "loading" };
if (type === "finished") return { status: "finished", data: payload };
return state;
};


const initState = {
status: "idle"
};


const AppContext = React.createContext();


const AppContextProvider = ({ children }) => {
const [state, dispatch] = React.useReducer(reducer, initState);
const asyncDispatch = () => { // adjust args to your needs
dispatch({ type: "loading" });
fetchData().then(data => {
dispatch({ type: "finished", payload: data });
});
};


return (
<AppContext.Provider value=\{\{ state, dispatch: asyncDispatch }}>
{children}
</AppContext.Provider>
);
};


function App() {
return (
<AppContextProvider>
<Child />
</AppContextProvider>
);
}


const Child = () => {
const val = React.useContext(AppContext);
const {
state: { status, data },
dispatch
} = val;
return (
<div>
<p>Status: {status}</p>
<p>Data: {data || "-"}</p>
<button onClick={dispatch}>Fetch data</button>
</div>
);
};


function fetchData() {
return new Promise(resolve => {
setTimeout(() => {
resolve(42);
}, 2000);
});
}


ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>

2. 为 dispatch(通用)使用中间件

dispatch可以与 中间件一起增强,如 再来一次还原-可观察还原传奇,以获得更大的灵活性和可重用性。

比如说,我们想要1。)使用 redux-thunk2获取异步数据。)做一些日志记录。)使用最终结果调用 dispatch。首先定义中间件:

import thunk from "redux-thunk";
const middlewares = [thunk, logger]; // logger is our own implementation

然后编写一个定制的 useMiddlewareReducer Hook,你可以在这里看到 useReducer与其他中间件捆绑在一起,类似于 Redux applyMiddleware:

const [state, dispatch] = useMiddlewareReducer(middlewares, reducer, initState);

中间件作为第一个参数传递,否则 API 与 useReducer相同。为了实现,我们采用 applyMiddleware 源代码并把它带到反应钩子。

const middlewares = [ReduxThunk, logger];


const reducer = (state, { type, payload }) => {
if (type === "loading") return { ...state, status: "loading" };
if (type === "finished") return { status: "finished", data: payload };
return state;
};


const initState = {
status: "idle"
};


const AppContext = React.createContext();


const AppContextProvider = ({ children }) => {
const [state, dispatch] = useMiddlewareReducer(
middlewares,
reducer,
initState
);
return (
<AppContext.Provider value=\{\{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};


function App() {
return (
<AppContextProvider>
<Child />
</AppContextProvider>
);
}


const Child = () => {
const val = React.useContext(AppContext);
const {
state: { status, data },
dispatch
} = val;
return (
<div>
<p>Status: {status}</p>
<p>Data: {data || "-"}</p>
<button onClick={() => dispatch(fetchData())}>Fetch data</button>
</div>
);
};


function fetchData() {
return (dispatch, getState) => {
dispatch({ type: "loading" });
setTimeout(() => {
// fake async loading
dispatch({ type: "finished", payload: (getState().data || 0) + 42 });
}, 2000);
};
}


function logger({ getState }) {
return next => action => {
console.log("state:", JSON.stringify(getState()), "action:", JSON.stringify(action));
return next(action);
};
}


// same API as useReducer, with middlewares as first argument
function useMiddlewareReducer(
middlewares,
reducer,
initState,
initializer = s => s
) {
const [state, setState] = React.useState(initializer(initState));
const stateRef = React.useRef(state); // stores most recent state
const dispatch = React.useMemo(
() =>
enhanceDispatch({
getState: () => stateRef.current, // access most recent state
stateDispatch: action => {
stateRef.current = reducer(stateRef.current, action); // makes getState() possible
setState(stateRef.current); // trigger re-render
return action;
}
})(...middlewares),
[middlewares, reducer]
);


return [state, dispatch];
}


//                                                         |  dispatch fn  |
// A middleware has type (dispatch, getState) => nextMw => action => action
function enhanceDispatch({ getState, stateDispatch }) {
return (...middlewares) => {
let dispatch;
const middlewareAPI = {
getState,
dispatch: action => dispatch(action)
};
dispatch = middlewares
.map(m => m(middlewareAPI))
.reduceRight((next, mw) => mw(next), stateDispatch);
return dispatch;
};
}


ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-thunk/2.3.0/redux-thunk.min.js" integrity="sha256-2xw5MpPcdu82/nmW2XQ6Ise9hKxziLWV2GupkS9knuw=" crossorigin="anonymous"></script>
<script>var ReduxThunk = window.ReduxThunk.default</script>

注意: 我们将居间态存储在 可变参考文献-stateRef.current = reducer(...)中,因此每个中间件都可以在使用 getState调用时访问当前的、最新的状态。

要将 一模一样 API 作为 useReducer,可以动态创建 Hook:

const useMiddlewareReducer = createUseMiddlewareReducer(middlewares); //init Hook
const MyComp = () => { // later on in several components
// ...
const [state, dispatch] = useMiddlewareReducer(reducer, initState);
}

const middlewares = [ReduxThunk, logger];


const reducer = (state, { type, payload }) => {
if (type === "loading") return { ...state, status: "loading" };
if (type === "finished") return { status: "finished", data: payload };
return state;
};


const initState = {
status: "idle"
};


const AppContext = React.createContext();


const useMiddlewareReducer = createUseMiddlewareReducer(middlewares);


const AppContextProvider = ({ children }) => {
const [state, dispatch] = useMiddlewareReducer(
reducer,
initState
);
return (
<AppContext.Provider value=\{\{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};


function App() {
return (
<AppContextProvider>
<Child />
</AppContextProvider>
);
}


const Child = () => {
const val = React.useContext(AppContext);
const {
state: { status, data },
dispatch
} = val;
return (
<div>
<p>Status: {status}</p>
<p>Data: {data || "-"}</p>
<button onClick={() => dispatch(fetchData())}>Fetch data</button>
</div>
);
};


function fetchData() {
return (dispatch, getState) => {
dispatch({ type: "loading" });
setTimeout(() => {
// fake async loading
dispatch({ type: "finished", payload: (getState().data || 0) + 42 });
}, 2000);
};
}


function logger({ getState }) {
return next => action => {
console.log("state:", JSON.stringify(getState()), "action:", JSON.stringify(action));
return next(action);
};
}


function createUseMiddlewareReducer(middlewares) {
return (reducer, initState, initializer = s => s) => {
const [state, setState] = React.useState(initializer(initState));
const stateRef = React.useRef(state); // stores most recent state
const dispatch = React.useMemo(
() =>
enhanceDispatch({
getState: () => stateRef.current, // access most recent state
stateDispatch: action => {
stateRef.current = reducer(stateRef.current, action); // makes getState() possible
setState(stateRef.current); // trigger re-render
return action;
}
})(...middlewares),
[middlewares, reducer]
);
return [state, dispatch];
}
}


//                                                         |  dispatch fn  |
// A middleware has type (dispatch, getState) => nextMw => action => action
function enhanceDispatch({ getState, stateDispatch }) {
return (...middlewares) => {
let dispatch;
const middlewareAPI = {
getState,
dispatch: action => dispatch(action)
};
dispatch = middlewares
.map(m => m(middlewareAPI))
.reduceRight((next, mw) => mw(next), stateDispatch);
return dispatch;
};
}


ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js" integrity="sha256-32Gmw5rBDXyMjg/73FgpukoTZdMrxuYW7tj8adbN8z4=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js" integrity="sha256-bjQ42ac3EN0GqK40pC9gGi/YixvKyZ24qMP/9HiGW7w=" crossorigin="anonymous"></script>
<div id="root"></div>


<script src="https://cdnjs.cloudflare.com/ajax/libs/redux-thunk/2.3.0/redux-thunk.min.js" integrity="sha256-2xw5MpPcdu82/nmW2XQ6Ise9hKxziLWV2GupkS9knuw=" crossorigin="anonymous"></script>
<script>var ReduxThunk = window.ReduxThunk.default</script>

更多信息-外部库: react-usereact-hooks-global-state,< a href = “ https://github.com/shiningjason/response-growth-reducer-hook”rel = “ noReferrer”> react-enhanced-reducer-hook

更新:

I’ve added another comment in the weblink below. It’s a custom hook called useAsyncReducer based on the code below that uses the exact same signature as a normal useReducer.

function useAsyncReducer(reducer, initState) {
const [state, setState] = useState(initState),
dispatchState = async (action) => setState(await reducer(state, action));
return [state, dispatchState];
}


async function reducer(state, action) {
switch (action.type) {
case 'switch1':
// Do async code here
return 'newState';
}
}


function App() {
const [state, dispatchState] = useAsyncReducer(reducer, 'initState');
return <ExampleComponent dispatchState={dispatchState} />;
}


function ExampleComponent({ dispatchState }) {
return <button onClick={() => dispatchState({ type: 'switch1' })}>button</button>;
}

老办法:

我只是张贴了这个回复 给你,并认为它可能是好张贴在这里以及万一它有助于任何人。

我的解决方案是使用 useState + 一个异步函数来模拟 useReducer:

async function updateFunction(action) {
switch (action.type) {
case 'switch1':
// Do async code here (access current state with 'action.state')
action.setState('newState');
break;
}
}


function App() {
const [state, setState] = useState(),
callUpdateFunction = (vars) => updateFunction({ ...vars, state, setState });


return <ExampleComponent callUpdateFunction={callUpdateFunction} />;
}


function ExampleComponent({ callUpdateFunction }) {
return <button onClick={() => callUpdateFunction({ type: 'switch1' })} />
}

很简单 您可以改变状态后,使用效果异步函数的结果

为获取结果定义 useState

const [resultFetch, setResultFetch] = useState(null);

useEffect收听 setResultFetch

获取异步 API 调用 setResultFetch(result of response)之后

useEffect(() => {
if (resultFetch) {
const user = resultFetch;
dispatch({ type: AC_USER_LOGIN, userId: user.ID})


}}, [resultFetch])