velopert / react-tutorial

벨로퍼트와 함께하는 모던 리액트 튜토리얼 문서
https://react.vlpt.us/
347 stars 100 forks source link

5. redux-thunk로 프로미스 다루기 · GitBook #63

Open utterances-bot opened 3 years ago

utterances-bot commented 3 years ago

5. redux-thunk로 프로미스 다루기 · GitBook

https://react.vlpt.us/redux-middleware/05-redux-thunk-with-promise.html

hotdari commented 3 years ago

export const createPromiseThunk = (type, promiseCreator) => { const [SUCCESS, ERROR] = [${type}_SUCCESS, ${type}_ERROR];

return param => async dispatch => { dispatch({ type, param }); try { const payload = await promiseCreator(param); dispatch({ type: SUCCESS, payload }); } catch (e) { dispatch({ type: ERROR, payload: e, error: true }); } }; };

위의 해당 소스에 궁금한점이 있습니다. createPromiseThunk를 불러올때 param 값이 undefind 인 경우가 생기는데 이 부분에 대해서는 문제가 없을까요?

yeongil4764 commented 3 years ago

중간에 리팩토링전에 module/posts.js 부분에 오타가 있는거 같습니다. 액션 반환값에 로딩값들이 전부다 true로 설정되어 있어 success, error일 경우도 무조건 로딩중이 뜨게 되네요.

leesd88 commented 3 years ago

리펙토링 하는 부분 export const createPromiseThunk = ...이 코드 해석이 잘 안되네요ㅠ return 값에 param이 어떤식으로 동작하는지를 모르겠습니다... 갑툭튀?느낌?

leesd88 commented 3 years ago
case GET_POSTS:
case GET_POSTS_SUCCESS:
case GET_POSTS_ERROR:
  return handleAsyncActions(GET_POSTS, 'posts')(state, action);

위 같은 코드도 벨로퍼트님이 쭉 강의 해오신거 다 봤지만 낯선 방식의 코드에요ㅠ;

