💻 Frontend/React

[코딩온] 프론트엔드 입문 Day 42 (React event, ref)

hjinn0813 2024. 4. 13. 11:40
728x90

이 날은 React에서 event, ref에 대해 배웠다. 제대로 이해된 부분도 있고 그렇지 못한 부분도 있어서, 블로그에 정리하면서 완전한 내 것으로 소화시키려고 한다.


React - Event Handling

이벤트 핸들링(Event Handling)은 요소에 특정 동작이 실행되도록 하는 것을 의미한다. HTML에서는 속성으로 함수를 연결해서 사용했었는데, React에서 이벤트 핸들링을 사용하려면 아래의 특징들을 주의해야 한다.

  • 속성명을 카멜 케이스(camelCase)로 작성한다.
  • JSX 문법으로 함수 이름만 불러오면 React가 알아서 찾아와서 실행시킨다. (소괄호 입력 금지)
  • input, btn 등 기본 DOM 요소에만 설정할 수 있다. 컴포넌트에 이벤트를 걸 수는 없지만, props를 구조분해하여 정의를 내릴 때 onClick을 같이 보내주면 가능하다.

Synthetic Event(합성 이벤트)는 브라우저 이벤트를 포함하는 React 고유의 이벤트 객체이다. React는 브라우저의 nativeEvent를 감싼 '합성 이벤트'를 사용하며, 모든 브라우저에서 동일하게 이벤트를 처리하기 위해 wrapper 객체를 갖고 있다. 텍스트 설명보다 수업시간에 사용했던 예시 코드들을 보는게 이해가 빠를 것 같다.👀


함수형 컴포넌트의 이벤트

// 함수형 컴포넌트의 이벤트
// 1. 클릭 이벤트로 실행될 함수를 onClick에 전달

import { useState } from 'react';

export default function FuncEvent() {
  const [msg, setMsg] = useState('힘내세요!!');
  const msgToEng = () => {
    setMsg('fighting~');
  };
  const msgToKor = () => {
    setMsg('힘내세요!!');
  };

  return (
    <>
      <div>{msg}</div>
      <button onClick={msgToEng}>fighting~</button>
      <button onClick={msgToKor}>힘내세요!!</button>
    </>
  );
}

어쩌면 위 코드가 함수형 컴포넌트에서 가장 보편적으로 사용하는 이벤트 핸들링 방법일 것 같은데,

state를 설정하고, 클릭 이벤트로 실행될 함수를 만들어서 onClick으로 연결하는 방법이다.

주의할 점은, 여기서 onClick에 연결하면서 함수를 호출하면 안 된다.


// 함수형 컴포넌트의 이벤트
// 2. 함수에 매개변수를 전달하는 경우

import { useState } from 'react';

export default function FuncEvent() {
  const alertMsg = (msg) => {
    alert(msg);
  };
  
  return (
    <>
      <button onClick={() => alertMsg('오늘은 금요일!')}>
        메시지 alert (익명함수 선언)
      </button>
      <br />

      <button onClick={alertMsg.bind(null, '끝까지 불태워봅시다!')}>
        메시지 alert (bind 메서드)
      </button>
    </>
  );
}

함수형 컴포넌트에서 이벤트 핸들링할 때, 함수에 매개변수를 전달해야하는 경우에는 어떻게 작성하는지도 실험해봤다.

  • onClick 안에서 바로 익명함수를 선언하여 그 안에서 실행시키는 방법
    → 인자가 없으면 함수 이름만 불러오면 되지만, 인자가 있다면 익명의 화살표함수에 인자를 담아서 같이 전달한다.
  • onClick에 연결할 함수를 bind 메서드로 묶어서 전달하는 방법
    → bind()의 앞에 들어오는 인자는 this, 뒤에 들어오는건 bind 앞에 체이닝된 함수에 들어가는 것이다.
💡 여기서 잠깐!
bind()는 함수처럼 호출 가능한 '특수 객체'를 반환한다. 예를 들어,

function getBinding() {
  return this;
}
let panda = 'fubao';
console.log(getBinding.bind(panda));
console.log(getBinding.bind(panda)());

이렇게 작성한다면 첫번째 콘솔은 bind 메서드의 인자로 panda가 들어가서 새롭게 정의된 getBinding 함수가 찍힌다. bind는 함수를 호출하지 않기 때문에 원하는 결과를 얻으려면 소괄호를 한번 더 입력해서 확실하게 호출해야 한다. (혹은 아래 사진처럼 다른 변수에 담아서, 변수를 함수로 호출하는 방법도 있다.)

