[모던리액트]- 3장 (1) 리액트 훅 깊게 살펴보기

nais·2024년 9월 27일
0

모던리액트

목록 보기
3/6
post-thumbnail

3.1 리액트의 모든 훅 파헤치기

3.1.1 useState

  • 함수 컴포넌트 내부에서 상태를 정의하고, 이상태를 관리할 수 있게 해주는 훅

  • 함수 컴포넌트는 매번 함수를 실행해 렌더링이 일어나고 함수 내의 값인 state의 값도 재실행 된다 이 값을 유지하고 사용하기 위해 리액트는 클로저를 활용

  • 게으른 초기화(lazy initialization)
    useState에 변수 대신 함수를 넣어서 초기값이 복잡하거나 무거운 연산(ex.localStorage, sessionStorage, 고차함수)이 요구 될 때 사용,이는 컴포넌트가 처음 구동될 때만 실행되고, 이후 리렌더링 시에는 실행되지 않음

//함수를 실행해 값을 반환한다 
const [count, setCount] = useState(()=>Number.parseInt(window.localStorage.getItem(cacheKey)))
                      

3.1.2 useEffect

  • 애플리케이션 내 컴포넌트의 여러 값들을활용해 부수효과를 만드는 메커니즘
function Componenet(){
  	
  // 의존성 배열을 가진 형태 
	useEffect(()=>{
      ...
    },[props,state])
  
  // 아무런 값이 없는 빈 배열 
  	useEffect(()=>{
      ...
    },[])
  
   // 배열 자체를 넣지 않고 생략 
  	useEffect(()=>{
      ...
    },[])
            

}
  • 의존성 배열을 가진 형태 : 의존성에 있는 값을 보면서 이 의존성의 값이 이전과 다른게 하나라도 있으면(얕은 비교) 부수효과를 실행, useEffect의 본질이다.

  • 아무런 값이 없는 빈 배열 : 리액트가 이 useEffect 는 비교할 의존성이 없다고 판단해 최초 렌더링 직후에 실행 된 다음 더 이상 실행되지 않음

  • 배열 자체를 넣지 않고 생략 : 컴포넌트가 렌더링 될 때마다 useEffect가 실행
    컴포넌트의 렌더링이 완료된 이후에 실행되기 때문에 csr에서 실행되는 것을 보장

  • 클린업 함수

    • 새로운 값을 기반으로 렌더링 뒤에 실행, 변경된 값을 읽는 것이 아니라 함수가 정의 됐을 당시 선언했던 이전 값을 보고 실행
    • 이벤트를 추가하기 이전에 등록했던 이벤트 핸드러를 삭제해 주면서 이벤트 핸들러 무한히 추가를 방지
    • 언마운트라기보다는 함수 컴포넌트가 리렌더링 됐을 때 의존성 변화가 있었을 당시 이전의 값을 기준으로 실행되는 청소의 개념이다
  • 사용할 때 주의 점

    • 컴포넌트의 state , props와 같은 어떤 값의 변경과 useEffect 의 부수효과가 별개로 작동하지 않도록 한다 (ex.의존성 배열에 해당되지 않은 state, props 에 접근하거나 부수효과 실행)
    • 첫번 째 인수에 함수명은 부여하라 => 왜 만들어진 useEffect인지 인지가 쉬움
    • 거대한 useEffect를 만들지 말고 부득이하게 크게 만들어야 한다면 분리하여 여러개 만드는 방법으로 가라
    • 불필요한 외부함수를 만들지마라 (useEffect 안에서 사용하는 함수면 useEffect 내부에서 선언하기)

3.1.3 useMemo

  • 비용이 큰 연산에 대한 결과를 저장(메모이제이션)해 두고, 저장된 값을 반환하는 훅
  • 단순한 값뿐만 아니라 컴포넌트로 반환 가능(물론 React.memo를 쓰는 것이 더 현명)
  • 값을 계산할 때 해당 값을 연산하는데 비용이 많이 든다면 사용하기 좋다

import { useMemo } from 'react'

const memoizedValue = useMemo(()=>expensiveComputation(a,b),[a,b]) //인수로 어떠한 값을 반환하는 생성함수,해당 함수가 의존하는 값의 배열 형태 

3.1.4 useCallback

  • 인수로 넘겨 받은 콜백 자체를 기억( 특정 함수를 새로 만들지 않고 다시 재사용한다는 의미)
  • 함수의 재생성을 막아 불필요한 리소스 또는 리렌더링을 방지
  • useMemo 로도 만들어서 사용할 수 있지만 함수를 메모이제이션 하는 용도라면 useCallback 이 좀 더 간단하게 사용 가능

import { useState, useCallback } from 'react'

const [status, setStatus ] = useState(false);

const toggle = () => { 
	setStatus(!status)
}
const toggle = useCallback(toggle,[status]);

