• React

[React] React 동작원리 ( + useMemo / useCallback )

man_on 2022. 1. 27. 15:21
반응형

 

 

 

     

     

    How does react work?

     

     

    React

    : A JavsScript library for building user interfaces.

    React는 component로 UI를 효율적으로 구축하고 업데이트한다.

     

     

     

     

    React는 component와 state를 관리하는 라이브러리일뿐이다!

    컴포넌트들을 관리하고, 컴포넌트의 props, state, context가 변할 때 마다 그것을 쓰는 component를 update하고

    컴포넌트가 화면에 새로운 내용을 띄우려는지 확인한다. 

    새로운 내용이 있으면 react가 reactDOM으로 전달해서 DOM이 새로운 화면, 즉 새로운 component를 화면에 띄운다.

     

    ( DOM : html을 계층구조화하여 트리구조로 만든 객체 모델이다.

      DOM을 활용하여 JavaScript로 자유롭게 html태그를 객체처럼 다뤄서 수정과 컨텐츠 생성이 가능하다.)

     

     

     

    ReactDOM?

    web interface이며, 이는 react 자체나 react파일은 웹 브라우저와 관련이 없다.

    ( 브라우저의 일부인 real DOM을 상대하니까! )

     

     

    Virtual DOM?

    React는 virtualDOM을 사용하는데, virtualDOM은 'component tree'를 결정한다.

    모든 컴포넌트엔 JSX 코드를 return 하는 하위 tree가 있는데 virtualDOM은 트리의 현재 모양과 최종모양을 정한다.

    state를 update한 후 그 정보를 ReactDOM이 전달받고 차이점을 파악한 후

    real DOM과 virtualDOM을 맞추기 위해 realDOM을 조작한다. (component tree에서 받은 가상 snapshot을 토대로)

     

    virtual DOM이 왜 필요 ?

    real DOM이 조작되는 작업은 무겁기 때문에 virtual DOM이 존재한다.

    react는 실제 DOM의 변경 사항을 빠르게 파악하고 반영하기 위하여 내부적으로 가상 DOM을 만들어서 관리하는 것이다.

     

     

    [간단요약]

    원리 : 각 component가 반환하는 element를 이전에 반환한것과 비교하고, 다른경우에만 해당하는 DOM node에 CRUD 작업수행.

     

     

     

     

     

     

     

    component 재평가와 re-rendering은 다르다.

     

    component는 props, state, context가 바뀔때마다 재평가된다. 그다음 react가 컴포넌트 함수를 실행한다.

    반면, real DOM은 react가 컴포넌트의 이전 state와 현재 state를 비교한 뒤 차이점이 있을 때만 업데이트 된다.

    즉, React의 구조는 가상DOM으로 이전과 현재 snapshot을 가상으로 비교한 뒤, 둘의 차이점을 realDOM에 보내는 것이다.

     

     

     

     

     

     

    [ 예시 ]

    더보기

     

    toggle 버튼 클릭하면 state가 변경되면서 단락이 생기는 구조.

     

    import React, { useState } from 'react';
    import Button from './components/UI/Button/Button';
    import './App.css';
    
    function App() {
      const [show, setShow] = useState(false);
      const togglehandler = () => {
        setShow((prev) => !prev);
      };
      console.log('app running');
      return (
        <div className="app">
          <h1>Hi there!</h1>
          {show && <p>New!</p>}
          <Button onClick={togglehandler}>Toggle</Button>
        </div>
      );
    }
    
    export default App;

     

    toggle btn - true로 단락이 생성되면 state가 변경되어서 app 컴포넌트의 모든 state도 바뀌고 재실행되고 재평가된다.

    console창에서 버튼 클릭시마다 출력

     

    realDOM에서는 바뀐 <p>태그 부분만 반짝이면서 변화가 생긴다.

     

     

     

     

     

     


     

     

     

     

     

    React.memo

     

     

    component 재평가 시, 부모 컴포넌트가 렌더링되면 자식 컴포넌트는 무조건 같이 렌더링이 된다.

    이런 불필요한 렌더링을 방지하고, component를 필요한 특정상황에서만 재실행시키면서

    React의 component를 최적화하여 성능을 향상시킬 때 React.memo를 사용한다.

     

    React.memo는 인자로 넣은 컴포넌트에 어떤 props가 들어가는지 확인하고, 

    이 컴포넌트가 바뀔때마다 새 props값이랑 이전 props값을 비교한다.

    그리고 props 값이 변했을때만 컴포넌트를 재실행, 재평가한다.

    (부모 컴포넌트가 바뀌었지만 prop는 바귀지 않은 경우, 컴포넌트가 재실행되지않는다)

     

     

     

    [ 사용법 ] 

    export문에 React.memo로 감싸준다.

    import Para from './Para';
    
    const Demo = (props) => {
      return <Para>{props.show ? 'this is new' : ''}</Para>;
    };
    
    export default React.memo(Demo);

     

     

    🤷🏻‍♀️ 언제 사용하나요?

       : 렌더링이 자주되고 보통 같은 props로 re-rendering이 일어날 때 사용하면 효과적이다.

         ( 주로 부모 컴포넌트의 렌더링에 의해서 자식 컴포넌트가 렌더링이 일어날 때 - 부모컴포넌트에 써주면 자식은 안써줘도 됨)

         ➡️ 소규모 앱은 component tree가 작아서 추가할 필요가 없는 반면, 

             대규모 앱은 불필요한 재평가의 방지를 위해 component tree 가지를 잘라내는게 좋다.

             ( 모든 component에 말고, 뿌리를 골라서 자식 component의 가지를 모두 잘라내는 것이 효율적이다. )

     

     

     

     

     

     

     

    💥 주의

        예시 ) 동일한 false값을 내는 props이지만 함수로 되있을 경우 이전과 후를 다른 props값으로 인식하여 memo가 작동하지않는다.

     

      ➡️  아래의 예시에서 false와 true로 바뀌는 (prev)=> !prev 함수에서 문제가 발생한다.

           memo를 사용했지만 Button 컴포넌트가 렌더링이 되는데 이는 리엑트의 문제가아니라 자바스크립트의 문제이다.

     

    function App() {
      const [show, setShow] = useState(false);
      
      const togglehandler = () => {
        setShow((prev) => !prev);
      };
    
      return (
        <div className="app">
          <h1>Hi there!</h1>
          {show && <p>New!</p>}
          <Button onClick={togglehandler}>Toggle</Button>
        </div>
      );
    }
    //Button.js
    
    const Button = (props) => {
      console.log('Button Running')
      return (
        <button
          type={props.type || 'button'}
          className={`${classes.button} ${props.className}`}
          onClick={props.onClick}
          disabled={props.disabled}
        >
          {props.children}
        </button>
      );
    };
    
    export default React.memo(Button);

     

     

     

      ➡️ JavaScript에서 함수는 object일 뿐이다. 두 객체의 내용이 같더라도 자바스크립트에서는 다르게 인식한다.

            React.memo는 자바스크립트 작동원리에따라 값이 바뀌었다고 인식하게되는 문제가 발생한다.

           이를 해결하기 위해서 useCallback을 사용한다.

     

    자바스크립트의 기본값과 참조값 참고!

     

     

     

     

     


     

     

     

     

     

     

     

    useCallback

     

     

    useCallback은 기본적으로 컴포넌트 실행 전반에 걸친 함수를 저장(memoization)하는 hook이다.

     

     

    [ 사용방법 ] 

    첫번째 인자로 넘어온 함수를, 두번째 인자로 넘어온 배열 내의 값이 변경될 때까지 저장해놓고 재사용할 수 있게 해준다.

    const memoizedCallback = useCallback(함수, 배열);

     

    (예시)

    function App() {
    
      const togglehandler = useCallback(() => {
        setShow((prev) => !prev);
      }, []);
      
      return (
        <div className="app">
          <Button onClick={togglehandler}>Toggle</Button>
        </div>
        ...

     

    적용 전
    useCallback 적용 후

     

     

     

     

     

    [작동원리]

    useCallback은 우리가 선택한 함수를 react 내부 저장소 어딘가에 저장하고,

    컴포넌트 함수가 실행될 때 항상 동일한 함수 객체를 재사용한다.

     

    사실 컴포넌트가 랜더링 될 때마다 함수를 새로 선언하는 것은 자바스크립트가 브라우저에서 실행되는 속도를 생각해보면

    성능상 큰 문제가 되지않는다.

     따라서 단순히 컴포넌트 내에서 함수를 반복해서 생성하지않기 위하여 useCallback을 사용하는 것은 큰 의미가 없다.

    의미있는 성능향상을 위해서는 먼저 자바스크립트의 함수간의 동등함의 결정을 알아야한다.

     

     

     

    객체 1과 2가 있지만 이것은 자바스크립트에서 같은것이 아니다.

    하지만 객체 1과 2를 같다고 먼저 선언해주고 똑같은 메모리 위치를 가리켜주면 1과2는 같다고 나온다. 

    이것을 useCallback이 해주는것!

     

     

     

     

     

     

    [ 주의 ]

    useCallback 함수 사용시 dependency 배열을 넣어줘야 한다.

     

    자바스크립트에서 함수는 클로저이다. 즉, 해당 환경에서 사용할 수 있는 값에대해 클로저를 형성한다.

    함수가 정의되면 기본적으로 거기서 사용하는 모든 변수를 잠근다,

     

     

     

     

     

     

     

     


     

     

     

     

     

     

     

    요약

     

     

    React는 component단위이다.

    component안에서 state, props, context로 작업되는데 이는 모두 state변경으로 귀결되어서 component를 변경하고,

    이 component는 application 일부에 영향을주는 데이터를 변경한다.

     

    component에서 state가 변경될때마다 해당 component는 재평가되는데,

    이는 즉 component의 모든 코드들이 다시 실행되고 새로운 출력값을 얻게되는 것이다.

    이 때 영향을 받는 모든 component에서 재평가가 일어나고, 최신평가의 결과를 가지고 이전평가와 비교한다.

    확인된 모든 변경사항, 차이점을 React DOM에게 전달하고 rendering되어, 브라우져의 real DOM에 적용되게 된다. (변경사항만)

     

    이러한 재평가때 해당 component의 자식 component까지 재실행되는데 이러한 불필요한 재실행을 피하기위해서

    React.memo를 사용하여 props가 실제로 변경되거나 새로운 값을 얻은 경우에만 이 component를 다시 평가하게 할 수 있다.

     

    이 때 compoenent안의 함수들에게는 적용이 안될 수 있는데 이럴 때 useCallback을 사용하여 React.memo가 작동하게 한다.

    useCallback을 사용하면 React에게 함수를 저장하고, 지정한 특정 dependency가 변경되지 않는 한 주변 함수가 다시 실행되어도

    그 함수를 재실행 하지마라고 설정이 가능하다.

     

     

     

     

     

     

     

     

    반응형

    '• React' 카테고리의 다른 글

    [React] React Hook Form  (0) 2022.05.08
    [Redux] Redux & Redux-toolkit  (0) 2022.02.13
    [React] Carousel 구현  (0) 2022.01.18
    [React] useRef / useMemo  (0) 2021.12.23
    [React] Formik / Yup  (0) 2021.11.22