프로그래머스 데브코스

[React] 컴포넌트 분리, 컴포넌트 값 전달하기, useEffect, useRef 기본

hellosonic 2023. 12. 4. 18:48

🚀 들어가며

드디어 리액트 강의 주차가 되었다. 강의 첫 날에는 기초적인 것을 많이 배웠다. 리액트 기초와 useEffect, useRef 모두 어느정도 알고 있었던 내용이지만, 강의를 통해 다시 복기할 필요성을 느낀 내용만 정리해보았다.

 

 

✅ 컴포넌트 분리하기

create-react-app으로 생성한 프로젝트의 App.js 중 일부를 컴포넌트로 분리해봤다.

디렉토리 구조 - 컴포넌트로 분리했다.
App.js

  • line 2,3 : 사용할 컴포넌트를 import 키워드로 불러와야 된다.
  • line 9 : 하위 컴포넌트에 props를 전달할 수 있다. 숫자는 {} 안에 넣어서 전달하고, 문자는 "" 안에 넣어서 전달한다. 

▪︎ Logo 컴포넌트

Logo/index.js

  • line 1 : img 태그 src 속성에 사용될 로고를 불러온다.
  • line 2: 전달 받은 props의 타입을 지정해줄 수 있다. 이때, 'prop-types'로부터 import 해야 한다.

▪︎ Paragraph 컴포넌트

Paragraph/index.js

  • line 1 : 전달 받은 props의 타입을 지정하기 위해 역시 'prop-types'로부터 import 해왔다.
  • line 2 : props에는 children이라는 프로퍼티가 존재한다. children에는 태그 안의 내용이 담겨있다.
  • line 8 : 태그 안의 내용이 담긴 children 프로퍼티도 타입 지정이 가능하다. 이 때 타입은 node이고, .isRequired는 해당 프로퍼티를 반드시 전달받아야함을 의미한다.

 

✅ 자식 컴포넌트에서 부모 컴포넌트로 값 전달하기

App.js
Counter.js - 1
Counter.js - 2

  • Point : 부모 컴포넌트(App.js)와 자식 컴포넌트(Counter.js)의 상태를 따로 만들고, 자식 컴포넌트에서 이벤트 발생시 이벤트 핸들러를 통해 부모로 부터 전달 받은 props(부모의 상태를 변경시키는 핸들러)를 호출한다.

각 자식 컴포넌트가 서로 독립적이고, 부모 컴포넌트에만 의존하게 된다.

 

 

 

✅ useEffect hook 기본 활용법

useEffect 훅의 여러 예시

  1. useEffect hook은 감시 대상에 변화가 있을 경우 실행되도록 할 수 있다.
  2. useEffect hook은 처음 컴포넌트가 로드될 때 실행되도록 할 수 있다.(즉, 컴포넌트의 라이프 사이클에 활용할 수 있다.)
  3. useEffect hook은 전역으로 발생하는 이벤트를 사용할 때 쓸 수 있다.
    1. 현재 코드는 스크롤 y축을 출력하는 이벤트
    2. 전역 이벤트는 반드시 해제 해줘야하는데 useEffect hook의 return 키워드로 해제해주면 된다. (useEffect hook의 return 키워드 이후의 코드는 컴포넌트가 제거될 때 실행된다.)

✅ useRef hook 기본 활용법

▪︎ DOM 요소에 직접 접근하기

useRef 훅을 사용해서 DOM 요소에 접근할 수 있다는 것과, 이 점을 활용해서 input 태그에 focus() 처리를 할 수 있다는 것은 알고 있었다. 여기서 새롭게 알게된 내용이 하나 있다. 바로 다른 컴포넌트로 ref props를 전달해줄 수 있다는 것이다.

App.js

  • line 11 : useRef()를 통해 ref 객체를 초기화한다.
  • line 14 : Input 컴포넌트의 props로 ref 객체를 전달한다.
  • line 16 : 버튼을 클릭하면 inputRef.current(<input>)에 포커스가 된다.

components/Input.js

  • line 3 : React.forwardRef((props, ref) => { return jsx }); 함수를 활용한다. 이 때, 두 번째 파마리터로 다른 컴포넌트에서 생성한 ref 객체를 전달한다.
  • line 6 : 다른 컴포넌트(App.js)에서 버튼 클릭시 포커스되는 함수에 의해 Input 컴포넌트의 input 태그가 포커스된다.

