[React] React 이벤트 핸들링

WONNY_LOG·2023년 6월 29일

STUDY

목록 보기
5/6

React에서 이벤트 처리방식 ?

DOM 엘리먼트에서 이벤트를 처리하는 방식과유사하다.

html에서와의 차이점 ?

  • React의 이벤트 이름은 카멜 케이스(camelCase)를 사용한다.
  • 이벤트에는 JSX를 사용하여 문자열이 아닌 함수로 값을 전달한다.
  • DOM 요소에만 이벤트를 설정할 수 있다. 리액트 컴포넌트에는 prop를 전달해주기만하지 이벤트가 실행되진 않는다

ex) HTML

<button onclick="activateLasers()">Activate Lasers</button>

ex) React

<button onClick={activateLasers}>Activate Lasers</button>

React 합성 이벤트

이벤트 핸들러는 모든 브라우저에서 이벤트를 동일하게 처리하기 위한 이벤트 래퍼 SyntheticEvent 객체를 전달받는다

  • 브라우저마다 이벤트 이름부터 시작해서 이벤트 종류나 이벤트가 처리되는 방식이 다르다.
    이를 동일하게 처리하기 위해 React는 Synthetic 이벤트로 브라우저마다 다른 native 이벤트를 묶어서 처리한다. (크로스브라우징 문제 해결)
  • stopPropagation() 와 preventDefault()를 포함해서 인터페이스는 브라우저의 고유 이벤트와 같지만 모든 브라우저에서 동일하게 동작한다.
  • 리액트에서 전달되는 event 객체는 W3C 명세에 따라 SyntheticEvent를 정의하기 때문에 순수 자바스크립트에서 사용하는 것과 동일하게 사용하면 된다.

컴포넌트에서의 이벤트 핸들링방법

Event Listener 등록과 Event Handler 호출

17이하 버전에서는 컴포넌트에 상위 document에 Listener를 붙였지만,React 17부터는 document가 아닌 Native Event를 Listen하고, 그 이벤트에 따라 ‘알맞는 타겟의 핸들러를 찾아서 실행시키는’ 핸들러를 포함한 Lisnener를 root에 등록시킨다

React는 최상위 부모인 document에 이벤트 리스너를 연결하였다. 이벤트 리스너를 각 요소(div)가 아닌 document에 연결하는 것이다. 이 덕분에 React는 좀 더 빠르게 동작하게 된다.

  1. Event Listener가 이벤트가 발생하는지 계속 쳐다보고있음
  2. 이벤트가 발생하면 DOM에 미리 등록해 둔 Event Handler 호출
  3. Event Handler는 발생된 이벤트에 맞는 적절한 메서드를 실행시킴

클래스형 컴포넌트

이벤트 핸들러를 클래스의 메서드로 만들고 'this'바인딩을 통해 이를 컴포넌트 내에서 메서드를 사용할 수 있다.

class TestComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isTestOn: true};
    //콜백에서 `this`가 작동하려면 바인딩을 해줘야한다
    this.handleClick = this.handleClick.bind(this); 
  }

  handleClick() {
    this.setState(state => ({
      isTestOn: !state.isTestOn
    }));
  }

  render() {
    return (
      //render() 함수 안에서 this 값은 render() 함수가 속한 컴포넌트를 가리킴
      <button onClick={this.handleClick}>
        {this.state.isTestOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}
  • JS의 클래스 메서드는 기본적으로 바인딩되어있지 않기 때문에 this.handleClick을 바인딩하지 않고 호출하면 this는 undefined가 된다. 따라서 바인딩해주고 사용해야한다.
  • 즉, binding을 따로 지정해주지 않으면 해당 메서드에서 호출하는 this가 해당 클래스안에서의 event값이 아닌 최상위의 window에서 값을 가져올 수 있다.

함수형 컴포넌트

이벤트 핸들러를 const 키워드 + 함수 형태로 선언해서 사용해야한다.

import React, {useState} from "react";

function NumberTest() {
  const [num, setNumber] = useState(0);

  const increase = () => {
    setNumber(num+1);
  }

  return (
    <div>
      <h1>Number Test</h1>
      <h2>{num}</h2>
      <button onClick={increase}>증가</button>
    </div>
  );
}

export default NumberTest;

함수형 컴포넌트 자체와 함수형 컴포넌트 안에서 선언한 함수들 모두 전역 객체를 this로 가지기 때문에 애초에 this가 다 같다.

리액트의 성능 최적화

컴포넌트가 렌더링되는 조건들을 생각해보면됨!

  1. useMemo

    리턴값을 memoize 하는데 사용된다

  const average = useMemo(() => {
    return users.reduce((acc, cur) => {
      return acc + cur.score / users.length;
    }, 0);
  }, [users]);

특징
- React에서 CPU 소모가 심한 함수들을 캐싱하기 위해 사용된다
- 종속 변수들이 변하지 않으면 함수를 굳이 다시 호출하지 않고 이전에 반환한 참조값을 재사용 한다.

장점
함수 호출 시간도 세이브할 수 있고 같은 값을 props로 받는 하위 컴포넌트의 리렌더링도 방지할 수 있다

  1. React.memo 컴포넌트 메모이제이션 (shouldComponentUpdate)

    컴포넌트의 props가 바뀌지 않았다면, 리렌더링하지 않도록 설정한다.

//부모 컴포넌트
{users.map((user) => {
        return (<Item key={user.id} user={user} /> );
      })}
      
//자식컴포넌트
import React,{ memo } from "react";

function Item({ user }) {
  return (<div className="item">이름: {user.name}</div>);
}

export default memo(Item);

특징
- 클래스형 컴포넌트와 함수형컴포넌트 둘다 사용 가능
- 함수형 컴포넌트에서 shouldComponentUpdate의 대안책

장점
UserList에 리렌더링 시키더라도 새로 추가된 Item만 새로 렌더되고 이미 렌더된 Item들은 리렌더링 되지 않는다.

  1. useCallback

    함수 선언을 memoize 하는데 사용된다.

const addUser = useCallback(() => {
    setUsers([
      {
        id: 2,
        age: 30,
        score: 90,
      },
      ...users,
    ]);
  }, [users]);

특징
UserList > Button 구조로 Button의 props로 addUser함수를 내려줄때
UserList가 리렌더될때마다 addUser함수를 새로 생성하고 Button의 props로 전달한다.
그럼 아무리 Button에 메모이제이션을 적용해도 덩달아 리렌더링된다.
왜냐하면 함수는 객체이고 새로 생성된 함수는 다른 참조 값을 가지기 때문.
그래서 UserList가 리렌더될때마다 addUser함수를 재생성하는 것을 막고자useCallback을 사용한다.

장점
함수를 props받는 하위 컴포넌트 불필요한 리렌더 방지

  1. props로 객체를 넘겨줄 경우 새로 만들지 말고 그대로 넘겨주기

    동일한 메모리를 가지고 있는 props내려주기

// 생성자 함수
<Component props={new Obj("x")} />
// 객체 리터럴
<Component props={{property: "x"}} />
  1. 컴포넌트를 매핑할 때에는 key값으로 index를 사용하지 않는다.

    배열의 중간에 어떤 요소가 삽입되면 index값이 바뀌고 그로인해 key값이 변경되면 리마운트가 일어나게된다.

특징
- 데이터가 key와 매치가 안되어 서로 꼬이는 부작용도 발생한다.
- key값에 고유 id를 넣어주면, 배열의 중간에 어떤 요소가 삽입되더라도 기존에 있는 원소들이 가지고 있는 key가 끊어질 위험이 없다.

  1. state
    • 가장 상위 컴포넌트에 선언하여 사용하기
    • 객체 타입의 state는 최대한 분할하여 선언하기
  1. Input에 onChange 최적화

    setTimeout통해 일정 시간 간격뒤 searchRef값 업데이트

 <input
    ref={searchRef}
    onKeyUp={() => {
         let searchQuery = searchRef.current.value.toLowerCase();
         setTimeout(() => {
         if (searchQuery === searchRef.current.value.toLowerCase()) {
         setText(searchQuery);
              }
            }, 400);
          }}
        />
  1. Lazy Loading

    React.lazy는 코드분할을 하게 해준다.
    앱의 코드 양을 줄이지 않고도 사용자가 필요하지 않은 코드를 불러오지 않게 하며 앱의 초기화 로딩에 필요한 비용을 줄여준다.

//부모 컴포넌트
{users.map((user) => {
        return (<Item key={user.id} user={user} /> );
      })}
      
//자식컴포넌트
import React,{ memo } from "react";

function Item({ user }) {
  return (<div className="item">이름: {user.name}</div>);
}

export default memo(Item);

특징
- React에서 component를 lazy load를 이용하기 위해 React.lazy() API를 사용한다.
- React.lazy 함수는 동적 import를 사용하며 일반 component처럼 렌더링할 수 있게 해준다.

장점
코드분할을 통해 lazy 컴포넌트가 로드되길 기다리는 동안 로딩 화면과 같은 예비 컨텐츠를 보여줄 수 있게 해준다.
아직 서버 사이드 렌더링을 하지 못 해서 서버사이드 렌더링을 위해서는 Loadable Components를 추천한다고 한다.

  1. webpack

    서로 의존성 있는 여러파일들을 번들 파일로 묶어 코드 스플릿팅을 시킴

  2. 긴 목록 가상화

    윈도잉 (windowing)기술 사용

주어진 시간 내에 행의 작은 부분만 렌더링하므로 컴포넌트를 다시 렌더링하는 데 걸리는 시간과 생성된 DOM 노드 갯수를 크게 줄일 수 있다.















Event Handler: 이벤트에 맞춰 적절한 처리를 담아둔 메서드 집합
실행컨텍스트


리액트에서 이벤트 처리방식
리액트의 이벤트리스너 등록
리액트의 event 처리 동작
Event Listener 등록과 Event Handler 호출
버블링과 캡쳐링
렌더링 성능 최적화하는 7가지 방법

0개의 댓글