Effect & Reducer

Hover·2022년 10월 5일
0

Udemy리액트

목록 보기
19/19

리액트 개발자로써 꼭 알아야 할 Effect,Reducer,Context를 정리

  • Effect의 사용법
  • 복잡한 상태를 Reducer로 관리하는 법
  • 여러 개의 컴포넌트에 영향을 주는 state는 context라는 개념

1. side Effect란 무엇이며 useEffect를 소개합니다.

이펙트는 사이드이펙트랑 같은 뜻이다.

리액트 라이브러리는 UI렌더링이며, 사용자 입력에 따라 UI를 리렌더링 하는 것이다.

렌더링 이외의 서버 백엔드를 통해 request를 보낸다던가 하는 작업은 리액트가 하는 것이 아니다.

따라서 위와 같은 작업들은 React의 컴포넌트 평가의 밖에서 일어나야 하는 일이다.(일반적인 컴포넌트 함수 밖)

http에 의한 request로 state의 값이 변경된다면, 무한 루프에 빠질 수도 있다. 여기서 사이드이펙트는 직접적으로 이 컴포넌트에 직접적으로 간섭해서는 안된다.

여기서 사이드이펙트를 관리하는 useEffect 훅을 사용하게 된다.

useEffect(()=>{...},[dependencies]);

useEffect의 첫 번째 인수는 함수로, 모든 컴포넌트 평가 후에 실행되어야 하는 함수다.

두 번째 인수는 의존성 배열로, 해당 변수가 변화 시 useEffect내의 함수를 실행한다.

2. useEffect 훅 사용하기

로그인 페이지가 있다. 사용자가 로그인 시 로그인 페이지를 보여주는 로직인데 만약 페이지를 다시 시작할 경우 사용자가 세부 정보를 입력해야 다시 로그인된다.

정보를 어딘가에 저장할 경우 세부 정보를 입력하지 않아도 된다. 이것을 위해 useEffect를 사용한다.

const storedUserLoggedInInformation = localStorage.getItem('isLoggedIn');
    if(storedUserLoggedInInformation==='1'){
      setIsLoggedIn(true)
    }


const loginHandler = (email, password) => {
    localStorage.setItem('isLoggedIn','1');
    setIsLoggedIn(true);
  };

위와 같은 코드가 있다
loginHandler에서 로그인을 하면 localStorage에 두 개의 text값을 저장한다.

이후 storedUserLoggedInInformation 상수가 getItem을 통해 키값을 가져오고

해당 키값(상수)가 1이면 login state를 true로 변환한다.

로그인이 성공하면 이후에도 로그인 성공 상태를 유지하는 것이다.

하지만, 이 코드는 문제점이 하나 있다. setIsLoggedIn을 통해 state 변경시 리렌더링 되며 전체 함수가 재실행되는데, 이때 또 setIsLoggedIn을 실행시켜주는 무한루프가 발생한다.


 useEffect(()=>{
    const storedUserLoggedInInformation =localStorage.getItem('isLoggedIn');
    if(storedUserLoggedInInformation==='1'){
      setIsLoggedIn(true)
    }
  },[]);//모든 컴포넌트 재평가 후 실행

따라서, useEffect를 사용한다.

useEffect는 처음에 한번 실행되며, 이후 의존성배열이 변경될 때만 실행한다.

3. useEffect()의 종속성

login 패널을 담당하는 코드를 보면 이메일과 비밀번호 그리고 Login이 가능한지에 대한 유효성을 검사하는 코드가 있다.

유효성 검사를 useEffect에 옮기는 작업을 했다.

  useEffect(()=>{
    setFormIsValid(
      enteredEmail.includes('@') && enteredPassword.trim().length > 6
    );
  },[enteredEmail,enteredPassword]);
  //둘 중 하나라도 바뀐게 있으면 실행

setFormIsValid 안에 boolean변수를 넣어주었다.

enteredEmail에 '@'이 들어가는지와 enteredPassword의 길이가 6 이상인지를 true/false로 반환하고 해당 값을 setFormIsValid에 넣어준다.

여기서 의존성 배열에는 enteredEmail과 enteredPassword가 들어가는데 두 변수가 변화하면 useEffect 함수를 실행시킨다.

  const emailChangeHandler = (event) => {
    setEnteredEmail(event.target.value);
  };

  const passwordChangeHandler = (event) => {
    setEnteredPassword(event.target.value);
  };