3.1.5 useRef

  • useState와 동일하게 컴포넌트 내부에서 렌더링이 일어나도 변경 가능한 상태값을 저장한다는 공통점
  • 반환값인 객체 내부에 있는 current로 값에 접근 또는 변경할 수 있다
  • current 값이 변하더라도 렌더링을 발생시키지 않음
  • 컴포넌트가 렌더링 될 때만 생성되고 컴포넌트 인스턴스가 여러개라도 각각 별개의 값을 바라본다
  • 최초 기본값은 return에 정의해둔 DOM 이 아니고 useRef() 로 넘겨받은 인수이다
  • 선언 당시에는 아직 컴포넌트가 렌더링되기 전이라 컴포넌트 DOM 이 반환되기 전이므라 undefined
function RefComponent(){
  const count = useRef(0)
  
  const handleClick = () => {
 	count.current +=1;   
  }
  
  
  return <button onClick={handleClick}>{count.current}</button>
 }

3.1.6 useContext

  • 상위 컴포넌트에서 만들어진 Context를 컴포넌트에서 사용할 수 있도록 만들어진 훅
  • 상위 컴포넌트 어딘가에서 선언된 <Context.Provider/> 에서 제공한 값을 사용( 여러개 일 땐 가장 가까운 Provider 의 값을 가져온다)
  • Props drilling 을 막아줄 수는 있지만, 이 훅이 있는 컴포넌트는 Provider 와 의존성을 가지게 됨
  • 단순히 props 를 하위로 전달해주는 (상태 주입 API) ,렌더링 최적화는 이루어지지 않음
const Context = createContext();
		  
    function Child() {
      const value = useContext(Context);
      return (
        <div>자식이다</div>
      );
    }
    
    function App() {
    
      return (
        <>
          <Context.Provider value={{ hello: "react"}}>
            <Context.Provider value={{ hello: "NextJS" }}>
              <Child />
            </Context.Provider>
          </Context.Provider>
        </>
      );
    }

3.1.7 useReducer

  • useState와 비슷한 형태를 띠지만 좀 더 복잡한 상태값을 미리 정의해 놓은 시나리오에 따라 관리가능
  • 사전에 정의된 dispatcher로만 수정할 수 있게 만들어 줌,state 값을 변경하는 시나리오를 제한적으로 두고 이에 대한 변경을 빠르게 확인이 가능
  • useState 와 useReducer 둘 다 세부 작동과 쓰임에만 차이가 있고, 결국 클로저를 활용해 값을 가둬서 관리하는 것이다

3.1.8 useImperativeHandle

ref 를 사용하는 방법 forwardRef

  • ref 는 useRef에서 반환한 객체로, HTMLElement 접근으로 주로 사용
  • 리액트 컴포넌트의 예약어로 별도로 선언돼 있지 않아도 prop으로 사용할수있다
  • 네이밍의 완전한 자유보다는 ref 전달의 예측이 가능
  • ref 를 받고자 하는 컴포넌트를 forwardRef 로 감싸야지 사용이 가능
const ChildComponent = forwardRef((props, ref) => {
  return <div> 내가 자식 컴포넌트 </div>;
});
const ParentComonent = () => {
  const inputRef = useRef();
  return (
    <>
      <div>내가 부모 컴포넌트야</div>
      <ChildComponent ref={inputRef} />
    </>
  );
};

const ChildComponent = forwardRef((props: defaultProps, ref) => {
  useImperativeHandle(
    ref,
    () => ({
      alert: () => alert(props.value),
    }),
    [props.value],
  );
  return <input ref={ref} {...props} />;
});

const ParentComonent = () => {
  const inputRef = useRef();
  const [text, setText] = useState("");

  const handleClick = () => {
    if (!inputRef.current) return;
    inputRef.current.alert();
  };

  return (
    <>
      <ChildComponent ref={inputRef} value={text} />
      <button onClick={handleClick}></button>
    </>
  );
};
  • 부모에게서 넘겨 받은 ref 를 원하는 값이나 액션을 정의해서 사용할 수 있는 훅
  • 원래 ref 는 {current: HTMLElement} 와 같응 형태로 HTMLElement 만 주입할 수 있는 객체인데 해당 훅을 통해서 추가적인 동작이 가능해졌다

3.1.9 useLayoutEffect

실행 순서
1. 리액트 DOM 을 업데이트
2. useLayoutEffect를 실행
3. 브라우저에서 변경 사항을 반영
4. useEffect를 실행

  • useEffect 와 사용 방법이 같아서인지 동일한 모습으로 작동하는 것 처럼 보인다
  • useLayoutEffect는 리액트가 DOM을 업데이트 (렌더링) 후에 콜백 함수를 실행 함수는 동기적으로 발생한다
  • useLayoutEffect가 완료 될 때까지 리액트 컴포넌트는 기다린다 그래서 일시 중지 되는 문제가 발생할 수 있다
  • DOM은 계산 됐지만 이것이 화면이 반영되기 전인 애니메이션, 스크롤 위치 제어 등이 알맞다

3.1.10 useDebugValue

  • 디버깅 하고 싶은 정보를 훅에다 사용하면 리액트 개발자 도구에서 볼 수 있다
  • 사용 시에 오직 다른 훅 내부에서만 실행할 수 있다( 컴포넌트 레벨에서는 작동하지 않음)
profile
왜가 디폴트값인 프론트엔드 개발자

0개의 댓글