useEffect
: 컴포넌트가 마운트 (처음 나타났을 때), 언마운트 (사라질 때), 업데이트 (특정 props가 바뀔 때) 특정 작업을 처리
- 컴포넌트의 내용이 업데이트되면 기존의 값을 언마운트하고 새로운 값을 마운트함
- 첫 번째 파라미터에는 함수, 두 번째 파라미터에는 의존값이 들어있는 배열 (deps) 넣기
- useEffect 안에서 사용하는 상태나 props 가 있다면, useEffect 의 deps 에 넣어주기 (useEffect가 최신 props / 상태를 가리키기 위함)
- useEffect 에서 함수를 return할 수 있음 -> cleanup 함수 (뒷정리)
1. deps 가 빈배열
useEffect(() => { // 마운트
console.log('컴포넌트가 화면에 나타남');
return () => { // 언마운트
console.log('컴포넌트가 화면에서 사라짐');
};
}, []);
- 컴포넌트가 처음 마운트될 때 useEffect 내 함수 호출 (componentDidmount)
- 컴포넌트가 언마운트될 때 cleanup 함수 호출 (componentWillUnmount)
2. deps 에 의존 값 있음
useEffect(() => { // 마운트
console.log('user 값이 설정됨');
console.log(user);
return () => { // 언마운트
console.log('user 가 바뀌기 전..');
console.log(user);
};
}, [user]);
- 컴포넌트가 처음 마운트될 때 useEffect 내 함수 호출 (componentDidmount)
- 의존 값이 업데이트 됐을 때 (componentDidUpdate)
- 컴포넌트가 언마운트될 때 cleanup 함수 호출 (componentWillUnmount)
3. deps 파라미터 생략
useEffect(() => { // 마운트
console.log(user);
});
- 그냥 컴포넌트가 리렌더링될 때마다 함수 호출됨
- 리액트 컴포넌트는 기본적으로 부모 컴포넌트가 리렌더링되면 자식 컴포넌트 또한 리렌더링됨
- Virtual DOM 에 모두 렌더링 -> 바뀐 내용이 있는 컴포넌트만 실제 DOM 에 반영
useMemo
: 성능 최적화할 때 사용
- "memorized" 이전에 계산한 값을 재사용한다는 의미
- 첫 번째 파라미터에는 어떻게 연산할지 정의하는 함수를, 두 번째 파라미터에는 deps 배열 넣기
- deps 배열 안에 넣은 내용이 바뀌면 함수 호출, 내용이 바뀌지 않으면 이전 값을 재사용
useMemo 사용 전 : input 값을 바꿀 때에도 countActiveUsers 함수가 호출됨 (불필요할 때에도 호출하여서 자원 낭비)
const countActiveUsers = () => {
console.log('count active users..');
return users.filter(user => user.active).length;
}
const count = countActiveUsers(users);
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle} />
<div>활성사용자 수 : {count}</div>
</>
);
- ex) 이메일을 입력한다고 치면 CreateUser 컴포넌트의 email이라는 속성(props)가 바뀌게 되는데 이 값은 원래 App 컴포넌트의 state임
- 결국 input에 입력하는 행위는 App Component의 state를 바꾸게 되는 행위
- App의 state가 변경됐으니 App Component가 다시 렌더링되면서 countActiveUsers가 실행되면서 호출됨
useMemo 사용 : users에 변화가 있을 때만 countActiveUsers 함수 호출해서 성능 최적화
const countActiveUsers = () => {
console.log('count active users..');
return users.filter(user => user.active).length;
}
const count = useMemo(() => countActiveUsers(users), [users]);
return (
<>
<CreateUser
username={username}
email={email}
onChange={onChange}
onCreate={onCreate}
/>
<UserList users={users} onRemove={onRemove} onToggle={onToggle} />
<div>활성사용자 수 : {count}</div>
</>
);
useCallback
: useMemo를 기반으로 만들어져 성능 최적화할 때 사용
const onToggle = useMemo(
() => () => {
/* ... */
},
[users]
);
- 위랑 같은 건데, 함수를 위해서 사용할 때 더욱 편하게 사용 가능
userCallback 사용 전 : 함수들은 컴포넌트가 리렌더링될 때마다 새로 만들어짐
const onCreate = () => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users.concat(user));
setInputs({
username: '',
email: ''
});
nextId.current += 1;
};
const onRemove = id => {
// user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
// = user.id 가 id 인 것을 제거함
setUsers(users.filter(user => user.id !== id));
};
const onToggle = id => {
setUsers(
users.map(user =>
user.id === id ? { ...user, active: !user.active } : user
)
);
};
userCallback 사용 : 함수는 필요할 때만 한 번 새로 만들고 재사용 (최적화 위함)
const onCreate = useCallback(() => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users.concat(user));
setInputs({
username: '',
email: ''
});
nextId.current += 1;
}, [users, username, email]);
const onRemove = useCallback(
id => {
// user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
// = user.id 가 id 인 것을 제거함
setUsers(users.filter(user => user.id !== id));
},
[users]
);
const onToggle = useCallback(
id => {
setUsers(
users.map(user =>
user.id === id ? { ...user, active: !user.active } : user
)
);
},
[users]
);
React.memo
: 컴포넌트의 리렌더링 성능 최적화할 때 사용
- 컴포넌트에서 리렌더링이 필요한 상황에서만 리렌더링 하도록 설정
- 컴포넌트의 props가 바뀌지 않았다면 리렌더링을 방지 (props 비교해서)
export default React.memo(CreateUser);
export default React.memo(UserList);
- input을 수정할 때 하단의 UserList가 리렌더링되지 않게 됨
- 그런데, User 중 하나라도 수정하면 모든 User들이 리렌더링되고, CreateUser도 리렌더링됨
- why? deps 에 users 가 들어있어서 users 배열이 바뀔 때마다 onCreate, onToggle, onRemove 함수가 새로 만들어지기 때문
-> deps 에서 users 를 지우고, 함수들에서 현재 useState 로 관리하는 users 를 참조하지 않게 하기 (함수형 업데이트)
const onCreate = useCallback(() => {
const user = {
id: nextId.current,
username,
email
};
setUsers(users => users.concat(user));
setInputs({
username: '',
email: ''
});
nextId.current += 1;
}, [username, email]);
const onRemove = useCallback(id => {
// user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬
// = user.id 가 id 인 것을 제거함
setUsers(users => users.filter(user => user.id !== id));
}, []);
const onToggle = useCallback(id => {
setUsers(users =>
users.map(user =>
user.id === id ? { ...user, active: !user.active } : user
)
);
}, []);
- setUsers 에 등록하는 콜백함수의 파라미터에서 최신 users 를 참조할 수 있기 때문에 deps 에 users 를 넣지 않아도 됨
- 실제로 렌더링을 방지할 수있는 상황에만 React.memo 사용할 것 (불필요한 props 비교 방지)
- React.memo 에서 두 번째 파라미터에 propsAreEqual 함수를 사용하여 특정 값들만 비교하는 것도 가능
export default React.memo(
UserList,
(prevProps, nextProps) => prevProps.users === nextProps.users
);
학습 사이트: https://react.vlpt.us/basic/19-React.memo.html
- useEffect를 사용하여 마운트/언마운트/업데이트시 할 작업 설정하기
- useMemo 를 사용하여 연산한 값 재사용하기
- useCallback 을 사용하여 함수 재사용하기
- React.memo 를 사용한 컴포넌트 리렌더링 방지