응답 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
하면 될 것 같아요.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
는 '함수'로 .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
(카메라에)
을 dispatch
asyncDispatch
콘텍스트가 이 기능을 전달합니다.
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
redux-thunk, redux-contractible, redux-contractible 등의 미들웨어를 통해 유연성과 재사용성을 높일 수 있습니다.아니면 직접 쓰거나.
1) 비동기 를 1) 비동기 데이터로 .redux-thunk
2)3) 2) 로깅을 한다.dispatch
최종 결과입니다. 번째 정의: " " " " " " "
import thunk from "redux-thunk";
const middlewares = [thunk, logger]; // logger is our own implementation
다음 을 쓰세요.useMiddlewareReducer
에는 '후크(Hook)'로 됩니다).useReducer
Redux와 유사한 추가 미들웨어에 번들되어 있습니다.
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())
});
완전한 샘플 코드는 다음과 같습니다.
비동기 기능 결과 후 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
'programing' 카테고리의 다른 글
jQuery가 요청 본문에 유효한 json을 게시합니다. (0) | 2023.04.05 |
---|---|
네이티브와 안드로이드 대응 (0) | 2023.04.05 |
JS index.js 파일에 접속하는 index.html을 ID 참조용으로 어떻게 반응합니까? (0) | 2023.04.05 |
재료 UI 변경 시 반응 테스트 라이브러리 구성 요소 선택 (0) | 2023.04.05 |
BS Modal에서의 WooCommerce 고객 주문 상세 (0) | 2023.04.05 |