상태값과 useState
- 상태값은 사용자의 입력, API 응답 등으로 인해 시간에 따라 변할 수 있는 값
- 상태는 컴포넌트의 렌더링 결과를 결정하는 요소 중 하나로, 상태가 변경되면 컴포넌트는 재렌더링됨
- react에서는 useState의 setter를 사용해야 변경상태 감지
-> 상태값을 직접 변경하면, React는 상태가 변경되었는지 알 수 없으며, 이로 인해 컴포넌트가 적절하게 업데이트되지 않을 수 있다. 따라서 상태를 변경할 때는 항상 setter함수를 사용해야 한다.
- useState의 첫번째 요소는 관리할 상태값의 초기값, 두번째 요소는 해당 상태값을 변경할 때 사용하는 setter 함수
useState의 setter의 특징
useState의 setter는 상태값을 업데이트할 때 렌더링 전까지 이전 상태값을 참조함
setCount((prev) => {
console.log('변경 이전값: ', prev);
return prev + 1;
});
setCount(count => count + 1);
- 현재 값이 3이라면 prev = 3이다. 콜백에서 prev + 1을 리턴하고 있으므로 여기서 prev가 4가된다. 그리고 count는 4가되고 여기서도 1이 늘어나므로 결과적으로 3 -> 5로 되어 2씩 늘어나게 해주는 것을 알 수 있다.
React 상태 관리: 단일 값 vs 객체
1. 단일 값 상태: 상태의 각 부분을 별도의 상태로 관리하는 방식
import React, { useState } from 'react';
import './ExpenseForm.css';
const ExpenseForm = () => {
const [title, setTitle] = useState('');
const [price, setPrice] = useState(0);
const [date, setDate] = useState(null);
const getTodayDate = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
const titleChangeHandler = e => {
setTitle(e.target.value);
};
const priceChangeHandler = e => {
setPrice(+e.target.value);
};
const dateChangeHandler = e => {
setDate(e.target.value);
};
const submitHandler = e => {
e.preventDefault();
const newExpense = {
title,
price,
date
};
console.log(newExpense);
};
return (
<form onSubmit={submitHandler}>
<div className="new-expense__controls">
<div className="new-expense__control">
<label>Title</label>
<input type="text" onChange={titleChangeHandler} />
</div>
<div className="new-expense__control">
<label>Price</label>
<input
type="number"
min="100"
step="100"
onChange={priceChangeHandler}
/>
</div>
<div className="new-expense__control">
<label>Date</label>
<input
type="date"
min="2019-01-01"
max={getTodayDate()}
onChange={dateChangeHandler}
/>
</div>
</div>
<div className="new-expense__actions">
<button type="submit">Add Expense</button>
</div>
</form>
);
};
export default ExpenseForm;
2. 객체 상태: 여러 상태를 하나의 객체로 묶어 관리하는 방식
객체나 배열상태로 관리되는 상태값은 상태변경시 새로운 객체나 배열을 setter에 전달해야 함 -> 리액트는 상태변경시 무조건 새 객체에 넣어서 전달해함
import React, { useState } from 'react';
import './ExpenseForm.css';
const ExpenseForm = () => {
const [userInput, setUserInput] = useState({
title: '',
price: '',
date: ''
});
const getTodayDate = () => {
const today = new Date();
const year = today.getFullYear();
const month = String(today.getMonth() + 1).padStart(2, '0');
const day = String(today.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
const titleChangeHandler = e => {
setUserInput(prevUserInput => ({
...prevUserInput,
title: e.target.value
}));
};
const priceChangeHandler = e => {
setUserInput({
...userInput,
price: +e.target.value
})
};
const dateChangeHandler = e => {
setUserInput({
...userInput,
date: e.target.value
})
};
const submitHandler = e => {
e.preventDefault();
console.log(userInput);
setUserInput({
title: '',
price: '',
date: ''
});
};
return (
<form onSubmit={submitHandler}>
<div className="new-expense__controls">
<div className="new-expense__control">
<label>Title</label>
<input type="text"
value={userInput.title}
onChange={titleChangeHandler} />
</div>
<div className="new-expense__control">
<label>Price</label>
<input
type="number"
min="100"
step="100"
value={userInput.price}
onChange={priceChangeHandler}
/>
</div>
<div className="new-expense__control">
<label>Date</label>
<input
type="date"
min="2019-01-01"
max={getTodayDate()}
value={userInput.date}
onChange={dateChangeHandler}
/>
</div>
</div>
<div className="new-expense__actions">
<button type="submit">Add Expense</button>
</div>
</form>
);
};
export default ExpenseForm;
실습
- 체크 버튼을 누르면 버튼 색이 바뀌게함
-> 버튼 클릭했을 때 true, false로 나오게함
-> className을 조건부로 입력
import React, { useState } from "react";
import "./CheckBoxStyle.css";
const CheckBoxStyle = () => {
const [isChecked, setIsChecked] = useState(false);
const checkChangeHandler = (e) => {
setIsChecked(!isChecked);
};
return (
<div className="checkbox-container">
<input
type="checkbox"
id="styled-checkbox"
onChange={checkChangeHandler}
/>
<label
htmlFor="styled-checkbox"
className={isChecked ? 'checked' : 'unchecked'}
>
Check me!
</label>
</div>
);
};
export default CheckBoxStyle;
Props Drilling
- react는 props를 통해 하향식 데이터 전달 가능
- 자식컴포넌트 -> 부모컴포넌트 데이터 전달하는 법은 Props Drilling (props의 반대, 상향식 전달)
-> 부모가 자식에게 함수를 props로 내려보내고 자식은 그 함수에 데이터를 담아서 위로 전달
- 부모에서부터 자식 순서대로 타고타고 내려가서 전달해야 한다, 타겟 자식에서 데이터를 획득하면 함수의 파라미터로 보내고, 부모측에서 같은 이름으로 받아서 사용 가능
동적 리스트 렌더링
- 배열에 데이터를 추가하면 화면에 자동으로 렌더링되게함
import React from 'react'
import ExpenseItem from './ExpenseItem'
import ExpenseFilter from './ExpenseFilter'
const ExpenseList = ({ expenses }) => {
const onFilterChange = (filteredYear) => {
console.log(filteredYear);
}
return (
<div className='expenses'>
<ExpenseFilter onChangeFilter={onFilterChange}/>
{ expenses
.map(ex => <ExpenseItem title={ex.title} price={ex.price} date={ex.date} />)
}
</div>
)
}
export default ExpenseList
동적 리스트 렌더링 (필터 추가)
- 여러개의 컴포넌트를 반복문으로 생성할 경우, 각 컴포넌트를 구분하기 위한
속성으로 랜덤값(key)을 부여해야 한다.
보통 DB에 있는 데이터를 사용하기에, PK를 쓴다.
사용하지 않을 시에 올바르지 않은 렌더링이 됨.
import React, { useState } from 'react';
import ExpenseItem from './ExpenseItem'
import './ExpenseList.css'
import ExpenseFilter from './ExpenseFilter'
const ExpenseList = ({expenses}) => {
const [filteredYear, setFilteredYear]
= useState(new Date().getFullYear().toString())
const onFilterChange = (value) => {
console.log(value)
setFilteredYear(value)
};
return (
<div className="expenses">
<ExpenseFilter onFilter={onFilterChange}/>
{ expenses
.filter(ex => ex.date.getFullYear().toString() === filteredYear)
.map(ex =>
<ExpenseItem
key={Math.random().toString()}
title={ex.title}
price={ex.price}
date={ex.date}
/>)}
</div>
);
};
export default ExpenseList;