• 티스토리 홈
  • 프로필사진
    redpome
  • 방명록
  • 공지사항
  • 태그
  • 블로그 관리
  • 글 작성
redpome
  • 프로필사진
    redpome
    • 분류 전체보기 (50)
      • 내일배움캠프 (23)
      • 웹개발 지식 (2)
      • 프로그래머스 (8)
      • React (7)
      • 코딩테스트 (1)
      • UI-UX (1)
      • 타입스크립트 (2)
      • Next.js (3)
  • 방문자 수
    • 전체:
    • 오늘:
    • 어제:
  • 최근 댓글
      등록된 댓글이 없습니다.
    • 최근 공지
        등록된 공지가 없습니다.
      # Home
      # 공지사항
      #
      # 태그
      # 검색결과
      # 방명록
      • React hooks(1)
        2025년 01월 27일
        • redpome
        • 작성자
        • 2025.01.27.:57

        useState를 통해 변수와 해당 변수를 관리하는 콜백 함수를 통해 상태 변경에 대한 연습과 리렌더링이 되는 과정을 실습했었다. useState외에도 다른 훅들을 간단히 정리해보자.


        리렌더링 조건

        우선 리액트에서 리렌더링이 되는 조건으로는 다음과 같이 정의할 수 있다.

         

        • 컴포넌트에서 state의 변경
        • 컴포넌트에서 Props의 변경
        • 부모 컴포넌트에서의 리렌더링은 자식 컴포넌트까지 영향을 미친다.

        위와 같이 하나의 상태를 변경하고나면 해당 상태만 변경하는 것이 아니라 다른 요소들도 리렌더링이 되는데 이는 불필요하게 비용이 발생시킨다, 따라서 최적화를 필요로하게 되고 대표적인 방법으로 memo를 통해 최적화를 하는 방법들이 존재한다.

         

        • React.memo : 컴포넌트 캐싱 - 해당 컴포넌트의 state 변경 시 props의 변경 전까지는 리렌더링 방지
        • useCallback : 함수 캐싱 - 함수도 참조에 의한  변수이므로 메모리에 함수를 저장하여 변경을 방지
        • useMemo : 값을 캐싱 - 렌더링 시 메모리에 값들을 저장하고 변경되는 상태 외에는 저장한 메모리를 불러와 사용

        당연히 위와 같은 방법들은 메모리를 요구하기에 필요에 따라 적절히 사용해야한다.

         

        UseMemo

         

        useMemo는 앞서 설명한 최적화와 관련이 있다. 부모 컴포넌트가 존재하고 3개의 자식 컴포넌트가 있다고하자, 이 때 자식 컴포넌트들은 형제 관계라하면 부모 컴포넌트가 렌더링 될 때 자식 컴포넌트에서 상태 변화가 없어도 리렌더링이 진행된다. 불필요한 리렌더링을 방지하기 위해 사용하는 것이 React.memo로서 자식 컴포넌트를 감싸 사용한다.

         

        export default React.memo(Box1);
        export default React.memo(Box2);
        export default React.memo(Box3);

         

         

        최초 렌더링을 제외하고 부모 컴포넌트에서의 상태 변경 후 리렌더링 시 자식 컴포넌트에는 리렌더링이 발생하지 않는다.

         

        왼쪽 - 최초 렌더링 후 부모 컴포넌트 상태 변경/오른쪽 - React.memo 적용 후 부모 컴포넌트 상태 변경 시

         

        useCallback

        인자로 들어오는 함수를 기억하는 useCallback은 함수가 참조 변수임을 이용하여 메모리에 해당 함수를 저장하는 방식이다.

         

        카운터 증가와 초기화 시 렌더링 되는 컴포넌트

        React.memo는 Box1 컴포넌트에 적용되었음에도 위와 같이 렌더링 되는 것을 알 수 있다. 함수형 컴포넌트를 사용하게 되면 리렌더링 시 코드가 다시 만들어지기에 객체의 한 종류인 함수는 주소값이 달라지게 된다. 따라서 모양만 같을뿐 하위 컴포넌트인 Box1은 Props가 변경된 것으로 인식한다.

         

        이하의 코드는  useCallback을 사용하여 메모이제이션을 사용하고 함수를 메모리 공간에 저장한 것이다. 결국 함수의 주소값이 고정되어 특정 상황외에는 변경되지 않는것이다.

        // 변경 전
        const initCount = () => {
          setCount(0);
        };
        
        // 변경 후
        const initCount = useCallback(() => {
          setCount(0);
        }, []);

         

         

         

         

        참고로 count의 값을 console.log로그로 확인하여 초기화 전후의 값을 확인하면 0으로 나오는데 이는 initCount의 dependency array가 []이기에 count=0일때의 시점을 저장하기 때문이다. [count]를 넣어 해당 값을 추적하도록하자.


        useContext

        부모와 자식 간 관계에서 Props를 통해 매개변수를 넘겨준다. 이때 넘겨주는 Props의 갯수가 많아지다보면 컴포넌트의 관리가 어려워지므로, 연관이 있는 컴포넌트 사이에서 공유하는 문맥을 사용하여 전역적인 데이터를 관리할 수 있도록한다.

         

        예시로서 부모,자식1,자식2에 해당하는 컴포넌트가 존재한다고하자, 최종적으로 자식2로 Props를 넘겨 화면을 렌더링할 때 자식1의 경우 단순히 부모-자식2 사이의 매개역할만 한다고할 때 자식1 컴포넌트의 역할은 무의미하게 느껴질 수 있다. 또한 넘겨주는 Props가 많아질 경우 관리가 힘들다.

         

        useContext 사용을 위해 Context 디렉터리에 다음과 같은 js를 생성했다.

        import { createContext } from "react";
        
        export const FamilyContext = createContext(null);

         

         

        최상위 부모의 컴포넌트가 다음과 같다고하자

        import React from "react";
        import Father from "./Father";
        import { FamilyContext } from "../context/FamilyContext";
        
        function GrandFather() {
          const houseName = "스파르타";
          const pocketMoney = 10000;
        
          return (
            <FamilyContext.Provider value={{ houseName, pocketMoney }}>
              <Father />
            </FamilyContext.Provider>
          );
        }
        
        export default GrandFather;

         

        Context 컴포넌트를 호출하고 Provider를 통해 props를 넘겨주어 Father라는 자식1 컴포넌트에 전달하였다.

        Father 컴포넌트에서는 다음과 같은 코드로 작성한다.

         

        import React from "react";
        import Child from "./Child";
        
        function Father() {
          return <Child />;
        }
        
        export default Father;

         

        마치 넘겨받은 Props가 없는 것처럼 보인다. 이후 Child인 자식2 컴포넌트를 반환하므로 자식2 컴포넌트의 코드는 다음과 같아진다.

         

        import React, { useContext } from "react";
        import { FamilyContext } from "../context/FamilyContext";
        
        function Child({ houseName, pocketMoney }) {
          const stressedWord = {
            color: "red",
            fontWeight: "900",
          };
        
          const data = useContext(FamilyContext);
          console.log("data", data);
        
          return (
            <div>
              나는 이 집안의 막내에요.
              <br />
              할아버지가 우리 집 이름은 <span style={stressedWord}>{data.houseName}</span>
              라고 하셨어요.
              <br />
              게다가 용돈도 <span style={stressedWord}>{data.pocketMoney}</span>원만큼이나
              주셨답니다.
            </div>
          );
        }
        
        export default Child;

         

         

        자식 컴포넌트에서는 Context.Provider를 통해 넘겨받은 인자에 접근할 수 있다. 따라서 useContext를 자식 컴포넌트에서 선언하고 해당 데이터를 사용함을 알 수 있다.

         


        useRef

        useState처럼 값을 저장할 수는 있지만 한 가지 차이점이라면 화면을 리렌더링하지 않는다는 점이다. 

        • useState - 리렌더링이 필요할 때 사용
        • useRef - 값 변경은 있지만 렌더링이 필요하지않을 때 사용

        특히 DOM 요소를 다룰때 많이 쓴다. 

        import { useState, useRef } from "react";
        import "./App.css";
        const App = () => {
          const [count, setCount] = useState(0);
          const countRef = useRef(0);
        
          const plus = () => {
            setCount(count + 1);
          };
          const refPlus = () => {
            countRef.current++;
          };
          return (
            <div>
              <h1>useRef</h1>
              <div>{count}</div>
              <button onClick={plus}>state+</button>
              <button>state-</button>
              <div>{countRef.current}</div>
              <button onClick={refPlus}>ref+</button>
              <button>ref-</button>
            </div>
          );
        };
        
        export default App;

         

        간단한 카운터로 useState의 값은 버튼을 누르면 바로 리렌더링이 되지만 useRef의 버튼은 그렇지않다. 그러다 useRef의 값은 useState의 버튼을 누르면 화면이 리렌더링 되므로 값이 찍힌다.

        ?? 근데, 내부 변수는 그냥 let 키워드로 선언해서 변수 사용하면 안되나요?
        아주 좋은 질문이에요!! let 키워드를 사용하게 되면 렌더링 시 어떻게 될까요? 다시 변수가 초기화 된답니다. 함수라서 그래요.
        리렌더링이 된다 = 함수가 다시 호출된다 = 내부 변수가 다시 쫙 초기화된다

         

        위의 사항도 참고하자.

         

        useEffect와의 사용

        useRef를 useEffect와 사용하여 사용자 입력이 필요한 양식에 포커스 시킬 수 있다.

        import { useEffect, useRef } from "react";
        import "./App.css";
        
        function App() {
          const idRef = useRef("");
        
          // 렌더링이 될 때
          useEffect(() => {
            idRef.current.focus();
          }, []);
        
          return (
            <>
              <div>
                아이디 : <input type="text" ref={idRef} />
              </div>
              <div>
                비밀번호 : <input type="password" />
              </div>
            </>
          );
        }
        
        export default App;

         

        input 필드에 페이지가 렌더링 될 때 바로 입력이 가능한 상태로 두어 사용자 경험을 높일 수 있다.

         

        import "./App.css";
        import { useState, useRef, useEffect } from "react";
        
        function App() {
          const [id, setId] = useState("");
          const idRef = useRef("");
          const passwordRef = useRef("");
          useEffect(() => {
            idRef.current.focus();
          }, []);
        
          const handleIdInput = (e) => {
            setId(e.target.value);
          };
          useEffect(() => {
            if (id.length >= 10) {
              passwordRef.current.focus();
            }
          }, [id]);
          return (
            <>
              <div>
                아이디 :{" "}
                <input type="text" value={id} onChange={handleIdInput} ref={idRef} />
              </div>
              <div>
                비밀번호 : <input type="password" ref={passwordRef} />
              </div>
            </>
          );
        }
        
        export default App;

         

        위의 코드에서 id.length의 자리가 10자리 이상이 되면 비밀번호 입력란에 포커스가 되도록해주었다. id 상태를 useEffect에서 의존성을 설정해 놓은것을 볼 수 있다. 

        배치 업데이트와 관련하여 useEffect의 동작원리를 설명하면 아래와 같다.

         

        • useEffect는 DOM이 업데이트된 후 실행된다. id가 여러 번 변경되면 리액트에서는 배치 업데이트를 수행한다.
        • 배치 업데이트는 id의 상태가 여러 번 변경되어도 한 번만 실행되도록하고 id.length>-10가 충족될 시 한 번만 실행되게한다.

         

        커스텀 훅

         

        hook을 만들 수도 있다. input을 통해 사용자의 입력을 받는 코드를 훅으로 만들고 관련 기능을 만들어놓으면 호출해서 손쉽게 쓸 수 있다.

         

        // src/App.jsx
        
        import React from "react";
        import useInput from "./hooks/useInput";
        
        const App = () => {
        	// 우리가 만든 훅을 마치 원래 있던 훅인것마냥 사용해봅니다. 
          const [title, onChangeTitleHandler] = useInput();
          const [body, onChangeBodyHandler] = useInput();
        
          return (
            <div>
              <input
                type="text"
                name="title"
                value={title}
                onChange={onChangeTitleHandler}
              />
        
              <input
                type="text"
                name="title"
                value={body}
                onChange={onChangeBodyHandler}
              />
            </div>
          );
        };
        
        export default App;

         

        위에서 useInput이라는 훅은 다음과 같이 만든 것이다.

         

        // src/hooks/useInput.js
        
        import React, { useState } from "react";
        
        const useInput = () => {
        	// 2. value는 useState로 관리하고, 
          const [value, setValue] = useState("");
        
        	// 3. 핸들러 로직도 구현합니다.
          const handler = (e) => {
            setValue(e.target.value);
          };
        
        	// 1. 이 훅은 [ ] 을 반환하는데, 첫번째는 value, 두번째는 핸들러를 반환합니다.
          return [value, handler];
        };
        
        export default useInput;

         

        만약 커스텀 훅을 만들지않고 위의 코드를 그대로 작성해도 되겠지만 이를 한 번에 만들고 필요할 때만 불러서 쓰면 더욱 사용하기 좋을 것이다.

         

        'React' 카테고리의 다른 글

        Redux, RTK 사용법 정리 - (2)  (0) 2025.02.10
        Redux, RTK 사용법 정리 - (1)  (0) 2025.02.07
        투두 리스트 복기  (1) 2025.01.24
        개인과제 회고 - 올림픽 메달 기록  (0) 2025.01.22
        리액트 리스트 추가 및 생성  (1) 2025.01.22
        다음글
        다음 글이 없습니다.
        이전글
        이전 글이 없습니다.
        댓글
      조회된 결과가 없습니다.
      스킨 업데이트 안내
      현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
      ("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
      목차
      표시할 목차가 없습니다.
        • 안녕하세요
        • 감사해요
        • 잘있어요

        티스토리툴바