本文介绍了反应 useReducer 异步数据获取的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!
问题描述
我正在尝试使用新的 react useReducer API 获取一些数据,但卡在需要异步获取的阶段.我只是不知道如何:/
如何将数据获取放在 switch 语句中,或者这不是应该如何完成的方式?
从 'react' 导入 Reactconst ProfileContext = React.createContext()常量初始状态 = {数据:假}let reducer = async (state, action) =>{开关(动作.类型){案例卸载":返回初始状态案例重新加载":return { data: reloadProfile() }//怎么做???}}const reloadProfile = async() =>{尝试 {让 profileData = await fetch('/profile')profileData = 等待 profileData.json()返回个人资料数据} 捕捉(错误){控制台日志(错误)}}函数 ProfileContextProvider(props) {让 [profile, profileR] = React.useReducer(reducer, initialState)返回 (<ProfileContext.Provider value={{ profile, profileR }}>{props.children}</ProfileContext.Provider>)}导出 { ProfileContext, ProfileContextProvider }
我试图这样做,但它不适用于 async ;(
let reducer = async (state, action) =>{开关(动作.类型){案例卸载":返回初始状态案例重新加载":{返回等待{数据:2}}}}
解决方案
保持 reducers 纯净.它将使 useReducer
更具可预测性并简化可测试性.后续方法都将异步操作与纯 reducer 结合起来:
1.dispatch
前取数据(简单)
用 asyncDispatch
包裹原始的 dispatch
并让上下文传递这个函数:
const AppContextProvider = ({ children }) =>{const [状态,调度] = useReducer(reducer, initState);const asyncDispatch = () =>{//根据您的需要调整参数调度({类型:加载"});fetchData().then(data => {dispatch({ type: finished", payload: data });});};返回 (<AppContext.Provider value={{ state, dispatch: asyncDispatch }}>{孩子们}</AppContext.Provider>);//注意:记住上下文值,如果 Provider 被更频繁地重新渲染};
const reducer = (state, { type, payload }) =>{if (type === "loading") return { status: "loading" };if (type === "finished") return { status: "finished", data: payload };返回状态;};const initState = {状态:空闲"};const AppContext = React.createContext();const AppContextProvider = ({ children }) =>{const [state, dispatch] = React.useReducer(reducer, initState);const asyncDispatch = () =>{//根据您的需要调整参数dispatch({ type: "loading" });fetchData().then(data => {dispatch({ type: "finished", payload: data });});};返回 (<AppContext.Provider value={{ state, dispatch: asyncDispatch }}>{孩子们}</AppContext.Provider>);};功能应用(){返回 (<AppContextProvider><孩子/></AppContextProvider>);}const Child = () =>{const val = React.useContext(AppContext);常量{状态:{状态,数据},派遣} = val;返回 (<div><p>状态:{状态}</p><p>数据:{数据||"-"}<button onClick={dispatch}>获取数据</button>
);};函数 fetchData() {返回新的承诺(解决 => {setTimeout(() => {解决(42);}, 2000);});}ReactDOM.render(, 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="匿名"></script><div id="root"></div>
2.使用中间件dispatch
(通用)
dispatch
可能会使用 中间件 增强,例如 redux-thunk, redux-observable, redux-sagaa> 以获得更大的灵活性和可重用性.或者自己编写一个.>
假设,我们想要 1.) 使用 redux-thunk
获取异步数据 2.) 做一些日志记录 3.) 使用最终结果调用 dispatch
.首先定义中间件:
从redux-thunk"导入 thunk;const 中间件 = [thunk, logger];//logger 是我们自己的实现
然后编写一个自定义的 useMiddlewareReducer
Hook,您可以在此处将其视为与附加中间件捆绑在一起的 useReducer
,类似于 Redux applyMiddleware
:
const [state, dispatch] = useMiddlewareReducer(middlewares, reducer, initState);
中间件作为第一个参数传递,否则 API 与 useReducer
相同.对于实现,我们采用 applyMiddleware
源代码 并将其转移到 React Hooks.
const middlewares = [ReduxThunk, logger];const reducer = (state, { type, payload }) =>{if (type === "loading") return { ...state, status: "loading" };if (type === "finished") return { status: "finished", data: payload };返回状态;};const initState = {状态:空闲"};const AppContext = React.createContext();const AppContextProvider = ({ children }) =>{const [状态,调度] = useMiddlewareReducer(中间件,减速器,初始化状态);返回 (<AppContext.Provider value={{ state, dispatch }}>{孩子们}</AppContext.Provider>);};功能应用(){返回 (<AppContextProvider><孩子/></AppContextProvider>);}const Child = () =>{const val = React.useContext(AppContext);常量{状态:{状态,数据},派遣} = val;返回 (<div><p>状态:{状态}</p><p>数据:{数据||"-"}<button onClick={() =>dispatch(fetchData())}>获取数据</button>
);};函数 fetchData() {返回(调度,getState)=>{dispatch({ type: "loading" });setTimeout(() => {//假异步加载dispatch({ type: "finished", payload: (getState().data || 0) + 42 });}, 2000);};}函数记录器({ getState }){返回下一个 =>动作 =>{console.log("state:", JSON.stringify(getState()), "action:", JSON.stringify(action));返回下一个(动作);};}//与 useReducer 相同的 API,中间件作为第一个参数函数 useMiddlewareReducer(中间件,减速器,初始状态,初始值设定项 = s =>秒){const [state, setState] = React.useState(initializer(initState));const stateRef = React.useRef(state);//存储最近的状态const dispatch = React.useMemo(() =>增强调度({getState: () =>stateRef.current,//访问最近的状态stateDispatch: 动作 =>{stateRef.current = reducer(stateRef.current, action);//使 getState() 成为可能setState(stateRef.current);//触发重新渲染返回动作;}})(...中间件),[中间件,减速器]);返回 [状态,调度];}//|调度 fn |//中间件的类型为 (dispatch, getState) =>下一个Mw =>动作 =>行动function EnhanceDispatch({ getState, stateDispatch }) {返回(...中间件)=>{让调度;const 中间件 API = {获取状态,调度:动作=>调度(动作)};调度 = 中间件.map(m => m(middlewareAPI)).reduceRight((next, mw) => mw(next), stateDispatch);退货发货;};}ReactDOM.render(, 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="匿名"></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="anony"></script><script>var ReduxThunk = window.ReduxThunk.default</script>
注意:我们将中间状态存储在 可变引用 - stateRef.current = reducer(...)
,所以每个中间件都可以在调用时访问当前的、最近的状态 getState
.
要将 exact API 设为 useReducer
,您可以动态创建 Hook:
const useMiddlewareReducer = createUseMiddlewareReducer(middlewares);//初始化钩子const MyComp = () =>{//稍后在几个组件中//...const [状态,调度] = 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 };返回状态;};const initState = {状态:空闲"};const AppContext = React.createContext();const useMiddlewareReducer = createUseMiddlewareReducer(middlewares);const AppContextProvider = ({ children }) =>{const [状态,调度] = useMiddlewareReducer(减速器,初始化状态);返回 (<AppContext.Provider value={{ state, dispatch }}>{孩子们}</AppContext.Provider>);};功能应用(){返回 (<AppContextProvider><孩子/></AppContextProvider>);}const Child = () =>{const val = React.useContext(AppContext);常量{状态:{状态,数据},派遣} = val;返回 (<div><p>状态:{状态}</p><p>数据:{数据||"-"}<button onClick={() =>dispatch(fetchData())}>获取数据</button>
);};函数 fetchData() {返回(调度,getState)=>{dispatch({ type: "loading" });setTimeout(() => {//假异步加载dispatch({ type: "finished", payload: (getState().data || 0) + 42 });}, 2000);};}函数记录器({ getState }){返回下一个 =>动作 =>{console.log("state:", JSON.stringify(getState()), "action:", JSON.stringify(action));返回下一个(动作);};}函数 createUseMiddlewareReducer(middlewares) {return (reducer, initState, initializer = s => s) =>{const [state, setState] = React.useState(initializer(initState));const stateRef = React.useRef(state);//存储最近的状态const dispatch = React.useMemo(() =>增强调度({getState: () =>stateRef.current,//访问最近的状态stateDispatch: 动作 =>{stateRef.current = reducer(stateRef.current, action);//使 getState() 成为可能setState(stateRef.current);//触发重新渲染返回动作;}})(...中间件),[中间件,减速器]);返回 [状态,调度];}}//|调度 fn |//中间件的类型为 (dispatch, getState) =>下一个Mw =>动作 =>行动function EnhanceDispatch({ getState, stateDispatch }) {返回(...中间件)=>{让调度;const 中间件 API = {获取状态,调度:动作=>调度(动作)};调度 = 中间件.map(m => m(middlewareAPI)).reduceRight((next, mw) => mw(next), stateDispatch);退货发货;};}ReactDOM.render(, 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="匿名"></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="anony"></script><script>var ReduxThunk = window.ReduxThunk.default</script>
更多信息 - 外部库: react-use
, react-hooks-global-state, react-enhanced-reducer-hook
I'am trying to fetch some data with new react useReducer API and stuck on stage where i need to fetch it async. I just don't know how :/
How to place data fetching in switch statement or it's not a way how it's should be done?
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 }
I was trying to do it like this, but it's not working with async ;(
let reducer = async (state, action) => {
switch (action.type) {
case 'unload':
return initialState
case 'reload': {
return await { data: 2 }
}
}
}
解决方案
It is a good practice to keep reducers pure. It will make useReducer
more predictable and ease up testability. Subsequent approaches both combine async operations with pure reducers:
1. Fetch data before dispatch
(simple)
Wrap the original dispatch
with asyncDispatch
and let context pass this function down:
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. Use middleware for dispatch
(generic)
dispatch
might be enhanced with middlewares like redux-thunk, redux-observable, redux-saga for more flexibility and reusability. Or write your own one.
Let's say, we want to 1.) fetch async data with redux-thunk
2.) do some logging 3.) invoke dispatch
with the final result. First define middlewares:
import thunk from "redux-thunk";
const middlewares = [thunk, logger]; // logger is our own implementation
Then write a custom useMiddlewareReducer
Hook, which you can see here as useReducer
bundled with additional middlewares, akin to Redux applyMiddleware
:
const [state, dispatch] = useMiddlewareReducer(middlewares, reducer, initState);
Middlewares are passed as first argument, otherwise API is the same as useReducer
. For the implementation, we take applyMiddleware
source code and carry it over to React Hooks.
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>
Note: we store intermediate state in mutable refs - stateRef.current = reducer(...)
, so each middleware can access current, most recent state at the time of its invocation with getState
.
To have the exact API as useReducer
, you can create the Hook dynamically:
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>
More infos - external libraries: react-use
, react-hooks-global-state
, react-enhanced-reducer-hook
这篇关于反应 useReducer 异步数据获取的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!