onChange
onChange
Prop을 사용하면 입력 값이 바뀔 때마다 핸들러 함수를 실행함<label />
태그에서 사용하는 속성인 for
는 자바스크립트 반복문 키워드인 for 와 겹치기 때문에 리액트에서는 htmlFor
를 사용함target.value
값을 사용해서 값을 변경해 줄 수 있음value
Prop으로 스테이트 값을 내려주고, onChange
Prop으로 핸들러 함수를 넘겨줌function TripSearchForm() {
const [location, setLocation] = useState('Seoul');
const [checkIn, setCheckIn] = useState('2022-01-01');
const [checkOut, setCheckOut] = useState('2022-01-02');
const handleLocationChange = (e) => setLocation(e.target.value);
const handleCheckInChange = (e) => setCheckIn(e.target.value);
const handleCheckOutChange = (e) => setCheckOut(e.target.value);
return (
<form>
<h1>검색 시작하기</h1>
<label htmlFor="location">위치</label>
<input id="location" name="location" value={location} placeholder="어디로 여행가세요?" onChange={handleLocationChange} />
<label htmlFor="checkIn">체크인</label>
<input id="checkIn" type="date" name="checkIn" value={checkIn} onChange={handleCheckInChange} />
<label htmlFor="checkOut">체크아웃</label>
<input id="checkOut" type="date" name="checkOut" value={checkOut} onChange={handleCheckOutChange} />
<button type="submit">검색</button>
</form>
)
}
function TripSearchForm() {
const [values, setValues] = useState({
location: 'Seoul',
checkIn: '2022-01-01',
checkOut: '2022-01-02',
})
const handleChange = (e) => {
const { name, value } = e.target;
setValues((prevValues) => ({
...prevValues,
[name]: value,
}));
}
return (
<form>
<h1>검색 시작하기</h1>
<label htmlFor="location">위치</label>
<input id="location" name="location" value={values.location} placeholder="어디로 여행가세요?" onChange={handleChange} />
<label htmlFor="checkIn">체크인</label>
<input id="checkIn" type="date" name="checkIn" value={values.checkIn} onChange={handleChange} />
<label htmlFor="checkOut">체크아웃</label>
<input id="checkOut" type="date" name="checkOut" value={values.checkOut} onChange={handleChange} />
<button type="submit">검색</button>
</form>
)
}
const handleSubmit = (e) => {
e.preventDefault();
// ...
}
value
속성을 지정하고 사용하는 컴포넌트//예시 1
function TripSearchForm() {
const [values, setValues] = useState({
location: 'Seoul',
checkIn: '2022-01-01',
checkOut: '2022-01-02',
})
const handleChange = (e) => {
const { name, value } = e.target;
setValues((prevValues) => ({
...prevValues,
[name]: value,
}));
}
return (
<form>
<h1>검색 시작하기</h1>
<label htmlFor="location">위치</label>
<input id="location" name="location" value={values.location} placeholder="어디로 여행가세요?" onChange={handleChange} />
<label htmlFor="checkIn">체크인</label>
<input id="checkIn" type="date" name="checkIn" value={values.checkIn} onChange={handleChange} />
<label htmlFor="checkOut">체크아웃</label>
<input id="checkOut" type="date" name="checkOut" value={values.checkOut} onChange={handleChange} />
<button type="submit">검색</button>
</form>
)
}
//예시 2
function TripSearchForm({ values, onChange }) {
return (
<form>
<h1>검색 시작하기</h1>
<label htmlFor="location">위치</label>
<input id="location" name="location" value={values.location} placeholder="어디로 여행가세요?" onChange={onChange} />
<label htmlFor="checkIn">체크인</label>
<input id="checkIn" type="date" name="checkIn" value={values.checkIn} onChange={onChange} />
<label htmlFor="checkOut">체크아웃</label>
<input id="checkOut" type="date" name="checkOut" value={values.checkOut} onChange={onChange} />
<button type="submit">검색</button>
</form>
)
}
value
속성을 리액트에서 지정하지 않고 사용하는 컴포넌트function TripSearchForm({ onSubmit }) {
return (
<form onSubmit={onSubmit} >
<h1>검색 시작하기</h1>
<label htmlFor="location">위치</label>
<input id="location" name="location" placeholder="어디로 여행가세요?" />
<label htmlFor="checkIn">체크인</label>
<input id="checkIn" type="date" name="checkIn" />
<label htmlFor="checkOut">체크아웃</label>
<input id="checkOut" type="date" name="checkOut" />
<button type="submit">검색</button>
</form>
)
}
// 이벤트 객체의 target 활용
const handleSubmit = (e) => {
e.preventDefault();
const form = e.target;
const location = form['location'].value;
const checkIn = form['checkIn'].value;
const checkOut = form['checkOut'].value;
// ....
}
// FormData 만들기
const handleSubmit = (e) => {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
// ...
}
import { useRef } from 'react';
// ...
const ref = useRef();
ref
Prop에다가 앞에서 만든 Ref 객체를 내려주면 됩const ref = useRef();
// ...
<div ref={ref}> ... </div>
const node = ref.current;
if (node) {
// node 를 사용하는 코드
}
import { useRef } from 'react';
function Image({ src }) {
const imgRef = useRef();
const handleSizeClick = () => {
const imgNode = imgRef.current;
if (!imgNode) return;
const { width, height } = imgNode;
console.log(`${width} x ${height}`);
};
return (
<div>
<img src={src} ref={imgRef} alt="크기를 구할 이미지" />
<button onClick={handleSizeClick}>크기 구하기</button>
</div>
);
}
// 예시
let count = 0;
function add(a, b) {
const result = a + b;
count += 1; // 함수 외부의 값을 변경
return result;
}
const val1 = add(1, 2);
const val2 = add(-4, 5);
useEffect
는 리액트 컴포넌트 함수 안에서 사이드 이펙트를 실행하고 싶을 때 사용하는 함수// 페이지 정보 변경
useEffect(() => {
document.title = title; // 페이지 데이터를 변경
}, [title]);
// 네트워크 요청
useEffect(() => {
fetch('https://example.com/data') // 외부로 네트워크 리퀘스트
.then((response) => response.json())
.then((body) => setData(body));
}, [])
// 데이터 저장
useEffect(() => {
localStorage.setItem('theme', theme); // 로컬 스토리지에 테마 정보를 저장
}, [theme]);
//타이머
useEffect(() => {
localStorage.setItem('theme', theme); // 로컬 스토리지에 테마 정보를 저장
}, [theme]);
// 핸들러 함수만 사용한 예시
import { useState } from 'react';
const INITIAL_TITLE = 'Untitled';
function App() {
const [title, setTitle] = useState(INITIAL_TITLE);
const handleChange = (e) => {
const nextTitle = e.target.value;
setTitle(nextTitle);
document.title = nextTitle;
};
const handleClearClick = () => {
const nextTitle = INITIAL_TITLE;
setTitle(nextTitle);
document.title = nextTitle;
};
return (
<div>
<input value={title} onChange={handleChange} />
<button onClick={handleClearClick}>초기화</button>
</div>
);
}
export default App;
handleChange
함수와 handleClearClick
함수가 있음title
스테이트를 변경한 후에 document.title
도 함께 변경해주고 있음document.title
값을 바꾸는 건 외부의 상태를 변경하는 거니까 사이드 이펙트임document.title
값도 변경해야 한다는 걸 기억해뒀다가 관련된 코드를 작성해야 한다는 점이 아쉬움import { useEffect, useState } from 'react';
const INITIAL_TITLE = 'Untitled';
function App() {
const [title, setTitle] = useState(INITIAL_TITLE);
const handleChange = (e) => {
const nextTitle = e.target.value;
setTitle(nextTitle);
};
const handleClearClick = () => {
setTitle(INITIAL_TITLE);
};
useEffect(() => {
document.title = title;
}, [title]);
return (
<div>
<input value={title} onChange={handleChange} />
<button onClick={handleClearClick}>초기화</button>
</div>
);
}
export default App;
useEffect
를 사용한 예시에서는 document
를 다루는 사이드 이펙트 부분만 따로 처리하고 있음setTitle
함수를 쓸 때마다 document.title
을 변경하는 코드를 신경 쓰지 않아도 되니까 편리함title
스테이트 값을 가지고 항상 document.title
에 반영해줄 것이라고 쉽게 예측할 수 있음useEffect
는 리액트 안과 밖의 데이터를 일치시키는데 활용하면 좋음useEffect
를 사용했을 때 반복되는 코드를 줄이고, 동작을 쉽게 예측할 수 있는 코드를 작성할 수 있음// 예시: 타이머
import { useEffect, useState } from 'react';
function Timer() {
const [second, setSecond] = useState(0);
useEffect(() => {
const timerId = setInterval(() => {
console.log('타이머 실행중 ... ');
setSecond((prevSecond) => prevSecond + 1);
}, 1000);
console.log('타이머 시작 🏁');
return () => {
clearInterval(timerId);
console.log('타이머 멈춤 ✋');
};
}, []);
return <div>{second}</div>;
}
function App() {
const [show, setShow] = useState(false);
const handleShowClick = () => setShow(true);
const handleHideClick = () => setShow(false);
return (
<div>
{show && <Timer />}
<button onClick={handleShowClick}>보이기</button>
<button onClick={handleHideClick}>감추기</button>
</div>
);
}
export default App;
Timer 컴포넌트에서는 useEffect 에서 타이머를 시작하고, 정리 함수를 리턴합니다.
콘솔에는 '타이머 시작 🏁'이 출력됩니다.
다시 사용자가 '감추기' 버튼을 누르면 show 값이 거짓으로 바뀌면서 다시 렌더링 됩니다.
조건부 렌더링에 의해서 이제 Timer 컴포넌트를 렌더링 하지 않겠죠?
그럼 리액트에선 마지막으로 앞에서 기억해뒀던 정리 함수를 실행해줍니다.
타이머를 멈추고 콘솔에는 '타이머 멈춤 ✋'이 출력됩니다.
이런 식으로 정리 함수를 리턴하면 사이드 이펙트를 정리하고 안전하게 사용할 수 있다는 것.
꼭 기억해주세요!