리듀서(Reducer)를 사용하여 부작용 처리 & 컨택스트 API사용하기

ZeroJun·2022년 6월 30일
0

React

목록 보기
9/13

useEffect

useEffect는 애플리케이션 내에서 발생하는 사이드이팩트를 처리하기 위한 훅이다. 사이드 이팩트를 일반 state로 관리하게 되면 무한 루프에 빠질 수도 있다.
ex) 재랜더링 => http호출 => http응답 => state변경 => 재랜더링 => http호출 ...

사이드 이펙트의 종류

  • store data in browser Storage
  • send http requests to backend ervers set & manage timers etc.
useEffect(() => {}, [dependencies]);
// dependedcies중 하나의 상태라도 변경되면 콜백함수가 실행된다.

로그인 정보 확인을 위한 useEffect사용

로그인 정보를 로컬 스토리지에 저장 후 새로고침을 할 때, 저장된 정보를 읽어오고 싶다면 앱이 시작할때 실행되는 useEffect를 활용할 수 있다.

const [isLoggedIn, setIsLoggedIn] = useState(false);

useEffect(() => {
  const 유저로그인정보 = localStorage.getItem("isLoggedIn");
  if (유저로그인정보 === "1") setIsLoggedIn(true);
}, []); // 의존성 배열이 비어 있어서 앱이 구동될 때 한번만 실행한다.

앱을 시작하면 전체 랜더링 후 useEffect의 의존성 배열이 비어있을지라도 첫 시작이므로 의존성이 변경된 것이라 간주하여 useEffect()가 실행된다. 따라서 isLoggedIn의 상태가 변화하여 컴포넌트가 다시 랜더링된다. 컴포넌트 랜더링이 완료된 후 useEffect의 의존성 배열이 비어있기 때문에 더이상 실행하지 않는다. 즉, 최초 1회만 실행되게 된다.

앱시작 - 랜더링 - useEffect실행(isLoggedIn변경) - 랜더링

아이디 유효성 검사를 위한 useEffect사용

다음은 아이디 속에 @가 포함되어 있고, 동시에 6글자 이상인 경우에 상태를 true로 만들어주는 코드다.

이 때 의존성 배열에는 어떤 것이 들어가야 할까?

  const [enteredEmail, setEnteredEmail] = useState("");
  const [emailIsValid, setEmailIsValid] = useState();
  const [enteredPassword, setEnteredPassword] = useState("");
  const [passwordIsValid, setPasswordIsValid] = useState();
  const [formIsValid, setFormIsValid] = useState(false);

  useEffect(() => {
    setFormIsValid(
      enteredEmail.includes("@") && enteredPassword.trim().length > 6
    );
  }, [enteredEmail, enteredPassword]);

바로 useEffect 함수에서 사용하는 요소를 추가하면 된다.
setFormIsValid, enteredEmail, enteredPassword를 사용하고 있으니 이 요소들의 상태를 의존성 배열에 추가해주면 된다. 이 때 setFormIsValid와 같은 setState함수는 보통 생략한다.

useEffect에서 다루는 사이드 이펙트는 어떤 액션에 대한 응답이 액션인 경우 모두 사이드 이펙트가 된다.

useEffect에서 clean Up사용하기

위에서 작성한 유효성 검사 코드는 키를 하나 입력할 때마다 state가 변경된다.
만약 backend서버로 유효성검사에 대한 요청을 이런 식으로 보낸다면 불필요한 네트워크 트래픽이 발생할 것이다.

이럴 때 사용자가 적극적으로 타이핑 할 때는 유효성 검사를 하지 않고, 일정 시간(500ms정도) 중지했을 경우에 검사를 하면 좋을 것이다.

이런 기술을 바로 '디바운싱'이라고 한다. 사용자 입력을 디바운스(그룹화)하는 것이다.

  useEffect(() => {
    const identifier = setTimeout(() => {
      console.log("check form validity");
      setFormIsValid(
        enteredEmail.includes("@") && enteredPassword.trim().length > 6
      );
    }, 500);

    return () => {
      console.log("clean up");
      clearTimeout(identifier);
    }; // clean up
  }, [enteredEmail, enteredPassword]);

useEffect에선 함수를 리턴할 수 있는데, 함수를 리턴하게 되면 그 함수는 처음 앱이 시작할 때를 제외하고 그 후엔 계속 useEffect보다 먼저 실행된다. 이 함수를 clean up함수라고 한다.

