티스토리 뷰

목차

useRef Hook

  • ref가 하는 일, 예시
  • 제어 컴포넌트와 비제어 컴포넌트 

useEffect Hook 

  • useEffect 의 종속성(의존성 배열) 예외 사항
  • side Effect 가 무엇인가? 
  • clean-up 함수를 통해 디바운싱(debouncing)구현하기

[전제조건] 리액트 hook이기 때문에 당연히 함수형 컴포넌트(<->클래스형) 안에서만 사용할 수 있습니다. 

ref는 참조(reference)를 뜻합니다. 리액트에서는 ref 라고 부릅니다. 

 

useRef는 어떤 값이는 저장할 수 있는 자바스크립트 객체이다. 

리액트가 렌더링할 때마다 동일한 객체를 넘기기 때문에 값이 변경되어도 리렌더링이 발생하지 않는다. 

ref 가 하는 일 ?

다른 DOM 요소에 직접 접근해서 그것들로 작업할 수 있게 해준다. ( HTML을 직접 조적하는 것이므로 JQuery 시절에 주로 쓰이던 imperative(명령형) 방식의 웹 프로그래밍 <-> React는 선언형 프로그래밍 패러다임을 기반하기 때문에 명령형 접근 방식은 웬만하면 X )

(마지막에 렌더링되는 HTML요소들과 다른 JS코드들을 연결시켜줌으로써)

ref 프로퍼티를 JSX의 HTML요소에 연결해주면 연결이 완성된다. 

 

useRef가 반환하는 것은 항상 객체입니다(즉, 항상 current 프롭을 갖고 있다).

current 프롭은 해당 ref가 연결된  실제 값(실제 DOM node값)을 갖습니다. 

🔥 DOM은 리액트에 의해서만 조작되는 것이 좋습니다. -> 값을 수정하는 것은 X 하지만 리액트가 아닌 DOM API(ref 사용)을 이용해서 input에서 데이터를 읽는 것은 좋음(데이터를 수정하는 것이 아니기 때문에) 

const nameInputRef = useRef();

(코드 중략)

console.log(nameInputRef.current.value); 로 input태그의 실제 값을 읽을 수 있다. 
// current 는 저장된 값을 나타낸다.

ref를 사용하면 모든 키 입력을 기록하지 않더라도(onChangeHandler 함수를 만들어서 체크) 실제 값에 접근할 수 있다.

하지만 이렇게되면 useState의 값을 직접 조작하는 함수(e.g. setState)를 사용하지 않게되는데 DOM을 조작하기 위해서 ref 를 사용할 수  있지만 ref 를 사용하는 것은 매우 드문일이라고 합니다. 

위에서도 언급했지만 웬만하면 컴포넌트는 React에 의해서 DOM이 조작되어야 한다는 것을 강조합니다. 

nameInputRef.current.value= ''; //이렇게 빈 값으로 input값을 직접 변경하는 것은 드문일
// 하지만 안되는 것은 아니다. 
// state기반 솔루션으로 다시 되돌아가도 됨 state와 setState로!
// 값을 읽기만 한다면 ref가 더 나은 선택

여기까지 ref를 사용하여 사용자 입력을 얻는 방법에 대해서 배웠습니다. 

 

 

🤸‍♀️제어 컴포넌트와 비제어 컴포넌트

위에서 보여드렸던 ref를 사용하여 인풋필드를 조작하는 예시를 바로 비제어 컴포넌트라고 합니다.

왜냐하면 React로 인해서 내부 state를 제어하는 게 아니기 때문입니다. 

일반적으로 input 즉, 폼 컴포넌트에 대해서 비제어 컴포넌트라고 얘기합니다. 왜냐하면 브라우저에 의해 내부 state를 갖는 경향이 있기 때문입니다. ref를 사용하기 이전에 state로 컴포넌트에 접근한 방식이 제어된 접근 방식입니다(내부 state가 React에 의해서 제어되기 때문입니다). 

 

