티스토리 뷰

codestates/section4

Unit4 - [React] 심화

나아눙 2023. 3. 22. 17:18

React에는 Virtual DOM이라고 하는 가상의 DOM 객체가 있다.

Real DOM의 가벼운 사본과 같다.

 

Real DOM (DOM)

DOM은 Document Object Model의 약자

DOM은 브라우저가 HTML 문서를 조작할 수 있도록 트리 구조화한 객체 모델

querySelector, addEventListener와 같은 DOM API를 통해 문서의 요소들을 조작

 

 

브라우저 렌더링 과정에서

브라우저는 렌더링 과정에서 DOM 트리와 CSSOM 트리를 토대로 Render 트리를 생성하고

각 요소가 배치될 공간을 계산(Layout)한 뒤 이를 화면에 그려낸다.

이때 DOM이 변경되면 DOM트리를 재건축하게 되는데 

레이아웃 재연산을 수행하는 리플로우, 그리고 이를 화면에 그려내는 Repaint 과정을 거친다.

잦은 리플로우 발생으로 성능이 떨어진게 된다.(비용 증가)

=>비효율적인 업데이트가 빈번하게 발생

 

뀐 부분만 비교해서 그 부분만 렌더링하기위해 Virtual DOM이 등장

 

React의 가상 DOM 객체는 자바스크립트 객체로 이루어져 있기 때문에

실제 DOM 객체와 동일한 속성을 가지고 있음에도 가벼운 사본이라고 할수 있다.

 

가상 DOM 객체는 이전 상태 가상DOM과 비교하기 위해 사용된다.

리액트에서 컴포넌트의 상태나 속성이 변경될 때마다 새로 생성

변경된 부분만 실제 DOM에 반영한다. 

=>재조정한다.

 

여러개의 상태 변화가 있을 경우

일괄적으로 한번에 업데이트 한다.

 

React는 Diffing 알고리즘을 사용하여 변경된 부분을 발견

 

React Diffing Algorithm

React는 기존의 가상 DOM 트리와 새롭게 변경된 가상 DOM 트리를 비교할 때,

트리의 레벨 순서대로 순회하는 방식으로 탐색

 

같은 레벨끼리 비교한다.(BFS의 일종)

 

다른 타입의 DOM엘리먼트인 경우

//부모 태그가 div에서 span으로 바뀐다.
<span>
	<Counter />
</span>

기존 트리를 버리고 새로운 트리를 구축 -> 이전의 DOM노드들은 전부 파괴

새로운 <Counter/> 컴포넌트가 실행되면서 가 기존의 컴포넌트는 해제, 가지고 있던 기존 state도 파괴

 

같은 타입의 DOM엘리먼트인 경우

irtual DOM 내부의 프로퍼티만 수정한 뒤, 모든 노드에 걸친 업데이트가 끝나면

한번 실제 DOM으로의 렌더링

1.

<div className="a" title="stuff" />

//className만 바뀜
<div className="bcr" title="stuff" />

2. 