★ bind() 메서드 설명 참고 - https://ko.javascript.info/bind


// 함수형 컴포넌트의 이벤트
// 3. input에서의 이벤트 핸들링

import { useState } from 'react';

export default function FuncEvent() {
  const [menu, setMenu] = useState('');
  
  const handleEnter = (e) => {
    console.log(e);
    if (e.key === 'enter') {
      alert(`점심 메뉴는 ${menu}로 결정!`);
    }
  };

  return (
    <>
      <input type="text" value={menu}
        placeholder="오늘 점심 뭐 먹지?"
        onChange={(e) => {setMenu(e.target.value);}}
        onKeyDown={handleEnter} />
    </>
  );
}
  • input에서 변경사항이 발생되면 해당 input의 value를 onChange를 통해 콘솔에 출력한다. 
  • input에서 키보드의 Enter 키를 누르면 입력한 값을 alert로 보여주는 설정은 onKeyDown 속성으로 함수를 연결했다.
  • handleEnter 함수에서 사용된 매개변수 e는 React의 synthetic 이벤트 객체를, if문에 들어있는 key는 해당 이벤트가 발생하기 위해 눌려진 키보드 키를 의미한다. → key의 값으로 일반 문자는 process(한글)나 알파벳(영어), enter는 enter를 가지고 있다.


클래스형 컴포넌트의 이벤트

// 클래스형 컴포넌트의 이벤트

import { Component } from 'react';

export default class ClassEvent extends Component {
  constructor(props) {
    super(props);

    this.handleClick = this.handleClick.bind(this);
    // handleClick 함수의 this를 클래스 자체를 의미하는 this로 bind
  }

  state = {
    msg: '즐거운 금요일!',
  };
  
  // 함수 선언문 사용하여 메서드를 정의
  // 함수 내부의 this는 클래스의 this와 다른 자체적인 this
  handleClick() {
    this.setState({ msg: '주말은 짧아 ㅠㅠ' });
  }

  render() {
    return (
      <>
        <h2>클래스형 컴포넌트에서의 이벤트</h2>
        <div>{this.state.msg}</div>
        <button onClick={this.handleClick}>변경하기!</button>
        <br />
        <input type="text"
          onChange={(e) => {
            console.log(e);
            console.log(e.target.value);
          }}
        />
      </>
    );
  }
}

클래스형 컴포넌트와 state를 만들고, 버튼에 onClick으로 handleClick 함수를 연결하여 클릭 이벤트가 발생하면 state가 변경되도록 했다. 여기서 this 키워드를 많이 사용했는데, 어디에서 사용했는지에 따라 의미가 다르다.

handleClick() 내부의 this는 함수 자체를 의미하지만, 생성자 함수 내부의 this는 bind() 메서드를 사용하여 클래스 자체를 의미하는 this가 되었다. (생성자함수에서 bind를 안 하면 this가 함수 자체를 의미하게 되어 오류 발생)


React - ref

React는 Virtual DOM을 사용해서 원래 요소와 업데이트된 요소를 비교하기 때문에 되도록이면 DOM 요소를 건드리지 않는게 좋지만, 특정 요소에 효과를 준다거나 하는 등 부득이하게 직접 건드려야하는 경우에는 ref를 사용한다. (reference의 약자)

가장 큰 특징은, ref의 값이 변경되어도 해당 컴포넌트가 다시 렌더링되지 않는다는 것이다.

클래스형 컴포넌트에서만 기본 기능으로 제공되고, 함수형 컴포넌트에서는 useRef를 import해서 사용한다.


함수형 컴포넌트의 ref

// 함수형 컴포넌트의 ref
// 1. DOM 요소 조작하기

import { useRef } from 'react';

export default function FuncRef() {
  const input = useRef();
  const focusInput = () => {
    console.log(input);
    console.log(input.current);
    input.current.focus();
  };

  return (
    <>
      <input type="text" ref={input} />
      <button onClick={focusInput}>버튼</button>
    </>
  );
}
  • 함수형 컴포넌트라서 useRef를 import하여, 앞으로 ref 기능을 사용하겠다고 명시한다.
  • input에 useRef()를 담아서 ref 객체를 만들고, input 태그에 ref 속성으로 연결한다.
  • useRef()를 담은 input을 콘솔에 찍으면 ref 객체가 {current : input}이라는 속성과 값을 가지고 있다는 것을 알 수 있다.
  • 그래서 input.current를 콘솔에 찍으면 ref를 통해 선택한 DOM 요소를 확인할 수 있다.
  • ref로 선택한 DOM 요소(input 태그)에 포커스 이벤트를 주는 focusInput 함수를 onClick으로 버튼에 연결하면, 버튼 클릭시 input 태그에 포커스가 된다.

