이 글의 코드 예시와 설명은 유튜버 개발자 Ben Awad님의 React Hooks 튜토리얼과 카카오 프렌즈의 이재승 개발자님의 실전 리액트 프로그래밍과 리액트 훅스 공식 문서 등을 주로 참조하여 작성하였습니다
제 부족한 블로그에 위 자료들을 인용할 수 있도록 허락해주신 Ben Awad님과 이재승 개발자님께 감사드린다는 말 전해드리며, 리액트 공부하시는 분들께 이재승 개발자님의 책 추천드리며 이재승 개발자님 인프런 강의도 있기 때문에 책이 어려우신 분들은 강의를 보시는 것도 추천드립니다.
(The code examples and explanations are referenced from Ben Awad's Youtube tutorial. Thanks Ben for allowing me to use your codes on my blog!)
// Unlike the class component setState, the updates are not allowed to be partial
type SetStateAction<S> = S | ((prevState: S) => S);
// Since action _can_ be undefined, dispatch may be called without any parameters.
type Dispatch<A> = (value: A) => void;
/*** initalState을 지정한 경우 ***/
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
//Type S나 그를 리턴하는 함수를 입력받아 배열을 리턴
// 해당 배열의 0번째 인덱스는 해당 state을, 1번째 인덱스는 state을 지정하는 함수를 리턴(하단의 2번 예제 참조할 것)
/*** initalState을 지정하지 않은 경우 ***/
function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>];
import React, { useState } from 'react';
const App = () => {
const [count, setCount] = useState(10); // count = initialValue 10
return (
<div>
<button onClick={() => setCount(count + 1)}></button>
</div>
);
};
export default App;
import React, { useState } from 'react';
const App = () => {
const _useState = useState(0),
count = _useState[0],
setCount = _useState[1];
return (
<div>
<button onClick={() => setCount(count + 1)}></button>
</div>
);
};
export default App;
import React, { useState } from 'react';
const App = () => {
const [count, setCount] = useState(10);
return (
<div>
//currentCount는 parameter 명칭임... 아무렇게나 작성해도 됨
<button onClick={() => setCount((currentCount) => currentCount + 1)}>
click me
</button>
<div>{count}</div>
</div>
);
};
export default App;
import React, { useState } from 'react';
const App = () => {
const [count, setCount] = useState({ value: 0 });
const onClick = () => {
setCount({ value: count.value + 1 });
setCount({ value: count.value + 1 });
};
console.log('render called');
return (
<div>
<h2>{count.value}</h2>
<button onClick={onClick}>증가</button>
</div>
);
};
onClick이 invoke 될 때 setCount는 2번이 아니라 1번만 실행이 됨
State 변경 함수가 비동기로 작동하기 때문이며, 리액트는 효율적으로 렌더링 하기 위해 여러 개의 상탯값 변경 요청을 배치로 처리함
위 코드대로 작성해서 브라우저의 콘솔창을 보면, console.log('render called')
가 2번이 아니라 1번만 실행이 된 것을 확인할 수 있음
만일 리액트가 State 변경 함수를 동기로 처리한다면, 하나의 상태값 변경 함수가 호출 될 때마다 화면을 다시 그리기 때문에 성능 이슈가 발생할 수 있으며, 동기로 처리하지만 매번 화면을 다시 그리지 않는다면 UI 데이터와 화면 간의 불일치가 발생해서 혼라스러울 수 있기 때문에 State을 비동기/배치 방식으로 처리함
...currentState
에 count: currentState.count + 1
과 같이 변경할 state을 덮어주는 형식으로 진행해야 함 const App = () => {
const [{ count, count2 }, setCount] = useState({ count: 10, count2: 20 });
const onClick = () => {
setCount((currentState) => ({
...currentState,
count: currentState.count + 1,
}));
};
return (
<div>
<button onClick={onClick}>증가</button>
<div>count 1: {count}</div>
<div>count 2: {count2}</div>
</div>
);
};
const App = () => {
const [count, setCount] = useState(10);
const [count2, setCount2] = useState(20);
const onClick = () => {
setCount(() => count + 1);
setCount2(() => count2 + 1);
};
return (
<div>
<button onClick={onClick}>증가</button>
<div>count 1: {count}</div>
<div>count 2: {count2}</div>
</div>
);
};
useState의 가장 큰 장점은 custom hooks를 만들 수 있다는 점인데요..
다음은 useState으로 custom hooks를 만드는 예시입니다.
import { useState } from 'react';
export const useForm = (initialValues) => {
const [values, setValues] = useState(initialValues);
return [
values,
(e) => {
setValues({
...values,
[e.target.name]: e.target.name,
});
},
];
};
import React from 'react';
import { useForm } from './useForm';
const App = () => {
const [values, handleChange] = useForm({ email: '', password: '' });
return (
<div>
<input name='email' value={values.email} onChange={handleChange} />
<input
type='password'
name='password'
value={values.password}
onChange={handleChange}
/>
</div>
);
};
export default App;
import React, { useEffect, useState } from 'react';
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const onClick = () => {
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
};
window.addEventListener('click', onClick);
return () => window.removeEventListener('click', onClick);
});
console.log('render called');
return <div>{count}</div>;
};
위 예시의 경우, 이벤트 처리 함수를 window 객체에 추가함으로서 리액트 내부에서 구동이 되지 않기 때문에 window 화면을 클릭할 경우 setCount
와 console.log('render called')
가 2번 호출 됨.
만일 리액트 외부에서 관리되는 이벤트 처리 함수에도 State 변경을 배치로 처리하고자 한다면, unstable_batchedUpdate
함수를 이용하면 됨
대신에 unsateble_batchedUpdate
을 많이 이용하는 것은 권장되지 않음
아래를 보면 setCount는 2번 실행이 되기는 했지만, console.log('render called')
는 1번 밖에 실행이 안 된 것을 확인할 수 있음.
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const onClick = () => {
ReactDOM.unstable_batchedUpdates(() => {
setCount((prev) => prev + 1);
setCount((prev) => prev + 1);
});
};
window.addEventListener('click', onClick);
return () => window.removeEventListener('click', onClick);
});
console.log('render called');
return <div>{count}</div>;
};