useState
는 컴포넌트에 state 변수를 추가할 수 있게 해주는 React 훅입니다.
1. 매개 변수
initialState
: 초기 state를 설정할 값2. 반환값(두 개의 값을 가진 배열)
initialState
와 일치)set(설정자)함수
import { useState } from "react";
export default function InputSample() {
const [text, setText] = useState("");
const onChange = (e) => {
setText(e.target.value);
};
const onReset = (e) => {
setText("");
};
return (
<div>
<input onChange={onChange} value={text} />
<button onClick={onReset}>초기화</button>
<div>
<b>값: {text}</b>
</div>
</div>
);
}
3. 주의사항
useState
는 훅이므로 컴포넌트의 최상위 레벨이나 커스텀훅에서만 호출할 수 있습니다. 반복문이나 조건문 안에서는 호출할 수 없습니다. 필요한 경우 새 컴포넌트를 추출하고 state를 그 안으로 옮기세요.4. 사용법
1️⃣ 이전 state를 기반으로 state 업데이트 하기
age
가 42라고 가정합니다. 이 핸들러는 setAge(age + 1)
를 세 번 호출합니다.
function handleClick() {
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
}
그러나 클릭을 해보면 age
는 45가 아니라 43이 됩니다! 이는 set
함수를 호출해도 이미 실행 중인 코드에서 age
state 변수가 업데이트되지 않기 때문입니다. 따라서 각 setAge(age + 1)
호출은 setAge(43)
이 됩니다.
이 문제를 해결하려면 다음 state 대신 setAge
에 업데이터 함수를 전달할 수 있습니다.
function handleClick() {
setAge(a => a + 1); // setAge(42 => 43)
setAge(a => a + 1); // setAge(43 => 44)
setAge(a => a + 1); // setAge(44 => 45)
}
여기서 a => a + 1
은 업데이터 함수입니다. 이 함수는 대기 중인 state
를 가져와서 다음 state
를 계산합니다.
React는 업데이터 함수를 큐에 넣습니다. 그러면 다음 렌더링 중에 동일한 순서로 호출합니다.
1. a => a + 1
은 대기 중인 state로 42를 받고 다음 state로 43을 반환합니다.
2. a => a + 1
은 대기 중인 state로 43를 받고 다음 state로 44을 반환합니다.
3. a => a + 1
은 대기 중인 state로 44를 받고 다음 state로 45을 반환합니다.
대기 중인 다른 업데이트가 없으므로, React는 결국 45를 현재 state로 저장합니다.
항상 업데이터를 사용하는 것이 더 좋을까?
사용해서 나쁠 건 없지만 항상 그래야만 하는 것은 아닙니다.
대부분의 경우, 이 두 가지 접근 방식에는 차이가 없습니다. 다만 동일한 이벤트 내에서 여러 업데이트를 수행하는 경우에는 업데이터가 도움이 될 수 있습니다. state 변수 자체에 접근하는 것이 어려운 경우에도 유용합니다.
2️⃣ 초기 state 다시 생성하지 않기
React는 초기 state를 한 번 저장하고 다음 렌더링부터는 이를 무시합니다.
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos());
}
createInitalTodos()
의 결과는 초기 렌더링에만 사용되지만, 여전히 모든 렌더링에서 이 함수를 호출하게 됩니다. 이는 큰 배열을 생성하거나 값비싼 계산을 수행하는 경우 낭비가 될 수 있습니다.
아 문제를 해결하기 위해 useState
에 초기화 함수를 전달합니다.
함수를 호출한 경과인 createInitialTodos()
가 아니라 함수 자체인 createInitialTodos
를 전달합니다.
3️⃣ key로 state 재설정하기
컴포넌트에 다른 key
를 전달하여 컴포넌트의 state를 재설정할 수 있습니다.
import { useState } from 'react';
export default function App() {
const [version, setVersion] = useState(0);
function handleReset() {
setVersion(version + 1);
}
return (
<>
<button onClick={handleReset}>Reset</button>
<Form key={version} />
</>
);
}
function Form() {
const [name, setName] = useState('Taylor');
return (
<>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<p>Hello, {name}.</p>
</>
);
}
useRef
는 렌더링에 필요하지 않은 값을 참조할 수 있는 React훅입니다.
1. 매개변수
initialValue
: ref객체의 current 프로퍼티 초기 설정값입니다. 여기에는 어떤 유형의 값이든 지정할 수 있습니다. 이 인자는 초기 렌더링 이후부터는 무시됩니다.2. 반환값(단일 프로퍼티를 가진 객체)
current
: 처음에는 전달한 initialValue
로 설정됩니다. 나중에는 다른 값으로 바꿀 수 있습니다. ref 객체를 JSX 노드의 ref
속성으로 React에 전달하면 React는 current
프로퍼티를 설정합니다.3. 주의사항
ref.current
프로퍼티는 state와 달리 변이할 수 있습니다. 그러나 렌더링에 사용되는 객체를 포함하는 경우 해당 객체를 변이해서는 안 됩니다.ref.current
프로퍼티를 변경해도 React는 컴포넌트를 다시 렌더링하지 않습니다. ref는 일반 JavaScript 객체이기 때문에 React는 사용자가 언제 변경했는지 알지 못합니다.4. 사용법
1️⃣ ref로 값 참조하기
ref
를 변경해도 리렌더링을 촉발하지 않습니다. 즉, ref는 컴포넌트의 시각적 출력에 영향을 미치지 않는 정보를 저장하는 데 적합합니다.
예를 들어, Interval ID를 저장했다가 나중에 불러와야 하는 경우 ref에 넣을 수 있습니다.
function handleStartClick() {
const intervalId = setInterval(() => {
// ...
}, 1000);
intervalRef.current = intervalId;
}
나중에 ref에서 해당 interval ID를 읽어 해당 interval을 취소할 수 있습니다.
function handleStopClick() {
const intervalId = intervalRef.current;
clearInterval(intervalId);
}
2️⃣ ref로 DOM 조작하기
먼저 초기값이 null인 ref객체를 선언합니다.
import { useRef } from 'react';
function MyComponent() {
const inputRef = useRef(null);
// ...
}
그런 다음 ref 객체를 ref 속성으로 조작하려는 DOM 노드의 JSX에 전달합니다.
// ...
return <input ref={inputRef} />;
React가 DOM 노드를 생성하고 화면에 그린 후, React는 ref 객체의 current 프로퍼티를 DOM 노드로 설정합니다. 이제 DOM 노드 <input>
에 접근해 focus()
와 같은 메서드를 호출할 수 있습니다.
function handleClick() {
inputRef.current.focus();
}
노드가 화면에서 제거되면 React는 current
프로퍼티를 다시 null
로 설정합니다.
Push & Single Source of Truth
Input Element
State
를 값으로 넘기고 그 State
를 다룰 수 있는 핸들러
를 콜백으로 넘긴다.const ControlledInput = () => {
const [value, setValue] = useState('');
const handleChange = ({target: { value }}) => {
setValue(value);
};
return <input value={value} onChange={handleChange} />;
}
Pull
HTML
처럼 DOM
에 제어되는 input Element
const UnControlledInput = () => {
const inputRef = useRef(null);
const handleSubmit = (event) => {
event.preventDefault();
// inputRef.current.value
};
return (
<form onSubmit={handleSubmit}>
<input ref={inputRef} />
<button type="submit">Submit</button>
</form>
);
};
제목1 | Controlled | Uncontrolled |
---|---|---|
지향점 | Push | Pull |
성능 | 잦은 리렌더링 | 구현하는 방법에 따라 다르지만 성능에 이점이 있을 수 있음 |
동적 핸들링 | 상태를 중심으로 개발하기 때문에 상태 변경에 따른 제어가 용이함. 이벤트 핸들러 관리 비용 지불 | DOM을 직접 조작하기 때문에 제어가 어려움 |
관찰&유효성 검사 | 상태 변경 => UI 자동으로 업데이트. 유저와 많은 인터렉션하는데 용이함 | DOM을 직접 조작하기 때문에 핸들링이 어렵고 값 비싼 비용 지불 |
1회성 값 검색 (ex. onSubmit) | ✅ | ✅ |
제출 이벤트 발생시 호출 | ✅ | ✅ |
실시간 필드 유효성 검사 지원 | ✅ | ❌ |
조건별 제출 버튼 비활성화 제어 기능 | ✅ | ❌ |
특정 입력 형식(input type) 강제 | ✅ | ❌ |
하나의 데이터를 여러 방식의 이벤트로 입력 가능 | ✅ | ❌ |
동적 입력 가능 | ✅ | ❌ |