react에서는 기본 event handler들을
'on'
으로 시작하는 props 형태로 component에 전달한다. 모든 event handler는 다음과 같이 function을 전달받아야 한다.
onClick = {clickHandler}
이때 주의할 사항은, onClick
에 props로 함수값을 넘길 때는 함수의 이름만 넘기면 된다. 와 같이 실행 ()을 할 필요가 없다! ()를 넣을 경우 버튼이 클릭될 때가 아닌 JSX 코드가 return 될 때 해당 clickHandler가 실행되게 된다. onClick = {clickHandler()}
const clickHandler = () => {
console.log('Clicked')
}
...
<button onClick={clickHandler}>Change Title</button>
input 태그에 onChange Listener 넣기
const ExpenseForm = () => {
const titleChangeHandler = () => {
console.log('Title changed!');
};
<input type="text" onChange={titleChangeHandler}/>
input에 타이핑을 할 때마다 콘솔에 'Title changed!'가 찍힐것이다.
바닐라 JS를 떠올려보면, event listener들은 자동으로 해당 event 객체를 사용할 수 있도록 해준다. 이를 리액트에서도 당연히 활용 가능하다.
const ExpenseForm = () => {
const titleChangeHandler = (event) => {
console.log(event.target.value);
};
<input type="text" onChange={titleChangeHandler}/>
event.target : event가 일어나는 DOM 요소
event.target.value : 해당 요소의 value값 (ex. input의 value값)
react 에서는 초기에 rendering될 때, component tree를 따라 가장 밑의 component가 실행될 때까지 파일들을 돌면서 화면에 띄운다. 문제는 이러한 과정이 리액트 초기 실행시 딱 한 번만 이루어진다는 것이다.
따라서 component 내에서 이벤트함수를 통해 변수값을 js 변수값을 업데이트 하여 동적으로 적용하려는 경우, 리액트는 초기 한 번만 렌더링을 하기 때문에 업데이트한 값이 화면에 적용되지 않는 문제가 발생한다.
그래서 우리는 react에게 어떤 component의 값이 바뀌었으니 다시 렌더링 해야해~ 라고 알려줄 필요가 있다. 이때 사용하는 것이 useState 이다.
useState는 state값이 변경되면 component를 다시 rendering해서 화면에 띄워주는 react Hook이다.
✅ React Hook의 가장 큰 규칙 중 하나는 component 내부에서만 사용되어야 하고, component 내부의 함수 (nested function) 안에서 사용되어선 안된다는 것이다.
useState는 초기값 설정을 해주어야 한다.
useState는 변수와 해당 변수에 새로운 값을 update할 수 있는 함수를 배열에 담아 반환한다.
const [title, setTitle] = useState(props.title);
state를 update하는 함수 (setTitle)를 호출하면, 해당 함수는 parameter로 전달받은 새로운 title 값을 update해주는 것 뿐만 아니라, 해당 state가 들어있는 component를 re-redering 해준다!!!
즉 state가 변하면 component를 다시 redering 해주게 된다.
useState는 완전 처음 호출될 때만 state를 초기값으로 setting해준다. re-rendering될 때는 더이상 초기값으로 설정하지 않고, setter함수에 의해 update된 값을 기반으로 가장 최신에 update된 값으로 설정해준다.
둘 중 상황에 맞게 사용하면 된다. 보통은 1번의 방법을 사용한다.
1️⃣ state 여러개 사용하는 방법
const [enteredTitle, setEnteredTitle] = useState('');
const [enteredAmount, setEnteredAmount] = useState('');
const [enteredDate, setEnteredDate] = useState('');
const titleChangeHandler = (event) => {
setEnteredTitle(event.target.value);
};
const amountChangeHandler = (event) =>{
setEnteredAmount(event.target.value);
}
const dateChangeHandler = (event) =>{
setEnteredDate(event.target.value);
}
...
2️⃣ 여러개를 객체에 담아서 state 하나만 사용하는 방법
주의! ) setter함수를 업데이트 하는 경우, 나머지 두 개의 setter값도 설정해주어야 한다. react는 re-rendering할 때 이전의 데이터와 merge하는 것이 아닌, 새로 update된 값으로 value를 대체하는 것이므로, 객체에 들어있는 모든 값들을 update해주어야 한다.
const ExpenseForm = () => {
const [userInput, setUserInput] = useState({
enteredTitle: '',
enteredAmount: '',
enteredDate: ''
})
const titleChangeHandler = (event) => {
setUserInput({
...userInput,
enteredTitle: event.target.value,
})
};
const amountChangeHandler = (event) =>{
setUserInput({
...userInput,
enteredAmount: event.target.value,
})
}
const dateChangeHandler = (event) =>{
setUserInput({
...userInput,
enteredDate: event.target.value,
})
}
하지만,, 이런식으로 update하는 것은 기술적으론 문제가 없지만 에러를 일으킬 가능성이 있다. 왜냐하면 React는 state가 업데이트 될 때 재실행을 하는데, 동시에 많은 state값을 update하게 되면 오래된 state나 잘못된 state값을 가져오게 될 수 있기 때문이다. 따라서 아래의 방법을 사용하자!
spread 문법 사용하기
🚨 꼭 지켜야 할 규칙!
setter 함수 안에 바로 값을 넣는게 아니라, 함수를 통해서 값을 넣어야 한다. react에서는 자동으로 해당 함수에 prevState를 전달한다. 즉 이전 state를 parameter로 전달받을 수 있다는 것!const titleChangeHandler = (event) => { setUserInput((prevState) => { return {...prevState, enteredTitle: event.target.value}; }) };
form 태그 안에 있는 type="submit"인 button이 눌린다면, 해당 버튼이 속한 form에서 submit event를 감지한다. 이 event를 우리는
onSubmit event listener
로 감지하고 사용할 수 있다.
cf) form이 submit되면 browser의 기본 동작으로 인해 페이지가 새로고침된다. 이를 방지하기 위해선, preventDefault()라는 JS의 내장함수를 사용한다.
버튼을 누르면, input에 입력된 값들을 저장하도록 하고 싶은경우
const submitHandler = (event) => {
event.preventDefault();
const expenseData = {
title: enteredTitle,
amount: enteredAmount,
date: new Date(enteredDate)
}
// submit된 후 input안의 value값 초기화하기
setEnteredTitle('');
setEnteredAmount('');
setEnteredDate('');
}
return (
<div>
...생략
// submit event handler
<form onSubmit={submitHandler}>
...
<button type="submit">Add Expense</button>
</form>
</div>
)
부모 component에서 함수를 선언하고, 자식 component에서 함수를 호출할 때 parameter에 데이터 값을 전달할 수 있다!
1️⃣ 부모에서 함수를 선언
2️⃣ props를 이용하여 자식에 해당 함수 전달
// 부모 : NewExpense.js
// 부모에서 함수를 선언
const NewExpense = () => {
const saveExpenseDataHandler = (enteredExpenseData) => {
const expenseData = {
...enteredExpenseData,
id: Math.random().toString()
};
}
return (
<div className='new-expense'>
// props를 이용하여 자식에 해당 함수 전달
<ExpenseForm onSaveExpenseData={saveExpenseDataHandler} />
</div>
)
};
3️⃣ 자식에서 해당 함수를 props로 불러와서 호출
4️⃣ 호출할 때 parameter에 부모에게 전달하려는 값을 넣기
// 자식 : ExpenseForm.js
const ExpenseForm = (props) => {
... 생략
const submitHandler = (event) => {
event.preventDefault();
const expenseData = {
title: enteredTitle,
amount: enteredAmount,
date: new Date(enteredDate)
}
// 자식에서 해당 함수를 props로 불러와서 호출
// 호출할 때 parameter에 부모에게 전달하려는 값을 넣기
props.onSaveExpenseData(expenseData);
setEnteredTitle('');
setEnteredAmount('');
setEnteredDate('');
}
return (
// 호출
<form onSubmit={submitHandler}>
... 생략
</form>
)
}
1️⃣ parent -> child
child component에 prop을 통해 전달2️⃣ child -> parent
parent component에 함수 선언 후 child component의 props로 함수 전달
child component에서 props로 받은 함수를 호출. 호출 시 전달하려는 데이터를 parameter로 넣어서 전달3️⃣ sibling끼리
data를 generate하는 component가 child일 경우
child -> parent -> other child 로 전달
뛰어난 글이네요, 감사합니다.