공부블로그

콜백함수와 useCallback 안에서의 비동기 처리 ( + ThunkAction ) 본문

공부하기

콜백함수와 useCallback 안에서의 비동기 처리 ( + ThunkAction )

떠어영 2022. 11. 3. 16:02

콜백함수가 처음엔 이해도 안되고 너무 어려웠는데 계속 보다보니까 이제 조금 이해가 되는 것 같다.

그냥  인자로 넘기거나 넘겨받을 수 있어서 원하는 시점에 실행되는 함수 다. 이게 끝인데 지금까지는 너무 어렵게 생각한 것 같다. 

자식 컴포넌트에서 부모 컴포넌트의 상태를 변경할 때 props로 전달하는 함수나 이벤트 발생 시 실행되는 함수 등이 콜백함수이다.

 

추가로 useEffect hook도 dependecy가 변화하는 effect가 발생했을 때 실행되는 콜백함수를 등록하는 기능이다.

//useEffect(()=> {}, [dep])
useEffect(callback ,[dependencies]);

 

이벤트 리스너에 이벤트 핸들러를 등록하면 이벤트가 발생했을 때 핸들러에 등록한 함수를 실행한다!

 

이벤트 핸들러를 등록하는 방법에는 두가지 방법이 있다!

  • 일반적인 경우 : 함수 자체를 등록
const openModalHandler = () => {
	console.log('이벤트 핸들링')
};
//함수자체를 등록하면, 이벤트 발생시에만 실행된다
< ModalBtn onClick={openModalHandler} >

 

ex) 자식 컴포넌트에서 부모 컴포넌트의 상태를 변경하는 경우 = 상위 컴포넌트에서 넘겨준 함수를 이벤트 핸들러에 등록하는 것!

//부모 컴포넌트에서 state를 관리하고 있는데 자식 컴포넌트의 변화를 감지해야할 때
const TermsAgreement: React.FC = () => {
	//원하는 시점에 실행하고 싶은 함수를 만든다.
    const handleAgreementChange = (index: number) => {
        setAgreement(...)
      };
    ... 
    return(
        <Term
            key={key}
            agreement={agreement}
            //콜백함수를 자식에 props로 전달
            handleAgreementChange={handleAgreementChange} 
         />
     )
 }
            
const Term: React.FC<Props> = ({ agreement, handleAgreementChange }) => {
  //부모에서 넘어온 함수를 하위 컴포넌트에서 이벤트 핸들러로 등록
  const onIsAgreeChange = () => {
    handleAgreementChange(index);
  };

  return (
    <TermsWrapper>
        <CheckBox
          width={isMobile ? 32 : 24}
          height={isMobile ? 32 : 24}
          id={`agreement-${title}`}
          isChecked={agreement[index]}
          onChange={onIsAgreeChange}
          placeholder={title}
        />
    </TermsWrapper>
  );
};

 

  • 인자가 있는 경우 : 콜백함수 형식으로 등록 
const selectMenuHandler = (index) => {
    setIndex(index)
};
//이벤트가 발생하면 콜백함수를 실행한다
//onClick = {selectMenuHandler(idx)}로 작성하면 실행된 값이 등록되는 것이므로 에러!
return <li onClick={()=>selectMenuHandler(idx)}>{el.name}</li>

 

⊙ useCallback

useMemo와 비슷한 Hook이다. useMemo는 특정 결괏값을 재사용할 때 사용하는 반면,  useCallback특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용한다.

 

useCallback은 첫 번째 인자로 넘어온 함수를, 두 번째 인자로 넘어온 배열 형태의 함수 실행 조건의 값이 변경될 때까지 저장해놓고 재사용할 수 있게 해 줍니다. (콜백함수)

 

리액트 컴포넌트 안에 함수가 선언되어있을 때 이 함수는 해당 컴포넌트가 렌더링 될 때마다 새로운 함수가 생성되는데, useCallback을 사용하면 해당 컴포넌트가 렌더링 되더라도 그 함수가 의존하는 값(deps)들이 바뀌지 않는 한 기존 함수를 재사용할 수 있습니다.

const memoizedCallback = useCallback(() => { ... }, [deps]);

 

 

async & await 비동기 처리

( 비동기 : 요청과 결과가 동시에 일어나지 않는 것 )
async/await는 비동기를 동기식으로 처리하는 기법 → await으로 결과를 기다리므로 요청과 결과가 동시에 일어난 것처럼 처리해준다.

 

