React 생초보 탈출하기 1🌹
공부했던 내용을 복습하기 위해 작성된 글이므로, 틀린 부분이나 부족한 부분이 있다면 댓글 부탁드립니다.😆🙌🏻
1. useState(초기값)
Component 내에서 사용될 state와 해당 state를 변경하는 함수를 반환해줍니다.
// count: state, setCount: 값 변경
const [count, setCount] = useState(0);
// 초기값은 원시값만이 아닌 객체도 전달할 수 있습니다.
const [position, setPosition] = useState({x:0,y:0});
이렇게 생성된 state들은 element의 value 속성에 사용될 수도 있으며, setter 메서드를 통해 값이 변경되면
관련된 element들을 re-rendering 시켜주게 됩니다.😊👍
input을 예롤 들어보겠습니다.
function example() {
const [value, setValue] = useState('');
return (
<input type="text" value={value} onChange={(e) => setValue(e.target.value)}/>
)
}
위의 코드에서는 input에 text를 작성하게 되면 onChange 이벤트 콜백 메서드에 의해 setValue가 호출되고 value의 값이 변경됩니다.
따라서 화면이 re-rendering되어 input의 value값도 함께 변경되는 것을 알 수 있습니다.
2. useEffect(function, dependencies)
useEffect는 Component가 mount 또는 unmount되었을 때, 또는 특정 state(dependencies)가 변경되었을 때 호출되어 특정 작업을 처리할 수 있는 hook 입니다.
mount / unmount
useEffect의 두번째 인자는 dependency 값으로 배열의 형태로 전달하게 되며, 만약 빈 배열을 넘길 경우
Component가 처음 화면에 나타날 때에만 호출되어 전달된 함수를 실행하게 됩니다.
useEffect(() => {
// 처음 Component가 화면에 나타났을 때 실행할 함수
// 주로 전역적인 Event 등록에 사용됩니다.
}, [])
🔥 useEffect에 전달된 function에서는 함수를 반환할 수 있는데, 이를 clearup 함수라고 합니다.
왜 cleanup 함수일까?🧐
바로 Component가 화면에서 제거될 때(unmount 될 때) 호출되는 함수이기 때문입니다.
🔥 만약 useEffect내에서 등록한 전역 Event 또는 비동기적으로 실행되는 코드가 있다면, 반드시 cleanup 함수에서 제거해주어야 합니다.!!
그럼 dependency 배열에 특정 값을 넣는다면 어떻게 동작하게 될까요?
바로 위와 같이 mount 시에도 호출되지만, 전달된 값이 바뀔 때에도 변경될 때마다 호출이 되게 됩니다.
useEffect(() => {
// 전달된 count의 값이 변경될 때마다 호출
}, [count]);
// 물론 여러 개의 dependency도 전달할 수 있습니다.
useEffect(() => {
// count 또는 name의 값이 변경될 때마다 호출
}, [count, name]);
🔥 useEffect 내에서 사용되는 state, props, method가 있다면 dependency로 전달해주어야 합니다.
이 때, 갑자기 드는 궁금증! 두 번째 인자(deps)를 전달해주지 않으면 어떻게 될까?
만약 useEffect의 두번째 인자를 전달해 주지 않을 경우, 해당 Component가 re-rendering 될 때마다 호출된다고 합니다.(주의!)
3. useMemo(function, deps Array)
useMemo는 잦은 re-rendering이 발생하는 React 환경에서 성능을 최적화하기 위해 사용되는 hook입니다.
React에서는 re-rendering이 이루어지게 되면 Component내에 있는 모든 코드를 다시 수행하게 됩니다.🤨
예를 들어, 아래와 같은 코드가 있다고 합시다.
function example() {
const [count, setCount] = useState(0);
const [userId, setUserId] = useState('');
const user = getUser(userId);
return (
<div>
{count}
{user.name}
<button onClick={()=>setCount((prev) => prev+1)}>+</button>
</div>
)
}
위의 코드에서 count 값이 변경되게 되면, example Component는 re-rendering될 것이고, 앞에서 말한 것처럼 코드를 다시 한 줄씩 실행하게 될 것입니다.
이 때 만약 getUser(id)가 빠르게 끝나는 함수가 아니라 서버와의 통신과 같은 많은 resource가 필요한 함수일 때는 성능에 좋지 않습니다.😩
이럴 때 사용할 수 있는 것이 바로 useMemo hook입니다.
저희는 userId의 값이 변경될 때에만 getUser를 호출해 user 값을 받아오고 싶습니다.
const user = useMemo(() => getUser(userId), [userId);
이제는 userId의 값이 변경될 때에만 getUser 메서드를 호출해 사용자 정보를 받아게 됩니다.
여기서 잠깐! 설명을 보다보면 useEffect와 비슷합니다. useEffect도 전달된 deps가 변경되었을 때에만 호출되기 때문입니다.
🔥 useMemo는 연산을 수행한 결과값을 반환하는 반면, useEffect는 연산만을 수행하고 Component가 제거될 때 실행할 함수를 반환하게 됩니다.
📌 React.memo
React에서는 Component에서 사용하는 state 또는 전달된 props가 변경되었을때 re-rendering을 수행하기도 하지만
부모 Component가 변경되었을 때도 따라서 re-rendering을 수행하게 됩니다.
만약 부모 Component가 변경되어도 아무런 변화가 없는 자식 Component라면? 이는 자원 낭비일 수 밖에 없습니다.😩
이 때 사용할 수 있는 것이 React.memo입니다.
React.memo는 부모 Component가 변경되었을 때 자식 Component에 아무런 영향이 없다면 re-rendering 하지 않도록 최적화 해주게 됩니다.
// 부모 Component
function Parent() {
const [count, setCount] = useState(0);
return (
<>
{count}
<Child/>
</>
)
}
// 자식 Component
function Child() {
return <div>Hello World!</div>
}
위의 코드에서 부모 Component의 값이 변경되게 되면 자식 Component인 Child까지 모두 다시 화면을 갱신하게 됩니다.
하지만 코드에서도 알 수 있듯이 부모 Component가 변경되어도 자식 Component는 변경될 것이 없어 불필요한 갱신이 발생하게 됩니다.
이를 위해 아래 처럼 React.memo를 사용할 수 있습니다.
// 자식 Component
const Child = React.memo(() => {
return <div>Hello World!</div>
});
4. useCallback(function, deps Array)
useCallback은 useMemo와 비슷하게 재생성될 필요 없는 함수를 memoization하는데 사용됩니다.
위에서도 말했듯이 Component가 re-rendering하게 되면 내부 코드를 하나씩 수행되게 되며,
이는 함수 선언문과 함수 표현식도 마찬가지로 적용되게 됩니다.
function example() {
const [count, setCount] = useState(0);
const handleInputChange = (e) => {
console.log(e.target.value);
};
return (
<div>
{count}
<input type="text" onChange={handleInputChange}/>
</div>
);
}
위의 코드에서 count 값이 변경되어 example Component가 re-rendering될 때마 handleInputChange 함수가 새로 생성되게 됩니다. 물론 함수를 생성하는 작업 때문에 성능에 크게 문제가 생길일 은 없지만,
아래와 같은 코드에서는 어떨까요?
// 부모 Component
function Parent() {
const [count, setCount] = useState(0);
const handleIncrease = () => {
setCount(count + 1);
};
return (
<>
{count}
<Child onIncrease={handleIncrease}/>
</>
);
}
// 자식 Component
const Child = React.memo(({onIncrease}) => {
return (
<div><button onClick={onIncrease}>+</button></div>
);
});
어떠한 이유로 Parent Component가 재호출될 경우, Child Component에는 React.memo를 사용해주었으니 Child Component는 아무런 영향이 없다고 생각하실 수 있습니다.
하지만 실제로 확인해보시면! Child Component 또한 re-rendering이 될 것입니다.
왜 다시 호출된 것일까요? 분면 React.memo를 사용해 줬는데...😭
🌹 바로 Child Component로 전달되는 props인 handleIncrease 함수가 새로 생성되었기 때문입니다.
이럴 때 사용할 수 있는 것이 바로 useCallback hook입니다.
function example() {
const [count, setCount] = useState(0);
const handleInputChange = useCallback((e) => {
console.log(e.target.value);
}, []);
return (
<div>
{count}
<input type="text" onChange={handleInputChange}/>
</div>
);
}
위와 같이 useCallback을 사용할 경우, 전달된 dependency 값이 변경되지 않는다면, 첫번째 인자로 전달된 함수를 새로 생성하는 것이 아닌,
이전에 생성해 두었던 함수를 가지고 와 초기화해주게 됩니다. (이것이 바로 메모이제이션!😆👍)
React를 공부하다 보니 다양한 기능을 제공하는 hook에 대해서 공부해볼 수 있게 되었습니다.🤗
이제 이들을 활용한 custom hook을 만들어봐야 겠어요.ㅎㅎ
앞으로도 공부하다가 알게된 새로운 React hook에 대해서도 꾸준히 작성해 나갈 예정입니다.👍
위에서도 말씀드렸듯이 혹시 틀린 점이나 추가해야될 사항이 있다면 댓글로 남겨주시면 감사드립니다.!!😆