🐱‍👤요약:

ref 를 사용하여 컴포넌트를 조작하면 비제어 컴포넌트
state를 사용하여 컴포넌트를 조작하면 제어 컴포넌트이다. 
제어의 주체가 React이냐 아니냐에 따라서 제어/비제어로 나뉩니다.

 

🛠참고사항

비제어 컴포넌트의 기본 값 :

React 렌더링 생명주기에서 폼 엘리먼트의 value 어트리뷰트는 DOM의 value를 대체합니다. 비제어 컴포넌트를 사용하면 React의 초깃값을 지정하지만, 그 후의 업데이트는 제어하지 않는 것이 좋습니다. 이러한 경우는 value어트리뷰트 대신 defaultValue 를 지정하면 됩니다. 컴포넌트가 마운트된 후 defaultValue 어트리뷰트를 변경해도 DOM의 값이 업데이트되지 않습니다.

→ 비제어 컴포넌트에서 ref를 사용할 때에는 defaultValue를 사용 ! 

value를 사용하려면 onChange를 사용하여 value값을 monitor해줘야 합니다. 즉, 기존 제어 방식(useState)을 사용할 때에만 value사용이 유효한 것입니다. 

 

🤸‍♀️useEffect Hook 

  • useEffect는 사이드이펙트*를 처리하기 위해 존재한다.
  • useEffect 함수에서 사용하는 "모든 것"을 종속성으로 추가해야 한다(두 번째 인자인 의존성 배열에!). 즉, 거기에서 사용하는 모든 상태 변수와 함수를 포함해야 한다. 하지만 몇 가지 예외*가 있음 

🛠useEffect 함수의 종속성 예외 사항 (의존성 배열에 넣지 않아도 되는 것들)

  • 상태 업데이트 기능은 추가할 필요 없다. (e.g setFormIsValid, set함수) Why? React는 해당 함수가 절대 변경되지 않도록 보장하므로 종속성으로 추가할 필요가 없습니다. 
  • 내장 API 또는 함수를 추가할 필요 없다. fetch() 혹은 localStorage 같은 것 Why? React 구성 요소 렌더링 주기와 관련이 없으며 변경되지 않기 때문에
  • 변수나 함수를 추가할 필요가 없다. (구성 요소 외부에서 정의한 변수나 함수)

정리: effect 함수에서 사용하는 모든 것들을 추가해야한다. 구성 요소(또는 일부 상위 구성 요소)가 다시 렌더링되어 이 요소들이 변경될 수 있는 경우. 그렇기 때문에 컴포넌트 함수에 정의된 변수나 상태, 컴포넌트 함수에 정의된 props 또는 함수는 종속성으로 추가되어야 할 것들 입니다.   

 

사이드 이펙트(Side Effect)가 무엇인가?

  1. 간단히 말하면 React가 하는 일(변화된 상태를 감지하여 UI 그리기)과 상관없는 작업들을 말합니다. 예를 들어 http로 서버와 통신하는 것 등
  2. React 컴포넌트가 화면에 렌더링된 이후에 비동기로 처리되어야 하는 부수적인 효과들을 Side Effect 라고 한다. e.g. http 리퀘스트 / 키 입력을 듣고 입력된 데이터를 저장하는 것 / validation 유효성 검사(사용자 입력 데이터의 사이드이펙트)

대표적인 예시로 어떤 데이터를 가져오기 위해서 외부 API를 호출하는 경우, 일단 화면에 렌더링할 수 있는 것들을 먼저 렌더링하고 

실제 데이터는 비동기로 가져오는 것이 권장된다. 

 

Side Effect 두 종류

  1. 정리(clean-up)이 필요한 것 
  2. 정리가 필요하지 않은 것 ( network request, DOM 수동 조작, 로깅 등) 

