이벤트 헨들링

Posted by yunki kim on June 21, 2021

  리엑트의 이벤트 시스템은 웹 브라우저의 HTML 이벤트 인터페이스와 동일하기 때문에 사용법이 비슷하다. 다만 다음과 같은 주의 사항이 존재한다.

  1. 이벤트 이름은 카멜 표기법으로 표기한다

  2. 이벤트에 실행할 js 코드를 전달하는 것이 아닌 함수 형태의 값을 전달한다.

    HTML에서 이벤트를 설정할 때는 큰 따옴표 안에 실행할 코드를 넣었지만 리엑트에서는 함수 형태의 객체를 전달한다

  3. DOM요소에만 이벤트를 설정할 수 있다

    직접만든 컴포넌트에는 이벤트를 자체적으로 설정할 수 없다. 만약 직접 만든 컴포넌트에 onClick={sth}이라는 클릭 이벤트를 설정한다면 이는 이름이 onClick인 props가 된다. 하지만 전달 받은 props를 컴포넌트 내부의 DOM이벤트로 설정할 수는 있다.

리엑트 이벤트 메뉴얼: https://reactjs.org/docs/events.html

 

SyntheticEvent – React

A JavaScript library for building user interfaces

reactjs.org

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, {Component} from 'react';
 
class ClassComponentEnvet extends Component {
    render() {
        return (
            <div>
                <h1>practice event</h1>
                <input type="text"
                       name="message"
                       placeholder="input anything"
                       onChange={
                           (e) => {
                               console.log(e);
                           }
                       }
                />
            </div>
        )
    }
}
 
export default ClassComponentEnvet;
cs

  위 코드를 실행하면 다음과 같은 결과가 출력된다

  여기서 콘솔레 출력되는 e객체는 SyntheticEvent로 웹 브라우저의 네이티브 이벤트를 감싸는 객체이다. 네이티브 이벤트와 인터페이스가 같으므로 바닐라에서 HTML 이벤트를 다룰때와 똑같이 사용하면 된다. SyntheticEvent는 네이티브 이벤트와 달리 이벤트가 끝나면 이벤트 가 초기화 되므로 정보를 참조할 수 없다. 

합성 이벤트(Synthetic Event)

  브라우저마다 이벤트 이름, 종류, 처리 방식이 다 다르다. 이를 동일하게 처리하기 위해 리엑트는 합성 이벤트로 브라우저  마다 다른 네이팁브 이벤트를 감싸서 처리한다. 즉, stopPropagation(), preventDefault()를 포함하는 인터페이스는 브라우저의 고유 이벤트와 같지만 모든 브라우저에서 동일하게 동작 된다. 

  합성 이벤트는 다음과 같은 attribute를 가진다.

마우스 이벤트에 대한 레퍼런스:https://ko.reactjs.org/docs/events.html#mouse-events

 

합성 이벤트(SyntheticEvent) – React

A JavaScript library for building user interfaces

ko.reactjs.org

  합성 이벤트는 리엑트 v16까지는 object pooling 방식을 사용했다. 그때문에 합성 이벤트는 네이티브 이벤트와 달리 이벤트가 끝나면 이벤트가 초기화 됬기 때문에 비동기적으로 객체를 참조하려면 persist()를 사용해야 됬다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
onChange={
  (e) => {
    setTimeout(() => {
      //console.warn(e.type);
      console.log(e.type);//null출력
    }, 500);
  }
}
//Warning: This synthetic event is reused for performance reasponse. 출력됨
 
onChange={
  e.persist();
  (e) => {
      setTimeout(() => {
        //console.warn(e.type);
        console.log(e.type);//change 출력
      }, 500);
  }
}
cs

  하지만 리엑트 v17이후 더이상 object pooling을 사용하지 않는다. 리엑트가 object pooling을 사용했던 이유는 구식 브라우저에서의 퍼포먼스 향상을 위해서 였다. 하지만 현대의 브라우저는 더이상 object pooling으로 인한 퍼포먼스 향상을 기대하기 어렵고 리엑트 유저에게 혼란을 야기한다. 따라서 persist()는 여전히 존재 하지만 더이상 쓸모 없게 되었다.

 

state에 input값 담기

  state에 초기값을 설정 하고, 이벤트 핸들링 함수 내부에서 this.setState()메서드를 호출해 state을 업데이트 한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import Rect, {Component} from 'react';
 
class ClassComponentEnvet extends Component {
    state = {
        message: '',
    };
 
    render() {
        return (
            <div>
                <h1>practice event</h1>
                <input type="text"
                       name="message"
                       placeholder="input anything"
                       value={this.state.message}
                       onChange={
                           async (e) => {
                           //setState()는 비동기 이므로 await 또는 callback을 사용해
                           //console.log()를 해야 원하는 값을 출력할 수 있다
                               await this.setState({
                                   message: e.target.value
                               });
                               console.log(this.state.message);
                           }
                       }
                />
            </div>
        );
    }
}
 
export default ClassComponentEnvet;
cs

 

