velopert / react-tutorial

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

3. useAsync 커스텀 Hook 만들어서 사용하기 · GitBook #35

Open utterances-bot opened 4 years ago

utterances-bot commented 4 years ago

3. useAsync 커스텀 Hook 만들어서 사용하기 · GitBook

https://react.vlpt.us/integrate-api/03-useAsync.html

sbin0819 commented 4 years ago

정말 많은 도움 됐습니다!!

maywngml commented 3 years ago

선생님 저 질문이 있습니다. 실행할때마다 동작은 잘되는데 콘솔창에 "React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps"라는 경고문구가 뜨더라고요. useEffect안에 비동기 함수를 넣어줘서 그런건가요?? 이런 경고가 뜨는 이유가 궁금합니다!

Conradmaker commented 3 years ago

선생님 저 질문이 있습니다. 실행할때마다 동작은 잘되는데 콘솔창에 "React Hook useEffect has a missing dependency: 'fetchData'. Either include it or remove the dependency array react-hooks/exhaustive-deps"라는 경고문구가 뜨더라고요. useEffect안에 비동기 함수를 넣어줘서 그런건가요?? 이런 경고가 뜨는 이유가 궁금합니다!

혹시 useEffect안에서 fetchData라는 함수를 실행하셨는데, 그 fetchData함수안에 state가 들어있지 않나요?? 만약 useEffect안에서 실행하는 함수안에 state가 들어있을때는 deps 즉, []안에 그 함수를 넣어주어야 하는걸로 알고 있어요..!! [fetchData]이렇게요

maywngml commented 3 years ago

@Conradmaker 강사님 코드 따라서 작성해보면 fetchData함수 안에서 dispatch를 이용해서 state를 변경시키잖아요. 그렇기 때문에 fetchData함수를 deps안에 넣어줘야 한다는 말씀이신거죠??

Conradmaker commented 3 years ago

@maywngml 네 맞습니다!

Leehaeun0 commented 3 years ago

get 말고 post 하고싶을때는 어떻게 훅을 만들어야 하나요?

post후 data 가 업데이트 되었을시. state를 업데이트 해라. 라는 코드를 작성해야 하는데 이런식으로 훅을 만들면 data가 변했다는것을 useEffect의 []deps를 사용해서 data가 변경되었다는것을 감지할 수 밖에 없는건가요??

Leehaeun0 commented 3 years ago

@Conradmaker 혹시 댓글 보시면 답변해주시면 감사하겠습니다...! 그리고 loading 이나 error 상태일때, 어떠한 함수를 호출해야 하는 경우에는 이럴때도 useEffect 의 [] 에서 값이 변경된걸 감지해서 호출해야 하는 건지도 궁금합니다

Conradmaker commented 3 years ago

@Leehaeun0

post를 하고 싶을때는 같은 훅을 사용해도 상관 없습니다.

훅이 중요한게 아니라 Users.js에서 만든 get요청을 보내는 함수를 post요청으로 수정하면 되겠죠

async function getUsers() {
  const response = await axios.post(
    '요청주소'
  );
  return response.data;
}

그리고 state를 업데이트 해라라는 함수는 이미 reducer에 작성이 되어있습니다.

    case 'SUCCESS':
      return {
        loading: false,
        data: action.data,   //이렇게 성공시에 state.data를 dispatch로 보내준 데이터를 이용해 바꿔주라고 하고 있어요
        error: null
      };

data가 변했다는것을 감지할 필요는 없어요

왜냐하면 state.data를 수정하는 것이기 때문인데 useState로 만든 상태데이터의 경우에는 리액트가 관찰하고 있는 데이터이기 때문에 리액트가 변경된 데이터를 적용시켜 줄 것이에요. 그리고 만약 useEffect의 []deps를 사용해서 data가 변경되었다는것을 감지하게 된다면, 그 useEffect를 통해서 서버로 요청보내는 fetch함수를 실행시키는데 fetch함수를 실행시키면 결과를 바탕으로 state의 loading,data,error 값이 변하게 되잖아요?? 그걸 useEffect가 또 감시하고 있다가 state의 변화를 읽고 또 fetch함수를 실행시켜 무한으로 서버로 요청을 보내게 될 것이에요. 만약 state.data만 관찰하더라도 로딩시 state.data를 null로 바꾸기 때문에 useEffect의 fetch함수가 실행되서 성공시 state.data를 가져온 데이터로 바꾸기 때문에 또 useEffect의 fetch함수가 실행되서 무한루프가 걸리겠죠..?

그리고 loading 이나 error 상태일때, 어떠한 함수를 호출해야 하는 경우에는 이럴때도 useEffect 의 [] 에서 값이 변경된걸 감지해서 호출해야 하는 건지도 궁금합니다