ryu9663 commented 2 years ago
switch (action.type){
            case GET_POSTS :
                return { 
                ...state,
                posts: {
                loading: true,
                data: null,
                error: null
                }
            };
            case GET_POSTS_SUCCESS :
                return {
                ...state,
                posts: {
                loading: false,
                data: action.posts,
                error: null
                }
            };
            case GET_POSTS_ERROR :
                return {
                    ...state,
                    posts: {
                        loading:false,
                        data:null,
                        error:action.error
                    }
                };

loading을 다 true 로 하면 계속 로딩중이 떠서, case GET_POSTS_SUCCESScase GET_POSTS_ERROR부분에서는 loading : false로 바꿔줬습니다.

jaykiim commented 2 years ago

@leesd88 리팩토링 하기 전에 기존 posts 모듈에서 thunk 함수 (getPost, getPosts) 하고 리듀서에 반복되는 로직이 많았기 때문에 반복되는 부분은 재사용하고 변경을 줘야하는 부분만 파라미터로 받아서 thunk랑 reducer를 생성해주는 함수들을 asyncUtils.js 파일에서 만드는건데 그 중에서 createPromiseThunk 같은 경우 기존 posts 모듈에서 thunk함수 부분 (getPost, getPosts) 있죠. 보면 디스패치하는 액션의 타입명 (그러니까 "어떤 요청"에 대한 로딩,성공,실패인지만 다르고 로딩,성공,실패 자체는 동일) 하고 호출하는 api만 변하고 나머지는 동일하기 때문에, 변화하는 부분만 파라미터 (type, promiseCreator) 로 받아서 thunk 함수를 생성해주는 함수createPromiseThunk 에요. 타입명은 type으로 받고 호출할 api는 promiseCreator 로 받고있어요. api호출같은 경우엔 api/posts.js에서 이미 api호출하는 함수를 만들었기 때문에 이걸 전달받을거라서 promiseCreator 인자의 타입은 결국 콜백 함수에요. 근데 해당 함수를 보시면 async함수죠. async 함수는 항상 프로미스를 리턴하기 때문에 promiseCreator 라고 이름지으신거같아요. 그리고 타입명같은 경우에도 보면 xxx_SUCCESS, xxx_ERROR 과 같이 _SUCCEESS, _ERROR 은 반복적으로 들어가기 때문에 const [SUCCESS, ERROR] = [${type}_SUCCESS,${type}_ERROR] 이렇게 처리했고, 리턴 부분은 위에도 썼다시피 createPromiseThunk 는 기존 posts 모듈의 thunk 함수에서 반복되는 패턴을 재사용하고 변경되는 부분만 인자로 받아서 thunk 함수를 생성 생성해주는 함수이기 때문에 함수 형태를 리턴해주고 있어요.
첫번째 화살표 다음의 함수가 thunk함수이고, 처음에 param 이거는 api 호출시에 필요에 따라서 넣어줘야할 파라미터에요. api 호출은 api/posts.js 에서 export 하고있는 함수를 promiseCreator로 받아서 호출할건데, api/posts.js 의 getPostsById 같은 경우 특정 포스트를 조회하는 함수잖아요. 그러니까 어떤 포스트를 조회할 것인지 id 라는 파라미터로 식별을 하기때문에, 이 getPostsById 함수를 promiseCreator로 받게 될 경우 id를 넘겨주어야겠죠. 즉 api 호출에서 필요에 따라 파라미터를 넣어줘야하는 경우도 있으므로 그것을 param으로 받는것이에요. 헷갈리실 경우 4장 API 연동하기의 Context와 함께 사용하기에 보시면 이거랑 굉장히 거의 똑같은 패턴이 나와있어요. 그리고

case GET_POSTS:
case GET_POSTS_SUCCESS:
case GET_POSTS_ERROR:
  return handleAsyncActions(GET_POSTS, 'posts')(state, action);

이 부분도 동일 챕터 맨마지막에 보시면 핸들러 두개를 합쳐서 하나의 리듀서로 만드는 부분에서 동일한 코드가 나온답니다! 순수 자바스크립트 문법인데, 스위치문에서 case 내에 리턴이나 break가 없을 경우 return이나 break를 만날 때까지 다음 케이스들의 코드가 주루룩 다 실행되는것을 이용한 것이에요.

Darcyu83 commented 2 years ago

return param => async dispatch => { // 요청 시작 dispatch({ type, param });

위의 dispatch 값은 아래와 같이 dispatch( {type: 'GET_POSTS' , param : undefined}) 로 전달 되면 reducer에서 switch 문 case GET_POSTS 에서 처리할 때 action.param값을 사용하지 않으면 문제가 되지 않을듯 합니다.

get Post by id 의 경우 in posts.js export const getPosts = createPromiseThunk( GET_POSTS, postsAPI.getPosts);

위와 같이 선언하고 사용할때 param값은 getPosts(param)함수의 파라미터로 전달해서 postsAPI.getPostById(param)에서 사용될듯하네요 .

restareaByWeezy commented 2 years ago

Functions are not valid as a React child. This may happen if you return a Component instead of from render. Or maybe you meant to call this function rather than return it.

라우팅 과정에서 계속 위와 같은 오류가 나서 스택오버플로우를 참조하여

https://stackoverflow.com/questions/48458334/functions-are-not-valid-as-a-react-child-this-may-happen-if-you-return-a-compon

App.js의 postlistpage와 postpage 작성부분에 꺽쇠를 넣어주었더니 작동하는데 혹시 오류 원인과 해결에 대한 답변을 들을 수 있을까요?

} exact={true} /> } /> {/* element={PostPage는 왜 안되는지} */}
</>
bsj805 commented 2 years ago

이제는 좀 코드가 바뀌어서 https://reactrouter.com/docs/en/v6/getting-started/tutorial#add-some-routes 이쪽을 참조해서 보셔야 할거에요 저것처럼 구현을 하시고 싶다 하시면

function App() {
  return (
    <Routes>
      <Route path="/" element={<PostListPage />} exact={true}>
        <Route path=":id" element={<PostPage />} />
      </Route>
    </Routes>

  );
}

이런식으로 하시면 페이지 추가가 구현이 안되는데, 이라는 컴포넌트를 페이지에 넣어주어야 합니다. 따라서 저는 PostListPage에 이를 넣었습니다

import PostListContainer from "../containers/PostListContainer";
import {Outlet} from "react-router-dom";

const PostListPage = () => {
  return (
    <>
      <PostListContainer />
      <Outlet />
    </>
  );
}

export default PostListPage;

postpage도 바뀌어야합니다

import PostContainer from "../containers/PostContainer";
import {useParams} from "react-router-dom";

const PostPage = () => {
  const params = useParams();
  return <PostContainer postId={parseInt(params.id, 10)} />;
}

라우터부터는 이제 React Router 홈페이지의 tutorial을 따라가시는 것이 더 잘 보일지도 모르겠네요. 많은 것이 바뀌었습니다.

froggy1014 commented 1 year ago

App.js

import React from 'react';
import { Route, Routes } from 'react-router-dom';
import PostListPage from './pages/PostListPage';
import PostPage from './pages/PostPage';

function App() {
  return (
    <>
    <Routes>
      <Route exact path="/" element={<PostListPage />} />
      <Route path=":id" element={<PostPage />} />
    </Routes>
    </>
  );
}

export default App;

PostPage.js

import React from 'react';
import PostContainer from '../containers/PostContainer';
import { useParams } from 'react-router';

function PostPage() {
  const params = useParams();
  return <PostContainer postId={parseInt(params.id, 10)} />;
}

export default PostPage;