React | Hook 정리 + 18 버전 새로운 Hook

    반응형

     

    안녕하세요. 오랜만에 블로그에 글을 써보네요!
    제가 개인 노션에서 계속 써왔던 내용들을 차근차근 블로그로 옮겨볼까 합니다.
    그 중 예~전에 열심히 정리했던 react hook에 관한 내용부터 쓰려고해요
    근데 저도 글을 정리하면서 검색해보니.. 새로운 hook들이 나왔더라고요!
    저도 맨날 쓰던것만 쓰다보니 새로운 hook을 알아본다거나 쓸 일은 없었는데,
    이 참에 저도 새로운 hook을 알아보고 기회가 될때마다 써보려고 합니다
    혹시 내용이 틀리다면 댓글 부탁드립니다 (전에 써둔 내용이라 틀린 부분도 있을 듯 해요🙄)

     

     


     

    1. 앞서 알고있어야 할 것들

    1-1. 리액트가 마운트 시에 하는 작업들

    • props로 받은 값을 컴포넌트의 로컬 상태로 설정
    • 외부 API 요청(REST API 등)
    • 라이브러리 사용 (D3, Video.js 등...)
    • setInterval을 통한 반복작업 혹은 setTimeout을 통한 작업 예약

    💥 참고로 리액트 컴포넌트는 부모 컴포넌트가 리렌더링 되면 자식 컴포넌트 또한 리렌더링 된다 (바뀐 내용이 없다 할지라도!)

     

     

    1-2. 리액트가 언마운트 시에 하는 작업들

    • setInterval, setTimeout 을 사용하여 등록한 작업들 clear 하기 (clearInterval, clearTimeout)
    • 라이브러리 인스턴스 제거

     

     

    1-3. Hook 컴포넌트를 작성할때 유의할점

    • return문이 hook보다 위에 있으면 안된다
      👉 컴포넌트 안에 존재하는 hook은 무조건 다 실행이 되어야한다.
            예외 처리로 작성한 if문 안에 return 문이 있을 경우에는 hook보다 위에 존재해서는 안되며,
            가장 아래쪽에 작성하는게 좋다
    • Hook끼리는 중첩이 불가능하다
    • (이거는 불확실한데...) useEffect hook은 해당 컴포넌트 함수 return문 바로 위에 위치하는게 좋다 (안전빵인가 뭔가...)

     

     

     

    2. Hook의 종류

    useEffect

    useEffeck hook이 실행될 때

    1. 마운트 : 컴포넌트가 마운트 됐을때(처음 나타났을 때)
    2. 언마운트 : 컴포넌트가 언마운트 됐을때 (사라질 때)
    3. 업데이트 될 때 : 특정 props가 바뀔 때
    useEffect(() => {
    	// 첫번째 파라미터 (함수) ...
    }, [ /* 두번째 파라미터 (deps, 의존성 배열) */ ]);
    • 첫번째 파라미터 : 함수가 들어가는 부분. 함수를 반환하게 되면 이를 cleanup 함수라고 부르게 된다. cleanup 함수는 useEffect에 대한 뒷정리를 해준다. 두번째 파라미터(deps)가 비어있는 경우에는 컴포넌트가 사라질 때 cleanup 함수가 호출된다
    • 두번째 파라미터 : 의존값이 들어있는 배열을 넣는다. 이 영역을 비워두면 컴포넌트가 처음 나타낼때만 첫번째 파라미터 함수가 호출된다. 반대로 값이 있다면 컴포넌트가 처음 마운트 될 때에도 호출되고, 지정한 값이 바뀔 때에도 호출되고, 언마운트 시에도 호출되고, 값이 바뀌기 직전에도 호출된다
    • 화면이 렌더링 될 때 실행할 콜백 함수와 렌더링을 유발하는 대상 리스트를 파라미터로 넘겨줄 수 있다
    // type EffectCallback = () => (void | (() => void | undefined));
    
    // 예제
    const revokeToken = () => { /.../ };
    
    useEffect(() => {
        void revokeToken();
     }, []);
    
    useEffect(() => {
    	return () => {
    		revokeToken();
    	};
    }, []);
    • 콜백 함수(revokeToken)는 리턴 값으로 void나 인자없이 void를 반환하는 함수를 반환할 수 있다
    • void를 반환하는 경우 화면이 렌더링 될 때 함수의 로직이 실행 (revokeToken 실행)
    • 그러나 함수를 반환하는 경우 그 함수는 컴포넌트가 언마운트 되기 전이나 업데이트 되기 직전에 실행된다

    useEffect를 사용할때 규칙
    useEffect 안에서 사용하는 상태나 props가 있다면 useEffect의 deps에 넣어줘야한다 만약 넣지 않게 된다면 useEffect에 등록한 함수가 실행 될 때 최신 props, 상태를 가르키지 않게 된다
    그러나, 꼭 이건 필수가 아니며 가끔은 오히려 deps를 넣어줬을때 무한 렌더링에 걸리는 상태가 발생한다(...) 적절하게 판단해서 쓰면 될 듯!!!

     

     

    useLayoutEffect

    브라우저가 화면에 DOM을 그리기 전에 실행

    useEffect와 형태는 같지만 차이점은

    • useEffect는 DOM의 레이아웃 배치와 페인트가 끝난 후 호출
    • 한마디로 DOM에 반영이 안된채 기본형태가 표출되었다가 업데이트가 됨

    DOM이 그러지기전에 업데이트가 되어야하는 형태여야하면 useLayoutEffect를 사용하면 된다

     

     

     

    useMemo

    성능 최적화를 위한 Hook. 값을 캐싱 memoized를 의미하며, 이전에 계산한 값을 재사용한다는 의미를 가지고 있다

    Ex. 원하는 값이 바뀔때만 호출하고 싶은데 연관 없는 값이 바뀔때도 같이 호출된다? ⇒ 이건 부모 컴포넌트가 바뀌니까 호출되는거 ⇒ 그럴때 쓰는게 useMemo이다!

    useMemo(() => 
    /// 첫번째 파라미터, 함수
    , [/* 두번째 파라미터, deps배열 */]);
    • 첫번째 파라미터 : 어떻게 연산할지 정의하는 함수를 넣어준다
    • 두번째 파라미터 : deps 배열을 넣어준다. 이 배열 안에 넣은 내용이 바뀌면 첫번째 파라미터로 등록한 함수를 호출해서 값을 연산해주고, 만약에 내용이 바뀌지 않았다면 이전에 연산한 값을 재사용한다

     

     

    React.memo

    컴포넌트의 props가 바뀌지 않았다면 리렌더링을 방지하여 컴포넌트의 리렌더링 성능 최적화를 해줄 수 있음

    export default React.memo(CreateUser);
    
    const User = React.memo(function User({}) {}
    

    해당 컴포넌트 파일의 export 부분을 감싸주거나 함수를 감싸주면 된다
    또한, 두번째 파라미터에 propsAreEqual이라는 함수를 사용하여 특정 값들만 비교하는 것도 가능하다

    export default React.memo(
    	UserList,
    	(prevProps, nextProps) => prevProps.users === nextProps,users
    );
    

    💥 주의할점!

    함수형 업데이트 전환을 안 했는데 위와 같이 props만 비교하게 된다면
    다른 함수들이 최신 deps 배열을 참조하지 않으므로 심각한 오류가 발생할 수 있다

    ✅ React.memo와 useMemo 차이점

    // React.memo
    const MyComponent = React.memo((props) => {
    	return ( ... );
    });
    • HoC이므로 인자로 받은 컴포넌트를 새로운 별도의 컴포넌트로 만든다
    • 만약 컴포넌트가 같은 props를 받을 때 같은 결과를 렌더링 한다면 React.memo를 사용하여 불필요한 컴포넌트 렌더링을 방지할 수 있다
    • React.memo는 오직 props가 변경 됐는지 아닌지만 체크한다
    • 만약 React.memo에 감싸진 함수형 컴포넌트가 함수 내부에서 useState나 useContext 같은 Hook을 사용하고 있다면 State, Context가 변경될 때마다 리렌더링 된다
    // 만약 비교 방식으로 작성하고 싶다면
    function MyComponent(props) {
    	// 컴포넌트 렌더링 코드
    }
    
    function areEqual(prevProps, nextProps) {
    	// 만약 전달되는 nextProps가 prevProps와 같다면 true,
    	// 같지 않다면 false를 반환
    }
    
    export default React.memo(MyComponent, areEqual);

     

     

     

    useCallback

    useMemo와 비슷한 Hook. 함수 캐싱
    useMemo를 기반으로 만들어졌다 조금 다른 점이라면 함수를 위해서 사용할때 더욱 간편하다는 점

    useMemo는 특정 결과값을 재사용 할 때 사용하는 반면,
    useCallback은 특정 함수를 새로 만들지 않고 재사용하고 싶을때 사용

    const onToggle = useMemo(
      () => () => {
        /* ... */
      },
      [users]
    );

    useCallback을 사용함으로써 바로 이뤄낼 수 있는 눈에 띄는 최적화는 없다

    useCallback을 사용할때 규칙

    useEffect 안에서 사용하는 상태나 props가 있다면 useEffect의 deps에 넣어줘야한다
    만약 넣지 않게 된다면 useEffect에 등록한 함수가 실행 될 때 최신 props/상태를 가르키지 않게 된다
    props로 받아오는 함수가 있다면 이 또한 deps에 넣어줘야한다

    👉 컴포넌트가 리렌더링 되는지 확인하는 법

    크롬 확장 프로그램에서 React DevTools를 설치 후 Highlight Updates를 체크해주면 된다

     

     

     

     

    useReducer

    상태를 관리할때 쓰는 useState와 비슷한 Hook이다
    useReducer를 사용하면 컴포넌트의 상태 업데이트 로직을 컴포넌트에서 분리시킬 수 있다
    상태 업데이트 로직을 컴포넌트 바깥에서 작성하거나, 다른 파일에서 작성 후 불러와서 사용 가능하다

    const [state, dispatch] = useReducer(reducer, initialState);
    
    • state : 컴포넌트에서 사용할 수 있는 상태
    • dispatch : 액션을 발생시키는 함수 Ex. dispatch({ type: 'INCREMENT' })
    • reducer : reducer 함수
    • initialState : 초기상태

    reducer란? → 현재 상태와 액션 객체를 파라미터로 받아와서 새로운 상태를 반환해주는 함수

    function reducer(state, action) {
    	// 새로운 상태를 만드는 로직
    	// ...
    	return nextState;  // 이 반환 값은 곧 컴포넌트가 지닐 새로운 상태
    }
    

    action이란? → 업데이트를 위한 정보를 가지고 있음. 주로 type 값을 지닌 객체 형태로 사용하지만. 필수는 아님

    // 카운터에 1을 더하는 액선
    {
    	type: 'INCREMENT'
    }
    // 카운터에 1을 빼는 액션
    {
    	type: 'DECREMENT'
    }
    // input 값을 바꾸는 액션
    {
    	type: 'CHANGE_INPUT',
    	key: 'email',
    	value: 'tester@react.com'
    }
    // 새 할일을 등록하는 액션
    {
    	type:'ADD_TODO',
    	todo: {
    		id: 1,
    		text: 'useReducer' 배우기,
    		done: false,
    	}
    }

     

     

     

     

    useReducer VS useState

    1. 관리하는 값이 하나이고 단순하다? → useState

    2. 관리하는 값이 여러개이고 구조가 복잡하다? → useReducer

    3. dispatch를 전역(context API)으로 사용하고 싶다? → useReducer

     

     

     

     

    3. 리액트 18 ver에 추가된 Hook

    쓱 최신 버전 업데이트 내용을 훑어봤으나 우리가 실무에서 바로 쓸 수 있는 Hook은 크게 없는 것 같더라고요
    (성능 최적화를 위한 hook이 많은 것 같고... 생각보다 개념들이 다 어려웠다...)
    그래도 그 중 알고 있으면 나중에 생각나서 검색 해볼법한 것들만 작성해보겠습니다!

     

    useId

    유니크 아이디를 생성해주는 hook
    새로운 스트리밍 렌더러가 HTML을 순서에 어긋나지 않게 전달해줄 수 있다
    아이디는 기본적으로 트리 내부의 노드의 위치를 나타내는 base 32 문자열이다
    (궁금한게 서어어얼마 DB에서 제공하는 유니크 id와 겹칠일은… 없겠죠?)

    아이디 생성 알고리즘 자료 ⇒ https://github.com/facebook/react/pull/22644

    import { useId } from 'react';
    
    const id = useId();

     

     

     

    useTransition

    일부 상태 업데이트를 긴급하지 않은 것으로 표시할 수 있다
    이것으로 표시되지 않은 상태 업데이트는 긴급한 것으로 간주된다
    긴급한 상태 업데이트가 긴급하지 않은 상태 업데이트를 중단할 수 있다

    function App() {
    	const [resource, setResource] = useState(initialResource);
    	const [startTransition, isPending] = useTransition({ timeoutMs: 3000 })
    	
    	return (
    		<>
    			<button 
    				disabled={isPending}
    				onClick={() => {
    					startTransition(() => {
    						const nextUserId = getNextId(resource.userId)
    						setResource(fetchProfileDate(nextUserId))
    					})
    				}}
    			>
    				다음
    			</button>
    		</>
    		{isPending ? 'Loading...' : null}
    		<ProfilePage resource={resource} /><
    	)
    }
    • startTransition : 함수. 리액트에 어떤 상태변화를 지연하고 싶은지 지정할 수 있다
    • isPending : 진행 여부로, 트랜지션이 진행중인지 알 수 있다
    • timeoutMs : 최대 3초간 이전 화면을 유지한다는 의미

    위 예시 코드는 버튼을 눌러도 바로 로딩 상태로 전환되는 것이 아닌, 이전 화면에서 진행 상태를 볼 수 있게 된다

     

     

     

    useDeferreValue

    트리에서 급하지 않은 부분의 재렌더링을 지연할 수 있다
    debounce 개념과 비슷하지만 장점이 더 많은 Hook이다!
    리액트는 첫번째 렌더링이 반영되는 즉시 지연 렌더링을 시도한다
    지연 렌더링은 인터럽트(끼어듦)가 가능하며, 사용자 입력을 차단하지 않는다

    import { useDeferredValue } from 'react';
    
    const deferredValue = useDeferredValue(value, {
    	timeoutMs: 5000,
    })

    value 값이 바뀌어도, 다른 렌더링이 발생하는 동안에는 최대 5초가 지연된다
    시간이 다 되거나, 렌더링이 완료된다면 deferredValue가 변경되면서 상태값이 변하게된다

     

     

     

    useInsertionEffect

    CSS-IN-JS 라이브러리가 렌더링 도중에 스타일을 삽입할 때 성능 문제를 해결할 수 있다
    해당 라이브러리를 사용하지 않는다면 사용할 일이 없는 hook이다
    useLayoutEffect와 비슷한데, 차이점은 DOM 노드에 대한 참조에 엑세세스를 할 수있다
    리액트가 DOM을 변환한 경우, 레이아웃에서 무언가를 읽기 전,
    페인트를 위해 브라우저에 값을 전달하기 전에 DOM에 대한 다른 변경과 동일한 타이밍에 작업을 하면 된다

    반응형

    댓글