두 변수의 변화는 event를 사용해서 useState를 통해 상태를 가진다.

위 종속성 배열에는 사용하는 모든 것을 추가해야 한다. 하지만 예외는 있다

  • 상태 업데이트 기능(useState-setState)
  • 내장 API함수

위 2가지 이외에 함수 내에서 사용하는 모든 변수들 + 구성 요소의 props까지 종속성 배열에 담아야 한다

import { useEffect, useState } from 'react';
 
let myTimer;
 
const MyComponent = (props) => {
  const [timerIsActive, setTimerIsActive] = useState(false);
 
  const { timerDuration } = props; // using destructuring to pull out specific props values
 
  useEffect(() => {
    if (!timerIsActive) {
      setTimerIsActive(true);
      myTimer = setTimeout(() => {
        setTimerIsActive(false);
      }, timerDuration);
    }
  }, [timerIsActive, timerDuration]);
};

timerIsActive 는 종속성으로 추가되었습니다. 왜냐하면 구성 요소가 변경될 때 변경될 수 있는 구성 요소 상태이기 때문이죠(예: 상태가 업데이트되었기 때문에)

timerDuration 은 종속성으로 추가되었습니다. 왜냐하면 해당 구성 요소의 prop 값이기 때문입니다 - 따라서 상위 구성 요소가 해당 값을 변경하면 변경될 수 있습니다(이 MyComponent 구성 요소도 다시 렌더링되도록 함).

setTimerIsActive 는 종속성으로 추가되지 않습니다. 왜냐하면예외 조건이기 때문입니다: 상태 업데이트 기능을 추가할 수 있지만 React는 기능 자체가 절대 변경되지 않음을 보장하므로 추가할 필요가 없습니다.

myTimer 는 종속성으로 추가되지 않습니다. 왜냐하면 그것은 구성 요소 내부 변수가 아니기 때문이죠. (즉, 어떤 상태나 prop 값이 아님) - 구성 요소 외부에서 정의되고 이를 변경합니다(어디에서든). 구성 요소가 다시 평가되도록 하지 않습니다.

setTimeout 은 종속성으로 추가되지 않습니다 왜냐하면 그것은 내장 API이기 때문입니다. (브라우저에 내장) - React 및 구성 요소와 독립적이며 변경되지 않습니다.

4. useEffect() 에서 cleanup함수 사용하기

useEffect에는 cleanup함수가 있다. useEffect내의 함수를 실행 후에 실행하는 함수다.

3번 챕터에서 input값이 변경 될 때마다 useEffect함수를 실행시켜 줬는데 이건 어떻게 보면

효율적이지 않을 수도 있다.

사용자가 끝까지 입력한 후 유효성 검사를 하는 편이 더 효율성있다.

이 때, 우리는 useEffect의 cleanup함수를 사용할 수 있다.

  useEffect(()=>{
    const identifier = setTimeout(()=>{
      setFormIsValid(
        enteredEmail.includes('@') && enteredPassword.trim().length > 6
      );
    },500);
    
    return ()=>{
      console.log("cleanup!");
      clearTimeout(identifier);
    };
    //처음 실행되는 경우를 제외하고 위 useEffect함수가 실행된 후 cleanup함수 실행
    //컴포넌트가 리렌더링이나 사라진 후 cleanup함수를 실행한다.
  },[enteredEmail,enteredPassword]);
  //둘 중 하나라도 바뀐게 있으면 실행

cleanup함수는 useEffect에서 return하는 함수로 useEffect 함수가 실행된 후 컴포넌트 리렌더링 or 컴포넌트가 사라진 후 실행한다.

위의 코드는 500의 딜레이가 담겨있는 setstate함수를 상수로 저장하여 cleanup함수에서 타이머를 클리어해준다.

따라서 사용자가 입력을 한 뒤 500의 딜레이 후 cleanup함수가 실행되는데 여기서 추가적인 입력을 하면 바로 딜레이 없이 cleanup함수가 실행되어 결과적으로 form 유효성 검사인 setFormiValid는 사용자가 입력을 멈춘 후 한번만 실행되는 로직을 구현하게 된다.

5. useReducer()

useReducer()는 복잡한 state관리를 위한 hook이다.

useReducer는 useState와 비슷한 모습을 보이는데, 어떨 때 useReducer를 사용하냐면 state의 상태가 매우 복잡하거나, state가 이전 state에 크게 의존할 경우 사용한다.

