3월 3주차. React.js 입문 2

변현섭·2024년 3월 25일
0

다우데이타 인턴십

목록 보기
10/17
post-thumbnail

1. Hook

1) 개념

지난 포스팅에서 함수 컴포넌트와 클래스 컴포넌트를 비교하였다. 함수 컴포넌트는 코드가 간결하다는 장점이 있지만, state 관리 및 생명 주기 메서드를 사용할 수 없다는 치명적인 단점이 존재한다. 바로 이러한 단점을 개선하기 위해 사용하는 것이 Hook이다.

Hook을 사용하면, 함수 컴포넌트에서 클래스 컴포넌트의 모든 기능을 동일하게 사용할 수 있게 된다. Hook은 갈고리라는 뜻의 영어 단어인데, 이는 원하는 기능에 갈고리를 걸어 원하는 시점에 실행될 수 있도록 만든 함수라는 의미이다. Hook의 이름 앞에는 use를 붙여 명명하는 것이 관례이다.

2) useState

가장 대표적인 Hook으로, useState()가 있다. useState() 함수는 말 그대로, state를 사용하기 위한 Hook이다. 함수 컴포넌트에서는 state 관리 기능이 제공되지 않기 때문에 함수 컴포넌트에서 state를 사용하고 싶다면, useState 훅을 사용해야 한다. 아래의 예시를 보자.

/** Counter.jsx **/
import React from "react";

function Counter(props) {
    var count = 0;

    return(
        <div>
            <p>총 {count}번 클릭했습니다.</p>
            <button onClick={() => count++}>클릭</button>
        </div>
    );
}

export default Counter;

/** index.js **/
import Counter from './first_proj/Counter';
...
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <Counter />
  </React.StrictMode>
);
...

위와 같이 코드를 작성 후 리액트 앱을 실행시키면, 아무리 클릭해도 클릭 횟수가 늘어나지 않는다.

사실 count 변수가 1씩 증가하고는 있지만, 화면에 리렌더링이 안 되기 때문에 마치 count가 안 올라가는 것처럼 보이는 것이다. 리렌더링을 하기 위해선 state가 변경되어야 하기 때문에 useState 훅을 사용해야 한다. 먼저 useState() 메서드의 형식은 아래와 같다.

import React, {useState} from "react";

const [변수명, setter 메서드명] = useState(초기 값);

위 코드가 정상적으로 동작하게 하기 위해 수정한 코드는 아래와 같다.

import React, {useState} from "react";

function Counter(props) {
    const [count, setCount] = useState(0);

    return(
        <div>
            <p>총 {count}번 클릭했습니다.</p>
            <button onClick={() => setCount(count + 1)}>클릭</button>
        </div>
    );
}

export default Counter;

이제부터 React는 State 변화를 감지하여 화면을 리렌더링하게 된다.

3) useEffect

useState와 더불어 자주 사용되는 Hook 중 하나로, useEffect Hook이 있다. useEffect 훅은 Side Effect를 수행하기 위한 목적으로 사용된다. Side Effect는 어떤 함수가 input이 아닌 값을 조작할 때 발생한다. 함수가 input이 아닌 다른 요소를 조작하는 상황은 생각보다 빈번하게 발생한다. 아래의 코드를 보자.

let count = 0;

function toggle(name) {           
  conunt += 1; // Side Effect
  return `${name}님 입장`;   
}

위 코드에선 현재 함수 외부에 선언되어 있는 count 변수의 값을 조작하고 있다. 즉, Side Effect는 어떤 함수가 함수 외부에 영향력을 행사하는 상황을 가리키는 것이다. 이러한 Side Effect의 특성상, 잠재적인 위험성이 어느 정도 내포될 수 있지만, 그렇다고 해서 Side Effect를 무작정 억제하는 방식도 좋은 방법은 아니다. 따라서, 상황에 맞게 Side Effect를 활용하되, 주의 깊게 사용하는 것이 중요하다.

useEffect는 두 개의 입력인자를 받는다. 첫번째 인자는 필수 값으로 콜백 메서드이며, 두번째 인자는 선택 값으로 의존성 배열이다. 의존성 배열을 전달할 경우, 해당 의존성 배열이 변경될 때마다 useEffect의 콜백 메서드가 실행된다.

먼저 useEffect를 사용하기 위해선 아래의 import 구문을 추가해야 한다.

import React, { useRef } from 'react';

이제 useEffect의 사용법에 대해 알아보자. useEffect 훅은 아래의 3가지 형태로 사용될 수 있다.

① 렌더링될 때마다 반복

useEffect(() => {
  console.log("Hi"); // 렌더링될 때마다 Hi를 출력
}) 

② 첫번째 렌더링에서만 실행

  • 의존성 배열에 빈 배열을 전달한다.
useEffect(() => {
  console.log("Hi"); // 렌더링될 때마다 Hi를 출력
}, []) 

③ 첫번째 렌더링 및 의존성 배열의 변경을 감지할 때마다 반복

const [formData, setFormData] = useState({
	emailAddress: '',
    name: '',
    phone: ''
});

useEffect(() => {
	console.log(formData); 
}, [formData]); // formData가 업데이트될 때마다 formData를 출력

※ useState와 useEffect 비교
주로 useState는 컴포넌트의 상태 데이터를 저장하기 위한 용도로 사용되고, useEffect는 새로운 값을 갱신하기 위한 용도로 사용된다. 즉, 사용자의 클릭 횟수처럼 이전 값을 저장해두어야 할 때에는 useState()를 사용하고, API 응답처럼 이전 값을 저장해두지 않아도 될 때에는 useEffect()를 사용한다.