유효성 검사 로직을 500ms후에 하도록 설정하고, clean up함수에 clearTimeout을 통해 타이머를 해제하면 매번 타이핑 할 때마다 500ms이내로 타이핑 하면 계속 타이머가 해제되어 유효성 검사 로직이 실행되지 않는다. 그러다가 사용자가 타이핑 후 500ms이상 대기하게 되면 그때서야 유효성 검사 로직이 실행된다.

이것이 http요청이였다면 타이핑 할 때마다 보내지 않고, 1번~2번 정도만 보냈을 것이다.

이 예시는 clean up함수의 좋은 활용 예시다.

추가적으로 clean up함수는 컴포넌트가 DOM에서 제거될 때도 실행된다.

useReducer 사용법

const [state, dispatchFn] = useReducer(reducerFn, initialStaet, initFn);

dispatchFn : 액션을 reducerFn에 넘겨준다.

reducerFn

  • 최신 state스냅샷을 자동으로 가져오는 함수. 이 리듀서 함수 실행을 트리거하는 디스패치된 액션을 가지고 온다. 또한 새로 업데이트된 state를 반환한다.
  • 리듀서함수는 컴포넌트 함수 내부에 정의된 그 어떤 것과도 상호작용할 필요가 없기 때문에 컴포넌트 밖에 정의헐 수 있다.

initFn : 초기 설정값이 복잡할 경우 사용한다. (http request결과 등)

useReducer은 보다 복잡한 상태를 관리할 때 사용한다.
현재 상태객체와 액션 객체를 받아서 새로운 상태 객체를 반환한다.

다음은 로그인 시 이메일에 대한 유효성 검사를 할 때, 사용자가 input을 입력할 때와 input입력창을 떠나서 password등으로 이동했을 때의 상태를 관리하는 상황에서 useReducer를 사용한 예시다.

// Reducer Function
const emailReducer = (state, action) => {
  if (action.type === "USER_INPUT") { // 액션에 따라 상태를 적절하게 return한다.
    return {value: action.payload, isValid: action.payload.includes('@')};
  }
  if (action.type === 'INPUT_BLUR') {
    return {value: state.value, isValid: state.value.includes('@')};
  }
  return { // default return
    value  : '',
    isValid: null
  };
};

const [emailState, dispatchEmail] = useReducer(emailReducer, {
  value  : '', 
  isValid: false,
  // 기존에는 입력값과 유효여부 상태를 따로 관리했다.
});

const emailChangeHandler = (event) => { // 무언가 입력할 때
  dispatchEmail({type: 'USER_INPUT', payload: event.target.value});

  setFormIsValid(
    event.target.value.includes('@') && enteredPassword.trim().length > 6
  );
};

const validateEmailHandler = () => { // input입력창을 떠날 때
  dispatchEmail({type: 'INPUT_BLUR'});
};

useReducer와 useEffect의 활용

useReducer의 상태 속에는 여러가지 상태가 있을 것이다. useReducer의 상태자체를 useEffect의 의존성 배열에 넣게 되면 useReducet의 내부 상태들 중 하나만 바뀌어도 useEffect가 실행될 것이다. 내부 상태 중 하나만 의존성 배열에 넣고 싶을 때 다음과 같이 하면 된다.

const { isValid: emailIsValid } = emailState;
const { isValid: passwordIsValid } = passwordState;
// 이것은 객체 구조분해 할당으로 객체 내부의 isValid의 값을 찾아서 각각 별칭을 부여해준 것이다.
// const emailIsValid = eamilState.isValid; 와 같다고 보면 된다.
// 이렇게 추출한 상태를 의존성 배열에 넣으면 위의 상태들만 감시하게 된다.

useEffect(() => {
  const identifier = setTimeout(() => {
    console.log('Checking form validity!');
    setFormIsValid(
      emailState.isValid && passwordState.isValid
    );
  }, 500);

  return () => {
    console.log('CLEANUP');
    clearTimeout(identifier);
  };
}, [emailIsValid, passwordIsValid]); // 위에서 추출한 리듀서 내부 상태를 넣어주었다.
// 이제 리듀서 상태가 변경되더라도 내부 상태인 위의 상태들이 변하지 않으면 useEffect는 실행되지 않을 것이다.

0개의 댓글