(본 글은 Velopert님의 https://react.vlpt.us/ 를 정리한 내용입니다.)
useEffect(<function>, <Array>);
: class형 컴포넌트에서 componentDidMount, componentDidUpdate와 같은
lifeCycle을 hooks에서는 useEffect로 구현한다.
- useEffect은 다음과 같은 두개의 인자를 받는다.
- Array의 여부와 어떤 값이 들어가냐에 따라 기능이 달라진다.
(기본적으로 useEffect는 componentDidMount 될 때 실행된다.)
- 컴포넌트가 마운트 되었을 때 / re-render 될 때 마다 실행
useEffect(() => { console.log("componentDidMount & re-render !"); });
- 컴포넌트가 마운트 되었을 때
useEffect(() => { console.log("componentDidMount"); }, []);
: Array에 []가 들어간 것에 의미는 어떠한 state가 변화되더라도 re-render하지 않겠다는 의미!
즉, 컴포넌트가 최초 마운트 될 때만 실행하게 된다!
- 컴포넌트가 마운트 되었을 때 / 특정 state값이 변화 할 때마다 실행
useEffect(() => { console.log("componentDidMount & state change !"); }, [number]);
: number이라는 state값이 변할 때 마다 실행되게 된다.
- 컴포넌트가 삭제될 때 --> return()
useEffect(() => { console.log("componentDidMount!"); return ( console.log("componentDidUnmount !") ) }, []);
: return을 지정하면 컴포넌트가 삭제될 때 실행된다.
(lifecycle의 componentWillUnmount 역할)
useMemo()
: re-render로 함수의 결과 값을 다시 얻기 위해 불필요하게 재 실행되는 경우를 막기 위한 방법
결과 값을 기억하는 용도로 사용한다.
(반드시 useMemo내부에서 사용되는 state는 두번째 인자 배열에 넣어줘야 한다!)
function countActiveUsers(users) { console.log('활성 사용자 수를 세는중...'); return users.filter(user => user.active).length; } function App() { ... // const count = countActiveUsers(users); const count = useMemo(() => countActiveUsers(users), [users]); ... }
: 함수형 컴포넌트 외부에 선언되어 있어서 re-render시에 countActiveUsers()가
재 선언되지는 않지만, 내부에서 사용하는 구문이 재 실행되어 불필요하게 재 실행되게 된다.
따라서 useMemo()를 이용해 특정 값이 바뀔 때만 실행되게 할 수 있다.
useCallback()
: useMemo()가 함수의 결과 값을 기억했다면, useCallback()은 함수의 불필요한 선언을
막기 위해 함수 자체를 기억하여 최적화를 이뤄내는 기능이다.
[ 미 적용 ]
const onCreate = () => { setUsers(users.concat(user)); setInputs({ username: '', email: '' }); 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 ) ); };
: 다음과 같은 코드처럼 우리는 특정 이벤트를 처리하는 많이 정의한다. 하지만, 이러한 함수들은
re-render될 때 재선언되어 불필요성을 가지게 된다. 따라서 useCallback()을 통해 내부에서
사용하는 특정 state가 바뀔 때 만 재선언 되게 하여 효율적인 코드로 바꿀 수 있다.
[ 적용 ]
// 내부에서 사용하는 users, username, email state값을 두번째 인자에 추가! const onCreate = useCallback(() => { setUsers(users.concat(user)); setInputs({ username: '', email: '' }); nextId.current += 1; }, [users, username, email]); // 내부에서 사용하는 users라는 state를 두번째 인자에 추가! const onRemove = useCallback( id => { // user.id 가 파라미터로 일치하지 않는 원소만 추출해서 새로운 배열을 만듬 // = user.id 가 id 인 것을 제거함 setUsers(users.filter(user => user.id !== id)); }, [users] ); // 내부에서 사용하는 users라는 state를 두번째 인자에 추가! const onToggle = useCallback( id => { setUsers( users.map(user => user.id === id ? { ...user, active: !user.active } : user ) ); }, [users] );
- React.memo()
: 함수형 컴포넌트가 re-render될 때 렌더링 결과를 기억해서 다음 렌더링 시 props가 같으면 기억된 내용을 재사용 하여 불필요한 re-render를 줄일 수 있는 방법.
(컴포넌트를 감싸는 래핑 방법이며, 고차 컴포넌트(hoc)라고 부른다.)function CreateUser() { ... } export default React.memo(CreateUser); // 컴포넌트를 전달해 다시 컴포넌트를 반환하는 고차 컴포넌트 hoc!
혹은
function React.memo(CreateUser() { ... }) export default CreateUser;
최적화 주의사항
- useMemo() / useCallback() / React.memo()를 사용해 최적화를 할 때에 반드시 성능 개선이 필요 한 경우에만 하는 것이 좋다.
- 복잡한 연산과 같이 꼭 필요한 경우 아니면 사용하지 않는 것이 좋다!
- 조금만 실수가 발생해도 로직이 매우 복잡해져 이해하기 힘든 상황이 올 수 있다.
: 지금까지는 state와 관련 로직을 useState를 사용해서 컴포넌트 내부에 처리하였다.
useReducer()를 이용해서 상태 업데이트 로직을 분리하여 컴포넌트 외부에서 상태관리 가능
[ 기존 Counter ]
import React, { useState } from 'react'; function Counter() { const [number, setNumber] = useState(0); const onIncrease = () => { setNumber(prevNumber => prevNumber + 1); }; const onDecrease = () => { setNumber(prevNumber => prevNumber - 1); }; return ( <div> <h1>{number}</h1> <button onClick={onIncrease}>+1</button> <button onClick={onDecrease}>-1</button> </div> ); } export default Counter;
[ useReducer()사용한 Counter ]
import React, { useReducer } from 'react'; function reducer(state, action) { switch (action.type) { case 'INCREMENT': return state + 1; case 'DECREMENT': return state - 1; default: return state; } } function Counter() { const [number, dispatch] = useReducer(reducer, 0); const onIncrease = () => { dispatch({ type: 'INCREMENT' }); }; const onDecrease = () => { dispatch({ type: 'DECREMENT' }); }; return ( <div> <h1>{number}</h1> <button onClick={onIncrease}>+1</button> <button onClick={onDecrease}>-1</button> </div> ); } export default Counter;
: 컴포넌트 내부에서 처리해야하는 상태 관리 로직을 reducer로 분리할 수 있다.
해당 내용을 이해하려면 기본적인 Redux의 flow를 알 필요가 있다.
( 1. 컴포넌트 내부에서 dispatch()를 통해 action을 reducer에게 넘긴다.
2. reducer는 상태 값을 변경 시킨다 )
- Redux flow
1) Component는 dispatch()를 통해 Action을 실행시킨다.
2) Action의 반환 값을 통해 Reducer는 상태를 변경시킨다.
3) Store에 상태가 저장된다.
4) Component에서는 Store에 접근하여 상태 정보를 받는다.
- Component -> action -> reducer 순서로 상태정보 변경
- axios()와 같은 로직은 action 파일에 넣는다.
- reducer에서는 해당 action에 대한 state 변경 부분만 넣는다.
[참고]
1. useReducer()로 Redux와 비슷한 역할을 하게 할 수 있는 것 같다.
2. 쌩 Redux와 react-redux를 쓰는 방법 이렇게 두가지가 있는 것 같다.