이 부분은 바로 위에 설명한 것 처럼 무한루프가 발생하기 때문에 저렇게 해서는 안되고, 한다고 해도 좋은 방법이 아니라고 생각해요 . fetchData함수를 보면

 const fetchData = async () => {
    dispatch({ type: 'LOADING' });    //로딩상태로 바뀌는 부분.
    //여기서 loading상태이기 때문에 여기서 loading시 실행할 함수를 실행하면 되겠죠. await로 비동기로 처리할 수도 있을 것 같아요.
    try {
      const data = await callback();
      //여기서 함수를 실행하면 서버로부터 데이터를 잘 받아오면 실행하게 됩니다. 단, 성공상태로 state가 업데이트 되기 전에 실행이 됩니다.
      dispatch({ type: 'SUCCESS', data });   //요청이 성공하면 성공상태로 바뀌는 부분
      //여기서 함수를 실행하면 마찬가지로 서버로부터 데이터를 잘 받아오면 실행하게 되지만 state가 성공상태로 update되고 실행이 됩니다.
    } catch (e) {
      dispatch({ type: 'ERROR', error: e }); //요청이 실패하면 성공상태로 바뀌는 부분
     //여기서는 서버로의 요청결과가 에러일시 실행되는 부분이기 때문에 성공시와 마찬가지고 catch문 안에서 함수를 실행하면 실패시 함수가 실행되어요
    }
  };

저도 아직 많이 부족하고 배우는 입장이지만 도움이 되셨으면 좋겠어요

Conradmaker commented 3 years ago

@Leehaeun0 마지막으로 사실 저의 경우에는 이렇게 hook을 사용해서 비동기작업을 처리하는 경우는 거의 없었던 것 같아요. 나중에 배우실 리덕스 미들웨어나, swr같은 좋은 라이브러리들이 많이 있기때문에 그런것들을 많이 사용하겠지만, 기본 원리는 비슷해서 알고 넘어가신다면 훨씬 도움이 될 것 같습니다

Leehaeun0 commented 3 years ago

@Conradmaker

상세한 답변 정말 감사합니다!! 사실 이런 훅 방식으로 비동기 작업을 현재 사용하고 있어서 이 부분에 고민이 많았습니다. 그런데 한가지 의문인 부분이 있는데 post 나 put, patch 같은 http 메소드를 사용했을때는 대부분 아래코드의 response의 해당하는 부분에 오는 값이 get처럼 배열의 전체가 반환되는게 아니라 한개의 아이디에 해당하는 배열중 하나의 객체값만 반환되지 않나요?

async function getUsers() {
  const response = await axios.post( // response에는 users 배열이 아니라 하나의 user가 올 예정
    '요청주소'
  );
  return response.data;
}

Users.js 컴포넌트 단에서 useAsync 를 사용할때는 아래의 users 변수명도 하나의 객체가 반환됨으로 user 가 될테고, 이 반환된 user 값으로 기존에 "별도로 사용하고 있던 users라는 배열 state에 업데이트 작업을 해줘야 한다" 고 생각했어요. 제가 data가 변경됌을 감지하고 싶다고 말씀드린 배경도, api호출에 성공했을시 users를 업데이트 하라. 라고 작성을 해야하는데 "api호출에 성공했을시"라는 조건을 어떻게 알아야 할까에 대해서 고민이었어요.

const { loading, data: users, error } = state;

그래서 위 부분에 대해 고민을 하다가 제 나름대로 업데이트 api 커스텀 훅을 만든것을 블로그 글로 작성을 해봤는데 부끄럽지만 한번 읽어보시고 피드백 부탁드려도 될까요...?

블로그 링크입니다!

clean-teach commented 3 years ago

Users 컴포넌트에서는 useAsync()를 사용할 때, 첫번째 파라미터에 그냥 getUsers로 입력하는데 왜 User 컴포넌트에서는useAsync()의 첫번째 파라미터에 () => getUser() 함수 형태로 입력하는 건가요?

Noma98 commented 3 years ago

@clean-teach 전달해야하는 인자가 있어서 그렇지 않나요? useAsync 가 ()=>useAsync() 랑 같은데 id를 전달해야 할 땐 useAsync(id)가 아닌 ()=>useAsync(id)를 사용해야 되서요. useAsync(id)는 콜백함수를 전달하는 게 아니라 그냥 즉시 실행시키는 거라 화살표 함수를 이용하는 것 같습니다.

yechukim commented 2 years ago

@clean-teach getUsers 함수는 인자가 없기 때문에 함수 이름만 넣어줬지만(호출은 useAsync 에서 콜백을 호출할때 하고), getUser함수는 id 라는 인자가 있기 때문에 getUser(id)를 넣어줘야 하는데, 이때 이렇게 넣어버리면 호출되기 때문에 화살표 함수를 이용해서 넣어주는 것 같아요. 화살표 함수를 넣지않고 바로 호출하는 것으로 넣어버리면 콘솔 메시지에도 callback is not a function 이라는 에러가 뜨는데, 바로 호출해버리는 getUser(id)를 콜백으로 넘겨버리면 useAsync에서도 호출할 수 있는 함수가 아니게 되는거죠 getUser(id)는 value를 리턴하게 되니까요

DoK6n commented 2 years ago

이제 로딩은 Suspense