⭐clean up 함수

  • effect에서 함수를 반환하는 이유?
    • effect를 위한 추가적인 정리(clean up)메커니즘입니다. 모든 effect는 정리를 위한 함수를 반환할 수 있고 이 점이 구독(subscription)의 추가와 제거를 위한 로직을 가까이 묶어둘 수 있게 해줍니다. 
    • 정리가 필요한 effect 처리를 위해서 → 외부 데이터에 구독(subscription)을 설정해야 하는 경우, 메모리 누수가 발생하지 않도록 정리(clean-up)해주는 것이 매우 중요하다.
    • Class형 컴포넌트에서는 componentDidMount 에 구독을 설정한 뒤 componenetWillUnmount에서 이를 정리합니다. 
  • React가 effect*를 정리하는 시점은 정확히 언제인가요?
 여기서 effect란 사이드 이펙트 함수를 말하며 useEffect 내부에서 실행시키는 함수라고 생각하시면 됩니다. 
  • 컴포넌트가 언마운트될 때에 정리를 실행 + 리렌더링이 실행되는 때마다 실행 (간단히 말하면 최초 1회를 제외한 사이드 이펙트 함수가 실행되기 이전 && 컴포넌트가 제거되기 전) 
  • useEffect의 첫번째 인자에서 클린 업이 필요한 경우는 return 에 클린업 함수를 써주면 됩니다. 정리가 필요없는 경우에는 어떤 것도 반환하지 않으면 된다. 
  • 꼭 이름이 있는 함수가 아니여도 되고 익명함수( 화살표 함수 )도 상관없습니다. 

 

⭐useEffect의 cleanup 함수를 통해 디바운싱(debouncing)구현하기

디바운싱쓰로틀링은 모두 웹에서 발생하는 이벤트를 제어하는 방법입니다. 주로 성능 개선을 위해서 사용됩니다. 

 

👾디바운싱(debouncing) 이란?

연속으로 호출되는 함수들 중에 마지막에 호출되는 함수(또는 제일 처음 함수)만 실행되도록 하는 것

👾쓰로틀링(throttling) 이란?

마지막 함수가 호출된 후 일정 시간이 지나기 전에 다시 호출되지 않도록 하는 것

 

const Login = (props) => {
  const [enteredEmail, setEnteredEmail] = useState('');
  const [emailIsValid, setEmailIsValid] = useState();
  const [enteredPassword, setEnteredPassword] = useState('');
  const [passwordIsValid, setPasswordIsValid] = useState();
  const [formIsValid, setFormIsValid] = useState(false);

// validation debouncing 처리
  useEffect(() => {
    // 설정된 타이머의 식별자를 반환한다고 치자
    const identifier = setTimeout(() => {
      console.log('checking form validity');
      setFormIsValid(
        enteredEmail.includes('@') && enteredPassword.trim().length > 6,
      );
    }, 1000);
    //useEffect 에서 return 을 할 수 있는데 무조건 함수여야만 한다. (clean up 함수)
    //첫실행 제외 useEffect가 실행되기 전에 클린업 함수가 먼저 실행된다. (최초 실행 1번 제외)
    //또한, 이펙트를 특정한 컴포넌트가 DOM에서 언마운트될 때마다 실행된다. 즉, 컴포넌트가 재사용될 때마다,
    // 모든 새로운 사이드 이펙트 함수가 실행되기 전에 && 컴포넌트가 제거되기 전에 실행된다.

    //실행 순서를 확인해보고싶다면 return 함수에 console.log() 로 찍어보면 됩니다.
    return () => {
      console.log('클린 업!');
      //clearTimeout에 식별자를 보내서 새로운 타이머를 설정할 수 있다!
      //즉, 새로운 타이머를 설정하기 전에 마지막 타이머를 지우는 것
      clearTimeout(identifier);
    };
  }, [enteredEmail, enteredPassword]);

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

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

    // setFormIsValid(
    //   event.target.value.trim().length > 6 && enteredEmail.includes('@'),
    // );
  };
  
  //코드 중략
댓글