-
TIL_200905(React Hook 공식문서)Today I Learned 2020. 9. 5. 22:55
왜 React에서 Hook을 사용해야하는가?
Hook은 Class없이 React 기능들을 사용하는 방법을 알려줍니다.
그렇다면 왜 Class없이 React를 사용하려 할까요?
- 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어렵습니다.
- Hook은 컴포넌트로부터 상태관련 로직을 추상화할 수 있기 때문에 독립적인 테스트와 재사용이 가능합니다.
- Hook은 계층 변화없이 상태관련 로직을 재사용할 수 있도록 도와줍니다.
- 복잡한 컴포넌트들은 이해하기 어렵습니다.
- 생명주기 메서드에 자주 관련없는 로직이 섞여서 이로 인해 버그가 쉽게 발생하고 무결성을 쉽게 해칩니다.
- componentDidMount와 componentDidUpdate로 데이터를 가져오는 것
- componentWillUnmount로 cleanup을 수행하는것
- Class는 사람과 기계를 혼동시킵니다.
- Javascript에서 this가 작동되는 방법, 이벤트 핸들러가 등록되는 방법들을 기억해야만 합니다.
State Hook
import React, { useState } from 'react'; function Example() { const [count, setCount] = useState(0); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
useState라는 Hook을 사용하여 카운터를 구현하는 예시입니다. useState는 현재의 state 값(count)과 값을 업데이트하는 함수(setCount)를 제공합니다. 지금까지 Functional 컴포넌트를 작성하다가 state 값이 필요하다면 Class 컴포넌트로 바꾸었습니다. 이제는 useState를 사용하여 state 값을 사용할 수 있습니다. useState는 Class의 this.setState와 유사하지만 다릅니다.
Hook 사용규칙
- 최상위에서만 Hooks를 호출해야 합니다. 반복문, 조건문, 중첩된 함수 내에서 Hook을 실행하지 마세요.
- React 함수 컴포넌트 내에서만 Hook을 호출해야합니다. 일반 Javascript 함수에서는 Hook을 호출하지 마세요.
만약에 상태 관련 로직을 컴포넌트간에 재사용하고 싶은 경우가 생긴다면 두가지 방법을 사용할 수 있습니다.
- higher-order components
- render props
로직을 useFriendStatus라는 custom Hook으로 뽑아냅니다.
import React, { useState, useEffect } from 'react'; function useFriendStatus(friendID) { const [isOnline, setIsOnline] = useState(null); function handleStatusChange(status) { setIsOnline(status.isOnline); } useEffect(() => { ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange); }; }); return isOnline; }
function FriendStatus(props) { const isOnline = useFriendStatus(props.friend.id); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
각 컴포넌트의 state는 완전히 독립된 state를 가집니다. 그래서 상태 관련 로직을 재사용하고 한 컴포넌트 안에서 같은 custom Hook을 두번 사용할 수도 있습니다.
useState
초기값이 0 인 count 라는 변수가 선언해봅니다.
import React, { useState } from 'react'; function Example() { // 새로운 state 변수인 count를 선언합니다. const [count, setCount] = useState(0)
state를 사용하는 방법은 간단합니다.
<p>You clicked {count} times</p>
count 변수의 값을 바꾸려면 setCount를 호출하면 됩니다.
<button onClick={() => setCount(count + 1)}> Click me </button>
useEffect
useEffect를 사용하여 함수 컴포넌트에서 side effect를 수행할 수 있습니다. 데이터 가져오기, 구독 설정하기, 수동으로 컴포넌트의 DOM을 수정하는 것들이 side effect입니다.
// componentDidMount, componentDidUpdate와 같은 방식으로 useEffect(() => { // 브라우저 API를 이용하여 문서 타이틀을 업데이트합니다. document.title = `You clicked ${count} times`; });
이전의 카운터 예시와 합친다면 이렇게 됩니다.
import React, { useState, useEffect } from 'react'; function Example() { const [count, setCount] = useState(0); // componentDidMount, componentDidUpdate와 같은 방식으로 useEffect(() => { // 브라우저 API를 이용하여 문서 타이틀을 업데이트합니다. document.title = `You clicked ${count} times`; }); return ( <div> <p>You clicked {count} times</p> <button onClick={() => setCount(count + 1)}> Click me </button> </div> ); }
- useEffect는 리액트가 컴포넌트를 렌더링한 이후에 수행할 것을 기억하도록 합니다.
- useEffect가 컴포넌트 안에서 불리는 이유는 컴포넌트 내부에서 state 변수에 접근하기 위해서 입니다.
- useEffect는 첫번째 렌더링과 이후의 모든 업데이트 이후에 수행됩니다.
두 종류의 side effect
- Clean-up이 필요한 경우
- Clean-up이 필요하지 않은 경우
Clean-up을 이용하는 Effects
친구의 온라인 상태를 구독할 수 있는 ChatAPI 모듈의 예입니다.
import React, { useState, useEffect } from 'react'; function FriendStatus(props) { const [isOnline, setIsOnline] = useState(null); useEffect(() => { function handleStatusChange(status) { setIsOnline(status.isOnline); } ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); // effect 이후에 어떻게 정리(clean-up)할 것인지 표시합니다. return function cleanup() { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; }); if (isOnline === null) { return 'Loading...'; } return isOnline ? 'Online' : 'Offline'; }
모든 effect는 정리를 위한 함수를 반환할 수 있기 때문에 구독의 추가와 제거를 위한 로직을 묶어둘 수 있습니다. React가 clean-up하는 시점은 렌더링이 실행되는 때마다 입니다. 다음 차례의 effect를 실행하기 전에 이전의 렌더링에서 파생된 effect가 정리되는 것입니다.
모든 리렌더링 시에 clean-up이 실행되는 이유는 업데이트 때문입니다. 마운트가 해제되기 전에 친구의 아이디가 바뀌는 경우가 있을 것입니다. 그렇다면 마운트를 해제하는 과정에서 충돌이 일어날 수 있습니다. 이럴때를 대비해서 class 컴포넌트는 componentDidUpdate를 추가합니다. 하지만 Hook을 사용한다면 달리 바꾸지 않아도 됩니다.
componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate(prevProps) { // 이전 friend.id에서 구독을 해지합니다. ChatAPI.unsubscribeFromFriendStatus( prevProps.friend.id, this.handleStatusChange ); // 다음 friend.id를 구독합니다. ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); }
useEffect(() => { // ... ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange); return () => { ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange); }; });
그러나 매번 effect를 정리하거나 적용하는 것이 성능 저하를 발생시키기도 합니다. 이때는 effect를 건너뛸 수 있습니다. 두번째 인수로 배열을 넘겨주고 특정 값이 변하지 않는다면 effect를 건너뛰게 됩니다.
useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // count가 바뀔 때만 effect를 재실행합니다.
'Today I Learned' 카테고리의 다른 글
TIL_200907(PixiJS - sprite) (0) 2020.09.07 TIL_200906(React Hook 공식문서) (0) 2020.09.06 TIL_200622 (0) 2020.06.22 TIL_200619 (0) 2020.06.20 TIL_200618 (0) 2020.06.18 - 컴포넌트 사이에서 상태와 관련된 로직을 재사용하기 어렵습니다.