임의의 메서드 만들기

  리엑트는 이벤트에 실행할 js코드를 전달하는 것이 아닌, 함수 형태의 값을 전달 한다. 따라서 이벤트를 렌더링 하는 동시에 함수를 만드렁 전달해 준다. 하지만 이 방식 대신 함수를 미리 정의 해서 전달해 주어도 된다. 이는 가독성을 높인다.

   함수가 호출 될때 this는 호출부에 따라 결정 된다. 따라서 클래스의 임의 메서드가 특정 HTML요소의 이벤트로 등록되는 과정에서 메서드와 this의 관계가 끊어진다. 따라서 임의 메서드가 이벤트로 등록 되도 this를 컴포넌트 자신으로 제대로 가르키기 위해 바인딩을 해야 하면 이는 생성자에서 한다.

    만약 생성자에서 바인딩을 하고 싶지 않다면 바벨의 transfor-class-properties문법을 사용해서 화살표 함수 형태로 메서드를 정의해도 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import React, {Component} from 'react';
 
class ClassComponentEnvet extends Component {
    state = {
        message: '',
    }
 
    constructor(props) {
        super(props);
        this.handleChange = this.handleChange.bind(this);
        this.handleClick = this.handleClick.bind(this);
    }
 
    handleChange(e) {
        this.setState({
            message: e.target.value,
        });
    }
 
    handleClick(e) {
        alert(this.state.message);
        this.setState({
            message: '',
        });
    }
 
    render() {
        return (
            <div>
                <h1>practice event</h1>
                <input type="text"
                       name="message"
                       placeholder="input anything"
                       value={this.state.message}
                       onChange={this.handleChange}
                />
                <button
                    onClick={this.handleClick}
                >확인</button>
            </div>
        );
    }
}
 
export default ClassComponentEnvet;
 
//화살표 함수:
//위 코드에서 생성자를 없애고, handleChange, handleClick을 다음과 같이 바꾼다
handleChnage = (e) => {
  this.setState({
      message: e.target.value,
  });
}
 
handleClick = (e) => {
  alert(this.state.message);
  this.setState({
    message: '',
  });
}
cs

 

input 여러개 다루기

  event객체를 활용하면 여러개의 인풋에 대해 각자 메서드를 만들 필요가 없다. event.target.name은 해당 인풋의 name property을 가리킨다. 이 값을 사용해서 state를 설정하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import React, {Component} from 'react';
 
class ClassComponentEnvet extends Component {
    state = {
        username: '',
        message: '',
    }
 
    handleChange = (e) => {
        this.setState({
            //e.target.name은 해당 인풋의 name property
            //객체 안에서 key를 []로 감싸면 그 안에 넣은 레퍼런스가 가리키는 실제 값이 key값으로 사용된다.
            [e.target.name]: e.target.value
        });
    }
 
    handleClick = (e) => {
        alert(`${this.state.username}: ${this.state.message}`);
        this.setState({
            username: '',
            message: '',
        });
    }
 
    render(){
        return (
            <div>
                <h1>practice event</h1>
                <input
                    type="text"
                    name="username"
                    placeholder="user name"
                    value={this.state.username}
                    onChange={this.handleChange}
                />
                <input type="text"
                       name="message"
                       placeholder="input anything"
                       value={this.state.message}
                       onChange={this.handleChange}
                />
                <button onClick={this.handleClick}>확인</button>
            </div>
        );
    }
}
 
export default ClassComponentEnvet;
cs

함수형 컴포넌트에의 이벤트 헨들링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import React, {useState} from 'react';
 
const EventPractice = () => {
    const [username, setUsername] = useState('');
    const [message, setMessage] = useState('');
    const onChangeUsername = (e) => setUsername(e.target.value);
    const onChangeMessage = (e) => setMessage(e.target.value);
 
    const onClick = () => {
        alert(`${username}: ${message}`);
        setUsername('');
        setMessage('');
    }
 
    const onKeyPress = (e) => {
        if(e.key === 'Enter'){
            onClick();
        }
    };
 
    return (
        <div>
            <h1>Practice Event</h1>
            <input type="text"
                   name="username"
                   placeholder="username"
                   value={username}
                   onChange={onChangeUsername}
            />
            <input type="text"
                   name="message"
                   placeholder={"input anything"}
                   value={message}
                   onChange={onChangeMessage}
                   onKeyPress={onKeyPress}
            />
            <button onClick={onClick}>confirm</button>
        </div>
    )
}
 
export default EventPractice;
 
 
//또는
const EventPractice = () => {
    const [form, setForm] = useState({
        username: '',
        message: '',
    });
    const {username, message} = form;
    const onChange = (e) => {
        const nextForm = {
            ...form,//기존의 form 내용을 이 자리에 복사하고
            [e.target.name]: e.target.value//원하는 값을 덮어 씌운다
        };
        setForm(nextForm);
    };
 
    const onClick = () => {
        alert(`${username}: ${message}`);
        setForm({
            username: '',
            message: ''
        });
    };
    ......
    //위의 input에서: onChange={onChange}로 대체
}
cs