[React] Hooks

👉🏼 KIM·2023년 7월 2일
0

useState

  • 가장 기본적인 hook
  • 함수형 컴포넌트 내에서 가변적인 상태를 갖게 함.
  • 카운터, todolist
const [state, setState] = useState(초기값)

배치형 업데이트(기본) VS 함수형 업데이트

function App() {
  const [number, setNumber] = useState(0);
  return (
    <>
      <div>Number State: {number}</div>
      <button
        onClick={() => {
          //기존 업데이트
          //setNumber(number + 1);

          //함수형 업데이트
          setNumber((currentNumber) => {
            return currentNumber + 1;
          });
        }}
      >
        누르면 UP
      </button>
    </>
  );
}
function App() {
  const [number, setNumber] = useState(0);
  return (
    <>
      <div>Number State: {number}</div>
      <button
        onClick={() => {
          //기존 업데이트 -> 배치형으로 처리되고, 
          //한꺼번에 변경된 내용들을 모아서 한번만 반영
          //세번을 써도 한꺼번에 모으기 때문에 똑같은 것이 3번 반복이라 같은 것으로 생각해서 업데이트를 치지 않는다.
          //setNumber(number + 1);
          //setNumber(number + 1);
          //setNumber(number + 1);

          //함수형 업데이트
          //명령들을 모아서 순차적으로 한번씩 실행시킨다
          //괄호 안, 즉 인자 부분에 현재 상태의 state를 넣고 화살표 후에는 바뀐 state를 반환한다. -> 최신값 유지
          setNumber((currentNumber) => currentNumber + 1);
          setNumber((currentNumber) => currentNumber + 1);
          setNumber((currentNumber) => currentNumber + 1);
        }}
      >
        누르면 UP
      </button>
    </>
  );
}

렌더링이 잦다 -> 성능에 이슈가 있다.
그렇게 때문에 한꺼번에 요청 사항을 모아서 한번만 처리하는게 렌더링을 줄일 수 있는 좋은 방법이다. 이래서 배치 업데이트라는 방식을 사용하는 것이다.


useEffect

  • 렌더링될 때, 특정한 작업을 수행해야 할 때 설정하는 훅
  • 컴포넌트가 화면에 보여졌을 때, 컴포넌트가 화면에서 사라졌을 때(return)
  • 의존성 배열(dependency array)
function App() {
  const [value, setValue] = useState("");

  useEffect(() => {
    console.log("hihi");
  });

  return (
    <div>
      <input
        text="text"
        value={value}
        onChange={(event) => {
          setValue(event.target.value);
        }}
      />
    </div>
  );
}
  1. input에 값을 입력
  2. value, 즉 state가 변경
  3. state가 바뀌었기 때문에 App 컴포넌트가 리렌더링
  4. 리렌더링 -> useEffect()
  5. (1~4번) 반복

useEffect를 통해서 한번만 콘솔이 찍혔으면 좋겠다.
이때 알아두어야 할 개념이 의존성 배열이다.

  • 이 배열에 값을 넣으면 그 값이 바뀔 때만 useEffect를 실행한다.
//[] 빈배열을 추가하면 화면이 처음 로딩될 때만 동작

  useEffect(() => {
    console.log("hihi");
  }, []); 

//value값이 바뀔때만 동작. 

  useEffect(() => {
    console.log(`hihi ${value}`);
  }, [value]);

Clean-up함수란,
useEffect()에서 파라미터로 넣은 함수의 return 함수이다. return을 쓰면 contents가 사라질때 동작한다.


useRef

  • 저장공간으로써의 useRef, DOM요소 접근 방법으로써의 useRef의 활용방법이 다르다.
  • DOM요소에 접근할 수 있도록 하는 hook. ( DOM요소를 어딘가에 저장해야한다. )
function App() {
  const ref = useRef("초기값");
  console.log("ref", ref);
  return <div></div>;
  
  ref 
{current: "초기값"}
current: "초기값"
  
}

설정된 ref 값은 컴포넌트가 계속해서 렌더링되어도 unmount 전까지 값을 유지한다.

저장공간

  • state와 비슷한 역할을 한다. 다만 state는 변화가 일어나면 다시 렌더링이 일어나고 내부 변수들은 초기화된다.
  • ref에 저장한 값은 렌더링을 일으키지 않는다. 즉, ref의 값 변화가 일어나도 렌더링으로 인해 내부 변수들이 초기화되는 것을 막을 수 있다.
  • 컴포넌트가 100번 렌더링 -> ref에 저장한 값을 유지된다.

💡 state는 리렌더링이 꼭 필요한 값을 다룰때 쓰면 된다.
💡 ref는 리렌더링을 발생시키지 않는 값을 저장할 때 사용한다.

function App() {
  const [count, setCount] = useState(0);
  const countRef = useRef(0);

  const plusStateCountButtonHandler = () => {
    setCount(count + 1);
  };

  const plusRefCountButtonHandler = () => {
    countRef.current++;
    console.log("countRef", countRef);
    /*
    	렌더링은 일어나고 있지 않아서 값이 바뀌지는 않지만 
    	console로 찍어보면 카운트가 증가하고 있다는 것을 알 수 있다. 
    */
  };
  return (
    <>
      <div
        style={{
          border: "1px solid black",
          margin: "10px",
          padding: "10px"
        }}
      >
        state 영역입니다. {count} <br />
        <button onClick={plusStateCountButtonHandler}>state 증가</button>
      </div>
      <div
        style={{
          border: "1px solid black",
          margin: "10px",
          padding: "10px"
        }}
      >
        ref 영역입니다. {countRef.current}
        <br />
        <button onClick={plusRefCountButtonHandler}>Ref 증가</button>
      </div>
    </>
  );
}

