反应 useReducer 异步数据获取 [英] React useReducer async data fetch

查看:32
本文介绍了反应 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 异步数据获取的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
其他开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