State로 사용자 입력 관리하기 2
// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
import { useState } from "react";
const Register = () => {
const [input, setInput] = useState({
name: "",
birth: "",
country: "",
bio: "",
});
const onChange = (e) => {
setInput({
...input,
[e.target.name]: e.target.value,
});
};
return (
<div>
<div>
<input
name="name"
value={input.name}
onChange={onChange}
placeholder={"이름"}
/>
</div>
<div>
<input
name="birth"
value={input.birth}
onChange={onChange}
type="date"
/>
</div>
<div>
<select name="country" value={input.country} onChange={onChange}>
<option></option>
<option value="kr">한국</option>
<option value="us">미국</option>
<option value="uk">영국</option>
</select>
</div>
<div>
<textarea name="bio" value={input.bio} onChange={onChange} />
</div>
</div>
);
};
export default Register;
지난 시간 배웠던 상태 관리는, 비슷한 형식의 코드를 반복해서 작성해야 했다. 이를테면 name, birth 별로 state를 각각 생성했었다. 그러나 우리 개발자들은 중복 코드를 아주 싫어하기에.. 코드를 더욱 간소화시키는 방법이 있었다.
첫 번째로, 비슷한 여러 개의 state들은 하나의 state에 객체로 저장한다. 위에서는 state 변수명이 input, 상태 관리 함수가 setInput인 하나의 state를 만든 것을 알 수 있다. useState 함수의 인수 자리에는 객체 형태로 차곡차곡 사용할 state들을 저장해준다.
코드를 간소화하는 두 번째 방법은, 비슷한 여러 개의 이벤트 핸들러들을 통합된 하나의 이벤트 핸들러로 만드는 것이다. 우리는 onChange 이벤트가 일어나면 state 값을 e.target.value 값으로 업데이트 해주는 동일한 방식을 사용하고 있었다. 그러므로 모든 onChange 이벤트의 핸들러를 onChange 라는 동일한 이름의 함수로 통합하였다. 이 함수에서 넘겨준 인수는 객체 형태인데, spread 연산자로 기존의 객체값들은 그대로 전달하고, 또 하나는 대괄호에 변수가 들어간 것을 볼 수 있다. 이는 해당 변수의 값이 프로퍼티의 키로 설정된다는 의미이다.
useRef로 컴포넌트의 변수 생성하기
// 간단한 회원가입 폼
// 1. 이름
// 2. 생년월일
// 3. 국적
// 4. 자기소개
import { useState, useRef } from "react";
const Register = () => {
const [input, setInput] = useState({
name: "",
birth: "",
country: "",
bio: "",
});
const countRef = useRef(0);
const inputRef = useRef();
const onChange = (e) => {
countRef.current++;
console.log(countRef.current);
setInput({
...input,
[e.target.name]: e.target.value,
});
};
const onSubmit = () => {
if (inputRef.current.value === "") {
// 이름을 입력하는 DOM 요소 포커스
inputRef.current.focus();
}
};
return (
<div>
<div>
<input
ref={inputRef}
name="name"
value={input.name}
onChange={onChange}
placeholder={"이름"}
/>
</div>
<div>
<input
name="birth"
value={input.birth}
onChange={onChange}
type="date"
/>
</div>
<div>
<select name="country" value={input.country} onChange={onChange}>
<option></option>
<option value="kr">한국</option>
<option value="us">미국</option>
<option value="uk">영국</option>
</select>
</div>
<div>
<textarea name="bio" value={input.bio} onChange={onChange} />
</div>
<button onClick={onSubmit}>제출</button>
</div>
);
};
export default Register;
useRef는 리렌더링을 유발하지 않고, 리렌더링의 영향도 받지 않는 reference 객체 생성 함수이다. 그래서 주로 렌더링에 영향을 미치지 않아야 하는 변수를 생성할 때, 또는 DOM 요소에 접근해야 할 때 사용한다. 왜냐하면 리렌더링 없이 안정적이고 안전하게 작업을 수행할 수 있기 때문이다.
렌더링에 영향을 미치지 않아야 하는 변수, countRef를 생성했다. 이는 onClick 이벤트 시에만 이벤트 핸들러 onChange 함수가 호출되어 값이 1씩 증가한다. (값에 접근하는 방법은 변수명.current) 이렇게 이벤트 핸들러만 실행되고 리렌더링은 일어나지 않는다.
DOM 요소에 접근, 예로 focus (창 굵게 표시) 하기 위해 inputRef를 생성했다. 사용하고자 하는 태그 내부에 ref = {inputRef} 속성을 넣어준다. 이는 제출 버튼을 누르는 onSubmit 이벤트 발생 시 사용되는데, 값이 비어 있으면 해당 dom을 focus 해준다.
React Hooks
// useInput.jsx
import { useState } from "react";
function useInput() {
const [input, setInput] = useState("");
const onChange = (e) => {
setInput(e.target.value);
};
return [input, onChange];
}
export default useInput;
// HookExam.jsx
import useInput from "./../hooks/useInput";
// 3가지 hook 관련된 팁
// 1. 함수 컴포넌트, 커스텀 훅 내부에서만 호출 가능
// 2. 조건부로 호출될 수는 없다
// 3. 나만의 훅(Custom Hook) 직접 만들 수 있다
const HookExam = () => {
const [input, onChange] = useInput();
const [input2, onChange2] = useInput();
return (
<div>
<input value={input} onChange={onChange} />
<input value={input2} onChange={onChange2} />
</div>
);
};
export default HookExam;
2017년도 이전의 리액트에서는 클래스 컴포넌트에서만 useState, useRef같은 모든 기능들이 이용 가능했다. 함수 컴포넌트는 UI 렌더링밖에 되지 않았다. 클래스 컴포넌트에서 기능을 사용하는 문법이 복잡했기 때문에, 사람들은 함수 컴포넌트로 기능들을 가져왔다. 이를 '낚아채다'라는 의미의 hook에서 따와, 함수 컴포넌트의 이러한 기능들을 react hooks라고 한다.
useState, useRef같은 hook을 사용하기 위한 몇 가지 조건이 있다.
- 함수 컴포넌트, 커스텀 훅 내부에서만 호출 가능
- 조건부(if문이나 for문 내에서)로 호출될 수 없다.
- 나만의 훅(Custom Hook)을 만들 수 있다.
위의 HookExam같은 컴포넌트가 여러 개 있다면, 해당 컴포넌트 내에서 state을 생성하고 이벤트 핸들러를 만드는 코드가 여러 번 중복될 것이다. 우리는 이때 커스텀 훅을 이용하여 이 내용들을 함수화하고, 더 나아가 별도의 컴포넌트로 빼줄 수 있다. 커스텀 훅을 만드는 방법은 간단하다. 함수의 이름 앞에 use를 써준다. useInput 함수를 만들어 state 생성과 이벤트 핸들러가 동작하는 방식을 선언했다. onChange 이벤트가 발생하면 이벤트 핸들러가 실행되고, 이는 useInput 내부에서 생성된 setInput에 의해 값을 업데이트 한다.
참고로 이러한 커스텀 훅은 보통 컴포넌트 파일에 같이 두지 않고, src 파일 밑에 hooks 폴더를 만들어 별도 저장하여 관리한다.
배우다보니 객체 기반 통합 관리와 커스텀 훅을 사용하는 데 어떤 차이가 있는지 궁금해졌다. 객체 기반 통합 관리는 DOM 요소가 조금씩 다를 때 사용하고, 커스텀 훅은 완전히 동일한 형태의 DOM 요소를 반복하여 사용하는 것인가? 지피티한테 물어봤는데 맞는 것 같다. 전자는 서로 다른 입력 필드나 동적인 상태 관리에 유리하지만, 다른 컴포넌트에서 재사용할 수는 없다. 후자는 동일한 형태의 DOM 요소를 반복적으로 관리할 수 있어 재사용성이 뛰어나다.
| 특징 | 객체 기반 통합 관리 | 커스텀 훅 사용 |
| 사용 목적 | 여러 상태를 하나의 객체로 묶어 동적으로 관리 | 독립적인 상태 관리 로직을 재사용하거나 반복 적용 |
| DOM 요소 | 각기 다른 입력 필드 관리 | 동일한 형태의 DOM 요소 반복적으로 관리 |
| 재사용성 | 특정 컴포넌트 내에서만 사용 | 여러 컴포넌트에서 반복 재사용 가능 |
| 확장성 | 상태 속성을 객체에 추가하거나 통합 이벤트 핸들러를 수정 가능 | 로직은 훅에 집중, 확장 시 개별 훅 추가 가능 |
'프론트엔드 > 한 입 크기로 잘라먹는 리액트' 카테고리의 다른 글
| React.js - useReducer 소개 (0) | 2025.02.04 |
|---|---|
| React.js - 카운터 앱, State Lifting (0) | 2025.01.30 |
| React.js - 이벤트 처리하기, State로 상태관리하기, State와 Props, State로 사용자 입력 관리하기 1 (0) | 2025.01.26 |
| React.js - React 컴포넌트, JSX로 UI 표현하기, Props로 데이터 전달하기 (1) | 2025.01.22 |
| React.js - 소개, App 생성, App 구동 (1) | 2025.01.20 |