훅이 무엇인가
복싱을 배운 나는 숏훅 롱훅 밖에 모르는데
리액트에서 훅은 복싱에서의 훅 만큼 강력한 듯 하다.
훅은 함수형 컴포넌트(리액트에서 쓰는 친구들) 에서 state와 lifecyclemethod 을 연동할 수 이있게 해주는 "함수"다.
리액트에 있는 내부 함수로써, state와 렌더링 관련해 많은 도움을 주는 함수들이다.
벨로퍼트 강의에서 한 CRUD를 훅들을 이용하여 최적화해보려고 한다.
useEffect 라는 훅으로 시작해보자
useEffect(() => {
마운트
return () => {
언마운트
클린업 함수
};
}, [배열]);
useEffect : 배열 안의 값이 생길 때, 바뀔 때, 사라질 때 위 훅에 의해 내부 함수들이 호출된다.
정확히는 값이 마운트(생김), 업데이트(바뀔때: 바뀌기전, 바뀌고 난뒤), 언마운트(사라질 때) 호출된다.
1) deps 가 비어있으면, component 가 처음 호출될 때만 useeffect의 함수가 호출된다.
useEffect 가 들어있는 컴포넌트!
User 에
useEffect(() => {
console.log('컴포넌트가 화면에 나타남');
return () => {
console.log('컴포넌트가 화면에서 사라짐');
};
}, []);
를 넣으면, 컴포넌트가 나타날 때 위 메세지, 사라질 때 아래 메세지가 뜬다.
2) deps 에 특정 값을 넣으면, 마운트(생김), 업데이트(바뀔때: 바뀌기전, 바뀌고 난뒤), 언마운트(사라질 때) 호출이된다.
User 에
useEffect(() => {
console.log('user 값이 설정됨');
console.log(user);
return () => {
console.log('user 가 바뀌기 전..');
console.log(user);
};
}, [user]);
이렇게 넣으면, user 가 재랜더링될 때마다 위 함수가 호출된다.
create할 때, toggle 할 때, delete 할 때 모두 user 가 바뀌므로 계속 호출되게 된다.
위 userEffect 에 user 라는 값을 사용하는데, 이 때 deps 로 user 를 무조건 사용해줘야 한다. 그래야 최신 값으로 참조 가능.
3) deps 파라미터를 생략하면, 컴포넌트 리렌더링 될 때 마다 호출. 빈 배열과 다른 점은
빈 배열은 나타날때, 사라질 때만 호출되지만 생략하면 컴포넌트 내부 state가 바뀌거나 부모 컴포넌트가 리랜더링될 때마다 계속 출력된다.
countActiveUsers 라는 함수로 toggle 이 active 인 요소의 개수를 반환해보자
function countActiveUsers(users) {
console.log('활성 사용자 수를 세는중...');
return users.filter(user => user.active).length;
}
이렇게 짜면 되는데,
이렇게 되면 input 이 업데이트되어도 함수가 계속 호출된다.(불필요)

