React Hook 총정리

김동현·2021년 2월 15일
14

React

목록 보기
4/4

개요

정의

  • React 16.8 버젼에 추가된 공식 라이브러리
  • Class형 컴포넌트에서만 쓸 수 있었던 state와 life cycle을 Function형 컴포넌트에서도 사용 가능
  • 현재 공식문서에서는, Class형 컴포넌트보다는 Function형 컴포넌트로 새로운 React 프로젝트를 만들기를 권장
    • 단, 기존의 Class형 컴포넌트들을 Hook을 이용한 Function형 컴포넌트로 refactoring할 이유는 전혀 없음

왜 필요한가?

  • 함수형 컴포넌트들은 기본적으로 리렌더링이 될때, 함수 안에 작성된 모든 코드가 다시 실행
    • 클래스형 컴포넌트들은 method의 개념이므로, 리렌더링이 되더라도 render() 를 제외한 나머지 method 및 state는 그대로 보존이 되어 있음.
  • 이는 함수형 컴포넌트들이 기존에 가지고 있던 상태(state)를 전혀 관리(기억)할 수 없게 만듦
    • 그래서 함수형 컴포넌트를 Stateless Component 라고 했던 것.
    • 단순하게 React에서의 state 만을 의미하는 것이 아닌, 함수내에 써져 있는 모든 코드 및 변수를 기억할 수 없다는 의미
      ⇒ 함수형 컴포넌트가 리렌더링될때 무조건 새롭게 선언 & 초기화 & 메모리에 할당이 됨
  • 하지만 Hook의 등장으로, 브라우저에 메모리를 할당 함으로써, 함수형 컴포넌트가 상태(state)를 가질 수 있게 한 것.
    ⇒ 쉽게 말해서 함수 내에 써져 있는 코드 및 변수를 기억할 수 있게 됐다 라는 의미

언제나 유용한가?

  • 위에서 말했듯이, Hook은 브라우저의 메모리 자원을 쓴다.
  • 따라서, 함부로 남발했다가는 오히려 전체적인 성능 저하를 불러올 수 있다.
  • Hook의 성능 최적화는 이 곳에서 다룬다.

useState

정의

  • React 에서 정의하는 state 를 Function Component에서 사용하게 해주는 Hook
  • this.statethis.setState 를 대체

사용법

const [value, setValue] = useState(0);
  1. useState(param) 의 parameter : value의 초깃값 설정
  2. useState(param) 의 반환 값 : 배열
    • 첫 번째 요소 ⇒ 관리할 value
    • 두 번째 요소 ⇒ value를 관리할 수 있는 함수 ( this.setState() ) 와 유사 )
      • 단, setState와 달리 대체함(replace)
    • 보통은 위의 예시 코드와 같이 비구조화 할당을 통해 변수에 할당함