function 키워드 앞에 async만 붙여주면 되고 프라미스를 기다려야하는 부분 앞에 await만 붙여주면 된다 

 

await async 함수 안에서만 동작한다.

async가 붙은 함수는 반드시 프라미스를 반환하고, 프라미스가 아닌 것은 프라미스로 감싸 반환한다. ( ex. Promise<void> )

함수를 호출하고, 함수 본문이 실행되는 도중에 await키워드에서 실행이 잠시 '중단’되었다가 프라미스가 처리되면 실행이 재개됩니다. 

await는 말 그대로 프라미스가 처리될 때까지 함수 실행을 기다리게 만듭니다. 프라미스가 처리되면 그 결과와 함께 실행이 재개되죠. 프라미스가 처리되길 기다리는 동안엔 엔진이 다른 일(다른 스크립트를 실행, 이벤트 처리 등)을 할 수 있기 때문에, CPU 리소스가 낭비되지 않습니다.

 

ex ) parameter을 받는 async함수

export const fetchCompletedOrderHistory = async ( params: CompletedOrderHistoryParams,
): 
  Promise<CompletedOrderHistoryResponse> => { //함수의 반환 타입 = api통신으로 받아온 response
  //api통신 기다리기 
  const { data } = await axios.get<CompletedOrderHistoryResponse>(completeOrderHistoryUrl, { params });
  return data;
};

 

ex ) useCallback을 사용해 재사용할 수 있는 async함수

const loadUserSummary = useCallback( async() => {
        const startDate = format(Date.parse('2020-01-01'), 'yyyy-MM-dd');
        const endDate = format(Date.now(), 'yyyy-MM-dd');
        //api통신해서 받아올 promise를 기다리고, 받아오면 함수 실행 재개
        const { data } = await fetchTodayUserSummaryStatistics({ startDate, endDate });
        setUserSummaryStats(data);
  }, [setUserSummaryStats]);

 

ex ) async함수를 리턴값으로 하는 Thunk함수

 

Thunk 함수를 만들기 위해 const function = ( ) : ThunkAction< ... > => { ... } 형식으로 작성해준다.

ThunkAction 의 Generics 로는 다음 값들을 순서대로 넣어준다. <TReturnType, TState, TExtraThunkArg, TBasicAction>

  1. TReturnType: thunk 함수에서 반환하는 값의 타입을 설정합니다.
  2. TState: 스토어의 상태에 대한 타입을 설정합니다.
  3. TExtraThunkArg: redux-thunk 미들웨어의 Extra Argument의 타입을 설정합니다.
  4. TBasicAction: dispatch 할 수 있는 액션들의 타입을 설정합니다.

TReturnType 의 경우 아무것도 반환하지 않는다면 void 라고 넣으면 된다. 현재 상황에서는 thunk 함수에서 async 를 사용하고 있어서 Promise<void>가 더 정확하지만 그냥 void 라고 입력해도 된다.

A thunk function is a function that accepts two arguments: the Redux store dispatch method, and the Redux store getState method. 

export const handleSignUp = ( 
  payload: SignUp,
  successCallback: () => Promise<void>,
  //thunk함수의 리턴값 = (Promise)<void>
): ThunkAction<void, RootState, null, Action> => {
  //thunk함수는 인자로 리덕스 스토어의 dispatch함수와 getState함수를 가질 수 있다. 
  return async (dispatch, getState) => { //비동기(await) 처리를 위한 async키워드
    const { isLoading } = getState().userStore;

    if (!isLoading) {
      dispatch(changeIsLoading(true));
      try {
      	//api통신 기다리기
        await fetchSignUp(payload);
        //성공시 실행하는 콜백함수 실행하고 완료될 때까지 기다리기
        await successCallback();
      } catch (err) {
        dispatch(
          addModal({
            title: 'ORDER_DETAIL_SYSTEM_MESSAGE',
            text: 'signup failed.',
            firstButtonText: 'CONFIRM',
          }),
        );
      } finally {
        dispatch(changeIsLoading(false));
      }
    }
  };
};

+ ) redux 공식문서에 있는 화살표 함수 thunk함수

 

//Writing thunks using arrow functions
export const fetchTodoById = todoId => async dispatch => {
  const response = await client.get(`/fakeApi/todo/${todoId}`)
  dispatch(todosLoaded(response.todos))
}​