// 함수형 컴포넌트의 ref
// 2. ref를 변수로 사용하기

import { useRef, useState } from 'react';

export default function FuncRef() {
  const localVar = useRef(0);
  const [state, setState] = useState(0);
  let justVar = 0;

  const increLocalVar = () => {
    localVar.current++;
    console.log(localVar.current);
  };

  const increJustVar = () => {
    justVar++;
    console.log(justVar.current);
  };

  return (
    <>
      <h2>localVar.current: {localVar.current}</h2>
      <h2>state: {state}</h2>
      <h2>justVar: {justVar}</h2>
      <button onClick={increLocalVar}>localVar +1</button>
      <button onClick={() => setState(state + 1)}>state +1</button>
      <button onClick={increJustVar}>justVar +1</button>
    </>
  );
}
  • 여기서는 useRef와 useState를 import하여, 앞으로 사용될 기능을 명시했다.
  • localVar에 useRef를 담고 초기값으로 0을 주었다.
    state, setState를 구조분해하여 useState에 담고, 초기값은 0으로 설정.
    justVar 역시 초기값을 0으로 설정했다.
  • 버튼을 클릭하면 값이 증가하는 이벤트를 위해서 increLocalVar(), increJustVar() 함수를 만들고 onClick으로 버튼 태그에 연결했다. state 버튼은 익명 화살표함수로 setState를 통해 들어갈 값을 설정했다.
  • 브라우저를 열어서 확인해보면,
    state는 즉시 렌더링되어 버튼 클릭하면 값이 변하는걸 실시간으로 볼 수 있다.
    localVar는 ref를 사용했기 때문에 값이 변하는걸 바로 확인할 수는 없지만, 값의 변경은 일어나고 있어서 state가 업데이트되면 그동안 변경된 값이 한번에 보여진다.
    justVar는 increJustVar() 함수에서 current로 접근하지 않아서, 버튼을 클릭하면 콘솔에서 undefined가 출력된다.

클래스형 컴포넌트의 ref

// 클래스형 컴포넌트의 ref

import { Component, createRef } from 'react';

export default class ClassRef extends Component {
  input = createRef();
  localVar = createRef();
  state = {
    state: 0,
  };

  render() {
    return (
      <>
        <input type="text" ref={this.input} />
        <button onClick={() => this.input.current.focus()}>버튼</button>
        <hr />
        <div>this.localVar.current: {this.localVar.current}</div>
        <div>this.state.state: {this.state.state}</div>
        <button onClick={() => this.localVar.current++}>localVar + 1</button>
        <button onClick={() => this.setState({ state: this.state.state + 1 })}>
          state + 1
        </button>
      </>
    );
  }
}

함수형 컴포넌트가 ref 기능의 사용을 위해 useRef를 사용한다면, 클래스형은 createRef를 사용한다.

  • 클래스형이니까 컴포넌트와 createRef부터 import하고, input과 localVar에 createRef()를 담았다.
  • input 태그는 ref 속성으로 연결했고, localVar는 this.localVar.current로 원하는 요소를 선택했다.
  • state는 초기값으로 0을 설정했다.
  • input 태그는 버튼을 클릭하면 입력창에 포커스가 되는 효과를 위해서 onClick으로 익명 화살표 함수를 불러와서, this.input.current로 요소를 콕 집어서 focus()를 실행시켰다.
  • localVar와 state는 여기에서도 마찬가지로, state는 실시간으로 값이 변하는걸 확인할 수 있지만, localVar는 값의 변경이 뒤에서 일어나다가 state가 업데이트되면 그동안 누적된 변경이 한번에 보여진다.
💡 결론 
변수는 렌더링 될 때마다 초기화된다.

state는 값이 변경되면 다시 렌더링되어, 변경되는 값이 실시간으로 보여진다.
ref는 값에 변경이 있어도 다시 렌더링되지 않아서, 변경되는 값을 바로 확인할 수 없다.

여기까지가 이 날 배운 내용이었다. 수업시간마다 배우는 내용을 현장에서는 50~60% 정도만 이해하고, 집에 돌아와 블로그에 정리하면서 더욱 확실히 이해하게 되는 것 같다. 진도를 나갈수록 결코 만만하지 않다는걸 느끼고 있지만, JS가 그랬듯이 React도 꾸준히 연구하다보면 지금보다 더 친해질 수 있겠지!🙂

728x90