▪︎ 지역 변수로 사용하기

useRef hook을 지역 변수로 사용할 수 있다. 예를 들어, setInterval 함수의 타이머 id를 useRef hook을 사용하여 지역 변수에 할당할 수 있다.

AutoCounter.js

  • line 5 : 빈 ref 객체를 갖는 intervalId 지역 변수를 초기화한다.
  • line 7 : Start 버튼 클릭 시 실행되는 이벤트 핸들러. 1초마다 count가 1씩 증가하는 setInterval 함수의 타이머 id를 ref 객체에 할당했다.
  • line 13 : ref 객체에 할당된 타이머를 clear한다.

또 중요한 것은 useState와 useRef의 차이점은 값이 변경될 때 렌더링 여부이다.

  • useState : 값이 변경되면 컴포넌트가 다시 렌더링된다.
  • useRef : 값이 변경되더라도 컴포넌트가 다시 렌더링되지 않는다.

이것에 대해 정확하게 체감되지 않았는데 아래의 블로그 포스팅을 보고 이해할 수 있었다.

 

useRef의 새로운 발견 (useState와 비교)

컴포넌트는 자신의 상태값이 변경되거나, 부모로 부터 받은 인자값이 변경되었을 때 새로 렌더링된다. 심지어 useMemo나 useCallback, React.memo 등 최적화함수가 적소에 사용되지 않은 경우 부모로부

velog.io

아래의 코드는 이메일 입력 후 회원가입 버튼 클릭 시 입력한 이메일이 결과에 표시되는 코드이다.

import { useState, useCallback } from 'react';

export default function SignUp() {
  const [mail, setMail] = useState('');
  const [result, setResult] = useState('');

  console.log('렌더가 된다.');

  const onSubmit = useCallback((e) => {
    e.preventDefault();
    setResult(result);
    // 최종적으로 onSubmit 이벤트(제출) 발생 시 useState 훅이 사용됨.
    // -> 컴포넌트 리렌더링
  }, []);

  return (
    <div id="container">
      <form onSubmit={onSubmit}>
        <label id="email-label">
          <span>이메일 주소</span>
          <div>
            <input
              type="email"
              id="email"
              name="email"
              value={mail}
              onChange={(e) => setMail(e.target.value)}
              // input 태그에 onChange 이벤트가 발생하면 useState 훅이 사용됨
              // -> 컴포넌트 리렌더링
            />
          </div>
        </label>
        <span>결과: {result}</span>
        <button type="submit">회원가입</button>
      </form>
    </div>
  );
}

위 코드의 경우 useState만 사용했을 경우이다. input 태그의 onChange 이벤트 발생 시 상태값을 변경시키게 되고 불필요한 컴포넌트 리렌더링이 계속해서 발생하게 된다. 

이를 해결하기 위해 useRef를 사용할 수 있다.

import { useState, useCallback, useRef } from 'react';

export default function SignUp() {
  const mailRef = useRef(null);
  const [mail, setMail] = useState('');

  console.log('렌더가 된다.');

  const onSubmit = useCallback((e) => {
    e.preventDefault();
    const { current } = mailRef; // ref 객체에 저장된 값을 사용한다.
    if (!current) return;
    setMail(current.value);
    // 최종적으로 ref 객체에 저장된 current(input 태그의 value)를 한 번만 사용하면서 한 번만 리렌더링된다.
  }, []);

  return (
    <div id="container">
      <form onSubmit={onSubmit}>
        <label id="email-label">
          <span>이메일 주소</span>
          <div>
            <input type="email" id="email" name="email" ref={mailRef} />
            {/* 객체의 값이 변경되어도 리렌더링 되지 않는다. */}
          </div>
        </label>
        <span>결과: {mail}</span>
        <button type="submit">회원가입</button>
      </form>
    </div>
  );
}

input 태그의 값이 변경될 때 ref 객체의 값만 변경이 되므로 리렌더링이 되지 않고 있다가 최종 결과 onSubmit 이벤트 발생시 한 번만 리렌더링 된다.

 

 

🛸 마치며

이번 강의를 통해 리액트 기본기를 점검할 수 있었고, 특히 useEffect, useRef의 간단한 예시를 보면서 더 깊게 이해할 수 있는 계기가 되었다. 모던 리액트 Deep Dive 책으로 리액트 훅에 대해 더욱 공부하고 앞으로 잘 활용할 수 있도록 노력해야겠다.