DOM

  • 렌더링이 되자마자 특정 input이 focusing 되어야 한다면 useRef를 사용.
function App() {
  const [id, setId] = useState("");

  const idValueHandler = (event) => {
    setId(event.target.value);
  };
  const idRef = useRef("");
  const pwRef = useRef("");

  //화면이 렌더링 될때, 어떤 작업을 하고 싶다. : useEffect
  useEffect(() => {
    //console.log("렌더링");
    idRef.current.focus();
    //pwRef.current.focus();
  }, []);

  useEffect(() => {
    if (id.length >= 10) {
      pwRef.current.focus();
    }
  }, [id]);
  return (
    <>
      <div>
        아이디 :{" "}
        <input type="text" ref={idRef} value={id} onChange={idValueHandler} />
      </div>
      <div>
        패스워드 : <input type="password" ref={pwRef} />
      </div>
    </>
  );
}

useContext

context API 필수 개념

  • createContext: context 생성
  • Consumer: context 변화 감지
  • Provider: context 전달(to 하위 컴포넌트)

단점

  • 렌더링 문제 : useContext를 사용할때, Provider에서 제공한 value가 달라진다면 useContext를 사용하고 있는 모든 컴포넌트가 리렌더링된다. 따라서 value부분을 항상 신경써줘야 한다. (보완: 메모이제이션)



React Hooks(최적화)

리-렌더링의 발생 조건

  1. 컴포넌트에서 state가 바뀌었을때
  2. 컴포넌트가 내려받은 props가 변경되었을때
  3. 부모 컴포넌트가 리-렌더링 된 경우 자식 컴포넌트 모두

리액트에서 리렌더링이 빈번하게 일어나는 것은 좋은 것이 아니다.(cost(=비용)이 많이 든다.) 불필요한 리렌더링을 줄이기 위해 최적화(Optimization)를 해주어야한다.
대표적인 방법은 memo(React.memo): 컴포넌트를 캐싱, useCallback: 함수를 캐싱, useMemo: 값을 캐싱 3가지가 있다.

1. React.memo

리-렌더링의 발생 조건 중 3번째 경우. 즉, 부모 컴포넌트가 리렌더링되면 자식 컴포넌트는 모두 리렌더링 된다.

  • 1번 컴포넌트가 리렌더링 된 경우, 2~7번이 모두 리렌더링 된다.
  • 4번 컴포넌트가 리렌더링 된 경우, 6,7번이 모두 리렌더링 된다.
    자녀 컴포넌트 입장에서는 바뀐게 없는데 다시 리렌더링 된다고 생각할 수 있다. 이 부분을 돕는 도구가 바로 React.memo이다.

memo를 통해 해결해보기

React.memo를 이용해서 컴포넌트를 메모리에 저장해두고 필요할 때 갖다 쓸 수 있다. 이렇게 하면 부모 컴포넌트의 state의 변경으로 인해 props가 변경이 일어나지 않는 한 컴포넌트는 리렌더링 되지 않는다.
이것은 컴포넌드 memoization라고 한다.

export default content
👇🏼 //아래처럼 메모에 저장해준다. 
export default React.memo(content)

2. useCallback

React.memo는 컴포넌트를 메모이제이션했다면, useCallback은 인자로 들어오는 함수 자체를 기억(메모이제이션)한다.
메모이제이션을 했음에도 불구하고 리렌더링 현상이 일어나는데 그것은 함수형 컴포넌트로 만들었기 때문에 새로운 메모리공간에 저장을 하고 바뀌었다고 판단해 리렌더링이 된다.

//이것을 useCallback으로 감싸주면 된다. 
const initCount = () => {
  setCount(0);
}

const initCount = useCallback(() => {
  setCount(0);
), [count]}
/*
	[]는 의존성 배열에 count를 안넣어주면 계속 0인상태로 스냅샷을 찍어서 
    보관해두기 때문에 뭘 해도  0 임.
*/

3. useMemo

여기서 말하는 memo는 memoization을 뜻한다. 기억한다.
동일한 값을 반환하는 함수를 계속 호출해야 하면 필요없는 렌더링을 한다고 볼 수있다. 맨 처음 해당 값을 반환할때 그 값을 특별한 곳(메모리)에 저장한다. 이렇게 하면 필요할때마다 다시 함수를 호출해서 계산하는게 아니라 이미 저장한 값을 단순히 꺼내와서 쓸 수 있다. 이러한 기법을 캐싱을 한다.라고 표현한다. (함수를 리턴한 값, 값 자체를 캐싱한다.)

//as-is
const value = 반환할_함수();

//to-be
const value = useMemo(() => {
	return 반환할_함수()
}, [dependencyArray]);

dependencyArray의 값이 변경될 때만 반환할_함수()가 호출된다. 그 외의 경우에는 memoization해놨던 값을 가져오기만 하면 된다.
남발하면 별도의 메모리 확보를 너무나 많이 하게 되기 때문에 오히려 성능이 악화될 수 있다. 필요할때만 쓰자!

profile
프론트는 순항중 ¿¿

0개의 댓글