React 002-1 State,Event
그동안 정적인 어플리케이션은 만들었다 이제는 상호작용하는 동적인 애플리케이션을 만들어보자!
이벤트 리스닝, 이벤트 핸들러
모든 on props 즉 모든 이벤트 핸들러 props는 값으로 함수가 필요하고 이런 on props에 전달된 함수는 이벤트가 발생했을 때 실행되너야 한다.
...
const ExpenseItem=(props)=> {
const clickHandler=()=>{
console.log('Clicked!!')
}
return (
...
<button onClick={clickHandler}>Change Title</button>
...
이벤트 이름을 지을 때 이벤트에서 트리거 퇴고 핸들러로 끝나게 만드는 게 좋다. clickHandler
State
state는 리액트에만 특화된 것은 아니지만 리액트에서 아주 중요한 개념이다.
useState는 가장 중요한 리액트 훅 중의 하나 (훅들은 이름이 use로 시작해서 쉽게 알아낼 수 있다) 컴포넌트 함수 안에서 호출되어야 한다.
- useState는 항상 두 개의 요소가 있는 배열을 반환하는데 첫번째 요소[title]는 현재 상태값이고 두번짜 요소[setTitle]는 그것을 업데이트 하는 함수이다.
const ExpenseItem=(props)=> {
const [title, setTitle] = useState(props.title);
const clickHandler=()=>{
setTitle('Updated!');
console.log(title);
/**
* 이 함수를 호출하는 것은 어떤 변수에 새로운 값을 할당하는 것이 아니라 이 특별한 변수로 시작한다.
* 이 특별한 변수는 새로운 값만 받는 것이 아니라 state를 업데이트 하는 함수를 호출하게 되고
* useState로 상태를 초기화 했던 곳에서 다시 실행될 것이다.
* state가 변할 때 이 컴포넌트 함수를 다시 호출하고 싶으면 이 state를 업데이트하는 함수를 호출하면된다.
*/
}
useState는 몇몇 state를 등록한다. useState는 특정 컴포넌트의 인스턴스를 위해 state를 등록한다. 같은 기능을 가진 컴포넌트를 수정해도 그 하나에만 영향을 주지 다른 컴포넌트에는 영향을 주지 않는다.
const를 사용하는 이유?
state를 업데이트하는 함수를 호출하고 구체적은 값은 리액트에 의해 관리된다. 그래서 ustState를 호출해서 리액트에게 어떤 값ㅅ을 관리해야한다고 선언하는 것 그리고 stat가 업데이트 되면 재 실행 되는 것
쉽게 말해
나 "야 리액트, 내가 저번에 관리하라고 했던 최신 제목상태를 줘"
라고 명령하고
리액트는ustState가 반환하는 배열 [title, setTitle] 에서 가장 최신의 상태를 우리에게 제공하는 것이다.
useState 가 작동하는 방식
- useState를 사용해서 상태를 등록한다.
- 항상[현재 상태값, 업데이트하는함수] 두개의 값을 갖는다
- state가 변할 때 마다 업데이트 함수를 호출한다.
- JSX코드에서 출력하기 위해 상태값을 사용하고 싶을 때 마다 첫번째 요소 [현재상태값]을 사용한다.
- 리액트가 작업을 한다.
- 상태가 변할때마다 컴포넌트형 함수를 다시 실행하고
- JSX코드를 다시 평가한다.
state는 응용프로그램에게 반응성을 추가하기 때문에 아주 중요하다.
Input추가하기
출력하는 컴포넌트들을 관리하는 폴더들을 만들었는데 이제는 입력하는 컴포넌트가 들어갈 폴더를 만들어보자
대강의 폼을 준 ExpenseForm.js
import React from'react';
const ExpenseForm=()=>{
return <form action="">
<div className="new-expense__controls">
<div className="new-expense__control">
<label>Title</label>
<input type="text" />
</div>
<div className="new-expense__control">
<label>Amount</label>
<input type="number" min="0.01" stop="0.01" />
</div>
<div className="new-expense__control">
<label>Date</label>
<input type="date" min="2019-01-01" max="2022-12-31" />
</div>
</div>
<div className="new-expense__actions">
<button type="submit">Add Expense</button>
</div>
</form>
};
export default ExpenseForm;
NewExpense.js에서 ExpenseForm.js를 임포트해주고 App.js에다 NeweExpense.js까지 임포트 해준다.
//NewExpense.js
import React from'react';
import ExpenseForm from './ExpenseForm';
import './NewExpense.css'
const NewExpense=()=>{
return <div className="new-expense">
<ExpenseForm />
</div>
};
export default NewExpense;
------------------------------------
//App.js
import React from'react';
import ReactDOM from'react-dom';
import Expenses from "./components/Expenses/Expenses";
import NewExpense from './components/NewExpense/NewExpense';
...
return (
<div>
<NewExpense />
<Expenses item={expenses} />
</div>
);
}
export default App;
나왔군요
사용자 입력 리스닝
이제 사용자가 입력하는 데이터를 모아주자. 리스너를 추가해서 사용자의 요청을 들을 수 있게 해주기
...
const ExpenseForm=()=>{
const titleChangeHandler=(event)=>{
console.log(event.target.value);
};
return <form action="">
<div className="new-expense__controls">
<div className="new-expense__control">
<label>Title</label>
<input type="text" onChange={titleChangeHandler} />
</div>
...
- titleChangeHandler라는 함수를 만들어주고 인자로 event를 잡아주면 어떤일이 일어나는지 알 수 있다.
- 확인을 위해 콘솔로 value가 어떻게 찍히는지 보고
- JSX input에 onChange를 주고 방금 만든 titleChangeHandler로 인자를 준다.
- input에 변화가 생길때마다 콘솔에 변화가 된 값이 찍히게 된다.
여러 State 다루기
이제 그거로 무엇을 할 수 있을까? 나중에 폼이 제출되었을 때 입력값들을 모아 객체에 전달해야한다.
import React, {useState} from'react';
import './ExpenseForm.css'
const ExpenseForm=()=>{
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);
};
...
다른 방식으로 State사용하기
useState를 한번만 뿌려주는 방법
import React, {useState} from'react';
import './ExpenseForm.css'
const ExpenseForm=()=>{
// const [enteredTitle, setEnteredTitle]=useState('');
// const [enteredAmount, setEnteredAmount]=useState('');
// const [enteredDate, setEnteredDate]=useState('');
const[userInput,setUserInput] = useState({
enteredTitle:'',
enteredAmount:'',
enteredDate:''
});
const titleChangeHandler=(event)=>{
// setEnteredTitle(event.target.value);
setUserInput({
...userInput, //다른 데이터들이 사라지지 않도록
enteredTitle:event.target.value,
})
};
const amountChangeHandler=(event)=>{
setUserInput({
...userInput, //다른 데이터들이 사라지지 않도록
enteredAmount:event.target.value,
})
};
const dateChangeHandler=(event)=>{
setUserInput({
...userInput, //다른 데이터들이 사라지지 않도록
enteredDate:event.target.value,
})
};
근데 특정 상태에서 실패할 수 도 있는 방법이라고 한다. 이전 state에 의존하는게 아니라 상태를 업데이트 하는 폼을 사용해주어야한다. 항상 업데이트가 될 수 있다는 것을 인지하게 해주는 것
const titleChangeHandler = (event) => {
setUserInput((prevState) => {
return {
...prevState,
enteredTitle: event.target.value,
};
});
}
근데 여기선 일단 첫 방법으로 진행할거다 돌아가 주자
폼 제출
버튼이 눌렸을 때 이 state들을 객체로 결합하고 싶다.
...
const submitHandler = (event) => { //이벤트 객체를 다시 얻는다.
event.preventDefault();//내장 자바스크립트 기본요청이 전달되는걸 막을 수 있다.
const expenseData = {
title:enteredTitle,
amount:enteredAmount,
date:new Date(enteredDate),
};
console.log(expenseData);
};
return (
<form onSubmit={submitHandler}>
...
양방향 바인딩 추가
입력된 것들을 어떻게 없앨 수 있지?
state를 사용하면 양방향 바인딩을 할 수 있다. 변경되는 입력값만 바꾸는 것이 아니라 입력에 새로운 값을 다시 전달 할 수 도 있는 것 이다. 프로그램에 따라 입력값을 재 설정 할 수 있는 것
간단하게 value를 설정해 주면 된다.
...
console.log(expenseData);
setEnteredTitle(''); //폼이 전송되고 입력했던 걸 오버라이드해서 깨끗하게 정리
setEnteredAmount('');
setEnteredDate('');
};
return (
...
다시 빈칸으로 돌아온다.
자식 대 부모 컴포넌트 통신(상향식)
이제ExpenseForm에는 정확하게는 App.js에는 데이터가 필요없다 데이터를 받아주기만 하면 되니까..
ExpenseForm에서 받은 데이터를 어떻게 App.js로 보낼 수 있을까?
- ExpenseForm에서 NewExpense로 보내기
- NewExpense에 속성을 추가해 준다.
- 맞춰서 함수도 작성한다.
import React from "react";
import ExpenseForm from "./ExpenseForm";
import "./NewExpense.css";
const NewExpense = () => {
const onSaveExpenseData = (enteredExpenseData)=>{
const expenseData={
...enteredExpenseData,
id:Math.random().toString()
};
console.log(expenseData);
};
return (
<div className="new-expense">
<ExpenseForm onSaveExpenseData={onSaveExpenseData} />
</div>
);
};
export default NewExpense;
- NewExpense에서 App.js로 보내기
...
const addExpenseHandler = expense => {
console.log('In App.js');
console.log(expense);
}
return (
<div>
<NewExpense onAddExpense={addExpenseHandler} />
<Expenses item={expenses} />
</div>
);
}
...