4) useRef

useRef 훅은 Reference를 사용하기 위한 훅이다. 여기서 Reference란, 특정 컴포넌트에 접근할 수 있는 객체를 의미한다. useRef의 사용법은 아래와 같다.

function InputTextWithButton(props) {

    // 초기 값 설정
    const inputText = useRef(null);

    const onBtnClick = () => {
        inputText.current.focus(); // 현재 참조 중인 엘리먼트에 포커싱
    };

	return (
    	<>
        	<input ref={inputText} type="text" /> // 현재 참조 중인 엘리먼트
            <button onClick={onBtnClick}>Focus to InputText</button>
        </>
    );
}

useRef를 사용하는 상황은 아래와 같다.

① 특정 DOM 요소에 접근하고자 할 때

  • 자바스크립트의 getElementById() 메서드를 사용하는 것과 유사하다.
  • 특정 DOM 요소에 포커싱을 주기 위한 용도로 자주 사용되며, useEffect와 함께 사용하여 innerText를 변경하는 등의 작업을 수행할 수도 있다.

② 데이터 저장 및 유지

  • useRef로 반환되는 객체는 컴포넌트의 생애주기 전체에 걸쳐 유지되므로, 해당 컴포넌트가 Unmount 되기 전까지 데이터가 저장된다.
  • useRef로 반환되는 컴포넌트의 current 속성이 변경되어도 리렌더링은 일어나지 않는다. 즉, 변경된 내용은 내부적으로만 저장될 뿐, 화면에 반영되지 않는다.
  • 변화에 대한 인지는 필요하지만 그 변화로 인한 렌더링은 필요하지 않을 때, useRef 훅을 사용할 수 있다. (useState를 사용할 때보다 성능이 크게 향상된다.)

2. Event Handling

1) 이벤트 리스너 등록하기

React에서 이벤트를 처리하는 방법에 대해 알아보기에 앞서 DOM의 이벤트에 대해서 알아보기로 하자. DOM에서는 클릭 이벤트에 대한 트리거 함수를 onclick 속성에 문자열 형태로 전달한다.

<button onclick="activate()">
	Activate
</button>

React에서는 이와 조금 다르게 Camel Case가 적용된 onClick을 사용하고, 문자열이 아닌 JavaScript 함수 그대로를 전달한다.

<button onClick={activate}>
	Activate
</button>

그러면 이제부터 본격적으로 이벤트 리스너를 등록하는 방법에 대해 알아보자.

import React, {useState} from "react";

function Toggle(props) {
    const [isToggleOn, setIsToggleOn] = useState(true);

    function handleClick() { // 또는 const handlClick = () => {
        setIsToggleOn((isToggleOn) => !isToggleOn);
    }

    return (
        <button onClick={handleClick}>
        	{isToggleOn ? '켜짐' : '꺼짐'}
        </button>
    );

}

export default Toggle;

2) Arguments 전달하기

이벤트 핸들러에 매개변수를 전달해야 하는 상황은 빈번히 발생한다. 함수 컴포넌트에서 이벤트 핸들러에 매개변수를 전달하는 방법을 알아보자.

return (
	<button onClick={(event) => handleDelete(1, event)}>삭제하기</button>);
}

3. List Component

1) 리스트 아이템 렌더링하기

같은 컴포넌트를 화면에 반복적으로 나타내야 할 때, JavaScript 배열의 map 함수를 사용할 수 있다. 여러 종류의 과일을 반복적으로 화면에 나타내야 하는 경우를 가정해보자. index.js에 아래의 코드를 작성한다.

...
const fruits = ['apple', 'banana', 'grapes', 'orange', 'lemon'];

// 각 과일을 리스트의 아이템으로 매핑
const listItems = fruits.map((fruit) =>
	<li>{fruit}</li>
); 

const root = ReactDOM.createRoot(document.getElementById('root'));

// 리스트 아이템들을 Ordered List로 나타냄
root.render(
  <React.StrictMode>
    <ol>{listItems}</ol>
  </React.StrictMode>
); 
...

리스트 아이템이 잘 렌더링된 것을 확인할 수 있다. 이제 F12 버튼을 눌러보자. Console을 확인해보면, 아래와 같은 경고 메시지가 나타나고 있을 것이다.

위 경고는 각 아이템에 key가 할당되지 않았음을 나타내는 경고 문구이다.

2) 리스트 아이템에 Unique Key 할당하기

Unique Key가 할당되지 않았다고 해서 에러가 발생하는 것은 아니지만, map 함수를 사용할 때에는 기본적으로 Key를 할당할 것을 권장한다.

① id를 사용하는 방식

  • 리스트 아이템이 이미 고유한 id를 가지고 있는 경우
const listItems = items.map((item) =>
	<li key={item.id]>
    	{item.name}
    </li>
);

② Index를 사용하는 방식

  • map 함수에서 두번째 파라미터로 제공해주는 인덱스를 키 값으로 사용하는 방식
  • 배열 내에서 순서가 바뀔 수 있는 경우에는 다른 방식을 사용할 것을 권장
const listItems = items.map((item, index) =>
	<li key={index]>
    	{item.name}
    </li>
);
profile
LG전자 Connected Service 1 Unit 연구원 변현섭입니다.

0개의 댓글