programing

응답 useReducer 비동기 데이터 가져오기

testmans 2023. 4. 5. 21:25
반응형

응답 useReducer 비동기 데이터 가져오기

새로운 react useReducer API로 데이터를 가져오려고 하는데 비동기적으로 가져올 필요가 있는 스테이지에 멈춰 있습니다.어떻게 된 건지 모르겠어:/

스위치 스테이트먼트에 데이터 페치를 배치하는 방법, 그렇지 않은 방법?

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 하면 될 것 같아요.componentDidMountuseEffect을 사용하다

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는 '함수'로 .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>
  );
}

명시적 콜백이 아닌 디스패치 기능을 사용하려면 디스패치를 상위 함수로 래핑하여 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>
  );
}

환원제를 순수하게 유지하는 은 좋은 관행이다.할 수 있을 것이다useReducer예측성이 향상되어 테스트 용이성이 향상됩니다.이후의 접근방식에서는 모두 비동기 조작과 순수 리듀서를 조합합니다.

에 1. 데이터 가져오기dispatch (카메라에)

dispatchasyncDispatch콘텍스트가 이 기능을 전달합니다.

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 (카메라에)

dispatchredux-thunk, redux-contractible, redux-contractible 등의 미들웨어통해 유연성과 재사용성을 높일 수 있습니다.아니면 직접 쓰거나.

1) 비동기 를 1) 비동기 데이터로 .redux-thunk2)3) 2) 로깅을 한다.dispatch최종 결과입니다. 번째 정의: " " " " " " "

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

다음 을 쓰세요.useMiddlewareReducer에는 '후크(Hook)'로 됩니다).useReducerRedux와 유사한 추가 미들웨어에 번들되어 있습니다.

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

는 첫 인수로 않은 경우 는 API와 .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(...)따라서 각 미들웨어는 를 호출했을 때의 현재 최신 상태에 액세스할 수 있습니다.

다음과 같은 정확한 API를 가지려면useReducer훅은 동적으로 작성할 수 있습니다.

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>

기타 정보 - 외부 라이브러리: ,

나는 그 문제와 가능한 해결책에 대해 매우 상세한 설명을 썼다.Dan Abramov가 솔루션 3을 제안했습니다.

주의: GIST의 예는 파일 조작의 예를 나타내고 있지만, 데이터 취득에도 같은 어프로치를 실장할 수 있습니다.

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

업데이트:

아래 웹링크에 다른 코멘트를 추가했습니다.건건 called called called called라고 하는 useAsyncReducer 시그니처와 합니다.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>;
}

오래된 솔루션:

저는 이 답장을 여기에 올렸습니다.누군가 도움이 될지 모르기 때문에 여기에 올리는 것도 좋을지도 모른다고 생각했습니다.

제 해결책은 이 모든 것을 모방하는 것이었습니다.useReducer를 사용합니다.useState + 비동기 함수:

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' })} />
}

비동기 액션 문제를 해결하기 위해 디스패치 방식을 레이어로 포장했습니다.

초기 상태를 나타냅니다.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);
    }
  };
}

비동기식 방법이 있다고 가정합니다.

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

비동기 기능 결과 후 useEffect에서 상태를 변경할 수 있는 것은 매우 간단합니다.

useState 득 of

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

★★★★★★★★★★★★★★★★★」useEffect 위해서setResultFetch

async 콜 후 API 콜setResultFetch(result of response)

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

}}, [resultFetch])

언급URL : https://stackoverflow.com/questions/53146795/react-usereducer-async-data-fetch

반응형