https://www.udemy.com/course/react-next-master/
Event
란 웹에서 발생하는 사용자의 행동들을 말한다. 가령 버튼을 누르거나 메세지를 입력하는 것들이 있다. 따라서, Event handling
이란 이벤트가 발생한 것을 어떻게 처리할 것인가?이다.
웹에서 발생하는 사용자의 행동에 따라 어떻게 처리할 지 결정하는 것이다.
button이 눌리면 경고창이 나오도록 해보자.
import './Button.css'
export default function Button({text, color, children}) {
return (
<button
className="button"
style={{backgroundColor: color}}
onClick={() => {
alert("버튼을 클릭했습니다.")
}}
>
{children}
</button>
)
}
onClick
안에 함수를 하나 만들어주면 된다. 이 함수가 바로 event handler가 되는 것이다. 다음처럼 arrow function을 바로 써줄 수도 있지만 따로 함수를 만들어 사용할 수도 있다.
import './Button.css'
export default function Button({text, color, children}) {
const onClick = () => {
alert("버튼을 클릭했습니다.")
}
return (
<button
className="button"
style={{backgroundColor: color}}
onClick={onClick}
>
{children}
</button>
)
}
단, onClick
과 같은 event에 event handler 함수를 전달할 때는 함수 이름을 전달하는 것이지 실행한 것을 전달하면 안된다. 즉, onClick()
으로 전달하면 안된다.
이렇게 event handler를 전달하면 event handler 함수 인자에 event객체를 매개변수로 넣어준다.
import './Button.css'
export default function Button({text, color, children}) {
const onClick = (e) => {
console.log(e)
}
return (
<button
className="button"
style={{backgroundColor: color}}
onClick={onClick}
>
{children}
</button>
)
}
onClick
event handler 함수에 들어간 매개변수 e
가 바로 event객체이다. console.log
로 찍어보면 다음을 알 수 있다.
SyntheticBaseEvent {_reactName: 'onClick', _targetInst: null, type: 'click', nativeEvent: PointerEvent, target: div, …}
SyntheticBaseEvent
는 말그대로 합성 이벤트 객체라고 하는데, 사실 웹브라우저는 정말 다양하고, 이들의 동작 방식이 서로 다르다. 브라우저마다의 동작 방식이 다르니, event들도 다를 수 밖에 없었다. 가령, chrome의 경우 현재 event가 발생한 요소를 target
이라 하지만, 사파리에서는 ETarget
이라 할 수도 있다.
이렇게 브라우저마다 동작 방식이 달라 발생하는 이슈를 cross browing issue라고 한다. 이를 해결해주는 것이 바로 합성 이벤트 객체이다. 이는 모든 웹 브라우저의 event를 하나의 객체로 합성해놓은 것이다. 따라서 개발자들은 이 통합 규격 이벤트 객체를 통해서, 하나의 code로 모든 브라우저를 호환할 수 있다.
state
는 상태를 말한다. 상태는 어떤 사물의 모양이나 동작을 말한다. 추가적으로 상태는 계속 변화하는 값이라고도 한다.
react의 component도 상태를 가지게 되는데, 이 변화한 상태에 따라 component의 모습을 바꾸어 렌더링된다. 가령, 전구가 off
상태이면 빛이 안켜지지만 on
으로 상태가 변한다면 빛이 켜지는 모습으로 다시 렌더링된다. 따라서 state의 변화는 rerendering을 호출한다고 볼 수 있다
state
를 생성하는 방법은 특별한 함수인 react
의 useState
를 사용해야한다. 따라서 다음의 import문이 필요하다.
import { useState } from "react";
이 useState
를 사용하는 방법은 매우 간단한데, useState
함수에 state의 초기값을 넣어주면 된다. 그러면 return value로 array를 반환하는데, array의 첫번째 값은 state
이고, 두번째 값은 state
의 값을 설정하는 setter이다.
import "./Body.css"
import { useState } from "react";
function Body() {
const [light, setLight] = useState("OFF")
console.log(light)
return (
<div className="body">
{light}
</div>
)
}
export default Body
다음과 같이 배열의 구조분해할당으로 값을 가져와 줄 수 있다.
정리하자면 light
라는 state가 나오고 setLight
를 통해서 light
의 값을 변경할 수 있다. 이를 상태변화함수라고 한다.
console.log(light)
로 확인하면 OFF
가 나오는 것을 볼 수 있고, javascript 변수처럼 light
state가 rendering되는 것 또한 볼 수 있다.
이제 setLight
를 통해서 light
state를 변경해보도록 하자.
import "./Body.css"
import { useState } from "react";
function Body() {
const [light, setLight] = useState("OFF")
console.log(light)
return (
<div className="body">
{light}
<button onClick={() => {
setLight("ON")
}}>
켜기
</button>
</div>
)
}
export default Body
버튼을 누르면 setLight
가 실행되어 light
가 OFF
에서 ON
으로 바뀐다. 재밌는 것은 렌더링도 다시되어 화면에 보이는데, 이는 state
인 light
가 ON
으로 변경되었고 react에서 이를 감지하여 리렌더링했다는 것이다.
다시 렌더링이 실행된다는 것은 다른 말로 함수를 다시 실행시킨다는 말과 같다. 따라서, Body
react component 전체가 다시 실행되어 console.log
값이 또 찍히게 될 것이다.
그런데, javascript의 변수로 동일한 logic을 만들면 되지 않는가? 그러나 일반 javascript 변수는 state
가 아니므로 변수값이 바뀌어도 rerendering이 발생하지 않는다.
import "./Body.css"
import { useState } from "react";
function Body() {
let light = "OFF"
return (
<div className="body">
{light}
<button onClick={() => {
light = "ON"
}}>
켜기
</button>
</div>
)
}
export default Body
버튼을 아무리 눌러도 화면이 바뀌지 않을 것이다. 이는 light
가 더이상 state가 아니고 일반 javascript 변수이기 때문이다. 따라서, light
값이 바뀌어도 리렌더링이 발생하지 않는다.
이제 state와 props를 함께 사용해보도록 하자. state를 props로 쓸 수 있는데, 자세히 말해보자면 부모의 state를 자식에게 props로 내려줄 수 있다는 것이다.
이를 위해서 다음과 같이 code를 만들어보도록 하자.
import "./Body.css"
import { useState } from "react";
function LightBulb({ light }) {
return <>
{light === "ON" ? <div style={{ backgroundColor: "orange"}}>ON</div>
: <div style={{ backgroundColor: "gray"}}>OFF</div>}
</>
}
function Body() {
const [light, setLight] = useState("OFF")
return (
<div className="body">
<LightBulb light={light}/>
<button onClick={() => {
setLight("ON")
}}>
켜기
</button>
<button onClick={() => {
setLight("OFF")
}}>
끄기
</button>
</div>
)
}
export default Body
LightBulb
는 자식 component로 light
를 props로 받아 렌더링을 해준다. 이 light
props를 내려주는 부모 component인 Body
는 state로 light
를 가지고 있고, 이를 LightBulb
props에 넣어주는 것이다.
이렇게 구성하면 끄기, 켜기 버튼을 번갈아 누를 때마다 LightBulb
가 리렌더링된다. 즉, light
state가 바뀔 때마다, 부모인 Body
component와 자식인 LightBulb
component가 리렌더링된다는 것이다. 이를 통해 알 수 있는 것은 state
가 변하면 state
를 가진 component는 리렌더링된다는 것이고, 부모로 부터받은 props
가 state
이고 이 값이 변하면 자식 component도 리렌더링된다는 것이다.
한 가지 재밌는 실험을 할 수 있는데, 다음의 예제를 보도록 하자.
import "./Body.css"
import { useState } from "react";
function LightBulb({ light }) {
return <>
{light === "ON" ? <div style={{ backgroundColor: "orange"}}>ON</div>
: <div style={{ backgroundColor: "gray"}}>OFF</div>}
</>
}
function StaticLightBulb() {
console.log("STATIC LIGHT BULB")
return <div style={{ backgroundColor: "gray"}}>OFF</div>
}
function Body() {
const [light, setLight] = useState("OFF")
return (
<div className="body">
<LightBulb light={light}/>
<StaticLightBulb/>
<button onClick={() => {
setLight("ON")
}}>
켜기
</button>
<button onClick={() => {
setLight("OFF")
}}>
끄기
</button>
</div>
)
}
export default Body
StaticLightBulb
는 부모로 부터 state
props를 받지도 않았고, 자체 state
도 없으니 light
state가 변해도 안변하는 것이 맞다. 그런데 정말일까? 켜기
, 끄기
버튼을 여러 번 누르고 console을 확인하면 다음을 볼 수 있다.
STATIC LIGHT BULB
STATIC LIGHT BULB
STATIC LIGHT BULB
STATIC LIGHT BULB
계속해서 리렌더링이 되는 것을 볼 수 있다. 이는 react에서 부모 component가 리렌더링되면 자식 component도 리렌더링된다는 것을 알 수 있다.
따라서, 부모 component가 리렌더링되면 자식 component 모두 리렌더링된다는 것이다. 이렇게되면 web의 성능이 매우 안좋아진다.
이러한 불필요한 리렌더링이 발생하지 않도록 부모-자식 component 관계를 최적화를 하는 것이 좋다.
먼저 input
tag를 만들어 사용자의 입력을 받도록 하자.
import "./Body.css"
function Body() {
return (
<div className="body">
<input />
</div>
)
}
export default Body
이제 input
tag에 들어갈 value를 state
로 다루고 싶다. value
를 state
로 다루어 Body
component에서 사용자의 입력으로 들어온 value
를 통해 로직을 부여하고, 제어하고 싶은 것이다.
그러나, 다음과 같이 input
의 value
를 state
로 넣어주면 input tag가 동작하지 않은 것을 볼 수 있다.
import "./Body.css"
import { useState } from "react";
function Body() {
const [name, setName] = useState("");
return (
<div className="body">
<input value={name} />
</div>
)
}
export default Body
아무리 input
html tag에 값을 입력해도 값이 써지지 않는 것을 볼 수 있다. 왜냐하면 name
state의 초기값인 ""
가 고정으로 들어가 input
tag의 value
가 변하지 않기 때문이다.
따라서, 사용자의 입력에 따라 name
state에 input의 value
가 들어가도록 event handler를 만들어주어야 한다.
import "./Body.css"
import { useState } from "react";
function Body() {
const [name, setName] = useState("");
const onChnageName = (e) => {
setName(e.target.value)
}
return (
<div className="body">
<input value={name} onChange={onChnageName} />
</div>
)
}
export default Body
이제 input
tag에 값을 입력하면, name
state에 반영이 될 것이다. e.target.value
가 바로 사용자가 입력한 input value값이기 때문이다. 즉, input
tag에 사용자 입력이 들어오면 onChange
event handler가 동작하고, 이에 따라 name
state의 값이 사용자의 입력인 e.target.value
로 바뀐다.
input
tag말고도 select
tag도 사용해보도록 하자.
import "./Body.css"
import { useState } from "react";
function Body() {
const [name, setName] = useState("");
const [gender, setGender] = useState("")
const onChnageName = (e) => {
setName(e.target.value)
}
const onChangeGender = (e) => {
setGender(e.target.value)
}
return (
<div className="body">
<div>
<input value={name} onChange={onChnageName} />
</div>
<div>
<select value={gender} onChange={onChangeGender}>
<option value="">밝히지 않음</option>
<option value="female">여성</option>
<option value="male">남성</option>
</select>
</div>
</div>
)
}
export default Body
방법이 크게 다르지 않다. select
tag에 value
를 state
값인 gender
를 넣어주고 onChange
로 setGender
를 호출하여 gender
값을 바꾸면 된다.
다음으로 textarea
tag도 동일한 방법으로 state
를 설정할 수 있다.
import "./Body.css"
import { useState } from "react";
function Body() {
const [name, setName] = useState("");
const [gender, setGender] = useState("")
const [bio, setBio] = useState("")
const onChnageName = (e) => {
setName(e.target.value)
}
const onChangeGender = (e) => {
setGender(e.target.value)
}
const onChangeBio = (e) => {
setBio(e.target.value)
}
return (
<div className="body">
<div>
<input value={name} onChange={onChnageName} />
</div>
<div>
<select value={gender} onChange={onChangeGender}>
<option value="">밝히지 않음</option>
<option value="female">여성</option>
<option value="male">남성</option>
</select>
</div>
<div>
<textarea value={bio} onChange={onChangeBio} />
</div>
</div>
)
}
export default Body
별반 다를바 없는 것을 볼 수 있다.
그런데, 사용자에게 입력을 받는 form이 늘어날 때마다, 이 과정을 계속 반복해야하는 것인가?? form이 너무 많아지면 어떻게해야할까?? 너무 많은 state
와 event handler가 있을 것이고, 동일한 로직을 가진 event handler로 범벅이 되어있을 것이다. 즉, redundancy가 너무 많아지는 것이다.
이를 해결하기 위한 방법으로 통합 state
를 구축해서 해결해보도록 하자.
import "./Body.css"
import { useState } from "react";
function Body() {
const [state, setState] = useState({
name: "",
gender: "",
bio: ""
})
const onChange = (e) => {
setState({
...state,
[e.target.name]: e.target.value
})
}
return (
<div className="body">
<div>
<input name={"name"} value={state.name} onChange={onChange} />
</div>
<div>
<select name={"gender"} value={state.gender} onChange={onChange}>
<option value="">밝히지 않음</option>
<option value="female">여성</option>
<option value="male">남성</option>
</select>
</div>
<div>
<textarea name={"bio"} value={state.bio} onChange={onChange} />
</div>
</div>
)
}
export default Body
방법은 그렇게 어렵지 않다. state
를 만들되 state
는 object로 다음의 property를 가진다.
{
name: "",
gender: "",
bio: ""
}
이 state
의 property를 각 input
, select
, textarea
의 value
로 넣어주도록 하는 것이다.
다음으로 onChange
event handler인데, 이 부분이 약간 tricky하다.
const onChange = (e) => {
setState({
...state,
[e.target.name]: e.target.value
})
}
setState
에 새로운 object를 넣는데, ...state
spread문법으로 기존 state
값들을 넣어준다. 다음으로 변경된 state
값을 넣어주어야하는데, 문제는 object의 key
값을 어떤 값으로 넣어주어야 하는 지 모른다는 것이다.
그래서 e.target.name
으로 설정했는데, 이 값이 바로 input
, select
, textarea
의 name
property값이다.
<div className="body">
<div>
<input name={"name"} value={state.name} onChange={onChange} />
</div>
<div>
<select name={"gender"} value={state.gender} onChange={onChange}>
...
</select>
</div>
<div>
<textarea name={"bio"} value={state.bio} onChange={onChange} />
</div>
</div>
input
, select
, textarea
html tag 각각에 name
property가 있는 것을 볼 수 있다. 이 값을 onChnage
event handler에서 가져와 object의 key값으로 써주는 것이다. 그게 [e.target.name]
이다.
왜 대괄호를 치냐고하냐면, 이것이 js문법이기 때문이다. js
에서는 object의 property key로 바로 변수값을 넣어줄 수 없다. 때문에 변수를 object의 key로 쓰기위해서는 []
를 변수에 넣어주어야 한다.
이 덕분에 하나의 state
와 eventHandler
를 통해서 여러 개의 입력 form들을 제어할 수 있는 것이다.
react에서 특정 DOM에 접근하기 위해서는 ref
를 사용해야한다. 이때 사용하는 react함수가 useRef
이다. ref
객체는 component가 리렌더링되어도 그대로 유지된다. 따라서, 특정 DOM을 참조하기 위해서 많이 사용된다.
만약 button을 눌렀을 때, name
input이 비어있다면 해당 DOM으로 focus가 가도록 하고 싶다고 하자. 다음과 같이 만들 수가 있다.
import "./Body.css"
import { useState, useRef } from "react";
function Body() {
const [state, setState] = useState({
name: "",
gender: "",
bio: ""
})
const onChange = (e) => {
setState({
...state,
[e.target.name]: e.target.value
})
}
const nameRef = useRef()
const onSubmit = () => {
if (state.name === "") {
nameRef.current.focus()
return
}
}
return (
<div className="body">
<div>
<input ref={nameRef} name={"name"} value={state.name} onChange={onChange} />
</div>
<div>
<select name={"gender"} value={state.gender} onChange={onChange}>
<option value="">밝히지 않음</option>
<option value="female">여성</option>
<option value="male">남성</option>
</select>
</div>
<div>
<textarea name={"bio"} value={state.bio} onChange={onChange} />
</div>
<div>
<button onClick={onSubmit}>회원가입</button>
</div>
</div>
)
}
export default Body
먼저 주목해야할 것은 useRef
이다. useRef
를 통해서 reference를 만들고 이 reference를 input
의 ref
에 넣어서 input DOM의 reference를 주입해주는 것이다.