(input 값이 바뀌면, const count = countActiveUsers(users); 는 App 컴포넌트 안에서 호출 되는데,
input은 useState라는 훅을 사용하는 App 컴포넌트 안의 state이므로 컴포넌트가 재랜더링 되어서 함수가 다시 호출되는 것이다.
참고 : https://velog.io/@eunbinn/when-does-react-render-your-component)
이것을 useMemo 라는 훅으로 최적화 시킬 수 있다!
useMemo : const 재사용할 값 = useMemo(() => 함수, [deps]);
첫번째 파라미터는 위 countActiveUsers 처럼 연산을 정의하는 함수를 넣어주면 되고,
두번째 파라미터 deps 배열에는 재랜더링 하는 기준이 되는 요소들을 넣어주면 된다. (deps 에 그걸 넣어줘야 최신 값 유지 가능!)
const count = useMemo(() => countActiveUsers(users), [users]);
로 사용하면, App 컴포넌트가 재랜더링 될 때 실행되는 것이 아닌, users 만 바뀔 때 다시 연산하게 되고 그 전에는 그대로 사용한다.
useEffect 써도 되지 않느냐???
쓰임이 조금 다르다.
useEffect 는 언마운트 마운트에서 실행할 절차들 핸들링에 특화된 것이고,
useMemo는 어떤 연산의 결과인 변수가 있을 때 이 변수를 재사용하는 것에 특화된 것이다.
(useEffect 는 바뀌면 ~~ 하겠다 느낌, useMemo는 안바뀌면 그대로 쓰겠다 느낌)
useMemo와 비슷하게 함수를 재사용하려면 useCallback 을 사용한다. 문법은 거의 똑같다.
REACT CRUD 에서 만든 함수들을 콜백함수로써 useCallback 훅 안에 정의해주면된다.
똑같이 deps 에는 최신으로 유지할 요소들을 넣어주면 된다.
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]
);
등등
하지만 여전히 input 이 바뀌면 UserList가 재랜더링되는데 그 이유는
input 이 바뀌면, App이 재랜더링 되고, App 안의 UserList 까지 재랜더링 된다.
UserList는 input의 변화와는 상관없는데 재랜더링 되서 비효율적이다.
뒤 이은 훅에서는 컴포넌트의 재랜더링을 방지해 줄 수 있다.
컴포넌트가 불필요하게 재랜더링 되지 않게끔 하기 위해 React.memo 라는 훅을 사용한다.
컴포넌트의 props 가 바뀌지 않으면, 해당 컴포넌트는 재랜더링 되지 않게끔 할때 사용한다.
export default React.memo(CreateUser);
이런식으로 컴포넌트를 export default 로 export 정의할 때 컴포넌트를 감싸주면 된다.
CreateUser 말고도 UserList, User 컴포넌트에도 적용해준다.
이렇게 되면, input 는 UserList 의 props 가 아니므로,
이젠 input 이 바뀌어도, UserList 가 리랜더링 되지 않는다. 굿~
하지만 User를 토글할 때, 모든 User들이 리랜더링 되고 ,CreateUser 마저 리랜더링 되는데 그 이유는 users 라는 배열이 바뀌면, onCreate도 재랜더링되고, onToggle, onRemove 도 재랜더링 되기 때문...(useCallback 의 deps 에 users가 있기 때문이다!)
.
.
이를 방지하려면 useCallback 의 deps 에서 users를 제거해야하는데 이러면 users 의 가장 최신 상태를 참조할지 미지수이다.
이 때 함수형 업데이트를 사용한다. (useState 업데이트 시 다음 상태를 파라미터로 넣어주는 것이 나닌, 값을 업데이트 하는 함수를 파라미터로 넣어주는 형식)
const onIncrease = () => {
setNumber(number + 1);
}
위 코드를
const onIncrease = () => {
setNumber(prevNumber => prevNumber + 1);
}
이렇게 변경하는 것
그래서 함수형 업데이트를 하면, useState 의 set함수에 등록하는 콜백함수의 파라미터에서 최신 상태를 참조할 수 있게 된다.
setInputs(inputs => ({
...inputs,
[name]: value
}));
setUsers(users => users.concat(user));
이런식으로 한다.
이렇게 최적화가 끝났다.
랜더링 관련 핸들링을 하는 useEffect,
변수 재사용의 useMemo,
함수 재사용의 useCallback,
컴포넌트 재사용의 React.Memo
4가지의 훅으로 CRUD의 최적화를 했다.
최적화는 당장은 급하지 않지만, 프로덕션이 성장할 수록 더더욱 필요할 것이라 잘 익혀두는 게 좋은 것 같다.
리액트로 뚝딱 CRUD 구성하고 디자인 잘하는 것도 중요하지만, 진짜 장인들은 최적화와 자료구조까지 신경써야 하니 디테일한 부분에서 많은 공부가 필요하구나 느낀다.