const [state,dispatchFunction] = useReducer(reducerFunction,init);

useReducer는 이런 형태로 나타낼 수 있다.

  • state : 가장 최신의 state
  • dispatchFunction : 해당 state를 업데이트할수 있게 해줌(액션을 디스패치)
  • reducerFunction : 액션을 소비하는 함수,리액트가 관리하는 최신의 state를 가져오며 이 함수를 실행하는 트리거인 액션을 가져옴, 이후 업데이트된 state 반환
  • init : state의 초기값

현재까지 작성한 코드로 useReduer()를 만들어보겠다.

email쪽부터 reducer를 만들겠다.

1. useReducer()선언
const [emailState,dispatchEmail] = useReducer(emailReducer,{value:'',isValid:null});

email의 현재 값과 유효한지에 대한 모든 값을 가지고있는 emailState를 선언한다.
이후 emailState의 상태를 변화시킬 dispatchEmail을 선언한 뒤
emailReducer에서 액션을 받아와서 상태를 변화시킨다.
선언부이므로, 초기값을 선언해준다.

2. dispatchEmail 함수 사용

  const emailChangeHandler = (event) => {
    dispatchEmail({type:'USER_INPUT',val:event.target.value})

    setFormIsValid(
      emailState.isValid && enteredPassword.trim().length > 6
    );
  };

email을 적는 input에 들어가는 이벤트함수다.
dispatchEmail에 type과 val을 설정해준다.
val은 현재 사용자가 입력하는 값인 event.target.value를 넣어준다.

setFormIsValid의 email부분도 useState에서 useReducer로 전환하는 작업이니
emailState.isValid로 바꿔준다

  const validateEmailHandler = () => {
    dispatchEmail({type:'INPUT_BLUR'})
  };

email을적는 input태그에 있는 함수다.

3. Reducer함수 선언

const emailReducer = (state,action)=>{
  if(action.type==='USER_INPUT'){
    return {value:action.val, isValid:action.val.includes('@')};
  }
  if(action.type==='INPUT_BLUR'){
    return {value:state.value,isValid:state.isValid}
  }
  return {value:'',isValid:false};
  // default state
}

emailReducer 함수다.
현재 상태값을 가지고 있으며, 액션을 받아온다.
action에서는 dispatchEmail이 값을 받아와 emailReducer에 넣어준다.
dispatchEmail은 type과 val을 가져오며, 각각의 타입에 맞는 액션을 취할 수 있다.

타입이 USER_INPUT라면, emailState의 값인 value와 isValid를 위 코드처럼 취한다.
밑의 타입 INPUT_BLUR도 마찬가지

만약 타입이 없으면 {value:'',isValid:false} 로 기본값을 지정한다.

6. useReducer & useEffect

로그인 form의 유효성을 현재는 state로 받고 있다. 하지만, 매번 변경할때마다 form 검사를 해주는건 불필요하다

예를들어서 이메일과 비밀번호 입력 란의 유효성이 충족되었음에도 불구하고 계속 유효성 검사를 하는 것은 낭비일 수가 있다.

const {isValid:emailIsValid} = emailState;
const {isValid:passwordIsValid} = passwordState;

우선 구조분해할당으로 각 input의 유효성 변수를 가져온다.
그 후 해당 변수를 상수로 선언한다.

  useEffect(()=>{
    const identifier = setTimeout(()=>{
      setFormIsValid(
        emailIsValid && passwordIsValid
      );
    },500);
    
    return ()=>{
      console.log("cleanup!");
      clearTimeout(identifier);
    };
  },[emailIsValid,passwordIsValid]);

이후 두 상수를 의존성배열에 넣고 useEffect로 유효성검사를 진행한다.

이러면 유효성이 변경될 때마다 form 유효성을 검사하게 된다.

7. useState() vs useReducer()

useState : 개별 state를 다루기에 적합하며 간단한 state를 다룰 때 적합하다. state 업데이트가 쉽고 몇 종류 안되는 경우에 적합하다. state로써의 객체가 없는 경우 적합

useReducer : state로써의 객체가 있을 경우 적합하다. 복잡한 state 업데이트 로직이 필요할 경우 사용

절대적으로 useReducer를 사용하는 것은 아니다

profile
프론트엔드 개발자 지망생입니다

0개의 댓글