-
[React.js] Hooks - useEffect()Front-End(Web)/React - 프레임워크(React, Next) 2020. 11. 28. 16:58반응형
🤷 Rendering 후에 사용되는 코드들
컴포넌트가 렌더링된 후에 사용되는 코드들이 있다. 이러한 코드들이 쓰이는 이유는 다음과 같다.
- 백앤드 사이드에서 변경된 데이터를 패치
- 데이터의 변동을 확인하기 위함
- Timers(setTimeout 등) 및 Intervals(setInterval) 의 운용
- 위 조건들에 따라 필요시 DOM에 변화를 주기 위해
useEffect() 역시 렌더링 후의 동작을 위해 사용되는 Hooks로, 클래스형 컴포넌트의 Lifecycle 중 componentDidMount(), componentDidUpdate(), componentWillUnmount() 의 기능을 담당한다고 생각하면 된다.
📒 useEffect()
useState와 마찬가지로 React 라이브러리에서 import를 해줘야 한다.(중괄호 {} 반드시!)
이후, useEffect(()=>{callback function}) 양식에 맞춰 이후 동작을 콜백함수 형태로 지정하면 된다.
import React, { useState, useEffect } from 'react'; // useEffect 가져오기 export default function Counter() { const [count, setCount] = useState(0); useEffect(() => { // useEffect 동작 정의 alert(`Count: ${count}`) }) const handleClick = () => { setCount((prevCount) => prevCount + 1); }; return ( <div> <p>You clicked {count} times</p> <button onClick={handleClick}> Click me </button> </div> ); }
* 이러한 useEffect 내 동작을, 간단하게 'Effect'라고 칭한다.
- Cleanup Function(Effect 제거)
대표적으로 이벤트리스너가 있다. DOM에 렌더링하면서 부여한 이벤트리스너는, 컴포넌트 삭제시 같이 없애줘야 한다.
Cleanup 없이 남아있다면, 메모리 소모가 많아지며 re-render 됬을 때 이벤트리스너 중복으로 인한 버그의 위험도 있다.
그래서, Updating(re-render), Unmounting 단계의 useEffect() 함수는 Clean up Function 반환을 포함하는 경우가 많다.
import React, { useState, useEffect } from 'react'; export default function Counter() { const [clickCount, setClickCount] = useState(0); useEffect(() => { document.addEventListener('mousedown', increment); // Cleanup Function return () => { document.removeEventListener('mousedown', increment); } }) const increment = () => { setClickCount(prevCount => prevCount + 1) } return ( <h1>Document Clicks: {clickCount}</h1> ); }
Document에 마우스를 누르면 +1되는 이벤트리스너를 추가했는데, 지워주지 않으니 +2, +3, +4.. 로 증가하는 것을 볼 수 있었다.
이벤트리스너가 남아서 누적됨에 따라 발생한 현상이었다. 이를 위해, Cleanup Function을 반환해서 이전 Effect를 미리 삭제하는 것이다.
- Dependency Array
어떤 Effect는 최초 Mount 시에만 발동하고, 이후 re-render 시에는 미발동해야하는 경우가 있을 수 있다.
이처럼, Effect를 특정 시점에서 발동하기 위해 useEffect() Hooks에서 사용되는 개념이 Dependency Array이다.
1. undefined: 모든 render 및 re-render 시 작동
지금까지 사용된 코드들 참고!
2. [] 빈 배열: Effect가 최초에만 작동, 이후엔 필요가 없음
import React, { useState, useEffect } from 'react'; export default function Timer() { const [time, setTime] = useState(0); const [name, setName] = useState(''); useEffect(() => { const intervalId = setInterval(() => { setTime((time) => time + 1) }, 1000) return () => { clearInterval(intervalId) } }, []) // * dependency array const handleChange = ({ target }) => { setName(target.value) } return ( <> <h1>Time: {time}</h1> <input value={name} onChange={handleChange} type='text'/> </> ); }
<input>에 값을 입력하면 onChange 이벤트리스너로 인해 계속해서 re-render된다.
그로 인해, <h1> {time}을 useEffect()의 callback function이 계속 작동하면서 clearInterval이 간섭되는 것이다.
이를 방지하기 위해, useEffetct(callback, dependency array) 두번째 인자로 빈 배열([])을 넣어 최초 렌더링시에만 callback이 실행되도록 한다.
3. [variable]: 변수가 바뀌는 경우에만 Effect 작동
useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // count가 바뀐 경우에만 useEffect() callback 작동
📒 Rules of Hooks
- only call Hooks at the top level
- only call Hooks from React functions
Hook을 사용하면서, 위 2가지 규칙을 명심해야한다. Hook은 다른 구문에 포함되지 않은 상위레벨, Hook은 리액트 함수형에서만 운용.
1. Hook은 다른 구문에 포함되지 않은 상위레벨에 작성
//Wrong way (if 안에 effect hook) if (userName !== '') { useEffect(() => { localStorage.setItem('savedUserName', userName); }); } //Right way useEffect(() => { if (userName !== '') { localStorage.setItem('savedUserName', userName); } });
* if (items) {useState()} 대신, useState(null)로 빈 기본값을 부여하는 방법.
* if (!items) {useEffect()} 대신, useEffect(callback, [])로 dependency array를 통해 비어있을 때(최초) 한번만 callback 작동하는 방법.
2. Hook은 리액트 함수형 컴포넌트에서만 운용 가능
- Hook을 다른 Javascript 함수나 클래스형 컴포넌트에서 사용할 수 없다.
- 단, Hook들을 활용한 Custom Hook 제작시 사용 가능하다. (참고: reactjs.org/docs/hooks-custom.html (공식사이트))
📒 Seperating Hooks의 이점
이 코드에서, useEffect() 부분을 주목하자. menu, news-feed, friends 세개 response를 1개 Effect로 일괄 처리중이다.
import React, { useState, useEffect } from 'react'; import { get } from './mockBackend/fetch'; export default function SocialNetwork() { const [data, setData] = useState(null); useEffect(() => { // Promise.all로 3개의 response를 받는다. Promise.all([get('/menu'), get('/news-feed'), get('/friends')]).then( ([menuResponse, newsFeedResponse, friendsResponse]) => { setData({ menu: menuResponse.data, newsFeed: newsFeedResponse.data, friends: friendsResponse.data }); } ); }, []); return ( <div className='App'> <h1>My Network</h1> {!data || !data.menu ? <p>Loading..</p> : ( <nav> {data.menu.map((menuItem) => ( <button key={menuItem}>{menuItem}</button> ))} </nav> )} <div className='content'> {!data || !data.newsFeed ? <p>Loading..</p> : ( <section> {data.newsFeed.map(({ id, title, message, imgSrc }) => ( <article key={id}> <h3>{title}</h3> <p>{message}</p> <img src={imgSrc} alt='' /> </article> ))} </section> )} {!data || !data.friends ? <p>Loading..</p> : ( <aside> <ul> {data.friends .sort((a, b) => (a.isOnline && !b.isOnline ? -1 : 0)) .map(({ id, name, isOnline }) => ( <li key={id} className={isOnline ? 'online' : 'offline'}> {name} </li> ))} </ul> </aside> )} </div> </div> ); }
해당 부분을 세 개의 Effect로 나눈 예시이다. 보시다시피, useState와 useEffect를 각각 사용했다.
기존처럼 하나의 Effect로 처리하는 것보다 코드 가독성, 데이터 관리 효율 및 안정성이 우수하다.
const [menu, setMenu] = useState(null); useEffect(() => { get('/menu').then((response) => { setMenu(response.data); }); }, []); const [newsFeed, setNewsFeed] = useState(null); useEffect(() => { get('/news-feed').then((response) => { setNewsFeed(response.data); }); }, []); const [friends, setFriends] = useState(null); useEffect(() => { get('/friends').then((response) => { setFriends(response.data); }); }, []);
확실히, 함수형 컴포넌트가 클래스형에 비해 작성이 용이한 것을 느꼈다.
Hooks 기능은 위처럼 코드를 기능별로 분리할 뿐 아니라, 이를 통해 에러를 줄이고 사용이 용이하게 해준다.
하지만, class의 state 관리기능을 보완하기 위해 추가된 기능인 만큼 단점도 있을 것이고,
앞으로 리액트 프로젝트 간 두 컴포넌트를 비교하면서 이런 부분들을 알아봐야겠다.
반응형'Front-End(Web) > React - 프레임워크(React, Next)' 카테고리의 다른 글
[React.js] Inline Style (0) 2020.11.30 [React.js] Stateful vs Stateless Components (0) 2020.11.29 [React.js] Hooks - useState() (0) 2020.11.27 [React.js] Lifecycle (0) 2020.11.27 [React.js] State (0) 2020.11.26