[패스트캠퍼스 수강 후기] 프론트엔드 인강 100% 환급 챌린지 25회차 미션 - 30강 React useEffect, useMemo
useEffect Hook
리액트 화면이 처음 나타날때, 상태가 바뀔때, 랜더링이 될때마다 어떤 작업을 수행할 수 있다. 한번 코드로 확인해보도록 하자. 우선 우리가 만든 UserList가 마운트될때와 언마운트될때 (나타날때, 사라질때) 효과를 줘 볼 것이다.
UserList.js
import React, { useEffect } from 'react'; //1. useEffect 선언
function User({ user, onRemove, onToggle }){
const {username, email, id, active} = user;
useEffect(()=>{ //2. 첫번째 파라미터에는 마운트시 실행할 것을 넣어줌.
console.log('컴포넌트가 화면에 나타남 - 마운트');
return () => { //4. 함수를 반환하면 삭제할때 실행됨. = 클리너함수
console.log('컴포넌트가 화면에서 사라짐 - 언마운트');
}
}, [])//3. 두번째 파라미터에는 비어있는 배열을 넣어줄건데 이 배열을 deps라고 부름(dependency) 의존되는 값을 배열에 넣어줄 껀데 만약에 그 의존되는 배열값이 비어있으면 처음 화면에 나타날때만 실행이 됨.
return(
<div>
<b
style=
onClick={() => onToggle(id)}
>
{username}
</b>
<span>({email})</span>
<button onClick={() => onRemove(id)}>삭제</button>
</div>
)
}
function UserList({ users, onRemove, onToggle }){
return(
<div>
{
users.map(
(user, index) => (
<User
user={user}
key={user.id}
onRemove={onRemove}
onToggle={onToggle}
/>)
)
}
</div>
)
}
export default UserList;
위와 같은 순서대로 useEffect를 상단에 선언해주고 User함수 안에서 useEffect를 불러와보자.
먼저 컴포넌트가 마운트될때 (화면에 UI가 나타날때) 우리가 주로 수행하는 일들은
- props => state : 프로프로 받은 값을 스테이트로 설정하는 것
- REST API : 외부 API요청할때
- D3 Video.js : 라이브러리를 사용할때
- setInterval, setTimeout
그리고 참고로 마운트 된 상태란, 우리의 UI가 이미 나오고 난 상태 이후이다. 여기서 바로 DOM에 접근하는 작업들을 해도 문제가 없다. (j-query 코딩시를 떠올려보면 $(window).load에 넣던 내용이지 않을까 싶음.)
두번째로 컴포넌트가 언마운트될때 (화면에 UI가 사라질때) 우리가 주로 수행하는 일은
- clearInterval, clearTimeout : setInterval, setTimeout을 제거할때
- 라이브러리 인스턴스를 제거할때
이렇게 있다. 마운트는 위와 같이 useEffect를 불러오면서 써주면 되고, 언마운트는 return에 함수를 만들면서 써주면 된다. 그리고 useEffect의 두번째 파라미터인 ‘,’하고 써주는 부분에 빈 배열을 넣으면 화면 UI가 처음 보일때만 실행한다. 만약 이곳에 props로 받아온 값을 넣어주면 그 props가 리랜더링 된다던지, 업데이트 될때도 실행이 된다. 그 부분에 대해서 코드로 더 알아보도록 하자.
UserList.js 에서 User함수만
function User({ user, onRemove, onToggle }){
const {username, email, id, active} = user;
useEffect(()=>{
console.log('user 값이 설정됨');
console.log(user);
return () => {
console.log('user 값이 바뀌기 전');
console.log(user);
}
},[user]);
return(
<div>
<b
style=
onClick={() => onToggle(id)}
>
{username}
</b>
<span>({email})</span>
<button onClick={() => onRemove(id)}>삭제</button>
</div>
)
}
위와 같이 두번째 파라미터 부분이 빈 배열이였을땐 전체 화면이 업데이트 될때만 useEffect가 실행됐었는데, 배열에 user로 받아온 props를 넣어주니 그 부분이 업데이트 될때마다 useEffect가 실행되는 것을 볼 수 있다.
여기서 기억해야할 것은 만약에 useEffect에서 props를 사용했는데 배열에 그 props를 넣어주지 않으면 최신 props상태를 받아오지 못해 제대로 작동이 안될 수도 있다는 점이다. 이 부분을 꼭 유의하도록 하자.
그런데 만약 배열부분(=deps, =dependency)을 아예 안쓰면 어떻게 될까?
User컴포넌트의 부모 컴포넌트인 UserList컴포넌트 전체가 리랜더링 된다. 나중에 항목들이 많아지게 된다면 deps를 안쓰면 전체를 리랜더링 하니까 느려질 수도 있다.
정리하자면
- useEffect를 사용할때에는 첫번째 파라미터에는 함수를 등록하고 두번째 파라미터에는 deps라는 배열을 등록한다.
- return 함수를 반환하게 되면 뒷정리해주는 cleaner함수여서 UI가 사라질 때(and 업데이트 되기 바로 직전) 호출이 된다.
- 함수내에서 props를 사용해줬다면 deps배열에 꼭 등록을 해줘야 문제가 안생긴다.
- deps배열이 비어있다면 컴포넌트가 처음 나타날때에만 호출이 된다.
- deps배열에 props가 들어있다면, useEffect안의 함수는 컴포넌트가 처음 나타날때, props가 업데이트될때 호출이 되며, 그 안에 있는 cleaner함수는 props가 바뀌가 바로 직전에도 호출이 되고, 컴포넌트가 사라지기 전에도 호출이 된다.
useMemo Hook
이전에 연산된 값을 재사용하는 방법에 대해서 알아보자. 이 함수는 성능을 최적화해야하는 상황에서 사용한다. 우선 우리가 만든 App.js에서 활성화된 active: true 유저의 수만 세주는 함수를 만든다고 가정을 해보자.
App.js
import React, { useRef, useState } from 'react';
import UserList from './UserList';
import CreateUser from './CreateUser';
function countActiveUsers(users){ //활성 사용자 수를 세는 함수 추가
console.log('활성 사용자 수를 세는중...');
return users.filter(user => user.active).length;
}
function App() {
const [inputs, setInputs] = useState({
username: '',
email: '',
})
const { username, email } = inputs;
const onChange = e =>{
const { name, value } = e.target;
setInputs({
...inputs,
[name]: value
});
}
const [users, setUsers] = useState([
{
id: 1,
username: 'bmyu',
email: 'bomee88@naver.com',
active: true,
},
{
id: 2,
username: 'tester',
email: 'tester@example.com',
active: false,
},
{
id: 3,
username: 'sample',
email: 'sample@example.com',
active: false,
}
]);
const nextId = useRef(4);
const onCreate = () => {
const user = {
id: nextId.current,
username,
email,
};
setUsers(users.concat(user));
setInputs({
username: '',
email:''
})
console.log(nextId.current);
nextId.current += 1;
}
const onRemove = id => {
setUsers(users.filter(user => user.id !== id));
}
const onToggle = id => {
setUsers(users.map(
user => user.id ===id? { ...user, active: !user.active }
:user
))
}
const count = countActiveUsers(users); //활성 사용자 수를 받아오는 count 변수 만듬.
return(
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle}/>
<div
//활성 사용자 수를 받아오는 div 추가
>활성 사용자 수 : {count}</div>
</>
)
}
export default App;
그럼 위의 화면에서 우리가 사용자 이름을 활성화/비활성화 할때마다 활성 사용자 수를 보여주는 것을 볼 수 있다. 그런데 여기서 문제는 input에 어떤 값을 써넣기만 해도 countActiveUser함수가 호출되는 것을 콘솔로 확인할 수 있다. 이것을 고치고 싶을때 useMemo Hook을 사용한다.
useMemo는 특정 값이 바뀌었을 때만 특정 함수를 사용해서 연산을 하도록 처리하고 만약에 우리가 원하는 값이 바뀌지 않았다면 리랜더링 할때 이전에 만들어놨던 값을 재사용할 수 있게 해준다. App.js의 일부를 이렇게 바꿔보자.
App.js 의 상단과 const count부분
import React, { useRef, useState, useMemo } from 'react';
// 중략
const count = useMemo(() => countActiveUsers(users),[users]);
countActiveUsers(users)
부분은 useMemo로 감싸준다. useEffect와 같이 useMemo도 첫번째 파라미터는 함수를, 두번째 deps 배열에는 바뀌는 것을 참조할 props를 넣어준다. 결국 deps의 값이 바뀌어야만 앞의 파라미터에 들어있는 함수를 실행시키는 것이다.
이렇게 useMemo를 사용하면 우리가 필요한때 필요한 함수만 사용할 수 있다는 것, 이것이 곧 성능 최적화라는 것을 잘 알아두면 되겠다!
오늘은 여기까지..
시청 영상 30강 17~18까지
프론트엔드 개발 올인원 패키지 with React Online. 👉 https://bit.ly/31Cf1hp
Comments