<div style={{color: 'pink', fontWeight: 'bold"}} title="two" />
//color만 바뀜
<div style={{color: 'green', fontWeight: 'bold"}} title="two" />

노드들 밑의 자식들을 순차적으로 동시에 순회하면서 차이가 발견할때마다 변경 => 재귀적으로 처리한다.

자식 엘리먼트의 재귀적 처리

//자식 엘리먼트 끝에 새로운 자식 엘리먼트를 추가
<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li>
</ul>

자식 노드를 순차적으로 위에서 아래로 비교하면서 바뀐점을 찾는다.

 

하지만

//자식 엘리먼트를 처음에 추가합니다.
<ul>
  <li>one</li>
  <li>two</li>
  <li>three</li>
</ul>

React는 리스트 전체가 바뀌었다고 생각=>전부 버리고 새롭게 렌더링

해결방안 => key 라는 속성을 지원

 

키(key)

key속성에 유니크한 값을 부여

배열의 인덱스를 key로 사용할수 있지만 비효율적 -> 배열은 다르게 정렬되어도 인덱스는 그대로 유지한다.

 

Component와 Hook

React에는 다양한 Hook이 존재

Hook은 함수 컴포넌트에서 사용하는 메소드

 

동작방식을 정확히 이해 불가하므로 Class Component에서 Function Componet 넘어감

 

Hook이란?

함수형 컴포넌트에서 상태 값 및 다른 여러 기능을 사용하기 편리하게 해주는 메소드를 의미

 

리액트 함수의 최상위에서만 호출

컴포넌트 안에는 useState나 useEffect 같은 Hook들이 여러 번 사용될 수 있는데,

React는 이 Hook을 호출되는 순서대로 저장

 

오직 리액트 함수 내에서만 사용

Hook은 React의 함수 컴포넌트 내에서 사용한다.

 

컴포넌트는 기본적으로 상태가 변경되거나 부모 컴포넌트가 렌더링이 될 때마다 리렌더링을 하는 구조

=>잦은 리렌더링은 성능에 좋지 않다.=> 렌더링 최적화를 위한 useCallback 과 useMemo hook

useMemo

특정 값(value)를 재사용하고자 할 때 사용하는 Hook

import { useMemo } from "react";

function Calculator({value}){

	const result = useMemo(() => calculate(value), [value]);

	return <>
      <div>
					{result}
      </div>
  </>;
}

value값이 동일할 경우에는 이전 렌더링의 value값을 그대로 재활용

 

메모이제이션(Memoization) 개념과 긴밀한 관계

기존에 수행한 연산의 결과값을 메모리에 저장을 해두고, 동일한 입력이 들어오면 재활용하는 프로그래밍 기법

 

실습

import React, { useState, useMemo } from "react";
import "./styles.css";
import { add } from "./add";

export default function App() {
  const [name, setName] = useState("");
  const [val1, setVal1] = useState(0);
  const [val2, setVal2] = useState(0);
  
  const answer = useMemo(() => add(val1, val2), [val1, val2]);

  return (
    <div>
      <input
        className="name-input"
        placeholder="이름을 입력해주세요"
        value={name}
        type="text"
        onChange={(e) => setName(e.target.value)}
      />
      <input
        className="value-input"
        placeholder="숫자를 입력해주세요"
        value={val1}
        type="number"
        onChange={(e) => setVal1(Number(e.target.value))}
      />
      <input
        className="value-input"
        placeholder="숫자를 입력해주세요"
        value={val2}
        type="number"
        onChange={(e) => setVal2(Number(e.target.value))}
      />
      <div>{answer}</div>
    </div>
  );
}

useMemo의 첫 번째 매개변수에는 answer 값을 계산하는 함수를 전달하고,

두 번째 매개변수에는 answer 값을 계산하는 데 필요한 의존성 배열을 전달

. 의존성 배열에는 val1과 val2를 포함시키므로 val1이나 val2가 변경될 때만 answer 값을 재계산

 

useCallback

useMemo는 값의 재사용을 위해 사용하는 Hook이라면,

useCallback은 함수의 재사용을 위해 사용하는 Hook

import React, { useCallback } from "react";

function Calculator({x, y}){

	const add = useCallback(() => x + y, [x, y]);

	return <>
      <div>
					{add()}
      </div>
  </>;
}

 

자식 컴포넌트의 props로 함수를 전달해줄 때 이 useCallback을 사용

useCallback은 참조 동등성에 의존

useCallback을 이용해 함수 자체를 저장해서 다시 사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용한다.

 

실습

import { useState, useCallback } from "react";
import "./styles.css";
import List from "./List";

export default function App() {
  const [input, setInput] = useState(1);
  const [light, setLight] = useState(true);

  const theme = {
    backgroundColor: light ? "White" : "grey",
    color: light ? "grey" : "white"
  };

  const getItems = useCallback(() => {
    return [input + 10, input + 100];
  }, [input]);

  const handleChange = (event) => {
    if (Number(event.target.value)) {
      setInput(Number(event.target.value));
    }
  };

  return (
    <>
      <div style={theme} className="wall-paper">
        <input
          type="number"
          className="input"
          value={input}
          onChange={handleChange}
        />
        <button
          className={(light ? "light" : "dark") + " button"}
          onClick={() => setLight((prevLight) => !prevLight)}
        >
          {light ? "dark mode" : "light mode"}
        </button>
        <List getItems={getItems} />
      </div>
    </>
  );
}

Custom Hooks

반복되는 로직을 함수로 뽑아내어 재사용

여러 url을 fetch할 때,

 

여러 input에 의한 상태 변경 등

const useFetch = ( initialUrl:string ) => {
	const [url, setUrl] = useState(initialUrl);
	const [value, setValue] = useState('');

	const fetchData = () => axios.get(url).then(({data}) => setValue(data));	

	useEffect(() => {
		fetchData();
	},[url]);

	return [value];
};

export default useFetch;

훅은 초기 URL 값을 받아들이고, 그 URL에서 데이터를 가져와 상태를 업데이트

fetchData 함수는 현재 URL에서 데이터를 가져오는 함수

이 함수는 axios 라이브러리를 사용하여 GET 요청을 보내고, 응답으로 받은 데이터를 setValue 함수를 사용하여 value 상태로 업데이트한다.

마지막으로 useEffect 훅을 사용하여 fetchData 함수를 호출

이 훅은 url 상태가 변경될 때마다 실행

 

반복되는 로직을

동일한 함수에서 작동하게 하고 싶을 때 커스텀 훅을 주로 사용한다.

 

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  useEffect(() => {
    function handleStatusChange(status) {
      setIsOnline(status.isOnline);
    }

    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

useFriendStatus Hook을 두 컴포넌트에 적용

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

로직만 공유할 뿐, state는 컴포넌트 내에서 독립적으로 정의

import { useState, useCallback } from 'react';

function useInputs(initialForm) {
  const [form, setForm] = useState(initialForm);
  // change
  const onChange = useCallback(e => {
    const { name, value } = e.target;
    setForm(form => ({ ...form, [name]: value }));
  }, []);
  const reset = useCallback(() => setForm(initialForm), [initialForm]);
  return [form, onChange, reset];
}

export default useInputs;

 

  1. useCallback을 사용하여 onChange 함수를 최적화
  2. . 새로운 이벤트 객체가 들어왔을 때, 그 안에서 name과 value를 추출하여 기존의 form 객체를 복사한 후, [name] 프로퍼티를 가진 값을 value로 업데이트 이때, 함수 내에서 이전 상태인 form 객체를 참조하는 것이 아니라, 함수 인자로 전달되는 form 객체를 참조하여 업데이트하게 됩니다.

React18 버전 이후 index.js

import { createRoot } from "react-dom/client";

const rootElement = document.getElementById("root");
const root = createRoot(rootElement);

root.render(
    <App />
);

 

코드 분할 (Code Spliting)

모던 웹으로 발전 -> DOM을 다루는 정도가 정교 , JavaScript 코드 자체가 방대하고 무거워짐

번들이 거대해지는 것을 방지하기 위해 번들을 물리적으로 나눈다.

 

코드 분할은 런타임 시 여러 번들을 동적으로 만들고 불러오는 것으로, Webpack, Rollup과 같은 번들러가 지원하는 기능

 

lodash는 배열, 숫자, 객체, 문자열을 사용할 때 반복적인 작업 같은 것을 할 시 사용하기에 좋은 라이브러리

mport _ from 'lodash';

...

_.find([]);

대신 

import find from 'lodash/find';

find([]);

 

React에서의 코드 분할

컴포넌트를 한번에 불러오면 첫화면 렌더링 시간이 너무 오래 걸린다.

사용하지 컴포넌트는 나중에 불러오기 위해 코드 분할을 하였다.

 

 

1. 동적 불러오기

Static Import

import moduleA from "library";

form.addEventListener("submit", e => {
  e.preventDefault();
  someFunction();
});

const someFunction = () => {
 
}

import 구문은 문서의 상위에 위치해야 했고, 블록문 안에서는 위치할 수 없는 제약 사항이 있다.

 

Dynamic Import

form.addEventListener("submit", e => {
  e.preventDefault();
  import('library.moduleA')
  //중간에 불러옴
    .then(module => module.default)
    .then(someFunction())
    .catch(handleError());
});

const someFunction = () => {
    /* moduleA를 여기서 사용합니다. */
}

비교를 하면

import 지시자를 사용하는 경우 모든 모듈을 최상위에서 불러오고 초기화하는 데 시간이 걸리지만,

모든 모듈이 로드되면 로딩 시간이 적게 든다.

반면 import() 구문을 사용하는 경우, 필요한 모듈만 로드되므로 초기 로딩 시간이 적지만

모듈이 동적으로 불러와지기 때문에 일부 지연(페이지 이동하는 과정에서 로딩화면이 보여짐)이 발생한다.

 

dynamic import는 React.lazy 와 함께 사용

React.lazy()

React.lazy 함수를 사용하면 dynamic import를 사용해 컴포넌트를 렌더링

React는 SPA다. 즉 , 초기 렌더링 시 사용하지 않는 컴포넌트까지 불러온다.

 

React.lazy로 감싼 컴포넌트는 단독으로 쓰일 수는 없고, React.suspense 컴포넌트의 하위에서 렌더링

import Component from './Component';

/* React.lazy로 dynamic import를 감쌉니다. */
const Component = React.lazy(() => import('./Component'));

React.Suspense

Suspense는 아직 렌더링이 준비되지 않은 컴포넌트가 있을 때 로딩 화면을 보여주고,

로딩이 완료되면 렌더링이 준비된 컴포넌트를 보여주는 기능

import { Suspense } from 'react';

const OneComponent = React.lazy(() => import('./OneComponent'));
const TwoComponent = React.lazy(() => import('./TwoComponent'));

function MyComponent() {
  return (
    <div>
			{/* 이런 식으로 React.lazy로 감싼 컴포넌트를 Suspense 컴포넌트의 하위에 렌더링 */}
      <Suspense fallback={<div>Loading...</div>}>
				{/* Suspense 컴포넌트 하위에 여러 개의 lazy 컴포넌트를 렌더링. */}
        <OneComponent />
		<TwoComponent />
      </Suspense>
    </div>
  );
}

React.lazy와 Suspense의 적용

import { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </Suspense>
  </Router>
);

 

 

'codestates > section4' 카테고리의 다른 글

Unit5 - [컴퓨터 공학] 기초  (0) 2023.03.27
custom hook실습  (0) 2023.03.23
조 발표 순서 계산 알고리즘  (0) 2023.03.21
웹팩 과제  (0) 2023.03.20
웹팩 튜토리얼  (0) 2023.03.19
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2025/12   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함