성능 최적화

  • 함수형 업데이트
    useState((prev)=> prev+1); // number
    useState((prev)=> {...prev, newID: newValue}) // object
    useState((prve)=> [...prev, newValue]) // array
  • useState 는 이전 state 값을 인자로 자동으로 받아오는 callback function을 인자로 넣을 수 있음.
  • 함수형 업데이트를 쓰는 이유 (중요)
  1. useCallback , useMemo 에서 성능 최적화 작업
    ⇒ 두번째 인자로 들어가는 dependency array의 요소를 줄일 수 있음
        // state의 값이 바뀐다고, useCallback이나 useMemo를 다시 할당할 이유가 없음.
        // 그러나 아래와 같이 함수형 업데이트를 사용하지 않으면, 반드시 재할당해야함 
        // 이전 state 값을 자체적으로 가지고 있기 때문

        const onChange = useCallback(e=> {
        	const {name, value} = e.target;
        	setUser({
        		...user,
        		name: value,
        	})
        }, [user]); // 반드시 두번째 인자로 user를 넘겨줘야함
        // 함수형 업데이트에서는 setState자체에서 이전 state값을 가져옴
        // 따라서 dependency로 넘겨주지 않아도 되고
        // state가 갱신될때 마다 해당 Hook을 재할당하지 않아도 됨
        const onChange = useCallback(e=> {
        	setUser((prev)=>{
        		const {name, value} = e.target;
        		return {
        			...prev,
        			name: value,
        		};
        	})
        }, []); // 더이상 user 관련하여 재할당하지 않음
  1. 이전 state 값을 가져오는 것을 항상 보장할 수 있음
        // 아래와 같이 아예 다른 state값을 할당해야 한다면 
        // 함수형 업데이트는 필요하지 않을 수도 있음.
        const onChangeName = (event) => {
        	const newName = event.currentTarget.value;
        	setName(newName);
        };
        // 하지만 대다수의 state (특히 객체와 배열) 는 이전 상태가 필요함.
        // 이 경우 함수형 업데이트가 필요함
        const onAddUser = (event) => {
        	setUser((prev)=>{
        		const newUser = event.currentTarget.value;
        		return [...prev,newUser];
        	})
        }
  1. setState 관련 로직이 좀 더 깔끔해짐
    → 새로운 state 와 관련된 모든 로직을 setState 안에 넣기 때문
        // not good, 깔끔하지 못한 setState
        const onClick = () =>{
        	const newId = 3;
          const newValue = "안녕하세요";
        	setState({
        		...state,
        		newID: newValue,
        	})
        }
        // good, 새로운 state와 관련된 모든 로직이 setState안에 들어있어서 구분이 깔끔함
        const onClick = () => {
        	setState((prev)=>{
        		const newId = 3;
        		const newValue = "안녕하세요";
        		return {...prev, newID: newValue};
        	}
        }

주의점

  1. setValue 를 쓴다고 즉각적으로 바뀌지 않음 (비동기적)
    • 따라서 바뀐 state를 이용하는 로직을 짜야하는 경우 반드시 useEffect 에서 처리할 것!!
    // bad code, count와 관련된 뒷처리 코드를 onClick에서 다 처리
    const onClick = () => {
    	setState(count+1);
    	if(count===3) alret("3 입니다!");
    }
    // good code, onClick에서는 state의 갱신만, 뒷처리 코드는 useEffect에서 처리
    useEffect(()=>{
    	if(count===3) alert("3 입니다!");
    })

    const onClick = () => {
    	setState(count+1);
    }
⇒ `useEffect` 가 존재하는 이유이기도 함.
  1. Array나 Object의 경우 setValue 에서 반드시 immutable 을 지켜서, 새롭게 할당한 변수를 넘겨줘야함
    ⇒ 함수형 컴포넌트가 state 가 바뀌는 것을 감지할때 shallow Comparison을 이용하기 때문.
    ⇒ 즉, 주소값만을 비교하기 때문에, immutability를 지키지 않고 수정을 해버리면, 바뀐지를 감지하지 못할것이고, 리렌더링 또한 진행이 안됨.
    - Spread operator 를 사용하는 것을 권장 ( [...] or {...} )

useEffect

정의

  • LifeCycle 에서 componentDidMount, componentDidUpdate, componentWillUnmount 를 대체
    • Component가 re-render 될때마다 호출
    • 즉 React 컴포넌트가 화면에 렌더링된 이후에 비동기로 처리되어야 하는 부수적인 효과들을 흔히 Side Effect라 부르는데, 이 Side Effect를 처리하기 위한 함수

사용 방법

useEffect(() => {
    console.log('렌더링이 완료되었습니다!');
  },[]);
  1. useEffect 의 첫번째 parameter : rendering이 될때 실행시킬 함수 (callback)
    • Side Effect에 해당하는 코드들을 작성하는 곳
    • 이 함수의 반환 값으로 뒷정리 (clean-up) 코드를 넣어줄 수 있음
      • 해당 컴포넌트가 unmount 되거나, dependency가 바뀌어서 effect가 달라져야 할때, 이전 effect를 청소하는 용도이다. (이펙트를 "되돌리는" 것)
      • 이 뒷처리 코드는 다음번 useEffect가 실행될때 이전의 state값을 가지고 먼저 실행되게 되어 있음
      • 즉, lifeCycle 에서 componentWillUnmount 와 같은 포지션
        useEffect(() => {
            console.log('effect');
            console.log(name);
            return () => {
              console.log('cleanup');
              console.log(name);
            };
          });
  • 최종적으로 useEffect가 동작하는 순서는 총 순서는 다음과 같다.
    1. React 가 새로운 state를 가지고 UI를 랜더링함
    2. 브라우저가 실제로 새로운 state를 가지고 그리기를 진행
    3. 리액트가 이전 state에 대한 이팩트를 클린업함 (클린업코드를 이용)
    4. 리액트가 새로운 state에 대한 이펙트를 실행함
  1. useEffect 의 두번째 parameter : 배열 (dependency array)
    • 배열 안의 요소가 바뀔 때만 실행 , 배열 안의 요소는 useState() 로 지정한 변수나 props
      • 빈 배열componentDidMount 일 때만 실행 (최초 랜더링시에만)
        useEffect(() => {
            console.log('마운트 될 때만 실행됩니다.');
          }, []);
  • 무언가 있는 배열 ⇒ 그 변수가 바뀔때만 실행
        useEffect(() => {
            console.log(name);
          }, [name]); // name이 바뀔때만 실행됨
  • 두번째 인자가 없음 ⇒ 매 렌더링때마다 실행 (avoid)
  • 성능에 정말 안 좋음.

성능 최적화

  1. 두번째 parameter 로 들어가는 dependency array를 잘 작성해 놓기.
    • 정말 필요한 경우에만 useEffect 가 실행될 수 있게끔.
  2. 로직별로 useEffect 분리하기
    • dependency에도 영향을 줌
    // bad, 모든 sideEffect를 하나의 useEffect에 넣음
    useEffect(()=>{
    	if (user.length==3) alert("3명의 유저가 있어요!");
    	if (sumbit === true) formRef.reset();
    }, [user,submit]); // user와 submit 2개의 dependency를 넣을 수 밖에 없음
    // good, useEffect의 로직별 분리
    useEffect(()=>{
    	if(user.length===3) alert("3명의 유저가 있어요!");
    },[user]);

    useEffect(()=>{
    	if(submit === true) formRef.reset();
    },[submit]);

주의점

  • 여러개의 useEffect 가 써져있는 경우, 위에서 부터 실행됨.

useRef

정의

  • 클래스형 컴포넌트에서 React.createRef 를 대체
  • React에서 DOM을 직접 컨트롤할때 사용함

사용 방법

function Sample() {
	const btnRef = useRef();
	const onClick=()=>{
		btnRef.style.color="blue";
	};

	return (
	<div>Ref 예제</div>
	<button
		onClick={onClick}
    ref={btnRef}
	/>
	)
}

성능 최적화

  • 없음

주의점

  • 없음

useCallBack

정의

  • 클래스형 컴포넌트에서는 method가 자동으로 메모리가 할당되는 반면, 함수형 컴포넌트에서는 그러지 못함
  • 따라서 함수형 컴포넌트 내부에 선언되는 함수들을 재참조하기 위한 Hook
    • 특정 함수를 새로 만들지 않고 재사용하고 싶을 때 사용

사용 방법

const onClick = useCallback((e)=>{
	setState((prev)=>prev+1);
}, []);
  1. 첫번째 인자 : 재사용하고 싶은 함수
  2. 두번째 인자 : dependency array
    • useEffect 와 동일하게, 해당 array안에 들어있는 것이 바뀔때만 함수를 재할당함
    • 빈 배열 → 한번 할당하고 다시 할당 X

성능 최적화

  1. setState 를 함수안에 사용할 경우 함수형 업데이트를 사용할 것.
    ⇒ dependency array의 인자를 줄일 수 있음.
  2. 하위 Component에 props로 넘겨주는 함수라면 더더욱 useCallback 을 이용할것
    ⇒ 하위 Component에서 React.memo 등으로 최적화를 진행했을때, 정상적으로 작동하기 때문.

주의점

  • 없음

useMemo

정의

  • 연산된 값을 재사용 하는 Hook
    • 특히 입력을 받을 때, 용이하게 사용될 수 있음
  • 연산된 값이 만약 함수라면 useCallback 과 동일한 효과
  • 여기서 memo 는 memoized 를 의미

사용 방법

const userLength = useMemo(()=>{
	console.log("memo 재 할당!);
	return user.length;
}, [user]);
  1. 첫번째 인자 : 어떻게 연산할지 정의하는 함수
  2. 두번째 인자 : dependency array
    • useEffect 와 동일하게, 해당 array안에 들어있는 것이 바뀔때만 함수를 재할당함
    • 빈 배열 → 한번 할당하고 다시 할당 X

성능 최적화

  1. setState 를 연산된 값 안에 사용할 경우 함수형 업데이트를 사용할 것.
    ⇒ dependency array의 인자를 줄일 수 있음.

주의점

  • 없음

왜 hook을 쓰면 좋은가?

1. Functionion형 Component로 통일

  • 함수형, 클래스형으로 분리해서 개발하던걸 함수형으로 통합 개발 가능
    • 그 이전에는 state 가 있으면 class
    • 오직 뷰만 그린다면 function

2. useEffect 를 통한 LifeCycle에 흩어져 있는 logic을 묶음

function FriendStatusWithCounter(props) {
	// 이 부분은 몇번이나 클릭했는지를 나타내는 부분!
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
	
	// 이 부분은 Server와의 송수신과 관련된 부분!
  const [isOnline, setIsOnline] = useState(null);
  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  // ...
}
  • 한 번 밖에 호출할 수 없었던 Life Cycle method와 달리, hook은 여러번 선언 가능
  • Multiple Effect 를 이용하면 코드가 무엇을 하는지에 따라 분기 가능
    • 단, hook은 위에서 부터 아래로 순서에 맞게 동작하므로 주의해야함

3. Custom Hook 을 이용한 logic의 직관적인 재사용

  • HOC (Higher Order Component) 나 render-props 같은 패턴이 가져오는 Component Tree의 불필요한 중첩을 없애줌
  • 보다 직관적이게 로직을 재사용할 수 있음

4. binding이 필요 없음

  • 애시당초 Class Component에서 binding이 필요했던 이유는, Event Listener로 함수를 하위 컴포넌트에 넘겨줬을때, this의 범위를 하위 컴포넌트로 선정했기 때문
  • 그렇지만 Function Component는 this 자체가 존재하지 않기 때문에 위와 같은 현상이 발생하지 않음

Hook의 규칙

최상위(at the Top Level)에서만 Hook을 호출

  • React 함수의 최상위에서만 Hook을 호출 할 것.
  • 반복문, 조건문, 중첩된 함수등에서 호출 X

React 함수에서만 Hook을 호출

  • 일반적인 JavaScript에서 호출하지 말 것

Eslint 사용

eslint-plugin-react-hooks

  • 위의 eslint를 사용하면, 2가지 규칙을 강제시킬 수 있음!

Hook의 최적화

최적화를 위한 가이드 라인

  1. Component가 반드시 필요한 리렌더링만을 진행하는가?
  2. 렌더링이 발생한다면, property 및 method가 반드시 필요한 것만 재할당 할 수 있게 하는가?
  3. 위의 2가지를 무시할 만큼, 렌더링이 자주하는가? (따라서 메모리를 할당하면서 위의 2가지를 지키지 않아도 되는가?)

⇒ 위 3가지 사항이 React가 View Library로서 성능을 최적화에 관여하는 규칙이라고 볼 수 있다.

(물론 내가 공부하면서 만든거다.)

참고 사이트

profile
Frontend Developer

2개의 댓글

comment-user-thumbnail
2021년 2월 16일

잘 읽었습니다. 유익한 시간이었습니다. 좋은 하루 보내세요.

답글 달기
comment-user-thumbnail
2022년 7월 11일

잘 읽었어요! 감사해요!!

답글 달기