기본적인 이벤트 핸들링에 대한 내용이다. state를 다루는 방법과 value를 관리하는 법에 집중해봤다.
HTML에서의 이벤트는 아래처럼 실행할 코드를 따옴표 안에 직접 작성하는 식이었다.
<button onclick="funcName()">btn</button>
리액트는 객체형태로 함수를 전달한다. 인라인으로 함수를 작성해도 되고, 따로 작성해둔 함수를 전달해도 된다.
const App = () =>{
function foo(){
alert('test');
}
return(
<>
<button onClick={foo}>BTN</button>
<button onClick={()=>alert('test2')}>btn</button>
</>
);
}
export default App;
위처럼 중괄호로 감싸 함수를 전달하면 이벤트 함수를 호출할 수 있다. 또 하나 차이가 있다면 이벤트 이름을 카멜표기법으로 쓴다는 것. 가령 onclick
은 onClick
으로 표기한다. 리액트에서는 다양한 이벤트들을 지원하지만 이번 장에서는 자주 쓰는 간단한 이벤트들만 실습해보자.
class EventPractice extends Component{
render(){
return(
<div>
<h1>이벤트 연습</h1>
<input
type = "text"
name = "message"
placeholder = "아무거나 입력해 보세요"
onChange = {
(e)=>{
console.log(e);
}
}
/>
</div>
);
}
}
export default EventPractice;
onChange 이벤트는 값의 변화를 감지한다. 위 코드는 input 값의 변화에 따라 이벤트 객체를 반환한다.
onChange에 넘겨진 함수가 받는 파라미터인 e는 이벤트 객체를 의미하는데(여기서는 input), 그 자체로는 참조 불가능한 형태의 객체이기 때문에 입력값의 변화를 비동기적으로 참조하고 싶다면 아래와 같은 형태로 작성하면 된다.
onChange = {(e)=>{console.log(e.target.value);}}
input값이 변화할 때마다 console에 기록된다. persisit함수라던가 더 큰 개념들은 나중에 알게 될테니 일단은 사용법 정도만 알아두는 걸로..
input에 입력된 값을 state에 저장하기 위해선 어떡해야 될까? 코드로 보자.
class EventPractice extends Component{
state = {
message : ''
}
render(){
const {message} = this.state;
return(
<div>
<h1>이벤트 연습</h1>
<input
type = "text"
name = "message"
placeholder = "Enter something"
onChange = {
(e)=>{
this.setState({message : e.target.value});
}
}
/>
<h1>{message}</h1>
</div>
);
}
}
setState를 사용해 state의 message값을 이벤트 객체의 값으로 갱신해주고 있다. (역시나 객체 형태로 넣어야 한다)
렌더링해보면 입력값이 바로바로 반영되는 모습을 확인할 수 있다.
앞서 작성한 코드를 조금 발전시켜서, input 박스 옆에 버튼을 만들고 그 버튼을 누르면 input값이 초기화되도록 만들어보자.
render(){
const {message} = this.state;
const clear = () =>{
this.setState({message:''})
}
return(
<div>
<h1>이벤트 연습</h1>
<input
type = "text"
name = "message"
value = {this.state.message} // for clear
placeholder = "Enter something"
onChange = {
(e)=>{
this.setState({message : e.target.value});
}
}
/>
<button onClick = {clear}>clear!</button>
<h1>{message}</h1>
</div>
);
}
input태그 내부가 조금 수정됐다. state의 message를 value에 할당해주고 있는데, message값이 공백이 됐을 때 input에서도 이를 반영하게 만들기 위해 추가된 부분이다.
onClick 이벤트 자체는매우 간단하게 추가됐다. 정말 단순하게 클릭했을 때 clear함수를 호출해 state값을 공백으로 만들어 주면 될 뿐이다. 렌더링 결과는 너무 뻔하기에 생략하도록 하자.
사실 클리어 버튼을 만들 때 이미 임의 메서드를 사용했다. clear함수를 미리 준비한 후 전달한 것이 그 예다. 다만 클래스형 컴포넌트에서 임의 메서드를 만들기 위해선 주의해야 될 것들이 있다.
constructor(props){
super(props);
this.methodName = this.methodName.bind(this);
}
기본적으로는 생성자 메소드에서 명시적으로 this(컴포넌트)를 바인딩 해준 후에 사용할 수 있다. 함수의 this는 호출되는 인스턴스에 따라 달라지기 때문에, HTML요소 내에서 사용되는 메서드는 컴포넌트와의 연결이 끊기게 된다. 따라서 임의로 컴포넌트에 바인드를 해주지 않으면 state를 읽어올 수 없다.
하지만 바벨의 transform-class-properties
문법을 사용하면 더 간단하게 작성이 가능하다. 당연히 난 이게 뭔지 모른다.. 아무튼 화살표 함수를 통해 메서드를 작성하면 생성자 메서드에서 따로 바인딩을 해주지 않아도 된다는 장점이 있다는 것만 알아두자. clear메서드를 그렇게 작성했기에 따로 예시 코드를 작성하진 않겠다.
input을 여러개 사용한다면 어떻게 관리해야 할까? username을 입력하는 input을 하나 더 만들어보자.
class EventPractice extends Component {
state = {
message: '',
username: '',
};
render() {
const { message, username } = this.state;
const clear = () => {
this.setState({ message: '', username: '' });
};
const handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
return (
<div>
<h1>이벤트 연습</h1>
<input
type="text"
name="message"
value={this.state.message}
placeholder="Enter something"
onChange={handleChange}
/>
<input
type="text"
name="username"
value={this.state.username}
placeholder="Username"
onChange={handleChange}
/>
<button onClick={clear}>clear!</button>
<h1>
{message}<br/>{username}
</h1>
</div>
);
}
}
태그 작성 자체는 달라질 부분이 없다. 유심히 봐야 할 부분은 여러개의 state를 관리하는 방법이다. 나는 아래와 같은 방식을 선택했다.
const handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
이벤트 객체의 이름을 state의 key값과 동일하게 설정해두면 위 코드처럼 유동적으로 적절한 값을 매핑할 수 있다. 일일히 함수를 따로 작성하는 방법도 있겠지만 이렇게 하는 편이 더 간단하다.
웹사이트를 이용하다 보면 굳이 버튼을 누르지 않고 input에 커서가 켜져있는 상태에서 Enter키를 누르면 버튼의 동작을 수행하게 만드는 경우를 종종 볼 수 있다. 그러한 기능을 리액트로 구현해보자.
class EventPractice extends Component {
state = {
message: '',
username: '',
};
clear = () => {
this.setState({message: '', username: '' });
};
handleChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
handleKeyPress = (e) =>{
if(e.key === 'Enter'){
this.clear();
}
}
render() {
const { message, username } = this.state;
return (
<div>
<h1>Event Practice</h1>
<input
type="text"
name="message"
value={this.state.message}
placeholder="Enter something"
onChange={this.handleChange}
/>
<input
type="text"
name="username"
value={this.state.username}
placeholder="Username"
onChange={this.handleChange}
onKeyPress={this.handleKeyPress}
/>
<button onClick={this.clear}>clear!</button>
<h1>
{message}
<br />
{username}
</h1>
</div>
);
}
}
event객체의 key값을 판단한 후 clear함수를 호출했다. 역시나 어려운 부분은 없다. 여태 메서드들을 render 함수 안에서 선언해놔서 이번엔 밖으로 빼놨다.
useState와 위에서 사용한 대괄호를 통한 state값 매핑으로 함수형 컴포넌트를 작성해보자.
import {useState} from 'react';
const EventPractice = () =>{
const [form,setForm] = useState({
username : '',
message : ''
});
const {username,message} = form;
const onChange = e =>{
const nextForm = {
...form,
[e.target.name] : e.target.value
}
setForm(nextForm);
}
const onClick = () =>{
setForm({
username:'',
message:''
});
}
const onKeyPress = e =>{
if(e.key === 'Enter'){
onClick();
}
}
return(
<div>
<input
type = "text"
name = "username"
placeholder = "username"
value = {username}
onChange={onChange}
/>
<input
type = "text"
name = "message"
placeholder = "message"
value = {message}
onChange = {onChange}
onKeyPress = {onKeyPress}
/>
<button onClick={onClick}>clear</button>
</div>
)
}
export default EventPractice;
코드 자체는 직관적으로 들어온다. 객체 형태의 state값 중 일부를 업데이트 하는 방법에 주목해보자.
const onChange = e =>{
const nextForm = {
...form,
[e.target.name] : e.target.value
}
setForm(nextForm);
}
const onClick = () =>{
setForm({
username:'',
message:''
});
}
사본을 만들어 준 후 변경값만 덮어씌우는 방식을 사용하고 있다. 반면 onClick은 아예 다시 객체를 다시 설정해주고 있는데, 이는 일부를 수정하는 것과 객체 자체를 초기화하는 것의 차이다.