useState
참조하기
useState
function MyComponent() {
const [age, setAge] = useState(28);
const [name, setName] = useState('Taylor');
const [todos, setTodos] = useState(() => createTodos());
- 컴포넌트의 최상위 레벨에서 useState를 호출하여 state 변수를 선언하세요.
- 배열 구조 분해를 사용하여 [something, setSomething]과 같은 state 변수의 이름을 지정하는 것이 일반적입니다.
매개변수
- initialState : 초기에 state를 설정할 값입니다. 값은 모든 데이터 타입이 허용되며, 이 인자는 초기 렌더링 이후에는 무시됩니다.
- 함수를 initialState로 전달하면 이를 초기화 함수로 취급합니다. React는 컴포넌트를 초기화할 때 초기화 함수를 호출하고, 그 반환값을 초기 state로 저장합니다.
- 순수 함수여야 합니다.
- 인자를 받지 않아야 합니다.
- 반드시 어떤 값을 반환해야 합니다.
반환값
- useState는 두 개의 값을 가진 배열을 반환합니다.
- 첫 번째 : 현재 state입니다. 첫 번째 렌더링 중에는 전달한 initialState와 일치합니다.
- 두 번째 : state를 다른 값으로 업데이트하고 리렌더링을 촉발할 수 있는 set 함수입니다.
주의사항
- useState는 훅이므로 컴포넌트의 최상위 레벨이나 직접 만든 훅에서만 호출할 수 있습니다.
- 반복문이나 조건문 안에서 호출하고 싶다면새 컴포넌트를 생성하고 state를 그 안으로 옮기세요
- Strict Mode에서 React는 순수 함수인지 확인하기 위해 초기화 함수를 두 번 호출합니다. 초기화 함수가 순수하다면 동작에 영향을 미치지 않습니다. 호출 중 하나의 결과는 무시됩니다.
setSomething(nextState)
매개변수
- nextState: state가 될 값입니다.
- 함수를 nextState로 전달하면 업데이터 함수로 취급됩니다.
- 함수는 순수해야 합니다.
- 대기중인 state를 유일한 인수로 사용해야 합니다.
- 다음 state를 반환해야 합니다.
- React는 업데이터 함수를 대기열에 넣고 컴포넌트를 리렌더링 합니다.
- 다음 렌더링 중에 React는 대기열에 있는 모든 업데이터를 이전 state에 적용하여 다음 state를 계산합니다.
반환값
주의사항
- set 함수는 다음 렌더링에 대한 state 변수만 업데이트 합니다. set 함수를 호출한 후에도 state 변수에는 이전 값이 담겨 있습니다.
- 사용자가 제공한 새로운 값이 Object.is에 의해 현재 state와 동일하다고 판단되면, React는 컴포넌트와 그 자식들을 리렌더링하지 않습니다.
- React는 state 업데이트를 일괄처리합니다. 모든 이벤트 핸들러가 실행되고 set 함수를 호출한 후에 화면을 업데이트 합니다.
- 렌더링 도중 set 함수를 호출하는 것은 현재 렌더링 중인 컴포넌트 내에서만 허용됩니다. React는 해당 출력을 버리고 즉시 새로운 state로 다시 렌더링을 시도합니다. 이전 렌더링의 정보를 저장하는 데 사용할 수 있습니다.
- Strict Mode에서 React는 의도치않은 불순물을 찾기 위해 업데이터 함수를 두 번 호출합니다.
사용법
컴포넌트에 state 추가하기
function MyComponent() {
const [age, setAge] = useState(42);
const [name, setName] = useState('Taylor');
- useState는 정확히 두 개의 요소가 있는 배열을 반환합니다.
- 현재 state로, 처음에 제공한 초기 state로 설정됩니다.
- 상호작용이 반응하여 다른 값으로 변경할 수 있는 set 함수입니다.
- 화면의 내용을 업데이트하려면 다음 state로 set 함수를 호출합니다.
function handleClick() {
setName('Robin');
console.log(name);
}
이전 state를 기반으로 state 업데이트하기
function handleClick() {
setAge(age + 1);
setAge(age + 1);
setAge(age + 1);
}
- handleClick이 실행되었을 때 age가 +3 될 것이라고 예상하지만 실제로는 +1로 동작합니다.
- set 함수는 비동기적으로 발동하기 때문에 이미 실행 중인 코드에서는 state 변수가 업데이트되지 않기 때문입니다.
function handleClick() {
setAge(a => a + 1);
setAge(a => a + 1);
setAge(a => a + 1);
}
- 해당 문제를 해결하기 위해 state대신 setAge에 업데이터 함수를 전달할 수 있습니다.
- 업데이터 함수는 대기 중인 state를 가져와서 다음 state를 계산합니다.
- React는 업데이터 함수를 큐에 넣습니다. 그러면 다음 렌더링 중에 동일한 순서로 호출합니다.
- 대기 중인 다른 업데이트가 없으면 React는 결국 45를 현재 state로 저장합니다.
- 업데이터 함수의 인수는 age의 a와 같이 state 변수 이름의 첫 글자 또는 prevAge 같은 명확한 이름으로 지정해야 합니다.
- 동일한 이벤트 내에서 여러 업데이트를 수행하는 경우에 유용합니다.
객체 및 배열 state 업데이트
- React의 state는 읽기 전용으로 간주되므로 기존 객체를 변이하지 않고, 교체를 해야 합니다.
form.firstName = 'Taylor';
setForm({
...form,
firstName: 'Taylor'
});
중첩 객체 업데이트
function handleTitleChange(e) {
setPerson({
...person,
artwork: {
...person.artwork,
title: e.target.value
}
});
}
- 중첩된 객체를 업데이트하기 위해서는 업데이트 하려는 객체의 복사본을 만들어야하며, 위쪽으로 올라갈 때마다 해당 객체를 "포함하는" 모든 객체에 대한 복사본을 만들어야 합니다.
중첩 배열 업데이트
function handleAddTodo(title) {
setTodos([
...todos,
{
id: nextId++,
title: title,
done: false
}
]);
}
function handleChangeTodo(nextTodo) {
setTodos(todos.map(t => {
if (t.id === nextTodo.id) {
return nextTodo;
} else {
return t;
}
}));
}
😀 초기 state 다시 생성하지 않기
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos());
- createInitialTodos()의 결과는 초기 렌더링에 사용되지만, 여전히 모든 렌더링에서 이 함수를 호출하게 됩니다.
- 이는 큰 배열을 생성하거나 값비싼 계산을 수행하는 경우 낭비가 될 수 있습니다.
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos);
- 이 문제를 해결하려면, useState에 초기화 함수로 전달하세요.
- 함수를 호출한 결과인 createInitialTodos()가 아니라 함수 자체인 createInitialTodos를 전달하면, React는 초기화 중에만 함수를 호출합니다. (이건 저도 몰랐던 부분이네요)
😀 key로 state 재설정하기
- 목록을 렌더링할 때 key 속성을 자주 접하게 됩니다. 하지만 key 속성은 다른 용도로도 사용됩니다.
- 컴포넌트에 다른 key를 전달하여 컴포넌트의 state를 재설정할 수 있습니다.
- 이 예제에서는 React 버튼이 version state 변수를 변경하고, 이를 Form에 key로 전달합니다. key가 변경되면 React는 Form 컴포넌트를 처음부터 다시 생성하므로 state가 초기화 됩니다.
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>
</>
);
}
😀 이전 렌더링에서 얻은 정보 저장하기
- 보통은 이벤트 핸들러에서 state를 업데이트 합니다. 하지만 드물게 렌더링에 대한 응답으로 state를 조정해야 하는 경우도 있습니다.
- 예를 들어, props가 변경될 때 state 변수를 변경하고 싶을 수 있습니다.
- 대부분의 경우 이 기능은 필요하지 않습니다.
- 필요한 값을 현재 props나 다른 state에서 모두 계산할 수 있는 경우, 중복되는 state를 모두 제거하세요.
- 너무 자주 재계산하는 것이 걱정된다면, useMemo hook을 사용하면 도움이 됩니다.
- 전체 컴포넌트 트리의 state를 재설정하려면 컴포넌트에 다른 key를 전달하세요.
- 가능하다면 이벤트 핸들러의 모든 관련 state를 업데이트하세요.
- 이 중 어느 것에도 해당하지 않는 희귀한 경우에는, 컴포넌트가 렌더링되는 동안 set 함수를 호출하여 지금까지 렌더링된 값을 기반으로 state를 업데이트하는 데 사용할 수 있는 패턴이 있습니다.
- CountLabel 컴포넌트는 전달된 count props를 표현합니다. 카운터가 마지막 변경 이후 증가 또는 감소했는지를 표시하고 싶다고 가정해 보겠습니다.
- 이를 추적하기 위해 prevCount state 변수를 추가합니다. 그리고 trend라는 또 다른 state 변수를 추가하여 count의 증가 또는 감소 여부를 추적합니다.
- prevCount와 count를 비교해서, 같지 않은 경우 prevCount와 trend를 모두 업데이트합니다.
export default function CountLabel({ count }) {
const [prevCount, setPrevCount] = useState(count);
const [trend, setTrend] = useState(null);
if (prevCount !== count) {
setPrevCount(count);
setTrend(count > prevCount ? 'increasing' : 'decreasing');
}
return (
<>
<h1>{count}</h1>
{trend && <p>The count is {trend}</p>}
</>
);
}
- 렌더링하는 동안 set 함수를 호출하는 경우, 그 set 함수는 prevCount !== count와 같은 조건 안에 있어야 하며, 조건 내부에 setPrevCount(count)와 같은 호출이 있어야합니다.
- 그렇지 않으면 리렌더링을 반복하다가 결국 깨질 것입니다. 또한 이 방식은 오직 현재 렌더링 중인 컴포넌트의 state만을 업데이트 할 수 있습니다.
- 렌더링 중에 다른 컴포넌트의 set 함수를 호출하는 것은 에러입니다. 마지막으로, 이 경우에도 set 함수 호출은 여전히 변이가 아닌 state 업데이트여야 합니다. 순수 함수의 다른 규칙을 지켜야합니다.
- 이 패턴은 일반적으로 피하는 것이 좋습니다. 하지만 Effect에서 state를 업데이트하는 것보다는 낫습니다.
- 렌더링 도중 set 함수를 호출하면 React는 컴포넌트가 return문으로 종료된 직후, 자식을 렌더링하기 전에, 해당 컴포넌트를 리렌더링합니다.
- 이렇게 하면 자식 컴포넌트를 두 번 렌더링할 필요가 없습니다.
- 나머지 컴포넌트 함수는 계속 실행되고 결과는 버려집니다.
- 조건이 모든 훅 호출보다 아래에 있으면 이른 return을 통해 렌더링을 더 일찍 다시 시작할 수 있습니다.
문제 해결
1. state를 업데이트 했지만 로그에는 계속 이전 값이 표시됩니다.
function handleClick() {
console.log(count);
setCount(count + 1);
console.log(count);
setTimeout(() => {
console.log(count);
}, 5000);
}
- set 함수를 호출해도 실행 중인 코드의 state는 변경되지 않습니다.
- 이유는 state가 스냅샷처럼 동작하기 때문입니다. state를 업데이트하면 새로운 state 값으로 다른 렌더링을 요청하지만 이미 실행 중인 이벤트 핸들러의 count 변수에는 영향을 미치지 않습니다.
- 다음 state를 사용해야 하는 경우에는, set 함수에 전달하기 전에 변수를 저장해야 합니다.
const nextCount = count + 1;
setCount(nextCount);
console.log(count);
console.log(nextCount);
2. state를 업데이트해도 화면이 바뀌지 않습니다.
obj.x = 10;
setObj(obj);
- React는 Object.is 비교 결과 다음 state가 이전 state와 같으면 업데이트를 무시합니다. 이는 보통 객체나 배열의 state를 직접 변경(변이)할 때 발생합니다.
setObj({
...obj,
x: 10
});
- 이를 해결하기 위해서는 항상 객체나 배열 state를 변이하는 대신 교체해야 합니다.
3. 에러가 발생했습니다: 리렌더링 횟수가 너무 많습니다.
- 전형적으로 이는 렌더링 중에 state를 무조건적으로 설정하고 있음을 의미 하기 때문에, 컴포넌트가 렌더링, state 설정(렌더링 유발), 렌더링, state 설정(렌더링 유발) 등의 루프에 들어가는 것입니다. 이 문제는 이벤트 핸들러를 지정하는 과정에서 많이 나옵니다.
return <button onClick={handleClick()}>Click me</button>
return <button onClick={handleClick}>Click me</button>
return <button onClick={(e) => handleClick(e)}>Click me</button>
4. 초기화 함수 또는 업데이터 함수가 두 번 실행됩니다.
- Strict Mode에서 React는 일부 함수를 한 번이 아닌 두 번 호출합니다. 이는 정상적인 현상이며, 이로 인해 코드가 손상되지 않아야 합니다.
- Strict Mode는 컴포넌트를 순수하게 유지하는 데 도움이 됩니다. React는 호출 중 하나의 결과를 사용하고 다른 호출의 결과는 무시합니다.
- 컴포넌트, 초기화 함수, 업데이터 함수가 순수하다면 이 동작은 로직에 영향을 미치지 않습니다.
- 반면 의도치 않게 순수하지 않을 경우에는 실수를 알아차리는 데 도움이 됩니다.
setTodos(prevTodos => {
prevTodos.push(createTodo());
});
setTodos(prevTodos => {
return [...prevTodos, createTodo()];
});
- 컴포넌트, 초기화 함수, 업데이터 함수는 순수해야 합니다
5. state의 값으로 함수를 설정하려고 하면 설정은 안되고 대신 호출됩니다.
- state에 함수를 넣을 수는 없습니다.
- useState에 함수를 전달하면 React는 초기화 함수로 여기고, setFn에 함수를 전달하면 업데이터 함수로 받아들입니다.
- 따라서 이들을 호출해서 그 결과를 저장하려고 시도합니다. 정말로 함수를 저장하길 원하다면, 함수 앞에 () => 를 넣어야 합니다.
const [fn, setFn] = useState(someFunction);
function handleClick() {
setFn(someOtherFunction);
}
const [fn, setFn] = useState(() => someFunction);
function handleClick() {
setFn(() => someOtherFunction);
}
Reference
정리가 잘 된 글이네요. 도움이 됐습니다.